mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2025-06-23 01:18:59 +02:00
misc
This commit is contained in:
parent
67e0a6a945
commit
4e67d56cd5
@ -5,6 +5,7 @@ const fs = require('../libs/fsExtra')
|
|||||||
const { toNumber } = require('../utils/index')
|
const { toNumber } = require('../utils/index')
|
||||||
const fileUtils = require('../utils/fileUtils')
|
const fileUtils = require('../utils/fileUtils')
|
||||||
const Database = require('../Database')
|
const Database = require('../Database')
|
||||||
|
const { validatePathExists } = require('../utils/fileUtils')
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef RequestUserObject
|
* @typedef RequestUserObject
|
||||||
@ -88,10 +89,10 @@ 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
|
// 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.)
|
// 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, filename, 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({
|
||||||
@ -99,10 +100,10 @@ class FileSystemController {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fileName && typeof fileName !== 'string') {
|
if (filename && typeof filename !== 'string') {
|
||||||
Logger.error(`[FileSystemController] Invalid fileName in request body: ${JSON.stringify(req.body)}`)
|
Logger.error(`[FileSystemController] Invalid filename in request body: ${JSON.stringify(req.body)}`)
|
||||||
return res.status(400).json({
|
return res.status(400).json({
|
||||||
error: 'Invalid fileName'
|
error: 'Invalid filename'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -130,82 +131,14 @@ class FileSystemController {
|
|||||||
return res.sendStatus(403)
|
return res.sendStatus(403)
|
||||||
}
|
}
|
||||||
|
|
||||||
let filepath = Path.join(libraryFolder.path, directory)
|
const result = await validatePathExists(libraryFolder, directory, filename, allowBookFiles, allowAudioFiles)
|
||||||
filepath = fileUtils.filePathToPOSIX(filepath)
|
|
||||||
|
|
||||||
// Ensure filepath is inside library folder (prevents directory traversal) (And convert libraryFolder to Path to normalize)
|
if (!result) return res.status(400)
|
||||||
if (!filepath.startsWith(libraryFolder.path)) {
|
|
||||||
Logger.error(`[FileSystemController] Filepath is not inside library folder: ${filepath}`)
|
|
||||||
return res.sendStatus(400)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (await fs.pathExists(filepath)) {
|
console.log(`[FileSystemController] Path exists check for "${directory}" in library "${libraryFolder.libraryId}" with filename "${filename}" returned: ${result.exists}`)
|
||||||
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
|
return res.json(result)
|
||||||
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
|
|
||||||
const possiblePaths = []
|
|
||||||
const subdir = Path.dirname(directory)
|
|
||||||
possiblePaths.push(fileUtils.filePathToPOSIX(Path.join(folderPath, subdir)))
|
|
||||||
if (subdir.includes('/')) {
|
|
||||||
possiblePaths.push(fileUtils.filePathToPOSIX(Path.join(folderPath, Path.dirname(subdir))))
|
|
||||||
}
|
|
||||||
|
|
||||||
const libraryItem = await Database.libraryItemModel.findOne({
|
|
||||||
where: {
|
|
||||||
path: possiblePaths
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
if (libraryItem) {
|
|
||||||
return res.json({
|
|
||||||
exists: true,
|
|
||||||
libraryItemTitle: libraryItem.title
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return res.json({
|
|
||||||
exists: false
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = new FileSystemController()
|
module.exports = new FileSystemController()
|
||||||
|
@ -6,11 +6,12 @@ const Logger = require('../Logger')
|
|||||||
const SocketAuthority = require('../SocketAuthority')
|
const SocketAuthority = require('../SocketAuthority')
|
||||||
const Database = require('../Database')
|
const Database = require('../Database')
|
||||||
const Watcher = require('../Watcher')
|
const Watcher = require('../Watcher')
|
||||||
|
const globals = require('../utils/globals')
|
||||||
|
|
||||||
const libraryItemFilters = require('../utils/queries/libraryItemFilters')
|
const libraryItemFilters = require('../utils/queries/libraryItemFilters')
|
||||||
const patternValidation = require('../libs/nodeCron/pattern-validation')
|
const patternValidation = require('../libs/nodeCron/pattern-validation')
|
||||||
const { isObject, getTitleIgnorePrefix } = require('../utils/index')
|
const { isObject, getTitleIgnorePrefix } = require('../utils/index')
|
||||||
const { sanitizeFilename } = require('../utils/fileUtils')
|
const { sanitizeFilename, validatePathExists } = require('../utils/fileUtils')
|
||||||
|
|
||||||
const TaskManager = require('../managers/TaskManager')
|
const TaskManager = require('../managers/TaskManager')
|
||||||
const adminStats = require('../utils/queries/adminStats')
|
const adminStats = require('../utils/queries/adminStats')
|
||||||
@ -78,6 +79,16 @@ 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 containsAudio = files.some(file => globals.SupportedAudioTypes.includes(Path.extname(file.name).toLowerCase()))
|
||||||
|
|
||||||
|
console.log(`Uploading files to ${outputDirectory} with containsBook: ${containsBook}, containsAudio: ${containsAudio}`)
|
||||||
|
|
||||||
|
if ((await validatePathExists(folder, outputDirectory, undefined, !containsBook, !containsAudio)).exists) {
|
||||||
|
Logger.error(`Upload path already exists: ${outputDirectory}`)
|
||||||
|
return res.status(400).send('Uploaded file already exists')
|
||||||
|
}
|
||||||
|
|
||||||
await fs.ensureDir(outputDirectory)
|
await fs.ensureDir(outputDirectory)
|
||||||
|
|
||||||
Logger.info(`Uploading ${files.length} files to`, outputDirectory)
|
Logger.info(`Uploading ${files.length} files to`, outputDirectory)
|
||||||
|
@ -6,6 +6,7 @@ const fs = require('../libs/fsExtra')
|
|||||||
const rra = require('../libs/recursiveReaddirAsync')
|
const rra = require('../libs/recursiveReaddirAsync')
|
||||||
const Logger = require('../Logger')
|
const Logger = require('../Logger')
|
||||||
const { AudioMimeType } = require('./constants')
|
const { AudioMimeType } = require('./constants')
|
||||||
|
const globals = require('./globals')
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Make sure folder separator is POSIX for Windows file paths. e.g. "C:\Users\Abs" becomes "C:/Users/Abs"
|
* Make sure folder separator is POSIX for Windows file paths. e.g. "C:\Users\Abs" becomes "C:/Users/Abs"
|
||||||
@ -577,3 +578,80 @@ async function copyToExisting(srcPath, destPath) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
module.exports.copyToExisting = copyToExisting
|
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)
|
||||||
|
|
||||||
|
// Ensure filepath is inside library folder (prevents directory traversal) (And convert libraryFolder to Path to normalize)
|
||||||
|
if (!filepath.startsWith(libraryFolder.path)) {
|
||||||
|
Logger.error(`[FileSystemController] Filepath is not inside library folder: ${filepath}`)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} 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)
|
||||||
|
})
|
||||||
|
|
||||||
|
// To let the sub dir check run
|
||||||
|
if(exists) return exists
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
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
|
||||||
|
const possiblePaths = []
|
||||||
|
const subdir = Path.dirname(directory)
|
||||||
|
possiblePaths.push(fileUtils.filePathToPOSIX(Path.join(folderPath, subdir)))
|
||||||
|
if (subdir.includes('/')) {
|
||||||
|
possiblePaths.push(fileUtils.filePathToPOSIX(Path.join(folderPath, Path.dirname(subdir))))
|
||||||
|
}
|
||||||
|
|
||||||
|
const libraryItem = await Database.libraryItemModel.findOne({
|
||||||
|
where: {
|
||||||
|
path: possiblePaths
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if (libraryItem) {
|
||||||
|
return {
|
||||||
|
exists: true,
|
||||||
|
libraryItemTitle: libraryItem.title
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
exists: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user