/**
 * The artifact asset-resolution contract.
 *
 * Every artifact render context resolves workspace-asset references in exactly
 * ONE way, chosen once per render as an explicit `AssetResolution` value:
 *
 *   token    — in-app canvas / present + headless Playwright render (PNG/PDF/
 *              inspect). URLs stay `/api/assets/...` and carry the `?at=`
 *              capability token; CSP allowlists `<origin>/api/assets/` only.
 *   share    — public share page. URLs are the token-scoped
 *              `/api/share/<token>/asset/...` surface; CSP allowlists exactly
 *              that token's path — never `/api/share/` broadly.
 *   relative — self-contained HTML export. Bundled refs become relative
 *              `./assets/...` paths; unbundled refs become labeled inline
 *              placeholders. CSP carries NO network sources, unconditionally
 *              (`file:` only, for opening from disk) — an exported file never
 *              phones home, and a copy hosted on an http origin cannot beacon
 *              same-origin either.
 *   none     — no asset access (unsaved previews, tests).
 *
 * Both document builders (`buildArtifactSrcdoc` in the web runtime and
 * `buildArtifactHtmlExport` in core) derive the URL rewrite AND the CSP from
 * the SAME resolution value via `applyAssetResolution` / `artifactCsp`, so a
 * context can never mix models — "URLs from one scheme, CSP from another"
 * (the share-page broken-images class) is unrepresentable.
 *
 * Fail-closed: a malformed origin or share token yields NO network source and
 * NO rewrite rather than a permissive or splicable CSP.
 */
import { appendAssetToken, rewriteAssetsToRelative, rewriteAssetsToShare, } from './artifact-asset-urls.js';
/** Normalize + validate an origin for use as a CSP source / URL prefix.
 *  Returns the bare `scheme://host[:port]` origin, or null when the value
 *  isn't a plain http(s) origin (fail-closed: no CSP source). */
function safeOrigin(value) {
    try {
        const url = new URL(value);
        if (url.protocol !== 'http:' && url.protocol !== 'https:')
            return null;
        return url.origin;
    }
    catch {
        return null;
    }
}
/** Share tokens are server-minted (`shr_` + base64url), so anything outside
 *  this charset is not a token — and must never be spliced into a CSP source
 *  or URL path (`;`/whitespace would terminate the directive; `/`/`..` would
 *  widen the path scope). */
const SHARE_TOKEN_RE = /^[A-Za-z0-9._-]+$/;
export function isValidShareToken(token) {
    return token.length > 0 && SHARE_TOKEN_RE.test(token);
}
/**
 * The network source expressions (if any) the resolution adds to the asset
 * CSP directives (`img-src` / `media-src` / `font-src`).
 *
 * D4 invariants (pinned by tests):
 *  - token    → `<origin>/api/assets/` and nothing else
 *  - share    → `<origin>/api/share/<token>/asset/` (the exact token path)
 *  - relative → `file:` ONLY, so the bundled `./assets/...` files load when
 *               the export is opened from disk. Deliberately NOT `'self'`:
 *               on an http(s)-hosted copy, `'self'` would let artifact JS
 *               beacon same-origin via img/media/font loads — "no network,
 *               unconditionally" means a hosted export shows placeholders
 *               instead (codex P1). A hostable export would be its own
 *               explicit mode, not a widening of this one.
 *  - none     → nothing
 */
export function artifactCspAssetSources(resolution) {
    switch (resolution.mode) {
        case 'token': {
            const origin = safeOrigin(resolution.origin);
            return origin ? [`${origin}/api/assets/`] : [];
        }
        case 'share': {
            const origin = safeOrigin(resolution.origin);
            if (!origin || !isValidShareToken(resolution.shareToken))
                return [];
            return [`${origin}/api/share/${resolution.shareToken}/asset/`];
        }
        case 'relative':
            return ['file:'];
        case 'none':
            return [];
    }
}
/**
 * The full iframe/document Content-Security-Policy for an artifact render.
 * Single definition for BOTH builders (web srcdoc + core HTML export) so the
 * policy can't drift between contexts. The asset directives are derived from
 * the resolution; everything else is the fixed sandbox posture (inline + blob
 * scripts allowed, no eval, no network, no frames/objects/forms).
 *
 * `allowBaseOrigin` — the Playwright render path injects `<base href>` so
 * root-relative asset URLs resolve in a `setContent` document; `base-uri`
 * must then allow exactly that origin (everything else keeps `'none'`).
 */
export function artifactCsp(resolution = { mode: 'none' }, opts = {}) {
    const sources = artifactCspAssetSources(resolution);
    const withAsset = (base) => (sources.length ? `${base} ${sources.join(' ')}` : base);
    const baseOrigin = opts.allowBaseOrigin ? safeOrigin(opts.allowBaseOrigin) : null;
    return [
        "default-src 'none'",
        baseOrigin ? `base-uri ${baseOrigin}` : "base-uri 'none'",
        "connect-src 'none'",
        withAsset('font-src data: blob:'),
        "form-action 'none'",
        "frame-src 'none'",
        withAsset('img-src data: blob:'),
        withAsset('media-src data: blob:'),
        "object-src 'none'",
        "script-src 'unsafe-inline' blob:",
        "style-src 'unsafe-inline'",
    ].join('; ');
}
/**
 * Rewrite every workspace-asset URL in a built document according to the
 * resolution. This is the ONLY url-rewrite exit in both builders — the same
 * value that produced the CSP produces the URLs.
 */
export function applyAssetResolution(html, resolution = { mode: 'none' }) {
    switch (resolution.mode) {
        case 'token':
            return resolution.token ? appendAssetToken(html, resolution.token) : html;
        case 'share':
            // Fail-closed symmetry: an invalid token produced no CSP source above,
            // so don't emit URLs either — the refs stay `/api/assets/...`, which the
            // share CSP blocks (visible as missing images, never as a leak).
            return isValidShareToken(resolution.shareToken)
                ? rewriteAssetsToShare(html, resolution.shareToken)
                : html;
        case 'relative':
            return rewriteAssetsToRelative(html, './assets', { bundledKeys: resolution.bundledKeys });
        case 'none':
            return html;
    }
}
//# sourceMappingURL=artifact-asset-resolution.js.map