follow-redirects 1.6.1 → 1.9.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 +23 -17
  2. package/index.js +85 -34
  3. package/package.json +9 -15
package/README.md CHANGED
@@ -1,11 +1,10 @@
1
1
  ## Follow Redirects
2
2
 
3
- Drop-in replacement for Nodes `http` and `https` that automatically follows redirects.
3
+ Drop-in replacement for Node's `http` and `https` modules that automatically follows redirects.
4
4
 
5
5
  [![npm version](https://img.shields.io/npm/v/follow-redirects.svg)](https://www.npmjs.com/package/follow-redirects)
6
6
  [![Build Status](https://travis-ci.org/follow-redirects/follow-redirects.svg?branch=master)](https://travis-ci.org/follow-redirects/follow-redirects)
7
7
  [![Coverage Status](https://coveralls.io/repos/follow-redirects/follow-redirects/badge.svg?branch=master)](https://coveralls.io/r/follow-redirects/follow-redirects?branch=master)
8
- [![Dependency Status](https://david-dm.org/follow-redirects/follow-redirects.svg)](https://david-dm.org/follow-redirects/follow-redirects)
9
8
  [![npm downloads](https://img.shields.io/npm/dm/follow-redirects.svg)](https://www.npmjs.com/package/follow-redirects)
10
9
 
11
10
  `follow-redirects` provides [request](https://nodejs.org/api/http.html#http_http_request_options_callback) and [get](https://nodejs.org/api/http.html#http_http_get_options_callback)
@@ -13,14 +12,13 @@ Drop-in replacement for Nodes `http` and `https` that automatically follows redi
13
12
  modules, with the exception that they will seamlessly follow redirects.
14
13
 
15
14
  ```javascript
16
- var http = require('follow-redirects').http;
17
- var https = require('follow-redirects').https;
15
+ const { http, https } = require('follow-redirects');
18
16
 
19
- http.get('http://bit.ly/900913', function (response) {
20
- response.on('data', function (chunk) {
17
+ http.get('http://bit.ly/900913', response => {
18
+ response.on('data', chunk => {
21
19
  console.log(chunk);
22
20
  });
23
- }).on('error', function (err) {
21
+ }).on('error', err => {
24
22
  console.error(err);
25
23
  });
26
24
  ```
@@ -32,7 +30,7 @@ If no redirection happened, `responseUrl` is the original request URL.
32
30
  https.request({
33
31
  host: 'bitly.com',
34
32
  path: '/UHfDGO',
35
- }, function (response) {
33
+ }, response => {
36
34
  console.log(response.responseUrl);
37
35
  // 'http://duckduckgo.com/robots.txt'
38
36
  });
@@ -43,7 +41,7 @@ https.request({
43
41
  Global options are set directly on the `follow-redirects` module:
44
42
 
45
43
  ```javascript
46
- var followRedirects = require('follow-redirects');
44
+ const followRedirects = require('follow-redirects');
47
45
  followRedirects.maxRedirects = 10;
48
46
  followRedirects.maxBodyLength = 20 * 1024 * 1024; // 20 MB
49
47
  ```
@@ -54,16 +52,22 @@ The following global options are supported:
54
52
 
55
53
  - `maxBodyLength` (default: 10MB) – sets the maximum size of the request body; if exceeded, an error will be emitted.
56
54
 
57
-
58
55
  ### Per-request options
59
56
  Per-request options are set by passing an `options` object:
60
57
 
61
58
  ```javascript
62
- var url = require('url');
63
- var followRedirects = require('follow-redirects');
59
+ const url = require('url');
60
+ const { http, https } = require('follow-redirects');
64
61
 
65
- var options = url.parse('http://bit.ly/900913');
62
+ const options = url.parse('http://bit.ly/900913');
66
63
  options.maxRedirects = 10;
64
+ options.beforeRedirect = options => {
65
+ // Use this function to adjust the options upon redirecting,
66
+ // or to cancel the request by throwing an error
67
+ if (options.hostname === "example.com") {
68
+ options.auth = "user:password";
69
+ }
70
+ };
67
71
  http.request(options);
68
72
  ```
69
73
 
@@ -75,6 +79,8 @@ the following per-request options are supported:
75
79
 
76
80
  - `maxBodyLength` (default: 10MB) – sets the maximum size of the request body; if exceeded, an error will be emitted.
77
81
 
82
+ - `beforeRedirect` (default: `undefined`) – optionally change the request `options` on redirects, or abort the request by throwing an error.
83
+
78
84
  - `agents` (default: `undefined`) – sets the `agent` option per protocol, since HTTP and HTTPS use different agents. Example value: `{ http: new http.Agent(), https: new https.Agent() }`
79
85
 
80
86
  - `trackRedirects` (default: `false`) – whether to store the redirected response details into the `redirects` array on the response object.
@@ -88,7 +94,7 @@ To enable features such as caching and/or intermediate request tracking,
88
94
  you might instead want to wrap `follow-redirects` around custom protocol implementations:
89
95
 
90
96
  ```javascript
91
- var followRedirects = require('follow-redirects').wrap({
97
+ const { http, https } = require('follow-redirects').wrap({
92
98
  http: require('your-custom-http'),
93
99
  https: require('your-custom-https'),
94
100
  });
@@ -103,8 +109,8 @@ the `http` and `https` browser equivalents perform redirects by default.
103
109
 
104
110
  By requiring `follow-redirects` this way:
105
111
  ```javascript
106
- var http = require('follow-redirects/http');
107
- var https = require('follow-redirects/https');
112
+ const http = require('follow-redirects/http');
113
+ const https = require('follow-redirects/https');
108
114
  ```
109
115
  you can easily tell webpack and friends to replace
110
116
  `follow-redirect` by the built-in versions:
@@ -130,9 +136,9 @@ Pull Requests are always welcome. Please [file an issue](https://github.com/foll
130
136
 
131
137
  ## Authors
132
138
 
139
+ - [Ruben Verborgh](https://ruben.verborgh.org/)
133
140
  - Olivier Lalonde (olalonde@gmail.com)
134
141
  - James Talmage (james@talmage.io)
135
- - [Ruben Verborgh](https://ruben.verborgh.org/)
136
142
 
137
143
  ## License
138
144
 
package/index.js CHANGED
@@ -12,9 +12,9 @@ var SAFE_METHODS = { GET: true, HEAD: true, OPTIONS: true, TRACE: true };
12
12
 
13
13
  // Create handlers that pass events from native requests
14
14
  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);
15
+ ["abort", "aborted", "connect", "error", "socket", "timeout"].forEach(function (event) {
16
+ eventHandlers[event] = function (arg1, arg2, arg3) {
17
+ this._redirectable.emit(event, arg1, arg2, arg3);
18
18
  };
19
19
  });
20
20
 
@@ -22,7 +22,7 @@ var eventHandlers = Object.create(null);
22
22
  function RedirectableRequest(options, responseCallback) {
23
23
  // Initialize the request
24
24
  Writable.call(this);
25
- options.headers = options.headers || {};
25
+ this._sanitizeOptions(options);
26
26
  this._options = options;
27
27
  this._ended = false;
28
28
  this._ending = false;
@@ -31,17 +31,6 @@ function RedirectableRequest(options, responseCallback) {
31
31
  this._requestBodyLength = 0;
32
32
  this._requestBodyBuffers = [];
33
33
 
34
- // Since http.request treats host as an alias of hostname,
35
- // but the url module interprets host as hostname plus port,
36
- // eliminate the host property to avoid confusion.
37
- if (options.host) {
38
- // Use hostname if set, because it has precedence
39
- if (!options.hostname) {
40
- options.hostname = options.host;
41
- }
42
- delete options.host;
43
- }
44
-
45
34
  // Attach a callback if passed
46
35
  if (responseCallback) {
47
36
  this.on("response", responseCallback);
@@ -53,18 +42,6 @@ function RedirectableRequest(options, responseCallback) {
53
42
  self._processResponse(response);
54
43
  };
55
44
 
56
- // Complete the URL object when necessary
57
- if (!options.pathname && options.path) {
58
- var searchPos = options.path.indexOf("?");
59
- if (searchPos < 0) {
60
- options.pathname = options.path;
61
- }
62
- else {
63
- options.pathname = options.path.substring(0, searchPos);
64
- options.search = options.path.substring(searchPos);
65
- }
66
- }
67
-
68
45
  // Perform the first request
69
46
  this._performRequest();
70
47
  }
@@ -147,10 +124,43 @@ RedirectableRequest.prototype.removeHeader = function (name) {
147
124
  this._currentRequest.removeHeader(name);
148
125
  };
149
126
 
127
+ // Global timeout for all underlying requests
128
+ RedirectableRequest.prototype.setTimeout = function (msecs, callback) {
129
+ if (callback) {
130
+ this.once("timeout", callback);
131
+ }
132
+
133
+ if (this.socket) {
134
+ startTimer(this, msecs);
135
+ }
136
+ else {
137
+ var self = this;
138
+ this._currentRequest.once("socket", function () {
139
+ startTimer(self, msecs);
140
+ });
141
+ }
142
+
143
+ this.once("response", clearTimer);
144
+ this.once("error", clearTimer);
145
+
146
+ return this;
147
+ };
148
+
149
+ function startTimer(request, msecs) {
150
+ clearTimeout(request._timeout);
151
+ request._timeout = setTimeout(function () {
152
+ request.emit("timeout");
153
+ }, msecs);
154
+ }
155
+
156
+ function clearTimer() {
157
+ clearTimeout(this._timeout);
158
+ }
159
+
150
160
  // Proxy all other public ClientRequest methods
151
161
  [
152
162
  "abort", "flushHeaders", "getHeader",
153
- "setNoDelay", "setSocketKeepAlive", "setTimeout",
163
+ "setNoDelay", "setSocketKeepAlive",
154
164
  ].forEach(function (method) {
155
165
  RedirectableRequest.prototype[method] = function (a, b) {
156
166
  return this._currentRequest[method](a, b);
@@ -164,6 +174,37 @@ RedirectableRequest.prototype.removeHeader = function (name) {
164
174
  });
165
175
  });
166
176
 
177
+ RedirectableRequest.prototype._sanitizeOptions = function (options) {
178
+ // Ensure headers are always present
179
+ if (!options.headers) {
180
+ options.headers = {};
181
+ }
182
+
183
+ // Since http.request treats host as an alias of hostname,
184
+ // but the url module interprets host as hostname plus port,
185
+ // eliminate the host property to avoid confusion.
186
+ if (options.host) {
187
+ // Use hostname if set, because it has precedence
188
+ if (!options.hostname) {
189
+ options.hostname = options.host;
190
+ }
191
+ delete options.host;
192
+ }
193
+
194
+ // Complete the URL object when necessary
195
+ if (!options.pathname && options.path) {
196
+ var searchPos = options.path.indexOf("?");
197
+ if (searchPos < 0) {
198
+ options.pathname = options.path;
199
+ }
200
+ else {
201
+ options.pathname = options.path.substring(0, searchPos);
202
+ options.search = options.path.substring(searchPos);
203
+ }
204
+ }
205
+ };
206
+
207
+
167
208
  // Executes the next native request (initial or redirect)
168
209
  RedirectableRequest.prototype._performRequest = function () {
169
210
  // Load the native protocol
@@ -231,11 +272,12 @@ RedirectableRequest.prototype._performRequest = function () {
231
272
  // Processes a response from the current native request
232
273
  RedirectableRequest.prototype._processResponse = function (response) {
233
274
  // Store the redirected response
275
+ var statusCode = response.statusCode;
234
276
  if (this._options.trackRedirects) {
235
277
  this._redirects.push({
236
278
  url: this._currentUrl,
237
279
  headers: response.headers,
238
- statusCode: response.statusCode,
280
+ statusCode: statusCode,
239
281
  });
240
282
  }
241
283
 
@@ -247,11 +289,13 @@ RedirectableRequest.prototype._processResponse = function (response) {
247
289
  // even if the specific status code is not understood.
248
290
  var location = response.headers.location;
249
291
  if (location && this._options.followRedirects !== false &&
250
- response.statusCode >= 300 && response.statusCode < 400) {
292
+ statusCode >= 300 && statusCode < 400) {
251
293
  // Abort the current request
252
294
  this._currentRequest.removeAllListeners();
253
295
  this._currentRequest.on("error", noop);
254
296
  this._currentRequest.abort();
297
+ // Discard the remainder of the response to avoid waiting for data
298
+ response.destroy();
255
299
 
256
300
  // RFC7231§6.4: A client SHOULD detect and intervene
257
301
  // in cyclical redirections (i.e., "infinite" redirection loops).
@@ -269,7 +313,7 @@ RedirectableRequest.prototype._processResponse = function (response) {
269
313
  // if it performs an automatic redirection to that URI.
270
314
  var header;
271
315
  var headers = this._options.headers;
272
- if (response.statusCode !== 307 && !(this._options.method in SAFE_METHODS)) {
316
+ if (statusCode !== 307 && !(this._options.method in SAFE_METHODS)) {
273
317
  this._options.method = "GET";
274
318
  // Drop a possible entity and headers related to it
275
319
  this._requestBodyBuffers = [];
@@ -293,11 +337,18 @@ RedirectableRequest.prototype._processResponse = function (response) {
293
337
  var redirectUrl = url.resolve(this._currentUrl, location);
294
338
  debug("redirecting to", redirectUrl);
295
339
  Object.assign(this._options, url.parse(redirectUrl));
340
+ if (typeof this._options.beforeRedirect === "function") {
341
+ try {
342
+ this._options.beforeRedirect.call(null, this._options);
343
+ }
344
+ catch (err) {
345
+ this.emit("error", err);
346
+ return;
347
+ }
348
+ this._sanitizeOptions(this._options);
349
+ }
296
350
  this._isRedirect = true;
297
351
  this._performRequest();
298
-
299
- // Discard the remainder of the response to avoid waiting for data
300
- response.destroy();
301
352
  }
302
353
  else {
303
354
  // The response is not a redirect; return it as-is
package/package.json CHANGED
@@ -1,7 +1,8 @@
1
1
  {
2
2
  "name": "follow-redirects",
3
- "version": "1.6.1",
3
+ "version": "1.9.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"
@@ -37,21 +38,14 @@
37
38
  "James Talmage <james@talmage.io>"
38
39
  ],
39
40
  "dependencies": {
40
- "debug": "=3.1.0"
41
+ "debug": "^3.0.0"
41
42
  },
42
43
  "devDependencies": {
43
- "concat-stream": "^1.6.0",
44
- "coveralls": "^3.0.2",
45
- "eslint": "^4.19.1",
46
- "express": "^4.16.2",
47
- "mocha": "^5.0.0",
48
- "nyc": "^11.8.0"
49
- },
50
- "license": "MIT",
51
- "nyc": {
52
- "reporter": [
53
- "lcov",
54
- "text"
55
- ]
44
+ "concat-stream": "^2.0.0",
45
+ "eslint": "^5.16.0",
46
+ "express": "^4.16.4",
47
+ "lolex": "^3.1.0",
48
+ "mocha": "^6.0.2",
49
+ "nyc": "^14.1.1"
56
50
  }
57
51
  }