follow-redirects 1.14.5 → 1.14.9
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 +85 -79
- package/package.json +1 -1
package/index.js
CHANGED
@@ -336,96 +336,101 @@ RedirectableRequest.prototype._processResponse = function (response) {
|
|
336
336
|
// the user agent MAY automatically redirect its request to the URI
|
337
337
|
// referenced by the Location field value,
|
338
338
|
// even if the specific status code is not understood.
|
339
|
+
|
340
|
+
// If the response is not a redirect; return it as-is
|
339
341
|
var location = response.headers.location;
|
340
|
-
if (location
|
341
|
-
statusCode
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
response.destroy();
|
346
|
-
|
347
|
-
// RFC7231§6.4: A client SHOULD detect and intervene
|
348
|
-
// in cyclical redirections (i.e., "infinite" redirection loops).
|
349
|
-
if (++this._redirectCount > this._options.maxRedirects) {
|
350
|
-
this.emit("error", new TooManyRedirectsError());
|
351
|
-
return;
|
352
|
-
}
|
342
|
+
if (!location || this._options.followRedirects === false ||
|
343
|
+
statusCode < 300 || statusCode >= 400) {
|
344
|
+
response.responseUrl = this._currentUrl;
|
345
|
+
response.redirects = this._redirects;
|
346
|
+
this.emit("response", response);
|
353
347
|
|
354
|
-
//
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
if ((statusCode === 301 || statusCode === 302) && this._options.method === "POST" ||
|
359
|
-
// RFC7231§6.4.4: The 303 (See Other) status code indicates that
|
360
|
-
// the server is redirecting the user agent to a different resource […]
|
361
|
-
// A user agent can perform a retrieval request targeting that URI
|
362
|
-
// (a GET or HEAD request if using HTTP) […]
|
363
|
-
(statusCode === 303) && !/^(?:GET|HEAD)$/.test(this._options.method)) {
|
364
|
-
this._options.method = "GET";
|
365
|
-
// Drop a possible entity and headers related to it
|
366
|
-
this._requestBodyBuffers = [];
|
367
|
-
removeMatchingHeaders(/^content-/i, this._options.headers);
|
368
|
-
}
|
348
|
+
// Clean up
|
349
|
+
this._requestBodyBuffers = [];
|
350
|
+
return;
|
351
|
+
}
|
369
352
|
|
370
|
-
|
371
|
-
|
353
|
+
// The response is a redirect, so abort the current request
|
354
|
+
abortRequest(this._currentRequest);
|
355
|
+
// Discard the remainder of the response to avoid waiting for data
|
356
|
+
response.destroy();
|
372
357
|
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
358
|
+
// RFC7231§6.4: A client SHOULD detect and intervene
|
359
|
+
// in cyclical redirections (i.e., "infinite" redirection loops).
|
360
|
+
if (++this._redirectCount > this._options.maxRedirects) {
|
361
|
+
this.emit("error", new TooManyRedirectsError());
|
362
|
+
return;
|
363
|
+
}
|
378
364
|
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
365
|
+
// RFC7231§6.4: Automatic redirection needs to done with
|
366
|
+
// care for methods not known to be safe, […]
|
367
|
+
// RFC7231§6.4.2–3: For historical reasons, a user agent MAY change
|
368
|
+
// the request method from POST to GET for the subsequent request.
|
369
|
+
if ((statusCode === 301 || statusCode === 302) && this._options.method === "POST" ||
|
370
|
+
// RFC7231§6.4.4: The 303 (See Other) status code indicates that
|
371
|
+
// the server is redirecting the user agent to a different resource […]
|
372
|
+
// A user agent can perform a retrieval request targeting that URI
|
373
|
+
// (a GET or HEAD request if using HTTP) […]
|
374
|
+
(statusCode === 303) && !/^(?:GET|HEAD)$/.test(this._options.method)) {
|
375
|
+
this._options.method = "GET";
|
376
|
+
// Drop a possible entity and headers related to it
|
377
|
+
this._requestBodyBuffers = [];
|
378
|
+
removeMatchingHeaders(/^content-/i, this._options.headers);
|
379
|
+
}
|
388
380
|
|
389
|
-
|
390
|
-
|
391
|
-
this._isRedirect = true;
|
392
|
-
var redirectUrlParts = url.parse(redirectUrl);
|
393
|
-
Object.assign(this._options, redirectUrlParts);
|
381
|
+
// Drop the Host header, as the redirect might lead to a different host
|
382
|
+
var currentHostHeader = removeMatchingHeaders(/^host$/i, this._options.headers);
|
394
383
|
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
384
|
+
// If the redirect is relative, carry over the host of the last request
|
385
|
+
var currentUrlParts = url.parse(this._currentUrl);
|
386
|
+
var currentHost = currentHostHeader || currentUrlParts.host;
|
387
|
+
var currentUrl = /^\w+:/.test(location) ? this._currentUrl :
|
388
|
+
url.format(Object.assign(currentUrlParts, { host: currentHost }));
|
399
389
|
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
390
|
+
// Determine the URL of the redirection
|
391
|
+
var redirectUrl;
|
392
|
+
try {
|
393
|
+
redirectUrl = url.resolve(currentUrl, location);
|
394
|
+
}
|
395
|
+
catch (cause) {
|
396
|
+
this.emit("error", new RedirectionError(cause));
|
397
|
+
return;
|
398
|
+
}
|
399
|
+
|
400
|
+
// Create the redirected request
|
401
|
+
debug("redirecting to", redirectUrl);
|
402
|
+
this._isRedirect = true;
|
403
|
+
var redirectUrlParts = url.parse(redirectUrl);
|
404
|
+
Object.assign(this._options, redirectUrlParts);
|
405
|
+
|
406
|
+
// Drop confidential headers when redirecting to a less secure protocol
|
407
|
+
// or to a different domain that is not a superdomain
|
408
|
+
if (redirectUrlParts.protocol !== currentUrlParts.protocol &&
|
409
|
+
redirectUrlParts.protocol !== "https:" ||
|
410
|
+
redirectUrlParts.host !== currentHost &&
|
411
|
+
!isSubdomain(redirectUrlParts.host, currentHost)) {
|
412
|
+
removeMatchingHeaders(/^(?:authorization|cookie)$/i, this._options.headers);
|
413
|
+
}
|
412
414
|
|
413
|
-
|
415
|
+
// Evaluate the beforeRedirect callback
|
416
|
+
if (typeof this._options.beforeRedirect === "function") {
|
417
|
+
var responseDetails = { headers: response.headers };
|
414
418
|
try {
|
415
|
-
this.
|
419
|
+
this._options.beforeRedirect.call(null, this._options, responseDetails);
|
416
420
|
}
|
417
|
-
catch (
|
418
|
-
this.emit("error",
|
421
|
+
catch (err) {
|
422
|
+
this.emit("error", err);
|
423
|
+
return;
|
419
424
|
}
|
425
|
+
this._sanitizeOptions(this._options);
|
420
426
|
}
|
421
|
-
else {
|
422
|
-
// The response is not a redirect; return it as-is
|
423
|
-
response.responseUrl = this._currentUrl;
|
424
|
-
response.redirects = this._redirects;
|
425
|
-
this.emit("response", response);
|
426
427
|
|
427
|
-
|
428
|
-
|
428
|
+
// Perform the redirected request
|
429
|
+
try {
|
430
|
+
this._performRequest();
|
431
|
+
}
|
432
|
+
catch (cause) {
|
433
|
+
this.emit("error", new RedirectionError(cause));
|
429
434
|
}
|
430
435
|
};
|
431
436
|
|
@@ -525,11 +530,12 @@ function removeMatchingHeaders(regex, headers) {
|
|
525
530
|
var lastValue;
|
526
531
|
for (var header in headers) {
|
527
532
|
if (regex.test(header)) {
|
528
|
-
lastValue = headers[header]
|
533
|
+
lastValue = headers[header];
|
529
534
|
delete headers[header];
|
530
535
|
}
|
531
536
|
}
|
532
|
-
return lastValue
|
537
|
+
return (lastValue === null || typeof lastValue === "undefined") ?
|
538
|
+
undefined : String(lastValue).trim();
|
533
539
|
}
|
534
540
|
|
535
541
|
function createErrorType(code, defaultMessage) {
|
@@ -558,7 +564,7 @@ function abortRequest(request) {
|
|
558
564
|
request.abort();
|
559
565
|
}
|
560
566
|
|
561
|
-
function
|
567
|
+
function isSubdomain(subdomain, domain) {
|
562
568
|
const dot = subdomain.length - domain.length - 1;
|
563
569
|
return dot > 0 && subdomain[dot] === "." && subdomain.endsWith(domain);
|
564
570
|
}
|