From 11cc9f256261678cd43210b419d51593f853d8c5 Mon Sep 17 00:00:00 2001 From: Vito0912 <86927734+Vito0912@users.noreply.github.com> Date: Thu, 12 Jun 2025 12:05:32 +0200 Subject: [PATCH] filenames --- client/pages/upload/index.vue | 2 +- server/controllers/FileSystemController.js | 20 +++--- server/controllers/MiscController.js | 6 +- server/utils/fileUtils.js | 78 +++++++++++++--------- 4 files changed, 61 insertions(+), 45 deletions(-) diff --git a/client/pages/upload/index.vue b/client/pages/upload/index.vue index eef05b60..3612f422 100644 --- a/client/pages/upload/index.vue +++ b/client/pages/upload/index.vue @@ -360,7 +360,7 @@ export default { // uploading fails if path already exists for (const item of items) { const exists = await this.$axios - .$post(`/api/filesystem/pathexists`, { directory: item.directory, folderPath: this.selectedFolder.fullPath }) + .$post(`/api/filesystem/pathexists`, { directory: item.directory, folderPath: this.selectedFolder.fullPath, filenames: item.files.map((f) => f.name) }) .then((data) => { if (data.exists) { if (data.libraryItemTitle) { diff --git a/server/controllers/FileSystemController.js b/server/controllers/FileSystemController.js index 4d87dea0..53a3de88 100644 --- a/server/controllers/FileSystemController.js +++ b/server/controllers/FileSystemController.js @@ -89,10 +89,11 @@ class FileSystemController { return res.sendStatus(403) } - // filename - If fileName is provided, the check only returns true if the actual file exists, not just the directory + // filenames - If filenames is provided, the check only returns true if the actual files exist, not just the directory // allowBookFiles - If true, allows containing other book related files (e.g. .pdf, .epub, etc.) // allowAudioFiles - If true, allows containing other audio related files (e.g. .mp3, .m4b, etc.) - const { directory, folderPath, filename, allowBookFiles, allowAudioFiles } = req.body + const { directory, folderPath, filenames, allowBookFiles, allowAudioFiles } = req.body + if (!directory?.length || typeof directory !== 'string' || !folderPath?.length || typeof folderPath !== 'string') { Logger.error(`[FileSystemController] Invalid request body: ${JSON.stringify(req.body)}`) return res.status(400).json({ @@ -100,14 +101,15 @@ class FileSystemController { }) } - if (filename && typeof filename !== 'string') { - Logger.error(`[FileSystemController] Invalid filename in request body: ${JSON.stringify(req.body)}`) + // Validate filenames: must be undefined or an array of non-empty strings + if (filenames !== undefined && (!Array.isArray(filenames) || filenames.some((f) => typeof f !== 'string' || f.trim().length === 0))) { + Logger.error(`[FileSystemController] Invalid filenames in request body: ${JSON.stringify(req.body)}`) return res.status(400).json({ - error: 'Invalid filename' + error: 'Invalid filenames' }) } - if (allowBookFiles && typeof allowBookFiles !== 'boolean' || allowAudioFiles && typeof allowAudioFiles !== 'boolean' || (allowBookFiles && allowAudioFiles)) { + if ((allowBookFiles && typeof allowBookFiles !== 'boolean') || (allowAudioFiles && typeof allowAudioFiles !== 'boolean') || (allowBookFiles && allowAudioFiles)) { Logger.error(`[FileSystemController] Invalid allowBookFiles or allowAudioFiles in request body: ${JSON.stringify(req.body)}`) return res.status(400).json({ error: 'Invalid allowBookFiles or allowAudioFiles' @@ -131,11 +133,11 @@ class FileSystemController { return res.sendStatus(403) } - const result = await validatePathExists(libraryFolder, directory, filename, allowBookFiles, allowAudioFiles) + const result = await validatePathExists(libraryFolder, directory, filenames, allowBookFiles, allowAudioFiles) - if (!result) return res.status(400) + if (!result) return res.status(400).end() - console.log(`[FileSystemController] Path exists check for "${directory}" in library "${libraryFolder.libraryId}" with filename "${filename}" returned: ${result.exists}`) + console.log(`[FileSystemController] Path exists check for "${directory}" in library "${libraryFolder.libraryId}" with filenames "${Array.isArray(filenames) ? filenames.join(', ') : 'N/A'}", allowBookFiles: ${allowBookFiles}, allowAudioFiles: ${allowAudioFiles} - Result: ${result.exists}`) return res.json(result) } diff --git a/server/controllers/MiscController.js b/server/controllers/MiscController.js index c57e0cde..421e5a10 100644 --- a/server/controllers/MiscController.js +++ b/server/controllers/MiscController.js @@ -79,12 +79,12 @@ class MiscController { const cleanedOutputDirectoryParts = outputDirectoryParts.filter(Boolean).map((part) => sanitizeFilename(part)) const outputDirectory = Path.join(...[folder.path, ...cleanedOutputDirectoryParts]) - const containsBook = files.some(file => globals.SupportedEbookTypes.includes(Path.extname(file.name).toLowerCase())) - const containsAudio = files.some(file => globals.SupportedAudioTypes.includes(Path.extname(file.name).toLowerCase())) + const containsBook = files.some(file => globals.SupportedEbookTypes.includes(Path.extname(file.name).toLowerCase().slice(1))) + const containsAudio = files.some(file => globals.SupportedAudioTypes.includes(Path.extname(file.name).toLowerCase().slice(1))) console.log(`Uploading files to ${outputDirectory} with containsBook: ${containsBook}, containsAudio: ${containsAudio}`) - if ((await validatePathExists(folder, outputDirectory, undefined, !containsBook, !containsAudio)).exists) { + if ((await validatePathExists(folder, outputDirectory, undefined, !containsBook, !containsAudio, true)).exists) { Logger.error(`Upload path already exists: ${outputDirectory}`) return res.status(400).send('Uploaded file already exists') } diff --git a/server/utils/fileUtils.js b/server/utils/fileUtils.js index 51a7c816..774360ea 100644 --- a/server/utils/fileUtils.js +++ b/server/utils/fileUtils.js @@ -579,48 +579,62 @@ async function copyToExisting(srcPath, destPath) { } module.exports.copyToExisting = copyToExisting -module.exports.validatePathExists = async function validatePathExists(libraryFolder, directory, filename, allowBookFiles, allowAudioFiles) { - let filepath = Path.join(libraryFolder.path, directory) - filepath = filePathToPOSIX(filepath) +module.exports.validatePathExists = async function validatePathExists( + libraryFolder, + directory, + filenames, + allowBookFiles, + allowAudioFiles, + skipLibraryFolder = false +) { + let filepath = Path.join(skipLibraryFolder ? '' : libraryFolder.path, directory); + filepath = filePathToPOSIX(filepath); - // Ensure filepath is inside library folder (prevents directory traversal) (And convert libraryFolder to Path to normalize) + // Ensure filepath is inside library folder (prevents directory traversal) if (!filepath.startsWith(libraryFolder.path)) { - Logger.error(`[FileSystemController] Filepath is not inside library folder: ${filepath}`) - return null + Logger.error( + `[FileSystemController] Filepath is not inside library folder: ${filepath}` + ); + return null; } + console.log(`[FileSystemController] Checking if path exists: "${filepath}"`); + if (await fs.pathExists(filepath)) { - if (filename) { - // Check if a specific file exists - const filePath = Path.join(filepath, filename) - if (await fs.pathExists(filePath)) { - return { - exists: true, + if (filenames && filenames.length > 0) { + // If any filename exists, not allowed to upload (exists: true) + for (const filename of filenames) { + const filePath = Path.join(filepath, filename); + if (await fs.pathExists(filePath)) { + return { + exists: true, + }; } } - } else if(allowBookFiles || allowAudioFiles) { - let allowedExtensions = [] - if (allowBookFiles && !allowAudioFiles) { - allowedExtensions = globals.SupportedEbookTypes - } else if (allowAudioFiles && !allowBookFiles) { - allowedExtensions = globals.SupportedAudioTypes - } else { - allowedExtensions = [] - } - const files = await fs.readdir(filepath) - const exists = allowedExtensions.length === 0 - ? files.length > 0 - : files.some((file) => { - const ext = Path.extname(file).toLowerCase().replace(/^\./, '') - return allowedExtensions.includes(ext) - }) + } } else if (allowBookFiles || allowAudioFiles) { + let restrictedExtensions = []; + if (allowBookFiles && !allowAudioFiles) { + restrictedExtensions = globals.SupportedAudioTypes; // Block audio files + } else if (allowAudioFiles && !allowBookFiles) { + restrictedExtensions = globals.SupportedEbookTypes; // Block book files + } - // To let the sub dir check run - if(exists) return exists + console.log(`[FileSystemController] Checking for restricted files in "${filepath}" with extensions: ${restrictedExtensions.join(', ')}`); + + if (restrictedExtensions.length > 0) { + const files = await fs.readdir(filepath); + const hasRestrictedFiles = files.some((file) => { + const ext = Path.extname(file).toLowerCase().replace(/^\./, ""); + return restrictedExtensions.includes(ext); + }); + + if (hasRestrictedFiles) { + return { exists: true }; + } } else { return { - exists: true - } + exists: true, + }; } }