mirror of
https://github.com/DumbWareio/DumbDrop.git
synced 2025-10-25 00:53:53 +00:00
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:
@@ -516,6 +516,15 @@
|
|||||||
// Reset the input to allow selecting the same folder again
|
// Reset the input to allow selecting the same folder again
|
||||||
const input = e.target;
|
const input = e.target;
|
||||||
files = [...input.files];
|
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 => ({
|
console.log('Folder selection files:', files.map(f => ({
|
||||||
name: f.name,
|
name: f.name,
|
||||||
path: f.webkitRelativePath,
|
path: f.webkitRelativePath,
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ const crypto = require('crypto');
|
|||||||
const path = require('path');
|
const path = require('path');
|
||||||
const { config } = require('../config');
|
const { config } = require('../config');
|
||||||
const logger = require('../utils/logger');
|
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 { sendNotification } = require('../services/notifications');
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const { cleanupIncompleteUploads } = require('../utils/cleanup');
|
const { cleanupIncompleteUploads } = require('../utils/cleanup');
|
||||||
@@ -174,8 +174,8 @@ router.post('/init', async (req, res) => {
|
|||||||
// Update batch activity
|
// Update batch activity
|
||||||
batchActivity.set(batchId, Date.now());
|
batchActivity.set(batchId, Date.now());
|
||||||
|
|
||||||
// Sanitize filename and convert to forward slashes
|
// Sanitize filename and convert to forward slashes, preserving directory structure
|
||||||
const sanitizedFilename = sanitizeFilename(filename);
|
const sanitizedFilename = sanitizePathPreserveDirs(filename);
|
||||||
const safeFilename = path.normalize(sanitizedFilename)
|
const safeFilename = path.normalize(sanitizedFilename)
|
||||||
.replace(/^(\.\.(\/|\\|$))+/, '')
|
.replace(/^(\.\.(\/|\\|$))+/, '')
|
||||||
.replace(/\\/g, '/')
|
.replace(/\\/g, '/')
|
||||||
@@ -205,35 +205,36 @@ router.post('/init', async (req, res) => {
|
|||||||
const pathParts = safeFilename.split('/').filter(Boolean); // Remove empty parts
|
const pathParts = safeFilename.split('/').filter(Boolean); // Remove empty parts
|
||||||
|
|
||||||
if (pathParts.length > 1) {
|
if (pathParts.length > 1) {
|
||||||
// Handle files within folders
|
// The first part is the root folder name from the client
|
||||||
const originalFolderName = pathParts[0];
|
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}`);
|
let newFolderName = folderMappings.get(`${originalFolderName}-${batchId}`);
|
||||||
|
const folderPath = path.join(config.uploadDir, newFolderName || originalFolderName);
|
||||||
if (!newFolderName) {
|
if (!newFolderName) {
|
||||||
try {
|
try {
|
||||||
// First ensure parent directories exist
|
// Ensure parent directories exist
|
||||||
await fs.promises.mkdir(path.dirname(folderPath), { recursive: true });
|
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 });
|
await fs.promises.mkdir(folderPath, { recursive: false });
|
||||||
newFolderName = originalFolderName;
|
newFolderName = originalFolderName;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (err.code === 'EEXIST') {
|
if (err.code === 'EEXIST') {
|
||||||
|
// If the folder exists, generate a unique folder name for this batch
|
||||||
const uniqueFolderPath = await getUniqueFolderPath(folderPath);
|
const uniqueFolderPath = await getUniqueFolderPath(folderPath);
|
||||||
newFolderName = path.basename(uniqueFolderPath);
|
newFolderName = path.basename(uniqueFolderPath);
|
||||||
logger.info(`Folder "${originalFolderName}" exists, using "${newFolderName}"`);
|
logger.info(`Folder "${originalFolderName}" exists, using "${newFolderName}" for batch ${batchId}`);
|
||||||
} else {
|
} else {
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Store the mapping for this batch
|
||||||
folderMappings.set(`${originalFolderName}-${batchId}`, newFolderName);
|
folderMappings.set(`${originalFolderName}-${batchId}`, newFolderName);
|
||||||
}
|
}
|
||||||
|
// Always apply the mapping for this batch
|
||||||
pathParts[0] = newFolderName;
|
pathParts[0] = newFolderName;
|
||||||
filePath = path.join(config.uploadDir, ...pathParts);
|
filePath = path.join(config.uploadDir, ...pathParts);
|
||||||
|
// Ensure all parent directories exist for the file
|
||||||
// Ensure all parent directories exist
|
|
||||||
await fs.promises.mkdir(path.dirname(filePath), { recursive: true });
|
await fs.promises.mkdir(path.dirname(filePath), { recursive: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -165,11 +165,20 @@ function sanitizeFilename(fileName) {
|
|||||||
return sanitized;
|
return sanitized;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function sanitizePathPreserveDirs(filePath) {
|
||||||
|
// Split on forward slashes, sanitize each part, and rejoin
|
||||||
|
return filePath
|
||||||
|
.split('/')
|
||||||
|
.map(part => sanitizeFilename(part))
|
||||||
|
.join('/');
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
formatFileSize,
|
formatFileSize,
|
||||||
calculateDirectorySize,
|
calculateDirectorySize,
|
||||||
ensureDirectoryExists,
|
ensureDirectoryExists,
|
||||||
getUniqueFilePath,
|
getUniqueFilePath,
|
||||||
getUniqueFolderPath,
|
getUniqueFolderPath,
|
||||||
sanitizeFilename
|
sanitizeFilename,
|
||||||
|
sanitizePathPreserveDirs
|
||||||
};
|
};
|
||||||
Reference in New Issue
Block a user