test.js
  1  /* eslint max-statements:0 */
  2  'use strict';
  3  
  4  var assert = require('assert');
  5  var parseUrl = require('url').parse;
  6  
  7  var getProxyForUrl = require('./').getProxyForUrl;
  8  
  9  // Runs the callback with process.env temporarily set to env.
 10  function runWithEnv(env, callback) {
 11    var originalEnv = process.env;
 12    process.env = env;
 13    try {
 14      callback();
 15    } finally {
 16      process.env = originalEnv;
 17    }
 18  }
 19  
 20  // Defines a test case that checks whether getProxyForUrl(input) === expected.
 21  function testProxyUrl(env, expected, input) {
 22    assert(typeof env === 'object' && env !== null);
 23    // Copy object to make sure that the in param does not get modified between
 24    // the call of this function and the use of it below.
 25    env = JSON.parse(JSON.stringify(env));
 26  
 27    var title = 'getProxyForUrl(' + JSON.stringify(input) + ')' +
 28       ' === ' + JSON.stringify(expected);
 29  
 30    // Save call stack for later use.
 31    var stack = {};
 32    Error.captureStackTrace(stack, testProxyUrl);
 33    // Only use the last stack frame because that shows where this function is
 34    // called, and that is sufficient for our purpose. No need to flood the logs
 35    // with an uninteresting stack trace.
 36    stack = stack.stack.split('\n', 2)[1];
 37  
 38    it(title, function() {
 39      var actual;
 40      runWithEnv(env, function() {
 41        actual = getProxyForUrl(input);
 42      });
 43      if (expected === actual) {
 44        return;  // Good!
 45      }
 46      try {
 47        assert.strictEqual(expected, actual); // Create a formatted error message.
 48        // Should not happen because previously we determined expected !== actual.
 49        throw new Error('assert.strictEqual passed. This is impossible!');
 50      } catch (e) {
 51        // Use the original stack trace, so we can see a helpful line number.
 52        e.stack = e.message + stack;
 53        throw e;
 54      }
 55    });
 56  }
 57  
 58  describe('getProxyForUrl', function() {
 59    describe('No proxy variables', function() {
 60      var env = {};
 61      testProxyUrl(env, '', 'http://example.com');
 62      testProxyUrl(env, '', 'https://example.com');
 63      testProxyUrl(env, '', 'ftp://example.com');
 64    });
 65  
 66    describe('Invalid URLs', function() {
 67      var env = {};
 68      env.ALL_PROXY = 'http://unexpected.proxy';
 69      testProxyUrl(env, '', 'bogus');
 70      testProxyUrl(env, '', '//example.com');
 71      testProxyUrl(env, '', '://example.com');
 72      testProxyUrl(env, '', '://');
 73      testProxyUrl(env, '', '/path');
 74      testProxyUrl(env, '', '');
 75      testProxyUrl(env, '', 'http:');
 76      testProxyUrl(env, '', 'http:/');
 77      testProxyUrl(env, '', 'http://');
 78      testProxyUrl(env, '', 'prototype://');
 79      testProxyUrl(env, '', 'hasOwnProperty://');
 80      testProxyUrl(env, '', '__proto__://');
 81      testProxyUrl(env, '', undefined);
 82      testProxyUrl(env, '', null);
 83      testProxyUrl(env, '', {});
 84      testProxyUrl(env, '', {host: 'x', protocol: 1});
 85      testProxyUrl(env, '', {host: 1, protocol: 'x'});
 86    });
 87  
 88    describe('http_proxy and HTTP_PROXY', function() {
 89      var env = {};
 90      env.HTTP_PROXY = 'http://http-proxy';
 91  
 92      testProxyUrl(env, '', 'https://example');
 93      testProxyUrl(env, 'http://http-proxy', 'http://example');
 94      testProxyUrl(env, 'http://http-proxy', parseUrl('http://example'));
 95  
 96      // eslint-disable-next-line camelcase
 97      env.http_proxy = 'http://priority';
 98      testProxyUrl(env, 'http://priority', 'http://example');
 99    });
100  
101    describe('http_proxy with non-sensical value', function() {
102      var env = {};
103      // Crazy values should be passed as-is. It is the responsibility of the
104      // one who launches the application that the value makes sense.
105      // TODO: Should we be stricter and perform validation?
106      env.HTTP_PROXY = 'Crazy \n!() { ::// }';
107      testProxyUrl(env, 'Crazy \n!() { ::// }', 'http://wow');
108  
109      // The implementation assumes that the HTTP_PROXY environment variable is
110      // somewhat reasonable, and if the scheme is missing, it is added.
111      // Garbage in, garbage out some would say...
112      env.HTTP_PROXY = 'crazy without colon slash slash';
113      testProxyUrl(env, 'http://crazy without colon slash slash', 'http://wow');
114    });
115  
116    describe('https_proxy and HTTPS_PROXY', function() {
117      var env = {};
118      // Assert that there is no fall back to http_proxy
119      env.HTTP_PROXY = 'http://unexpected.proxy';
120      testProxyUrl(env, '', 'https://example');
121  
122      env.HTTPS_PROXY = 'http://https-proxy';
123      testProxyUrl(env, 'http://https-proxy', 'https://example');
124  
125      // eslint-disable-next-line camelcase
126      env.https_proxy = 'http://priority';
127      testProxyUrl(env, 'http://priority', 'https://example');
128    });
129  
130    describe('ftp_proxy', function() {
131      var env = {};
132      // Something else than http_proxy / https, as a sanity check.
133      env.FTP_PROXY = 'http://ftp-proxy';
134  
135      testProxyUrl(env, 'http://ftp-proxy', 'ftp://example');
136      testProxyUrl(env, '', 'ftps://example');
137    });
138  
139    describe('all_proxy', function() {
140      var env = {};
141      env.ALL_PROXY = 'http://catch-all';
142      testProxyUrl(env, 'http://catch-all', 'https://example');
143  
144      // eslint-disable-next-line camelcase
145      env.all_proxy = 'http://priority';
146      testProxyUrl(env, 'http://priority', 'https://example');
147    });
148  
149    describe('all_proxy without scheme', function() {
150      var env = {};
151      env.ALL_PROXY = 'noscheme';
152      testProxyUrl(env, 'http://noscheme', 'http://example');
153      testProxyUrl(env, 'https://noscheme', 'https://example');
154  
155      // The module does not impose restrictions on the scheme.
156      testProxyUrl(env, 'bogus-scheme://noscheme', 'bogus-scheme://example');
157  
158      // But the URL should still be valid.
159      testProxyUrl(env, '', 'bogus');
160    });
161  
162    describe('no_proxy empty', function() {
163      var env = {};
164      env.HTTPS_PROXY = 'http://proxy';
165  
166      // NO_PROXY set but empty.
167      env.NO_PROXY = '';
168      testProxyUrl(env, 'http://proxy', 'https://example');
169  
170      // No entries in NO_PROXY (comma).
171      env.NO_PROXY = ',';
172      testProxyUrl(env, 'http://proxy', 'https://example');
173  
174      // No entries in NO_PROXY (whitespace).
175      env.NO_PROXY = ' ';
176      testProxyUrl(env, 'http://proxy', 'https://example');
177  
178      // No entries in NO_PROXY (multiple whitespace / commas).
179      env.NO_PROXY = ',\t,,,\n,  ,\r';
180      testProxyUrl(env, 'http://proxy', 'https://example');
181    });
182  
183    describe('no_proxy=example (single host)', function() {
184      var env = {};
185      env.HTTP_PROXY = 'http://proxy';
186  
187      env.NO_PROXY = 'example';
188      testProxyUrl(env, '', 'http://example');
189      testProxyUrl(env, '', 'http://example:80');
190      testProxyUrl(env, '', 'http://example:0');
191      testProxyUrl(env, '', 'http://example:1337');
192      testProxyUrl(env, 'http://proxy', 'http://sub.example');
193      testProxyUrl(env, 'http://proxy', 'http://prefexample');
194      testProxyUrl(env, 'http://proxy', 'http://example.no');
195      testProxyUrl(env, 'http://proxy', 'http://a.b.example');
196      testProxyUrl(env, 'http://proxy', 'http://host/example');
197    });
198  
199    describe('no_proxy=sub.example (subdomain)', function() {
200      var env = {};
201      env.HTTP_PROXY = 'http://proxy';
202  
203      env.NO_PROXY = 'sub.example';
204      testProxyUrl(env, 'http://proxy', 'http://example');
205      testProxyUrl(env, 'http://proxy', 'http://example:80');
206      testProxyUrl(env, 'http://proxy', 'http://example:0');
207      testProxyUrl(env, 'http://proxy', 'http://example:1337');
208      testProxyUrl(env, '', 'http://sub.example');
209      testProxyUrl(env, 'http://proxy', 'http://no.sub.example');
210      testProxyUrl(env, 'http://proxy', 'http://sub-example');
211      testProxyUrl(env, 'http://proxy', 'http://example.sub');
212    });
213  
214    describe('no_proxy=example:80 (host + port)', function() {
215      var env = {};
216      env.HTTP_PROXY = 'http://proxy';
217  
218      env.NO_PROXY = 'example:80';
219      testProxyUrl(env, '', 'http://example');
220      testProxyUrl(env, '', 'http://example:80');
221      testProxyUrl(env, '', 'http://example:0');
222      testProxyUrl(env, 'http://proxy', 'http://example:1337');
223      testProxyUrl(env, 'http://proxy', 'http://sub.example');
224      testProxyUrl(env, 'http://proxy', 'http://prefexample');
225      testProxyUrl(env, 'http://proxy', 'http://example.no');
226      testProxyUrl(env, 'http://proxy', 'http://a.b.example');
227    });
228  
229    describe('no_proxy=.example (host suffix)', function() {
230      var env = {};
231      env.HTTP_PROXY = 'http://proxy';
232  
233      env.NO_PROXY = '.example';
234      testProxyUrl(env, 'http://proxy', 'http://example');
235      testProxyUrl(env, 'http://proxy', 'http://example:80');
236      testProxyUrl(env, 'http://proxy', 'http://example:1337');
237      testProxyUrl(env, '', 'http://sub.example');
238      testProxyUrl(env, '', 'http://sub.example:80');
239      testProxyUrl(env, '', 'http://sub.example:1337');
240      testProxyUrl(env, 'http://proxy', 'http://prefexample');
241      testProxyUrl(env, 'http://proxy', 'http://example.no');
242      testProxyUrl(env, '', 'http://a.b.example');
243    });
244  
245    describe('no_proxy=*', function() {
246      var env = {};
247      env.HTTP_PROXY = 'http://proxy';
248      env.NO_PROXY = '*';
249      testProxyUrl(env, '', 'http://example.com');
250    });
251  
252    describe('no_proxy=*.example (host suffix with *.)', function() {
253      var env = {};
254      env.HTTP_PROXY = 'http://proxy';
255  
256      env.NO_PROXY = '*.example';
257      testProxyUrl(env, 'http://proxy', 'http://example');
258      testProxyUrl(env, 'http://proxy', 'http://example:80');
259      testProxyUrl(env, 'http://proxy', 'http://example:1337');
260      testProxyUrl(env, '', 'http://sub.example');
261      testProxyUrl(env, '', 'http://sub.example:80');
262      testProxyUrl(env, '', 'http://sub.example:1337');
263      testProxyUrl(env, 'http://proxy', 'http://prefexample');
264      testProxyUrl(env, 'http://proxy', 'http://example.no');
265      testProxyUrl(env, '', 'http://a.b.example');
266    });
267  
268    describe('no_proxy=*example (substring suffix)', function() {
269      var env = {};
270      env.HTTP_PROXY = 'http://proxy';
271  
272      env.NO_PROXY = '*example';
273      testProxyUrl(env, '', 'http://example');
274      testProxyUrl(env, '', 'http://example:80');
275      testProxyUrl(env, '', 'http://example:1337');
276      testProxyUrl(env, '', 'http://sub.example');
277      testProxyUrl(env, '', 'http://sub.example:80');
278      testProxyUrl(env, '', 'http://sub.example:1337');
279      testProxyUrl(env, '', 'http://prefexample');
280      testProxyUrl(env, '', 'http://a.b.example');
281      testProxyUrl(env, 'http://proxy', 'http://example.no');
282      testProxyUrl(env, 'http://proxy', 'http://host/example');
283    });
284  
285    describe('no_proxy=.*example (arbitrary wildcards are NOT supported)',
286        function() {
287      var env = {};
288      env.HTTP_PROXY = 'http://proxy';
289  
290      env.NO_PROXY = '.*example';
291      testProxyUrl(env, 'http://proxy', 'http://example');
292      testProxyUrl(env, 'http://proxy', 'http://sub.example');
293      testProxyUrl(env, 'http://proxy', 'http://sub.example');
294      testProxyUrl(env, 'http://proxy', 'http://prefexample');
295      testProxyUrl(env, 'http://proxy', 'http://x.prefexample');
296      testProxyUrl(env, 'http://proxy', 'http://a.b.example');
297    });
298  
299    describe('no_proxy=[::1],[::2]:80,10.0.0.1,10.0.0.2:80 (IP addresses)',
300        function() {
301      var env = {};
302      env.HTTP_PROXY = 'http://proxy';
303  
304      env.NO_PROXY = '[::1],[::2]:80,10.0.0.1,10.0.0.2:80';
305      testProxyUrl(env, '', 'http://[::1]/');
306      testProxyUrl(env, '', 'http://[::1]:80/');
307      testProxyUrl(env, '', 'http://[::1]:1337/');
308  
309      testProxyUrl(env, '', 'http://[::2]/');
310      testProxyUrl(env, '', 'http://[::2]:80/');
311      testProxyUrl(env, 'http://proxy', 'http://[::2]:1337/');
312  
313      testProxyUrl(env, '', 'http://10.0.0.1/');
314      testProxyUrl(env, '', 'http://10.0.0.1:80/');
315      testProxyUrl(env, '', 'http://10.0.0.1:1337/');
316  
317      testProxyUrl(env, '', 'http://10.0.0.2/');
318      testProxyUrl(env, '', 'http://10.0.0.2:80/');
319      testProxyUrl(env, 'http://proxy', 'http://10.0.0.2:1337/');
320    });
321  
322    describe('no_proxy=127.0.0.1/32 (CIDR is NOT supported)', function() {
323      var env = {};
324      env.HTTP_PROXY = 'http://proxy';
325  
326      env.NO_PROXY = '127.0.0.1/32';
327      testProxyUrl(env, 'http://proxy', 'http://127.0.0.1');
328      testProxyUrl(env, 'http://proxy', 'http://127.0.0.1/32');
329    });
330  
331    describe('no_proxy=127.0.0.1 does NOT match localhost', function() {
332      var env = {};
333      env.HTTP_PROXY = 'http://proxy';
334  
335      env.NO_PROXY = '127.0.0.1';
336      testProxyUrl(env, '', 'http://127.0.0.1');
337      // We're not performing DNS queries, so this shouldn't match.
338      testProxyUrl(env, 'http://proxy', 'http://localhost');
339    });
340  
341    describe('no_proxy with protocols that have a default port', function() {
342      var env = {};
343      env.WS_PROXY = 'http://ws';
344      env.WSS_PROXY = 'http://wss';
345      env.HTTP_PROXY = 'http://http';
346      env.HTTPS_PROXY = 'http://https';
347      env.GOPHER_PROXY = 'http://gopher';
348      env.FTP_PROXY = 'http://ftp';
349      env.ALL_PROXY = 'http://all';
350  
351      env.NO_PROXY = 'xxx:21,xxx:70,xxx:80,xxx:443';
352  
353      testProxyUrl(env, '', 'http://xxx');
354      testProxyUrl(env, '', 'http://xxx:80');
355      testProxyUrl(env, 'http://http', 'http://xxx:1337');
356  
357      testProxyUrl(env, '', 'ws://xxx');
358      testProxyUrl(env, '', 'ws://xxx:80');
359      testProxyUrl(env, 'http://ws', 'ws://xxx:1337');
360  
361      testProxyUrl(env, '', 'https://xxx');
362      testProxyUrl(env, '', 'https://xxx:443');
363      testProxyUrl(env, 'http://https', 'https://xxx:1337');
364  
365      testProxyUrl(env, '', 'wss://xxx');
366      testProxyUrl(env, '', 'wss://xxx:443');
367      testProxyUrl(env, 'http://wss', 'wss://xxx:1337');
368  
369      testProxyUrl(env, '', 'gopher://xxx');
370      testProxyUrl(env, '', 'gopher://xxx:70');
371      testProxyUrl(env, 'http://gopher', 'gopher://xxx:1337');
372  
373      testProxyUrl(env, '', 'ftp://xxx');
374      testProxyUrl(env, '', 'ftp://xxx:21');
375      testProxyUrl(env, 'http://ftp', 'ftp://xxx:1337');
376    });
377  
378    describe('no_proxy should not be case-sensitive', function() {
379      var env = {};
380      env.HTTP_PROXY = 'http://proxy';
381      env.NO_PROXY = 'XXX,YYY,ZzZ';
382  
383      testProxyUrl(env, '', 'http://xxx');
384      testProxyUrl(env, '', 'http://XXX');
385      testProxyUrl(env, '', 'http://yyy');
386      testProxyUrl(env, '', 'http://YYY');
387      testProxyUrl(env, '', 'http://ZzZ');
388      testProxyUrl(env, '', 'http://zZz');
389    });
390  
391    describe('NPM proxy configuration', function() {
392      describe('npm_config_http_proxy should work', function() {
393        var env = {};
394        // eslint-disable-next-line camelcase
395        env.npm_config_http_proxy = 'http://http-proxy';
396  
397        testProxyUrl(env, '', 'https://example');
398        testProxyUrl(env, 'http://http-proxy', 'http://example');
399  
400        // eslint-disable-next-line camelcase
401        env.npm_config_http_proxy = 'http://priority';
402        testProxyUrl(env, 'http://priority', 'http://example');
403      });
404      // eslint-disable-next-line max-len
405      describe('npm_config_http_proxy should take precedence over HTTP_PROXY and npm_config_proxy', function() {
406        var env = {};
407        // eslint-disable-next-line camelcase
408        env.npm_config_http_proxy = 'http://http-proxy';
409        // eslint-disable-next-line camelcase
410        env.npm_config_proxy = 'http://unexpected-proxy';
411        env.HTTP_PROXY = 'http://unexpected-proxy';
412  
413        testProxyUrl(env, 'http://http-proxy', 'http://example');
414      });
415      describe('npm_config_https_proxy should work', function() {
416        var env = {};
417        // eslint-disable-next-line camelcase
418        env.npm_config_http_proxy = 'http://unexpected.proxy';
419        testProxyUrl(env, '', 'https://example');
420  
421        // eslint-disable-next-line camelcase
422        env.npm_config_https_proxy = 'http://https-proxy';
423        testProxyUrl(env, 'http://https-proxy', 'https://example');
424  
425        // eslint-disable-next-line camelcase
426        env.npm_config_https_proxy = 'http://priority';
427        testProxyUrl(env, 'http://priority', 'https://example');
428      });
429      // eslint-disable-next-line max-len
430      describe('npm_config_https_proxy should take precedence over HTTPS_PROXY and npm_config_proxy', function() {
431        var env = {};
432        // eslint-disable-next-line camelcase
433        env.npm_config_https_proxy = 'http://https-proxy';
434        // eslint-disable-next-line camelcase
435        env.npm_config_proxy = 'http://unexpected-proxy';
436        env.HTTPS_PROXY = 'http://unexpected-proxy';
437  
438        testProxyUrl(env, 'http://https-proxy', 'https://example');
439      });
440      describe('npm_config_proxy should work', function() {
441        var env = {};
442        // eslint-disable-next-line camelcase
443        env.npm_config_proxy = 'http://http-proxy';
444        testProxyUrl(env, 'http://http-proxy', 'http://example');
445        testProxyUrl(env, 'http://http-proxy', 'https://example');
446  
447        // eslint-disable-next-line camelcase
448        env.npm_config_proxy = 'http://priority';
449        testProxyUrl(env, 'http://priority', 'http://example');
450        testProxyUrl(env, 'http://priority', 'https://example');
451      });
452      // eslint-disable-next-line max-len
453      describe('HTTP_PROXY and HTTPS_PROXY should take precedence over npm_config_proxy', function() {
454        var env = {};
455        env.HTTP_PROXY = 'http://http-proxy';
456        env.HTTPS_PROXY = 'http://https-proxy';
457        // eslint-disable-next-line camelcase
458        env.npm_config_proxy = 'http://unexpected-proxy';
459        testProxyUrl(env, 'http://http-proxy', 'http://example');
460        testProxyUrl(env, 'http://https-proxy', 'https://example');
461      });
462      describe('npm_config_no_proxy should work', function() {
463        var env = {};
464        env.HTTP_PROXY = 'http://proxy';
465        // eslint-disable-next-line camelcase
466        env.npm_config_no_proxy = 'example';
467  
468        testProxyUrl(env, '', 'http://example');
469        testProxyUrl(env, 'http://proxy', 'http://otherwebsite');
470      });
471      // eslint-disable-next-line max-len
472      describe('npm_config_no_proxy should take precedence over NO_PROXY', function() {
473        var env = {};
474        env.HTTP_PROXY = 'http://proxy';
475        env.NO_PROXY = 'otherwebsite';
476        // eslint-disable-next-line camelcase
477        env.npm_config_no_proxy = 'example';
478  
479        testProxyUrl(env, '', 'http://example');
480        testProxyUrl(env, 'http://proxy', 'http://otherwebsite');
481      });
482    });
483  });