mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2025-02-04 12:29:34 +01:00
Update:Validate image URI content-type before writing image file
This commit is contained in:
parent
1f8372f5e5
commit
c98fac30b6
@ -3,7 +3,7 @@ const Logger = require('../Logger')
|
|||||||
const Path = require('path')
|
const Path = require('path')
|
||||||
const Audnexus = require('../providers/Audnexus')
|
const Audnexus = require('../providers/Audnexus')
|
||||||
|
|
||||||
const { downloadFile } = require('../utils/fileUtils')
|
const { downloadImageFile } = require('../utils/fileUtils')
|
||||||
|
|
||||||
class AuthorFinder {
|
class AuthorFinder {
|
||||||
constructor() {
|
constructor() {
|
||||||
@ -45,7 +45,7 @@ class AuthorFinder {
|
|||||||
const filename = authorId + '.' + ext
|
const filename = authorId + '.' + ext
|
||||||
const outputPath = Path.posix.join(authorDir, filename)
|
const outputPath = Path.posix.join(authorDir, filename)
|
||||||
|
|
||||||
return downloadFile(url, outputPath).then(() => {
|
return downloadImageFile(url, outputPath).then(() => {
|
||||||
return {
|
return {
|
||||||
path: outputPath
|
path: outputPath
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,7 @@ const readChunk = require('../libs/readChunk')
|
|||||||
const imageType = require('../libs/imageType')
|
const imageType = require('../libs/imageType')
|
||||||
|
|
||||||
const globals = require('../utils/globals')
|
const globals = require('../utils/globals')
|
||||||
const { downloadFile, filePathToPOSIX, checkPathIsFile } = require('../utils/fileUtils')
|
const { downloadImageFile, filePathToPOSIX, checkPathIsFile } = require('../utils/fileUtils')
|
||||||
const { extractCoverArt } = require('../utils/ffmpegHelpers')
|
const { extractCoverArt } = require('../utils/ffmpegHelpers')
|
||||||
const CacheManager = require('../managers/CacheManager')
|
const CacheManager = require('../managers/CacheManager')
|
||||||
|
|
||||||
@ -122,7 +122,7 @@ class CoverManager {
|
|||||||
var temppath = Path.posix.join(coverDirPath, 'cover')
|
var temppath = Path.posix.join(coverDirPath, 'cover')
|
||||||
|
|
||||||
let errorMsg = ''
|
let errorMsg = ''
|
||||||
let success = await downloadFile(url, temppath).then(() => true).catch((err) => {
|
let success = await downloadImageFile(url, temppath).then(() => true).catch((err) => {
|
||||||
errorMsg = err.message || 'Unknown error'
|
errorMsg = err.message || 'Unknown error'
|
||||||
Logger.error(`[CoverManager] Download image file failed for "${url}"`, errorMsg)
|
Logger.error(`[CoverManager] Download image file failed for "${url}"`, errorMsg)
|
||||||
return false
|
return false
|
||||||
@ -287,7 +287,7 @@ class CoverManager {
|
|||||||
await fs.ensureDir(coverDirPath)
|
await fs.ensureDir(coverDirPath)
|
||||||
|
|
||||||
const temppath = Path.posix.join(coverDirPath, 'cover')
|
const temppath = Path.posix.join(coverDirPath, 'cover')
|
||||||
const success = await downloadFile(url, temppath).then(() => true).catch((err) => {
|
const success = await downloadImageFile(url, temppath).then(() => true).catch((err) => {
|
||||||
Logger.error(`[CoverManager] Download image file failed for "${url}"`, err)
|
Logger.error(`[CoverManager] Download image file failed for "${url}"`, err)
|
||||||
return false
|
return false
|
||||||
})
|
})
|
||||||
|
@ -204,7 +204,16 @@ async function recurseFiles(path, relPathToReplace = null) {
|
|||||||
}
|
}
|
||||||
module.exports.recurseFiles = recurseFiles
|
module.exports.recurseFiles = recurseFiles
|
||||||
|
|
||||||
module.exports.downloadFile = (url, filepath) => {
|
/**
|
||||||
|
* Download file from web to local file system
|
||||||
|
* Uses SSRF filter to prevent internal URLs
|
||||||
|
*
|
||||||
|
* @param {string} url
|
||||||
|
* @param {string} filepath path to download the file to
|
||||||
|
* @param {Function} [contentTypeFilter] validate content type before writing
|
||||||
|
* @returns {Promise}
|
||||||
|
*/
|
||||||
|
module.exports.downloadFile = (url, filepath, contentTypeFilter = null) => {
|
||||||
return new Promise(async (resolve, reject) => {
|
return new Promise(async (resolve, reject) => {
|
||||||
Logger.debug(`[fileUtils] Downloading file to ${filepath}`)
|
Logger.debug(`[fileUtils] Downloading file to ${filepath}`)
|
||||||
axios({
|
axios({
|
||||||
@ -215,6 +224,12 @@ module.exports.downloadFile = (url, filepath) => {
|
|||||||
httpAgent: ssrfFilter(url),
|
httpAgent: ssrfFilter(url),
|
||||||
httpsAgent: ssrfFilter(url)
|
httpsAgent: ssrfFilter(url)
|
||||||
}).then((response) => {
|
}).then((response) => {
|
||||||
|
// Validate content type
|
||||||
|
if (contentTypeFilter && !contentTypeFilter?.(response.headers?.['content-type'])) {
|
||||||
|
return reject(new Error(`Invalid content type "${response.headers?.['content-type'] || ''}"`))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write to filepath
|
||||||
const writer = fs.createWriteStream(filepath)
|
const writer = fs.createWriteStream(filepath)
|
||||||
response.data.pipe(writer)
|
response.data.pipe(writer)
|
||||||
|
|
||||||
@ -227,6 +242,21 @@ module.exports.downloadFile = (url, filepath) => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Download image file from web to local file system
|
||||||
|
* Response header must have content-type of image/ (excluding svg)
|
||||||
|
*
|
||||||
|
* @param {string} url
|
||||||
|
* @param {string} filepath
|
||||||
|
* @returns {Promise}
|
||||||
|
*/
|
||||||
|
module.exports.downloadImageFile = (url, filepath) => {
|
||||||
|
const contentTypeFilter = (contentType) => {
|
||||||
|
return contentType?.startsWith('image/') && contentType !== 'image/svg+xml'
|
||||||
|
}
|
||||||
|
return this.downloadFile(url, filepath, contentTypeFilter)
|
||||||
|
}
|
||||||
|
|
||||||
module.exports.sanitizeFilename = (filename, colonReplacement = ' - ') => {
|
module.exports.sanitizeFilename = (filename, colonReplacement = ' - ') => {
|
||||||
if (typeof filename !== 'string') {
|
if (typeof filename !== 'string') {
|
||||||
return false
|
return false
|
||||||
|
Loading…
Reference in New Issue
Block a user