feat: Enhance folder upload handling and filename sanitation

- Added support for checking webkitRelativePath in folder uploads, alerting users if their browser does not support this feature.
- Introduced sanitizePathPreserveDirs function to sanitize filenames while preserving directory structure.
- Updated upload route to utilize the new sanitation function and ensure consistent folder naming during uploads.

Fixes #45
This commit is contained in:
greirson
2025-05-02 14:38:28 -07:00
parent 8f4b2ea873
commit ccd06f92bb
3 changed files with 33 additions and 14 deletions

View File

@@ -516,6 +516,15 @@
// Reset the input to allow selecting the same folder again
const input = e.target;
files = [...input.files];
// Check for webkitRelativePath support
const missingRelPath = files.some(f => !('webkitRelativePath' in f) || !f.webkitRelativePath);
if (missingRelPath) {
alert('Your browser does not support folder uploads with structure. Please use a modern browser like Chrome or Edge.');
files = [];
updateFileList();
input.value = '';
return;
}
console.log('Folder selection files:', files.map(f => ({
name: f.name,
path: f.webkitRelativePath,

View File

@@ -10,7 +10,7 @@ const crypto = require('crypto');
const path = require('path');
const { config } = require('../config');
const logger = require('../utils/logger');
const { getUniqueFilePath, getUniqueFolderPath, sanitizeFilename } = require('../utils/fileUtils');
const { getUniqueFilePath, getUniqueFolderPath, sanitizeFilename, sanitizePathPreserveDirs } = require('../utils/fileUtils');
const { sendNotification } = require('../services/notifications');
const fs = require('fs');
const { cleanupIncompleteUploads } = require('../utils/cleanup');
@@ -174,8 +174,8 @@ router.post('/init', async (req, res) => {
// Update batch activity
batchActivity.set(batchId, Date.now());
// Sanitize filename and convert to forward slashes
const sanitizedFilename = sanitizeFilename(filename);
// Sanitize filename and convert to forward slashes, preserving directory structure
const sanitizedFilename = sanitizePathPreserveDirs(filename);
const safeFilename = path.normalize(sanitizedFilename)
.replace(/^(\.\.(\/|\\|$))+/, '')
.replace(/\\/g, '/')
@@ -205,35 +205,36 @@ router.post('/init', async (req, res) => {
const pathParts = safeFilename.split('/').filter(Boolean); // Remove empty parts
if (pathParts.length > 1) {
// Handle files within folders
// The first part is the root folder name from the client
const originalFolderName = pathParts[0];
const folderPath = path.join(config.uploadDir, originalFolderName);
// Always use a consistent mapping for this batch to avoid collisions
// This ensures all files in the batch go into the same (possibly renamed) root folder
let newFolderName = folderMappings.get(`${originalFolderName}-${batchId}`);
const folderPath = path.join(config.uploadDir, newFolderName || originalFolderName);
if (!newFolderName) {
try {
// First ensure parent directories exist
// Ensure parent directories exist
await fs.promises.mkdir(path.dirname(folderPath), { recursive: true });
// Then try to create the target folder
// Try to create the target folder
await fs.promises.mkdir(folderPath, { recursive: false });
newFolderName = originalFolderName;
} catch (err) {
if (err.code === 'EEXIST') {
// If the folder exists, generate a unique folder name for this batch
const uniqueFolderPath = await getUniqueFolderPath(folderPath);
newFolderName = path.basename(uniqueFolderPath);
logger.info(`Folder "${originalFolderName}" exists, using "${newFolderName}"`);
logger.info(`Folder "${originalFolderName}" exists, using "${newFolderName}" for batch ${batchId}`);
} else {
throw err;
}
}
// Store the mapping for this batch
folderMappings.set(`${originalFolderName}-${batchId}`, newFolderName);
}
// Always apply the mapping for this batch
pathParts[0] = newFolderName;
filePath = path.join(config.uploadDir, ...pathParts);
// Ensure all parent directories exist
// Ensure all parent directories exist for the file
await fs.promises.mkdir(path.dirname(filePath), { recursive: true });
}

View File

@@ -165,11 +165,20 @@ function sanitizeFilename(fileName) {
return sanitized;
}
function sanitizePathPreserveDirs(filePath) {
// Split on forward slashes, sanitize each part, and rejoin
return filePath
.split('/')
.map(part => sanitizeFilename(part))
.join('/');
}
module.exports = {
formatFileSize,
calculateDirectorySize,
ensureDirectoryExists,
getUniqueFilePath,
getUniqueFolderPath,
sanitizeFilename
sanitizeFilename,
sanitizePathPreserveDirs
};