diff --git a/README.md b/README.md index ea618ab..b0b9fa9 100644 --- a/README.md +++ b/README.md @@ -63,10 +63,16 @@ const { http, https } = require('follow-redirects'); const options = url.parse('http://bit.ly/900913'); options.maxRedirects = 10; -options.beforeRedirect = (options, { headers }) => { +options.beforeRedirect = (options, { headers, statusCode }, { method, url }) => { // Use this to adjust the request options upon redirecting, // to inspect the latest response headers, // or to cancel the request by throwing an error + + // headers = the redirect response headers + // statusCode = the redirect response code (eg. 301, 307, etc.) + + // method = the request method that resulted in a redirect + // url = the requested URL that resulted in a redirect if (options.hostname === "example.com") { options.auth = "user:password"; } diff --git a/index.js b/index.js index a5b28d9..794e7dc 100644 --- a/index.js +++ b/index.js @@ -366,6 +366,7 @@ RedirectableRequest.prototype._processResponse = function (response) { // 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. + var method = this._options.method; 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 […] @@ -413,10 +414,18 @@ RedirectableRequest.prototype._processResponse = function (response) { } // Evaluate the beforeRedirect callback - if (typeof this._options.beforeRedirect === "function") { - var responseDetails = { headers: response.headers }; + var beforeRedirect = this._options.beforeRedirect; + if (typeof beforeRedirect === "function") { + var responseDetails = { + headers: response.headers, + statusCode: statusCode, + }; + var requestDetails = { + method: method, + url: currentUrl, + }; try { - this._options.beforeRedirect.call(null, this._options, responseDetails); + beforeRedirect(this._options, responseDetails, requestDetails); } catch (err) { this.emit("error", err); diff --git a/package-lock.json b/package-lock.json index 68be43b..0bcde7e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,6 +5,7 @@ "requires": true, "packages": { "": { + "name": "follow-redirects", "version": "1.14.9", "funding": [ { diff --git a/test/test.js b/test/test.js index 4e3ffc9..ede3e02 100644 --- a/test/test.js +++ b/test/test.js @@ -1581,6 +1581,95 @@ describe("follow-redirects", function () { assert.equal(body[header.toLowerCase()], undefined); }); }); + + it("passes the redirect status code to beforeRedirect", function () { + app.get("/a", redirectsTo("/b")); + app.get("/b", redirectsTo("/c", 301)); + app.get("/c", redirectsTo("/d", 302)); + app.get("/d", redirectsTo("/e", 303)); + app.get("/e", redirectsTo("/f", 307)); + app.get("/f", redirectsTo("/g", 308)); + app.get("/g", sendsJson({ a: "b" })); + + const statusCodes = []; + + return server.start(app) + .then(asPromise(function (resolve, reject) { + var options = { + host: "localhost", + port: 3600, + path: "/a", + method: "GET", + beforeRedirect: function (_, response) { + statusCodes.push(response.statusCode); + }, + }; + http.get(options, concatJson(resolve, reject)).on("error", reject); + })) + .then(function (res) { + assert.deepEqual(res.responseUrl, "http://localhost:3600/g"); + assert.deepEqual(res.parsedJson, { a: "b" }); + assert.deepEqual(statusCodes, [302, 301, 302, 303, 307, 308]); + }); + }); + + it("passes the request method to beforeRedirect", function () { + app.post("/a", redirectsTo("/b", 308)); + app.post("/b", redirectsTo("/c", 301)); + app.get("/c", redirectsTo("/d", 301)); + app.get("/d", sendsJson({ a: "b" })); + + const requestMethods = []; + + return server.start(app) + .then(asPromise(function (resolve, reject) { + var options = { + host: "localhost", + port: 3600, + path: "/a", + method: "POST", + beforeRedirect: function (_, __, request) { + requestMethods.push(request.method); + }, + }; + http.get(options, concatJson(resolve, reject)).on("error", reject); + })) + .then(function (res) { + assert.deepEqual(res.responseUrl, "http://localhost:3600/d"); + assert.deepEqual(res.parsedJson, { a: "b" }); + assert.deepEqual(requestMethods, ["POST", "POST", "GET"]); + }); + }); + + it("passes the original request URL to beforeRedirect", function () { + app.get("/a", redirectsTo("/b")); + app.get("/b", redirectsTo("/c")); + app.get("/c", sendsJson({ a: "b" })); + + const urlChain = []; + + return server.start(app) + .then(asPromise(function (resolve, reject) { + var options = { + host: "localhost", + port: 3600, + path: "/a", + method: "GET", + beforeRedirect: function (_, __, request) { + urlChain.push(request.url); + }, + }; + http.get(options, concatJson(resolve, reject)).on("error", reject); + })) + .then(function (res) { + assert.deepEqual(res.responseUrl, "http://localhost:3600/c"); + assert.deepEqual(res.parsedJson, { a: "b" }); + assert.deepEqual(urlChain, [ + "http://localhost:3600/a", + "http://localhost:3600/b", + ]); + }); + }); }); describe("when the followRedirects option is set to false", function () {