mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2025-08-29 22:29:31 +02:00
filenames
This commit is contained in:
parent
4e67d56cd5
commit
11cc9f2562
4 changed files with 61 additions and 45 deletions
|
@ -360,7 +360,7 @@ export default {
|
||||||
// uploading fails if path already exists
|
// uploading fails if path already exists
|
||||||
for (const item of items) {
|
for (const item of items) {
|
||||||
const exists = await this.$axios
|
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) => {
|
.then((data) => {
|
||||||
if (data.exists) {
|
if (data.exists) {
|
||||||
if (data.libraryItemTitle) {
|
if (data.libraryItemTitle) {
|
||||||
|
|
|
@ -89,10 +89,11 @@ class FileSystemController {
|
||||||
return res.sendStatus(403)
|
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.)
|
// 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.)
|
// 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') {
|
if (!directory?.length || typeof directory !== 'string' || !folderPath?.length || typeof folderPath !== 'string') {
|
||||||
Logger.error(`[FileSystemController] Invalid request body: ${JSON.stringify(req.body)}`)
|
Logger.error(`[FileSystemController] Invalid request body: ${JSON.stringify(req.body)}`)
|
||||||
return res.status(400).json({
|
return res.status(400).json({
|
||||||
|
@ -100,14 +101,15 @@ class FileSystemController {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if (filename && typeof filename !== 'string') {
|
// Validate filenames: must be undefined or an array of non-empty strings
|
||||||
Logger.error(`[FileSystemController] Invalid filename in request body: ${JSON.stringify(req.body)}`)
|
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({
|
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)}`)
|
Logger.error(`[FileSystemController] Invalid allowBookFiles or allowAudioFiles in request body: ${JSON.stringify(req.body)}`)
|
||||||
return res.status(400).json({
|
return res.status(400).json({
|
||||||
error: 'Invalid allowBookFiles or allowAudioFiles'
|
error: 'Invalid allowBookFiles or allowAudioFiles'
|
||||||
|
@ -131,11 +133,11 @@ class FileSystemController {
|
||||||
return res.sendStatus(403)
|
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)
|
return res.json(result)
|
||||||
}
|
}
|
||||||
|
|
|
@ -79,12 +79,12 @@ class MiscController {
|
||||||
const cleanedOutputDirectoryParts = outputDirectoryParts.filter(Boolean).map((part) => sanitizeFilename(part))
|
const cleanedOutputDirectoryParts = outputDirectoryParts.filter(Boolean).map((part) => sanitizeFilename(part))
|
||||||
const outputDirectory = Path.join(...[folder.path, ...cleanedOutputDirectoryParts])
|
const outputDirectory = Path.join(...[folder.path, ...cleanedOutputDirectoryParts])
|
||||||
|
|
||||||
const containsBook = files.some(file => globals.SupportedEbookTypes.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()))
|
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}`)
|
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}`)
|
Logger.error(`Upload path already exists: ${outputDirectory}`)
|
||||||
return res.status(400).send('Uploaded file already exists')
|
return res.status(400).send('Uploaded file already exists')
|
||||||
}
|
}
|
||||||
|
|
|
@ -579,48 +579,62 @@ async function copyToExisting(srcPath, destPath) {
|
||||||
}
|
}
|
||||||
module.exports.copyToExisting = copyToExisting
|
module.exports.copyToExisting = copyToExisting
|
||||||
|
|
||||||
module.exports.validatePathExists = async function validatePathExists(libraryFolder, directory, filename, allowBookFiles, allowAudioFiles) {
|
module.exports.validatePathExists = async function validatePathExists(
|
||||||
let filepath = Path.join(libraryFolder.path, directory)
|
libraryFolder,
|
||||||
filepath = filePathToPOSIX(filepath)
|
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)) {
|
if (!filepath.startsWith(libraryFolder.path)) {
|
||||||
Logger.error(`[FileSystemController] Filepath is not inside library folder: ${filepath}`)
|
Logger.error(
|
||||||
return null
|
`[FileSystemController] Filepath is not inside library folder: ${filepath}`
|
||||||
|
);
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log(`[FileSystemController] Checking if path exists: "${filepath}"`);
|
||||||
|
|
||||||
if (await fs.pathExists(filepath)) {
|
if (await fs.pathExists(filepath)) {
|
||||||
if (filename) {
|
if (filenames && filenames.length > 0) {
|
||||||
// Check if a specific file exists
|
// If any filename exists, not allowed to upload (exists: true)
|
||||||
const filePath = Path.join(filepath, filename)
|
for (const filename of filenames) {
|
||||||
|
const filePath = Path.join(filepath, filename);
|
||||||
if (await fs.pathExists(filePath)) {
|
if (await fs.pathExists(filePath)) {
|
||||||
return {
|
return {
|
||||||
exists: true,
|
exists: true,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if(allowBookFiles || allowAudioFiles) {
|
} } else if (allowBookFiles || allowAudioFiles) {
|
||||||
let allowedExtensions = []
|
let restrictedExtensions = [];
|
||||||
if (allowBookFiles && !allowAudioFiles) {
|
if (allowBookFiles && !allowAudioFiles) {
|
||||||
allowedExtensions = globals.SupportedEbookTypes
|
restrictedExtensions = globals.SupportedAudioTypes; // Block audio files
|
||||||
} else if (allowAudioFiles && !allowBookFiles) {
|
} else if (allowAudioFiles && !allowBookFiles) {
|
||||||
allowedExtensions = globals.SupportedAudioTypes
|
restrictedExtensions = globals.SupportedEbookTypes; // Block book files
|
||||||
} 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
|
console.log(`[FileSystemController] Checking for restricted files in "${filepath}" with extensions: ${restrictedExtensions.join(', ')}`);
|
||||||
if(exists) return exists
|
|
||||||
|
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 {
|
} else {
|
||||||
return {
|
return {
|
||||||
exists: true
|
exists: true,
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue