diff --git a/lib/dispatcher/env-http-proxy-agent.js b/lib/dispatcher/env-http-proxy-agent.js index f88437f1936..df2e9fec9f4 100644 --- a/lib/dispatcher/env-http-proxy-agent.js +++ b/lib/dispatcher/env-http-proxy-agent.js @@ -10,6 +10,22 @@ const DEFAULT_PORTS = { 'https:': 443 } +/** + * Normalizes a proxy URL by prepending a scheme if one is missing. + * This matches the behavior of curl and Go's httpproxy package, which + * assume http:// for scheme-less proxy values. + * + * @param {string} proxyUrl - The proxy URL to normalize + * @param {string} defaultScheme - The scheme to prepend if missing ('http' or 'https') + * @returns {string} The normalized proxy URL + */ +function normalizeProxyUrl (proxyUrl, defaultScheme) { + if (!proxyUrl) return proxyUrl + // If the value already contains a scheme (e.g. http://, https://, socks5://), return as-is + if (/^[a-z][a-z0-9+\-.]*:\/\//i.test(proxyUrl)) return proxyUrl + return `${defaultScheme}://${proxyUrl}` +} + class EnvHttpProxyAgent extends DispatcherBase { #noProxyValue = null #noProxyEntries = null @@ -23,14 +39,20 @@ class EnvHttpProxyAgent extends DispatcherBase { this[kNoProxyAgent] = new Agent(agentOpts) - const HTTP_PROXY = httpProxy ?? process.env.http_proxy ?? process.env.HTTP_PROXY + const HTTP_PROXY = normalizeProxyUrl( + httpProxy ?? process.env.http_proxy ?? process.env.HTTP_PROXY, + 'http' + ) 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 + const HTTPS_PROXY = normalizeProxyUrl( + httpsProxy ?? process.env.https_proxy ?? process.env.HTTPS_PROXY, + 'https' + ) if (HTTPS_PROXY) { this[kHttpsProxyAgent] = new ProxyAgent({ ...agentOpts, uri: HTTPS_PROXY }) } else { diff --git a/test/env-http-proxy-agent.js b/test/env-http-proxy-agent.js index 97969cfaf20..bf061704906 100644 --- a/test/env-http-proxy-agent.js +++ b/test/env-http-proxy-agent.js @@ -124,6 +124,61 @@ test('prefers lowercase over uppercase env vars even when empty', async (t) => { return dispatcher.close() }) +test('handles scheme-less HTTP_PROXY by assuming http://', async (t) => { + t = tspl(t, { plan: 2 }) + process.env.HTTP_PROXY = 'example.com:8080' + const dispatcher = new EnvHttpProxyAgent() + t.ok(dispatcher[kHttpProxyAgent] instanceof ProxyAgent) + t.equal(dispatcher[kHttpProxyAgent][kProxy].uri, 'http://example.com:8080/') + return dispatcher.close() +}) + +test('handles scheme-less HTTPS_PROXY by assuming https://', async (t) => { + t = tspl(t, { plan: 2 }) + process.env.HTTPS_PROXY = 'example.com:8443' + const dispatcher = new EnvHttpProxyAgent() + t.ok(dispatcher[kHttpsProxyAgent] instanceof ProxyAgent) + t.equal(dispatcher[kHttpsProxyAgent][kProxy].uri, 'https://example.com:8443/') + return dispatcher.close() +}) + +test('handles scheme-less httpProxy option by assuming http://', async (t) => { + t = tspl(t, { plan: 2 }) + const dispatcher = new EnvHttpProxyAgent({ httpProxy: 'myproxy:9000' }) + t.ok(dispatcher[kHttpProxyAgent] instanceof ProxyAgent) + t.equal(dispatcher[kHttpProxyAgent][kProxy].uri, 'http://myproxy:9000/') + return dispatcher.close() +}) + +test('handles scheme-less httpsProxy option by assuming https://', async (t) => { + t = tspl(t, { plan: 2 }) + const dispatcher = new EnvHttpProxyAgent({ httpsProxy: 'myproxy:9443' }) + t.ok(dispatcher[kHttpsProxyAgent] instanceof ProxyAgent) + t.equal(dispatcher[kHttpsProxyAgent][kProxy].uri, 'https://myproxy:9443/') + return dispatcher.close() +}) + +test('does not modify proxy URLs that already have a scheme', async (t) => { + t = tspl(t, { plan: 4 }) + process.env.HTTP_PROXY = 'http://example.com:8080' + process.env.HTTPS_PROXY = 'http://example.com:8443' + const dispatcher = new EnvHttpProxyAgent() + t.ok(dispatcher[kHttpProxyAgent] instanceof ProxyAgent) + t.equal(dispatcher[kHttpProxyAgent][kProxy].uri, 'http://example.com:8080/') + t.ok(dispatcher[kHttpsProxyAgent] instanceof ProxyAgent) + t.equal(dispatcher[kHttpsProxyAgent][kProxy].uri, 'http://example.com:8443/') + return dispatcher.close() +}) + +test('handles scheme-less hostname-only proxy values', async (t) => { + t = tspl(t, { plan: 2 }) + process.env.HTTP_PROXY = 'myproxy' + const dispatcher = new EnvHttpProxyAgent() + t.ok(dispatcher[kHttpProxyAgent] instanceof ProxyAgent) + t.equal(dispatcher[kHttpProxyAgent][kProxy].uri, 'http://myproxy/') + return dispatcher.close() +}) + test('creates a proxy agent only for https when only https_proxy is set', async (t) => { t = tspl(t, { plan: 5 }) process.env.https_proxy = 'http://example.com:8443'