Skip to content

Commit

Permalink
Merge 5a3be67 into 96d01f8
Browse files Browse the repository at this point in the history
  • Loading branch information
Zekfad committed Jul 16, 2020
2 parents 96d01f8 + 5a3be67 commit 78fecea
Show file tree
Hide file tree
Showing 8 changed files with 174 additions and 36 deletions.
5 changes: 4 additions & 1 deletion .eslintrc
Expand Up @@ -2,6 +2,9 @@
"env": {
"node": true
},
"parserOptions": {
"ecmaVersion": 2015
},
"rules": {
"array-bracket-spacing": [2, "never"],
"block-scoped-var": 2,
Expand All @@ -13,7 +16,7 @@
"eqeqeq": [2, "smart"],
"max-depth": [1, 3],
"max-len": [1, 80],
"max-statements": [1, 15],
"max-statements": [1, 16],
"new-cap": 1,
"no-extend-native": 2,
"no-mixed-spaces-and-tabs": 2,
Expand Down
1 change: 1 addition & 0 deletions .gitignore
@@ -1,3 +1,4 @@
*.swp
coverage/
.nyc_output/
node_modules/
11 changes: 11 additions & 0 deletions .npmignore
@@ -0,0 +1,11 @@
# Code coverage
.nyc_output
coverage/

# Tests
test.js

# Development configs and utils
**/.*ignore
**/.*rc*
**/.*.yml
9 changes: 5 additions & 4 deletions .travis.yml
Expand Up @@ -4,7 +4,8 @@ node_js:
- lts/*
script:
- npm run lint
# test-coverage will also run the tests, but does not print helpful output upon test failure.
# So we also run the tests separately.
- npm run test
- npm run test-coverage && cat coverage/lcov.info | ./node_modules/.bin/coveralls && rm -rf coverage
# We don't need to run standalone test on Travis,
# due to fact that nyc is display normal output from mocha
# - npm run test
- npm run test-coverage
- npm run test-coverage-report
30 changes: 30 additions & 0 deletions README.md
Expand Up @@ -91,6 +91,36 @@ Matching follows the following rules:

See `test.js` for examples of what should match and what does not.

#### Multiple NO_PROXY lists

By default this module overrides NO_PROXY lists in following order:

- `npm_config_no_proxy`
- `no_proxy`

This behavior can be changed to merging mode via additional options:

```javascript
getProxyForUrl(some_url, {
overrideNoProxy: false, // This will merge lists instead of overriding
});
```

Also you can pass additional lists to check programmatic:

```javascript
getProxyForUrl(some_url, {
additionalNoProxy: 'mydomain',
});
// or
getProxyForUrl(some_url, {
additionalNoProxy: [
'mydomain1,mydomain2',
process.env.MY_OWN_NO_PROXY_ENV_VAR
],
});
```

### \*\_PROXY

The environment variable used for the proxy depends on the protocol of the URL.
Expand Down
89 changes: 70 additions & 19 deletions index.js
@@ -1,8 +1,8 @@
'use strict';

var parseUrl = require('url').parse;
let parseUrl = require('url').parse;

var DEFAULT_PORTS = {
let DEFAULT_PORTS = {
ftp: 21,
gopher: 70,
http: 80,
Expand All @@ -11,21 +11,31 @@ var DEFAULT_PORTS = {
wss: 443,
};

var stringEndsWith = String.prototype.endsWith || function(s) {
let stringEndsWith = String.prototype.endsWith || function(s) {
return s.length <= this.length &&
this.indexOf(s, this.length - s.length) !== -1;
};

/**
* @param {string|object} url - The URL, or the result from url.parse.
* @param {object} options - Additional options.
* @param {boolean} [options.overrideNoProxy=true] - Whatever
* to override NO_PROXY lists or combine them.
* @param {string|string[]} [options.additionalNoProxy=[]] - Additional
* custom NO_PROXY lists.
* @return {string} The URL of the proxy that should handle the request to the
* given URL. If no proxy is set, this will be an empty string.
*/
function getProxyForUrl(url) {
var parsedUrl = typeof url === 'string' ? parseUrl(url) : url || {};
var proto = parsedUrl.protocol;
var hostname = parsedUrl.host;
var port = parsedUrl.port;
function getProxyForUrl(url, options) {
options = Object.assign({
overrideNoProxy: true,
additionalNoProxy: [],
}, options); // Default values

let parsedUrl = typeof url === 'string' ? parseUrl(url) : url || {};
let proto = parsedUrl.protocol;
let hostname = parsedUrl.host;
let port = parsedUrl.port;
if (typeof hostname !== 'string' || !hostname || typeof proto !== 'string') {
return ''; // Don't proxy URLs without a valid scheme or host.
}
Expand All @@ -35,11 +45,16 @@ function getProxyForUrl(url) {
// sure that the brackets around IPv6 addresses are kept.
hostname = hostname.replace(/:\d*$/, '');
port = parseInt(port) || DEFAULT_PORTS[proto] || 0;
if (!shouldProxy(hostname, port)) {
if (!shouldProxy(
hostname,
port,
options.overrideNoProxy,
options.additionalNoProxy
)) {
return ''; // Don't proxy URLs that match NO_PROXY.
}

var proxy =
let proxy =
getEnv('npm_config_' + proto + '_proxy') ||
getEnv(proto + '_proxy') ||
getEnv('npm_config_proxy') ||
Expand All @@ -56,15 +71,51 @@ function getProxyForUrl(url) {
*
* @param {string} hostname - The host name of the URL.
* @param {number} port - The effective port of the URL.
* @param {boolean} overrideNoProxy - Override NO_PROXY instead on merging.
* @param {string|string[]} additionalNoProxy - Additional NO_PROXY values.
* @returns {boolean} Whether the given URL should be proxied.
* @private
*/
function shouldProxy(hostname, port) {
var NO_PROXY =
(getEnv('npm_config_no_proxy') || getEnv('no_proxy')).toLowerCase();
function shouldProxy(hostname, port, overrideNoProxy, additionalNoProxy) {
if (typeof additionalNoProxy === 'string') {
additionalNoProxy = [
additionalNoProxy,
];
}

let defaultNoProxy = [
getEnv('npm_config_no_proxy'),
getEnv('no_proxy'),
];

let noProxyList = [
...defaultNoProxy,
...additionalNoProxy,
];

let NO_PROXY;

if (overrideNoProxy) {
// Behaviour from 1.1.0
NO_PROXY = noProxyList
.reduce((a, b) => a || b);
} else {
// Never proxy if wildcard is set in any of lists.
if (noProxyList.indexOf('*') !== -1) {
return false;
}

NO_PROXY = noProxyList
.filter(item => !!item) // exclude empty items
.join(',');
}

NO_PROXY = NO_PROXY.toLowerCase();

if (!NO_PROXY) {
return true; // Always proxy if NO_PROXY is not set.
}

if (NO_PROXY === '*') {
return false; // Never proxy if wildcard is set.
}
Expand All @@ -73,9 +124,9 @@ function shouldProxy(hostname, port) {
if (!proxy) {
return true; // Skip zero-length hosts.
}
var parsedProxy = proxy.match(/^(.+):(\d+)$/);
var parsedProxyHostname = parsedProxy ? parsedProxy[1] : proxy;
var parsedProxyPort = parsedProxy ? parseInt(parsedProxy[2]) : 0;
let parsedProxy = proxy.match(/^(.+):(\d+)$/);
let parsedProxyHostname = parsedProxy ? parsedProxy[1] : proxy;
let parsedProxyPort = parsedProxy ? parseInt(parsedProxy[2]) : 0;
if (parsedProxyPort && parsedProxyPort !== port) {
return true; // Skip if ports don't match.
}
Expand All @@ -95,10 +146,10 @@ function shouldProxy(hostname, port) {
}

/**
* Get the value for an environment variable.
* Get the value for an environment letiable.
*
* @param {string} key - The name of the environment variable.
* @return {string} The value of the environment variable.
* @param {string} key - The name of the environment letiable.
* @return {string} The value of the environment letiable.
* @private
*/
function getEnv(key) {
Expand Down
15 changes: 8 additions & 7 deletions package.json
@@ -1,12 +1,13 @@
{
"name": "proxy-from-env",
"version": "1.1.0",
"version": "1.2.0",
"description": "Offers getProxyForUrl to get the proxy URL for a URL, respecting the *_PROXY (e.g. HTTP_PROXY) and NO_PROXY environment variables.",
"main": "index.js",
"scripts": {
"lint": "eslint *.js",
"test": "mocha ./test.js --reporter spec",
"test-coverage": "istanbul cover ./node_modules/.bin/_mocha -- --reporter spec"
"test": "mocha ./test.js",
"test-coverage": "nyc npm test",
"test-coverage-report": "nyc report --reporter=text-lcov | coveralls"
},
"repository": {
"type": "git",
Expand All @@ -26,9 +27,9 @@
},
"homepage": "https://github.com/Rob--W/proxy-from-env#readme",
"devDependencies": {
"coveralls": "^3.0.9",
"eslint": "^6.8.0",
"istanbul": "^0.4.5",
"mocha": "^7.1.0"
"coveralls": "^3.1.0",
"eslint": "^7.4.0",
"mocha": "^8.0.1",
"nyc": "^15.1.0"
}
}
50 changes: 45 additions & 5 deletions test.js
Expand Up @@ -17,15 +17,21 @@ function runWithEnv(env, callback) {
}
}

// Defines a test case that checks whether getProxyForUrl(input) === expected.
function testProxyUrl(env, expected, input) {
// Defines a test case that checks whether
// getProxyForUrl(input, options) === expected.
function testProxyUrl(env, expected, input, options) {
assert(typeof env === 'object' && env !== null);
// Copy object to make sure that the in param does not get modified between
// the call of this function and the use of it below.
env = JSON.parse(JSON.stringify(env));

var title = 'getProxyForUrl(' + JSON.stringify(input) + ')' +
' === ' + JSON.stringify(expected);
let title = `getProxyForUrl(${
JSON.stringify(input)
}, ${
JSON.stringify(options)
}) === ${
JSON.stringify(expected)
}`;

// Save call stack for later use.
var stack = {};
Expand All @@ -38,7 +44,7 @@ function testProxyUrl(env, expected, input) {
it(title, function() {
var actual;
runWithEnv(env, function() {
actual = getProxyForUrl(input);
actual = getProxyForUrl(input, options);
});
if (expected === actual) {
return; // Good!
Expand Down Expand Up @@ -480,4 +486,38 @@ describe('getProxyForUrl', function() {
testProxyUrl(env, 'http://proxy', 'http://otherwebsite');
});
});
// eslint-disable-next-line max-len
describe('additional options', function() {
// eslint-disable-next-line max-len
describe('concat NO_PROXY lists instead of overriding', function() {
let env = {};
let options = {
overrideNoProxy: false,
};
env.HTTP_PROXY = 'http://proxy';
env.NO_PROXY = 'otherwebsite';
// eslint-disable-next-line camelcase
env.npm_config_no_proxy = 'example';

testProxyUrl(env, '', 'http://example', options);
testProxyUrl(env, '', 'http://otherwebsite', options);

});
// eslint-disable-next-line max-len
describe('programmatic additional NO_PROXY lists', function () {
let env = {};
let options = {
overrideNoProxy: false,
additionalNoProxy: 'mysite',
};
env.HTTP_PROXY = 'http://proxy';
env.NO_PROXY = 'otherwebsite';
// eslint-disable-next-line camelcase
env.npm_config_no_proxy = 'example';

testProxyUrl(env, '', 'http://example', options);
testProxyUrl(env, '', 'http://otherwebsite', options);
testProxyUrl(env, '', 'http://mysite', options);
});
});
});

0 comments on commit 78fecea

Please sign in to comment.