follow-redirects 1.15.3 → 1.15.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of follow-redirects might be problematic. Click here for more details.

Files changed (2) hide show
  1. package/index.js +114 -75
  2. package/package.json +1 -1
package/index.js CHANGED
@@ -6,6 +6,30 @@ var Writable = require("stream").Writable;
6
6
  var assert = require("assert");
7
7
  var debug = require("./debug");
8
8
 
9
+ // Whether to use the native URL object or the legacy url module
10
+ var useNativeURL = false;
11
+ try {
12
+ assert(new URL());
13
+ }
14
+ catch (error) {
15
+ useNativeURL = error.code === "ERR_INVALID_URL";
16
+ }
17
+
18
+ // URL fields to preserve in copy operations
19
+ var preservedUrlFields = [
20
+ "auth",
21
+ "host",
22
+ "hostname",
23
+ "href",
24
+ "path",
25
+ "pathname",
26
+ "port",
27
+ "protocol",
28
+ "query",
29
+ "search",
30
+ "hash",
31
+ ];
32
+
9
33
  // Create handlers that pass events from native requests
10
34
  var events = ["abort", "aborted", "connect", "error", "socket", "timeout"];
11
35
  var eventHandlers = Object.create(null);
@@ -15,19 +39,20 @@ events.forEach(function (event) {
15
39
  };
16
40
  });
17
41
 
42
+ // Error types with codes
18
43
  var InvalidUrlError = createErrorType(
19
44
  "ERR_INVALID_URL",
20
45
  "Invalid URL",
21
46
  TypeError
22
47
  );
23
- // Error types with codes
24
48
  var RedirectionError = createErrorType(
25
49
  "ERR_FR_REDIRECTION_FAILURE",
26
50
  "Redirected request failed"
27
51
  );
28
52
  var TooManyRedirectsError = createErrorType(
29
53
  "ERR_FR_TOO_MANY_REDIRECTS",
30
- "Maximum number of redirects exceeded"
54
+ "Maximum number of redirects exceeded",
55
+ RedirectionError
31
56
  );
32
57
  var MaxBodyLengthExceededError = createErrorType(
33
58
  "ERR_FR_MAX_BODY_LENGTH_EXCEEDED",
@@ -62,7 +87,13 @@ function RedirectableRequest(options, responseCallback) {
62
87
  // React to responses of native requests
63
88
  var self = this;
64
89
  this._onNativeResponse = function (response) {
65
- self._processResponse(response);
90
+ try {
91
+ self._processResponse(response);
92
+ }
93
+ catch (cause) {
94
+ self.emit("error", cause instanceof RedirectionError ?
95
+ cause : new RedirectionError({ cause: cause }));
96
+ }
66
97
  };
67
98
 
68
99
  // Perform the first request
@@ -280,8 +311,7 @@ RedirectableRequest.prototype._performRequest = function () {
280
311
  var protocol = this._options.protocol;
281
312
  var nativeProtocol = this._options.nativeProtocols[protocol];
282
313
  if (!nativeProtocol) {
283
- this.emit("error", new TypeError("Unsupported protocol " + protocol));
284
- return;
314
+ throw new TypeError("Unsupported protocol " + protocol);
285
315
  }
286
316
 
287
317
  // If specified, use the agent corresponding to the protocol
@@ -380,8 +410,7 @@ RedirectableRequest.prototype._processResponse = function (response) {
380
410
  // RFC7231§6.4: A client SHOULD detect and intervene
381
411
  // in cyclical redirections (i.e., "infinite" redirection loops).
382
412
  if (++this._redirectCount > this._options.maxRedirects) {
383
- this.emit("error", new TooManyRedirectsError());
384
- return;
413
+ throw new TooManyRedirectsError();
385
414
  }
386
415
 
387
416
  // Store the request headers if applicable
@@ -415,33 +444,23 @@ RedirectableRequest.prototype._processResponse = function (response) {
415
444
  var currentHostHeader = removeMatchingHeaders(/^host$/i, this._options.headers);
416
445
 
417
446
  // If the redirect is relative, carry over the host of the last request
418
- var currentUrlParts = url.parse(this._currentUrl);
447
+ var currentUrlParts = parseUrl(this._currentUrl);
419
448
  var currentHost = currentHostHeader || currentUrlParts.host;
420
449
  var currentUrl = /^\w+:/.test(location) ? this._currentUrl :
421
450
  url.format(Object.assign(currentUrlParts, { host: currentHost }));
422
451
 
423
- // Determine the URL of the redirection
424
- var redirectUrl;
425
- try {
426
- redirectUrl = url.resolve(currentUrl, location);
427
- }
428
- catch (cause) {
429
- this.emit("error", new RedirectionError({ cause: cause }));
430
- return;
431
- }
432
-
433
452
  // Create the redirected request
434
- debug("redirecting to", redirectUrl);
453
+ var redirectUrl = resolveUrl(location, currentUrl);
454
+ debug("redirecting to", redirectUrl.href);
435
455
  this._isRedirect = true;
436
- var redirectUrlParts = url.parse(redirectUrl);
437
- Object.assign(this._options, redirectUrlParts);
456
+ spreadUrlObject(redirectUrl, this._options);
438
457
 
439
458
  // Drop confidential headers when redirecting to a less secure protocol
440
459
  // or to a different domain that is not a superdomain
441
- if (redirectUrlParts.protocol !== currentUrlParts.protocol &&
442
- redirectUrlParts.protocol !== "https:" ||
443
- redirectUrlParts.host !== currentHost &&
444
- !isSubdomain(redirectUrlParts.host, currentHost)) {
460
+ if (redirectUrl.protocol !== currentUrlParts.protocol &&
461
+ redirectUrl.protocol !== "https:" ||
462
+ redirectUrl.host !== currentHost &&
463
+ !isSubdomain(redirectUrl.host, currentHost)) {
445
464
  removeMatchingHeaders(/^(?:authorization|cookie)$/i, this._options.headers);
446
465
  }
447
466
 
@@ -456,23 +475,12 @@ RedirectableRequest.prototype._processResponse = function (response) {
456
475
  method: method,
457
476
  headers: requestHeaders,
458
477
  };
459
- try {
460
- beforeRedirect(this._options, responseDetails, requestDetails);
461
- }
462
- catch (err) {
463
- this.emit("error", err);
464
- return;
465
- }
478
+ beforeRedirect(this._options, responseDetails, requestDetails);
466
479
  this._sanitizeOptions(this._options);
467
480
  }
468
481
 
469
482
  // Perform the redirected request
470
- try {
471
- this._performRequest();
472
- }
473
- catch (cause) {
474
- this.emit("error", new RedirectionError({ cause: cause }));
475
- }
483
+ this._performRequest();
476
484
  };
477
485
 
478
486
  // Wraps the key/value object of protocols with redirect functionality
@@ -492,27 +500,16 @@ function wrap(protocols) {
492
500
 
493
501
  // Executes a request, following redirects
494
502
  function request(input, options, callback) {
495
- // Parse parameters
496
- if (isString(input)) {
497
- var parsed;
498
- try {
499
- parsed = urlToOptions(new URL(input));
500
- }
501
- catch (err) {
502
- /* istanbul ignore next */
503
- parsed = url.parse(input);
504
- }
505
- if (!isString(parsed.protocol)) {
506
- throw new InvalidUrlError({ input });
507
- }
508
- input = parsed;
503
+ // Parse parameters, ensuring that input is an object
504
+ if (isURL(input)) {
505
+ input = spreadUrlObject(input);
509
506
  }
510
- else if (URL && (input instanceof URL)) {
511
- input = urlToOptions(input);
507
+ else if (isString(input)) {
508
+ input = spreadUrlObject(parseUrl(input));
512
509
  }
513
510
  else {
514
511
  callback = options;
515
- options = input;
512
+ options = validateUrl(input);
516
513
  input = { protocol: protocol };
517
514
  }
518
515
  if (isFunction(options)) {
@@ -551,27 +548,57 @@ function wrap(protocols) {
551
548
  return exports;
552
549
  }
553
550
 
554
- /* istanbul ignore next */
555
551
  function noop() { /* empty */ }
556
552
 
557
- // from https://github.com/nodejs/node/blob/master/lib/internal/url.js
558
- function urlToOptions(urlObject) {
559
- var options = {
560
- protocol: urlObject.protocol,
561
- hostname: urlObject.hostname.startsWith("[") ?
562
- /* istanbul ignore next */
563
- urlObject.hostname.slice(1, -1) :
564
- urlObject.hostname,
565
- hash: urlObject.hash,
566
- search: urlObject.search,
567
- pathname: urlObject.pathname,
568
- path: urlObject.pathname + urlObject.search,
569
- href: urlObject.href,
570
- };
571
- if (urlObject.port !== "") {
572
- options.port = Number(urlObject.port);
553
+ function parseUrl(input) {
554
+ var parsed;
555
+ /* istanbul ignore else */
556
+ if (useNativeURL) {
557
+ parsed = new URL(input);
558
+ }
559
+ else {
560
+ // Ensure the URL is valid and absolute
561
+ parsed = validateUrl(url.parse(input));
562
+ if (!isString(parsed.protocol)) {
563
+ throw new InvalidUrlError({ input });
564
+ }
565
+ }
566
+ return parsed;
567
+ }
568
+
569
+ function resolveUrl(relative, base) {
570
+ /* istanbul ignore next */
571
+ return useNativeURL ? new URL(relative, base) : parseUrl(url.resolve(base, relative));
572
+ }
573
+
574
+ function validateUrl(input) {
575
+ if (/^\[/.test(input.hostname) && !/^\[[:0-9a-f]+\]$/i.test(input.hostname)) {
576
+ throw new InvalidUrlError({ input: input.href || input });
573
577
  }
574
- return options;
578
+ if (/^\[/.test(input.host) && !/^\[[:0-9a-f]+\](:\d+)?$/i.test(input.host)) {
579
+ throw new InvalidUrlError({ input: input.href || input });
580
+ }
581
+ return input;
582
+ }
583
+
584
+ function spreadUrlObject(urlObject, target) {
585
+ var spread = target || {};
586
+ for (var key of preservedUrlFields) {
587
+ spread[key] = urlObject[key];
588
+ }
589
+
590
+ // Fix IPv6 hostname
591
+ if (spread.hostname.startsWith("[")) {
592
+ spread.hostname = spread.hostname.slice(1, -1);
593
+ }
594
+ // Ensure port is a number
595
+ if (spread.port !== "") {
596
+ spread.port = Number(spread.port);
597
+ }
598
+ // Concatenate path
599
+ spread.path = spread.search ? spread.pathname + spread.search : spread.pathname;
600
+
601
+ return spread;
575
602
  }
576
603
 
577
604
  function removeMatchingHeaders(regex, headers) {
@@ -597,8 +624,16 @@ function createErrorType(code, message, baseClass) {
597
624
 
598
625
  // Attach constructor and set default properties
599
626
  CustomError.prototype = new (baseClass || Error)();
600
- CustomError.prototype.constructor = CustomError;
601
- CustomError.prototype.name = "Error [" + code + "]";
627
+ Object.defineProperties(CustomError.prototype, {
628
+ constructor: {
629
+ value: CustomError,
630
+ enumerable: false,
631
+ },
632
+ name: {
633
+ value: "Error [" + code + "]",
634
+ enumerable: false,
635
+ },
636
+ });
602
637
  return CustomError;
603
638
  }
604
639
 
@@ -628,6 +663,10 @@ function isBuffer(value) {
628
663
  return typeof value === "object" && ("length" in value);
629
664
  }
630
665
 
666
+ function isURL(value) {
667
+ return URL && value instanceof URL;
668
+ }
669
+
631
670
  // Exports
632
671
  module.exports = wrap({ http: http, https: https });
633
672
  module.exports.wrap = wrap;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "follow-redirects",
3
- "version": "1.15.3",
3
+ "version": "1.15.5",
4
4
  "description": "HTTP and HTTPS modules that follow redirects.",
5
5
  "license": "MIT",
6
6
  "main": "index.js",