fix(upload): sanitize multipart filename for folder uploads (#121)

Browsers send the full relative path (e.g. 'Screenshots/file.png') as
the multipart filename when uploading folders via webkitRelativePath.
The File entity rejects names containing '/' or '\', causing all files
in a folder upload to fail with 'Invalid file name'.

Three fixes:
- Backend: strip path components from multipart filename in file_handler,
  keeping only the basename. Also prevents path-traversal attacks.
- Frontend (fileOperations.js): explicitly pass file.name as the third
  argument to FormData.append() in uploadFolderFiles() to override the
  browser's relative path.
- Frontend (ui.js): detect folder drops in drag-and-drop handlers by
  checking webkitRelativePath, and route them to uploadFolderFiles()
  instead of uploadFiles() so subfolders are created first.

Closes #121
This commit is contained in:
Dionisio
2026-02-16 17:58:50 +01:00
parent f70890e884
commit a4709426d9
4 changed files with 35 additions and 5 deletions
+18 -2
View File
@@ -291,7 +291,15 @@ const ui = {
e._oxiHandled = true; // Mark as handled for document-level fallback
dropzone.classList.remove('active');
if (e.dataTransfer.files.length > 0) {
fileOps.uploadFiles(e.dataTransfer.files);
// Detect folder drops: files from folder drops have webkitRelativePath set
const hasRelativePaths = Array.from(e.dataTransfer.files).some(
f => f.webkitRelativePath && f.webkitRelativePath.includes('/')
);
if (hasRelativePaths) {
fileOps.uploadFolderFiles(e.dataTransfer.files);
} else {
fileOps.uploadFiles(e.dataTransfer.files);
}
}
setTimeout(() => {
dropzone.style.display = 'none';
@@ -327,7 +335,15 @@ const ui = {
if (e._oxiHandled) return;
if (e.dataTransfer.files.length > 0) {
fileOps.uploadFiles(e.dataTransfer.files);
// Detect folder drops: files from folder drops have webkitRelativePath set
const hasRelativePaths = Array.from(e.dataTransfer.files).some(
f => f.webkitRelativePath && f.webkitRelativePath.includes('/')
);
if (hasRelativePaths) {
fileOps.uploadFolderFiles(e.dataTransfer.files);
} else {
fileOps.uploadFiles(e.dataTransfer.files);
}
}
setTimeout(() => {