follow-redirects 1.6.0 → 1.8.1

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 +31 -41
  2. package/index.js +83 -32
  3. package/package.json +12 -21
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
  });
@@ -96,42 +102,26 @@ var followRedirects = require('follow-redirects').wrap({
96
102
 
97
103
  Such custom protocols only need an implementation of the `request` method.
98
104
 
99
- ## Browserify Usage
105
+ ## Browser Usage
100
106
 
101
- Due to the way `XMLHttpRequest` works, the `browserify` versions of `http` and `https` already follow redirects.
102
- If you are *only* targeting the browser, then this library has little value for you. If you want to write cross
103
- platform code for node and the browser, `follow-redirects` provides a great solution for making the native node
104
- modules behave the same as they do in browserified builds in the browser. To avoid bundling unnecessary code
105
- you should tell browserify to swap out `follow-redirects` with the standard modules when bundling.
106
- To make this easier, you need to change how you require the modules:
107
+ Due to the way the browser works,
108
+ the `http` and `https` browser equivalents perform redirects by default.
107
109
 
110
+ By requiring `follow-redirects` this way:
108
111
  ```javascript
109
- var http = require('follow-redirects/http');
110
- var https = require('follow-redirects/https');
112
+ const http = require('follow-redirects/http');
113
+ const https = require('follow-redirects/https');
111
114
  ```
115
+ you can easily tell webpack and friends to replace
116
+ `follow-redirect` by the built-in versions:
112
117
 
113
- You can then replace `follow-redirects` in your browserify configuration like so:
114
-
115
- ```javascript
116
- "browser": {
118
+ ```json
119
+ {
117
120
  "follow-redirects/http" : "http",
118
121
  "follow-redirects/https" : "https"
119
122
  }
120
123
  ```
121
124
 
122
- The `browserify-http` module has not kept pace with node development, and no long behaves identically to the native
123
- module when running in the browser. If you are experiencing problems, you may want to check out
124
- [browserify-http-2](https://www.npmjs.com/package/http-browserify-2). It is more actively maintained and
125
- attempts to address a few of the shortcomings of `browserify-http`. In that case, your browserify config should
126
- look something like this:
127
-
128
- ```javascript
129
- "browser": {
130
- "follow-redirects/http" : "browserify-http-2/http",
131
- "follow-redirects/https" : "browserify-http-2/https"
132
- }
133
- ```
134
-
135
125
  ## Contributing
136
126
 
137
127
  Pull Requests are always welcome. Please [file an issue](https://github.com/follow-redirects/follow-redirects/issues)
@@ -146,9 +136,9 @@ Pull Requests are always welcome. Please [file an issue](https://github.com/foll
146
136
 
147
137
  ## Authors
148
138
 
139
+ - [Ruben Verborgh](https://ruben.verborgh.org/)
149
140
  - Olivier Lalonde (olalonde@gmail.com)
150
141
  - James Talmage (james@talmage.io)
151
- - [Ruben Verborgh](https://ruben.verborgh.org/)
152
142
 
153
143
  ## License
154
144
 
package/index.js CHANGED
@@ -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
@@ -387,7 +438,7 @@ function urlToOptions(urlObject) {
387
438
  hash: urlObject.hash,
388
439
  search: urlObject.search,
389
440
  pathname: urlObject.pathname,
390
- path: `${urlObject.pathname}${urlObject.search}`,
441
+ path: urlObject.pathname + urlObject.search,
391
442
  href: urlObject.href,
392
443
  };
393
444
  if (urlObject.port !== "") {
package/package.json CHANGED
@@ -1,8 +1,12 @@
1
1
  {
2
2
  "name": "follow-redirects",
3
- "version": "1.6.0",
3
+ "version": "1.8.1",
4
4
  "description": "HTTP and HTTPS modules that follow redirects.",
5
+ "license": "MIT",
5
6
  "main": "index.js",
7
+ "files": [
8
+ "*.js"
9
+ ],
6
10
  "engines": {
7
11
  "node": ">=4.0"
8
12
  },
@@ -33,28 +37,15 @@
33
37
  "Olivier Lalonde <olalonde@gmail.com> (http://www.syskall.com)",
34
38
  "James Talmage <james@talmage.io>"
35
39
  ],
36
- "files": [
37
- "index.js",
38
- "create.js",
39
- "http.js",
40
- "https.js"
41
- ],
42
40
  "dependencies": {
43
- "debug": "=3.1.0"
41
+ "debug": "^3.0.0"
44
42
  },
45
43
  "devDependencies": {
46
- "concat-stream": "^1.6.0",
47
- "coveralls": "^3.0.2",
48
- "eslint": "^4.19.1",
49
- "express": "^4.16.2",
50
- "mocha": "^5.0.0",
51
- "nyc": "^11.8.0"
52
- },
53
- "license": "MIT",
54
- "nyc": {
55
- "reporter": [
56
- "lcov",
57
- "text"
58
- ]
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"
59
50
  }
60
51
  }