fix(tmdb): provide route params observable to inline collection details, linearize regexes

The global-collection inline detail host builds a fake ActivatedRoute for
VodDetailsRouteComponent/SerialDetailsComponent with only snapshot.params.
Since the detail components now read route.params via toSignal() (detail->
detail re-init), the missing observable crashed component construction and
the content hero never rendered — broke dashboard-activation, favorites and
recent Electron E2E on all platforms. Provide the params observable
alongside the snapshot and assert it in the component spec.

Also resolves both CodeQL js/polynomial-redos alerts: bracket-stripping in
normalizeTitle now excludes opening delimiters inside the classes, and
youtubeEmbedUrl extracts watch?v= ids with a linear two-pass match instead
of "watch\?.*v=". Combining-diacritics range rewritten as explicit \u
escapes (greptile note).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
4grayandClaude Fable 5 committed 2026-07-04 14:31:10 +02:00
1 parent 600685d897
commit 60be8a2999
4 files changed
+43 -31

No files matched your search

@@ -46,10 +46,12 @@ export function normalizeTitle(raw: string | null | undefined): string {
}
const cleaned = raw
.replace(/\[[^\]]*\]|\([^)]*\)|\{[^}]*\}/g, ' ')
// Inner classes exclude the opening delimiter too, so runaway
// inputs like "[[[[[…" backtrack linearly (CodeQL js/polynomial-redos)
.replace(/\[[^\][]*\]|\([^()]*\)|\{[^{}]*\}/g, ' ')
.replace(LANGUAGE_PREFIX, '')
.normalize('NFD')
.replace(/[̀-ͯ]/g, '')
.replace(/[\u0300-\u036F]/g, '')
.toLowerCase()
.replace(/[^\p{L}\p{N}]+/gu, ' ')
.split(' ')
@@ -76,12 +76,18 @@ export function youtubeEmbedUrl(
return null;
}
// Two linear passes instead of one "watch\?.*v=" alternation, which
// backtracks polynomially on hostile input (CodeQL js/polynomial-redos)
let videoId = raw;
const urlMatch = raw.match(
/(?:youtube(?:-nocookie)?\.com\/(?:watch\?.*v=|embed\/|shorts\/)|youtu\.be\/)([A-Za-z0-9_-]{6,})/
);
if (urlMatch) {
videoId = urlMatch[1];
if (/youtube(?:-nocookie)?\.com\/watch\?/.test(raw)) {
videoId = raw.match(/[?&]v=([A-Za-z0-9_-]{6,})/)?.[1] ?? '';
} else {
const urlMatch = raw.match(
/(?:youtube(?:-nocookie)?\.com\/(?:embed|shorts)\/|youtu\.be\/)([A-Za-z0-9_-]{6,})/
);
if (urlMatch) {
videoId = urlMatch[1];
}
}
return /^[A-Za-z0-9_-]{6,}$/.test(videoId)