import type { Dispatcher } from "undici"; import { logWarn } from "../../logger.js"; import { captureHttpExchange } from "../../proxy-capture/runtime.js"; import { buildTimeoutAbortSignal } from "../../utils/fetch-timeout.js"; import { hasProxyEnvConfigured } from "./proxy-env.js"; import { retainSafeHeadersForCrossOriginRedirect as retainSafeRedirectHeaders } from "./redirect-headers.js"; import {
fetchWithRuntimeDispatcher,
isMockedFetch,
type DispatcherAwareRequestInit,
} from "./runtime-fetch.js"; import {
assertHostnameAllowedWithPolicy,
closeDispatcher,
createPinnedDispatcher,
resolvePinnedHostnameWithPolicy,
type LookupFn,
type PinnedDispatcherPolicy,
SsrFBlockedError,
type SsrFPolicy,
} from "./ssrf.js"; import { _globalUndiciStreamTimeoutMs } from "./undici-global-dispatcher.js"; import {
createHttp1Agent,
createHttp1EnvHttpProxyAgent,
createHttp1ProxyAgent,
} from "./undici-runtime.js";
function resolveDispatcherTimeoutMs(fromParams: number | undefined): number | undefined { if (fromParams !== undefined) { return fromParams;
} // Fall back to module-level bridge set by ensureGlobalUndiciStreamTimeouts // (avoids reading Undici's non-public `.options` field) if (_globalUndiciStreamTimeoutMs !== undefined) { return _globalUndiciStreamTimeoutMs;
} return undefined;
}
async function assertExplicitProxyAllowed(
dispatcherPolicy: PinnedDispatcherPolicy | undefined,
lookupFn: LookupFn | undefined,
policy: SsrFPolicy | undefined,
): Promise<void> { if (!dispatcherPolicy || dispatcherPolicy.mode !== "explicit-proxy") { return;
}
let parsedProxyUrl: URL; try {
parsedProxyUrl = new URL(dispatcherPolicy.proxyUrl);
} catch { thrownew Error("Invalid explicit proxy URL");
} if (!["http:", "https:"].includes(parsedProxyUrl.protocol)) { thrownew Error("Explicit proxy URL must use http or https");
}
await resolvePinnedHostnameWithPolicy(parsedProxyUrl.hostname, {
lookupFn,
policy:
dispatcherPolicy.allowPrivateProxy === true
? { // The proxy hostname is operator-configured, not user input. // Clear the target-scoped hostnameAllowlist so configured proxies // like localhost or internal hosts aren't rejected by an allowlist // that was built for the target URL (for example api.example.test). // Private-network IP checks still apply via allowPrivateNetwork.
...policy,
allowPrivateNetwork: true,
hostnameAllowlist: undefined,
}
: policy,
});
}
function isRedirectStatus(status: number): boolean { return status === 301 || status === 302 || status === 303 || status === 307 || status === 308;
}
let released = false; const release = async (dispatcher?: Dispatcher | null) => { if (released) { return;
}
released = true;
cleanup();
await closeDispatcher(dispatcher ?? undefined);
};
const visited = new Set<string>([params.url]);
let currentUrl = params.url;
let currentInit = params.init ? { ...params.init } : undefined;
let redirectCount = 0;
while (true) {
let parsedUrl: URL; try {
parsedUrl = new URL(currentUrl);
} catch {
await release(); thrownew Error("Invalid URL: must be http or https");
} if (!["http:", "https:"].includes(parsedUrl.protocol)) {
await release(); thrownew Error("Invalid URL: must be http or https");
}
let dispatcher: Dispatcher | null = null; try { const usesTrustedExplicitProxyMode =
mode === GUARDED_FETCH_MODE.TRUSTED_EXPLICIT_PROXY &&
params.dispatcherPolicy?.mode === "explicit-proxy";
assertExplicitProxySupportsPinnedDns(
parsedUrl,
params.dispatcherPolicy,
usesTrustedExplicitProxyMode ? false : params.pinDns,
);
await assertExplicitProxyAllowed(params.dispatcherPolicy, params.lookupFn, params.policy); const canUseTrustedEnvProxy =
mode === GUARDED_FETCH_MODE.TRUSTED_ENV_PROXY && hasProxyEnvConfigured(); const timeoutMs = resolveDispatcherTimeoutMs(params.timeoutMs); if (canUseTrustedEnvProxy) {
dispatcher = createHttp1EnvHttpProxyAgent(undefined, timeoutMs);
} elseif (usesTrustedExplicitProxyMode) { // Explicit proxy targets are still checked against the caller's hostname // policy, but the proxy does the DNS resolution for the final target.
assertHostnameAllowedWithPolicy(parsedUrl.hostname, params.policy);
dispatcher = createPolicyDispatcherWithoutPinnedDns(params.dispatcherPolicy, timeoutMs);
} elseif (params.pinDns === false) {
await resolvePinnedHostnameWithPolicy(parsedUrl.hostname, {
lookupFn: params.lookupFn,
policy: params.policy,
});
dispatcher = createPolicyDispatcherWithoutPinnedDns(params.dispatcherPolicy, timeoutMs);
} else { const pinned = await resolvePinnedHostnameWithPolicy(parsedUrl.hostname, {
lookupFn: params.lookupFn,
policy: params.policy,
});
dispatcher = createPinnedDispatcher(
pinned,
params.dispatcherPolicy,
params.policy,
timeoutMs,
);
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.