follow-redirects 1.15.2 → 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 +129 -79
- package/package.json +2 -3
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",
|
@@ -38,6 +62,9 @@ var WriteAfterEndError = createErrorType(
|
|
38
62
|
"write after end"
|
39
63
|
);
|
40
64
|
|
65
|
+
// istanbul ignore next
|
66
|
+
var destroy = Writable.prototype.destroy || noop;
|
67
|
+
|
41
68
|
// An HTTP(S) request that can be redirected
|
42
69
|
function RedirectableRequest(options, responseCallback) {
|
43
70
|
// Initialize the request
|
@@ -59,7 +86,13 @@ function RedirectableRequest(options, responseCallback) {
|
|
59
86
|
// React to responses of native requests
|
60
87
|
var self = this;
|
61
88
|
this._onNativeResponse = function (response) {
|
62
|
-
|
89
|
+
try {
|
90
|
+
self._processResponse(response);
|
91
|
+
}
|
92
|
+
catch (cause) {
|
93
|
+
self.emit("error", cause instanceof RedirectionError ?
|
94
|
+
cause : new RedirectionError({ cause: cause }));
|
95
|
+
}
|
63
96
|
};
|
64
97
|
|
65
98
|
// Perform the first request
|
@@ -68,10 +101,17 @@ function RedirectableRequest(options, responseCallback) {
|
|
68
101
|
RedirectableRequest.prototype = Object.create(Writable.prototype);
|
69
102
|
|
70
103
|
RedirectableRequest.prototype.abort = function () {
|
71
|
-
|
104
|
+
destroyRequest(this._currentRequest);
|
105
|
+
this._currentRequest.abort();
|
72
106
|
this.emit("abort");
|
73
107
|
};
|
74
108
|
|
109
|
+
RedirectableRequest.prototype.destroy = function (error) {
|
110
|
+
destroyRequest(this._currentRequest, error);
|
111
|
+
destroy.call(this, error);
|
112
|
+
return this;
|
113
|
+
};
|
114
|
+
|
75
115
|
// Writes buffered data to the current native request
|
76
116
|
RedirectableRequest.prototype.write = function (data, encoding, callback) {
|
77
117
|
// Writing is not allowed if end has been called
|
@@ -184,6 +224,7 @@ RedirectableRequest.prototype.setTimeout = function (msecs, callback) {
|
|
184
224
|
self.removeListener("abort", clearTimer);
|
185
225
|
self.removeListener("error", clearTimer);
|
186
226
|
self.removeListener("response", clearTimer);
|
227
|
+
self.removeListener("close", clearTimer);
|
187
228
|
if (callback) {
|
188
229
|
self.removeListener("timeout", callback);
|
189
230
|
}
|
@@ -210,6 +251,7 @@ RedirectableRequest.prototype.setTimeout = function (msecs, callback) {
|
|
210
251
|
this.on("abort", clearTimer);
|
211
252
|
this.on("error", clearTimer);
|
212
253
|
this.on("response", clearTimer);
|
254
|
+
this.on("close", clearTimer);
|
213
255
|
|
214
256
|
return this;
|
215
257
|
};
|
@@ -268,8 +310,7 @@ RedirectableRequest.prototype._performRequest = function () {
|
|
268
310
|
var protocol = this._options.protocol;
|
269
311
|
var nativeProtocol = this._options.nativeProtocols[protocol];
|
270
312
|
if (!nativeProtocol) {
|
271
|
-
|
272
|
-
return;
|
313
|
+
throw new TypeError("Unsupported protocol " + protocol);
|
273
314
|
}
|
274
315
|
|
275
316
|
// If specified, use the agent corresponding to the protocol
|
@@ -361,15 +402,14 @@ RedirectableRequest.prototype._processResponse = function (response) {
|
|
361
402
|
}
|
362
403
|
|
363
404
|
// The response is a redirect, so abort the current request
|
364
|
-
|
405
|
+
destroyRequest(this._currentRequest);
|
365
406
|
// Discard the remainder of the response to avoid waiting for data
|
366
407
|
response.destroy();
|
367
408
|
|
368
409
|
// RFC7231§6.4: A client SHOULD detect and intervene
|
369
410
|
// in cyclical redirections (i.e., "infinite" redirection loops).
|
370
411
|
if (++this._redirectCount > this._options.maxRedirects) {
|
371
|
-
|
372
|
-
return;
|
412
|
+
throw new TooManyRedirectsError();
|
373
413
|
}
|
374
414
|
|
375
415
|
// Store the request headers if applicable
|
@@ -403,33 +443,23 @@ RedirectableRequest.prototype._processResponse = function (response) {
|
|
403
443
|
var currentHostHeader = removeMatchingHeaders(/^host$/i, this._options.headers);
|
404
444
|
|
405
445
|
// If the redirect is relative, carry over the host of the last request
|
406
|
-
var currentUrlParts =
|
446
|
+
var currentUrlParts = parseUrl(this._currentUrl);
|
407
447
|
var currentHost = currentHostHeader || currentUrlParts.host;
|
408
448
|
var currentUrl = /^\w+:/.test(location) ? this._currentUrl :
|
409
449
|
url.format(Object.assign(currentUrlParts, { host: currentHost }));
|
410
450
|
|
411
|
-
// Determine the URL of the redirection
|
412
|
-
var redirectUrl;
|
413
|
-
try {
|
414
|
-
redirectUrl = url.resolve(currentUrl, location);
|
415
|
-
}
|
416
|
-
catch (cause) {
|
417
|
-
this.emit("error", new RedirectionError({ cause: cause }));
|
418
|
-
return;
|
419
|
-
}
|
420
|
-
|
421
451
|
// Create the redirected request
|
422
|
-
|
452
|
+
var redirectUrl = resolveUrl(location, currentUrl);
|
453
|
+
debug("redirecting to", redirectUrl.href);
|
423
454
|
this._isRedirect = true;
|
424
|
-
|
425
|
-
Object.assign(this._options, redirectUrlParts);
|
455
|
+
spreadUrlObject(redirectUrl, this._options);
|
426
456
|
|
427
457
|
// Drop confidential headers when redirecting to a less secure protocol
|
428
458
|
// or to a different domain that is not a superdomain
|
429
|
-
if (
|
430
|
-
|
431
|
-
|
432
|
-
!isSubdomain(
|
459
|
+
if (redirectUrl.protocol !== currentUrlParts.protocol &&
|
460
|
+
redirectUrl.protocol !== "https:" ||
|
461
|
+
redirectUrl.host !== currentHost &&
|
462
|
+
!isSubdomain(redirectUrl.host, currentHost)) {
|
433
463
|
removeMatchingHeaders(/^(?:authorization|cookie)$/i, this._options.headers);
|
434
464
|
}
|
435
465
|
|
@@ -444,23 +474,12 @@ RedirectableRequest.prototype._processResponse = function (response) {
|
|
444
474
|
method: method,
|
445
475
|
headers: requestHeaders,
|
446
476
|
};
|
447
|
-
|
448
|
-
beforeRedirect(this._options, responseDetails, requestDetails);
|
449
|
-
}
|
450
|
-
catch (err) {
|
451
|
-
this.emit("error", err);
|
452
|
-
return;
|
453
|
-
}
|
477
|
+
beforeRedirect(this._options, responseDetails, requestDetails);
|
454
478
|
this._sanitizeOptions(this._options);
|
455
479
|
}
|
456
480
|
|
457
481
|
// Perform the redirected request
|
458
|
-
|
459
|
-
this._performRequest();
|
460
|
-
}
|
461
|
-
catch (cause) {
|
462
|
-
this.emit("error", new RedirectionError({ cause: cause }));
|
463
|
-
}
|
482
|
+
this._performRequest();
|
464
483
|
};
|
465
484
|
|
466
485
|
// Wraps the key/value object of protocols with redirect functionality
|
@@ -480,27 +499,16 @@ function wrap(protocols) {
|
|
480
499
|
|
481
500
|
// Executes a request, following redirects
|
482
501
|
function request(input, options, callback) {
|
483
|
-
// Parse parameters
|
484
|
-
if (
|
485
|
-
|
486
|
-
try {
|
487
|
-
parsed = urlToOptions(new URL(input));
|
488
|
-
}
|
489
|
-
catch (err) {
|
490
|
-
/* istanbul ignore next */
|
491
|
-
parsed = url.parse(input);
|
492
|
-
}
|
493
|
-
if (!isString(parsed.protocol)) {
|
494
|
-
throw new InvalidUrlError({ input });
|
495
|
-
}
|
496
|
-
input = parsed;
|
502
|
+
// Parse parameters, ensuring that input is an object
|
503
|
+
if (isURL(input)) {
|
504
|
+
input = spreadUrlObject(input);
|
497
505
|
}
|
498
|
-
else if (
|
499
|
-
input =
|
506
|
+
else if (isString(input)) {
|
507
|
+
input = spreadUrlObject(parseUrl(input));
|
500
508
|
}
|
501
509
|
else {
|
502
510
|
callback = options;
|
503
|
-
options = input;
|
511
|
+
options = validateUrl(input);
|
504
512
|
input = { protocol: protocol };
|
505
513
|
}
|
506
514
|
if (isFunction(options)) {
|
@@ -539,27 +547,57 @@ function wrap(protocols) {
|
|
539
547
|
return exports;
|
540
548
|
}
|
541
549
|
|
542
|
-
/* istanbul ignore next */
|
543
550
|
function noop() { /* empty */ }
|
544
551
|
|
545
|
-
|
546
|
-
|
547
|
-
|
548
|
-
|
549
|
-
|
550
|
-
|
551
|
-
|
552
|
-
|
553
|
-
|
554
|
-
|
555
|
-
|
556
|
-
|
557
|
-
|
558
|
-
|
559
|
-
|
560
|
-
|
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 });
|
576
|
+
}
|
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);
|
561
596
|
}
|
562
|
-
|
597
|
+
// Concatenate path
|
598
|
+
spread.path = spread.search ? spread.pathname + spread.search : spread.pathname;
|
599
|
+
|
600
|
+
return spread;
|
563
601
|
}
|
564
602
|
|
565
603
|
function removeMatchingHeaders(regex, headers) {
|
@@ -585,17 +623,25 @@ function createErrorType(code, message, baseClass) {
|
|
585
623
|
|
586
624
|
// Attach constructor and set default properties
|
587
625
|
CustomError.prototype = new (baseClass || Error)();
|
588
|
-
CustomError.prototype
|
589
|
-
|
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
|
+
});
|
590
636
|
return CustomError;
|
591
637
|
}
|
592
638
|
|
593
|
-
function
|
639
|
+
function destroyRequest(request, error) {
|
594
640
|
for (var event of events) {
|
595
641
|
request.removeListener(event, eventHandlers[event]);
|
596
642
|
}
|
597
643
|
request.on("error", noop);
|
598
|
-
request.
|
644
|
+
request.destroy(error);
|
599
645
|
}
|
600
646
|
|
601
647
|
function isSubdomain(subdomain, domain) {
|
@@ -616,6 +662,10 @@ function isBuffer(value) {
|
|
616
662
|
return typeof value === "object" && ("length" in value);
|
617
663
|
}
|
618
664
|
|
665
|
+
function isURL(value) {
|
666
|
+
return URL && value instanceof URL;
|
667
|
+
}
|
668
|
+
|
619
669
|
// Exports
|
620
670
|
module.exports = wrap({ http: http, https: https });
|
621
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
|
+
"version": "1.15.4",
|
4
4
|
"description": "HTTP and HTTPS modules that follow redirects.",
|
5
5
|
"license": "MIT",
|
6
6
|
"main": "index.js",
|
@@ -11,9 +11,8 @@
|
|
11
11
|
"node": ">=4.0"
|
12
12
|
},
|
13
13
|
"scripts": {
|
14
|
-
"test": "npm run lint && npm run mocha",
|
15
14
|
"lint": "eslint *.js test",
|
16
|
-
"
|
15
|
+
"test": "nyc mocha"
|
17
16
|
},
|
18
17
|
"repository": {
|
19
18
|
"type": "git",
|