diff --git a/lib/needle.js b/lib/needle.js index cfae245ce..7fe84b440 100644 --- a/lib/needle.js +++ b/lib/needle.js @@ -5,18 +5,20 @@ // MIT Licensed ////////////////////////////////////////// -var fs = require('fs'), - http = require('http'), - https = require('https'), - url = require('url'), - stream = require('stream'), - debug = require('debug')('needle'), - stringify = require('./querystring').build, - multipart = require('./multipart'), - auth = require('./auth'), - cookies = require('./cookies'), - parsers = require('./parsers'), - decoder = require('./decoder'); +var fs = require('fs'), + http = require('http'), + https = require('https'), + url = require('url'), + stream = require('stream'), + debug = require('debug')('needle'), + stringify = require('./querystring').build, + multipart = require('./multipart'), + auth = require('./auth'), + cookies = require('./cookies'), + parsers = require('./parsers'), + decoder = require('./decoder'), + should_proxy_to = require('./proxy').should_proxy_to, + get_env_var = require('./proxy').get_env_var; ////////////////////////////////////////// // variabilia @@ -127,17 +129,6 @@ Object.keys(aliased.options).map(function(k) { ////////////////////////////////////////// // helpers -function get_env_var(keys, try_lower) { - var val, i = -1, env = process.env; - while (!val && i < keys.length-1) { - val = env[keys[++i]]; - if (!val && try_lower) { - val = env[keys[i].toLowerCase()]; - } - } - return val; -} - function keys_by_type(type) { return Object.keys(defaults).map(function(el) { if (defaults[el] !== null && defaults[el].constructor == type) @@ -190,22 +181,6 @@ function host_and_ports_match(url1, url2) { == String(b.port || (b.protocol == 'https:' ? 443 : 80)); } -// 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; - } - } - - return true; -} - function pump_streams(streams, cb) { if (stream.pipeline) return stream.pipeline.apply(null, streams.concat(cb)); diff --git a/lib/proxy.js b/lib/proxy.js new file mode 100644 index 000000000..991abfce9 --- /dev/null +++ b/lib/proxy.js @@ -0,0 +1,47 @@ +function get_env_var(keys, try_lower) { + var val, i = -1, env = process.env; + while (!val && i < keys.length-1) { + val = env[keys[++i]]; + if (!val && try_lower) { + val = env[keys[i].toLowerCase()]; + } + } + return val; +} + +// returns false if a no_proxy host matches given url +function should_proxy_to(uri) { + const noProxy = get_env_var(["NO_PROXY"], true); + if (!noProxy) { + return true; + } + + var 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) { + if(pattern.trim().length == 0) { + continue; + } + + // replace leading dot by asterisk, escape dots and finally replace asterisk by .* + const preparedPattern = pattern.replace(/^\./, "*").replace(/[.]/g, '\\$&').replace(/\*/g, '.*') + const regex = new RegExp(preparedPattern) + const isRegexExists = uri.match(regex); + if (isRegexExists) { + const matches = (isRegexExists.length > 0); + if (matches) { + // hostname + port of the request URL match a given NO_PROXY pattern + urlMatchedNoProxyPattern = true; + break; + } + } + } + + return !urlMatchedNoProxyPattern; +} + +module.exports.should_proxy_to = should_proxy_to; +module.exports.get_env_var = get_env_var; diff --git a/test/proxy_spec.js b/test/proxy_spec.js index 3b7c6009c..96a9e720f 100644 --- a/test/proxy_spec.js +++ b/test/proxy_spec.js @@ -2,7 +2,8 @@ var helpers = require('./helpers'), should = require('should'), sinon = require('sinon'), http = require('http'), - needle = require('./../'); + needle = require('./../'), + should_proxy_to = require('./../lib/proxy').should_proxy_to; var port = 7707; var url = 'localhost:' + port; @@ -137,9 +138,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 +153,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; + 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 = ""; + should_proxy_to(URI).should.true() + delete process.env.NO_PROXY; + done(); + }); + + it("shall return false if NO_PROXY is a wildcard", function(done) { + process.env.NO_PROXY = "*"; + should_proxy_to(URI).should.false() + 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; + 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; + 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; + 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; + 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; + 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; + 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; + 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/, " "); + 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/, " "); + should_proxy_to(URIip).should.false() + delete process.env.NO_PROXY; + done(); + }); + + }) + }) describe('and proxy url contains user:pass', function() {