From 41e7049d39247d12ec22ae2d88debd103447a3e9 Mon Sep 17 00:00:00 2001 From: Avishagp Date: Thu, 25 Aug 2022 16:33:22 +0100 Subject: [PATCH] fix: no_proxy issue --- lib/needle.js | 43 ++++++++++++++----- package-lock.json | 43 +++++++++++++++++++ package.json | 1 + test/proxy_spec.js | 101 ++++++++++++++++++++++++++++++++++++++++++++- 4 files changed, 175 insertions(+), 13 deletions(-) diff --git a/lib/needle.js b/lib/needle.js index cfae245ce..d851c19c2 100644 --- a/lib/needle.js +++ b/lib/needle.js @@ -16,7 +16,8 @@ var fs = require('fs'), auth = require('./auth'), cookies = require('./cookies'), parsers = require('./parsers'), - decoder = require('./decoder'); + decoder = require('./decoder'), + micromatch = require('micromatch'); ////////////////////////////////////////// // variabilia @@ -191,19 +192,36 @@ function host_and_ports_match(url1, url2) { } // returns false if a no_proxy host matches given url -function should_proxy_to(url) { - var no_proxy = get_env_var(['NO_PROXY'], true); - if (!no_proxy) return true; - - var host, hosts = no_proxy.split(','); - for (var i in hosts) { - host = hosts[i]; - if (host_and_ports_match(host, url)) { - return false; +function should_proxy_to(uri) { + const noProxy = get_env_var(["NO_PROXY"], true); + if (!noProxy) { + return true; + } + + let urlMatchedNoProxyPattern = false + const requestUrl = new URL(uri); + const patternList = noProxy.split(/[\s,]+/); + + // iterate over all NO_PROXY patterns and determine whether the given URL matches any of them + for (const pattern of patternList) { + const patternNoDot = pattern.replace(/^\./, "*") + const match = patternNoDot.match(/^(?.+?)(?::(?\d+))?$/); + + if (!match || !match.groups || !match.groups.host) { + // given pattern is invalid, go to next + continue; + } + + const hostnameMatches = micromatch.isMatch( requestUrl.hostname, match.groups.host ); + const portMatches = (!match.groups.port || (requestUrl.port && (requestUrl.port === match.groups.port))); + if ( hostnameMatches && portMatches) { + // hostname + port of the request URL match a given NO_PROXY pattern + urlMatchedNoProxyPattern = true + break; } } - return true; + return !urlMatchedNoProxyPattern; } function pump_streams(streams, cb) { @@ -914,3 +932,6 @@ module.exports.defaults = function(obj) { module.exports.request = function(method, uri, data, opts, callback) { return new Needle(method, uri, data, opts, callback).start(); }; + +// Exporting for testing +module.exports.should_proxy_to = should_proxy_to; diff --git a/package-lock.json b/package-lock.json index 9fe1068dc..b6d42b1b3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -30,6 +30,14 @@ "concat-map": "0.0.1" } }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "requires": { + "fill-range": "^7.0.1" + } + }, "browser-stdout": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", @@ -77,6 +85,14 @@ "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", "dev": true }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "requires": { + "to-regex-range": "^5.0.1" + } + }, "formatio": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/formatio/-/formatio-1.2.0.tgz", @@ -148,6 +164,11 @@ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "dev": true }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==" + }, "isarray": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", @@ -172,6 +193,15 @@ "integrity": "sha1-OpoCg0UqR9dDnnJzG54H1zhuSfY=", "dev": true }, + "micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "requires": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + } + }, "minimatch": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", @@ -267,6 +297,11 @@ "isarray": "0.0.1" } }, + "picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==" + }, "pump": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", @@ -390,6 +425,14 @@ "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", "dev": true }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "requires": { + "is-number": "^7.0.0" + } + }, "type-detect": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", diff --git a/package.json b/package.json index 6bcda3fc4..7f66662cd 100644 --- a/package.json +++ b/package.json @@ -42,6 +42,7 @@ "dependencies": { "debug": "^3.2.6", "iconv-lite": "^0.6.3", + "micromatch": "^4.0.5", "sax": "^1.2.4" }, "devDependencies": { diff --git a/test/proxy_spec.js b/test/proxy_spec.js index 3b7c6009c..1c8cac9a6 100644 --- a/test/proxy_spec.js +++ b/test/proxy_spec.js @@ -137,9 +137,9 @@ describe('proxy option', function() { })) }) - it('proxies request if matching host in list but different port', function(done) { + it('does not proxy request if matching host in list and just has a different port', function(done) { process.env.NO_PROXY = 'localhost'; - send_request({ proxy: nonexisting_host + ':123/done' }, proxied(nonexisting_host, '123', function() { + send_request({ proxy: nonexisting_host + ':123/done' }, not_proxied(function() { delete process.env.NO_PROXY; done(); })) @@ -152,6 +152,103 @@ describe('proxy option', function() { done(); })) }) + + describe('should_proxy_to()', function() { + + const noProxy = ".ic1.mycorp,localhost,127.0.0.1,*.mycorp.org"; + const noProxyWithPorts = ",.mycorp.org:1234,.ic1.mycorp,localhost,127.0.0.1"; + const URI = "http://registry.random.opr.mycorp.org"; + const URIWithPort = "http://registry.random.opr.mycorp.org:9874"; + const URIWithPort1234 = "http://registry.random.opr.mycorp.org:1234"; + const URIlocalhost = "http://localhost"; + const URIip = "http://127.0.0.1"; + + it("shall return true if NO_PROXY is undefined", function(done) { + process.env.NO_PROXY = undefined; + needle.should_proxy_to(URI).should.true() + delete process.env.NO_PROXY; + done(); + }); + + it("shall return true if NO_PROXY is empty", function(done) { + process.env.NO_PROXY = ""; + needle.should_proxy_to(URI).should.true() + delete process.env.NO_PROXY; + done(); + }); + + it("shall return true if the host matches and the ports don't (URI doesn't have port specified)", function(done) { + process.env.NO_PROXY = noProxyWithPorts; + needle.should_proxy_to(URI).should.true() + delete process.env.NO_PROXY; + done(); + }); + + it("shall return true if the host matches and the ports don't (both have a port specified but just different values)", function(done) { + process.env.NO_PROXY = noProxyWithPorts; + needle.should_proxy_to(URIWithPort).should.true() + delete process.env.NO_PROXY; + done(); + }); + + it("shall return false if the host matches and the ports don't (no_proxy pattern doesn't have a port)", function(done) { + process.env.NO_PROXY = noProxy; + needle.should_proxy_to(URIWithPort).should.false() + delete process.env.NO_PROXY; + done(); + }); + + it("shall return false if host matches", function(done) { + process.env.NO_PROXY = noProxy; + needle.should_proxy_to(URI).should.false() + delete process.env.NO_PROXY; + done(); + }); + + it("shall return false if host matches", function(done) { + process.env.NO_PROXY = noProxy; + needle.should_proxy_to(URI).should.false() + delete process.env.NO_PROXY; + done(); + }); + + it("shall return false if the host and port matches", function(done) { + process.env.NO_PROXY = noProxyWithPorts; + needle.should_proxy_to(URIWithPort1234).should.false() + delete process.env.NO_PROXY; + done(); + }); + + it("shall return false if the host matches (localhost)", function(done) { + process.env.NO_PROXY = noProxy; + needle.should_proxy_to(URIlocalhost).should.false() + delete process.env.NO_PROXY; + done(); + }); + + it("shall return false if the host matches (ip)", function(done) { + process.env.NO_PROXY = noProxy; + needle.should_proxy_to(URIip).should.false() + delete process.env.NO_PROXY; + done(); + }); + + it("shall return false if the host matches (ip)", function(done) { + process.env.NO_PROXY = noProxy.replace(/g,/, " "); + needle.should_proxy_to(URIip).should.false() + delete process.env.NO_PROXY; + done(); + }); + + it("shall return false if the host matches (ip)", function(done) { + process.env.NO_PROXY = noProxy.replace(/g,/, " "); + needle.should_proxy_to(URIip).should.false() + delete process.env.NO_PROXY; + done(); + }); + + }) + }) describe('and proxy url contains user:pass', function() {