Merge branch 'master' into inode-bug-fix

This commit is contained in:
Jason Axley 2025-12-03 11:43:54 -08:00 committed by GitHub
commit d6fed92b11
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
87 changed files with 4539 additions and 751 deletions

View file

@ -99,7 +99,7 @@ module.exports.resizeImage = resizeImage
/**
*
* @param {import('../objects/PodcastEpisodeDownload')} podcastEpisodeDownload
* @returns {Promise<{success: boolean, isFfmpegError?: boolean}>}
* @returns {Promise<{success: boolean, isRequestError?: boolean}>}
*/
module.exports.downloadPodcastEpisode = (podcastEpisodeDownload) => {
return new Promise(async (resolve) => {
@ -118,7 +118,7 @@ module.exports.downloadPodcastEpisode = (podcastEpisodeDownload) => {
method: 'GET',
responseType: 'stream',
headers: {
'Accept': '*/*',
Accept: '*/*',
'User-Agent': userAgent
},
timeout: global.PodcastDownloadTimeout
@ -139,7 +139,8 @@ module.exports.downloadPodcastEpisode = (podcastEpisodeDownload) => {
if (!response) {
return resolve({
success: false
success: false,
isRequestError: true
})
}
@ -204,8 +205,7 @@ module.exports.downloadPodcastEpisode = (podcastEpisodeDownload) => {
Logger.error(`Full stderr dump for episode url "${podcastEpisodeDownload.url}": ${stderrLines.join('\n')}`)
}
resolve({
success: false,
isFfmpegError: true
success: false
})
})
ffmpeg.on('progress', (progress) => {

View file

@ -277,3 +277,57 @@ module.exports.timestampToSeconds = (timestamp) => {
}
return null
}
class ValidationError extends Error {
constructor(paramName, message, status = 400) {
super(`Query parameter "${paramName}" ${message}`)
this.name = 'ValidationError'
this.paramName = paramName
this.status = status
}
}
module.exports.ValidationError = ValidationError
class NotFoundError extends Error {
constructor(message, status = 404) {
super(message)
this.name = 'NotFoundError'
this.status = status
}
}
module.exports.NotFoundError = NotFoundError
/**
* Safely extracts a query parameter as a string, rejecting arrays to prevent type confusion
* Express query parameters can be arrays if the same parameter appears multiple times
* @example ?author=Smith => "Smith"
* @example ?author=Smith&author=Jones => throws error
*
* @param {Object} query - Query object
* @param {string} paramName - Parameter name
* @param {string} defaultValue - Default value if undefined/null
* @param {boolean} required - Whether the parameter is required
* @param {number} maxLength - Optional maximum length (defaults to 10000 to prevent ReDoS attacks)
* @returns {string} String value
* @throws {ValidationError} If value is an array
* @throws {ValidationError} If value is too long
* @throws {ValidationError} If value is required but not provided
*/
module.exports.getQueryParamAsString = (query, paramName, defaultValue = '', required = false, maxLength = 1000) => {
const value = query[paramName]
if (value === undefined || value === null) {
if (required) {
throw new ValidationError(paramName, 'is required')
}
return defaultValue
}
// Explicitly reject arrays to prevent type confusion
if (Array.isArray(value)) {
throw new ValidationError(paramName, 'is an array')
}
// Reject excessively long strings to prevent ReDoS attacks
if (typeof value === 'string' && value.length > maxLength) {
throw new ValidationError(paramName, 'is too long')
}
return String(value)
}

View file

@ -171,7 +171,7 @@ function extractEpisodeData(item) {
// Full description with html
if (item['content:encoded']) {
const rawDescription = (extractFirstArrayItem(item, 'content:encoded') || '').trim()
const rawDescription = (extractFirstArrayItemString(item, 'content:encoded') || '').trim()
episode.description = htmlSanitizer.sanitize(rawDescription)
}