2025-04-02 06:50:39 -04:00

166 lines
4.2 KiB
JavaScript

'use strict';
const DispatcherBase = require('./dispatcher-base');
const {
kClose,
kDestroy,
kClosed,
kDestroyed,
kDispatch,
kNoProxyAgent,
kHttpProxyAgent,
kHttpsProxyAgent,
} = require('../core/symbols');
const ProxyAgent = require('./proxy-agent');
const Agent = require('./agent');
const DEFAULT_PORTS = {
'http:': 80,
'https:': 443,
};
class EnvHttpProxyAgent extends DispatcherBase {
#noProxyValue = null;
#noProxyEntries = null;
#opts = null;
constructor(opts = {}) {
super();
this.#opts = opts;
const { httpProxy, httpsProxy, noProxy, ...agentOpts } = opts;
this[kNoProxyAgent] = new Agent(agentOpts);
const HTTP_PROXY =
httpProxy ?? process.env.http_proxy ?? process.env.HTTP_PROXY;
if (HTTP_PROXY) {
this[kHttpProxyAgent] = new ProxyAgent({ ...agentOpts, uri: HTTP_PROXY });
} else {
this[kHttpProxyAgent] = this[kNoProxyAgent];
}
const HTTPS_PROXY =
httpsProxy ?? process.env.https_proxy ?? process.env.HTTPS_PROXY;
if (HTTPS_PROXY) {
this[kHttpsProxyAgent] = new ProxyAgent({
...agentOpts,
uri: HTTPS_PROXY,
});
} else {
this[kHttpsProxyAgent] = this[kHttpProxyAgent];
}
this.#parseNoProxy();
}
[kDispatch](opts, handler) {
const url = new URL(opts.origin);
const agent = this.#getProxyAgentForUrl(url);
return agent.dispatch(opts, handler);
}
async [kClose]() {
await this[kNoProxyAgent].close();
if (!this[kHttpProxyAgent][kClosed]) {
await this[kHttpProxyAgent].close();
}
if (!this[kHttpsProxyAgent][kClosed]) {
await this[kHttpsProxyAgent].close();
}
}
async [kDestroy](err) {
await this[kNoProxyAgent].destroy(err);
if (!this[kHttpProxyAgent][kDestroyed]) {
await this[kHttpProxyAgent].destroy(err);
}
if (!this[kHttpsProxyAgent][kDestroyed]) {
await this[kHttpsProxyAgent].destroy(err);
}
}
#getProxyAgentForUrl(url) {
let { protocol, host: hostname, port } = url;
// Stripping ports in this way instead of using parsedUrl.hostname to make
// sure that the brackets around IPv6 addresses are kept.
hostname = hostname.replace(/:\d*$/, '').toLowerCase();
port = Number.parseInt(port, 10) || DEFAULT_PORTS[protocol] || 0;
if (!this.#shouldProxy(hostname, port)) {
return this[kNoProxyAgent];
}
if (protocol === 'https:') {
return this[kHttpsProxyAgent];
}
return this[kHttpProxyAgent];
}
#shouldProxy(hostname, port) {
if (this.#noProxyChanged) {
this.#parseNoProxy();
}
if (this.#noProxyEntries.length === 0) {
return true; // Always proxy if NO_PROXY is not set or empty.
}
if (this.#noProxyValue === '*') {
return false; // Never proxy if wildcard is set.
}
for (let i = 0; i < this.#noProxyEntries.length; i++) {
const entry = this.#noProxyEntries[i];
if (entry.port && entry.port !== port) {
continue; // Skip if ports don't match.
}
if (!/^[.*]/.test(entry.hostname)) {
// No wildcards, so don't proxy only if there is not an exact match.
if (hostname === entry.hostname) {
return false;
}
} else {
// Don't proxy if the hostname ends with the no_proxy host.
if (hostname.endsWith(entry.hostname.replace(/^\*/, ''))) {
return false;
}
}
}
return true;
}
#parseNoProxy() {
const noProxyValue = this.#opts.noProxy ?? this.#noProxyEnv;
const noProxySplit = noProxyValue.split(/[,\s]/);
const noProxyEntries = [];
for (let i = 0; i < noProxySplit.length; i++) {
const entry = noProxySplit[i];
if (!entry) {
continue;
}
const parsed = entry.match(/^(.+):(\d+)$/);
noProxyEntries.push({
hostname: (parsed ? parsed[1] : entry).toLowerCase(),
port: parsed ? Number.parseInt(parsed[2], 10) : 0,
});
}
this.#noProxyValue = noProxyValue;
this.#noProxyEntries = noProxyEntries;
}
get #noProxyChanged() {
if (this.#opts.noProxy !== undefined) {
return false;
}
return this.#noProxyValue !== this.#noProxyEnv;
}
get #noProxyEnv() {
return process.env.no_proxy ?? process.env.NO_PROXY ?? '';
}
}
module.exports = EnvHttpProxyAgent;