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.
- package/index.js +113 -75
- 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
|
-
|
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
|
-
|
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
|
-
|
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 =
|
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
|
-
|
452
|
+
var redirectUrl = resolveUrl(location, currentUrl);
|
453
|
+
debug("redirecting to", redirectUrl.href);
|
435
454
|
this._isRedirect = true;
|
436
|
-
|
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 (
|
442
|
-
|
443
|
-
|
444
|
-
!isSubdomain(
|
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
|
-
|
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
|
-
|
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 (
|
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;
|
502
|
+
// Parse parameters, ensuring that input is an object
|
503
|
+
if (isURL(input)) {
|
504
|
+
input = spreadUrlObject(input);
|
509
505
|
}
|
510
|
-
else if (
|
511
|
-
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
|
-
|
558
|
-
|
559
|
-
|
560
|
-
|
561
|
-
|
562
|
-
|
563
|
-
|
564
|
-
|
565
|
-
|
566
|
-
|
567
|
-
|
568
|
-
|
569
|
-
|
570
|
-
|
571
|
-
|
572
|
-
|
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
|
-
|
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
|
601
|
-
|
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;
|