follow-redirects 1.9.0 → 1.12.0

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/README.md CHANGED
@@ -137,8 +137,8 @@ Pull Requests are always welcome. Please [file an issue](https://github.com/foll
137
137
  ## Authors
138
138
 
139
139
  - [Ruben Verborgh](https://ruben.verborgh.org/)
140
- - Olivier Lalonde (olalonde@gmail.com)
141
- - James Talmage (james@talmage.io)
140
+ - [Olivier Lalonde](mailto:olalonde@gmail.com)
141
+ - [James Talmage](mailto:james@talmage.io)
142
142
 
143
143
  ## License
144
144
 
package/debug.js ADDED
@@ -0,0 +1,9 @@
1
+ var debug;
2
+ try {
3
+ /* eslint global-require: off */
4
+ debug = require("debug")("follow-redirects");
5
+ }
6
+ catch (error) {
7
+ debug = function () { /* */ };
8
+ }
9
+ module.exports = debug;
package/index.js CHANGED
@@ -2,13 +2,9 @@ var url = require("url");
2
2
  var URL = url.URL;
3
3
  var http = require("http");
4
4
  var https = require("https");
5
- var assert = require("assert");
6
5
  var Writable = require("stream").Writable;
7
- var debug = require("debug")("follow-redirects");
8
-
9
- // RFC7231§4.2.1: Of the request methods defined by this specification,
10
- // the GET, HEAD, OPTIONS, and TRACE methods are defined to be safe.
11
- var SAFE_METHODS = { GET: true, HEAD: true, OPTIONS: true, TRACE: true };
6
+ var assert = require("assert");
7
+ var debug = require("./debug");
12
8
 
13
9
  // Create handlers that pass events from native requests
14
10
  var eventHandlers = Object.create(null);
@@ -18,6 +14,24 @@ var eventHandlers = Object.create(null);
18
14
  };
19
15
  });
20
16
 
17
+ // Error types with codes
18
+ var RedirectionError = createErrorType(
19
+ "ERR_FR_REDIRECTION_FAILURE",
20
+ ""
21
+ );
22
+ var TooManyRedirectsError = createErrorType(
23
+ "ERR_FR_TOO_MANY_REDIRECTS",
24
+ "Maximum number of redirects exceeded"
25
+ );
26
+ var MaxBodyLengthExceededError = createErrorType(
27
+ "ERR_FR_MAX_BODY_LENGTH_EXCEEDED",
28
+ "Request body larger than maxBodyLength limit"
29
+ );
30
+ var WriteAfterEndError = createErrorType(
31
+ "ERR_STREAM_WRITE_AFTER_END",
32
+ "write after end"
33
+ );
34
+
21
35
  // An HTTP(S) request that can be redirected
22
36
  function RedirectableRequest(options, responseCallback) {
23
37
  // Initialize the request
@@ -51,12 +65,12 @@ RedirectableRequest.prototype = Object.create(Writable.prototype);
51
65
  RedirectableRequest.prototype.write = function (data, encoding, callback) {
52
66
  // Writing is not allowed if end has been called
53
67
  if (this._ending) {
54
- throw new Error("write after end");
68
+ throw new WriteAfterEndError();
55
69
  }
56
70
 
57
71
  // Validate input and shift parameters if necessary
58
72
  if (!(typeof data === "string" || typeof data === "object" && ("length" in data))) {
59
- throw new Error("data should be a string, Buffer or Uint8Array");
73
+ throw new TypeError("data should be a string, Buffer or Uint8Array");
60
74
  }
61
75
  if (typeof encoding === "function") {
62
76
  callback = encoding;
@@ -79,7 +93,7 @@ RedirectableRequest.prototype.write = function (data, encoding, callback) {
79
93
  }
80
94
  // Error when we exceed the maximum body length
81
95
  else {
82
- this.emit("error", new Error("Request body larger than maxBodyLength limit"));
96
+ this.emit("error", new MaxBodyLengthExceededError());
83
97
  this.abort();
84
98
  }
85
99
  };
@@ -211,7 +225,7 @@ RedirectableRequest.prototype._performRequest = function () {
211
225
  var protocol = this._options.protocol;
212
226
  var nativeProtocol = this._options.nativeProtocols[protocol];
213
227
  if (!nativeProtocol) {
214
- this.emit("error", new Error("Unsupported protocol " + protocol));
228
+ this.emit("error", new TypeError("Unsupported protocol " + protocol));
215
229
  return;
216
230
  }
217
231
 
@@ -300,43 +314,43 @@ RedirectableRequest.prototype._processResponse = function (response) {
300
314
  // RFC7231§6.4: A client SHOULD detect and intervene
301
315
  // in cyclical redirections (i.e., "infinite" redirection loops).
302
316
  if (++this._redirectCount > this._options.maxRedirects) {
303
- this.emit("error", new Error("Max redirects exceeded."));
317
+ this.emit("error", new TooManyRedirectsError());
304
318
  return;
305
319
  }
306
320
 
307
321
  // RFC7231§6.4: Automatic redirection needs to done with
308
- // care for methods not known to be safe […],
309
- // since the user might not wish to redirect an unsafe request.
310
- // RFC7231§6.4.7: The 307 (Temporary Redirect) status code indicates
311
- // that the target resource resides temporarily under a different URI
312
- // and the user agent MUST NOT change the request method
313
- // if it performs an automatic redirection to that URI.
314
- var header;
315
- var headers = this._options.headers;
316
- if (statusCode !== 307 && !(this._options.method in SAFE_METHODS)) {
322
+ // care for methods not known to be safe, […]
323
+ // RFC7231§6.4.2–3: For historical reasons, a user agent MAY change
324
+ // the request method from POST to GET for the subsequent request.
325
+ if ((statusCode === 301 || statusCode === 302) && this._options.method === "POST" ||
326
+ // RFC7231§6.4.4: The 303 (See Other) status code indicates that
327
+ // the server is redirecting the user agent to a different resource […]
328
+ // A user agent can perform a retrieval request targeting that URI
329
+ // (a GET or HEAD request if using HTTP) […]
330
+ (statusCode === 303) && !/^(?:GET|HEAD)$/.test(this._options.method)) {
317
331
  this._options.method = "GET";
318
332
  // Drop a possible entity and headers related to it
319
333
  this._requestBodyBuffers = [];
320
- for (header in headers) {
321
- if (/^content-/i.test(header)) {
322
- delete headers[header];
323
- }
324
- }
334
+ removeMatchingHeaders(/^content-/i, this._options.headers);
325
335
  }
326
336
 
327
337
  // Drop the Host header, as the redirect might lead to a different host
328
- if (!this._isRedirect) {
329
- for (header in headers) {
330
- if (/^host$/i.test(header)) {
331
- delete headers[header];
332
- }
333
- }
334
- }
338
+ var previousHostName = removeMatchingHeaders(/^host$/i, this._options.headers) ||
339
+ url.parse(this._currentUrl).hostname;
335
340
 
336
- // Perform the redirected request
341
+ // Create the redirected request
337
342
  var redirectUrl = url.resolve(this._currentUrl, location);
338
343
  debug("redirecting to", redirectUrl);
339
- Object.assign(this._options, url.parse(redirectUrl));
344
+ this._isRedirect = true;
345
+ var redirectUrlParts = url.parse(redirectUrl);
346
+ Object.assign(this._options, redirectUrlParts);
347
+
348
+ // Drop the Authorization header if redirecting to another host
349
+ if (redirectUrlParts.hostname !== previousHostName) {
350
+ removeMatchingHeaders(/^authorization$/i, this._options.headers);
351
+ }
352
+
353
+ // Evaluate the beforeRedirect callback
340
354
  if (typeof this._options.beforeRedirect === "function") {
341
355
  try {
342
356
  this._options.beforeRedirect.call(null, this._options);
@@ -347,8 +361,16 @@ RedirectableRequest.prototype._processResponse = function (response) {
347
361
  }
348
362
  this._sanitizeOptions(this._options);
349
363
  }
350
- this._isRedirect = true;
351
- this._performRequest();
364
+
365
+ // Perform the redirected request
366
+ try {
367
+ this._performRequest();
368
+ }
369
+ catch (cause) {
370
+ var error = new RedirectionError("Redirected request failed: " + cause.message);
371
+ error.cause = cause;
372
+ this.emit("error", error);
373
+ }
352
374
  }
353
375
  else {
354
376
  // The response is not a redirect; return it as-is
@@ -447,6 +469,29 @@ function urlToOptions(urlObject) {
447
469
  return options;
448
470
  }
449
471
 
472
+ function removeMatchingHeaders(regex, headers) {
473
+ var lastValue;
474
+ for (var header in headers) {
475
+ if (regex.test(header)) {
476
+ lastValue = headers[header];
477
+ delete headers[header];
478
+ }
479
+ }
480
+ return lastValue;
481
+ }
482
+
483
+ function createErrorType(code, defaultMessage) {
484
+ function CustomError(message) {
485
+ Error.captureStackTrace(this, this.constructor);
486
+ this.message = message || defaultMessage;
487
+ }
488
+ CustomError.prototype = new Error();
489
+ CustomError.prototype.constructor = CustomError;
490
+ CustomError.prototype.name = "Error [" + code + "]";
491
+ CustomError.prototype.code = code;
492
+ return CustomError;
493
+ }
494
+
450
495
  // Exports
451
496
  module.exports = wrap({ http: http, https: https });
452
497
  module.exports.wrap = wrap;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "follow-redirects",
3
- "version": "1.9.0",
3
+ "version": "1.12.0",
4
4
  "description": "HTTP and HTTPS modules that follow redirects.",
5
5
  "license": "MIT",
6
6
  "main": "index.js",
@@ -37,8 +37,8 @@
37
37
  "Olivier Lalonde <olalonde@gmail.com> (http://www.syskall.com)",
38
38
  "James Talmage <james@talmage.io>"
39
39
  ],
40
- "dependencies": {
41
- "debug": "^3.0.0"
40
+ "peerDependencies": {
41
+ "debug": "^3.0.0 || ^4.0.0"
42
42
  },
43
43
  "devDependencies": {
44
44
  "concat-stream": "^2.0.0",