mirror of
				https://github.com/advplyr/audiobookshelf.git
				synced 2025-10-27 11:18:14 +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 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 | ||||
|       } | ||||
|  | ||||
| @ -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 | ||||
|       }) | ||||
|  | ||||
| @ -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 | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user