@aglyn/tenant-data-admin 1.0.0-beta.223 → 1.0.0-beta.225

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../../libs/tenant/data/admin/src/lib/server/serve-media-cdn.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { pipeline } from 'node:stream/promises'\nimport {\n type AglynOrgBilling,\n BANDWIDTH_CAP_RETRY_AFTER_SECONDS,\n bandwidthCapApplies,\n bandwidthCapEngaged,\n bandwidthCeilingDegradesHost,\n bandwidthCeilingMonthKey,\n isLockdownActive,\n type LockdownState,\n MEDIA_BANDWIDTH_DAY_FIELD,\n MEDIA_CDN_POSTER_PARAM,\n MEDIA_CDN_RENDITION_AUTO,\n MEDIA_CDN_RENDITION_PARAM,\n MEDIA_CDN_ROUTE,\n MEDIA_CDN_VERSION_PARAM,\n MEDIA_POSTER_OBJECT_SUFFIX,\n MEDIA_VARIANT_ENCODER_VERSION,\n MEDIA_VARIANT_ENCODER_VERSION_FIELD,\n type MediaCdnVersion,\n isSecurityClassLockdownReason,\n mediaDisplayObjectPath,\n mediaPosterObjectPath,\n mediaVariantEncoderVersionOf,\n mediaRenditionObjectPath,\n type MediaVideoRendition,\n normalizeHostLockdown,\n normalizeOrgLockdown,\n parseMediaCdnVersionToken,\n parseMediaRenditions,\n} from '@aglyn/aglyn/server'\n// By path, and out of every barrel: see the module note in `media-cdn-scope`.\nimport {\n MEDIA_CDN_SEGMENT,\n type MediaCdnScope,\n mediaCdnScopeRefusal,\n parseMediaCdnScope,\n} from '@aglyn/aglyn/app-utils/media-cdn-scope'\nimport { mediaDeliveryProvider } from '@aglyn/aglyn/plugin-manager/media-delivery-provider'\nimport {\n parseSiteIconSpec,\n SITE_ICON_BACKGROUND_PARAM,\n SITE_ICON_PARAM,\n SITE_ICON_VERSION_PARAM,\n} from '@aglyn/aglyn/app-utils/site-icon-set'\nimport type { NextApiRequest, NextApiResponse } from 'next'\nimport { analyticsDayExpiresAt } from './analytics-retention'\nimport { firebaseAdmin } from './firebase-admin'\nimport { recordMediaServe } from './media-serve-count'\nimport { getPlatformLockdown } from './lockdown'\nimport {\n engageMediaBandwidthCap,\n mediaBandwidthEvaluationDue,\n} from './media-bandwidth-cap'\nimport { mediaCdnRateLimitRefusal } from './media-cdn-rate-limit'\nimport { mediaDeliveryOrgIdFor, mediaDeliveryRedirect } from './media-delivery'\nimport {\n mediaDeliveryCopiesStale,\n regenerateMediaDeliveryCopies,\n} from './media-delivery-regeneration'\nimport { getMediaQuarantine } from './media-quarantine'\nimport { verifyMediaAccess } from './media-signing'\nimport { mediaStoragePathInScope } from './media-storage-path'\nimport { serveMediaCdnIcon } from './media-cdn-icon'\n\n/**\n * Variant widths generated at upload (AGL-175).\n *\n * Re-exported, not defined: the list now lives in `@aglyn/aglyn`'s media-ref\n * so the RENDERER can reach it too. `image.tsx` carried its own\n * `[320, 640, 1280]` literal, so a width added here never reached the srcSet.\n */\nexport { MEDIA_CDN_VARIANT_WIDTHS } from '@aglyn/aglyn/server'\n\n/**\n * The scope grammar and the refusal this handler enforces (AGL-1043).\n *\n * Re-exported, not defined: the pure rule lives in `@aglyn/aglyn`'s\n * `media-cdn-scope`, so the readers that must reach this handler's verdict —\n * the composition's video facts and the besigner canvas — call the functions\n * it calls.\n */\nexport {\n mediaCdnAllows,\n type MediaCdnScope,\n type MediaCdnScopeRefusal,\n mediaCdnScopeRefusal,\n parseMediaCdnScope,\n} from '@aglyn/aglyn/app-utils/media-cdn-scope'\n\nconst SEGMENT = MEDIA_CDN_SEGMENT\n\n/**\n * The query string to carry onto the stable URL when a stale content pin\n * redirects (AGL-2685).\n *\n * REBUILT from the four parameters this handler reads, never forwarded whole.\n * Two reasons, and the second is the one that matters:\n *\n * 1. Everything else is inert here — it would only split the CDN cache key\n * for a response that is identical either way.\n * 2. `Location` is a header, and the value would otherwise be caller-supplied\n * text. Node rejects a CR or LF in a header value rather than splitting\n * the response, so this is a second lock on a door that is already shut —\n * which is the right number of locks for a header built from user input.\n *\n * `exp` and `sig` ride along because a private asset's signature covers\n * (scope, mediaId, exp) and never the hash, so it is still valid at the\n * target. Dropping them would turn a redirect into a 404 for exactly the\n * assets whose delivery is most sensitive.\n */\nexport function mediaCdnForwardedQuery(query: NextApiRequest['query']): string {\n const params = new URLSearchParams()\n // `poster` and `r` join the set for the reason the others are in it: the\n // redirect must name the SAME representation the caller asked for, or a\n // stale content pin on a poster URL lands the browser on the master video\n // (AGL-2743).\n // `icon`, `bg` and `v` for the same reason: a site icon (AGL-3484) behind a\n // stale content pin must stay the icon, not become the full-size original.\n for (const key of [\n 'w',\n 'poster',\n 'r',\n 'download',\n SITE_ICON_PARAM,\n SITE_ICON_BACKGROUND_PARAM,\n SITE_ICON_VERSION_PARAM,\n 'exp',\n 'sig',\n 'aud',\n ] as const) {\n const raw = query[key]\n const value = Array.isArray(raw) ? raw[0] : raw\n if (value !== undefined && value !== '') params.set(key, String(value))\n }\n const encoded = params.toString()\n return encoded ? `?${encoded}` : ''\n}\n\n/**\n * The stable (non-content-hashed) URL's caching contract.\n *\n * This was `public, max-age=3600, stale-while-revalidate=86400`, and that\n * header contradicted the promise the stable URL exists to make. Measured on\n * production 2026-08-12: Vercel's edge DOES cache this route on a bare\n * `max-age` (`x-vercel-cache: MISS` then `HIT`, `age` climbing) — so the\n * saving was never in question. What a browser `max-age` breaks is\n * *propagation*. `max-age=3600` reaches the client, so a browser holding a\n * replaced asset will not send a conditional request for a full hour: the\n * ETag below never gets a chance to answer, and nothing on our side can bust\n * a browser cache. Every other cacheable route in the repo already uses\n * `s-maxage` for exactly this reason — see `seo-origin.spec.ts`, \"never a\n * browser `max-age`, which nothing could bust\".\n *\n * So: a short browser `max-age` that still collapses the burst of repeat\n * requests within one page view (an image referenced by four `srcSet`\n * candidates, a tile rendered in a grid and again in a drawer), and the full\n * hour moved to `s-maxage` where it belongs. Worst stale read is now 60 s of\n * replaced bytes in one browser rather than an hour, at the cost of one\n * conditional request per image per minute — answered by the EDGE from its\n * own copy, so it adds no Storage read and no Firestore read.\n *\n * The immutable content-hashed form is untouched: its URL changes with its\n * bytes, so it can and should be pinned in the browser for a year.\n *\n * Since AGL-1515 this policy applies to IMAGE responses only — see\n * {@link mediaCdnEdgeCacheable}.\n */\nexport const MEDIA_CDN_STABLE_CACHE_CONTROL =\n 'public, max-age=60, s-maxage=3600, stale-while-revalidate=86400'\n\n/**\n * The stable URL's policy for types the edge must never hold (AGL-1515):\n * the same 60-second browser window and the same ETag/304 contract, with\n * `private` in place of `public, s-maxage` so no shared cache stores a\n * full body.\n *\n * Why the edge must never hold one: Vercel's edge, holding a cached\n * full-body 200, answers a `Range` request FROM that entry as a\n * spec-violating hybrid — status 200, a `Content-Range` header, and only\n * the requested slice as the body (`x-vercel-cache: HIT`; reproduced twice\n * on production, 2026-08-13). A video player seeking into such an asset\n * adopts a 100-byte slice as the complete file: silent playback corruption.\n *\n * The S4 shape below this constant was built on Vercel's documented\n * cacheable-response criteria (\"Request doesn't contain Range header\"),\n * read as \"ranged requests bypass the edge\". Production falsified that\n * reading: the criteria govern what the edge STORES, not what it SERVES. A\n * ranged request is still matched against the URL-keyed entry a previous\n * plain GET left behind, and the edge's slicing layer rewrites the body\n * and adds `Content-Range` without rewriting the stored 200 status.\n *\n * `private` is the lever because it is in the same documented criteria\n * list as an absolute storage preventer (\"Response doesn't contain the\n * `private` … directives\"), where `Vary: Range` is undocumented on\n * Vercel, discouraged by RFC 9110, and untestable anywhere but a\n * production deploy. What it costs: edge caching for non-image assets\n * under Vercel's 10 MB cacheable-size cap. Real video mostly sits ABOVE\n * the cap and was never edge-cached — the mangling reproduced on a 186 KB\n * asset precisely because small ones are the ones that get cached.\n */\nexport const MEDIA_CDN_STABLE_EDGE_BYPASS_CACHE_CONTROL =\n 'private, max-age=60'\n\n/**\n * The VERSIONED stable URL's policy (AGL-3485): `?v={contentHash}.{encoder}`\n * on the stable path, answered while the token names the asset's current\n * bytes and current variants.\n *\n * The browser keeps it for a year, `immutable`, which is the whole point: the\n * 60-second window above made every repeat view of every published image a\n * conditional request, and Lighthouse reports that policy against each one.\n * It is safe for the reason the content-hashed form never was in a page\n * (AGL-2798): the page names this URL only while the token is current, the\n * composition reads the token off the media document on every render, and a\n * replace or a regeneration therefore moves the page to a new URL instead of\n * leaving it on a pinned copy of the old bytes.\n *\n * The EDGE keeps the stable URL's hour and its revalidation, deliberately not\n * a year: the edge is the copy a takedown or a lockdown has to outrun for\n * every new visitor (`media-takedown-reach.ts`), and an hour is the bound the\n * product states for it. Other visitors still get the edge's copy within that\n * hour, which is what `s-maxage` is for. Images only — every other type keeps\n * the stable policy, for the AGL-1515 reasons above.\n */\nexport const MEDIA_CDN_VERSIONED_CACHE_CONTROL =\n 'public, max-age=31536000, s-maxage=3600, stale-while-revalidate=86400, immutable'\n\n/**\n * Whether a versioned request may be answered with\n * {@link MEDIA_CDN_VERSIONED_CACHE_CONTROL} (AGL-3485).\n *\n * Only when everything the token promises is true of what is being served:\n *\n * - the token's content hash is the document's CURRENT one. A stale token is\n * served the current bytes under the stable URL's short policy — a stale\n * page shows the new picture, and nothing pins it;\n * - the representation is the original or a `?w=` variant. A poster or a\n * rendition keeps its own policy;\n * - for a `?w=` request, the variant itself is what is served. A width the\n * asset has no variant for falls back to another representation, and a\n * fallback is not something to keep for a year: the variant may be\n * generated later;\n * - and the asset's delivery copies were made by the encoder generation the\n * token names, which is the one this code runs. The media document records\n * which generation made them (`MEDIA_VARIANT_ENCODER_VERSION_FIELD`, absent\n * meaning the first). A page rendered after the encoder moved on names the\n * new generation before the asset has been regenerated; answering that\n * request with the old encode for a year would hide the regeneration behind\n * the very URL that was minted to reach it. Asked of every request, not\n * only `?w=`, because the encoder makes what the bare URL serves too.\n */\nexport function mediaCdnVersionIsCurrent(options: {\n token: MediaCdnVersion | null\n currentHash: string\n /** A `?w=` was asked for. */\n widthRequested: boolean\n /** The `?w=` variant itself is what will be served. */\n variantServed: boolean\n /** A poster or a rendition was selected instead of the image. */\n otherRepresentation: boolean\n /** The document's {@link MEDIA_VARIANT_ENCODER_VERSION_FIELD}. */\n documentEncoderVersion: unknown\n}): boolean {\n const { token, currentHash } = options\n if (!token || !currentHash || token.contentHash !== currentHash) return false\n if (options.otherRepresentation) return false\n if (options.widthRequested && !options.variantServed) return false\n const recorded = options.documentEncoderVersion\n const generation =\n typeof recorded === 'number' && Number.isInteger(recorded) && recorded > 0\n ? recorded\n : 1\n return (\n token.encoderVersion === MEDIA_VARIANT_ENCODER_VERSION &&\n generation === token.encoderVersion\n )\n}\n\n/** The immutable content-hashed URL's policy for edge-cacheable (image) types. */\nexport const MEDIA_CDN_IMMUTABLE_CACHE_CONTROL =\n 'public, max-age=31536000, immutable'\n\n/**\n * The immutable URL's policy for edge-bypassing types (AGL-1515): the\n * browser keeps its year — the URL still changes with the bytes — while\n * the edge holds nothing it could mangle.\n */\nexport const MEDIA_CDN_IMMUTABLE_EDGE_BYPASS_CACHE_CONTROL =\n 'private, max-age=31536000, immutable'\n\n/**\n * Whether a response of this content type may be edge-cached (AGL-1515).\n *\n * The split is \"types no realistic client ranges into\": browsers fetch\n * images with plain GETs (`<img>`, `srcSet`, save-as — all of them), while\n * every consumer that seeks or resumes — `<video>`/`<audio>` players, PDF\n * viewers, download managers — operates on the non-image types. Images stay\n * on the shared edge policy because they are the DAM grid's hot path (4.3 KB\n * WebP tiles at volume), and pushing them to origin to fix video would trade\n * a real regression for a theoretical one.\n *\n * Accepted residual, recorded here on purpose: a hand-built `Range` request\n * against an edge-cached IMAGE can still be answered with the hybrid (that\n * is exactly the AGL-1515 favicon repro). No browser or player issues one,\n * and the alternative — `private` on images too — costs the hot path an\n * edge hit rate it measurably has.\n *\n * Unknown or absent types return false: correctness over cache.\n */\nexport function mediaCdnEdgeCacheable(contentType: unknown): boolean {\n return String(contentType ?? '')\n .split(';')[0]\n .trim()\n .toLowerCase()\n .startsWith('image/')\n}\n\n/**\n * The CDN's OWN Content-Security-Policy (AGL-1474).\n *\n * An uploaded `image/svg+xml` is a document, not a picture. It passes the\n * `image/*` allowlist, it is stored under whatever type the client declared,\n * and this handler serves it `inline` from the console's own origin — and\n * from every tenant site's, since the same handler mounts in both apps. So\n * `<script>alert(document.domain)</script>` inside an uploaded logo executed\n * on `app.aglyn.com` the moment the asset URL was opened top-level, for\n * anyone with editor rights.\n *\n * Nothing already in place stopped it. `X-Content-Type-Options: nosniff`\n * blocks HTML *mislabelled* as an image; it says nothing about a file\n * honestly labelled `image/svg+xml`, which browsers render as a scripted\n * document. And there was no CSP on this response **at all**: since AGL-523\n * the policy is built per-response in each app's middleware, and both\n * middlewares' matchers exclude `api` (`apps/console/middleware.ts`,\n * `apps/tenant/middleware.ts`), so a directly-navigated CDN URL never passed\n * through the code that sets one.\n *\n * That is precisely why the header is set HERE, on the response, rather than\n * added to a matcher: a route-level header cannot be lost to a matcher edit,\n * a rewrite, or a new mount of `serveMediaCdn` in a third app. It is also\n * what makes this the containment rather than the remediation — it covers\n * every asset already in the bucket, including the SVGs uploaded before the\n * sanitizer existed, without rewriting a byte.\n *\n * **It does not touch `<img src>`, which is how logos and marks are used\n * across this product.** A browser loading an SVG as an image neither runs\n * its script nor applies the response's CSP — CSP governs documents and\n * workers. The policy only becomes live in the case that is the vector: the\n * asset opened as a top-level document (or framed via `<object>`/`<iframe>`).\n */\nexport const MEDIA_CDN_BASE_CSP =\n \"default-src 'none'; script-src 'none'; object-src 'none'; \" +\n \"base-uri 'none'; form-action 'none'\"\n\n/**\n * The policy for a type a browser will treat as an ACTIVE DOCUMENT. Adds\n * `sandbox` — an opaque origin, so even a hypothetical execution has no\n * `document.domain`, no cookies and no storage to reach for.\n *\n * `style-src 'unsafe-inline'` and the two `data:` allowances are not\n * concessions to script: with `script-src 'none'` and `sandbox` in force,\n * nothing in CSS or a data URI can execute. They exist so that opening a\n * logo's URL directly still shows the logo — `default-src 'none'` alone\n * would blank an SVG's own `<style>` block and its embedded raster fills,\n * which is a visible regression on legitimate assets and buys no safety.\n */\nexport const MEDIA_CDN_ACTIVE_DOCUMENT_CSP =\n `${MEDIA_CDN_BASE_CSP}; style-src 'unsafe-inline'; img-src data:; ` +\n 'font-src data:; sandbox'\n\n/**\n * Types a browser parses as a document rather than rendering as an image.\n *\n * Keying off the SERVED content type is sound only because this response also\n * carries `nosniff`: the browser is bound to the label we send, so an SVG\n * uploaded under a `image/png` label is decoded as a PNG and is inert. Every\n * type here except SVG is refused by the upload allowlist today; they are\n * listed anyway because the allowlist has moved before (AGL-1465) and because\n * a legacy fourth upload route accepted arbitrary types for its whole life\n * before AGL-1485 deleted it.\n */\nconst MEDIA_CDN_ACTIVE_DOCUMENT_TYPES = new Set([\n 'image/svg+xml',\n 'image/svg',\n 'text/html',\n 'application/xhtml+xml',\n 'application/xml',\n 'text/xml',\n 'text/xsl',\n 'application/xslt+xml',\n 'application/mathml+xml',\n])\n\n/**\n * Types this CDN will serve `inline`.\n *\n * `?download=1` has always been able to force `attachment`, but it is the\n * REQUESTER's flag — the safe disposition cannot depend on a caller asking\n * for it. Anything not on this list is served as a download whatever the\n * query says, so opening its URL top-level saves a file instead of handing\n * the type to a renderer on the customer's own domain.\n *\n * The list is what a browser has a real inline use for on this route:\n * pictures, media a player streams, and PDFs, which people expect to open in\n * a tab. Everything else — the office and text formats, JSON, ZIP, and any\n * legacy `text/html` still sitting in the bucket from before the allowlist\n * narrowed — has no inline use case here and every one of them is a document\n * a browser would happily parse.\n *\n * SVG stays INLINE deliberately. AGL-1474 chose the sandboxing CSP over a\n * forced download precisely so that opening a logo's URL still shows the\n * logo, and that policy already denies it script, an origin, and everything\n * it could reach for. Turning it into a download now would take that\n * decision back without adding to it.\n */\nfunction servesInline(contentType: string): boolean {\n const type = String(contentType ?? '')\n .split(';')[0]\n .trim()\n .toLowerCase()\n return (\n type.startsWith('image/') ||\n type.startsWith('video/') ||\n type.startsWith('audio/') ||\n type === 'application/pdf'\n )\n}\n\n/** The `Content-Security-Policy` for a response serving `contentType`. */\nexport function mediaCdnContentSecurityPolicy(contentType: string): string {\n const type = String(contentType ?? '')\n .split(';')[0]\n .trim()\n .toLowerCase()\n return MEDIA_CDN_ACTIVE_DOCUMENT_TYPES.has(type)\n ? MEDIA_CDN_ACTIVE_DOCUMENT_CSP\n : MEDIA_CDN_BASE_CSP\n}\n\n/**\n * Lockdown on the delivery path (AGL-1520).\n *\n * A security-locked org's SITE stops within seconds (the tenant middleware\n * 503s, AGL-1501) — but until this gate existed its public media kept\n * serving worldwide: to hot-links, to third-party embeds, and to the\n * infected asset's own URL, which for an \"infected host\" security lock is\n * the exact thing the lock was pressed to stop.\n *\n * **Which reasons stop delivery — decided per reason, not blanket:**\n *\n * - `security` — REFUSE. The point of the lock: the org's content, media\n * included, must stop serving.\n * - `manual` — REFUSE. A staff suspension with no reason code (every\n * pre-lockdown `suspendedAt` normalizes to `manual`) is \"we turned this\n * org off\"; content continuing to serve would make the kill partial.\n * - `maintenance` — SERVE. The maintenance notice surface may itself\n * reference org assets (a logo on the notice page), the window is\n * temporary and non-adversarial, and blanking every image buys no safety.\n * - `billing` — SERVE. The AGL-1506 principle: billing-locked orgs keep the\n * surfaces that let them come back (members can still reach billing to\n * pay). The site already 503s, so whether a hot-linked image serves is\n * nearly moot — and refusing would punish e.g. an email-signature logo\n * for a payment lapse. Serving is the cheap, reversible answer.\n *\n * Scopes are checked INDIVIDUALLY against that matrix rather than through\n * `resolveLockdown`, on purpose: the resolver answers \"which notice does a\n * visitor see\" and returns the WIDEST active scope — so a platform\n * `maintenance` window would mask a concurrent org `security` lock and the\n * infected asset would keep serving. Delivery has no notice to pick; the\n * question is only \"does ANY active lockdown demand these bytes stop\".\n */\nexport function lockdownStopsMediaDelivery(\n state: LockdownState | null | undefined,\n nowMs: number,\n): boolean {\n if (!state || !isLockdownActive(state, nowMs)) return false\n return isSecurityClassLockdownReason(state.reason) || state.reason === 'manual'\n}\n\n/**\n * **Read cost (AGL-1302):** the verdict inputs are TTL-cached in-process\n * per CDN scope — one lookup per scope per {@link MEDIA_CDN_LOCK_TTL_MS},\n * not per asset. A DAM grid firing dozens of requests coalesces into one;\n * the platform doc rides `getPlatformLockdown`'s existing 15s cache. Same\n * fail-open posture as the verdict core: an unreachable Firestore is an\n * outage, not a lockdown, and must not blank every customer image.\n *\n * That lookup is one BatchGetDocuments at org scope, and two at host scope\n * — `hosts` and `hostIndex` batched together, then the owning org, whose id\n * is what `hostIndex` returns and so cannot join the batch. The host and\n * index reads are projected to {@link SUSPENSION_FIELDS} plus the two fields\n * the bandwidth verdict needs off them: the host document is the largest in\n * the product. Measured against production, projecting the three reads and\n * batching two of them took the host branch from 3 round trips and 2,964 B to\n * 2 and 34 B. The ORG read is whole since AGL-3474, because the bandwidth\n * verdict asks the org's plan, and the plan is resolved from more fields than\n * a projection could promise to keep in step with — a mask missing one would\n * read a paying org as Free and pause its video. Still one read per scope\n * per TTL.\n *\n * **Staleness bound, stated rather than hidden:** a warm origin refuses\n * within ≤15s of the org-doc write (the platform panic number). What the\n * origin cannot reach: browsers hold the stable URL up to 60s\n * (`max-age=60`), and a versioned image URL a published page names for a\n * year in a browser that already fetched it (AGL-3485); Vercel's edge holds image responses up to `s-maxage=3600`\n * (+ one stale serve while revalidating) — so an already-edge-cached image\n * URL can serve up to ~1h into a lock; non-image types are `private`\n * (AGL-1515) and never edge-held. The immutable content-hashed form is\n * browser-pinned for a year in clients that already fetched it — there is\n * no per-asset purge API, so that copy is out of reach by design; NEW\n * fetchers of the same URL refuse at the next origin miss. Compare the\n * AGL-1501 drill: host pages flip ≤10s; assets lag minutes-to-an-hour at\n * the caching tiers, which is the accepted trade for a near-zero read cost\n * on the hottest unauthenticated path.\n *\n * **Out of this control's reach entirely:** free-tier raw\n * `firebasestorage.googleapis.com` URLs — no code of ours runs there.\n * Stated on AGL-1520, and now answered elsewhere rather than here: a\n * `security` lock rotates those objects' download tokens\n * (`media-download-tokens.ts`, AGL-1526), which kills the URL at Google's\n * edge. That is a SIBLING control, not an extension of this one — it is\n * `security`-only, it is irreversible for embeds, and like this gate it\n * stops only new origin fetches (AGL-1615). Bytes already cached anywhere\n * remain out of reach of both.\n */\nconst MEDIA_CDN_LOCK_TTL_MS = 15_000\n\n/**\n * Everything the scope documents say about serving, read once per scope per\n * {@link MEDIA_CDN_LOCK_TTL_MS}: the lockdown verdict and, off the same\n * documents, the bandwidth one (AGL-3474).\n */\ninterface MediaCdnScopeVerdict {\n /** A lockdown stops every byte (AGL-1520). */\n locked: boolean\n /**\n * The bandwidth band stops video and files: the owning org's Free cap is\n * engaged this month, or the site's abuse ceiling degrades it. The same two\n * predicates the site's own pages are paused by, so a paused site's media\n * stops with it — and only its counted media: images are inside the page\n * weight the band already measures, and a paused site serves no page that\n * could ask for one.\n */\n bandwidthPaused: boolean\n /** The owning org is on a plan the cap stops rather than bills. */\n capApplies: boolean\n /** The org that owns the scope, when it is known. */\n orgId: string | null\n}\n\n/** The verdict when nothing could be read: serve, the lockdown core's posture. */\nconst OPEN_SCOPE_VERDICT: MediaCdnScopeVerdict = {\n locked: false,\n bandwidthPaused: false,\n capApplies: false,\n orgId: null,\n}\n\nconst lockCache = new Map<string, { at: number; verdict: MediaCdnScopeVerdict }>()\nconst lockPending = new Map<string, Promise<MediaCdnScopeVerdict>>()\n\n/**\n * Drop the per-scope lock cache. Tests need it between cases; production\n * convergence is the TTL — the lock is written by the console app and served\n * by the tenant app, different processes an in-process invalidation can\n * never reach.\n */\nexport function invalidateMediaCdnLockCache(): void {\n lockCache.clear()\n lockPending.clear()\n}\n\n/**\n * Every field the delivery verdict reads off a host or org document, and\n * the projection sent to Firestore — ONE list, because the carrier below is\n * built from it. A mask that omitted a field the carrier reads would leave\n * that field `undefined` and silently soften the lock: drop\n * `suspendedReasonCode` and every lock normalizes to `manual`, which\n * REFUSES delivery for locks that should serve; drop `suspendedAt` and no\n * lock is ever seen at all. Neither failure can be introduced here without\n * changing the one array both sides use.\n *\n * `suspendedMode` and `suspendedEnforcement` are deliberately absent:\n * {@link lockdownStopsMediaDelivery} decides on `reason` and the active\n * window alone, so a read-only or takedown-class lock stops these bytes on\n * exactly the same terms as a full one.\n */\nconst SUSPENSION_FIELDS = [\n 'suspendedAt',\n 'suspendedReasonCode',\n 'suspendedUntilMs',\n] as const\n\n/** Which host owns the asset's scope — the only field read off `hostIndex`. */\nconst HOST_INDEX_ORG_FIELD = 'orgId'\n\n/** The site's abuse-ceiling stamp (AGL-2155), read beside its lockdown. */\nconst HOST_BANDWIDTH_CEILING_FIELD = 'bandwidthCeiling'\n\n/** The `suspended*` field family off a snapshot, for the normalizers. */\nconst suspensionCarrier = (snapshot: {\n get: (field: string) => unknown\n}): {\n suspendedAt?: unknown\n suspendedReasonCode?: unknown\n suspendedUntilMs?: unknown\n} =>\n Object.fromEntries(\n SUSPENSION_FIELDS.map((field) => [field, snapshot.get(field)]),\n )\n\n/**\n * The verdict an org document gives: its lockdown and, when it is not locked,\n * whether its bandwidth stops video and files. `host` is the site's projected\n * document for a host-library scope, whose abuse ceiling (AGL-2155) pauses the\n * site's own media as it pauses the site's pages; an org library belongs to\n * no site, so only the org-wide cap reaches it.\n */\nfunction orgScopeVerdict(\n org: { get: (field: string) => unknown; data: () => unknown },\n orgId: string,\n host: { get: (field: string) => unknown } | null,\n nowMs: number,\n): MediaCdnScopeVerdict {\n if (lockdownStopsMediaDelivery(normalizeOrgLockdown(suspensionCarrier(org)), nowMs)) {\n return { ...OPEN_SCOPE_VERDICT, locked: true, orgId }\n }\n const billing = (org.data() ?? null) as Partial<AglynOrgBilling> | null\n const now = new Date(nowMs)\n return {\n locked: false,\n bandwidthPaused:\n bandwidthCapEngaged(billing, now) ||\n (host !== null &&\n bandwidthCeilingDegradesHost(\n { [HOST_BANDWIDTH_CEILING_FIELD]: host.get(HOST_BANDWIDTH_CEILING_FIELD) },\n bandwidthCeilingMonthKey(now),\n billing,\n )),\n // A missing org document resolves as Free, but there is no org to total\n // or to stamp, so nothing is evaluated for it.\n capApplies: billing !== null && bandwidthCapApplies(billing),\n orgId,\n }\n}\n\n/** TTL-cached: what the documents covering `scope` say about serving it. */\nasync function mediaCdnScopeVerdict(\n scope: MediaCdnScope,\n): Promise<MediaCdnScopeVerdict> {\n const key = `${scope.isOrg ? 'org' : 'host'}:${scope.scopeId}`\n const cached = lockCache.get(key)\n if (cached && Date.now() - cached.at < MEDIA_CDN_LOCK_TTL_MS) {\n return cached.verdict\n }\n let pending = lockPending.get(key)\n if (!pending) {\n pending = (async () => {\n let verdict: MediaCdnScopeVerdict = OPEN_SCOPE_VERDICT\n try {\n const nowMs = Date.now()\n // Platform first: cached, and a platform security lock is the panic\n // button — asset delivery is part of what it stops.\n if (lockdownStopsMediaDelivery(await getPlatformLockdown(), nowMs)) {\n verdict = { ...OPEN_SCOPE_VERDICT, locked: true }\n } else if (scope.isOrg) {\n const firestore = firebaseAdmin.app().firestore()\n // Org forms (`org:{orgId}` and `org:{orgId}:{hostId}`): the org\n // doc governs. The context host's own lock is not consulted — a\n // suspended HOST's pages 503 already, and which sites may USE an\n // org asset is `visibleTo`'s question, not the lock's.\n const [org] = await firestore.getAll(\n firestore.collection('orgs').doc(scope.scopeId),\n )\n verdict = orgScopeVerdict(org, scope.scopeId, null, nowMs)\n } else {\n // Host-library form: the host's own lock, and the OWNING org's —\n // an org lock never stamps host docs (AGL-1506), so a host-only\n // read would silently miss the very lock this issue is about.\n //\n // One `getAll`, not two parallel gets: `DocumentReference.get()`\n // is `getAll([ref])`, so a `Promise.all` of two of them is two\n // BatchGetDocuments round trips where the batch is one. The org\n // read below cannot join them — its id is what `hostIndex`\n // returns — so two is the floor for this branch, not three.\n const firestore = firebaseAdmin.app().firestore()\n const [host, hostIndex] = await firestore.getAll(\n firestore.collection('hosts').doc(scope.scopeId),\n firestore.collection('hostIndex').doc(scope.scopeId),\n {\n fieldMask: [\n ...SUSPENSION_FIELDS,\n HOST_INDEX_ORG_FIELD,\n HOST_BANDWIDTH_CEILING_FIELD,\n ],\n },\n )\n const orgId = hostIndex.get(HOST_INDEX_ORG_FIELD)\n if (\n lockdownStopsMediaDelivery(\n normalizeHostLockdown(suspensionCarrier(host)),\n nowMs,\n )\n ) {\n verdict = { ...OPEN_SCOPE_VERDICT, locked: true }\n } else if (typeof orgId === 'string' && orgId) {\n const [org] = await firestore.getAll(\n firestore.collection('orgs').doc(orgId),\n )\n verdict = orgScopeVerdict(org, orgId, host, nowMs)\n }\n }\n } catch {\n // Fail open — the lockdown core's posture (see lockdown.ts): an\n // unreachable Firestore is an outage, not a lockdown, and it is not a\n // spent band either.\n verdict = OPEN_SCOPE_VERDICT\n }\n lockCache.set(key, { at: Date.now(), verdict })\n return verdict\n })().finally(() => {\n lockPending.delete(key)\n })\n lockPending.set(key, pending)\n }\n return pending\n}\n\n/** TTL-cached: does any lockdown covering `scope` stop delivery? */\nasync function mediaCdnScopeLocked(scope: MediaCdnScope): Promise<boolean> {\n return (await mediaCdnScopeVerdict(scope)).locked\n}\n\n/**\n * THE delivery-policy seam: \"may this asset be served at all?\" — one\n * question, one function, consulted once per request before any caching\n * exit. Distinct from `mediaCdnAllows` (which site may use it under this\n * URL) and from the signature check (may the public fetch it): this asks\n * whether the platform is willing to serve the bytes to ANYONE.\n *\n * Two independent reasons to refuse, both answered here so the handler\n * keeps ONE refusal path:\n *\n * - `'locked'` — a lockdown covering this SCOPE (AGL-1520): the whole org\n * or host is off.\n * - `'quarantined'` — this ASSET is on the deny list (AGL-1512): one\n * infected, abusive or DMCA-noticed file is off while everything else in\n * the same workspace keeps serving. That proportionality is the point —\n * a single bad object should not cost a customer their whole site.\n *\n * Order is scope-then-asset because the scope verdict is the cheaper cached\n * one and the wider fact; the caller does not distinguish them on the wire\n * (both are the same neutral 410), so the order is a cost decision only.\n *\n * Read cost, since this sits on the hottest unauthenticated path: the scope\n * verdict is one read per SCOPE per 15s, and the quarantine deny list is\n * one read per PROCESS per 15s for every asset in existence — it is a\n * single document. Fifty DAM tiles pay two reads between them, not a\n * hundred.\n */\nexport async function mediaCdnServeBlock(\n scope: MediaCdnScope,\n asset: {\n /**\n * The strong full-width digest (AGL-1614) when the document has one.\n * Preferred over `contentHash` as a quarantine key — it is one\n * algorithm at full width, where `contentHash` is a 64-bit truncation\n * of either sha256 or md5 depending on the ingesting route.\n */\n contentSha256?: string\n contentHash?: string\n /**\n * The raw URL scope segment and media id — the fallback quarantine key\n * for the assets that carry no `contentHash` at all (legacy uploads,\n * and composite objects GCS gives no md5 for). Without it the largest\n * files in the product would be the ones a takedown could not touch.\n */\n scopeSegment?: string\n mediaId?: string\n },\n): Promise<'locked' | 'quarantined' | null> {\n if (await mediaCdnScopeLocked(scope)) return 'locked'\n const quarantined = await getMediaQuarantine({\n contentSha256: asset.contentSha256,\n contentHash: asset.contentHash,\n scopeSegment: asset.scopeSegment,\n mediaId: asset.mediaId,\n })\n return quarantined ? 'quarantined' : null\n}\n\n/**\n * Whether `?download=1` was asked for (AGL-1411). Strictly opt-in: absent,\n * `0`, `false` or junk all keep the historical `inline`, because the default\n * is what every `<img src>` in every published site relies on.\n *\n * Only `1` and `true` count, rather than \"any truthy-looking string\". The\n * query string is part of the CDN cache key, so every accepted spelling is a\n * separate edge entry for identical bytes; two is enough.\n */\nexport function wantsMediaDownload(value: unknown): boolean {\n const raw = Array.isArray(value) ? value[0] : value\n const normalized = String(raw ?? '').toLowerCase()\n return normalized === '1' || normalized === 'true'\n}\n\n/**\n * The display copy a media document records (AGL-3486), or null when it has\n * none. Only the type is needed to serve it — the object path is derived — and\n * a record whose type is not an image is treated as no record: the display\n * copy is always an image, and serving its object under another type would be\n * a document field choosing a response's `Content-Type`.\n */\nexport function mediaCdnDisplayCopy(\n value: unknown,\n): { contentType: string } | null {\n if (!value || typeof value !== 'object') return null\n const contentType = (value as { contentType?: unknown }).contentType\n if (typeof contentType !== 'string' || !/^image\\/[a-z0-9.+-]+$/.test(contentType)) {\n return null\n }\n return { contentType }\n}\n\n/**\n * Runs `task` once the response has been sent, through Next's `after()`.\n * Required when first asked for rather than imported, for the reason\n * `capture-email-check.ts` gives; false where there is no request to run\n * after — a spec, a script — and the task is then not run at all.\n */\nfunction scheduleAfterResponse(task: () => Promise<void>): boolean {\n try {\n const loaded = require('next/server') as {\n after?: (task: () => Promise<void>) => void\n }\n if (typeof loaded?.after !== 'function') return false\n loaded.after(() =>\n task().catch((error) => {\n console.error('[media-cdn] after-response task failed', error)\n }),\n )\n return true\n } catch {\n return false\n }\n}\n\n/**\n * Whether `?poster=1` was asked for (AGL-2743).\n *\n * Deliberately the same two-spelling rule as {@link wantsMediaDownload},\n * and for the same reason rather than out of symmetry: the query string is\n * part of the CDN cache key, so every accepted spelling of \"yes\" is another\n * edge entry holding identical bytes. A poster is the response most likely\n * to be requested at volume from a grid, which makes cache-key discipline\n * worth more here than anywhere else on the route.\n */\nexport function mediaCdnWantsPoster(value: unknown): boolean {\n return wantsMediaDownload(value)\n}\n\n/**\n * The container every browser has decoded for a decade, and the encoding the\n * producer always emits (`720p`, H.264 High in MP4). WebM/VP9 is opt-in there\n * and is the only entry a client has to earn.\n */\nconst MEDIA_CDN_BASELINE_RENDITION_TYPE = 'video/mp4'\n\n/**\n * What a client said about media types, split into the two answers that\n * matter: types it NAMED, and types it explicitly refused with `q=0`.\n *\n * Wildcards are deliberately dropped rather than expanded. `video/*` and\n * `*\\/*` are a client declining to answer the question, and the browsers that\n * matter mostly decline: Chrome and Safari send `*\\/*` for a media element's\n * request and only Firefox enumerates containers. Reading `*\\/*` as \"every\n * container decodes here\" is how a Safari 13 gets handed a WebM.\n */\nfunction parseAcceptedTypes(header: unknown): {\n named: Set<string>\n refused: Set<string>\n} {\n const raw = Array.isArray(header) ? header[0] : header\n const named = new Set<string>()\n const refused = new Set<string>()\n for (const part of String(raw ?? '').split(',')) {\n const [type, ...params] = part.split(';')\n const name = type.trim().toLowerCase()\n if (!name || name.includes('*')) continue\n // `q=0` is a refusal in RFC 9110, so it removes an entry rather than\n // selecting it — the one case where naming a type argues against it.\n const isRefusal = params.some(\n (param) => /^\\s*q\\s*=/i.test(param) && Number(param.split('=')[1]) === 0,\n )\n if (isRefusal) refused.add(name)\n else named.add(name)\n }\n return { named, refused }\n}\n\n/**\n * Which rendition `?r=auto` resolves to, or undefined for \"serve the master\"\n * (AGL-2753).\n *\n * ## Why the CDN chooses at all\n *\n * Because it is the only participant holding the media document when the\n * bytes are asked for. A page cannot name a rendition — the encodings are\n * produced out of band, after the placement, and the page is ISR-cached HTML\n * that neither regenerates when one lands nor varies on a visitor's `Accept`.\n * The composition does read a placed film's document for its length and\n * shape (AGL-2807), but that answer is frozen into the cached page. The\n * alternatives were a node backfill that rewrites published screens nobody\n * edited, or naming encodings in that same frozen page. This costs neither:\n * the document is already in hand on this request.\n *\n * ## Why the stored order is not simply obeyed\n *\n * The producer writes most-efficient-codec first, which is the right order for\n * `<source>` elements — there the BROWSER picks, using what it actually knows\n * about its own decoders. A single negotiated URL has no such luxury, so\n * taking the head of the list would hand a WebM to anything that could not\n * decode one and turn a saving into a black player.\n *\n * So an entry is selected on an EXPLICIT type match, and everything else falls\n * to the MP4 baseline the producer always emits. A client that names\n * `video/webm` gets the 30%-smaller file; a client that says `*\\/*` gets the\n * universally decodable one; a client that refused every candidate gets the\n * master. Every outcome is a video that plays.\n *\n * ## Why negotiating here is safe under a cache\n *\n * A rendition response is `video/*`, so `mediaCdnEdgeCacheable` is false and\n * it is served `private` (AGL-1515) — no shared cache stores it, which is the\n * invariant `serve-media-cdn.video.spec.ts` pins. The only cache that can hold\n * one is the requesting browser's own, and a browser's `Accept` for media does\n * not change between the first request and the Range requests that follow it.\n * That matters more than it looks: negotiation on a RANGEABLE resource that\n * could flip mid-playback would stitch byte ranges from two different files\n * together, which is the AGL-1515 corruption reached by a new road.\n */\nexport function selectAutoRendition(\n renditions: readonly MediaVideoRendition[],\n acceptHeader: unknown,\n): MediaVideoRendition | undefined {\n if (renditions.length === 0) return undefined\n const { named, refused } = parseAcceptedTypes(acceptHeader)\n const typeOf = (entry: MediaVideoRendition) => entry.contentType.toLowerCase()\n return (\n renditions.find((entry) => named.has(typeOf(entry))) ??\n renditions.find(\n (entry) =>\n typeOf(entry) === MEDIA_CDN_BASELINE_RENDITION_TYPE &&\n !refused.has(MEDIA_CDN_BASELINE_RENDITION_TYPE),\n )\n )\n}\n\n/** Anything that cannot appear literally in an HTTP field-value. */\nconst NON_ASCII_FIELD_VALUE = /[^\\x20-\\x7e]/g\n/** ...and what additionally cannot appear inside a quoted-string. */\nconst NOT_QUOTED_STRING_SAFE = /[\"\\\\]/g\n\n/**\n * The download name for an asset: the stored `fileName`, reduced to its\n * basename, falling back to the media id (the URL is id-keyed and\n * extensionless, so without a name a \"save as\" lands as a nameless blob).\n */\nexport function mediaDownloadName(fileName: unknown, mediaId: string): string {\n const raw = String(fileName ?? '').trim()\n // A stored name can carry a path from whatever uploaded it; only the last\n // segment is a filename, and `..` must never reach a client's save dialog.\n const base = raw.split(/[\\\\/]/).pop() ?? ''\n // Cap by CODE POINT so a truncation can't split a surrogate pair into a\n // lone half, which is exactly the kind of thing that later throws in a\n // header encoder.\n return Array.from(base).slice(0, 200).join('') || mediaId\n}\n\n/**\n * A `Content-Disposition` value for `name` (AGL-1411).\n *\n * A filename is attacker-adjacent data — it is whatever the uploader typed —\n * and it is being pasted into a header, so a quote or a CRLF in it is a\n * header-injection vector. The old code stripped `[\"\\\\\\r\\n]` and left\n * everything else, which is safe against injection but silently mangles the\n * name and passes non-ASCII through raw: a byte above 0x7e in a header is\n * either mojibake at the client or, on a stricter encoder than ours, a\n * throw. So both forms of RFC 6266 §4.1 are emitted:\n *\n * - `filename=\"…\"` — ASCII only, every unrepresentable character replaced\n * (not dropped) so the extension and the shape of the name survive. This\n * is the fallback, and it always exists.\n * - `filename*=UTF-8''…` — RFC 8187 percent-encoding of the real name, added\n * only when the ASCII form actually lost something. Every current browser\n * prefers it, so a non-ASCII name arrives intact.\n */\nexport function mediaContentDisposition(\n name: string,\n options: { download: boolean },\n): string {\n const type = options.download ? 'attachment' : 'inline'\n const ascii = name\n .replace(NON_ASCII_FIELD_VALUE, '_')\n .replace(NOT_QUOTED_STRING_SAFE, '_')\n const header = `${type}; filename=\"${ascii}\"`\n if (ascii === name) return header\n // `encodeURIComponent` leaves `!'()*` alone, and `'`, `(`, `)` and `*` are\n // not `attr-char` (RFC 8187 §3.2.1) — a bare `'` in particular would be\n // read as the charset/language delimiter. Encode them too.\n const encoded = encodeURIComponent(name).replace(\n /['()*]/g,\n (character) =>\n `%${character.charCodeAt(0).toString(16).toUpperCase().padStart(2, '0')}`,\n )\n return `${header}; filename*=UTF-8''${encoded}`\n}\n\n/**\n * A parsed `Range` request (AGL-1442 S4). Three answers, and the difference\n * between the last two is the whole game:\n *\n * - `{ start, end }` — a single satisfiable byte range, both ends INCLUSIVE\n * (matching `createReadStream`, which is where the numbers go).\n * - `'unsatisfiable'` — syntactically a range, but nothing in it can be\n * served (`start` past EOF, `-0`, an empty object). RFC 9110 §14.1.2 says\n * this one earns a 416.\n * - `null` — everything else: no header, another unit, a multi-range, or\n * malformed syntax. RFC 9110 §14.2 lets a server ignore a Range header\n * outright, and ignoring means a full 200 — which is both the safe answer\n * and the pre-S4 behavior, so every shape this parser does not positively\n * recognise degrades to exactly what shipped before it existed.\n *\n * Multi-range requests land in `null` DELIBERATELY. Serving them needs a\n * `multipart/byteranges` body with generated boundaries; no browser sends\n * them for media (a `<video>` seek is always one range), and a 416 would be\n * wrong because a multi-range over a real file is satisfiable. A full 200\n * is the RFC-sanctioned refusal that no client can misread.\n */\nexport type MediaCdnRange = { start: number; end: number } | 'unsatisfiable' | null\n\nexport function parseMediaCdnRange(\n header: unknown,\n size: number,\n): MediaCdnRange {\n if (typeof header !== 'string') return null\n const unit = /^bytes=(.*)$/i.exec(header.trim())\n if (!unit) return null\n const specs = unit[1].split(',')\n if (specs.length !== 1) return null\n const spec = /^(\\d*)-(\\d*)$/.exec(specs[0].trim())\n if (!spec) return null\n const [, startRaw, endRaw] = spec\n if (!startRaw && !endRaw) return null\n if (!startRaw) {\n // Suffix form `bytes=-N`: the LAST N bytes. `-0` names zero bytes and a\n // 200 would over-answer it, so it is the one suffix that 416s.\n const suffix = Number(endRaw)\n if (!Number.isSafeInteger(suffix)) return null\n if (suffix === 0 || size === 0) return 'unsatisfiable'\n return { start: Math.max(0, size - suffix), end: size - 1 }\n }\n const start = Number(startRaw)\n if (!Number.isSafeInteger(start)) return null\n const end = endRaw ? Number(endRaw) : size - 1\n if (!Number.isSafeInteger(end)) return null\n // `bytes=5-2` is a syntax error, not an unsatisfiable range — ignore it.\n if (endRaw && end < start) return null\n if (start >= size) return 'unsatisfiable'\n return { start, end: Math.min(end, size - 1) }\n}\n\n/**\n * Whether a delivery ended early only because its client stopped reading\n * (AGL-2810).\n *\n * A `<video>` element abandons its opening request once it has buffered\n * enough, and abandons another on every seek, so this is the ordinary way a\n * streamed response ends early — and it is not a delivery failure, so it is\n * neither logged nor answered.\n *\n * `pipeline` reports it as a premature close, and reports a Storage read that\n * closed early the same way. The response tells them apart: a client that\n * canceled destroyed it without an error, while a failed read makes\n * `pipeline` destroy it WITH the read's error.\n */\nfunction mediaCdnClientWentAway(\n error: unknown,\n res: { errored?: Error | null },\n): boolean {\n return (\n (error as { code?: unknown } | null)?.code === 'ERR_STREAM_PREMATURE_CLOSE' &&\n !res.errored\n )\n}\n\n/**\n * The headers that describe the file, removed from an answer that will not\n * carry it.\n *\n * A failure found after the representation was chosen would otherwise go out\n * under the file's own headers: a `Content-Length` of the whole object on a\n * short JSON body leaves the client waiting for bytes that never come, a\n * `video/mp4` type misdescribes the body outright, and the validator would\n * attach the file's identity to an error. `no-store` because the answer is\n * about this request, not the URL.\n */\nfunction clearMediaCdnRepresentation(res: NextApiResponse): void {\n for (const header of [\n 'Content-Type',\n 'Content-Length',\n 'Content-Range',\n 'Content-Disposition',\n 'Accept-Ranges',\n 'ETag',\n ]) {\n res.removeHeader(header)\n }\n res.setHeader('Cache-Control', 'no-store')\n}\n\n/**\n * Note `bytes` of counted media against the scope's org on this instance, and\n * when that makes it due, total the org's month and engage the Free cap\n * (AGL-3474; see `media-bandwidth-cap.ts`).\n *\n * Only for an org whose plan the cap stops and that is not already paused,\n * both read off the cached scope verdict, so a paying org's delivery reads\n * nothing for it. Never rejects.\n */\nasync function mediaCdnBandwidthEvaluation(\n scope: MediaCdnScope,\n bytes: number,\n): Promise<void> {\n const verdict = await mediaCdnScopeVerdict(scope)\n if (!verdict.capApplies || verdict.bandwidthPaused || !verdict.orgId) return\n if (!mediaBandwidthEvaluationDue(verdict.orgId, bytes)) return\n await engageMediaBandwidthCap(verdict.orgId)\n}\n\n/**\n * CDN media delivery (AGL-175 / AGL-829). Two URL shapes resolve the same\n * asset by `mediaId`, so delivery never depends on the object's storage\n * location (folder moves don't change the URL):\n *\n * - **Stable** `/api/media/cdn/[scope]/[mediaId]` — always serves the\n * asset's CURRENT bytes, so it survives a **replace** too. Revalidated\n * with an ETag (the content hash) + `stale-while-revalidate`, so a\n * replaced asset propagates without ever breaking references. This is\n * the URL the console hands out.\n * - **Immutable** `/api/media/cdn/[scope]/[mediaId]/[contentHash]` —\n * year-long `immutable` cache; bytes are served only while the hash\n * matches the current content, and a stale hash 302s to the stable URL\n * (AGL-2685). No renderer names this form (AGL-2798): an edge or a browser\n * holding an immutable response never asks again, so a replace could not\n * reach a page that did. It is answered for the URLs already handed out.\n *\n * `?w=[width]` selects a generated WebP variant. `?download=1` swaps the\n * `Content-Disposition` from `inline` to `attachment` (AGL-1411) — the press\n * kit hands out links that must SAVE rather than open a tab. Both are read\n * only after every access gate, so neither can widen what is served; both\n * are part of the CDN cache key, so neither variant can poison the other.\n * The same handler mounts in both the tenant and console apps; raw storage\n * URLs already embedded in screens keep working unchanged.\n *\n * Single byte-range requests are honored with a 206 (AGL-1442 S4) — see\n * {@link parseMediaCdnRange} and the block below `Accept-Ranges` for the\n * exact semantics. This is the capability whose absence kept video on raw\n * `firebasestorage.googleapis.com` URLs (S8): a `<video>` seek is a Range\n * request, and a server that ignores it forces the player to re-download\n * the whole file.\n *\n * The body streams (AGL-2810): each chunk leaves as Storage produces it, so no\n * file is held whole in function memory. A GET that will read the file is\n * first counted against its caller (AGL-2812); see\n * {@link mediaCdnRateLimitRefusal}.\n */\nexport async function serveMediaCdn(\n req: NextApiRequest,\n res: NextApiResponse,\n): Promise<void> {\n // AGL-1474, set FIRST so no exit from this handler can be reached without\n // it — including the refusals, the 304 and the 500. Upgraded below to the\n // sandboxing form once the served content type is known. `nosniff` is\n // repeated here rather than left to the app config's `/(.*)` header: the\n // policy above keys off the declared type, so this response has to carry\n // the header that makes the declared type binding, on its own.\n res.setHeader('Content-Security-Policy', MEDIA_CDN_BASE_CSP)\n res.setHeader('X-Content-Type-Options', 'nosniff')\n if (req.method !== 'GET' && req.method !== 'HEAD') {\n res.setHeader('Allow', 'GET, HEAD')\n res.status(405).end()\n return\n }\n const path = Array.isArray(req.query['path']) ? req.query['path'] : []\n const [scopeSegment, mediaId, hash] = path.map((value) =>\n String(value ?? ''),\n )\n const scope = parseMediaCdnScope(scopeSegment)\n const isOrg = scope?.isOrg ?? false\n const scopeId = scope?.scopeId ?? ''\n // 3 segments = the immutable content-hashed URL; 2 = the stable URL.\n const hashed = path.length === 3\n if (\n !scope ||\n (path.length !== 2 && path.length !== 3) ||\n !SEGMENT.test(mediaId) ||\n (hashed && !SEGMENT.test(hash))\n ) {\n res.status(400).json({ error: 'Bad media path' })\n return\n }\n\n try {\n const firestore = firebaseAdmin.app().firestore()\n const snapshot = await firestore\n .collection(isOrg ? 'orgs' : 'hosts')\n .doc(scopeId)\n .collection('media')\n .doc(mediaId)\n .get()\n const currentHash = String(snapshot.get('contentHash') ?? '')\n if (!snapshot.exists || snapshot.get('deletedAt')) {\n res.setHeader('Cache-Control', 'public, max-age=60')\n res.status(404).json({ error: 'Not found' })\n return\n }\n // Delivery gate — scope lockdown (AGL-1520) and asset quarantine\n // (AGL-1512). See mediaCdnServeBlock for the reason matrix, the\n // read-cost accounting and the staleness bound. Three shape decisions,\n // argued, and all three apply to BOTH refusal kinds:\n //\n // **Before the ETag/304 exit**, necessarily: a browser revalidating its\n // 60s copy during a lock or a quarantine must be refused, not handed a\n // 304 that renews the copy for another minute.\n //\n // **410, not 423, and identical for both kinds.** The 423 body is the\n // AGL-1506 discipline for authenticated API consumers who deserve\n // \"suspended: billing\"; this response answers anonymous `<img>` fetches\n // nobody parses, and a 423 would confirm to any prober that a lock\n // exists — which for a security lock is information. It is information\n // for a quarantine too, and of a worse kind: the existence of a DMCA\n // notice or a malware finding on a specific file is not something an\n // anonymous fetcher has standing to learn, and telling quarantine apart\n // from lockdown on the wire would leak which one it was. So the owning\n // org learns the reason in the console (`mediaQuarantineNotice`), and\n // the CDN says only `410 Gone` — neutral, indistinguishable in kind\n // from a deleted asset, and a hint to well-behaved consumers to drop\n // the link. (404 would also be neutral, but 410's \"permanently gone\"\n // semantics discourage retry loops from embedders.)\n //\n // **`no-store`, absolutely** (the AGL-1515 lesson): the refusal is a\n // function of a TTL'd verdict, not of the URL — a cached refusal would\n // weld the asset's URL identity to \"gone\" past the unlock, and edge\n // entries have no per-asset purge. The 60s negative window the 404s use\n // is not acceptable here either. For quarantine that argument is\n // strictly stronger than for lockdown: REVERSIBILITY is the whole\n // reason quarantine exists instead of deletion, so a cached 410 that\n // outlived the lift would defeat the feature's only advantage over the\n // irreversible option. The refusal is also nearly free to re-serve —\n // the verdict is one Firestore read per process per 15s for the entire\n // deny list, and the body is a few bytes with no Storage read — so\n // there is no saving to weigh against it. CSP + nosniff are already on\n // the response (set first, before any exit).\n const blockedBy = await mediaCdnServeBlock(scope, {\n // The strong digest is read for the quarantine key ONLY. It is\n // deliberately not the ETag and not the immutable URL segment\n // (AGL-1614): changing either of those would change live URLs and\n // cache validators for every existing asset, and this change must be\n // additive to the point of being invisible on the wire.\n contentSha256: String(snapshot.get('contentSha256') ?? ''),\n contentHash: currentHash,\n scopeSegment,\n mediaId,\n })\n if (blockedBy) {\n res.setHeader('Cache-Control', 'no-store')\n res.status(410).json({ error: 'Gone' })\n return\n }\n // Scope check (AGL-1043). The bare `org:` form serves ORG-WIDE assets\n // only; a restricted asset must be requested through the form that\n // names the site using it, which is when AGL-1045's confirmation already\n // warns which pages are affected.\n //\n // An asset carrying no `visibleTo` is refused here rather than treated as\n // org-wide: absent is \"nobody has said who may see this\", not \"everybody\"\n // (`app-utils/scope-tokens`). The scope backfill stamps such documents and\n // the weekly drift detector reports the ones it has not reached, so the\n // repair is a stamp rather than a wider read.\n //\n // 404 rather than 403: whether a restricted asset exists is itself\n // something the caller has no standing to learn. Short max-age so\n // re-widening a scope propagates quickly instead of being pinned by a\n // long-lived negative cache entry.\n const refusal = mediaCdnScopeRefusal(scope, snapshot.get('visibleTo'))\n if (refusal) {\n /**\n * A `restricted` refusal is the gate WORKING and is deliberately\n * silent: it is the ordinary consequence of an internal asset's URL\n * being shared outside the site it belongs to, it can be provoked by\n * anyone who can guess a scope segment, and a log line per anonymous\n * request would make this route amplify traffic into the drain.\n *\n * The other two cannot be provoked from outside — they are properties\n * of the document, not of the request — and they are the ones nothing\n * else says out loud. An asset undeliverable under every URL form\n * shows up as an image that stopped rendering on a live site, and\n * until the weekly drift detector runs the only trace it leaves is a\n * 404 indistinguishable from a deleted asset. Naming it here is what\n * makes the next occurrence readable from the logs.\n */\n if (refusal !== 'restricted') {\n console.error(\n '[media-cdn] asset has no site it can be served to',\n JSON.stringify({ scopeId, mediaId, refusal }),\n )\n }\n res.setHeader('Cache-Control', 'public, max-age=60')\n res.status(404).json({ error: 'Not found' })\n return\n }\n /**\n * Private assets (AGL-1051) leave the public model entirely: no\n * `cdnPath`, and no bytes without an unexpired signature. This is a\n * different question from the scope check above — that one asks which\n * SITE may use the asset, this one asks whether the public may fetch\n * it at all.\n *\n * `no-store`, not a short max-age. Every other response here is safe to\n * cache because it is the same answer for everyone, but a signed\n * request is per-caller and time-boxed: letting a shared cache keep\n * either the denial OR the bytes would outlive the signature and hand\n * the asset to the next requester on the same URL.\n *\n * Which is why the header goes through `setCacheControl` from here on\n * rather than `res.setHeader` — the private case has to win at EVERY\n * exit, and remembering that at each of six call sites is precisely\n * the kind of thing that gets missed when a seventh is added. It\n * already was: the stale-hash 404 below used to overwrite `no-store`\n * with `public, max-age=60`.\n */\n const isPrivate = snapshot.get('private') === true\n const setCacheControl = (value: string) => {\n res.setHeader('Cache-Control', isPrivate ? 'private, no-store' : value)\n }\n if (isPrivate) {\n const signed = verifyMediaAccess(scopeSegment, mediaId, {\n exp: Number(req.query['exp'] ?? 0),\n sig: String(req.query['sig'] ?? ''),\n aud: req.query['aud'] === 'team' ? 'team' : undefined,\n })\n setCacheControl('private, no-store')\n if (!signed) {\n res.status(404).json({ error: 'Not found' })\n return\n }\n }\n /*\n * WHAT COUNTS TOWARD THE BANDWIDTH BAND (AGL-3474).\n *\n * Every type the edge never holds, because each such response leaves from\n * origin. A public image is the exception: it is inside the page weight\n * the band already measures, and the edge serves most of it. A PRIVATE\n * image is not — it cannot be placed on a page, and `no-store` sends every\n * request to origin — so it counts like a file. The one serve that never\n * counts is the workspace previewing its own private library in the\n * console: its signature names the team (`aud=team`, inside the HMAC, so\n * a visitor's link cannot claim it).\n */\n const teamPreview = isPrivate && req.query['aud'] === 'team'\n const countsTowardBand = (type: unknown) =>\n !teamPreview && (isPrivate || !mediaCdnEdgeCacheable(type))\n /**\n * A stale hash on the immutable form REDIRECTS to the stable URL\n * (AGL-2685). It used to 404.\n *\n * The invariant the 404 protected is intact and is the reason this is a\n * redirect rather than a body: **no version of this URL ever serves bytes\n * that are not the ones its hash names.** A stale one now serves a\n * pointer to the URL that is allowed to change instead of a dead end.\n *\n * What the 404 cost was every copy of this URL breaking on the first\n * **replace** of its asset. With a redirect, a stale URL that reaches\n * this handler costs one extra hop and self-heals. It does NOT make the\n * form safe to publish: an edge or a browser already holding the\n * immutable response answers from its copy and never reaches this line,\n * which is why no renderer names this form (AGL-2798).\n *\n * **302, not 301.** A hash can become current again — a replace reverted\n * to the previous bytes restores it — and a permanent redirect is exactly\n * the thing browsers refuse to forget.\n *\n * **The stable URL's own cache policy**, not a shorter one. The redirect\n * is as re-checkable as the resource it points at, and it is the same\n * answer for every caller, so there is no reason for the edge to hold it\n * less well than it holds the target. `setCacheControl` still forces\n * `private, no-store` for a signed asset.\n *\n * The query string is REBUILT from the parameters this handler reads\n * rather than forwarded: `?w=`, `?download=` and the signature pair are\n * the whole set, they are already validated by the code below, and\n * echoing arbitrary caller input into a `Location` header is a habit\n * worth not having. Dropping an unknown parameter costs a cache-key\n * split and nothing else.\n */\n if (hashed && currentHash !== hash) {\n setCacheControl(MEDIA_CDN_STABLE_CACHE_CONTROL)\n res.setHeader(\n 'Location',\n `${MEDIA_CDN_ROUTE}/${scopeSegment}/${mediaId}${mediaCdnForwardedQuery(req.query)}`,\n )\n res.status(302).end()\n return\n }\n\n /*\n * A site icon (AGL-3484): one size of the favicon, touch icon or manifest\n * set, drawn from this asset's bytes. Past every gate above and ahead of\n * every other representation, which it excludes — see `media-cdn-icon.ts`.\n */\n const iconSpec = parseSiteIconSpec(req.query[SITE_ICON_PARAM])\n if (iconSpec) {\n const iconBase = `${isOrg ? 'orgs' : 'hosts'}/${scopeId}`\n const iconFile = firebaseAdmin\n .app()\n .storage()\n .bucket(process.env['NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET'])\n .file(\n mediaStoragePathInScope({\n storagePath: snapshot.get('storagePath'),\n base: iconBase,\n mediaId,\n }),\n )\n await serveMediaCdnIcon({\n req,\n res,\n spec: iconSpec,\n source: {\n contentType: snapshot.get('contentType'),\n contentHash: currentHash,\n stablePath: `${MEDIA_CDN_ROUTE}/${scopeSegment}/${mediaId}`,\n file: iconFile as never,\n },\n setCacheControl,\n cacheControl: {\n stable: MEDIA_CDN_STABLE_CACHE_CONTROL,\n immutable: MEDIA_CDN_IMMUTABLE_CACHE_CONTROL,\n },\n rateLimit: () =>\n mediaCdnRateLimitRefusal({\n headers: req.headers,\n remoteAddress: req.socket?.remoteAddress,\n rateClass: 'image',\n }),\n })\n return\n }\n\n const width = Number(req.query['w'] ?? 0)\n const variants: number[] = snapshot.get('variants') ?? []\n /*\n * WHICH REPRESENTATION OF THIS ASSET (AGL-2743).\n *\n * Three derived forms now sit behind one media id — the image variants\n * `?w=` has selected since AGL-175, a video's poster still, and its\n * encoded renditions. They are resolved here, together, because they are\n * mutually exclusive and because every one of them has to reach the ETag,\n * the cache tier and the object path in agreement. A second lookup later\n * in the handler is how a poster ends up cached under the master's\n * validator.\n *\n * Precedence is poster, then rendition, then width, and it is total: a\n * caller sending `?poster=1&r=720p` gets the poster rather than an error,\n * because the parameters are a REQUEST for a representation and the only\n * useful answer to an incoherent one is the safest coherent one.\n *\n * ## Falling back to the master is right for a WIDTH and wrong for a POSTER\n *\n * `?w=` on an asset with no such variant serves the original, and that has\n * always been safe because both answers are the same KIND of thing: an\n * image, larger than asked for. A rendition inherits it for the same\n * reason — an unknown `?r=` serves the master, which is the same video in\n * more bytes.\n *\n * `?poster=1` cannot. The caller asked for a still and the master is a\n * film, so the fallback would answer a `<video poster>` with 60 MB of\n * `video/mp4` — a request for 40 KB satisfied with the entire asset,\n * which is the precise cost this feature exists to remove, delivered by\n * the feature itself. So a poster that does not exist is a **404**, and\n * that is the better answer in the browser too: a `<video>` whose\n * `poster` 404s behaves exactly like a `<video>` with no `poster`, which\n * is what every video did before AGL-2742. It also lets a renderer emit\n * the attribute without first reading the document — the property the\n * fallback was supposed to buy, bought honestly.\n */\n const poster = snapshot.get('poster')\n const posterVariants: number[] = poster?.variants ?? []\n const wantsPoster = mediaCdnWantsPoster(req.query[MEDIA_CDN_POSTER_PARAM])\n if (wantsPoster && !poster) {\n // See above: a still is not a film, so this is the one representation\n // that must refuse rather than degrade. `no-store` because the answer\n // is about a DOCUMENT field that a poster backfill can change at any\n // time, and a cached 404 would outlive the fix.\n setCacheControl('private, no-store')\n res.status(404).json({ error: 'No poster' })\n return\n }\n const usePoster = wantsPoster\n const usePosterVariant = usePoster && Boolean(width) && posterVariants.includes(width)\n const renditionParam = req.query[MEDIA_CDN_RENDITION_PARAM]\n const renditionKey = String(\n Array.isArray(renditionParam) ? renditionParam[0] : (renditionParam ?? ''),\n )\n /*\n * `?r=auto` is the form a PAGE emits (AGL-2753), because a page cannot\n * know which encodings an asset has — they are produced out of band, long\n * after the placement. `selectAutoRendition` answers from the document\n * already read above, so serving the encodings costs no extra read\n * anywhere and a rendition made today reaches a video placed last month.\n *\n * An explicit key stays exact and stays supported: the producer's report\n * and the specs address one encoding by name, and a debugging request for\n * `?r=720p-vp9` should get that file or the master, never a negotiation.\n */\n const negotiated = !usePoster && renditionKey === MEDIA_CDN_RENDITION_AUTO\n const availableRenditions = usePoster\n ? []\n : parseMediaRenditions(snapshot.get('videoRenditions'))\n const rendition = usePoster\n ? undefined\n : negotiated\n ? selectAutoRendition(availableRenditions, req.headers['accept'])\n : availableRenditions.find((entry) => entry.key === renditionKey)\n if (negotiated) {\n // Declared for correctness rather than as the thing keeping this safe.\n // What keeps it safe is `private` (AGL-1515): a video response is never\n // held by a shared cache, so there is no entry for a second client's\n // `Accept` to be answered from. This tells the BROWSER's own cache — and\n // any intermediary that ignored `private` — that the URL alone does not\n // identify the bytes. Set before the 304 exit below, because a validator\n // handed out without it would be reused across representations.\n res.setHeader('Vary', 'Accept')\n }\n const useVariant =\n !usePoster && !rendition && Boolean(width) && variants.includes(width)\n // Read only here, past every gate — a refusal above returns before the\n // parameter is ever looked at, so `?download=1` can never be the reason\n // a response happens (AGL-1411).\n const download = wantsMediaDownload(req.query['download'])\n /*\n * THE DISPLAY COPY (AGL-3486).\n *\n * An image whose original is larger than the delivery edge, carries EXIF,\n * XMP or IPTC, or leans on an EXIF orientation has a display copy: the\n * same format, downscaled, upright and stripped. It answers every INLINE\n * request that is not a variant — the bare URL, and a `?w=` the asset has\n * no variant for, which used to fall back to the original. That is the\n * request a link preview, an email client and a CSS background make, and\n * it used to hand them a 6 MB photo with the GPS position it was taken at.\n *\n * `?download=1` still serves the ORIGINAL, byte for byte: Download file is\n * a promise the library makes, and it is the one request that asks for the\n * file rather than for a picture of it.\n */\n const display = mediaCdnDisplayCopy(snapshot.get('display'))\n const useDisplay =\n !usePoster && !rendition && !useVariant && !download && display !== null\n // The generation that made the variant or display copy, in its validator:\n // a regeneration changes the bytes behind the same URL, and a browser\n // revalidating the old encode must be sent the new one, not a 304.\n const encoderVersion = mediaVariantEncoderVersionOf(\n snapshot.get(MEDIA_VARIANT_ENCODER_VERSION_FIELD),\n )\n const encoderTag = encoderVersion > 1 ? `-e${encoderVersion}` : ''\n /**\n * The ETag's representation tag, and the cache key's conscience.\n *\n * A validator is not scoped to a URL in practice — the reasoning the\n * `-dl` suffix below was added for — so two representations of one asset\n * that share a validator can be swapped for each other by any cache that\n * revalidates. Between an image variant and its original that is a wrong\n * size; between a poster and its video it is a 40 KB still standing in\n * for a 40 MB film, or the reverse.\n */\n const representation = usePoster\n ? `-poster${usePosterVariant ? `-w${width}` : ''}`\n : rendition\n ? `-r${rendition.key}`\n : useVariant\n ? `-w${width}${encoderTag}`\n : useDisplay\n ? `-d${encoderTag}`\n : ''\n\n // Stable URL: revalidate against an ETag so a replaced asset is picked\n // up (a conditional GET returns 304 while the content is unchanged).\n //\n // The disposition is part of the ETag for the same reason the variant\n // width is. The query string is already part of the CDN cache key, so\n // the two URLs are separate edge entries — but a validator is not scoped\n // to a URL in practice: a client holding the inline ETag that revalidates\n // the download URL would be answered 304 and reuse its stored INLINE\n // headers, and the file would open in a tab anyway. Same bytes, different\n // representation, so: different validator.\n const etag = currentHash\n ? `\"${currentHash}${representation}${download ? '-dl' : ''}\"`\n : null\n // AGL-1515: which cache tier may hold this response is a function of the\n // served type — see mediaCdnEdgeCacheable. Decided here from the DOC's\n // type (a variant serve is always `image/webp`) because the 304 exit\n // below runs before the Storage metadata read; re-derived from the\n // authoritative served type once metadata is in hand.\n //\n // A VERSIONED request (AGL-3485) whose token is current is the one image\n // response a browser may keep for a year; see\n // `MEDIA_CDN_VERSIONED_CACHE_CONTROL`. The hashed path form keeps its own\n // policy below, so a `?v=` on it is inert.\n const versionCurrent =\n !hashed &&\n mediaCdnVersionIsCurrent({\n token: parseMediaCdnVersionToken(req.query[MEDIA_CDN_VERSION_PARAM]),\n currentHash,\n widthRequested: Boolean(width),\n variantServed: useVariant,\n otherRepresentation: usePoster || Boolean(rendition),\n documentEncoderVersion: snapshot.get(\n MEDIA_VARIANT_ENCODER_VERSION_FIELD,\n ),\n })\n const stableCacheControlFor = (type: unknown) =>\n mediaCdnEdgeCacheable(type)\n ? versionCurrent\n ? MEDIA_CDN_VERSIONED_CACHE_CONTROL\n : MEDIA_CDN_STABLE_CACHE_CONTROL\n : MEDIA_CDN_STABLE_EDGE_BYPASS_CACHE_CONTROL\n // A poster is `image/webp` and therefore edge-cacheable through the rule\n // that already exists — which is the whole delivery argument for it\n // (AGL-2743). `mediaCdnEdgeCacheable` is untouched: nothing here relaxes\n // AGL-1515, because a poster genuinely IS an image and no player ranges\n // into a `<video poster>`. A RENDITION reports its own `video/*` type and\n // stays origin-only, deliberately — a small fixed-size rendition is\n // exactly the size class Vercel's 10 MB cap admits, so edge-caching one\n // would maximize exposure to the very hybrid AGL-1515 recorded rather\n // than avoid it.\n const docServedType =\n usePoster || useVariant\n ? 'image/webp'\n : rendition\n ? rendition.contentType\n : useDisplay\n ? display.contentType\n : snapshot.get('contentType')\n /*\n * THE BANDWIDTH BAND (AGL-3474).\n *\n * Video and files count against the org's bandwidth allowance (see\n * `media-bandwidth.ts`), so they stop where the band stops: when the Free\n * cap is engaged for the owning org, or the site's abuse ceiling degrades\n * it — the same two predicates that pause the site's pages, read off the\n * scope documents the lockdown verdict above already holds. No read is\n * added.\n *\n * Public images keep serving. They are not counted here, a paused site\n * serves no page that asks for one, and the ones the edge holds would\n * keep serving from it whatever this said. A private image counts, so it\n * stops with the band like a file; the console's own preview does not.\n *\n * Before the provider redirect, which would otherwise hand a paused org's\n * film to the provider, and before the 304, which would renew a browser's\n * copy for another minute. `503` with `Retry-After` and `no-store`, the\n * shape the paused site's pages answer with: the pause lifts on an\n * upgrade or at the month's end, and nothing may keep the refusal past\n * either.\n */\n if (\n countsTowardBand(docServedType) &&\n (await mediaCdnScopeVerdict(scope)).bandwidthPaused\n ) {\n res.setHeader('Cache-Control', 'no-store')\n res.setHeader('Retry-After', String(BANDWIDTH_CAP_RETRY_AFTER_SECONDS))\n res.status(503).json({ error: 'Over the monthly traffic limit' })\n return\n }\n /*\n * VIDEO FROM THE DELIVERY PROVIDER (AGL-2824).\n *\n * Every gate above has run: the scope, lockdown and quarantine, the\n * private signature and the stale-pin redirect. With the representation\n * chosen, a video whose current copy sits at the configured delivery\n * provider is answered with a `302` to a short-lived signed URL there, and\n * the provider serves the bytes and their ranges. `mediaDeliveryRedirect`\n * says null for everything else — an image, a poster, no copy of this\n * representation made from the current bytes, the release flag off for\n * the owning org — and the request carries on below exactly as before.\n *\n * With no provider configured to deliver, nothing here runs: the check is\n * synchronous and reads nothing.\n *\n * A download stays here, because its disposition is this route's to set.\n * The redirect is decided before the ETag and the 304: it names a URL\n * that expires, so it carries no validator and `private, no-store`, and a\n * private asset's URL expires no later than the signature that reached\n * it. It is not counted against the caller's delivery budget, which\n * guards the bytes this route sends; a redirect sends none.\n */\n const deliveryProvider =\n usePoster || useVariant || useDisplay || download\n ? null\n : mediaDeliveryProvider('deliver')\n if (deliveryProvider) {\n const delivery = await mediaDeliveryRedirect({\n provider: deliveryProvider,\n asset: { collection: isOrg ? 'orgs' : 'hosts', scopeId, mediaId },\n document: snapshot,\n rendition,\n servedType: docServedType,\n claims: {\n scope: scopeSegment,\n mediaId,\n hostId: isOrg ? (scope.contextHostId ?? null) : scopeId,\n },\n orgId: () => mediaDeliveryOrgIdFor(isOrg ? 'orgs' : 'hosts', scopeId),\n ...(isPrivate ? { notAfterMs: Number(req.query['exp']) } : {}),\n }).catch((error) => {\n // The provider is an alternative to the path below, never a reason\n // for this request to fail.\n console.error('[media-cdn] delivery redirect failed', scopeSegment, mediaId, error)\n return null\n })\n if (delivery) {\n res.setHeader('Cache-Control', 'private, no-store')\n res.setHeader('Location', delivery.location)\n // A play starts here, so it is counted here. `redirects` says how many\n // of the serves left through the provider; the per-asset `bytes` stay\n // the provider's to measure, because none leave from this route.\n //\n // The bandwidth band is not the provider's (AGL-3474): moving a film\n // off origin must not move it off the meter. A player sends every\n // range of a sitting to the URL it was redirected to, so this request\n // is the only one of the sitting that reaches us, and it is counted\n // at the copy's full size — the most one sitting sends, whatever range\n // a player opens with.\n let evaluation: Promise<void> | null = null\n let recorded: Promise<unknown> | null = null\n if (req.method === 'GET') {\n const bandwidthBytes = teamPreview ? 0 : delivery.sizeBytes\n recorded = recordMediaServe({\n firestore,\n collection: isOrg ? 'orgs' : 'hosts',\n scopeId,\n mediaId,\n bandwidthBytes,\n redirect: true,\n })\n evaluation = mediaCdnBandwidthEvaluation(scope, bandwidthBytes)\n }\n res.status(302).end()\n // Awaited after the response, as the evaluation is, so the function\n // lives until the count is written rather than being frozen with it\n // in flight.\n await Promise.all([recorded, evaluation])\n return\n }\n }\n /*\n * LAZY REGENERATION (AGL-3486) — see `media-delivery-regeneration.ts`.\n *\n * An asset whose delivery copies an older encoder made is answered from\n * what exists, and regenerated once the response has gone. Scheduled\n * before the 304 exit, because a browser revalidating its copy is a view\n * of the asset as much as a download is.\n */\n if (\n req.method === 'GET' &&\n mediaDeliveryCopiesStale({\n contentType: snapshot.get('contentType'),\n cdnPath: snapshot.get('cdnPath'),\n sizeBytes: snapshot.get('sizeBytes'),\n width: snapshot.get('width'),\n [MEDIA_VARIANT_ENCODER_VERSION_FIELD]: snapshot.get(\n MEDIA_VARIANT_ENCODER_VERSION_FIELD,\n ),\n variantRegeneration: snapshot.get('variantRegeneration'),\n })\n ) {\n scheduleAfterResponse(async () => {\n const regenerationPath = mediaStoragePathInScope({\n storagePath: snapshot.get('storagePath'),\n base: `${isOrg ? 'orgs' : 'hosts'}/${scopeId}`,\n mediaId,\n onRefused: () => undefined,\n })\n const outcome = await regenerateMediaDeliveryCopies({\n docRef: snapshot.ref,\n bucket: firebaseAdmin\n .app()\n .storage()\n .bucket(process.env['NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET']),\n basePath: regenerationPath,\n })\n if (outcome === 'failed') {\n console.error('[media-cdn] delivery copies could not be regenerated', scopeSegment, mediaId)\n }\n })\n }\n if (!hashed) {\n setCacheControl(stableCacheControlFor(docServedType))\n if (etag) res.setHeader('ETag', etag)\n if (etag && req.headers['if-none-match'] === etag) {\n res.status(304).end()\n return\n }\n }\n\n const bucket = firebaseAdmin\n .app()\n .storage()\n .bucket(process.env['NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET'])\n // Real folders: the doc records its object path; legacy assets fall\n // back to the flat layout.\n //\n // The recorded path is CLIENT DATA and this is an ADMIN-SDK bucket read,\n // which the Storage rules do not constrain — so it is honoured only when\n // it is genuinely inside this scope's media prefix (AGL-1881). Without\n // that check a host or org editor could point their own media document at\n // any key in the shared bucket and have this route stream it to an\n // anonymous caller behind a `s-maxage=3600` edge-cached URL, including the\n // fixed `adminAudit-archive/` and `erasures/` retention prefixes.\n const basePath = mediaStoragePathInScope({\n storagePath: snapshot.get('storagePath'),\n base: `${isOrg ? 'orgs' : 'hosts'}/${scopeId}`,\n mediaId,\n onRefused: (candidate) => {\n console.error(\n '[media-cdn] out-of-scope storagePath refused',\n JSON.stringify({ scopeId, mediaId, candidate }),\n )\n },\n })\n // Every derived path is built from `basePath`, which `mediaStoragePathInScope`\n // has already proved is inside this scope's prefix (AGL-1881), plus a\n // suffix whose grammar admits no separator: the widths are numbers,\n // and `parseMediaRendition` refuses a key or extension that is not\n // `[a-z0-9-]`. So no representation can address an object the master\n // could not.\n const objectPath = usePoster\n ? usePosterVariant\n ? `${basePath}${MEDIA_POSTER_OBJECT_SUFFIX}__w${width}.webp`\n : mediaPosterObjectPath(basePath)\n : rendition\n ? mediaRenditionObjectPath(basePath, rendition)\n : useVariant\n ? `${basePath}__w${width}.webp`\n : useDisplay\n ? mediaDisplayObjectPath(basePath)\n : basePath\n let file = bucket.file(objectPath)\n // The caller's count (AGL-2812), asked here and nowhere earlier: past every\n // gate and the 304 exit, where the Storage reads begin. It starts before the\n // metadata read and is awaited after it, so the two run together. It never\n // rejects, and answers `null` whenever it could not count.\n const callerCount =\n req.method === 'GET'\n ? mediaCdnRateLimitRefusal({\n headers: req.headers,\n remoteAddress: req.socket?.remoteAddress,\n rateClass: mediaCdnEdgeCacheable(docServedType) ? 'image' : 'non-image',\n })\n : null\n let [metadata] = await file.getMetadata().catch(() => [null as any])\n // A display copy the document names but Storage does not hold degrades\n // to the original — what this URL served before display copies existed —\n // rather than to a 404 on a live page.\n let servesDisplay = useDisplay\n if (!metadata && useDisplay) {\n servesDisplay = false\n file = bucket.file(basePath)\n ;[metadata] = await file.getMetadata().catch(() => [null as any])\n }\n if (!metadata) {\n res.status(404).json({ error: 'Not found' })\n return\n }\n const refused = await callerCount\n if (refused) {\n // The file's validator and cache policy are already set. A refusal is\n // about this caller rather than the URL, so no cache may keep it: an\n // edge holding a 429 would refuse every visitor to that image for an\n // hour.\n clearMediaCdnRepresentation(res)\n res.setHeader('Retry-After', String(refused.retryAfterSeconds))\n res.status(429).json({ error: 'Too many requests' })\n return\n }\n\n const servedType =\n usePoster || useVariant\n ? 'image/webp'\n : rendition\n ? rendition.contentType\n : servesDisplay && display\n ? display.contentType\n : String(\n metadata.contentType ??\n snapshot.get('contentType') ??\n 'application/octet-stream',\n )\n res.setHeader('Content-Type', servedType)\n // AGL-1474: an SVG (or anything else a browser treats as a document) gets\n // the sandboxing policy. Everything else keeps the base one set above —\n // `sandbox` is withheld from raster and PDF responses on purpose, since it\n // would change how a directly-opened PDF is handled for no security gain.\n res.setHeader(\n 'Content-Security-Policy',\n mediaCdnContentSecurityPolicy(servedType),\n )\n // Range support (AGL-1442 S4) — this is what lets video ride this route\n // at all: a `<video>` seek is a Range request, and before S4 the raw\n // storage URL was the only server that would answer one.\n //\n // `Accept-Ranges` is advertised unconditionally because it is now TRUE\n // unconditionally — the handler honors a single byte-range for every\n // asset class. Limiting it to \"types that benefit\" would only teach a\n // PDF viewer or a download manager not to ask for something we would\n // happily serve, and the header is not part of any cache key.\n //\n // Caching shape (AGL-1515): S4 read Vercel's cacheable-response\n // criteria (\"Request doesn't contain Range header\") as \"a cached full\n // body and a function-served partial can never cross\". Production\n // falsified that: the criteria govern what the edge STORES, not what it\n // SERVES, and the edge answers a ranged request from an existing cached\n // full-body 200 as a 200 + Content-Range + sliced-body hybrid. The fix\n // is upstream of this line — every type a ranged consumer actually uses\n // is served `private` (see mediaCdnEdgeCacheable), so the entry the\n // edge would mangle never exists and every ranged request reaches this\n // function for a real 206. In the browser the 206 rides the same\n // Cache-Control as its URL class: a 206 under a STRONG validator is\n // exactly what lets a player reuse the segments it already fetched.\n res.setHeader('Accept-Ranges', 'bytes')\n // The authoritative tier decision, now that the served type is known\n // from Storage metadata rather than inferred from the doc. Overwrites\n // the header set before the 304 exit iff the two disagree — and the\n // BODY-serving response is the one the edge could store, so this one\n // must win.\n if (!hashed) setCacheControl(stableCacheControlFor(servedType))\n const size = Number(metadata.size ?? NaN)\n // Range applies to GET only (RFC 9110 §14.2); a HEAD answers the full\n // representation's metadata. And with no known size there is no honest\n // `Content-Range` to write, so the header is ignored and the full body\n // served — the degradation every unrecognised shape shares.\n const parsed =\n req.method === 'GET' && Number.isFinite(size) && size >= 0\n ? parseMediaCdnRange(req.headers['range'], size)\n : null\n // `If-Range`: the client is saying \"this range is against version X; if\n // you hold anything else, send me the whole file\". Honoring the range on\n // a mismatch is how a stale player splices two versions of a video into\n // one stream — so anything but an exact match with the CURRENT strong\n // validator (including a date form, and including the no-hash legacy\n // shape that has no validator at all) collapses to a full 200.\n const ifRange = req.headers['if-range']\n const range =\n parsed !== null && typeof ifRange === 'string' && ifRange !== etag\n ? null\n : parsed\n if (range === 'unsatisfiable') {\n // The refusal is a function of the REQUEST header, not the URL, and\n // shared caches key on the URL — no cache may keep it.\n setCacheControl('private, no-store')\n res.setHeader('Content-Range', `bytes */${size}`)\n res.status(416).end()\n return\n }\n const partial = range !== null && typeof range === 'object'\n const servedBytes = partial\n ? range.end - range.start + 1\n : Number(metadata.size ?? 0)\n if (partial) {\n res.setHeader('Content-Range', `bytes ${range.start}-${range.end}/${size}`)\n res.setHeader('Content-Length', String(servedBytes))\n } else if (metadata.size) {\n res.setHeader('Content-Length', String(metadata.size))\n }\n // Give downloads a real filename+extension (AGL-834): the URL is\n // mediaId-keyed and extensionless, so without this a \"save image\" lands\n // as a name-less blob. `?download=1` makes it an actual download rather\n // than a tab (AGL-1411).\n res.setHeader(\n 'Content-Disposition',\n mediaContentDisposition(\n mediaDownloadName(snapshot.get('fileName'), mediaId),\n // `?download=1` can force a download; nothing can force an INLINE\n // render of a type that has no inline use here.\n { download: download || !servesInline(servedType) },\n ),\n )\n // `immutable` for a year is the strongest possible caching, and on a\n // private asset it would be a permanent public copy of a file whose\n // whole point is that it expires (AGL-1051) — `setCacheControl` holds\n // that line. The year stays browser-only for non-image types\n // (AGL-1515): the URL still changes with the bytes, but the edge must\n // hold no full body it could slice.\n if (hashed) {\n setCacheControl(\n mediaCdnEdgeCacheable(servedType)\n ? MEDIA_CDN_IMMUTABLE_CACHE_CONTROL\n : MEDIA_CDN_IMMUTABLE_EDGE_BYPASS_CACHE_CONTROL,\n )\n }\n\n // HEAD returns here, before the delivery counter below (AGL-2811). It\n // carries a GET's headers and no body, and `servedBytes` is the whole\n // object for any request without a range. `serves` and `bytes` count\n // representations that left, which is also why the 304 and 416 exits\n // above return before this point.\n if (req.method === 'HEAD') {\n res.status(200).end()\n return\n }\n\n // Delivery volume (AGL-176): per-asset serves/bytes on the AGL-82\n // analytics day-doc, awaited after the body rather than ahead of it.\n // Only cache MISSES reach this\n // code — edge-cached responses aren't counted, so these are origin\n // serves, not user-facing totals.\n // Hot-doc note: a single day-doc caps at ~1 write/sec sustained;\n // acceptable at current traffic, shard or sample if an asset gets hot.\n //\n // Written before the first byte, so `bytes` is what this request asked\n // the origin to send. A client that abandons the stream receives less;\n // the figure is the ceiling on what left, not a count of what arrived.\n //\n // The same bytes count against the org's bandwidth band when\n // `countsTowardBand` says so (above). In this write, so counting costs no\n // write of its own.\n const countedBytes = countsTowardBand(servedType) ? servedBytes : 0\n const day = new Date().toISOString().slice(0, 10)\n const recorded = firestore\n .collection(isOrg ? 'orgs' : 'hosts')\n .doc(scopeId)\n .collection('analytics')\n .doc(day)\n .set(\n {\n // Retention (AGL-1844): every writer of a day doc stamps the\n // day-anchored expiry the TTL policy sweeps on.\n expiresAt: analyticsDayExpiresAt(day),\n ...(countedBytes > 0\n ? {\n [MEDIA_BANDWIDTH_DAY_FIELD]:\n firebaseAdmin.firestore.FieldValue.increment(countedBytes),\n }\n : {}),\n media: {\n [mediaId]: {\n serves: firebaseAdmin.firestore.FieldValue.increment(1),\n // The bytes that actually leave, not the object's size — a\n // seek in a 200 MB video is a few MB of egress, and counting\n // the full object per range would overstate delivery by the\n // number of seeks (AGL-1442 S4).\n bytes: firebaseAdmin.firestore.FieldValue.increment(servedBytes),\n },\n },\n },\n { merge: true },\n )\n .catch((error: unknown) => logUncountedServe(scopeSegment, mediaId, error))\n // Totalled beside the stream rather than ahead of it, and awaited — with\n // the count above — once the stream ends, so the function lives until\n // both are done.\n const evaluation =\n countedBytes > 0 ? mediaCdnBandwidthEvaluation(scope, countedBytes) : null\n if (partial) res.status(206)\n // `start`/`end` are inclusive in `createReadStream`, matching the parsed\n // range — GCS is asked for exactly the requested bytes and nothing is\n // over-read from Storage on a partial serve.\n //\n // The response streams (AGL-2810): each chunk leaves as Storage produces\n // it, and the next is read only once the client has taken the last, so a\n // file is never held in function memory whatever its size. `pipeline`\n // rather than `pipe` because streaming needs teardown in both directions,\n // which `pipe` does not give — a failed Storage read must fail the\n // response, and a client that stops reading must stop the Storage read.\n try {\n await pipeline(\n file.createReadStream(partial ? { start: range.start, end: range.end } : {}),\n res,\n )\n } finally {\n await Promise.all([recorded, evaluation])\n }\n } catch (error) {\n if (mediaCdnClientWentAway(error, res)) return\n console.error('serveMediaCdn failed', scopeSegment, mediaId, error)\n if (!res.headersSent) {\n clearMediaCdnRepresentation(res)\n res.status(500).json({ error: 'Delivery failed' })\n } else {\n // The status line has already left, so the body is the only place the\n // failure can surface. A destroyed response is a broken transfer to the\n // client; ending it would pass a truncated file off as a whole one.\n res.destroy(error instanceof Error ? error : new Error(String(error)))\n }\n }\n}\n\n/**\n * A serve the day doc did not take. The response has already gone, so the\n * failure is the log's to report rather than the visitor's — but reported,\n * because a dropped count is bandwidth the band and the invoice never see.\n */\nfunction logUncountedServe(scopeSegment: string, mediaId: string, error: unknown): void {\n console.error('[media-cdn] serve not counted', scopeSegment, mediaId, error)\n}\n"],"names":["pipeline","BANDWIDTH_CAP_RETRY_AFTER_SECONDS","bandwidthCapApplies","bandwidthCapEngaged","bandwidthCeilingDegradesHost","bandwidthCeilingMonthKey","isLockdownActive","MEDIA_BANDWIDTH_DAY_FIELD","MEDIA_CDN_POSTER_PARAM","MEDIA_CDN_RENDITION_AUTO","MEDIA_CDN_RENDITION_PARAM","MEDIA_CDN_ROUTE","MEDIA_CDN_VERSION_PARAM","MEDIA_POSTER_OBJECT_SUFFIX","MEDIA_VARIANT_ENCODER_VERSION","MEDIA_VARIANT_ENCODER_VERSION_FIELD","isSecurityClassLockdownReason","mediaDisplayObjectPath","mediaPosterObjectPath","mediaVariantEncoderVersionOf","mediaRenditionObjectPath","normalizeHostLockdown","normalizeOrgLockdown","parseMediaCdnVersionToken","parseMediaRenditions","MEDIA_CDN_SEGMENT","mediaCdnScopeRefusal","parseMediaCdnScope","mediaDeliveryProvider","parseSiteIconSpec","SITE_ICON_BACKGROUND_PARAM","SITE_ICON_PARAM","SITE_ICON_VERSION_PARAM","analyticsDayExpiresAt","firebaseAdmin","recordMediaServe","getPlatformLockdown","engageMediaBandwidthCap","mediaBandwidthEvaluationDue","mediaCdnRateLimitRefusal","mediaDeliveryOrgIdFor","mediaDeliveryRedirect","mediaDeliveryCopiesStale","regenerateMediaDeliveryCopies","getMediaQuarantine","verifyMediaAccess","mediaStoragePathInScope","serveMediaCdnIcon","MEDIA_CDN_VARIANT_WIDTHS","mediaCdnAllows","SEGMENT","mediaCdnForwardedQuery","query","params","URLSearchParams","key","raw","value","Array","isArray","undefined","set","String","encoded","toString","MEDIA_CDN_STABLE_CACHE_CONTROL","MEDIA_CDN_STABLE_EDGE_BYPASS_CACHE_CONTROL","MEDIA_CDN_VERSIONED_CACHE_CONTROL","mediaCdnVersionIsCurrent","options","token","currentHash","contentHash","otherRepresentation","widthRequested","variantServed","recorded","documentEncoderVersion","generation","Number","isInteger","encoderVersion","MEDIA_CDN_IMMUTABLE_CACHE_CONTROL","MEDIA_CDN_IMMUTABLE_EDGE_BYPASS_CACHE_CONTROL","mediaCdnEdgeCacheable","contentType","split","trim","toLowerCase","startsWith","MEDIA_CDN_BASE_CSP","MEDIA_CDN_ACTIVE_DOCUMENT_CSP","MEDIA_CDN_ACTIVE_DOCUMENT_TYPES","Set","servesInline","type","mediaCdnContentSecurityPolicy","has","lockdownStopsMediaDelivery","state","nowMs","reason","MEDIA_CDN_LOCK_TTL_MS","OPEN_SCOPE_VERDICT","locked","bandwidthPaused","capApplies","orgId","lockCache","Map","lockPending","invalidateMediaCdnLockCache","clear","SUSPENSION_FIELDS","HOST_INDEX_ORG_FIELD","HOST_BANDWIDTH_CEILING_FIELD","suspensionCarrier","snapshot","Object","fromEntries","map","field","get","orgScopeVerdict","org","host","billing","data","now","Date","mediaCdnScopeVerdict","scope","isOrg","scopeId","cached","at","verdict","pending","firestore","app","getAll","collection","doc","hostIndex","fieldMask","finally","delete","mediaCdnScopeLocked","mediaCdnServeBlock","asset","quarantined","contentSha256","scopeSegment","mediaId","wantsMediaDownload","normalized","mediaCdnDisplayCopy","test","scheduleAfterResponse","task","loaded","require","after","catch","error","console","mediaCdnWantsPoster","MEDIA_CDN_BASELINE_RENDITION_TYPE","parseAcceptedTypes","header","named","refused","part","name","includes","isRefusal","some","param","add","selectAutoRendition","renditions","acceptHeader","length","typeOf","entry","find","NON_ASCII_FIELD_VALUE","NOT_QUOTED_STRING_SAFE","mediaDownloadName","fileName","base","pop","from","slice","join","mediaContentDisposition","download","ascii","replace","encodeURIComponent","character","charCodeAt","toUpperCase","padStart","parseMediaCdnRange","size","unit","exec","specs","spec","startRaw","endRaw","suffix","isSafeInteger","start","Math","max","end","min","mediaCdnClientWentAway","res","code","errored","clearMediaCdnRepresentation","removeHeader","setHeader","mediaCdnBandwidthEvaluation","bytes","serveMediaCdn","req","method","status","path","hash","hashed","json","metadata","exists","blockedBy","refusal","JSON","stringify","isPrivate","setCacheControl","signed","exp","sig","aud","teamPreview","countsTowardBand","iconSpec","iconBase","iconFile","storage","bucket","process","env","file","storagePath","source","stablePath","cacheControl","stable","immutable","rateLimit","headers","remoteAddress","socket","rateClass","width","variants","poster","posterVariants","wantsPoster","usePoster","usePosterVariant","Boolean","renditionParam","renditionKey","negotiated","availableRenditions","rendition","useVariant","display","useDisplay","encoderTag","representation","etag","versionCurrent","stableCacheControlFor","docServedType","deliveryProvider","delivery","provider","document","servedType","claims","hostId","contextHostId","notAfterMs","location","evaluation","bandwidthBytes","sizeBytes","redirect","Promise","all","cdnPath","variantRegeneration","regenerationPath","onRefused","outcome","docRef","ref","basePath","candidate","objectPath","callerCount","getMetadata","servesDisplay","retryAfterSeconds","NaN","parsed","isFinite","ifRange","range","partial","servedBytes","countedBytes","day","toISOString","expiresAt","FieldValue","increment","media","serves","merge","logUncountedServe","createReadStream","headersSent","destroy","Error"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../../libs/tenant/data/admin/src/lib/server/serve-media-cdn.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { pipeline } from 'node:stream/promises'\nimport {\n type AglynOrgBilling,\n BANDWIDTH_CAP_RETRY_AFTER_SECONDS,\n bandwidthCapApplies,\n bandwidthCapEngaged,\n bandwidthCeilingDegradesHost,\n bandwidthCeilingMonthKey,\n isLockdownActive,\n type LockdownState,\n MEDIA_BANDWIDTH_DAY_FIELD,\n MEDIA_CDN_POSTER_PARAM,\n MEDIA_CDN_RENDITION_AUTO,\n MEDIA_CDN_RENDITION_PARAM,\n MEDIA_CDN_ROUTE,\n MEDIA_CDN_VERSION_PARAM,\n MEDIA_POSTER_OBJECT_SUFFIX,\n MEDIA_VARIANT_ENCODER_VERSION,\n MEDIA_VARIANT_ENCODER_VERSION_FIELD,\n type MediaCdnVersion,\n isSecurityClassLockdownReason,\n mediaDisplayObjectPath,\n mediaPosterObjectPath,\n mediaVariantEncoderVersionOf,\n mediaRenditionObjectPath,\n type MediaVideoRendition,\n normalizeHostLockdown,\n normalizeOrgLockdown,\n parseMediaCdnVersionToken,\n parseMediaRenditions,\n} from '@aglyn/aglyn/server'\n// By path, and out of every barrel: see the module note in `media-cdn-scope`.\nimport {\n MEDIA_CDN_SEGMENT,\n type MediaCdnScope,\n mediaCdnScopeRefusal,\n parseMediaCdnScope,\n} from '@aglyn/aglyn/app-utils/media-cdn-scope'\nimport { mediaDeliveryProvider } from '@aglyn/aglyn/plugin-manager/media-delivery-provider'\nimport {\n parseSiteIconSpec,\n SITE_ICON_BACKGROUND_PARAM,\n SITE_ICON_PARAM,\n SITE_ICON_VERSION_PARAM,\n} from '@aglyn/aglyn/app-utils/site-icon-set'\nimport type { NextApiRequest, NextApiResponse } from 'next'\nimport { scheduleAfterResponse } from './after-response'\nimport { analyticsDayExpiresAt } from './analytics-retention'\nimport { firebaseAdmin } from './firebase-admin'\nimport { recordMediaServe } from './media-serve-count'\nimport { getPlatformLockdown } from './lockdown'\nimport {\n engageMediaBandwidthCap,\n mediaBandwidthEvaluationDue,\n} from './media-bandwidth-cap'\nimport { mediaCdnRateLimitRefusal } from './media-cdn-rate-limit'\nimport { mediaDeliveryOrgIdFor, mediaDeliveryRedirect } from './media-delivery'\nimport {\n mediaDeliveryCopiesStale,\n regenerateMediaDeliveryCopies,\n} from './media-delivery-regeneration'\nimport { getMediaQuarantine } from './media-quarantine'\nimport { verifyMediaAccess } from './media-signing'\nimport { mediaStoragePathInScope } from './media-storage-path'\nimport { serveMediaCdnIcon } from './media-cdn-icon'\n\n/**\n * Variant widths generated at upload (AGL-175).\n *\n * Re-exported, not defined: the list now lives in `@aglyn/aglyn`'s media-ref\n * so the RENDERER can reach it too. `image.tsx` carried its own\n * `[320, 640, 1280]` literal, so a width added here never reached the srcSet.\n */\nexport { MEDIA_CDN_VARIANT_WIDTHS } from '@aglyn/aglyn/server'\n\n/**\n * The scope grammar and the refusal this handler enforces (AGL-1043).\n *\n * Re-exported, not defined: the pure rule lives in `@aglyn/aglyn`'s\n * `media-cdn-scope`, so the readers that must reach this handler's verdict —\n * the composition's video facts and the besigner canvas — call the functions\n * it calls.\n */\nexport {\n mediaCdnAllows,\n type MediaCdnScope,\n type MediaCdnScopeRefusal,\n mediaCdnScopeRefusal,\n parseMediaCdnScope,\n} from '@aglyn/aglyn/app-utils/media-cdn-scope'\n\nconst SEGMENT = MEDIA_CDN_SEGMENT\n\n/**\n * The query string to carry onto the stable URL when a stale content pin\n * redirects (AGL-2685).\n *\n * REBUILT from the four parameters this handler reads, never forwarded whole.\n * Two reasons, and the second is the one that matters:\n *\n * 1. Everything else is inert here — it would only split the CDN cache key\n * for a response that is identical either way.\n * 2. `Location` is a header, and the value would otherwise be caller-supplied\n * text. Node rejects a CR or LF in a header value rather than splitting\n * the response, so this is a second lock on a door that is already shut —\n * which is the right number of locks for a header built from user input.\n *\n * `exp` and `sig` ride along because a private asset's signature covers\n * (scope, mediaId, exp) and never the hash, so it is still valid at the\n * target. Dropping them would turn a redirect into a 404 for exactly the\n * assets whose delivery is most sensitive.\n */\nexport function mediaCdnForwardedQuery(query: NextApiRequest['query']): string {\n const params = new URLSearchParams()\n // `poster` and `r` join the set for the reason the others are in it: the\n // redirect must name the SAME representation the caller asked for, or a\n // stale content pin on a poster URL lands the browser on the master video\n // (AGL-2743).\n // `icon`, `bg` and `v` for the same reason: a site icon (AGL-3484) behind a\n // stale content pin must stay the icon, not become the full-size original.\n for (const key of [\n 'w',\n 'poster',\n 'r',\n 'download',\n SITE_ICON_PARAM,\n SITE_ICON_BACKGROUND_PARAM,\n SITE_ICON_VERSION_PARAM,\n 'exp',\n 'sig',\n 'aud',\n ] as const) {\n const raw = query[key]\n const value = Array.isArray(raw) ? raw[0] : raw\n if (value !== undefined && value !== '') params.set(key, String(value))\n }\n const encoded = params.toString()\n return encoded ? `?${encoded}` : ''\n}\n\n/**\n * The stable (non-content-hashed) URL's caching contract.\n *\n * This was `public, max-age=3600, stale-while-revalidate=86400`, and that\n * header contradicted the promise the stable URL exists to make. Measured on\n * production 2026-08-12: Vercel's edge DOES cache this route on a bare\n * `max-age` (`x-vercel-cache: MISS` then `HIT`, `age` climbing) — so the\n * saving was never in question. What a browser `max-age` breaks is\n * *propagation*. `max-age=3600` reaches the client, so a browser holding a\n * replaced asset will not send a conditional request for a full hour: the\n * ETag below never gets a chance to answer, and nothing on our side can bust\n * a browser cache. Every other cacheable route in the repo already uses\n * `s-maxage` for exactly this reason — see `seo-origin.spec.ts`, \"never a\n * browser `max-age`, which nothing could bust\".\n *\n * So: a short browser `max-age` that still collapses the burst of repeat\n * requests within one page view (an image referenced by four `srcSet`\n * candidates, a tile rendered in a grid and again in a drawer), and the full\n * hour moved to `s-maxage` where it belongs. Worst stale read is now 60 s of\n * replaced bytes in one browser rather than an hour, at the cost of one\n * conditional request per image per minute — answered by the EDGE from its\n * own copy, so it adds no Storage read and no Firestore read.\n *\n * The immutable content-hashed form is untouched: its URL changes with its\n * bytes, so it can and should be pinned in the browser for a year.\n *\n * Since AGL-1515 this policy applies to IMAGE responses only — see\n * {@link mediaCdnEdgeCacheable}.\n */\nexport const MEDIA_CDN_STABLE_CACHE_CONTROL =\n 'public, max-age=60, s-maxage=3600, stale-while-revalidate=86400'\n\n/**\n * The stable URL's policy for types the edge must never hold (AGL-1515):\n * the same 60-second browser window and the same ETag/304 contract, with\n * `private` in place of `public, s-maxage` so no shared cache stores a\n * full body.\n *\n * Why the edge must never hold one: Vercel's edge, holding a cached\n * full-body 200, answers a `Range` request FROM that entry as a\n * spec-violating hybrid — status 200, a `Content-Range` header, and only\n * the requested slice as the body (`x-vercel-cache: HIT`; reproduced twice\n * on production, 2026-08-13). A video player seeking into such an asset\n * adopts a 100-byte slice as the complete file: silent playback corruption.\n *\n * The S4 shape below this constant was built on Vercel's documented\n * cacheable-response criteria (\"Request doesn't contain Range header\"),\n * read as \"ranged requests bypass the edge\". Production falsified that\n * reading: the criteria govern what the edge STORES, not what it SERVES. A\n * ranged request is still matched against the URL-keyed entry a previous\n * plain GET left behind, and the edge's slicing layer rewrites the body\n * and adds `Content-Range` without rewriting the stored 200 status.\n *\n * `private` is the lever because it is in the same documented criteria\n * list as an absolute storage preventer (\"Response doesn't contain the\n * `private` … directives\"), where `Vary: Range` is undocumented on\n * Vercel, discouraged by RFC 9110, and untestable anywhere but a\n * production deploy. What it costs: edge caching for non-image assets\n * under Vercel's 10 MB cacheable-size cap. Real video mostly sits ABOVE\n * the cap and was never edge-cached — the mangling reproduced on a 186 KB\n * asset precisely because small ones are the ones that get cached.\n */\nexport const MEDIA_CDN_STABLE_EDGE_BYPASS_CACHE_CONTROL =\n 'private, max-age=60'\n\n/**\n * The VERSIONED stable URL's policy (AGL-3485): `?v={contentHash}.{encoder}`\n * on the stable path, answered while the token names the asset's current\n * bytes and current variants.\n *\n * The browser keeps it for a year, `immutable`, which is the whole point: the\n * 60-second window above made every repeat view of every published image a\n * conditional request, and Lighthouse reports that policy against each one.\n * It is safe for the reason the content-hashed form never was in a page\n * (AGL-2798): the page names this URL only while the token is current, the\n * composition reads the token off the media document on every render, and a\n * replace or a regeneration therefore moves the page to a new URL instead of\n * leaving it on a pinned copy of the old bytes.\n *\n * The EDGE keeps the stable URL's hour and its revalidation, deliberately not\n * a year: the edge is the copy a takedown or a lockdown has to outrun for\n * every new visitor (`media-takedown-reach.ts`), and an hour is the bound the\n * product states for it. Other visitors still get the edge's copy within that\n * hour, which is what `s-maxage` is for. Images only — every other type keeps\n * the stable policy, for the AGL-1515 reasons above.\n */\nexport const MEDIA_CDN_VERSIONED_CACHE_CONTROL =\n 'public, max-age=31536000, s-maxage=3600, stale-while-revalidate=86400, immutable'\n\n/**\n * The generated width that answers a `?w=` request (AGL-3486): the width\n * itself when the asset has it, otherwise the smallest larger one, and `null`\n * when every variant is narrower than asked — then the display copy or the\n * original is the closest picture there is.\n */\nexport function mediaCdnVariantFor(\n variants: readonly unknown[],\n width: number,\n): number | null {\n if (!(width > 0)) return null\n let best: number | null = null\n for (const candidate of variants) {\n if (typeof candidate !== 'number' || !Number.isInteger(candidate)) continue\n if (candidate === width) return width\n if (candidate > width && (best === null || candidate < best)) best = candidate\n }\n return best\n}\n\n/**\n * Whether a versioned request may be answered with\n * {@link MEDIA_CDN_VERSIONED_CACHE_CONTROL} (AGL-3485).\n *\n * Only when everything the token promises is true of what is being served:\n *\n * - the token's content hash is the document's CURRENT one. A stale token is\n * served the current bytes under the stable URL's short policy — a stale\n * page shows the new picture, and nothing pins it;\n * - the representation is the original or a `?w=` variant. A poster or a\n * rendition keeps its own policy;\n * - for a `?w=` request, the variant itself is what is served. A width the\n * asset has no variant for falls back to another representation, and a\n * fallback is not something to keep for a year: the variant may be\n * generated later;\n * - and the asset's delivery copies were made by the encoder generation the\n * token names, which is the one this code runs. The media document records\n * which generation made them (`MEDIA_VARIANT_ENCODER_VERSION_FIELD`, absent\n * meaning the first). A page rendered after the encoder moved on names the\n * new generation before the asset has been regenerated; answering that\n * request with the old encode for a year would hide the regeneration behind\n * the very URL that was minted to reach it. Asked of every request, not\n * only `?w=`, because the encoder makes what the bare URL serves too.\n */\nexport function mediaCdnVersionIsCurrent(options: {\n token: MediaCdnVersion | null\n currentHash: string\n /** A `?w=` was asked for. */\n widthRequested: boolean\n /** The `?w=` variant itself is what will be served. */\n variantServed: boolean\n /** A poster or a rendition was selected instead of the image. */\n otherRepresentation: boolean\n /** The document's {@link MEDIA_VARIANT_ENCODER_VERSION_FIELD}. */\n documentEncoderVersion: unknown\n}): boolean {\n const { token, currentHash } = options\n if (!token || !currentHash || token.contentHash !== currentHash) return false\n if (options.otherRepresentation) return false\n if (options.widthRequested && !options.variantServed) return false\n const recorded = options.documentEncoderVersion\n const generation =\n typeof recorded === 'number' && Number.isInteger(recorded) && recorded > 0\n ? recorded\n : 1\n return (\n token.encoderVersion === MEDIA_VARIANT_ENCODER_VERSION &&\n generation === token.encoderVersion\n )\n}\n\n/** The immutable content-hashed URL's policy for edge-cacheable (image) types. */\nexport const MEDIA_CDN_IMMUTABLE_CACHE_CONTROL =\n 'public, max-age=31536000, immutable'\n\n/**\n * The immutable URL's policy for edge-bypassing types (AGL-1515): the\n * browser keeps its year — the URL still changes with the bytes — while\n * the edge holds nothing it could mangle.\n */\nexport const MEDIA_CDN_IMMUTABLE_EDGE_BYPASS_CACHE_CONTROL =\n 'private, max-age=31536000, immutable'\n\n/**\n * Whether a response of this content type may be edge-cached (AGL-1515).\n *\n * The split is \"types no realistic client ranges into\": browsers fetch\n * images with plain GETs (`<img>`, `srcSet`, save-as — all of them), while\n * every consumer that seeks or resumes — `<video>`/`<audio>` players, PDF\n * viewers, download managers — operates on the non-image types. Images stay\n * on the shared edge policy because they are the DAM grid's hot path (4.3 KB\n * WebP tiles at volume), and pushing them to origin to fix video would trade\n * a real regression for a theoretical one.\n *\n * Accepted residual, recorded here on purpose: a hand-built `Range` request\n * against an edge-cached IMAGE can still be answered with the hybrid (that\n * is exactly the AGL-1515 favicon repro). No browser or player issues one,\n * and the alternative — `private` on images too — costs the hot path an\n * edge hit rate it measurably has.\n *\n * Unknown or absent types return false: correctness over cache.\n */\nexport function mediaCdnEdgeCacheable(contentType: unknown): boolean {\n return String(contentType ?? '')\n .split(';')[0]\n .trim()\n .toLowerCase()\n .startsWith('image/')\n}\n\n/**\n * The CDN's OWN Content-Security-Policy (AGL-1474).\n *\n * An uploaded `image/svg+xml` is a document, not a picture. It passes the\n * `image/*` allowlist, it is stored under whatever type the client declared,\n * and this handler serves it `inline` from the console's own origin — and\n * from every tenant site's, since the same handler mounts in both apps. So\n * `<script>alert(document.domain)</script>` inside an uploaded logo executed\n * on `app.aglyn.com` the moment the asset URL was opened top-level, for\n * anyone with editor rights.\n *\n * Nothing already in place stopped it. `X-Content-Type-Options: nosniff`\n * blocks HTML *mislabelled* as an image; it says nothing about a file\n * honestly labelled `image/svg+xml`, which browsers render as a scripted\n * document. And there was no CSP on this response **at all**: since AGL-523\n * the policy is built per-response in each app's middleware, and both\n * middlewares' matchers exclude `api` (`apps/console/middleware.ts`,\n * `apps/tenant/middleware.ts`), so a directly-navigated CDN URL never passed\n * through the code that sets one.\n *\n * That is precisely why the header is set HERE, on the response, rather than\n * added to a matcher: a route-level header cannot be lost to a matcher edit,\n * a rewrite, or a new mount of `serveMediaCdn` in a third app. It is also\n * what makes this the containment rather than the remediation — it covers\n * every asset already in the bucket, including the SVGs uploaded before the\n * sanitizer existed, without rewriting a byte.\n *\n * **It does not touch `<img src>`, which is how logos and marks are used\n * across this product.** A browser loading an SVG as an image neither runs\n * its script nor applies the response's CSP — CSP governs documents and\n * workers. The policy only becomes live in the case that is the vector: the\n * asset opened as a top-level document (or framed via `<object>`/`<iframe>`).\n */\nexport const MEDIA_CDN_BASE_CSP =\n \"default-src 'none'; script-src 'none'; object-src 'none'; \" +\n \"base-uri 'none'; form-action 'none'\"\n\n/**\n * The policy for a type a browser will treat as an ACTIVE DOCUMENT. Adds\n * `sandbox` — an opaque origin, so even a hypothetical execution has no\n * `document.domain`, no cookies and no storage to reach for.\n *\n * `style-src 'unsafe-inline'` and the two `data:` allowances are not\n * concessions to script: with `script-src 'none'` and `sandbox` in force,\n * nothing in CSS or a data URI can execute. They exist so that opening a\n * logo's URL directly still shows the logo — `default-src 'none'` alone\n * would blank an SVG's own `<style>` block and its embedded raster fills,\n * which is a visible regression on legitimate assets and buys no safety.\n */\nexport const MEDIA_CDN_ACTIVE_DOCUMENT_CSP =\n `${MEDIA_CDN_BASE_CSP}; style-src 'unsafe-inline'; img-src data:; ` +\n 'font-src data:; sandbox'\n\n/**\n * Types a browser parses as a document rather than rendering as an image.\n *\n * Keying off the SERVED content type is sound only because this response also\n * carries `nosniff`: the browser is bound to the label we send, so an SVG\n * uploaded under a `image/png` label is decoded as a PNG and is inert. Every\n * type here except SVG is refused by the upload allowlist today; they are\n * listed anyway because the allowlist has moved before (AGL-1465) and because\n * a legacy fourth upload route accepted arbitrary types for its whole life\n * before AGL-1485 deleted it.\n */\nconst MEDIA_CDN_ACTIVE_DOCUMENT_TYPES = new Set([\n 'image/svg+xml',\n 'image/svg',\n 'text/html',\n 'application/xhtml+xml',\n 'application/xml',\n 'text/xml',\n 'text/xsl',\n 'application/xslt+xml',\n 'application/mathml+xml',\n])\n\n/**\n * Types this CDN will serve `inline`.\n *\n * `?download=1` has always been able to force `attachment`, but it is the\n * REQUESTER's flag — the safe disposition cannot depend on a caller asking\n * for it. Anything not on this list is served as a download whatever the\n * query says, so opening its URL top-level saves a file instead of handing\n * the type to a renderer on the customer's own domain.\n *\n * The list is what a browser has a real inline use for on this route:\n * pictures, media a player streams, and PDFs, which people expect to open in\n * a tab. Everything else — the office and text formats, JSON, ZIP, and any\n * legacy `text/html` still sitting in the bucket from before the allowlist\n * narrowed — has no inline use case here and every one of them is a document\n * a browser would happily parse.\n *\n * SVG stays INLINE deliberately. AGL-1474 chose the sandboxing CSP over a\n * forced download precisely so that opening a logo's URL still shows the\n * logo, and that policy already denies it script, an origin, and everything\n * it could reach for. Turning it into a download now would take that\n * decision back without adding to it.\n */\nfunction servesInline(contentType: string): boolean {\n const type = String(contentType ?? '')\n .split(';')[0]\n .trim()\n .toLowerCase()\n return (\n type.startsWith('image/') ||\n type.startsWith('video/') ||\n type.startsWith('audio/') ||\n type === 'application/pdf'\n )\n}\n\n/** The `Content-Security-Policy` for a response serving `contentType`. */\nexport function mediaCdnContentSecurityPolicy(contentType: string): string {\n const type = String(contentType ?? '')\n .split(';')[0]\n .trim()\n .toLowerCase()\n return MEDIA_CDN_ACTIVE_DOCUMENT_TYPES.has(type)\n ? MEDIA_CDN_ACTIVE_DOCUMENT_CSP\n : MEDIA_CDN_BASE_CSP\n}\n\n/**\n * Lockdown on the delivery path (AGL-1520).\n *\n * A security-locked org's SITE stops within seconds (the tenant middleware\n * 503s, AGL-1501) — but until this gate existed its public media kept\n * serving worldwide: to hot-links, to third-party embeds, and to the\n * infected asset's own URL, which for an \"infected host\" security lock is\n * the exact thing the lock was pressed to stop.\n *\n * **Which reasons stop delivery — decided per reason, not blanket:**\n *\n * - `security` — REFUSE. The point of the lock: the org's content, media\n * included, must stop serving.\n * - `manual` — REFUSE. A staff suspension with no reason code (every\n * pre-lockdown `suspendedAt` normalizes to `manual`) is \"we turned this\n * org off\"; content continuing to serve would make the kill partial.\n * - `maintenance` — SERVE. The maintenance notice surface may itself\n * reference org assets (a logo on the notice page), the window is\n * temporary and non-adversarial, and blanking every image buys no safety.\n * - `billing` — SERVE. The AGL-1506 principle: billing-locked orgs keep the\n * surfaces that let them come back (members can still reach billing to\n * pay). The site already 503s, so whether a hot-linked image serves is\n * nearly moot — and refusing would punish e.g. an email-signature logo\n * for a payment lapse. Serving is the cheap, reversible answer.\n *\n * Scopes are checked INDIVIDUALLY against that matrix rather than through\n * `resolveLockdown`, on purpose: the resolver answers \"which notice does a\n * visitor see\" and returns the WIDEST active scope — so a platform\n * `maintenance` window would mask a concurrent org `security` lock and the\n * infected asset would keep serving. Delivery has no notice to pick; the\n * question is only \"does ANY active lockdown demand these bytes stop\".\n */\nexport function lockdownStopsMediaDelivery(\n state: LockdownState | null | undefined,\n nowMs: number,\n): boolean {\n if (!state || !isLockdownActive(state, nowMs)) return false\n return isSecurityClassLockdownReason(state.reason) || state.reason === 'manual'\n}\n\n/**\n * **Read cost (AGL-1302):** the verdict inputs are TTL-cached in-process\n * per CDN scope — one lookup per scope per {@link MEDIA_CDN_LOCK_TTL_MS},\n * not per asset. A DAM grid firing dozens of requests coalesces into one;\n * the platform doc rides `getPlatformLockdown`'s existing 15s cache. Same\n * fail-open posture as the verdict core: an unreachable Firestore is an\n * outage, not a lockdown, and must not blank every customer image.\n *\n * That lookup is one BatchGetDocuments at org scope, and two at host scope\n * — `hosts` and `hostIndex` batched together, then the owning org, whose id\n * is what `hostIndex` returns and so cannot join the batch. The host and\n * index reads are projected to {@link SUSPENSION_FIELDS} plus the two fields\n * the bandwidth verdict needs off them: the host document is the largest in\n * the product. Measured against production, projecting the three reads and\n * batching two of them took the host branch from 3 round trips and 2,964 B to\n * 2 and 34 B. The ORG read is whole since AGL-3474, because the bandwidth\n * verdict asks the org's plan, and the plan is resolved from more fields than\n * a projection could promise to keep in step with — a mask missing one would\n * read a paying org as Free and pause its video. Still one read per scope\n * per TTL.\n *\n * **Staleness bound, stated rather than hidden:** a warm origin refuses\n * within ≤15s of the org-doc write (the platform panic number). What the\n * origin cannot reach: browsers hold the stable URL up to 60s\n * (`max-age=60`), and a versioned image URL a published page names for a\n * year in a browser that already fetched it (AGL-3485); Vercel's edge holds image responses up to `s-maxage=3600`\n * (+ one stale serve while revalidating) — so an already-edge-cached image\n * URL can serve up to ~1h into a lock; non-image types are `private`\n * (AGL-1515) and never edge-held. The immutable content-hashed form is\n * browser-pinned for a year in clients that already fetched it — there is\n * no per-asset purge API, so that copy is out of reach by design; NEW\n * fetchers of the same URL refuse at the next origin miss. Compare the\n * AGL-1501 drill: host pages flip ≤10s; assets lag minutes-to-an-hour at\n * the caching tiers, which is the accepted trade for a near-zero read cost\n * on the hottest unauthenticated path.\n *\n * **Out of this control's reach entirely:** free-tier raw\n * `firebasestorage.googleapis.com` URLs — no code of ours runs there.\n * Stated on AGL-1520, and now answered elsewhere rather than here: a\n * `security` lock rotates those objects' download tokens\n * (`media-download-tokens.ts`, AGL-1526), which kills the URL at Google's\n * edge. That is a SIBLING control, not an extension of this one — it is\n * `security`-only, it is irreversible for embeds, and like this gate it\n * stops only new origin fetches (AGL-1615). Bytes already cached anywhere\n * remain out of reach of both.\n */\nconst MEDIA_CDN_LOCK_TTL_MS = 15_000\n\n/**\n * Everything the scope documents say about serving, read once per scope per\n * {@link MEDIA_CDN_LOCK_TTL_MS}: the lockdown verdict and, off the same\n * documents, the bandwidth one (AGL-3474).\n */\ninterface MediaCdnScopeVerdict {\n /** A lockdown stops every byte (AGL-1520). */\n locked: boolean\n /**\n * The bandwidth band stops video and files: the owning org's Free cap is\n * engaged this month, or the site's abuse ceiling degrades it. The same two\n * predicates the site's own pages are paused by, so a paused site's media\n * stops with it — and only its counted media: images are inside the page\n * weight the band already measures, and a paused site serves no page that\n * could ask for one.\n */\n bandwidthPaused: boolean\n /** The owning org is on a plan the cap stops rather than bills. */\n capApplies: boolean\n /** The org that owns the scope, when it is known. */\n orgId: string | null\n}\n\n/** The verdict when nothing could be read: serve, the lockdown core's posture. */\nconst OPEN_SCOPE_VERDICT: MediaCdnScopeVerdict = {\n locked: false,\n bandwidthPaused: false,\n capApplies: false,\n orgId: null,\n}\n\nconst lockCache = new Map<string, { at: number; verdict: MediaCdnScopeVerdict }>()\nconst lockPending = new Map<string, Promise<MediaCdnScopeVerdict>>()\n\n/**\n * Drop the per-scope lock cache. Tests need it between cases; production\n * convergence is the TTL — the lock is written by the console app and served\n * by the tenant app, different processes an in-process invalidation can\n * never reach.\n */\nexport function invalidateMediaCdnLockCache(): void {\n lockCache.clear()\n lockPending.clear()\n}\n\n/**\n * Every field the delivery verdict reads off a host or org document, and\n * the projection sent to Firestore — ONE list, because the carrier below is\n * built from it. A mask that omitted a field the carrier reads would leave\n * that field `undefined` and silently soften the lock: drop\n * `suspendedReasonCode` and every lock normalizes to `manual`, which\n * REFUSES delivery for locks that should serve; drop `suspendedAt` and no\n * lock is ever seen at all. Neither failure can be introduced here without\n * changing the one array both sides use.\n *\n * `suspendedMode` and `suspendedEnforcement` are deliberately absent:\n * {@link lockdownStopsMediaDelivery} decides on `reason` and the active\n * window alone, so a read-only or takedown-class lock stops these bytes on\n * exactly the same terms as a full one.\n */\nconst SUSPENSION_FIELDS = [\n 'suspendedAt',\n 'suspendedReasonCode',\n 'suspendedUntilMs',\n] as const\n\n/** Which host owns the asset's scope — the only field read off `hostIndex`. */\nconst HOST_INDEX_ORG_FIELD = 'orgId'\n\n/** The site's abuse-ceiling stamp (AGL-2155), read beside its lockdown. */\nconst HOST_BANDWIDTH_CEILING_FIELD = 'bandwidthCeiling'\n\n/** The `suspended*` field family off a snapshot, for the normalizers. */\nconst suspensionCarrier = (snapshot: {\n get: (field: string) => unknown\n}): {\n suspendedAt?: unknown\n suspendedReasonCode?: unknown\n suspendedUntilMs?: unknown\n} =>\n Object.fromEntries(\n SUSPENSION_FIELDS.map((field) => [field, snapshot.get(field)]),\n )\n\n/**\n * The verdict an org document gives: its lockdown and, when it is not locked,\n * whether its bandwidth stops video and files. `host` is the site's projected\n * document for a host-library scope, whose abuse ceiling (AGL-2155) pauses the\n * site's own media as it pauses the site's pages; an org library belongs to\n * no site, so only the org-wide cap reaches it.\n */\nfunction orgScopeVerdict(\n org: { get: (field: string) => unknown; data: () => unknown },\n orgId: string,\n host: { get: (field: string) => unknown } | null,\n nowMs: number,\n): MediaCdnScopeVerdict {\n if (lockdownStopsMediaDelivery(normalizeOrgLockdown(suspensionCarrier(org)), nowMs)) {\n return { ...OPEN_SCOPE_VERDICT, locked: true, orgId }\n }\n const billing = (org.data() ?? null) as Partial<AglynOrgBilling> | null\n const now = new Date(nowMs)\n return {\n locked: false,\n bandwidthPaused:\n bandwidthCapEngaged(billing, now) ||\n (host !== null &&\n bandwidthCeilingDegradesHost(\n { [HOST_BANDWIDTH_CEILING_FIELD]: host.get(HOST_BANDWIDTH_CEILING_FIELD) },\n bandwidthCeilingMonthKey(now),\n billing,\n )),\n // A missing org document resolves as Free, but there is no org to total\n // or to stamp, so nothing is evaluated for it.\n capApplies: billing !== null && bandwidthCapApplies(billing),\n orgId,\n }\n}\n\n/** TTL-cached: what the documents covering `scope` say about serving it. */\nasync function mediaCdnScopeVerdict(\n scope: MediaCdnScope,\n): Promise<MediaCdnScopeVerdict> {\n const key = `${scope.isOrg ? 'org' : 'host'}:${scope.scopeId}`\n const cached = lockCache.get(key)\n if (cached && Date.now() - cached.at < MEDIA_CDN_LOCK_TTL_MS) {\n return cached.verdict\n }\n let pending = lockPending.get(key)\n if (!pending) {\n pending = (async () => {\n let verdict: MediaCdnScopeVerdict = OPEN_SCOPE_VERDICT\n try {\n const nowMs = Date.now()\n // Platform first: cached, and a platform security lock is the panic\n // button — asset delivery is part of what it stops.\n if (lockdownStopsMediaDelivery(await getPlatformLockdown(), nowMs)) {\n verdict = { ...OPEN_SCOPE_VERDICT, locked: true }\n } else if (scope.isOrg) {\n const firestore = firebaseAdmin.app().firestore()\n // Org forms (`org:{orgId}` and `org:{orgId}:{hostId}`): the org\n // doc governs. The context host's own lock is not consulted — a\n // suspended HOST's pages 503 already, and which sites may USE an\n // org asset is `visibleTo`'s question, not the lock's.\n const [org] = await firestore.getAll(\n firestore.collection('orgs').doc(scope.scopeId),\n )\n verdict = orgScopeVerdict(org, scope.scopeId, null, nowMs)\n } else {\n // Host-library form: the host's own lock, and the OWNING org's —\n // an org lock never stamps host docs (AGL-1506), so a host-only\n // read would silently miss the very lock this issue is about.\n //\n // One `getAll`, not two parallel gets: `DocumentReference.get()`\n // is `getAll([ref])`, so a `Promise.all` of two of them is two\n // BatchGetDocuments round trips where the batch is one. The org\n // read below cannot join them — its id is what `hostIndex`\n // returns — so two is the floor for this branch, not three.\n const firestore = firebaseAdmin.app().firestore()\n const [host, hostIndex] = await firestore.getAll(\n firestore.collection('hosts').doc(scope.scopeId),\n firestore.collection('hostIndex').doc(scope.scopeId),\n {\n fieldMask: [\n ...SUSPENSION_FIELDS,\n HOST_INDEX_ORG_FIELD,\n HOST_BANDWIDTH_CEILING_FIELD,\n ],\n },\n )\n const orgId = hostIndex.get(HOST_INDEX_ORG_FIELD)\n if (\n lockdownStopsMediaDelivery(\n normalizeHostLockdown(suspensionCarrier(host)),\n nowMs,\n )\n ) {\n verdict = { ...OPEN_SCOPE_VERDICT, locked: true }\n } else if (typeof orgId === 'string' && orgId) {\n const [org] = await firestore.getAll(\n firestore.collection('orgs').doc(orgId),\n )\n verdict = orgScopeVerdict(org, orgId, host, nowMs)\n }\n }\n } catch {\n // Fail open — the lockdown core's posture (see lockdown.ts): an\n // unreachable Firestore is an outage, not a lockdown, and it is not a\n // spent band either.\n verdict = OPEN_SCOPE_VERDICT\n }\n lockCache.set(key, { at: Date.now(), verdict })\n return verdict\n })().finally(() => {\n lockPending.delete(key)\n })\n lockPending.set(key, pending)\n }\n return pending\n}\n\n/** TTL-cached: does any lockdown covering `scope` stop delivery? */\nasync function mediaCdnScopeLocked(scope: MediaCdnScope): Promise<boolean> {\n return (await mediaCdnScopeVerdict(scope)).locked\n}\n\n/**\n * THE delivery-policy seam: \"may this asset be served at all?\" — one\n * question, one function, consulted once per request before any caching\n * exit. Distinct from `mediaCdnAllows` (which site may use it under this\n * URL) and from the signature check (may the public fetch it): this asks\n * whether the platform is willing to serve the bytes to ANYONE.\n *\n * Two independent reasons to refuse, both answered here so the handler\n * keeps ONE refusal path:\n *\n * - `'locked'` — a lockdown covering this SCOPE (AGL-1520): the whole org\n * or host is off.\n * - `'quarantined'` — this ASSET is on the deny list (AGL-1512): one\n * infected, abusive or DMCA-noticed file is off while everything else in\n * the same workspace keeps serving. That proportionality is the point —\n * a single bad object should not cost a customer their whole site.\n *\n * Order is scope-then-asset because the scope verdict is the cheaper cached\n * one and the wider fact; the caller does not distinguish them on the wire\n * (both are the same neutral 410), so the order is a cost decision only.\n *\n * Read cost, since this sits on the hottest unauthenticated path: the scope\n * verdict is one read per SCOPE per 15s, and the quarantine deny list is\n * one read per PROCESS per 15s for every asset in existence — it is a\n * single document. Fifty DAM tiles pay two reads between them, not a\n * hundred.\n */\nexport async function mediaCdnServeBlock(\n scope: MediaCdnScope,\n asset: {\n /**\n * The strong full-width digest (AGL-1614) when the document has one.\n * Preferred over `contentHash` as a quarantine key — it is one\n * algorithm at full width, where `contentHash` is a 64-bit truncation\n * of either sha256 or md5 depending on the ingesting route.\n */\n contentSha256?: string\n contentHash?: string\n /**\n * The raw URL scope segment and media id — the fallback quarantine key\n * for the assets that carry no `contentHash` at all (legacy uploads,\n * and composite objects GCS gives no md5 for). Without it the largest\n * files in the product would be the ones a takedown could not touch.\n */\n scopeSegment?: string\n mediaId?: string\n },\n): Promise<'locked' | 'quarantined' | null> {\n if (await mediaCdnScopeLocked(scope)) return 'locked'\n const quarantined = await getMediaQuarantine({\n contentSha256: asset.contentSha256,\n contentHash: asset.contentHash,\n scopeSegment: asset.scopeSegment,\n mediaId: asset.mediaId,\n })\n return quarantined ? 'quarantined' : null\n}\n\n/**\n * Whether `?download=1` was asked for (AGL-1411). Strictly opt-in: absent,\n * `0`, `false` or junk all keep the historical `inline`, because the default\n * is what every `<img src>` in every published site relies on.\n *\n * Only `1` and `true` count, rather than \"any truthy-looking string\". The\n * query string is part of the CDN cache key, so every accepted spelling is a\n * separate edge entry for identical bytes; two is enough.\n */\nexport function wantsMediaDownload(value: unknown): boolean {\n const raw = Array.isArray(value) ? value[0] : value\n const normalized = String(raw ?? '').toLowerCase()\n return normalized === '1' || normalized === 'true'\n}\n\n/**\n * The display copy a media document records (AGL-3486), or null when it has\n * none. Only the type is needed to serve it — the object path is derived — and\n * a record whose type is not an image is treated as no record: the display\n * copy is always an image, and serving its object under another type would be\n * a document field choosing a response's `Content-Type`.\n */\nexport function mediaCdnDisplayCopy(\n value: unknown,\n): { contentType: string } | null {\n if (!value || typeof value !== 'object') return null\n const contentType = (value as { contentType?: unknown }).contentType\n if (typeof contentType !== 'string' || !/^image\\/[a-z0-9.+-]+$/.test(contentType)) {\n return null\n }\n return { contentType }\n}\n\n/**\n * Whether `?poster=1` was asked for (AGL-2743).\n *\n * Deliberately the same two-spelling rule as {@link wantsMediaDownload},\n * and for the same reason rather than out of symmetry: the query string is\n * part of the CDN cache key, so every accepted spelling of \"yes\" is another\n * edge entry holding identical bytes. A poster is the response most likely\n * to be requested at volume from a grid, which makes cache-key discipline\n * worth more here than anywhere else on the route.\n */\nexport function mediaCdnWantsPoster(value: unknown): boolean {\n return wantsMediaDownload(value)\n}\n\n/**\n * The container every browser has decoded for a decade, and the encoding the\n * producer always emits (`720p`, H.264 High in MP4). WebM/VP9 is opt-in there\n * and is the only entry a client has to earn.\n */\nconst MEDIA_CDN_BASELINE_RENDITION_TYPE = 'video/mp4'\n\n/**\n * What a client said about media types, split into the two answers that\n * matter: types it NAMED, and types it explicitly refused with `q=0`.\n *\n * Wildcards are deliberately dropped rather than expanded. `video/*` and\n * `*\\/*` are a client declining to answer the question, and the browsers that\n * matter mostly decline: Chrome and Safari send `*\\/*` for a media element's\n * request and only Firefox enumerates containers. Reading `*\\/*` as \"every\n * container decodes here\" is how a Safari 13 gets handed a WebM.\n */\nfunction parseAcceptedTypes(header: unknown): {\n named: Set<string>\n refused: Set<string>\n} {\n const raw = Array.isArray(header) ? header[0] : header\n const named = new Set<string>()\n const refused = new Set<string>()\n for (const part of String(raw ?? '').split(',')) {\n const [type, ...params] = part.split(';')\n const name = type.trim().toLowerCase()\n if (!name || name.includes('*')) continue\n // `q=0` is a refusal in RFC 9110, so it removes an entry rather than\n // selecting it — the one case where naming a type argues against it.\n const isRefusal = params.some(\n (param) => /^\\s*q\\s*=/i.test(param) && Number(param.split('=')[1]) === 0,\n )\n if (isRefusal) refused.add(name)\n else named.add(name)\n }\n return { named, refused }\n}\n\n/**\n * Which rendition `?r=auto` resolves to, or undefined for \"serve the master\"\n * (AGL-2753).\n *\n * ## Why the CDN chooses at all\n *\n * Because it is the only participant holding the media document when the\n * bytes are asked for. A page cannot name a rendition — the encodings are\n * produced out of band, after the placement, and the page is ISR-cached HTML\n * that neither regenerates when one lands nor varies on a visitor's `Accept`.\n * The composition does read a placed film's document for its length and\n * shape (AGL-2807), but that answer is frozen into the cached page. The\n * alternatives were a node backfill that rewrites published screens nobody\n * edited, or naming encodings in that same frozen page. This costs neither:\n * the document is already in hand on this request.\n *\n * ## Why the stored order is not simply obeyed\n *\n * The producer writes most-efficient-codec first, which is the right order for\n * `<source>` elements — there the BROWSER picks, using what it actually knows\n * about its own decoders. A single negotiated URL has no such luxury, so\n * taking the head of the list would hand a WebM to anything that could not\n * decode one and turn a saving into a black player.\n *\n * So an entry is selected on an EXPLICIT type match, and everything else falls\n * to the MP4 baseline the producer always emits. A client that names\n * `video/webm` gets the 30%-smaller file; a client that says `*\\/*` gets the\n * universally decodable one; a client that refused every candidate gets the\n * master. Every outcome is a video that plays.\n *\n * ## Why negotiating here is safe under a cache\n *\n * A rendition response is `video/*`, so `mediaCdnEdgeCacheable` is false and\n * it is served `private` (AGL-1515) — no shared cache stores it, which is the\n * invariant `serve-media-cdn.video.spec.ts` pins. The only cache that can hold\n * one is the requesting browser's own, and a browser's `Accept` for media does\n * not change between the first request and the Range requests that follow it.\n * That matters more than it looks: negotiation on a RANGEABLE resource that\n * could flip mid-playback would stitch byte ranges from two different files\n * together, which is the AGL-1515 corruption reached by a new road.\n */\nexport function selectAutoRendition(\n renditions: readonly MediaVideoRendition[],\n acceptHeader: unknown,\n): MediaVideoRendition | undefined {\n if (renditions.length === 0) return undefined\n const { named, refused } = parseAcceptedTypes(acceptHeader)\n const typeOf = (entry: MediaVideoRendition) => entry.contentType.toLowerCase()\n return (\n renditions.find((entry) => named.has(typeOf(entry))) ??\n renditions.find(\n (entry) =>\n typeOf(entry) === MEDIA_CDN_BASELINE_RENDITION_TYPE &&\n !refused.has(MEDIA_CDN_BASELINE_RENDITION_TYPE),\n )\n )\n}\n\n/** Anything that cannot appear literally in an HTTP field-value. */\nconst NON_ASCII_FIELD_VALUE = /[^\\x20-\\x7e]/g\n/** ...and what additionally cannot appear inside a quoted-string. */\nconst NOT_QUOTED_STRING_SAFE = /[\"\\\\]/g\n\n/**\n * The download name for an asset: the stored `fileName`, reduced to its\n * basename, falling back to the media id (the URL is id-keyed and\n * extensionless, so without a name a \"save as\" lands as a nameless blob).\n */\nexport function mediaDownloadName(fileName: unknown, mediaId: string): string {\n const raw = String(fileName ?? '').trim()\n // A stored name can carry a path from whatever uploaded it; only the last\n // segment is a filename, and `..` must never reach a client's save dialog.\n const base = raw.split(/[\\\\/]/).pop() ?? ''\n // Cap by CODE POINT so a truncation can't split a surrogate pair into a\n // lone half, which is exactly the kind of thing that later throws in a\n // header encoder.\n return Array.from(base).slice(0, 200).join('') || mediaId\n}\n\n/**\n * A `Content-Disposition` value for `name` (AGL-1411).\n *\n * A filename is attacker-adjacent data — it is whatever the uploader typed —\n * and it is being pasted into a header, so a quote or a CRLF in it is a\n * header-injection vector. The old code stripped `[\"\\\\\\r\\n]` and left\n * everything else, which is safe against injection but silently mangles the\n * name and passes non-ASCII through raw: a byte above 0x7e in a header is\n * either mojibake at the client or, on a stricter encoder than ours, a\n * throw. So both forms of RFC 6266 §4.1 are emitted:\n *\n * - `filename=\"…\"` — ASCII only, every unrepresentable character replaced\n * (not dropped) so the extension and the shape of the name survive. This\n * is the fallback, and it always exists.\n * - `filename*=UTF-8''…` — RFC 8187 percent-encoding of the real name, added\n * only when the ASCII form actually lost something. Every current browser\n * prefers it, so a non-ASCII name arrives intact.\n */\nexport function mediaContentDisposition(\n name: string,\n options: { download: boolean },\n): string {\n const type = options.download ? 'attachment' : 'inline'\n const ascii = name\n .replace(NON_ASCII_FIELD_VALUE, '_')\n .replace(NOT_QUOTED_STRING_SAFE, '_')\n const header = `${type}; filename=\"${ascii}\"`\n if (ascii === name) return header\n // `encodeURIComponent` leaves `!'()*` alone, and `'`, `(`, `)` and `*` are\n // not `attr-char` (RFC 8187 §3.2.1) — a bare `'` in particular would be\n // read as the charset/language delimiter. Encode them too.\n const encoded = encodeURIComponent(name).replace(\n /['()*]/g,\n (character) =>\n `%${character.charCodeAt(0).toString(16).toUpperCase().padStart(2, '0')}`,\n )\n return `${header}; filename*=UTF-8''${encoded}`\n}\n\n/**\n * A parsed `Range` request (AGL-1442 S4). Three answers, and the difference\n * between the last two is the whole game:\n *\n * - `{ start, end }` — a single satisfiable byte range, both ends INCLUSIVE\n * (matching `createReadStream`, which is where the numbers go).\n * - `'unsatisfiable'` — syntactically a range, but nothing in it can be\n * served (`start` past EOF, `-0`, an empty object). RFC 9110 §14.1.2 says\n * this one earns a 416.\n * - `null` — everything else: no header, another unit, a multi-range, or\n * malformed syntax. RFC 9110 §14.2 lets a server ignore a Range header\n * outright, and ignoring means a full 200 — which is both the safe answer\n * and the pre-S4 behavior, so every shape this parser does not positively\n * recognise degrades to exactly what shipped before it existed.\n *\n * Multi-range requests land in `null` DELIBERATELY. Serving them needs a\n * `multipart/byteranges` body with generated boundaries; no browser sends\n * them for media (a `<video>` seek is always one range), and a 416 would be\n * wrong because a multi-range over a real file is satisfiable. A full 200\n * is the RFC-sanctioned refusal that no client can misread.\n */\nexport type MediaCdnRange = { start: number; end: number } | 'unsatisfiable' | null\n\nexport function parseMediaCdnRange(\n header: unknown,\n size: number,\n): MediaCdnRange {\n if (typeof header !== 'string') return null\n const unit = /^bytes=(.*)$/i.exec(header.trim())\n if (!unit) return null\n const specs = unit[1].split(',')\n if (specs.length !== 1) return null\n const spec = /^(\\d*)-(\\d*)$/.exec(specs[0].trim())\n if (!spec) return null\n const [, startRaw, endRaw] = spec\n if (!startRaw && !endRaw) return null\n if (!startRaw) {\n // Suffix form `bytes=-N`: the LAST N bytes. `-0` names zero bytes and a\n // 200 would over-answer it, so it is the one suffix that 416s.\n const suffix = Number(endRaw)\n if (!Number.isSafeInteger(suffix)) return null\n if (suffix === 0 || size === 0) return 'unsatisfiable'\n return { start: Math.max(0, size - suffix), end: size - 1 }\n }\n const start = Number(startRaw)\n if (!Number.isSafeInteger(start)) return null\n const end = endRaw ? Number(endRaw) : size - 1\n if (!Number.isSafeInteger(end)) return null\n // `bytes=5-2` is a syntax error, not an unsatisfiable range — ignore it.\n if (endRaw && end < start) return null\n if (start >= size) return 'unsatisfiable'\n return { start, end: Math.min(end, size - 1) }\n}\n\n/**\n * Whether a delivery ended early only because its client stopped reading\n * (AGL-2810).\n *\n * A `<video>` element abandons its opening request once it has buffered\n * enough, and abandons another on every seek, so this is the ordinary way a\n * streamed response ends early — and it is not a delivery failure, so it is\n * neither logged nor answered.\n *\n * `pipeline` reports it as a premature close, and reports a Storage read that\n * closed early the same way. The response tells them apart: a client that\n * canceled destroyed it without an error, while a failed read makes\n * `pipeline` destroy it WITH the read's error.\n */\nfunction mediaCdnClientWentAway(\n error: unknown,\n res: { errored?: Error | null },\n): boolean {\n return (\n (error as { code?: unknown } | null)?.code === 'ERR_STREAM_PREMATURE_CLOSE' &&\n !res.errored\n )\n}\n\n/**\n * The headers that describe the file, removed from an answer that will not\n * carry it.\n *\n * A failure found after the representation was chosen would otherwise go out\n * under the file's own headers: a `Content-Length` of the whole object on a\n * short JSON body leaves the client waiting for bytes that never come, a\n * `video/mp4` type misdescribes the body outright, and the validator would\n * attach the file's identity to an error. `no-store` because the answer is\n * about this request, not the URL.\n */\nfunction clearMediaCdnRepresentation(res: NextApiResponse): void {\n for (const header of [\n 'Content-Type',\n 'Content-Length',\n 'Content-Range',\n 'Content-Disposition',\n 'Accept-Ranges',\n 'ETag',\n ]) {\n res.removeHeader(header)\n }\n res.setHeader('Cache-Control', 'no-store')\n}\n\n/**\n * Note `bytes` of counted media against the scope's org on this instance, and\n * when that makes it due, total the org's month and engage the Free cap\n * (AGL-3474; see `media-bandwidth-cap.ts`).\n *\n * Only for an org whose plan the cap stops and that is not already paused,\n * both read off the cached scope verdict, so a paying org's delivery reads\n * nothing for it. Never rejects.\n */\nasync function mediaCdnBandwidthEvaluation(\n scope: MediaCdnScope,\n bytes: number,\n): Promise<void> {\n const verdict = await mediaCdnScopeVerdict(scope)\n if (!verdict.capApplies || verdict.bandwidthPaused || !verdict.orgId) return\n if (!mediaBandwidthEvaluationDue(verdict.orgId, bytes)) return\n await engageMediaBandwidthCap(verdict.orgId)\n}\n\n/**\n * CDN media delivery (AGL-175 / AGL-829). Two URL shapes resolve the same\n * asset by `mediaId`, so delivery never depends on the object's storage\n * location (folder moves don't change the URL):\n *\n * - **Stable** `/api/media/cdn/[scope]/[mediaId]` — always serves the\n * asset's CURRENT bytes, so it survives a **replace** too. Revalidated\n * with an ETag (the content hash) + `stale-while-revalidate`, so a\n * replaced asset propagates without ever breaking references. This is\n * the URL the console hands out.\n * - **Immutable** `/api/media/cdn/[scope]/[mediaId]/[contentHash]` —\n * year-long `immutable` cache; bytes are served only while the hash\n * matches the current content, and a stale hash 302s to the stable URL\n * (AGL-2685). No renderer names this form (AGL-2798): an edge or a browser\n * holding an immutable response never asks again, so a replace could not\n * reach a page that did. It is answered for the URLs already handed out.\n *\n * `?w=[width]` selects a generated WebP variant. `?download=1` swaps the\n * `Content-Disposition` from `inline` to `attachment` (AGL-1411) — the press\n * kit hands out links that must SAVE rather than open a tab. Both are read\n * only after every access gate, so neither can widen what is served; both\n * are part of the CDN cache key, so neither variant can poison the other.\n * The same handler mounts in both the tenant and console apps; raw storage\n * URLs already embedded in screens keep working unchanged.\n *\n * Single byte-range requests are honored with a 206 (AGL-1442 S4) — see\n * {@link parseMediaCdnRange} and the block below `Accept-Ranges` for the\n * exact semantics. This is the capability whose absence kept video on raw\n * `firebasestorage.googleapis.com` URLs (S8): a `<video>` seek is a Range\n * request, and a server that ignores it forces the player to re-download\n * the whole file.\n *\n * The body streams (AGL-2810): each chunk leaves as Storage produces it, so no\n * file is held whole in function memory. A GET that will read the file is\n * first counted against its caller (AGL-2812); see\n * {@link mediaCdnRateLimitRefusal}.\n */\nexport async function serveMediaCdn(\n req: NextApiRequest,\n res: NextApiResponse,\n): Promise<void> {\n // AGL-1474, set FIRST so no exit from this handler can be reached without\n // it — including the refusals, the 304 and the 500. Upgraded below to the\n // sandboxing form once the served content type is known. `nosniff` is\n // repeated here rather than left to the app config's `/(.*)` header: the\n // policy above keys off the declared type, so this response has to carry\n // the header that makes the declared type binding, on its own.\n res.setHeader('Content-Security-Policy', MEDIA_CDN_BASE_CSP)\n res.setHeader('X-Content-Type-Options', 'nosniff')\n if (req.method !== 'GET' && req.method !== 'HEAD') {\n res.setHeader('Allow', 'GET, HEAD')\n res.status(405).end()\n return\n }\n const path = Array.isArray(req.query['path']) ? req.query['path'] : []\n const [scopeSegment, mediaId, hash] = path.map((value) =>\n String(value ?? ''),\n )\n const scope = parseMediaCdnScope(scopeSegment)\n const isOrg = scope?.isOrg ?? false\n const scopeId = scope?.scopeId ?? ''\n // 3 segments = the immutable content-hashed URL; 2 = the stable URL.\n const hashed = path.length === 3\n if (\n !scope ||\n (path.length !== 2 && path.length !== 3) ||\n !SEGMENT.test(mediaId) ||\n (hashed && !SEGMENT.test(hash))\n ) {\n res.status(400).json({ error: 'Bad media path' })\n return\n }\n\n try {\n const firestore = firebaseAdmin.app().firestore()\n const snapshot = await firestore\n .collection(isOrg ? 'orgs' : 'hosts')\n .doc(scopeId)\n .collection('media')\n .doc(mediaId)\n .get()\n const currentHash = String(snapshot.get('contentHash') ?? '')\n if (!snapshot.exists || snapshot.get('deletedAt')) {\n res.setHeader('Cache-Control', 'public, max-age=60')\n res.status(404).json({ error: 'Not found' })\n return\n }\n // Delivery gate — scope lockdown (AGL-1520) and asset quarantine\n // (AGL-1512). See mediaCdnServeBlock for the reason matrix, the\n // read-cost accounting and the staleness bound. Three shape decisions,\n // argued, and all three apply to BOTH refusal kinds:\n //\n // **Before the ETag/304 exit**, necessarily: a browser revalidating its\n // 60s copy during a lock or a quarantine must be refused, not handed a\n // 304 that renews the copy for another minute.\n //\n // **410, not 423, and identical for both kinds.** The 423 body is the\n // AGL-1506 discipline for authenticated API consumers who deserve\n // \"suspended: billing\"; this response answers anonymous `<img>` fetches\n // nobody parses, and a 423 would confirm to any prober that a lock\n // exists — which for a security lock is information. It is information\n // for a quarantine too, and of a worse kind: the existence of a DMCA\n // notice or a malware finding on a specific file is not something an\n // anonymous fetcher has standing to learn, and telling quarantine apart\n // from lockdown on the wire would leak which one it was. So the owning\n // org learns the reason in the console (`mediaQuarantineNotice`), and\n // the CDN says only `410 Gone` — neutral, indistinguishable in kind\n // from a deleted asset, and a hint to well-behaved consumers to drop\n // the link. (404 would also be neutral, but 410's \"permanently gone\"\n // semantics discourage retry loops from embedders.)\n //\n // **`no-store`, absolutely** (the AGL-1515 lesson): the refusal is a\n // function of a TTL'd verdict, not of the URL — a cached refusal would\n // weld the asset's URL identity to \"gone\" past the unlock, and edge\n // entries have no per-asset purge. The 60s negative window the 404s use\n // is not acceptable here either. For quarantine that argument is\n // strictly stronger than for lockdown: REVERSIBILITY is the whole\n // reason quarantine exists instead of deletion, so a cached 410 that\n // outlived the lift would defeat the feature's only advantage over the\n // irreversible option. The refusal is also nearly free to re-serve —\n // the verdict is one Firestore read per process per 15s for the entire\n // deny list, and the body is a few bytes with no Storage read — so\n // there is no saving to weigh against it. CSP + nosniff are already on\n // the response (set first, before any exit).\n const blockedBy = await mediaCdnServeBlock(scope, {\n // The strong digest is read for the quarantine key ONLY. It is\n // deliberately not the ETag and not the immutable URL segment\n // (AGL-1614): changing either of those would change live URLs and\n // cache validators for every existing asset, and this change must be\n // additive to the point of being invisible on the wire.\n contentSha256: String(snapshot.get('contentSha256') ?? ''),\n contentHash: currentHash,\n scopeSegment,\n mediaId,\n })\n if (blockedBy) {\n res.setHeader('Cache-Control', 'no-store')\n res.status(410).json({ error: 'Gone' })\n return\n }\n // Scope check (AGL-1043). The bare `org:` form serves ORG-WIDE assets\n // only; a restricted asset must be requested through the form that\n // names the site using it, which is when AGL-1045's confirmation already\n // warns which pages are affected.\n //\n // An asset carrying no `visibleTo` is refused here rather than treated as\n // org-wide: absent is \"nobody has said who may see this\", not \"everybody\"\n // (`app-utils/scope-tokens`). The scope backfill stamps such documents and\n // the weekly drift detector reports the ones it has not reached, so the\n // repair is a stamp rather than a wider read.\n //\n // 404 rather than 403: whether a restricted asset exists is itself\n // something the caller has no standing to learn. Short max-age so\n // re-widening a scope propagates quickly instead of being pinned by a\n // long-lived negative cache entry.\n const refusal = mediaCdnScopeRefusal(scope, snapshot.get('visibleTo'))\n if (refusal) {\n /**\n * A `restricted` refusal is the gate WORKING and is deliberately\n * silent: it is the ordinary consequence of an internal asset's URL\n * being shared outside the site it belongs to, it can be provoked by\n * anyone who can guess a scope segment, and a log line per anonymous\n * request would make this route amplify traffic into the drain.\n *\n * The other two cannot be provoked from outside — they are properties\n * of the document, not of the request — and they are the ones nothing\n * else says out loud. An asset undeliverable under every URL form\n * shows up as an image that stopped rendering on a live site, and\n * until the weekly drift detector runs the only trace it leaves is a\n * 404 indistinguishable from a deleted asset. Naming it here is what\n * makes the next occurrence readable from the logs.\n */\n if (refusal !== 'restricted') {\n console.error(\n '[media-cdn] asset has no site it can be served to',\n JSON.stringify({ scopeId, mediaId, refusal }),\n )\n }\n res.setHeader('Cache-Control', 'public, max-age=60')\n res.status(404).json({ error: 'Not found' })\n return\n }\n /**\n * Private assets (AGL-1051) leave the public model entirely: no\n * `cdnPath`, and no bytes without an unexpired signature. This is a\n * different question from the scope check above — that one asks which\n * SITE may use the asset, this one asks whether the public may fetch\n * it at all.\n *\n * `no-store`, not a short max-age. Every other response here is safe to\n * cache because it is the same answer for everyone, but a signed\n * request is per-caller and time-boxed: letting a shared cache keep\n * either the denial OR the bytes would outlive the signature and hand\n * the asset to the next requester on the same URL.\n *\n * Which is why the header goes through `setCacheControl` from here on\n * rather than `res.setHeader` — the private case has to win at EVERY\n * exit, and remembering that at each of six call sites is precisely\n * the kind of thing that gets missed when a seventh is added. It\n * already was: the stale-hash 404 below used to overwrite `no-store`\n * with `public, max-age=60`.\n */\n const isPrivate = snapshot.get('private') === true\n const setCacheControl = (value: string) => {\n res.setHeader('Cache-Control', isPrivate ? 'private, no-store' : value)\n }\n if (isPrivate) {\n const signed = verifyMediaAccess(scopeSegment, mediaId, {\n exp: Number(req.query['exp'] ?? 0),\n sig: String(req.query['sig'] ?? ''),\n aud: req.query['aud'] === 'team' ? 'team' : undefined,\n })\n setCacheControl('private, no-store')\n if (!signed) {\n res.status(404).json({ error: 'Not found' })\n return\n }\n }\n /*\n * WHAT COUNTS TOWARD THE BANDWIDTH BAND (AGL-3474).\n *\n * Every type the edge never holds, because each such response leaves from\n * origin. A public image is the exception: it is inside the page weight\n * the band already measures, and the edge serves most of it. A PRIVATE\n * image is not — it cannot be placed on a page, and `no-store` sends every\n * request to origin — so it counts like a file. The one serve that never\n * counts is the workspace previewing its own private library in the\n * console: its signature names the team (`aud=team`, inside the HMAC, so\n * a visitor's link cannot claim it).\n */\n const teamPreview = isPrivate && req.query['aud'] === 'team'\n const countsTowardBand = (type: unknown) =>\n !teamPreview && (isPrivate || !mediaCdnEdgeCacheable(type))\n /**\n * A stale hash on the immutable form REDIRECTS to the stable URL\n * (AGL-2685). It used to 404.\n *\n * The invariant the 404 protected is intact and is the reason this is a\n * redirect rather than a body: **no version of this URL ever serves bytes\n * that are not the ones its hash names.** A stale one now serves a\n * pointer to the URL that is allowed to change instead of a dead end.\n *\n * What the 404 cost was every copy of this URL breaking on the first\n * **replace** of its asset. With a redirect, a stale URL that reaches\n * this handler costs one extra hop and self-heals. It does NOT make the\n * form safe to publish: an edge or a browser already holding the\n * immutable response answers from its copy and never reaches this line,\n * which is why no renderer names this form (AGL-2798).\n *\n * **302, not 301.** A hash can become current again — a replace reverted\n * to the previous bytes restores it — and a permanent redirect is exactly\n * the thing browsers refuse to forget.\n *\n * **The stable URL's own cache policy**, not a shorter one. The redirect\n * is as re-checkable as the resource it points at, and it is the same\n * answer for every caller, so there is no reason for the edge to hold it\n * less well than it holds the target. `setCacheControl` still forces\n * `private, no-store` for a signed asset.\n *\n * The query string is REBUILT from the parameters this handler reads\n * rather than forwarded: `?w=`, `?download=` and the signature pair are\n * the whole set, they are already validated by the code below, and\n * echoing arbitrary caller input into a `Location` header is a habit\n * worth not having. Dropping an unknown parameter costs a cache-key\n * split and nothing else.\n */\n if (hashed && currentHash !== hash) {\n setCacheControl(MEDIA_CDN_STABLE_CACHE_CONTROL)\n res.setHeader(\n 'Location',\n `${MEDIA_CDN_ROUTE}/${scopeSegment}/${mediaId}${mediaCdnForwardedQuery(req.query)}`,\n )\n res.status(302).end()\n return\n }\n\n /*\n * A site icon (AGL-3484): one size of the favicon, touch icon or manifest\n * set, drawn from this asset's bytes. Past every gate above and ahead of\n * every other representation, which it excludes — see `media-cdn-icon.ts`.\n */\n const iconSpec = parseSiteIconSpec(req.query[SITE_ICON_PARAM])\n if (iconSpec) {\n const iconBase = `${isOrg ? 'orgs' : 'hosts'}/${scopeId}`\n const iconFile = firebaseAdmin\n .app()\n .storage()\n .bucket(process.env['NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET'])\n .file(\n mediaStoragePathInScope({\n storagePath: snapshot.get('storagePath'),\n base: iconBase,\n mediaId,\n }),\n )\n await serveMediaCdnIcon({\n req,\n res,\n spec: iconSpec,\n source: {\n contentType: snapshot.get('contentType'),\n contentHash: currentHash,\n stablePath: `${MEDIA_CDN_ROUTE}/${scopeSegment}/${mediaId}`,\n file: iconFile as never,\n },\n setCacheControl,\n cacheControl: {\n stable: MEDIA_CDN_STABLE_CACHE_CONTROL,\n immutable: MEDIA_CDN_IMMUTABLE_CACHE_CONTROL,\n },\n rateLimit: () =>\n mediaCdnRateLimitRefusal({\n headers: req.headers,\n remoteAddress: req.socket?.remoteAddress,\n rateClass: 'image',\n }),\n })\n return\n }\n\n const width = Number(req.query['w'] ?? 0)\n const variants: number[] = snapshot.get('variants') ?? []\n /*\n * WHICH REPRESENTATION OF THIS ASSET (AGL-2743).\n *\n * Three derived forms now sit behind one media id — the image variants\n * `?w=` has selected since AGL-175, a video's poster still, and its\n * encoded renditions. They are resolved here, together, because they are\n * mutually exclusive and because every one of them has to reach the ETag,\n * the cache tier and the object path in agreement. A second lookup later\n * in the handler is how a poster ends up cached under the master's\n * validator.\n *\n * Precedence is poster, then rendition, then width, and it is total: a\n * caller sending `?poster=1&r=720p` gets the poster rather than an error,\n * because the parameters are a REQUEST for a representation and the only\n * useful answer to an incoherent one is the safest coherent one.\n *\n * ## Falling back to the master is right for a WIDTH and wrong for a POSTER\n *\n * `?w=` on an asset with no such variant serves the next larger variant,\n * or the display copy or original when none is larger, and that is safe\n * because every answer is the same KIND of thing: an image, larger than\n * asked for. A rendition inherits it for the same\n * reason — an unknown `?r=` serves the master, which is the same video in\n * more bytes.\n *\n * `?poster=1` cannot. The caller asked for a still and the master is a\n * film, so the fallback would answer a `<video poster>` with 60 MB of\n * `video/mp4` — a request for 40 KB satisfied with the entire asset,\n * which is the precise cost this feature exists to remove, delivered by\n * the feature itself. So a poster that does not exist is a **404**, and\n * that is the better answer in the browser too: a `<video>` whose\n * `poster` 404s behaves exactly like a `<video>` with no `poster`, which\n * is what every video did before AGL-2742. It also lets a renderer emit\n * the attribute without first reading the document — the property the\n * fallback was supposed to buy, bought honestly.\n */\n const poster = snapshot.get('poster')\n const posterVariants: number[] = poster?.variants ?? []\n const wantsPoster = mediaCdnWantsPoster(req.query[MEDIA_CDN_POSTER_PARAM])\n if (wantsPoster && !poster) {\n // See above: a still is not a film, so this is the one representation\n // that must refuse rather than degrade. `no-store` because the answer\n // is about a DOCUMENT field that a poster backfill can change at any\n // time, and a cached 404 would outlive the fix.\n setCacheControl('private, no-store')\n res.status(404).json({ error: 'No poster' })\n return\n }\n const usePoster = wantsPoster\n const usePosterVariant = usePoster && Boolean(width) && posterVariants.includes(width)\n const renditionParam = req.query[MEDIA_CDN_RENDITION_PARAM]\n const renditionKey = String(\n Array.isArray(renditionParam) ? renditionParam[0] : (renditionParam ?? ''),\n )\n /*\n * `?r=auto` is the form a PAGE emits (AGL-2753), because a page cannot\n * know which encodings an asset has — they are produced out of band, long\n * after the placement. `selectAutoRendition` answers from the document\n * already read above, so serving the encodings costs no extra read\n * anywhere and a rendition made today reaches a video placed last month.\n *\n * An explicit key stays exact and stays supported: the producer's report\n * and the specs address one encoding by name, and a debugging request for\n * `?r=720p-vp9` should get that file or the master, never a negotiation.\n */\n const negotiated = !usePoster && renditionKey === MEDIA_CDN_RENDITION_AUTO\n const availableRenditions = usePoster\n ? []\n : parseMediaRenditions(snapshot.get('videoRenditions'))\n const rendition = usePoster\n ? undefined\n : negotiated\n ? selectAutoRendition(availableRenditions, req.headers['accept'])\n : availableRenditions.find((entry) => entry.key === renditionKey)\n if (negotiated) {\n // Declared for correctness rather than as the thing keeping this safe.\n // What keeps it safe is `private` (AGL-1515): a video response is never\n // held by a shared cache, so there is no entry for a second client's\n // `Accept` to be answered from. This tells the BROWSER's own cache — and\n // any intermediary that ignored `private` — that the URL alone does not\n // identify the bytes. Set before the 304 exit below, because a validator\n // handed out without it would be reused across representations.\n res.setHeader('Vary', 'Accept')\n }\n /*\n * The variant that answers `?w=`: the width itself, or the smallest one\n * above it the asset has (AGL-3486). An asset made by an older encoder\n * lacks steps the page now asks for — generation 1 had 320/640/1280/1920\n * and srcsets ask for 768 — and answering those from the original sent\n * every phone the full-size JPEG. The next width up is the same picture,\n * a little larger than asked for, as a WebP. Only an exact width is a\n * `variantServed` for the year-long policy: a stand-in is replaced by the\n * real width once the asset is regenerated.\n */\n const variantWidth =\n !usePoster && !rendition && width > 0\n ? mediaCdnVariantFor(variants, width)\n : null\n const useVariant = variantWidth !== null\n const exactVariant = variantWidth === width\n // Read only here, past every gate — a refusal above returns before the\n // parameter is ever looked at, so `?download=1` can never be the reason\n // a response happens (AGL-1411).\n const download = wantsMediaDownload(req.query['download'])\n /*\n * THE DISPLAY COPY (AGL-3486).\n *\n * An image whose original is larger than the delivery edge, carries EXIF,\n * XMP or IPTC, or leans on an EXIF orientation has a display copy: the\n * same format, downscaled, upright and stripped. It answers every INLINE\n * request that is not a variant — the bare URL, and a `?w=` the asset has\n * no variant for, which used to fall back to the original. That is the\n * request a link preview, an email client and a CSS background make, and\n * it used to hand them a 6 MB photo with the GPS position it was taken at.\n *\n * `?download=1` still serves the ORIGINAL, byte for byte: Download file is\n * a promise the library makes, and it is the one request that asks for the\n * file rather than for a picture of it.\n */\n const display = mediaCdnDisplayCopy(snapshot.get('display'))\n const useDisplay =\n !usePoster && !rendition && !useVariant && !download && display !== null\n // The generation that made the variant or display copy, in its validator:\n // a regeneration changes the bytes behind the same URL, and a browser\n // revalidating the old encode must be sent the new one, not a 304.\n const encoderVersion = mediaVariantEncoderVersionOf(\n snapshot.get(MEDIA_VARIANT_ENCODER_VERSION_FIELD),\n )\n const encoderTag = encoderVersion > 1 ? `-e${encoderVersion}` : ''\n /**\n * The ETag's representation tag, and the cache key's conscience.\n *\n * A validator is not scoped to a URL in practice — the reasoning the\n * `-dl` suffix below was added for — so two representations of one asset\n * that share a validator can be swapped for each other by any cache that\n * revalidates. Between an image variant and its original that is a wrong\n * size; between a poster and its video it is a 40 KB still standing in\n * for a 40 MB film, or the reverse.\n */\n const representation = usePoster\n ? `-poster${usePosterVariant ? `-w${width}` : ''}`\n : rendition\n ? `-r${rendition.key}`\n : useVariant\n ? `-w${variantWidth}${encoderTag}`\n : useDisplay\n ? `-d${encoderTag}`\n : ''\n\n // Stable URL: revalidate against an ETag so a replaced asset is picked\n // up (a conditional GET returns 304 while the content is unchanged).\n //\n // The disposition is part of the ETag for the same reason the variant\n // width is. The query string is already part of the CDN cache key, so\n // the two URLs are separate edge entries — but a validator is not scoped\n // to a URL in practice: a client holding the inline ETag that revalidates\n // the download URL would be answered 304 and reuse its stored INLINE\n // headers, and the file would open in a tab anyway. Same bytes, different\n // representation, so: different validator.\n const etag = currentHash\n ? `\"${currentHash}${representation}${download ? '-dl' : ''}\"`\n : null\n // AGL-1515: which cache tier may hold this response is a function of the\n // served type — see mediaCdnEdgeCacheable. Decided here from the DOC's\n // type (a variant serve is always `image/webp`) because the 304 exit\n // below runs before the Storage metadata read; re-derived from the\n // authoritative served type once metadata is in hand.\n //\n // A VERSIONED request (AGL-3485) whose token is current is the one image\n // response a browser may keep for a year; see\n // `MEDIA_CDN_VERSIONED_CACHE_CONTROL`. The hashed path form keeps its own\n // policy below, so a `?v=` on it is inert.\n const versionCurrent =\n !hashed &&\n mediaCdnVersionIsCurrent({\n token: parseMediaCdnVersionToken(req.query[MEDIA_CDN_VERSION_PARAM]),\n currentHash,\n widthRequested: Boolean(width),\n variantServed: exactVariant,\n otherRepresentation: usePoster || Boolean(rendition),\n documentEncoderVersion: snapshot.get(\n MEDIA_VARIANT_ENCODER_VERSION_FIELD,\n ),\n })\n const stableCacheControlFor = (type: unknown) =>\n mediaCdnEdgeCacheable(type)\n ? versionCurrent\n ? MEDIA_CDN_VERSIONED_CACHE_CONTROL\n : MEDIA_CDN_STABLE_CACHE_CONTROL\n : MEDIA_CDN_STABLE_EDGE_BYPASS_CACHE_CONTROL\n // A poster is `image/webp` and therefore edge-cacheable through the rule\n // that already exists — which is the whole delivery argument for it\n // (AGL-2743). `mediaCdnEdgeCacheable` is untouched: nothing here relaxes\n // AGL-1515, because a poster genuinely IS an image and no player ranges\n // into a `<video poster>`. A RENDITION reports its own `video/*` type and\n // stays origin-only, deliberately — a small fixed-size rendition is\n // exactly the size class Vercel's 10 MB cap admits, so edge-caching one\n // would maximize exposure to the very hybrid AGL-1515 recorded rather\n // than avoid it.\n const docServedType =\n usePoster || useVariant\n ? 'image/webp'\n : rendition\n ? rendition.contentType\n : useDisplay\n ? display.contentType\n : snapshot.get('contentType')\n /*\n * THE BANDWIDTH BAND (AGL-3474).\n *\n * Video and files count against the org's bandwidth allowance (see\n * `media-bandwidth.ts`), so they stop where the band stops: when the Free\n * cap is engaged for the owning org, or the site's abuse ceiling degrades\n * it — the same two predicates that pause the site's pages, read off the\n * scope documents the lockdown verdict above already holds. No read is\n * added.\n *\n * Public images keep serving. They are not counted here, a paused site\n * serves no page that asks for one, and the ones the edge holds would\n * keep serving from it whatever this said. A private image counts, so it\n * stops with the band like a file; the console's own preview does not.\n *\n * Before the provider redirect, which would otherwise hand a paused org's\n * film to the provider, and before the 304, which would renew a browser's\n * copy for another minute. `503` with `Retry-After` and `no-store`, the\n * shape the paused site's pages answer with: the pause lifts on an\n * upgrade or at the month's end, and nothing may keep the refusal past\n * either.\n */\n if (\n countsTowardBand(docServedType) &&\n (await mediaCdnScopeVerdict(scope)).bandwidthPaused\n ) {\n res.setHeader('Cache-Control', 'no-store')\n res.setHeader('Retry-After', String(BANDWIDTH_CAP_RETRY_AFTER_SECONDS))\n res.status(503).json({ error: 'Over the monthly traffic limit' })\n return\n }\n /*\n * VIDEO FROM THE DELIVERY PROVIDER (AGL-2824).\n *\n * Every gate above has run: the scope, lockdown and quarantine, the\n * private signature and the stale-pin redirect. With the representation\n * chosen, a video whose current copy sits at the configured delivery\n * provider is answered with a `302` to a short-lived signed URL there, and\n * the provider serves the bytes and their ranges. `mediaDeliveryRedirect`\n * says null for everything else — an image, a poster, no copy of this\n * representation made from the current bytes, the release flag off for\n * the owning org — and the request carries on below exactly as before.\n *\n * With no provider configured to deliver, nothing here runs: the check is\n * synchronous and reads nothing.\n *\n * A download stays here, because its disposition is this route's to set.\n * The redirect is decided before the ETag and the 304: it names a URL\n * that expires, so it carries no validator and `private, no-store`, and a\n * private asset's URL expires no later than the signature that reached\n * it. It is not counted against the caller's delivery budget, which\n * guards the bytes this route sends; a redirect sends none.\n */\n const deliveryProvider =\n usePoster || useVariant || useDisplay || download\n ? null\n : mediaDeliveryProvider('deliver')\n if (deliveryProvider) {\n const delivery = await mediaDeliveryRedirect({\n provider: deliveryProvider,\n asset: { collection: isOrg ? 'orgs' : 'hosts', scopeId, mediaId },\n document: snapshot,\n rendition,\n servedType: docServedType,\n claims: {\n scope: scopeSegment,\n mediaId,\n hostId: isOrg ? (scope.contextHostId ?? null) : scopeId,\n },\n orgId: () => mediaDeliveryOrgIdFor(isOrg ? 'orgs' : 'hosts', scopeId),\n ...(isPrivate ? { notAfterMs: Number(req.query['exp']) } : {}),\n }).catch((error) => {\n // The provider is an alternative to the path below, never a reason\n // for this request to fail.\n console.error('[media-cdn] delivery redirect failed', scopeSegment, mediaId, error)\n return null\n })\n if (delivery) {\n res.setHeader('Cache-Control', 'private, no-store')\n res.setHeader('Location', delivery.location)\n // A play starts here, so it is counted here. `redirects` says how many\n // of the serves left through the provider; the per-asset `bytes` stay\n // the provider's to measure, because none leave from this route.\n //\n // The bandwidth band is not the provider's (AGL-3474): moving a film\n // off origin must not move it off the meter. A player sends every\n // range of a sitting to the URL it was redirected to, so this request\n // is the only one of the sitting that reaches us, and it is counted\n // at the copy's full size — the most one sitting sends, whatever range\n // a player opens with.\n let evaluation: Promise<void> | null = null\n let recorded: Promise<unknown> | null = null\n if (req.method === 'GET') {\n const bandwidthBytes = teamPreview ? 0 : delivery.sizeBytes\n recorded = recordMediaServe({\n firestore,\n collection: isOrg ? 'orgs' : 'hosts',\n scopeId,\n mediaId,\n bandwidthBytes,\n redirect: true,\n })\n evaluation = mediaCdnBandwidthEvaluation(scope, bandwidthBytes)\n }\n res.status(302).end()\n // Awaited after the response, as the evaluation is, so the function\n // lives until the count is written rather than being frozen with it\n // in flight.\n await Promise.all([recorded, evaluation])\n return\n }\n }\n /*\n * LAZY REGENERATION (AGL-3486) — see `media-delivery-regeneration.ts`.\n *\n * An asset whose delivery copies an older encoder made is answered from\n * what exists, and regenerated once the response has gone. Scheduled\n * before the 304 exit, because a browser revalidating its copy is a view\n * of the asset as much as a download is.\n */\n if (\n req.method === 'GET' &&\n mediaDeliveryCopiesStale({\n contentType: snapshot.get('contentType'),\n cdnPath: snapshot.get('cdnPath'),\n sizeBytes: snapshot.get('sizeBytes'),\n width: snapshot.get('width'),\n [MEDIA_VARIANT_ENCODER_VERSION_FIELD]: snapshot.get(\n MEDIA_VARIANT_ENCODER_VERSION_FIELD,\n ),\n variantRegeneration: snapshot.get('variantRegeneration'),\n })\n ) {\n await scheduleAfterResponse(async () => {\n const regenerationPath = mediaStoragePathInScope({\n storagePath: snapshot.get('storagePath'),\n base: `${isOrg ? 'orgs' : 'hosts'}/${scopeId}`,\n mediaId,\n onRefused: () => undefined,\n })\n const outcome = await regenerateMediaDeliveryCopies({\n docRef: snapshot.ref,\n bucket: firebaseAdmin\n .app()\n .storage()\n .bucket(process.env['NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET']),\n basePath: regenerationPath,\n })\n if (outcome === 'failed') {\n console.error('[media-cdn] delivery copies could not be regenerated', scopeSegment, mediaId)\n }\n }, '[media-cdn]')\n }\n if (!hashed) {\n setCacheControl(stableCacheControlFor(docServedType))\n if (etag) res.setHeader('ETag', etag)\n if (etag && req.headers['if-none-match'] === etag) {\n res.status(304).end()\n return\n }\n }\n\n const bucket = firebaseAdmin\n .app()\n .storage()\n .bucket(process.env['NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET'])\n // Real folders: the doc records its object path; legacy assets fall\n // back to the flat layout.\n //\n // The recorded path is CLIENT DATA and this is an ADMIN-SDK bucket read,\n // which the Storage rules do not constrain — so it is honoured only when\n // it is genuinely inside this scope's media prefix (AGL-1881). Without\n // that check a host or org editor could point their own media document at\n // any key in the shared bucket and have this route stream it to an\n // anonymous caller behind a `s-maxage=3600` edge-cached URL, including the\n // fixed `adminAudit-archive/` and `erasures/` retention prefixes.\n const basePath = mediaStoragePathInScope({\n storagePath: snapshot.get('storagePath'),\n base: `${isOrg ? 'orgs' : 'hosts'}/${scopeId}`,\n mediaId,\n onRefused: (candidate) => {\n console.error(\n '[media-cdn] out-of-scope storagePath refused',\n JSON.stringify({ scopeId, mediaId, candidate }),\n )\n },\n })\n // Every derived path is built from `basePath`, which `mediaStoragePathInScope`\n // has already proved is inside this scope's prefix (AGL-1881), plus a\n // suffix whose grammar admits no separator: the widths are numbers,\n // and `parseMediaRendition` refuses a key or extension that is not\n // `[a-z0-9-]`. So no representation can address an object the master\n // could not.\n const objectPath = usePoster\n ? usePosterVariant\n ? `${basePath}${MEDIA_POSTER_OBJECT_SUFFIX}__w${width}.webp`\n : mediaPosterObjectPath(basePath)\n : rendition\n ? mediaRenditionObjectPath(basePath, rendition)\n : useVariant\n ? `${basePath}__w${variantWidth}.webp`\n : useDisplay\n ? mediaDisplayObjectPath(basePath)\n : basePath\n let file = bucket.file(objectPath)\n // The caller's count (AGL-2812), asked here and nowhere earlier: past every\n // gate and the 304 exit, where the Storage reads begin. It starts before the\n // metadata read and is awaited after it, so the two run together. It never\n // rejects, and answers `null` whenever it could not count.\n const callerCount =\n req.method === 'GET'\n ? mediaCdnRateLimitRefusal({\n headers: req.headers,\n remoteAddress: req.socket?.remoteAddress,\n rateClass: mediaCdnEdgeCacheable(docServedType) ? 'image' : 'non-image',\n })\n : null\n let [metadata] = await file.getMetadata().catch(() => [null as any])\n // A display copy the document names but Storage does not hold degrades\n // to the original — what this URL served before display copies existed —\n // rather than to a 404 on a live page.\n let servesDisplay = useDisplay\n if (!metadata && useDisplay) {\n servesDisplay = false\n file = bucket.file(basePath)\n ;[metadata] = await file.getMetadata().catch(() => [null as any])\n }\n if (!metadata) {\n res.status(404).json({ error: 'Not found' })\n return\n }\n const refused = await callerCount\n if (refused) {\n // The file's validator and cache policy are already set. A refusal is\n // about this caller rather than the URL, so no cache may keep it: an\n // edge holding a 429 would refuse every visitor to that image for an\n // hour.\n clearMediaCdnRepresentation(res)\n res.setHeader('Retry-After', String(refused.retryAfterSeconds))\n res.status(429).json({ error: 'Too many requests' })\n return\n }\n\n const servedType =\n usePoster || useVariant\n ? 'image/webp'\n : rendition\n ? rendition.contentType\n : servesDisplay && display\n ? display.contentType\n : String(\n metadata.contentType ??\n snapshot.get('contentType') ??\n 'application/octet-stream',\n )\n res.setHeader('Content-Type', servedType)\n // AGL-1474: an SVG (or anything else a browser treats as a document) gets\n // the sandboxing policy. Everything else keeps the base one set above —\n // `sandbox` is withheld from raster and PDF responses on purpose, since it\n // would change how a directly-opened PDF is handled for no security gain.\n res.setHeader(\n 'Content-Security-Policy',\n mediaCdnContentSecurityPolicy(servedType),\n )\n // Range support (AGL-1442 S4) — this is what lets video ride this route\n // at all: a `<video>` seek is a Range request, and before S4 the raw\n // storage URL was the only server that would answer one.\n //\n // `Accept-Ranges` is advertised unconditionally because it is now TRUE\n // unconditionally — the handler honors a single byte-range for every\n // asset class. Limiting it to \"types that benefit\" would only teach a\n // PDF viewer or a download manager not to ask for something we would\n // happily serve, and the header is not part of any cache key.\n //\n // Caching shape (AGL-1515): S4 read Vercel's cacheable-response\n // criteria (\"Request doesn't contain Range header\") as \"a cached full\n // body and a function-served partial can never cross\". Production\n // falsified that: the criteria govern what the edge STORES, not what it\n // SERVES, and the edge answers a ranged request from an existing cached\n // full-body 200 as a 200 + Content-Range + sliced-body hybrid. The fix\n // is upstream of this line — every type a ranged consumer actually uses\n // is served `private` (see mediaCdnEdgeCacheable), so the entry the\n // edge would mangle never exists and every ranged request reaches this\n // function for a real 206. In the browser the 206 rides the same\n // Cache-Control as its URL class: a 206 under a STRONG validator is\n // exactly what lets a player reuse the segments it already fetched.\n res.setHeader('Accept-Ranges', 'bytes')\n // The authoritative tier decision, now that the served type is known\n // from Storage metadata rather than inferred from the doc. Overwrites\n // the header set before the 304 exit iff the two disagree — and the\n // BODY-serving response is the one the edge could store, so this one\n // must win.\n if (!hashed) setCacheControl(stableCacheControlFor(servedType))\n const size = Number(metadata.size ?? NaN)\n // Range applies to GET only (RFC 9110 §14.2); a HEAD answers the full\n // representation's metadata. And with no known size there is no honest\n // `Content-Range` to write, so the header is ignored and the full body\n // served — the degradation every unrecognised shape shares.\n const parsed =\n req.method === 'GET' && Number.isFinite(size) && size >= 0\n ? parseMediaCdnRange(req.headers['range'], size)\n : null\n // `If-Range`: the client is saying \"this range is against version X; if\n // you hold anything else, send me the whole file\". Honoring the range on\n // a mismatch is how a stale player splices two versions of a video into\n // one stream — so anything but an exact match with the CURRENT strong\n // validator (including a date form, and including the no-hash legacy\n // shape that has no validator at all) collapses to a full 200.\n const ifRange = req.headers['if-range']\n const range =\n parsed !== null && typeof ifRange === 'string' && ifRange !== etag\n ? null\n : parsed\n if (range === 'unsatisfiable') {\n // The refusal is a function of the REQUEST header, not the URL, and\n // shared caches key on the URL — no cache may keep it.\n setCacheControl('private, no-store')\n res.setHeader('Content-Range', `bytes */${size}`)\n res.status(416).end()\n return\n }\n const partial = range !== null && typeof range === 'object'\n const servedBytes = partial\n ? range.end - range.start + 1\n : Number(metadata.size ?? 0)\n if (partial) {\n res.setHeader('Content-Range', `bytes ${range.start}-${range.end}/${size}`)\n res.setHeader('Content-Length', String(servedBytes))\n } else if (metadata.size) {\n res.setHeader('Content-Length', String(metadata.size))\n }\n // Give downloads a real filename+extension (AGL-834): the URL is\n // mediaId-keyed and extensionless, so without this a \"save image\" lands\n // as a name-less blob. `?download=1` makes it an actual download rather\n // than a tab (AGL-1411).\n res.setHeader(\n 'Content-Disposition',\n mediaContentDisposition(\n mediaDownloadName(snapshot.get('fileName'), mediaId),\n // `?download=1` can force a download; nothing can force an INLINE\n // render of a type that has no inline use here.\n { download: download || !servesInline(servedType) },\n ),\n )\n // `immutable` for a year is the strongest possible caching, and on a\n // private asset it would be a permanent public copy of a file whose\n // whole point is that it expires (AGL-1051) — `setCacheControl` holds\n // that line. The year stays browser-only for non-image types\n // (AGL-1515): the URL still changes with the bytes, but the edge must\n // hold no full body it could slice.\n if (hashed) {\n setCacheControl(\n mediaCdnEdgeCacheable(servedType)\n ? MEDIA_CDN_IMMUTABLE_CACHE_CONTROL\n : MEDIA_CDN_IMMUTABLE_EDGE_BYPASS_CACHE_CONTROL,\n )\n }\n\n // HEAD returns here, before the delivery counter below (AGL-2811). It\n // carries a GET's headers and no body, and `servedBytes` is the whole\n // object for any request without a range. `serves` and `bytes` count\n // representations that left, which is also why the 304 and 416 exits\n // above return before this point.\n if (req.method === 'HEAD') {\n res.status(200).end()\n return\n }\n\n // Delivery volume (AGL-176): per-asset serves/bytes on the AGL-82\n // analytics day-doc, awaited after the body rather than ahead of it.\n // Only cache MISSES reach this\n // code — edge-cached responses aren't counted, so these are origin\n // serves, not user-facing totals.\n // Hot-doc note: a single day-doc caps at ~1 write/sec sustained;\n // acceptable at current traffic, shard or sample if an asset gets hot.\n //\n // Written before the first byte, so `bytes` is what this request asked\n // the origin to send. A client that abandons the stream receives less;\n // the figure is the ceiling on what left, not a count of what arrived.\n //\n // The same bytes count against the org's bandwidth band when\n // `countsTowardBand` says so (above). In this write, so counting costs no\n // write of its own.\n const countedBytes = countsTowardBand(servedType) ? servedBytes : 0\n const day = new Date().toISOString().slice(0, 10)\n const recorded = firestore\n .collection(isOrg ? 'orgs' : 'hosts')\n .doc(scopeId)\n .collection('analytics')\n .doc(day)\n .set(\n {\n // Retention (AGL-1844): every writer of a day doc stamps the\n // day-anchored expiry the TTL policy sweeps on.\n expiresAt: analyticsDayExpiresAt(day),\n ...(countedBytes > 0\n ? {\n [MEDIA_BANDWIDTH_DAY_FIELD]:\n firebaseAdmin.firestore.FieldValue.increment(countedBytes),\n }\n : {}),\n media: {\n [mediaId]: {\n serves: firebaseAdmin.firestore.FieldValue.increment(1),\n // The bytes that actually leave, not the object's size — a\n // seek in a 200 MB video is a few MB of egress, and counting\n // the full object per range would overstate delivery by the\n // number of seeks (AGL-1442 S4).\n bytes: firebaseAdmin.firestore.FieldValue.increment(servedBytes),\n },\n },\n },\n { merge: true },\n )\n .catch((error: unknown) => logUncountedServe(scopeSegment, mediaId, error))\n // Totalled beside the stream rather than ahead of it, and awaited — with\n // the count above — once the stream ends, so the function lives until\n // both are done.\n const evaluation =\n countedBytes > 0 ? mediaCdnBandwidthEvaluation(scope, countedBytes) : null\n if (partial) res.status(206)\n // `start`/`end` are inclusive in `createReadStream`, matching the parsed\n // range — GCS is asked for exactly the requested bytes and nothing is\n // over-read from Storage on a partial serve.\n //\n // The response streams (AGL-2810): each chunk leaves as Storage produces\n // it, and the next is read only once the client has taken the last, so a\n // file is never held in function memory whatever its size. `pipeline`\n // rather than `pipe` because streaming needs teardown in both directions,\n // which `pipe` does not give — a failed Storage read must fail the\n // response, and a client that stops reading must stop the Storage read.\n try {\n await pipeline(\n file.createReadStream(partial ? { start: range.start, end: range.end } : {}),\n res,\n )\n } finally {\n await Promise.all([recorded, evaluation])\n }\n } catch (error) {\n if (mediaCdnClientWentAway(error, res)) return\n console.error('serveMediaCdn failed', scopeSegment, mediaId, error)\n if (!res.headersSent) {\n clearMediaCdnRepresentation(res)\n res.status(500).json({ error: 'Delivery failed' })\n } else {\n // The status line has already left, so the body is the only place the\n // failure can surface. A destroyed response is a broken transfer to the\n // client; ending it would pass a truncated file off as a whole one.\n res.destroy(error instanceof Error ? error : new Error(String(error)))\n }\n }\n}\n\n/**\n * A serve the day doc did not take. The response has already gone, so the\n * failure is the log's to report rather than the visitor's — but reported,\n * because a dropped count is bandwidth the band and the invoice never see.\n */\nfunction logUncountedServe(scopeSegment: string, mediaId: string, error: unknown): void {\n console.error('[media-cdn] serve not counted', scopeSegment, mediaId, error)\n}\n"],"names":["pipeline","BANDWIDTH_CAP_RETRY_AFTER_SECONDS","bandwidthCapApplies","bandwidthCapEngaged","bandwidthCeilingDegradesHost","bandwidthCeilingMonthKey","isLockdownActive","MEDIA_BANDWIDTH_DAY_FIELD","MEDIA_CDN_POSTER_PARAM","MEDIA_CDN_RENDITION_AUTO","MEDIA_CDN_RENDITION_PARAM","MEDIA_CDN_ROUTE","MEDIA_CDN_VERSION_PARAM","MEDIA_POSTER_OBJECT_SUFFIX","MEDIA_VARIANT_ENCODER_VERSION","MEDIA_VARIANT_ENCODER_VERSION_FIELD","isSecurityClassLockdownReason","mediaDisplayObjectPath","mediaPosterObjectPath","mediaVariantEncoderVersionOf","mediaRenditionObjectPath","normalizeHostLockdown","normalizeOrgLockdown","parseMediaCdnVersionToken","parseMediaRenditions","MEDIA_CDN_SEGMENT","mediaCdnScopeRefusal","parseMediaCdnScope","mediaDeliveryProvider","parseSiteIconSpec","SITE_ICON_BACKGROUND_PARAM","SITE_ICON_PARAM","SITE_ICON_VERSION_PARAM","scheduleAfterResponse","analyticsDayExpiresAt","firebaseAdmin","recordMediaServe","getPlatformLockdown","engageMediaBandwidthCap","mediaBandwidthEvaluationDue","mediaCdnRateLimitRefusal","mediaDeliveryOrgIdFor","mediaDeliveryRedirect","mediaDeliveryCopiesStale","regenerateMediaDeliveryCopies","getMediaQuarantine","verifyMediaAccess","mediaStoragePathInScope","serveMediaCdnIcon","MEDIA_CDN_VARIANT_WIDTHS","mediaCdnAllows","SEGMENT","mediaCdnForwardedQuery","query","params","URLSearchParams","key","raw","value","Array","isArray","undefined","set","String","encoded","toString","MEDIA_CDN_STABLE_CACHE_CONTROL","MEDIA_CDN_STABLE_EDGE_BYPASS_CACHE_CONTROL","MEDIA_CDN_VERSIONED_CACHE_CONTROL","mediaCdnVariantFor","variants","width","best","candidate","Number","isInteger","mediaCdnVersionIsCurrent","options","token","currentHash","contentHash","otherRepresentation","widthRequested","variantServed","recorded","documentEncoderVersion","generation","encoderVersion","MEDIA_CDN_IMMUTABLE_CACHE_CONTROL","MEDIA_CDN_IMMUTABLE_EDGE_BYPASS_CACHE_CONTROL","mediaCdnEdgeCacheable","contentType","split","trim","toLowerCase","startsWith","MEDIA_CDN_BASE_CSP","MEDIA_CDN_ACTIVE_DOCUMENT_CSP","MEDIA_CDN_ACTIVE_DOCUMENT_TYPES","Set","servesInline","type","mediaCdnContentSecurityPolicy","has","lockdownStopsMediaDelivery","state","nowMs","reason","MEDIA_CDN_LOCK_TTL_MS","OPEN_SCOPE_VERDICT","locked","bandwidthPaused","capApplies","orgId","lockCache","Map","lockPending","invalidateMediaCdnLockCache","clear","SUSPENSION_FIELDS","HOST_INDEX_ORG_FIELD","HOST_BANDWIDTH_CEILING_FIELD","suspensionCarrier","snapshot","Object","fromEntries","map","field","get","orgScopeVerdict","org","host","billing","data","now","Date","mediaCdnScopeVerdict","scope","isOrg","scopeId","cached","at","verdict","pending","firestore","app","getAll","collection","doc","hostIndex","fieldMask","finally","delete","mediaCdnScopeLocked","mediaCdnServeBlock","asset","quarantined","contentSha256","scopeSegment","mediaId","wantsMediaDownload","normalized","mediaCdnDisplayCopy","test","mediaCdnWantsPoster","MEDIA_CDN_BASELINE_RENDITION_TYPE","parseAcceptedTypes","header","named","refused","part","name","includes","isRefusal","some","param","add","selectAutoRendition","renditions","acceptHeader","length","typeOf","entry","find","NON_ASCII_FIELD_VALUE","NOT_QUOTED_STRING_SAFE","mediaDownloadName","fileName","base","pop","from","slice","join","mediaContentDisposition","download","ascii","replace","encodeURIComponent","character","charCodeAt","toUpperCase","padStart","parseMediaCdnRange","size","unit","exec","specs","spec","startRaw","endRaw","suffix","isSafeInteger","start","Math","max","end","min","mediaCdnClientWentAway","error","res","code","errored","clearMediaCdnRepresentation","removeHeader","setHeader","mediaCdnBandwidthEvaluation","bytes","serveMediaCdn","req","method","status","path","hash","hashed","json","metadata","exists","blockedBy","refusal","console","JSON","stringify","isPrivate","setCacheControl","signed","exp","sig","aud","teamPreview","countsTowardBand","iconSpec","iconBase","iconFile","storage","bucket","process","env","file","storagePath","source","stablePath","cacheControl","stable","immutable","rateLimit","headers","remoteAddress","socket","rateClass","poster","posterVariants","wantsPoster","usePoster","usePosterVariant","Boolean","renditionParam","renditionKey","negotiated","availableRenditions","rendition","variantWidth","useVariant","exactVariant","display","useDisplay","encoderTag","representation","etag","versionCurrent","stableCacheControlFor","docServedType","deliveryProvider","delivery","provider","document","servedType","claims","hostId","contextHostId","notAfterMs","catch","location","evaluation","bandwidthBytes","sizeBytes","redirect","Promise","all","cdnPath","variantRegeneration","regenerationPath","onRefused","outcome","docRef","ref","basePath","objectPath","callerCount","getMetadata","servesDisplay","retryAfterSeconds","NaN","parsed","isFinite","ifRange","range","partial","servedBytes","countedBytes","day","toISOString","expiresAt","FieldValue","increment","media","serves","merge","logUncountedServe","createReadStream","headersSent","destroy","Error"],"mappings":"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"}