Compare commits

...

3 commits

Author SHA1 Message Date
advplyr
b27f21fd95 Update podcastUtils to sanitize episode subtitle from rss feed
Some checks are pending
CodeQL / Analyze (push) Waiting to run
Build and Push Docker Image / build (push) Waiting to run
Integration Test / build and test (push) Waiting to run
Run Unit Tests / Run Unit Tests (push) Waiting to run
2026-04-17 16:59:22 -05:00
advplyr
09fa0b38f5 Update podcast create path validation & fix relPath 2026-04-17 16:51:22 -05:00
advplyr
455e605162 Update author & library item image endpoints to clamp width/height query params 2026-04-17 16:30:08 -05:00
5 changed files with 32 additions and 8 deletions

View file

@ -10,7 +10,7 @@ const CacheManager = require('../managers/CacheManager')
const CoverManager = require('../managers/CoverManager')
const AuthorFinder = require('../finders/AuthorFinder')
const { reqSupportsWebp, isValidASIN } = require('../utils/index')
const { reqSupportsWebp, isValidASIN, clampPositiveInt } = require('../utils/index')
const naturalSort = createNewSortInstance({
comparer: new Intl.Collator(undefined, { numeric: true, sensitivity: 'base' }).compare
@ -412,8 +412,8 @@ class AuthorController {
const options = {
format: format || (reqSupportsWebp(req) ? 'webp' : 'jpeg'),
height: height ? parseInt(height) : null,
width: width ? parseInt(width) : null
height: clampPositiveInt(height ? parseInt(height) : null, 4096),
width: clampPositiveInt(width ? parseInt(width) : null, 4096)
}
return CacheManager.handleAuthorCache(res, authorId, options)
}

View file

@ -7,7 +7,7 @@ const SocketAuthority = require('../SocketAuthority')
const Database = require('../Database')
const zipHelpers = require('../utils/zipHelpers')
const { reqSupportsWebp } = require('../utils/index')
const { reqSupportsWebp, clampPositiveInt } = require('../utils/index')
const { ScanResult, AudioMimeType } = require('../utils/constants')
const { getAudioMimeTypeFromExtname, encodeUriPath } = require('../utils/fileUtils')
const LibraryItemScanner = require('../scanner/LibraryItemScanner')
@ -398,8 +398,8 @@ class LibraryItemController {
const options = {
format: format || (reqSupportsWebp(req) ? 'webp' : 'jpeg'),
height: height ? parseInt(height) : null,
width: width ? parseInt(width) : null
height: clampPositiveInt(height ? parseInt(height) : null, 4096),
width: clampPositiveInt(width ? parseInt(width) : null, 4096)
}
return CacheManager.handleCoverCache(res, libraryItemId, options)
}

View file

@ -7,7 +7,7 @@ const Database = require('../Database')
const fs = require('../libs/fsExtra')
const { getPodcastFeed, findMatchingEpisodes } = require('../utils/podcastUtils')
const { getFileTimestampsWithIno, filePathToPOSIX } = require('../utils/fileUtils')
const { getFileTimestampsWithIno, filePathToPOSIX, isSameOrSubPath } = require('../utils/fileUtils')
const { validateUrl } = require('../utils/index')
const htmlSanitizer = require('../utils/htmlSanitizer')
@ -58,8 +58,18 @@ class PodcastController {
return res.status(404).send('Folder not found')
}
if (typeof payload.path !== 'string' || !payload.path.trim()) {
return res.status(400).send('Invalid request body. "path" must be a non-empty string')
}
const libraryFolderPath = filePathToPOSIX(folder.path)
const podcastPath = filePathToPOSIX(payload.path)
if (!isSameOrSubPath(libraryFolderPath, podcastPath)) {
Logger.error(`[PodcastController] Create: Podcast path is outside library folder "${libraryFolderPath}": "${podcastPath}"`)
return res.status(400).send('Podcast path must be inside the selected library folder')
}
// Check if a library item with this podcast folder exists already
const existingLibraryItem =
(await Database.libraryItemModel.count({
@ -83,7 +93,7 @@ class PodcastController {
const libraryItemFolderStats = await getFileTimestampsWithIno(podcastPath)
let relPath = payload.path.replace(folder.fullPath, '')
let relPath = podcastPath.replace(libraryFolderPath, '')
if (relPath.startsWith('/')) relPath = relPath.slice(1)
let newLibraryItem = null

View file

@ -54,6 +54,16 @@ module.exports.isNullOrNaN = (num) => {
return num === null || isNaN(num)
}
/**
* @param {number|null|undefined} value
* @param {number} max
* @returns {number|null}
*/
module.exports.clampPositiveInt = (value, max) => {
if (value == null || !Number.isFinite(value) || value <= 0) return null
return Math.min(Math.floor(value), max)
}
const xmlToJSON = (xml) => {
return new Promise((resolve, reject) => {
parseString(xml, (err, results) => {

View file

@ -217,6 +217,10 @@ function extractEpisodeData(item) {
episode[cleanKey] = extractFirstArrayItemString(item, key)
})
if (episode.subtitle) {
episode.subtitle = htmlSanitizer.sanitize(episode.subtitle.trim())
}
// Extract psc:chapters if duration is set
episode.durationSeconds = episode.duration ? timestampToSeconds(episode.duration) : null