From d2d558754f290b56cf1e7a73c765337cb0ddba5d Mon Sep 17 00:00:00 2001 From: Vito0912 <86927734+Vito0912@users.noreply.github.com> Date: Wed, 11 Jun 2025 23:08:41 +0200 Subject: [PATCH] Add new options and fix path --- server/controllers/FileSystemController.js | 61 +++++++++++++++++++--- 1 file changed, 55 insertions(+), 6 deletions(-) diff --git a/server/controllers/FileSystemController.js b/server/controllers/FileSystemController.js index edfd869c..422089ba 100644 --- a/server/controllers/FileSystemController.js +++ b/server/controllers/FileSystemController.js @@ -88,7 +88,10 @@ class FileSystemController { return res.sendStatus(403) } - const { directory, folderPath } = req.body + // fileName - If fileName is provided, the check only returns true if the actual file exists, 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 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({ @@ -96,6 +99,20 @@ class FileSystemController { }) } + if (fileName && typeof fileName !== 'string') { + Logger.error(`[FileSystemController] Invalid fileName in request body: ${JSON.stringify(req.body)}`) + return res.status(400).json({ + error: 'Invalid fileName' + }) + } + + 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' + }) + } + // Check that library folder exists const libraryFolder = await Database.libraryFolderModel.findOne({ where: { @@ -110,20 +127,52 @@ class FileSystemController { const filepath = Path.join(libraryFolder.path, directory) - // Ensure filepath is inside library folder (prevents directory traversal) - if (!filepath.startsWith(libraryFolder.path)) { + // Ensure filepath is inside library folder (prevents directory traversal) (And convert libraryFolder to Path to normalize) + if (!filepath.startsWith(Path.join(libraryFolder.path))) { Logger.error(`[FileSystemController] Filepath is not inside library folder: ${filepath}`) return res.sendStatus(400) } if (await fs.pathExists(filepath)) { - return res.json({ - exists: true - }) + if (fileName) { + // Check if a specific file exists + const filePath = Path.join(filepath, fileName) + if (await fs.pathExists(filePath)) { + return res.json({ + exists: true, + }) + } + } else if(allowBookFiles || allowAudioFiles) { + let allowedExtensions = [] + if (allowBookFiles && !allowAudioFiles) { + allowedExtensions = ['epub', 'pdf', 'mobi', 'azw3', 'cbr', 'cbz'] + } else if (allowAudioFiles && !allowBookFiles) { + allowedExtensions = ['m4b', 'mp3', 'm4a', 'flac', 'opus', 'ogg', 'oga', 'mp4', 'aac', 'wma', 'aiff', 'aif', 'wav', 'webm', 'webma', 'mka', 'awb', 'caf', 'mpeg', 'mpg'] + } 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) + }) + + // To let the sub dir check run + if(exists) return res.json({ + exists: exists + }) + } else { + return res.json({ + exists: true + }) + } } // Check if a library item exists in a subdirectory // See: https://github.com/advplyr/audiobookshelf/issues/4146 + // For filenames it does not matter if the file is in a subdirectory or not because the file is not allowed to be created const cleanedDirectory = directory.split('/').filter(Boolean).join('/') if (cleanedDirectory.includes('/')) { // Can only be 2 levels deep