follow-redirects
Advanced tools
Comparing version 1.14.8 to 1.14.9
161
index.js
@@ -339,94 +339,98 @@ var url = require("url"); | ||
// even if the specific status code is not understood. | ||
// If the response is not a redirect; return it as-is | ||
var location = response.headers.location; | ||
if (location && this._options.followRedirects !== false && | ||
statusCode >= 300 && statusCode < 400) { | ||
// Abort the current request | ||
abortRequest(this._currentRequest); | ||
// Discard the remainder of the response to avoid waiting for data | ||
response.destroy(); | ||
if (!location || this._options.followRedirects === false || | ||
statusCode < 300 || statusCode >= 400) { | ||
response.responseUrl = this._currentUrl; | ||
response.redirects = this._redirects; | ||
this.emit("response", response); | ||
// RFC7231§6.4: A client SHOULD detect and intervene | ||
// in cyclical redirections (i.e., "infinite" redirection loops). | ||
if (++this._redirectCount > this._options.maxRedirects) { | ||
this.emit("error", new TooManyRedirectsError()); | ||
return; | ||
} | ||
// Clean up | ||
this._requestBodyBuffers = []; | ||
return; | ||
} | ||
// RFC7231§6.4: Automatic redirection needs to done with | ||
// care for methods not known to be safe, […] | ||
// RFC7231§6.4.2–3: For historical reasons, a user agent MAY change | ||
// the request method from POST to GET for the subsequent request. | ||
if ((statusCode === 301 || statusCode === 302) && this._options.method === "POST" || | ||
// RFC7231§6.4.4: The 303 (See Other) status code indicates that | ||
// the server is redirecting the user agent to a different resource […] | ||
// A user agent can perform a retrieval request targeting that URI | ||
// (a GET or HEAD request if using HTTP) […] | ||
(statusCode === 303) && !/^(?:GET|HEAD)$/.test(this._options.method)) { | ||
this._options.method = "GET"; | ||
// Drop a possible entity and headers related to it | ||
this._requestBodyBuffers = []; | ||
removeMatchingHeaders(/^content-/i, this._options.headers); | ||
} | ||
// The response is a redirect, so abort the current request | ||
abortRequest(this._currentRequest); | ||
// Discard the remainder of the response to avoid waiting for data | ||
response.destroy(); | ||
// Drop the Host header, as the redirect might lead to a different host | ||
var currentHostHeader = removeMatchingHeaders(/^host$/i, this._options.headers); | ||
// RFC7231§6.4: A client SHOULD detect and intervene | ||
// in cyclical redirections (i.e., "infinite" redirection loops). | ||
if (++this._redirectCount > this._options.maxRedirects) { | ||
this.emit("error", new TooManyRedirectsError()); | ||
return; | ||
} | ||
// If the redirect is relative, carry over the host of the last request | ||
var currentUrlParts = url.parse(this._currentUrl); | ||
var currentHost = currentHostHeader || currentUrlParts.host; | ||
var currentUrl = /^\w+:/.test(location) ? this._currentUrl : | ||
url.format(Object.assign(currentUrlParts, { host: currentHost })); | ||
// RFC7231§6.4: Automatic redirection needs to done with | ||
// care for methods not known to be safe, […] | ||
// RFC7231§6.4.2–3: For historical reasons, a user agent MAY change | ||
// the request method from POST to GET for the subsequent request. | ||
if ((statusCode === 301 || statusCode === 302) && this._options.method === "POST" || | ||
// RFC7231§6.4.4: The 303 (See Other) status code indicates that | ||
// the server is redirecting the user agent to a different resource […] | ||
// A user agent can perform a retrieval request targeting that URI | ||
// (a GET or HEAD request if using HTTP) […] | ||
(statusCode === 303) && !/^(?:GET|HEAD)$/.test(this._options.method)) { | ||
this._options.method = "GET"; | ||
// Drop a possible entity and headers related to it | ||
this._requestBodyBuffers = []; | ||
removeMatchingHeaders(/^content-/i, this._options.headers); | ||
} | ||
// Determine the URL of the redirection | ||
var redirectUrl; | ||
try { | ||
redirectUrl = url.resolve(currentUrl, location); | ||
} | ||
catch (cause) { | ||
this.emit("error", new RedirectionError(cause)); | ||
return; | ||
} | ||
// Drop the Host header, as the redirect might lead to a different host | ||
var currentHostHeader = removeMatchingHeaders(/^host$/i, this._options.headers); | ||
// Create the redirected request | ||
debug("redirecting to", redirectUrl); | ||
this._isRedirect = true; | ||
var redirectUrlParts = url.parse(redirectUrl); | ||
Object.assign(this._options, redirectUrlParts); | ||
// If the redirect is relative, carry over the host of the last request | ||
var currentUrlParts = url.parse(this._currentUrl); | ||
var currentHost = currentHostHeader || currentUrlParts.host; | ||
var currentUrl = /^\w+:/.test(location) ? this._currentUrl : | ||
url.format(Object.assign(currentUrlParts, { host: currentHost })); | ||
// Drop confidential headers when redirecting to another scheme:domain | ||
if (redirectUrlParts.protocol !== currentUrlParts.protocol || | ||
!isSameOrSubdomain(redirectUrlParts.host, currentHost)) { | ||
removeMatchingHeaders(/^(?:authorization|cookie)$/i, this._options.headers); | ||
} | ||
// Determine the URL of the redirection | ||
var redirectUrl; | ||
try { | ||
redirectUrl = url.resolve(currentUrl, location); | ||
} | ||
catch (cause) { | ||
this.emit("error", new RedirectionError(cause)); | ||
return; | ||
} | ||
// Evaluate the beforeRedirect callback | ||
if (typeof this._options.beforeRedirect === "function") { | ||
var responseDetails = { headers: response.headers }; | ||
try { | ||
this._options.beforeRedirect.call(null, this._options, responseDetails); | ||
} | ||
catch (err) { | ||
this.emit("error", err); | ||
return; | ||
} | ||
this._sanitizeOptions(this._options); | ||
} | ||
// Create the redirected request | ||
debug("redirecting to", redirectUrl); | ||
this._isRedirect = true; | ||
var redirectUrlParts = url.parse(redirectUrl); | ||
Object.assign(this._options, redirectUrlParts); | ||
// Perform the redirected request | ||
// Drop confidential headers when redirecting to a less secure protocol | ||
// or to a different domain that is not a superdomain | ||
if (redirectUrlParts.protocol !== currentUrlParts.protocol && | ||
redirectUrlParts.protocol !== "https:" || | ||
redirectUrlParts.host !== currentHost && | ||
!isSubdomain(redirectUrlParts.host, currentHost)) { | ||
removeMatchingHeaders(/^(?:authorization|cookie)$/i, this._options.headers); | ||
} | ||
// Evaluate the beforeRedirect callback | ||
if (typeof this._options.beforeRedirect === "function") { | ||
var responseDetails = { headers: response.headers }; | ||
try { | ||
this._performRequest(); | ||
this._options.beforeRedirect.call(null, this._options, responseDetails); | ||
} | ||
catch (cause) { | ||
this.emit("error", new RedirectionError(cause)); | ||
catch (err) { | ||
this.emit("error", err); | ||
return; | ||
} | ||
this._sanitizeOptions(this._options); | ||
} | ||
else { | ||
// The response is not a redirect; return it as-is | ||
response.responseUrl = this._currentUrl; | ||
response.redirects = this._redirects; | ||
this.emit("response", response); | ||
// Clean up | ||
this._requestBodyBuffers = []; | ||
// Perform the redirected request | ||
try { | ||
this._performRequest(); | ||
} | ||
catch (cause) { | ||
this.emit("error", new RedirectionError(cause)); | ||
} | ||
}; | ||
@@ -564,6 +568,3 @@ | ||
function isSameOrSubdomain(subdomain, domain) { | ||
if (subdomain === domain) { | ||
return true; | ||
} | ||
function isSubdomain(subdomain, domain) { | ||
const dot = subdomain.length - domain.length - 1; | ||
@@ -570,0 +571,0 @@ return dot > 0 && subdomain[dot] === "." && subdomain.endsWith(domain); |
{ | ||
"name": "follow-redirects", | ||
"version": "1.14.8", | ||
"version": "1.14.9", | ||
"description": "HTTP and HTTPS modules that follow redirects.", | ||
@@ -5,0 +5,0 @@ "license": "MIT", |
26596
528