follow-redirects 1.14.9 → 1.15.2

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 +83 -36
  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
@@ -15,6 +15,11 @@ events.forEach(function (event) {
15
15
  };
16
16
  });
17
17
 
18
+ var InvalidUrlError = createErrorType(
19
+ "ERR_INVALID_URL",
20
+ "Invalid URL",
21
+ TypeError
22
+ );
18
23
  // Error types with codes
19
24
  var RedirectionError = createErrorType(
20
25
  "ERR_FR_REDIRECTION_FAILURE",
@@ -75,10 +80,10 @@ RedirectableRequest.prototype.write = function (data, encoding, callback) {
75
80
  }
76
81
 
77
82
  // Validate input and shift parameters if necessary
78
- if (!(typeof data === "string" || typeof data === "object" && ("length" in data))) {
83
+ if (!isString(data) && !isBuffer(data)) {
79
84
  throw new TypeError("data should be a string, Buffer or Uint8Array");
80
85
  }
81
- if (typeof encoding === "function") {
86
+ if (isFunction(encoding)) {
82
87
  callback = encoding;
83
88
  encoding = null;
84
89
  }
@@ -107,11 +112,11 @@ RedirectableRequest.prototype.write = function (data, encoding, callback) {
107
112
  // Ends the current native request
108
113
  RedirectableRequest.prototype.end = function (data, encoding, callback) {
109
114
  // Shift parameters if necessary
110
- if (typeof data === "function") {
115
+ if (isFunction(data)) {
111
116
  callback = data;
112
117
  data = encoding = null;
113
118
  }
114
- else if (typeof encoding === "function") {
119
+ else if (isFunction(encoding)) {
115
120
  callback = encoding;
116
121
  encoding = null;
117
122
  }
@@ -270,25 +275,30 @@ RedirectableRequest.prototype._performRequest = function () {
270
275
  // If specified, use the agent corresponding to the protocol
271
276
  // (HTTP and HTTPS use different types of agents)
272
277
  if (this._options.agents) {
273
- var scheme = protocol.substr(0, protocol.length - 1);
278
+ var scheme = protocol.slice(0, -1);
274
279
  this._options.agent = this._options.agents[scheme];
275
280
  }
276
281
 
277
- // Create the native request
282
+ // Create the native request and set up its event handlers
278
283
  var request = this._currentRequest =
279
284
  nativeProtocol.request(this._options, this._onNativeResponse);
280
- this._currentUrl = url.format(this._options);
281
-
282
- // Set up event handlers
283
285
  request._redirectable = this;
284
- for (var e = 0; e < events.length; e++) {
285
- request.on(events[e], eventHandlers[events[e]]);
286
+ for (var event of events) {
287
+ request.on(event, eventHandlers[event]);
286
288
  }
287
289
 
290
+ // RFC7230§5.3.1: When making a request directly to an origin server, […]
291
+ // a client MUST send only the absolute path […] as the request-target.
292
+ this._currentUrl = /^\//.test(this._options.path) ?
293
+ url.format(this._options) :
294
+ // When making a request to a proxy, […]
295
+ // a client MUST send the target URI in absolute-form […].
296
+ this._options.path;
297
+
288
298
  // End a redirected request
289
299
  // (The first request must be ended explicitly with RedirectableRequest#end)
290
300
  if (this._isRedirect) {
291
- // Write the request entity and end.
301
+ // Write the request entity and end
292
302
  var i = 0;
293
303
  var self = this;
294
304
  var buffers = this._requestBodyBuffers;
@@ -362,10 +372,21 @@ RedirectableRequest.prototype._processResponse = function (response) {
362
372
  return;
363
373
  }
364
374
 
375
+ // Store the request headers if applicable
376
+ var requestHeaders;
377
+ var beforeRedirect = this._options.beforeRedirect;
378
+ if (beforeRedirect) {
379
+ requestHeaders = Object.assign({
380
+ // The Host header was set by nativeProtocol.request
381
+ Host: response.req.getHeader("host"),
382
+ }, this._options.headers);
383
+ }
384
+
365
385
  // RFC7231§6.4: Automatic redirection needs to done with
366
386
  // care for methods not known to be safe, […]
367
387
  // RFC7231§6.4.2–3: For historical reasons, a user agent MAY change
368
388
  // the request method from POST to GET for the subsequent request.
389
+ var method = this._options.method;
369
390
  if ((statusCode === 301 || statusCode === 302) && this._options.method === "POST" ||
370
391
  // RFC7231§6.4.4: The 303 (See Other) status code indicates that
371
392
  // the server is redirecting the user agent to a different resource […]
@@ -393,7 +414,7 @@ RedirectableRequest.prototype._processResponse = function (response) {
393
414
  redirectUrl = url.resolve(currentUrl, location);
394
415
  }
395
416
  catch (cause) {
396
- this.emit("error", new RedirectionError(cause));
417
+ this.emit("error", new RedirectionError({ cause: cause }));
397
418
  return;
398
419
  }
399
420
 
@@ -413,10 +434,18 @@ RedirectableRequest.prototype._processResponse = function (response) {
413
434
  }
414
435
 
415
436
  // Evaluate the beforeRedirect callback
416
- if (typeof this._options.beforeRedirect === "function") {
417
- var responseDetails = { headers: response.headers };
437
+ if (isFunction(beforeRedirect)) {
438
+ var responseDetails = {
439
+ headers: response.headers,
440
+ statusCode: statusCode,
441
+ };
442
+ var requestDetails = {
443
+ url: currentUrl,
444
+ method: method,
445
+ headers: requestHeaders,
446
+ };
418
447
  try {
419
- this._options.beforeRedirect.call(null, this._options, responseDetails);
448
+ beforeRedirect(this._options, responseDetails, requestDetails);
420
449
  }
421
450
  catch (err) {
422
451
  this.emit("error", err);
@@ -430,7 +459,7 @@ RedirectableRequest.prototype._processResponse = function (response) {
430
459
  this._performRequest();
431
460
  }
432
461
  catch (cause) {
433
- this.emit("error", new RedirectionError(cause));
462
+ this.emit("error", new RedirectionError({ cause: cause }));
434
463
  }
435
464
  };
436
465
 
@@ -452,15 +481,19 @@ function wrap(protocols) {
452
481
  // Executes a request, following redirects
453
482
  function request(input, options, callback) {
454
483
  // Parse parameters
455
- if (typeof input === "string") {
456
- var urlStr = input;
484
+ if (isString(input)) {
485
+ var parsed;
457
486
  try {
458
- input = urlToOptions(new URL(urlStr));
487
+ parsed = urlToOptions(new URL(input));
459
488
  }
460
489
  catch (err) {
461
490
  /* istanbul ignore next */
462
- input = url.parse(urlStr);
491
+ parsed = url.parse(input);
492
+ }
493
+ if (!isString(parsed.protocol)) {
494
+ throw new InvalidUrlError({ input });
463
495
  }
496
+ input = parsed;
464
497
  }
465
498
  else if (URL && (input instanceof URL)) {
466
499
  input = urlToOptions(input);
@@ -470,7 +503,7 @@ function wrap(protocols) {
470
503
  options = input;
471
504
  input = { protocol: protocol };
472
505
  }
473
- if (typeof options === "function") {
506
+ if (isFunction(options)) {
474
507
  callback = options;
475
508
  options = null;
476
509
  }
@@ -481,6 +514,9 @@ function wrap(protocols) {
481
514
  maxBodyLength: exports.maxBodyLength,
482
515
  }, input, options);
483
516
  options.nativeProtocols = nativeProtocols;
517
+ if (!isString(options.host) && !isString(options.hostname)) {
518
+ options.hostname = "::1";
519
+ }
484
520
 
485
521
  assert.equal(options.protocol, protocol, "protocol mismatch");
486
522
  debug("options", options);
@@ -538,37 +574,48 @@ function removeMatchingHeaders(regex, headers) {
538
574
  undefined : String(lastValue).trim();
539
575
  }
540
576
 
541
- function createErrorType(code, defaultMessage) {
542
- function CustomError(cause) {
577
+ function createErrorType(code, message, baseClass) {
578
+ // Create constructor
579
+ function CustomError(properties) {
543
580
  Error.captureStackTrace(this, this.constructor);
544
- if (!cause) {
545
- this.message = defaultMessage;
546
- }
547
- else {
548
- this.message = defaultMessage + ": " + cause.message;
549
- this.cause = cause;
550
- }
581
+ Object.assign(this, properties || {});
582
+ this.code = code;
583
+ this.message = this.cause ? message + ": " + this.cause.message : message;
551
584
  }
552
- CustomError.prototype = new Error();
585
+
586
+ // Attach constructor and set default properties
587
+ CustomError.prototype = new (baseClass || Error)();
553
588
  CustomError.prototype.constructor = CustomError;
554
589
  CustomError.prototype.name = "Error [" + code + "]";
555
- CustomError.prototype.code = code;
556
590
  return CustomError;
557
591
  }
558
592
 
559
593
  function abortRequest(request) {
560
- for (var e = 0; e < events.length; e++) {
561
- request.removeListener(events[e], eventHandlers[events[e]]);
594
+ for (var event of events) {
595
+ request.removeListener(event, eventHandlers[event]);
562
596
  }
563
597
  request.on("error", noop);
564
598
  request.abort();
565
599
  }
566
600
 
567
601
  function isSubdomain(subdomain, domain) {
568
- const dot = subdomain.length - domain.length - 1;
602
+ assert(isString(subdomain) && isString(domain));
603
+ var dot = subdomain.length - domain.length - 1;
569
604
  return dot > 0 && subdomain[dot] === "." && subdomain.endsWith(domain);
570
605
  }
571
606
 
607
+ function isString(value) {
608
+ return typeof value === "string" || value instanceof String;
609
+ }
610
+
611
+ function isFunction(value) {
612
+ return typeof value === "function";
613
+ }
614
+
615
+ function isBuffer(value) {
616
+ return typeof value === "object" && ("length" in value);
617
+ }
618
+
572
619
  // Exports
573
620
  module.exports = wrap({ http: http, https: https });
574
621
  module.exports.wrap = wrap;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "follow-redirects",
3
- "version": "1.14.9",
3
+ "version": "1.15.2",
4
4
  "description": "HTTP and HTTPS modules that follow redirects.",
5
5
  "license": "MIT",
6
6
  "main": "index.js",