// URL redaction and provider host policy for the preview feature. // // Signed preview URLs are ephemeral secrets: they carry an hmac and expire in // minutes (Phase 0: 13–15 min, then 403). They must never appear in logs, // session persistence, or diagnostics. Everything that wants to talk about a // URL goes through redactUrl() and gets host + a stable hash + the parsed // expiry — enough to correlate, nothing to replay. /** Providers the preview resolver may fetch media from. HTTPS only; exact * host match — no subdomain wildcards, because the CDN host is a single * stable name (Phase 0: 71/71 samples on cdnt-preview.dzcdn.net). */ export const MEDIA_HOST_ALLOWLIST = Object.freeze([ "cdnt-preview.dzcdn.net", // Apple preview hosts land here in Phase 2 when Apple identity is // plumbed through the backend; adding a host is a reviewed change. ]); /** Parse the signed expiry out of a preview URL. Deezer uses an Akamai * `hdnea=exp=~…` token; a bare `exp=` query also matches. * Returns epoch MILLISECONDS or null — never throws. */ export function parseSignedExpiryMs(url) { if (typeof url !== "string") return null; const m = /(?:^|[?&~])(?:hdnea=)?exp=(\d{9,11})(?:[~&]|$)/.exec(url); if (!m) return null; return Number(m[1]) * 1000; } /** FNV-1a — tiny, stable, dependency-free. Not cryptographic and does not * need to be: the hash only correlates log lines about the same URL. */ function fnv1a(str) { let h = 0x811c9dc5; for (let i = 0; i < str.length; i++) { h ^= str.charCodeAt(i); h = Math.imul(h, 0x01000193); } return (h >>> 0).toString(16).padStart(8, "0"); } /** The only representation of a media URL allowed outside the transport: * `{ host, hash, expMs }`. */ export function redactUrl(url) { if (typeof url !== "string" || url.length === 0) { return { host: null, hash: null, expMs: null }; } let host = null; try { host = new URL(url).host; } catch { /* malformed: host stays null, hash still correlates */ } return { host, hash: fnv1a(url), expMs: parseSignedExpiryMs(url) }; } /** True only for an https URL whose host is on the media allowlist. Used for * the initial URL and re-checked for every redirect target — a redirect off * the allowlist is a hard failure, not a follow. */ export function isAllowedMediaUrl(url) { try { const u = new URL(url); return u.protocol === "https:" && MEDIA_HOST_ALLOWLIST.includes(u.host); } catch { return false; } }