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.
- package/index.js +114 -75
- 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
|
-
|
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
|
-
|
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
|
-
|
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 =
|
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
|
-
|
453
|
+
var redirectUrl = resolveUrl(location, currentUrl);
|
454
|
+
debug("redirecting to", redirectUrl.href);
|
435
455
|
this._isRedirect = true;
|
436
|
-
|
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 (
|
442
|
-
|
443
|
-
|
444
|
-
!isSubdomain(
|
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
|
-
|
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
|
-
|
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 (
|
497
|
-
|
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 (
|
511
|
-
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
|
-
|
558
|
-
|
559
|
-
|
560
|
-
|
561
|
-
|
562
|
-
|
563
|
-
|
564
|
-
|
565
|
-
|
566
|
-
|
567
|
-
|
568
|
-
|
569
|
-
|
570
|
-
|
571
|
-
|
572
|
-
|
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
|
-
|
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
|
601
|
-
|
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;
|