follow-redirects 1.14.7 → 1.15.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 +8 -1
  2. package/index.js +102 -78
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -63,10 +63,17 @@ const { http, https } = require('follow-redirects');
63
63
 
64
64
  const options = url.parse('http://bit.ly/900913');
65
65
  options.maxRedirects = 10;
66
- options.beforeRedirect = (options, { headers }) => {
66
+ options.beforeRedirect = (options, response, request) => {
67
67
  // Use this to adjust the request options upon redirecting,
68
68
  // to inspect the latest response headers,
69
69
  // or to cancel the request by throwing an error
70
+
71
+ // response.headers = the redirect response headers
72
+ // response.statusCode = the redirect response code (eg. 301, 307, etc.)
73
+
74
+ // request.url = the requested URL that resulted in a redirect
75
+ // request.headers = the headers in the request that resulted in a redirect
76
+ // request.method = the method of the request that resulted in a redirect
70
77
  if (options.hostname === "example.com") {
71
78
  options.auth = "user:password";
72
79
  }
package/index.js CHANGED
@@ -270,7 +270,7 @@ RedirectableRequest.prototype._performRequest = function () {
270
270
  // If specified, use the agent corresponding to the protocol
271
271
  // (HTTP and HTTPS use different types of agents)
272
272
  if (this._options.agents) {
273
- var scheme = protocol.substr(0, protocol.length - 1);
273
+ var scheme = protocol.slice(0, -1);
274
274
  this._options.agent = this._options.agents[scheme];
275
275
  }
276
276
 
@@ -336,96 +336,120 @@ RedirectableRequest.prototype._processResponse = function (response) {
336
336
  // the user agent MAY automatically redirect its request to the URI
337
337
  // referenced by the Location field value,
338
338
  // even if the specific status code is not understood.
339
+
340
+ // If the response is not a redirect; return it as-is
339
341
  var location = response.headers.location;
340
- if (location && this._options.followRedirects !== false &&
341
- statusCode >= 300 && statusCode < 400) {
342
- // Abort the current request
343
- abortRequest(this._currentRequest);
344
- // Discard the remainder of the response to avoid waiting for data
345
- response.destroy();
346
-
347
- // RFC7231§6.4: A client SHOULD detect and intervene
348
- // in cyclical redirections (i.e., "infinite" redirection loops).
349
- if (++this._redirectCount > this._options.maxRedirects) {
350
- this.emit("error", new TooManyRedirectsError());
351
- return;
352
- }
342
+ if (!location || this._options.followRedirects === false ||
343
+ statusCode < 300 || statusCode >= 400) {
344
+ response.responseUrl = this._currentUrl;
345
+ response.redirects = this._redirects;
346
+ this.emit("response", response);
353
347
 
354
- // RFC7231§6.4: Automatic redirection needs to done with
355
- // care for methods not known to be safe, []
356
- // RFC7231§6.4.2–3: For historical reasons, a user agent MAY change
357
- // the request method from POST to GET for the subsequent request.
358
- if ((statusCode === 301 || statusCode === 302) && this._options.method === "POST" ||
359
- // RFC7231§6.4.4: The 303 (See Other) status code indicates that
360
- // the server is redirecting the user agent to a different resource […]
361
- // A user agent can perform a retrieval request targeting that URI
362
- // (a GET or HEAD request if using HTTP) […]
363
- (statusCode === 303) && !/^(?:GET|HEAD)$/.test(this._options.method)) {
364
- this._options.method = "GET";
365
- // Drop a possible entity and headers related to it
366
- this._requestBodyBuffers = [];
367
- removeMatchingHeaders(/^content-/i, this._options.headers);
368
- }
348
+ // Clean up
349
+ this._requestBodyBuffers = [];
350
+ return;
351
+ }
369
352
 
370
- // Drop the Host header, as the redirect might lead to a different host
371
- var currentHostHeader = removeMatchingHeaders(/^host$/i, this._options.headers);
353
+ // The response is a redirect, so abort the current request
354
+ abortRequest(this._currentRequest);
355
+ // Discard the remainder of the response to avoid waiting for data
356
+ response.destroy();
372
357
 
373
- // If the redirect is relative, carry over the host of the last request
374
- var currentUrlParts = url.parse(this._currentUrl);
375
- var currentHost = currentHostHeader || currentUrlParts.host;
376
- var currentUrl = /^\w+:/.test(location) ? this._currentUrl :
377
- url.format(Object.assign(currentUrlParts, { host: currentHost }));
358
+ // RFC7231§6.4: A client SHOULD detect and intervene
359
+ // in cyclical redirections (i.e., "infinite" redirection loops).
360
+ if (++this._redirectCount > this._options.maxRedirects) {
361
+ this.emit("error", new TooManyRedirectsError());
362
+ return;
363
+ }
378
364
 
379
- // Determine the URL of the redirection
380
- var redirectUrl;
381
- try {
382
- redirectUrl = url.resolve(currentUrl, location);
383
- }
384
- catch (cause) {
385
- this.emit("error", new RedirectionError(cause));
386
- return;
387
- }
365
+ // Store the request headers if applicable
366
+ var requestHeaders;
367
+ var beforeRedirect = this._options.beforeRedirect;
368
+ if (beforeRedirect) {
369
+ requestHeaders = Object.assign({
370
+ // The Host header was set by nativeProtocol.request
371
+ Host: response.req.getHeader("host"),
372
+ }, this._options.headers);
373
+ }
388
374
 
389
- // Create the redirected request
390
- debug("redirecting to", redirectUrl);
391
- this._isRedirect = true;
392
- var redirectUrlParts = url.parse(redirectUrl);
393
- Object.assign(this._options, redirectUrlParts);
375
+ // RFC7231§6.4: Automatic redirection needs to done with
376
+ // care for methods not known to be safe, […]
377
+ // RFC7231§6.4.2–3: For historical reasons, a user agent MAY change
378
+ // the request method from POST to GET for the subsequent request.
379
+ var method = this._options.method;
380
+ if ((statusCode === 301 || statusCode === 302) && this._options.method === "POST" ||
381
+ // RFC7231§6.4.4: The 303 (See Other) status code indicates that
382
+ // the server is redirecting the user agent to a different resource […]
383
+ // A user agent can perform a retrieval request targeting that URI
384
+ // (a GET or HEAD request if using HTTP) […]
385
+ (statusCode === 303) && !/^(?:GET|HEAD)$/.test(this._options.method)) {
386
+ this._options.method = "GET";
387
+ // Drop a possible entity and headers related to it
388
+ this._requestBodyBuffers = [];
389
+ removeMatchingHeaders(/^content-/i, this._options.headers);
390
+ }
394
391
 
395
- // Drop the confidential headers when redirecting to another domain
396
- if (!(redirectUrlParts.host === currentHost || isSubdomainOf(redirectUrlParts.host, currentHost))) {
397
- removeMatchingHeaders(/^(?:authorization|cookie)$/i, this._options.headers);
398
- }
392
+ // Drop the Host header, as the redirect might lead to a different host
393
+ var currentHostHeader = removeMatchingHeaders(/^host$/i, this._options.headers);
399
394
 
400
- // Evaluate the beforeRedirect callback
401
- if (typeof this._options.beforeRedirect === "function") {
402
- var responseDetails = { headers: response.headers };
403
- try {
404
- this._options.beforeRedirect.call(null, this._options, responseDetails);
405
- }
406
- catch (err) {
407
- this.emit("error", err);
408
- return;
409
- }
410
- this._sanitizeOptions(this._options);
411
- }
395
+ // If the redirect is relative, carry over the host of the last request
396
+ var currentUrlParts = url.parse(this._currentUrl);
397
+ var currentHost = currentHostHeader || currentUrlParts.host;
398
+ var currentUrl = /^\w+:/.test(location) ? this._currentUrl :
399
+ url.format(Object.assign(currentUrlParts, { host: currentHost }));
400
+
401
+ // Determine the URL of the redirection
402
+ var redirectUrl;
403
+ try {
404
+ redirectUrl = url.resolve(currentUrl, location);
405
+ }
406
+ catch (cause) {
407
+ this.emit("error", new RedirectionError(cause));
408
+ return;
409
+ }
410
+
411
+ // Create the redirected request
412
+ debug("redirecting to", redirectUrl);
413
+ this._isRedirect = true;
414
+ var redirectUrlParts = url.parse(redirectUrl);
415
+ Object.assign(this._options, redirectUrlParts);
416
+
417
+ // Drop confidential headers when redirecting to a less secure protocol
418
+ // or to a different domain that is not a superdomain
419
+ if (redirectUrlParts.protocol !== currentUrlParts.protocol &&
420
+ redirectUrlParts.protocol !== "https:" ||
421
+ redirectUrlParts.host !== currentHost &&
422
+ !isSubdomain(redirectUrlParts.host, currentHost)) {
423
+ removeMatchingHeaders(/^(?:authorization|cookie)$/i, this._options.headers);
424
+ }
412
425
 
413
- // Perform the redirected request
426
+ // Evaluate the beforeRedirect callback
427
+ if (typeof beforeRedirect === "function") {
428
+ var responseDetails = {
429
+ headers: response.headers,
430
+ statusCode: statusCode,
431
+ };
432
+ var requestDetails = {
433
+ url: currentUrl,
434
+ method: method,
435
+ headers: requestHeaders,
436
+ };
414
437
  try {
415
- this._performRequest();
438
+ beforeRedirect(this._options, responseDetails, requestDetails);
416
439
  }
417
- catch (cause) {
418
- this.emit("error", new RedirectionError(cause));
440
+ catch (err) {
441
+ this.emit("error", err);
442
+ return;
419
443
  }
444
+ this._sanitizeOptions(this._options);
420
445
  }
421
- else {
422
- // The response is not a redirect; return it as-is
423
- response.responseUrl = this._currentUrl;
424
- response.redirects = this._redirects;
425
- this.emit("response", response);
426
446
 
427
- // Clean up
428
- this._requestBodyBuffers = [];
447
+ // Perform the redirected request
448
+ try {
449
+ this._performRequest();
450
+ }
451
+ catch (cause) {
452
+ this.emit("error", new RedirectionError(cause));
429
453
  }
430
454
  };
431
455
 
@@ -559,7 +583,7 @@ function abortRequest(request) {
559
583
  request.abort();
560
584
  }
561
585
 
562
- function isSubdomainOf(subdomain, domain) {
586
+ function isSubdomain(subdomain, domain) {
563
587
  const dot = subdomain.length - domain.length - 1;
564
588
  return dot > 0 && subdomain[dot] === "." && subdomain.endsWith(domain);
565
589
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "follow-redirects",
3
- "version": "1.14.7",
3
+ "version": "1.15.0",
4
4
  "description": "HTTP and HTTPS modules that follow redirects.",
5
5
  "license": "MIT",
6
6
  "main": "index.js",