follow-redirects 1.15.3 → 1.15.4

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