"use strict";Object.defineProperty(exports, "__esModule", { value: true });exports.n = withStrictGuardedFetchMode;exports.r = withTrustedEnvProxyGuardedFetchMode;exports.t = fetchWithSsrFGuard;var _loggerU3s76KST = require("./logger-U3s76KST.js"); var _fetchTimeoutPoCDPIae = require("./fetch-timeout-poCDPIae.js"); var _ssrfD6FSPiLK = require("./ssrf-D6FSPiLK.js"); var _undici = require("undici"); //#region src/infra/net/fetch-guard.ts const GUARDED_FETCH_MODE = { STRICT: "strict", TRUSTED_ENV_PROXY: "trusted_env_proxy" }; const DEFAULT_MAX_REDIRECTS = 3; const CROSS_ORIGIN_REDIRECT_SAFE_HEADERS = new Set([ "accept", "accept-encoding", "accept-language", "cache-control", "content-language", "content-type", "if-match", "if-modified-since", "if-none-match", "if-unmodified-since", "pragma", "range", "user-agent"] ); function withStrictGuardedFetchMode(params) { return { ...params, mode: GUARDED_FETCH_MODE.STRICT }; } function withTrustedEnvProxyGuardedFetchMode(params) { return { ...params, mode: GUARDED_FETCH_MODE.TRUSTED_ENV_PROXY }; } function resolveGuardedFetchMode(params) { if (params.mode) return params.mode; if (params.proxy === "env" && params.dangerouslyAllowEnvProxyWithoutPinnedDns === true) return GUARDED_FETCH_MODE.TRUSTED_ENV_PROXY; return GUARDED_FETCH_MODE.STRICT; } function isRedirectStatus(status) { return status === 301 || status === 302 || status === 303 || status === 307 || status === 308; } function retainSafeHeadersForCrossOriginRedirect(init) { if (!init?.headers) return init; const incoming = new Headers(init.headers); const headers = new Headers(); for (const [key, value] of incoming.entries()) if (CROSS_ORIGIN_REDIRECT_SAFE_HEADERS.has(key.toLowerCase())) headers.set(key, value); return { ...init, headers }; } function buildAbortSignal(params) { const { timeoutMs, signal } = params; if (!timeoutMs && !signal) return { signal: void 0, cleanup: () => {} }; if (!timeoutMs) return { signal, cleanup: () => {} }; const controller = new AbortController(); const timeoutId = setTimeout(controller.abort.bind(controller), timeoutMs); const onAbort = (0, _fetchTimeoutPoCDPIae.t)(controller); if (signal) if (signal.aborted) controller.abort();else signal.addEventListener("abort", onAbort, { once: true }); const cleanup = () => { clearTimeout(timeoutId); if (signal) signal.removeEventListener("abort", onAbort); }; return { signal: controller.signal, cleanup }; } async function fetchWithSsrFGuard(params) { const fetcher = params.fetchImpl ?? globalThis.fetch; if (!fetcher) throw new Error("fetch is not available"); const maxRedirects = typeof params.maxRedirects === "number" && Number.isFinite(params.maxRedirects) ? Math.max(0, Math.floor(params.maxRedirects)) : DEFAULT_MAX_REDIRECTS; const mode = resolveGuardedFetchMode(params); const { signal, cleanup } = buildAbortSignal({ timeoutMs: params.timeoutMs, signal: params.signal }); let released = false; const release = async (dispatcher) => { if (released) return; released = true; cleanup(); await (0, _ssrfD6FSPiLK.n)(dispatcher ?? void 0); }; const visited = /* @__PURE__ */new Set(); let currentUrl = params.url; let currentInit = params.init ? { ...params.init } : void 0; let redirectCount = 0; while (true) { let parsedUrl; try { parsedUrl = new URL(currentUrl); } catch { await release(); throw new Error("Invalid URL: must be http or https"); } if (!["http:", "https:"].includes(parsedUrl.protocol)) { await release(); throw new Error("Invalid URL: must be http or https"); } let dispatcher = null; try { const pinned = await (0, _ssrfD6FSPiLK.c)(parsedUrl.hostname, { lookupFn: params.lookupFn, policy: params.policy }); if (mode === GUARDED_FETCH_MODE.TRUSTED_ENV_PROXY && (0, _ssrfD6FSPiLK.u)()) dispatcher = new _undici.EnvHttpProxyAgent();else if (params.pinDns !== false) dispatcher = (0, _ssrfD6FSPiLK.r)(pinned); const init = { ...(currentInit ? { ...currentInit } : {}), redirect: "manual", ...(dispatcher ? { dispatcher } : {}), ...(signal ? { signal } : {}) }; const response = await fetcher(parsedUrl.toString(), init); if (isRedirectStatus(response.status)) { const location = response.headers.get("location"); if (!location) { await release(dispatcher); throw new Error(`Redirect missing location header (${response.status})`); } redirectCount += 1; if (redirectCount > maxRedirects) { await release(dispatcher); throw new Error(`Too many redirects (limit: ${maxRedirects})`); } const nextParsedUrl = new URL(location, parsedUrl); const nextUrl = nextParsedUrl.toString(); if (visited.has(nextUrl)) { await release(dispatcher); throw new Error("Redirect loop detected"); } if (nextParsedUrl.origin !== parsedUrl.origin) currentInit = retainSafeHeadersForCrossOriginRedirect(currentInit); visited.add(nextUrl); response.body?.cancel(); await (0, _ssrfD6FSPiLK.n)(dispatcher); currentUrl = nextUrl; continue; } return { response, finalUrl: currentUrl, release: async () => release(dispatcher) }; } catch (err) { if (err instanceof _ssrfD6FSPiLK.t) (0, _loggerU3s76KST.i)(`security: blocked URL fetch (${params.auditContext ?? "url-fetch"}) target=${parsedUrl.origin}${parsedUrl.pathname} reason=${err.message}`); await release(dispatcher); throw err; } } } //#endregion /* v9-d5086a5ec7da0fed */