🐛 修复特殊字符文件名的处理问题
- 添加decodeHtmlEntities函数解码HTML实体 - 在rename/mkdir/folder-info/delete接口中解码文件名和路径 - 删除操作支持多候选路径,处理二次编码情况 - 移除sanitizeInput中对反引号的转义 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -206,8 +206,7 @@ function sanitizeInput(str) {
|
|||||||
'<': '<',
|
'<': '<',
|
||||||
'>': '>',
|
'>': '>',
|
||||||
'"': '"',
|
'"': '"',
|
||||||
"'": ''',
|
"'": '''
|
||||||
'`': '`'
|
|
||||||
};
|
};
|
||||||
return map[char];
|
return map[char];
|
||||||
});
|
});
|
||||||
@@ -221,6 +220,46 @@ function sanitizeInput(str) {
|
|||||||
return sanitized;
|
return sanitized;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 将 HTML 实体解码为原始字符(用于文件名/路径字段)
|
||||||
|
function decodeHtmlEntities(str) {
|
||||||
|
if (typeof str !== 'string') return str;
|
||||||
|
|
||||||
|
// 支持常见实体和数字实体(含多次嵌套,如 &#x60;)
|
||||||
|
const entityMap = {
|
||||||
|
amp: '&',
|
||||||
|
lt: '<',
|
||||||
|
gt: '>',
|
||||||
|
quot: '"',
|
||||||
|
apos: "'",
|
||||||
|
'#x27': "'",
|
||||||
|
'#x2F': '/',
|
||||||
|
'#x60': '`'
|
||||||
|
};
|
||||||
|
|
||||||
|
const decodeOnce = (input) =>
|
||||||
|
input.replace(/&(#x[0-9a-fA-F]+|#\d+|[a-zA-Z]+);/g, (match, code) => {
|
||||||
|
if (code[0] === '#') {
|
||||||
|
const isHex = code[1]?.toLowerCase() === 'x';
|
||||||
|
const num = isHex ? parseInt(code.slice(2), 16) : parseInt(code.slice(1), 10);
|
||||||
|
if (!Number.isNaN(num)) {
|
||||||
|
return String.fromCharCode(num);
|
||||||
|
}
|
||||||
|
return match;
|
||||||
|
}
|
||||||
|
const mapped = entityMap[code];
|
||||||
|
return mapped !== undefined ? mapped : match;
|
||||||
|
});
|
||||||
|
|
||||||
|
let output = str;
|
||||||
|
let decoded = decodeOnce(output);
|
||||||
|
// 处理嵌套实体(如 &#x60;),直到稳定
|
||||||
|
while (decoded !== output) {
|
||||||
|
output = decoded;
|
||||||
|
decoded = decodeOnce(output);
|
||||||
|
}
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
// HTML转义(用于模板输出)
|
// HTML转义(用于模板输出)
|
||||||
function escapeHtml(str) {
|
function escapeHtml(str) {
|
||||||
if (typeof str !== 'string') return str;
|
if (typeof str !== 'string') return str;
|
||||||
@@ -2275,7 +2314,9 @@ app.get('/api/files', authMiddleware, async (req, res) => {
|
|||||||
|
|
||||||
// 重命名文件
|
// 重命名文件
|
||||||
app.post('/api/files/rename', authMiddleware, async (req, res) => {
|
app.post('/api/files/rename', authMiddleware, async (req, res) => {
|
||||||
const { oldName, newName, path } = req.body;
|
const oldName = decodeHtmlEntities(req.body.oldName);
|
||||||
|
const newName = decodeHtmlEntities(req.body.newName);
|
||||||
|
const path = decodeHtmlEntities(req.body.path) || '/';
|
||||||
let storage;
|
let storage;
|
||||||
|
|
||||||
if (!oldName || !newName) {
|
if (!oldName || !newName) {
|
||||||
@@ -2313,7 +2354,8 @@ app.post('/api/files/rename', authMiddleware, async (req, res) => {
|
|||||||
|
|
||||||
// 创建文件夹
|
// 创建文件夹
|
||||||
app.post('/api/files/mkdir', authMiddleware, async (req, res) => {
|
app.post('/api/files/mkdir', authMiddleware, async (req, res) => {
|
||||||
const { path, folderName } = req.body;
|
const path = decodeHtmlEntities(req.body.path) || '/';
|
||||||
|
const folderName = decodeHtmlEntities(req.body.folderName);
|
||||||
let storage;
|
let storage;
|
||||||
|
|
||||||
// 参数验证
|
// 参数验证
|
||||||
@@ -2380,7 +2422,8 @@ app.post('/api/files/mkdir', authMiddleware, async (req, res) => {
|
|||||||
|
|
||||||
// 获取文件夹详情(大小统计)
|
// 获取文件夹详情(大小统计)
|
||||||
app.post('/api/files/folder-info', authMiddleware, async (req, res) => {
|
app.post('/api/files/folder-info', authMiddleware, async (req, res) => {
|
||||||
const { path: dirPath, folderName } = req.body;
|
const dirPath = decodeHtmlEntities(req.body.path) || '/';
|
||||||
|
const folderName = decodeHtmlEntities(req.body.folderName);
|
||||||
let storage;
|
let storage;
|
||||||
|
|
||||||
if (!folderName) {
|
if (!folderName) {
|
||||||
@@ -2475,7 +2518,10 @@ app.post('/api/files/folder-info', authMiddleware, async (req, res) => {
|
|||||||
|
|
||||||
// 删除文件
|
// 删除文件
|
||||||
app.post('/api/files/delete', authMiddleware, async (req, res) => {
|
app.post('/api/files/delete', authMiddleware, async (req, res) => {
|
||||||
const { fileName, path } = req.body;
|
const rawFileName = req.body.fileName;
|
||||||
|
const rawPath = req.body.path;
|
||||||
|
const fileName = decodeHtmlEntities(rawFileName);
|
||||||
|
const path = decodeHtmlEntities(rawPath) || '/';
|
||||||
let storage;
|
let storage;
|
||||||
|
|
||||||
if (!fileName) {
|
if (!fileName) {
|
||||||
@@ -2491,9 +2537,41 @@ app.post('/api/files/delete', authMiddleware, async (req, res) => {
|
|||||||
const storageInterface = new StorageInterface(req.user);
|
const storageInterface = new StorageInterface(req.user);
|
||||||
storage = await storageInterface.connect();
|
storage = await storageInterface.connect();
|
||||||
|
|
||||||
const filePath = path === '/' ? `/${fileName}` : `${path}/${fileName}`;
|
const tried = new Set();
|
||||||
|
const candidates = [fileName];
|
||||||
|
|
||||||
await storage.delete(filePath);
|
// 兼容被二次编码的实体(如 &#x60; -> `)
|
||||||
|
if (typeof rawFileName === 'string') {
|
||||||
|
const entityName = rawFileName.replace(/&/g, '&');
|
||||||
|
if (entityName && !candidates.includes(entityName)) {
|
||||||
|
candidates.push(entityName);
|
||||||
|
}
|
||||||
|
if (rawFileName && !candidates.includes(rawFileName)) {
|
||||||
|
candidates.push(rawFileName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const pathsToDelete = candidates.map(name => (path === '/' ? `/${name}` : `${path}/${name}`));
|
||||||
|
|
||||||
|
try {
|
||||||
|
for (const targetPath of pathsToDelete) {
|
||||||
|
if (tried.has(targetPath)) continue;
|
||||||
|
tried.add(targetPath);
|
||||||
|
try {
|
||||||
|
await storage.delete(targetPath);
|
||||||
|
break;
|
||||||
|
} catch (err) {
|
||||||
|
if (err.code === 'ENOENT') {
|
||||||
|
// 尝试下一个候选路径
|
||||||
|
if (targetPath === pathsToDelete[pathsToDelete.length - 1]) throw err;
|
||||||
|
} else {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
success: true,
|
success: true,
|
||||||
|
|||||||
Reference in New Issue
Block a user