diff --git a/Client/tauri-client/src/components/message-list/embeds.ts b/Client/tauri-client/src/components/message-list/embeds.ts index 1257befd..ca7f50fe 100644 --- a/Client/tauri-client/src/components/message-list/embeds.ts +++ b/Client/tauri-client/src/components/message-list/embeds.ts @@ -37,48 +37,38 @@ export function clearEmbedCaches(): void { // -- OG tag parsing ----------------------------------------------------------- -/** Escape special regex characters in a string for safe use in `new RegExp()`. */ -function escapeRegex(s: string): string { - return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); -} - -/** Extract Open Graph meta tags from raw HTML using regex (no DOM parser needed). */ +/** + * Extract Open Graph meta tags from raw HTML. + * + * F7: parse with the platform HTML tokenizer (DOMParser) instead of backtracking + * regexes. Untrusted preview HTML previously ran through patterns with two + * `[^>]*` quantifiers around a required literal, which backtrack polynomially and + * froze the UI thread on crafted input (ReDoS). A real tokenizer is linear and + * additionally ignores meta-like strings inside comments/scripts. + * + * The 50 KB slice at the call site (fetchOgMeta) is kept as a plain memory bound. + */ export function parseOgTags(html: string): OgMeta { - function getMetaContent(property: string): string | null { - // Match both property="og:X" and name="og:X" patterns - const escaped = escapeRegex(property); - const regex = new RegExp( - `]*(?:property|name)=["']${escaped}["'][^>]*content=["']([^"']*)["']` + - `|]*content=["']([^"']*)["'][^>]*(?:property|name)=["']${escaped}["']`, - "i", - ); - const match = html.match(regex); - if (match !== null) { - return match[1] ?? match[2] ?? null; + const doc = new DOMParser().parseFromString(html, "text/html"); + + // First matching element wins (document order), mirroring the old first-match + // behaviour. Returns "" for an empty content attribute but null when the + // attribute (or element) is absent, so the title→host fallback still fires. + function metaContent(...ogNames: readonly string[]): string | null { + for (const name of ogNames) { + const el = + doc.querySelector(`meta[property="${name}"]`) ?? doc.querySelector(`meta[name="${name}"]`); + const content = el?.getAttribute("content"); + if (content != null) return content; } return null; } - // Fallback: extract