follow-redirects 1.14.8 → 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 +82 -81
- package/package.json +1 -1
package/index.js
CHANGED
@@ -336,97 +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
|
-
|
399
|
-
}
|
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 }));
|
400
389
|
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
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
|
+
}
|
413
414
|
|
414
|
-
|
415
|
+
// Evaluate the beforeRedirect callback
|
416
|
+
if (typeof this._options.beforeRedirect === "function") {
|
417
|
+
var responseDetails = { headers: response.headers };
|
415
418
|
try {
|
416
|
-
this.
|
419
|
+
this._options.beforeRedirect.call(null, this._options, responseDetails);
|
417
420
|
}
|
418
|
-
catch (
|
419
|
-
this.emit("error",
|
421
|
+
catch (err) {
|
422
|
+
this.emit("error", err);
|
423
|
+
return;
|
420
424
|
}
|
425
|
+
this._sanitizeOptions(this._options);
|
421
426
|
}
|
422
|
-
else {
|
423
|
-
// The response is not a redirect; return it as-is
|
424
|
-
response.responseUrl = this._currentUrl;
|
425
|
-
response.redirects = this._redirects;
|
426
|
-
this.emit("response", response);
|
427
427
|
|
428
|
-
|
429
|
-
|
428
|
+
// Perform the redirected request
|
429
|
+
try {
|
430
|
+
this._performRequest();
|
431
|
+
}
|
432
|
+
catch (cause) {
|
433
|
+
this.emit("error", new RedirectionError(cause));
|
430
434
|
}
|
431
435
|
};
|
432
436
|
|
@@ -560,10 +564,7 @@ function abortRequest(request) {
|
|
560
564
|
request.abort();
|
561
565
|
}
|
562
566
|
|
563
|
-
function
|
564
|
-
if (subdomain === domain) {
|
565
|
-
return true;
|
566
|
-
}
|
567
|
+
function isSubdomain(subdomain, domain) {
|
567
568
|
const dot = subdomain.length - domain.length - 1;
|
568
569
|
return dot > 0 && subdomain[dot] === "." && subdomain.endsWith(domain);
|
569
570
|
}
|