From b17e6010fd0014dc4e273911d3e86d9a4289e9f3 Mon Sep 17 00:00:00 2001 From: advplyr Date: Tue, 4 Mar 2025 17:50:40 -0600 Subject: [PATCH] Add validation for custom metadata provider responses --- server/providers/CustomProviderAdapter.js | 66 +++++++++++++++++------ 1 file changed, 49 insertions(+), 17 deletions(-) diff --git a/server/providers/CustomProviderAdapter.js b/server/providers/CustomProviderAdapter.js index 91b34b0d..a5fed393 100644 --- a/server/providers/CustomProviderAdapter.js +++ b/server/providers/CustomProviderAdapter.js @@ -69,25 +69,57 @@ class CustomProviderAdapter { throw new Error('Custom provider returned malformed response') } + const toStringOrUndefined = (value) => { + if (typeof value === 'string' || typeof value === 'number') return String(value) + if (Array.isArray(value) && value.every((v) => typeof v === 'string' || typeof v === 'number')) return value.join(',') + return undefined + } + const validateSeriesArray = (series) => { + if (!Array.isArray(series) || !series.length) return undefined + return series + .map((s) => { + if (!s?.series || typeof s.series !== 'string') return undefined + const _series = { + series: s.series + } + if (s.sequence && (typeof s.sequence === 'string' || typeof s.sequence === 'number')) { + _series.sequence = String(s.sequence) + } + return _series + }) + .filter((s) => s !== undefined) + } + // re-map keys to throw out - return matches.map(({ title, subtitle, author, narrator, publisher, publishedYear, description, cover, isbn, asin, genres, tags, series, language, duration }) => { - return { - title, - subtitle, - author, - narrator, - publisher, - publishedYear, - description: typeof description === 'string' ? htmlSanitizer.sanitize(description) : description, - cover, - isbn, - asin, - genres, - tags: tags?.join(',') || null, - series: series?.length ? series : null, - language, - duration + return matches.map((match) => { + const { title, subtitle, author, narrator, publisher, publishedYear, description, cover, isbn, asin, genres, tags, series, language, duration } = match + + const payload = { + title: toStringOrUndefined(title), + subtitle: toStringOrUndefined(subtitle), + author: toStringOrUndefined(author), + narrator: toStringOrUndefined(narrator), + publisher: toStringOrUndefined(publisher), + publishedYear: toStringOrUndefined(publishedYear), + description: description && typeof description === 'string' ? htmlSanitizer.sanitize(description) : undefined, + cover: toStringOrUndefined(cover), + isbn: toStringOrUndefined(isbn), + asin: toStringOrUndefined(asin), + genres: Array.isArray(genres) && genres.every((g) => typeof g === 'string') ? genres : undefined, + tags: toStringOrUndefined(tags), + series: validateSeriesArray(series), + language: toStringOrUndefined(language), + duration: !isNaN(duration) && duration !== null ? Number(duration) : undefined } + + // Remove undefined values + for (const key in payload) { + if (payload[key] === undefined) { + delete payload[key] + } + } + + return payload }) } }