import { core, primordials } from "ext:core/mod.js";
import { op_get_env_no_permission_check } from "ext:core/ops";
const {
ArrayIsArray,
ArrayPrototypePush,
Number,
NumberIsInteger,
RegExpPrototypeExec,
SafeRegExp,
String,
StringPrototypeEndsWith,
StringPrototypeIndexOf,
StringPrototypeLastIndexOf,
StringPrototypeSlice,
StringPrototypeSplit,
StringPrototypeStartsWith,
StringPrototypeToLowerCase,
StringPrototypeTrim,
SymbolFor,
TypeError,
decodeURIComponent,
} = primordials;
const { Buffer } = core.loadExtScript("ext:deno_node/internal/buffer.mjs");
const {
ERR_INVALID_ARG_TYPE,
ERR_PROXY_INVALID_CONFIG,
} = core.loadExtScript("ext:deno_node/internal/errors.ts");
const HTTP_PROXY_KEYS = ["http_proxy", "HTTP_PROXY"];
const HTTPS_PROXY_KEYS = ["https_proxy", "HTTPS_PROXY"];
const NO_PROXY_KEYS = ["no_proxy", "NO_PROXY"];
const PRIVILEGED_ENV_KEYS = [
"http_proxy",
"HTTP_PROXY",
"https_proxy",
"HTTPS_PROXY",
"no_proxy",
"NO_PROXY",
"NODE_USE_ENV_PROXY",
];
function readPrivilegedEnv() {
const env = { __proto__: null };
for (let i = 0; i < PRIVILEGED_ENV_KEYS.length; i++) {
const key = PRIVILEGED_ENV_KEYS[i];
const value = op_get_env_no_permission_check(key);
if (value !== undefined && value !== null) {
env[key] = value;
}
}
return env;
}
function isPlainObject(value) {
if (value === null || typeof value !== "object") return false;
if (ArrayIsArray(value)) return false;
return true;
}
function readEnvKey(env, keys) {
for (let i = 0; i < keys.length; i++) {
const k = keys[i];
if (env[k] !== undefined && env[k] !== "") {
return env[k];
}
}
return undefined;
}
const CRLF_RE = new SafeRegExp(/[\r\n]/);
function parseProxyUrl(raw, kind, mode) {
if (raw === undefined || raw === null || raw === "") return null;
if (typeof raw !== "string") {
if (mode === "env") {
throw new TypeError(`Invalid URL: ${raw}`);
}
throw new ERR_PROXY_INVALID_CONFIG(
`Invalid proxy URL for ${kind}: must be a string`,
);
}
if (RegExpPrototypeExec(CRLF_RE, raw) !== null) {
throw new ERR_PROXY_INVALID_CONFIG(`Invalid proxy URL: ${raw}`);
}
let url;
try {
url = new URL(raw);
} catch {
if (mode === "env") {
throw new TypeError(`Invalid URL: ${raw}`);
}
throw new ERR_PROXY_INVALID_CONFIG(`Invalid proxy URL: ${raw}`);
}
if (url.protocol !== "http:" && url.protocol !== "https:") {
if (mode === "env") {
throw new TypeError(`Invalid URL: ${raw}`);
}
throw new ERR_PROXY_INVALID_CONFIG(
`Invalid proxy URL: ${raw} (unsupported scheme ${url.protocol})`,
);
}
const username = url.username ? decodeURIComponent(url.username) : "";
const password = url.password ? decodeURIComponent(url.password) : "";
let hostname = url.hostname;
if (
StringPrototypeStartsWith(hostname, "[") &&
StringPrototypeEndsWith(hostname, "]")
) {
hostname = StringPrototypeSlice(hostname, 1, -1);
}
return {
raw,
protocol: url.protocol,
hostname,
port: url.port ? Number(url.port) : (url.protocol === "https:" ? 443 : 80),
username,
password,
auth: username
? "Basic " + Buffer.from(`${username}:${password}`).toString("base64")
: undefined,
};
}
const IPV4_RE = new SafeRegExp(
/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/,
);
function parseIPv4(s) {
const m = RegExpPrototypeExec(IPV4_RE, s);
if (!m) return null;
let acc = 0;
for (let i = 1; i <= 4; i++) {
const oct = Number(m[i]);
if (oct > 255) return null;
acc = (acc * 256) + oct;
}
return acc;
}
function parseIPv4Range(s) {
const slash = StringPrototypeIndexOf(s, "/");
if (slash !== -1) {
const ip = parseIPv4(StringPrototypeSlice(s, 0, slash));
if (ip === null) return null;
const bits = Number(StringPrototypeSlice(s, slash + 1));
if (!NumberIsInteger(bits) || bits < 0 || bits > 32) return null;
const mask = bits === 0 ? 0 : (0xffffffff << (32 - bits)) >>> 0;
const lo = ip & mask;
const hi = lo | (~mask >>> 0);
return { lo, hi };
}
const dash = StringPrototypeIndexOf(s, "-");
if (dash !== -1) {
const lo = parseIPv4(StringPrototypeSlice(s, 0, dash));
const hi = parseIPv4(StringPrototypeSlice(s, dash + 1));
if (lo === null || hi === null || lo > hi) return null;
return { lo, hi };
}
return null;
}
function parseNoProxy(raw) {
if (!raw) return null;
if (typeof raw !== "string") return null;
const trimmed = StringPrototypeTrim(raw);
if (trimmed === "") return null;
if (trimmed === "*") return { all: true, entries: [] };
const entries = [];
const parts = StringPrototypeSplit(trimmed, ",");
for (let i = 0; i < parts.length; i++) {
const part = StringPrototypeTrim(parts[i]);
if (part === "") continue;
let host = part;
let port = null;
if (StringPrototypeStartsWith(host, "[")) {
const bracket = StringPrototypeIndexOf(host, "]");
if (bracket !== -1) {
const rest = StringPrototypeSlice(host, bracket + 1);
host = StringPrototypeSlice(host, 1, bracket);
if (StringPrototypeStartsWith(rest, ":")) {
port = StringPrototypeSlice(rest, 1);
}
}
} else {
const lastColon = StringPrototypeLastIndexOf(host, ":");
if (
lastColon !== -1 && StringPrototypeIndexOf(host, ":") === lastColon &&
StringPrototypeIndexOf(host, "/") === -1 &&
StringPrototypeIndexOf(host, "-") === -1
) {
port = StringPrototypeSlice(host, lastColon + 1);
host = StringPrototypeSlice(host, 0, lastColon);
}
}
const range = parseIPv4Range(host);
if (range) {
ArrayPrototypePush(entries, {
host: null,
ipRange: range,
port: port === null ? null : Number(port),
suffixMatch: false,
});
continue;
}
let suffixMatch = false;
if (StringPrototypeStartsWith(host, "*.")) {
suffixMatch = true;
host = StringPrototypeSlice(host, 2);
} else if (StringPrototypeStartsWith(host, ".")) {
suffixMatch = true;
host = StringPrototypeSlice(host, 1);
}
ArrayPrototypePush(entries, {
host: StringPrototypeToLowerCase(host),
ipRange: null,
port: port === null ? null : Number(port),
suffixMatch,
});
}
return { all: false, entries };
}
function stripIpv6Brackets(host) {
if (
host && StringPrototypeStartsWith(host, "[") &&
StringPrototypeEndsWith(host, "]")
) {
return StringPrototypeSlice(host, 1, -1);
}
return host;
}
function shouldBypassProxy(noProxy, host, port) {
if (!noProxy) return false;
if (noProxy.all) return true;
const normalizedHost = StringPrototypeToLowerCase(
stripIpv6Brackets(String(host || "")),
);
const portNum = port == null ? null : Number(port);
const hostAsIp = parseIPv4(normalizedHost);
for (let i = 0; i < noProxy.entries.length; i++) {
const entry = noProxy.entries[i];
if (entry.port !== null && entry.port !== portNum) {
continue;
}
if (entry.ipRange !== null) {
if (
hostAsIp !== null &&
hostAsIp >= entry.ipRange.lo &&
hostAsIp <= entry.ipRange.hi
) {
return true;
}
continue;
}
if (entry.host === "" || entry.host === "*") {
return true;
}
if (entry.suffixMatch) {
if (
normalizedHost === entry.host ||
StringPrototypeEndsWith(normalizedHost, "." + entry.host)
) {
return true;
}
} else {
if (normalizedHost === entry.host) {
return true;
}
if (StringPrototypeEndsWith(normalizedHost, "." + entry.host)) {
return true;
}
}
}
return false;
}
function buildProxyConfig(env, mode) {
const httpRaw = readEnvKey(env, HTTP_PROXY_KEYS);
const httpsRaw = readEnvKey(env, HTTPS_PROXY_KEYS);
const noProxyRaw = readEnvKey(env, NO_PROXY_KEYS);
const http = parseProxyUrl(httpRaw, "http_proxy", mode);
const https = parseProxyUrl(httpsRaw, "https_proxy", mode);
if (http === null && https === null) return null;
return {
http,
https,
noProxy: parseNoProxy(noProxyRaw),
};
}
function selectProxy(config, protocol, host, port) {
if (!config) return null;
if (shouldBypassProxy(config.noProxy, host, port)) return null;
if (protocol === "https:" || protocol === "wss:") {
return config.https || config.http;
}
return config.http;
}
let globalProxyConfig = null;
let initializedFromEnv = false;
function maybeInitFromEnv() {
if (initializedFromEnv) return;
initializedFromEnv = true;
const env = readPrivilegedEnv();
const cliOverride = globalThis[SymbolFor("Deno.internal.useEnvProxy")];
let enabled;
if (cliOverride === true || cliOverride === false) {
enabled = cliOverride;
} else {
const v = env.NODE_USE_ENV_PROXY;
enabled = v === "1" || v === "true";
}
if (!enabled) return;
const cfg = buildProxyConfig(env, "env");
if (cfg) globalProxyConfig = cfg;
}
function getGlobalProxyConfig() {
maybeInitFromEnv();
return globalProxyConfig;
}
function setGlobalProxyFromEnv(input) {
let env;
if (input === undefined) {
env = readPrivilegedEnv();
} else if (!isPlainObject(input)) {
throw new ERR_INVALID_ARG_TYPE(
"proxyEnv",
"Object",
input,
);
} else {
env = input;
}
try {
maybeInitFromEnv();
} catch {
}
initializedFromEnv = true;
const newConfig = buildProxyConfig(env, "strict");
const prev = globalProxyConfig;
globalProxyConfig = newConfig;
let restored = false;
return function restore() {
if (restored) return;
restored = true;
globalProxyConfig = prev;
};
}
function resolveAgentProxyConfig(agent) {
if (agent && agent.__proxyConfig !== undefined) return agent.__proxyConfig;
maybeInitFromEnv();
return globalProxyConfig;
}
function initAgentProxy(agent, options) {
if (options && options.proxyEnv !== undefined && options.proxyEnv !== null) {
if (!isPlainObject(options.proxyEnv)) {
throw new ERR_INVALID_ARG_TYPE(
"options.proxyEnv",
"Object",
options.proxyEnv,
);
}
agent.__proxyConfig = buildProxyConfig(options.proxyEnv, "strict");
}
}
function hasGlobalProxy() {
maybeInitFromEnv();
return globalProxyConfig !== null;
}
function initFromStartupEnv() {
const env = readPrivilegedEnv();
try {
const cfg = buildProxyConfig(env, "env");
if (cfg) globalProxyConfig = cfg;
} catch {
}
}
export {
buildProxyConfig,
getGlobalProxyConfig,
hasGlobalProxy,
initAgentProxy,
initFromStartupEnv,
parseNoProxy,
parseProxyUrl,
resolveAgentProxyConfig,
selectProxy,
setGlobalProxyFromEnv,
shouldBypassProxy,
};
export default {
buildProxyConfig,
getGlobalProxyConfig,
hasGlobalProxy,
initAgentProxy,
initFromStartupEnv,
parseNoProxy,
parseProxyUrl,
resolveAgentProxyConfig,
selectProxy,
setGlobalProxyFromEnv,
shouldBypassProxy,
};