follow-redirects 1.14.8 → 1.15.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 +8 -1
  2. package/index.js +116 -91
  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,25 +270,30 @@ 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
 
277
- // Create the native request
277
+ // Create the native request and set up its event handlers
278
278
  var request = this._currentRequest =
279
279
  nativeProtocol.request(this._options, this._onNativeResponse);
280
- this._currentUrl = url.format(this._options);
281
-
282
- // Set up event handlers
283
280
  request._redirectable = this;
284
- for (var e = 0; e < events.length; e++) {
285
- request.on(events[e], eventHandlers[events[e]]);
281
+ for (var event of events) {
282
+ request.on(event, eventHandlers[event]);
286
283
  }
287
284
 
285
+ // RFC7230§5.3.1: When making a request directly to an origin server, […]
286
+ // a client MUST send only the absolute path […] as the request-target.
287
+ this._currentUrl = /^\//.test(this._options.path) ?
288
+ url.format(this._options) :
289
+ // When making a request to a proxy, […]
290
+ // a client MUST send the target URI in absolute-form […].
291
+ this._currentUrl = this._options.path;
292
+
288
293
  // End a redirected request
289
294
  // (The first request must be ended explicitly with RedirectableRequest#end)
290
295
  if (this._isRedirect) {
291
- // Write the request entity and end.
296
+ // Write the request entity and end
292
297
  var i = 0;
293
298
  var self = this;
294
299
  var buffers = this._requestBodyBuffers;
@@ -336,97 +341,120 @@ RedirectableRequest.prototype._processResponse = function (response) {
336
341
  // the user agent MAY automatically redirect its request to the URI
337
342
  // referenced by the Location field value,
338
343
  // even if the specific status code is not understood.
344
+
345
+ // If the response is not a redirect; return it as-is
339
346
  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
- }
347
+ if (!location || this._options.followRedirects === false ||
348
+ statusCode < 300 || statusCode >= 400) {
349
+ response.responseUrl = this._currentUrl;
350
+ response.redirects = this._redirects;
351
+ this.emit("response", response);
353
352
 
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
- }
353
+ // Clean up
354
+ this._requestBodyBuffers = [];
355
+ return;
356
+ }
369
357
 
370
- // Drop the Host header, as the redirect might lead to a different host
371
- var currentHostHeader = removeMatchingHeaders(/^host$/i, this._options.headers);
358
+ // The response is a redirect, so abort the current request
359
+ abortRequest(this._currentRequest);
360
+ // Discard the remainder of the response to avoid waiting for data
361
+ response.destroy();
372
362
 
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 }));
363
+ // RFC7231§6.4: A client SHOULD detect and intervene
364
+ // in cyclical redirections (i.e., "infinite" redirection loops).
365
+ if (++this._redirectCount > this._options.maxRedirects) {
366
+ this.emit("error", new TooManyRedirectsError());
367
+ return;
368
+ }
378
369
 
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
- }
370
+ // Store the request headers if applicable
371
+ var requestHeaders;
372
+ var beforeRedirect = this._options.beforeRedirect;
373
+ if (beforeRedirect) {
374
+ requestHeaders = Object.assign({
375
+ // The Host header was set by nativeProtocol.request
376
+ Host: response.req.getHeader("host"),
377
+ }, this._options.headers);
378
+ }
379
+
380
+ // RFC7231§6.4: Automatic redirection needs to done with
381
+ // care for methods not known to be safe, […]
382
+ // RFC7231§6.4.2–3: For historical reasons, a user agent MAY change
383
+ // the request method from POST to GET for the subsequent request.
384
+ var method = this._options.method;
385
+ if ((statusCode === 301 || statusCode === 302) && this._options.method === "POST" ||
386
+ // RFC7231§6.4.4: The 303 (See Other) status code indicates that
387
+ // the server is redirecting the user agent to a different resource […]
388
+ // A user agent can perform a retrieval request targeting that URI
389
+ // (a GET or HEAD request if using HTTP) […]
390
+ (statusCode === 303) && !/^(?:GET|HEAD)$/.test(this._options.method)) {
391
+ this._options.method = "GET";
392
+ // Drop a possible entity and headers related to it
393
+ this._requestBodyBuffers = [];
394
+ removeMatchingHeaders(/^content-/i, this._options.headers);
395
+ }
388
396
 
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);
397
+ // Drop the Host header, as the redirect might lead to a different host
398
+ var currentHostHeader = removeMatchingHeaders(/^host$/i, this._options.headers);
394
399
 
395
- // Drop confidential headers when redirecting to another scheme:domain
396
- if (redirectUrlParts.protocol !== currentUrlParts.protocol ||
397
- !isSameOrSubdomain(redirectUrlParts.host, currentHost)) {
398
- removeMatchingHeaders(/^(?:authorization|cookie)$/i, this._options.headers);
399
- }
400
+ // If the redirect is relative, carry over the host of the last request
401
+ var currentUrlParts = url.parse(this._currentUrl);
402
+ var currentHost = currentHostHeader || currentUrlParts.host;
403
+ var currentUrl = /^\w+:/.test(location) ? this._currentUrl :
404
+ url.format(Object.assign(currentUrlParts, { host: currentHost }));
400
405
 
401
- // Evaluate the beforeRedirect callback
402
- if (typeof this._options.beforeRedirect === "function") {
403
- var responseDetails = { headers: response.headers };
404
- try {
405
- this._options.beforeRedirect.call(null, this._options, responseDetails);
406
- }
407
- catch (err) {
408
- this.emit("error", err);
409
- return;
410
- }
411
- this._sanitizeOptions(this._options);
412
- }
406
+ // Determine the URL of the redirection
407
+ var redirectUrl;
408
+ try {
409
+ redirectUrl = url.resolve(currentUrl, location);
410
+ }
411
+ catch (cause) {
412
+ this.emit("error", new RedirectionError(cause));
413
+ return;
414
+ }
415
+
416
+ // Create the redirected request
417
+ debug("redirecting to", redirectUrl);
418
+ this._isRedirect = true;
419
+ var redirectUrlParts = url.parse(redirectUrl);
420
+ Object.assign(this._options, redirectUrlParts);
413
421
 
414
- // Perform the redirected request
422
+ // Drop confidential headers when redirecting to a less secure protocol
423
+ // or to a different domain that is not a superdomain
424
+ if (redirectUrlParts.protocol !== currentUrlParts.protocol &&
425
+ redirectUrlParts.protocol !== "https:" ||
426
+ redirectUrlParts.host !== currentHost &&
427
+ !isSubdomain(redirectUrlParts.host, currentHost)) {
428
+ removeMatchingHeaders(/^(?:authorization|cookie)$/i, this._options.headers);
429
+ }
430
+
431
+ // Evaluate the beforeRedirect callback
432
+ if (typeof beforeRedirect === "function") {
433
+ var responseDetails = {
434
+ headers: response.headers,
435
+ statusCode: statusCode,
436
+ };
437
+ var requestDetails = {
438
+ url: currentUrl,
439
+ method: method,
440
+ headers: requestHeaders,
441
+ };
415
442
  try {
416
- this._performRequest();
443
+ beforeRedirect(this._options, responseDetails, requestDetails);
417
444
  }
418
- catch (cause) {
419
- this.emit("error", new RedirectionError(cause));
445
+ catch (err) {
446
+ this.emit("error", err);
447
+ return;
420
448
  }
449
+ this._sanitizeOptions(this._options);
421
450
  }
422
- else {
423
- // The response is not a redirect; return it as-is
424
- response.responseUrl = this._currentUrl;
425
- response.redirects = this._redirects;
426
- this.emit("response", response);
427
451
 
428
- // Clean up
429
- this._requestBodyBuffers = [];
452
+ // Perform the redirected request
453
+ try {
454
+ this._performRequest();
455
+ }
456
+ catch (cause) {
457
+ this.emit("error", new RedirectionError(cause));
430
458
  }
431
459
  };
432
460
 
@@ -553,17 +581,14 @@ function createErrorType(code, defaultMessage) {
553
581
  }
554
582
 
555
583
  function abortRequest(request) {
556
- for (var e = 0; e < events.length; e++) {
557
- request.removeListener(events[e], eventHandlers[events[e]]);
584
+ for (var event of events) {
585
+ request.removeListener(event, eventHandlers[event]);
558
586
  }
559
587
  request.on("error", noop);
560
588
  request.abort();
561
589
  }
562
590
 
563
- function isSameOrSubdomain(subdomain, domain) {
564
- if (subdomain === domain) {
565
- return true;
566
- }
591
+ function isSubdomain(subdomain, domain) {
567
592
  const dot = subdomain.length - domain.length - 1;
568
593
  return dot > 0 && subdomain[dot] === "." && subdomain.endsWith(domain);
569
594
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "follow-redirects",
3
- "version": "1.14.8",
3
+ "version": "1.15.1",
4
4
  "description": "HTTP and HTTPS modules that follow redirects.",
5
5
  "license": "MIT",
6
6
  "main": "index.js",