Update:Validate image URI content-type before writing image file

This commit is contained in:
advplyr 2023-10-14 10:52:56 -05:00
parent 1f8372f5e5
commit c98fac30b6
3 changed files with 36 additions and 6 deletions

View File

@ -3,7 +3,7 @@ const Logger = require('../Logger')
const Path = require('path')
const Audnexus = require('../providers/Audnexus')
const { downloadFile } = require('../utils/fileUtils')
const { downloadImageFile } = require('../utils/fileUtils')
class AuthorFinder {
constructor() {
@ -45,7 +45,7 @@ class AuthorFinder {
const filename = authorId + '.' + ext
const outputPath = Path.posix.join(authorDir, filename)
return downloadFile(url, outputPath).then(() => {
return downloadImageFile(url, outputPath).then(() => {
return {
path: outputPath
}

View File

@ -5,7 +5,7 @@ const readChunk = require('../libs/readChunk')
const imageType = require('../libs/imageType')
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 CacheManager = require('../managers/CacheManager')
@ -122,7 +122,7 @@ class CoverManager {
var temppath = Path.posix.join(coverDirPath, 'cover')
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'
Logger.error(`[CoverManager] Download image file failed for "${url}"`, errorMsg)
return false
@ -287,7 +287,7 @@ class CoverManager {
await fs.ensureDir(coverDirPath)
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)
return false
})

View File

@ -204,7 +204,16 @@ async function recurseFiles(path, relPathToReplace = null) {
}
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) => {
Logger.debug(`[fileUtils] Downloading file to ${filepath}`)
axios({
@ -215,6 +224,12 @@ module.exports.downloadFile = (url, filepath) => {
httpAgent: ssrfFilter(url),
httpsAgent: ssrfFilter(url)
}).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)
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 = ' - ') => {
if (typeof filename !== 'string') {
return false