follow-redirects 1.8.0 → 1.10.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.

Files changed (3) hide show
  1. package/README.md +2 -2
  2. package/index.js +71 -34
  3. package/package.json +4 -11
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/index.js CHANGED
@@ -6,18 +6,32 @@ var assert = require("assert");
6
6
  var Writable = require("stream").Writable;
7
7
  var debug = require("debug")("follow-redirects");
8
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 };
12
-
13
9
  // Create handlers that pass events from native requests
14
10
  var eventHandlers = Object.create(null);
15
- ["abort", "aborted", "error", "socket", "timeout"].forEach(function (event) {
16
- eventHandlers[event] = function (arg) {
17
- this._redirectable.emit(event, arg);
11
+ ["abort", "aborted", "connect", "error", "socket", "timeout"].forEach(function (event) {
12
+ eventHandlers[event] = function (arg1, arg2, arg3) {
13
+ this._redirectable.emit(event, arg1, arg2, arg3);
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,38 @@ 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
338
  if (!this._isRedirect) {
329
- for (header in headers) {
330
- if (/^host$/i.test(header)) {
331
- delete headers[header];
332
- }
333
- }
339
+ removeMatchingHeaders(/^host$/i, this._options.headers);
334
340
  }
335
341
 
336
- // Perform the redirected request
342
+ // Create the redirected request
337
343
  var redirectUrl = url.resolve(this._currentUrl, location);
338
344
  debug("redirecting to", redirectUrl);
345
+ this._isRedirect = true;
339
346
  Object.assign(this._options, url.parse(redirectUrl));
347
+
348
+ // Evaluate the beforeRedirect callback
340
349
  if (typeof this._options.beforeRedirect === "function") {
341
350
  try {
342
351
  this._options.beforeRedirect.call(null, this._options);
@@ -347,8 +356,16 @@ RedirectableRequest.prototype._processResponse = function (response) {
347
356
  }
348
357
  this._sanitizeOptions(this._options);
349
358
  }
350
- this._isRedirect = true;
351
- this._performRequest();
359
+
360
+ // Perform the redirected request
361
+ try {
362
+ this._performRequest();
363
+ }
364
+ catch (cause) {
365
+ var error = new RedirectionError("Redirected request failed: " + cause.message);
366
+ error.cause = cause;
367
+ this.emit("error", error);
368
+ }
352
369
  }
353
370
  else {
354
371
  // The response is not a redirect; return it as-is
@@ -447,6 +464,26 @@ function urlToOptions(urlObject) {
447
464
  return options;
448
465
  }
449
466
 
467
+ function removeMatchingHeaders(regex, headers) {
468
+ for (var header in headers) {
469
+ if (regex.test(header)) {
470
+ delete headers[header];
471
+ }
472
+ }
473
+ }
474
+
475
+ function createErrorType(code, defaultMessage) {
476
+ function CustomError(message) {
477
+ Error.captureStackTrace(this, this.constructor);
478
+ this.message = message || defaultMessage;
479
+ }
480
+ CustomError.prototype = new Error();
481
+ CustomError.prototype.constructor = CustomError;
482
+ CustomError.prototype.name = "Error [" + code + "]";
483
+ CustomError.prototype.code = code;
484
+ return CustomError;
485
+ }
486
+
450
487
  // Exports
451
488
  module.exports = wrap({ http: http, https: https });
452
489
  module.exports.wrap = wrap;
package/package.json CHANGED
@@ -1,13 +1,14 @@
1
1
  {
2
2
  "name": "follow-redirects",
3
- "version": "1.8.0",
3
+ "version": "1.10.0",
4
4
  "description": "HTTP and HTTPS modules that follow redirects.",
5
+ "license": "MIT",
5
6
  "main": "index.js",
6
7
  "files": [
7
8
  "*.js"
8
9
  ],
9
10
  "engines": {
10
- "node": ">=6.0"
11
+ "node": ">=4.0"
11
12
  },
12
13
  "scripts": {
13
14
  "test": "npm run lint && npm run mocha",
@@ -37,22 +38,14 @@
37
38
  "James Talmage <james@talmage.io>"
38
39
  ],
39
40
  "dependencies": {
40
- "debug": "^4.1.1"
41
+ "debug": "^3.0.0"
41
42
  },
42
43
  "devDependencies": {
43
44
  "concat-stream": "^2.0.0",
44
- "coveralls": "^3.0.3",
45
45
  "eslint": "^5.16.0",
46
46
  "express": "^4.16.4",
47
47
  "lolex": "^3.1.0",
48
48
  "mocha": "^6.0.2",
49
49
  "nyc": "^14.1.1"
50
- },
51
- "license": "MIT",
52
- "nyc": {
53
- "reporter": [
54
- "lcov",
55
- "text"
56
- ]
57
50
  }
58
51
  }