Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

follow-redirects

Package Overview
Dependencies
Maintainers
3
Versions
68
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

follow-redirects - npm Package Compare versions

Comparing version 0.3.0 to 1.0.0

272

index.js

@@ -10,161 +10,177 @@ 'use strict';

var nativeProtocols = {'http:': http, 'https:': https};
var publicApi = module.exports = {
var schemes = {};
var exports = module.exports = {
maxRedirects: 21
};
// RFC7231§4.2.1: Of the request methods defined by this specification,
// the GET, HEAD, OPTIONS, and TRACE methods are defined to be safe.
var safeMethods = {GET: true, HEAD: true, OPTIONS: true, TRACE: true};
// Wrapper around the native request
function RequestProxy() {
// Create handlers that pass events from native requests
var eventHandlers = Object.create(null);
['abort', 'aborted', 'error'].forEach(function (event) {
eventHandlers[event] = function (arg) {
this._redirectable.emit(event, arg);
};
});
// An HTTP(S) request that can be redirected
function RedirectableRequest(options, responseCallback) {
// Initialize the request
Writable.call(this);
}
RequestProxy.prototype = Object.create(Writable.prototype);
this._options = options;
this._redirectCount = 0;
RequestProxy.prototype.abort = function () {
this._request.abort();
};
// Attach a callback if passed
if (responseCallback) {
this.on('response', responseCallback);
}
RequestProxy.prototype.end = function (data, encoding, callback) {
this._request.end(data, encoding, callback);
};
// React to responses of native requests
var self = this;
this._onNativeResponse = function (response) {
self._processResponse(response);
};
RequestProxy.prototype.flushHeaders = function () {
this._request.flushHeaders();
};
// Perform the first request
this._performRequest();
}
RedirectableRequest.prototype = Object.create(Writable.prototype);
RequestProxy.prototype.setNoDelay = function (noDelay) {
this._request.setNoDelay(noDelay);
};
// Executes the next native request (initial or redirect)
RedirectableRequest.prototype._performRequest = function () {
// If specified, use the agent corresponding to the protocol
// (HTTP and HTTPS use different types of agents)
var protocol = this._options.protocol;
if (this._options.agents) {
this._options.agent = this._options.agents[schemes[protocol]];
}
RequestProxy.prototype.setSocketKeepAlive = function (enable, initialDelay) {
this._request.setSocketKeepAlive(enable, initialDelay);
};
// Create the native request
var nativeProtocol = nativeProtocols[this._options.protocol];
var request = this._currentRequest =
nativeProtocol.request(this._options, this._onNativeResponse);
this._currentUrl = url.format(this._options);
RequestProxy.prototype.setTimeout = function (timeout, callback) {
this._request.setSocketKeepAlive(timeout, callback);
};
// Set up event handlers
request._redirectable = this;
for (var event in eventHandlers) {
if (event) {
request.on(event, eventHandlers[event]);
}
}
RequestProxy.prototype._write = function (chunk, encoding, callback) {
this._request.write(chunk, encoding, callback);
// The first request is explicitly ended in RedirectableRequest#end
if (this._currentResponse) {
request.end();
}
};
function execute(options, callback) {
var fetchedUrls = [];
var requestProxy = new RequestProxy();
if (callback) {
requestProxy.on('response', callback);
}
cb();
return requestProxy;
// Processes a response from the current native request
RedirectableRequest.prototype._processResponse = function (response) {
// RFC7231§6.4: The 3xx (Redirection) class of status code indicates
// that further action needs to be taken by the user agent in order to
// fulfill the request. If a Location header field is provided,
// the user agent MAY automatically redirect its request to the URI
// referenced by the Location field value,
// even if the specific status code is not understood.
var location = response.headers.location;
if (location && this._options.followRedirects !== false &&
response.statusCode >= 300 && response.statusCode < 400) {
// RFC7231§6.4: A client SHOULD detect and intervene
// in cyclical redirections (i.e., "infinite" redirection loops).
if (++this._redirectCount > this._options.maxRedirects) {
return this.emit('error', new Error('Max redirects exceeded.'));
}
function cb(res) {
// skip the redirection logic on the first call.
if (res) {
var fetchedUrl = url.format(options);
fetchedUrls.unshift(fetchedUrl);
if (!isRedirect(res)) {
res.fetchedUrls = fetchedUrls;
requestProxy.emit('response', res);
return;
// RFC7231§6.4.7: The 307 (Temporary Redirect) status code indicates
// that the target resource resides temporarily under a different URI
// and the user agent MUST NOT change the request method
// if it performs an automatic redirection to that URI.
if (response.statusCode !== 307) {
// RFC7231§6.4: Automatic redirection needs to done with
// care for methods not known to be safe […],
// since the user might not wish to redirect an unsafe request.
if (!(this._options.method in safeMethods)) {
this._options.method = 'GET';
}
// need to use url.resolve() in case location is a relative URL
var redirectUrl = url.resolve(fetchedUrl, res.headers.location);
debug('redirecting to', redirectUrl);
extend(options, url.parse(redirectUrl));
}
if (fetchedUrls.length > options.maxRedirects) {
var err = new Error('Max redirects exceeded.');
requestProxy.emit('error', err);
return;
}
options.nativeProtocol = nativeProtocols[options.protocol];
options.defaultRequest = defaultMakeRequest;
var req = (options.makeRequest || defaultMakeRequest)(options, cb, res);
requestProxy._request = req;
mirrorEvent(req, 'abort');
mirrorEvent(req, 'aborted');
mirrorEvent(req, 'error');
return req;
// Perform the redirected request
var redirectUrl = url.resolve(this._currentUrl, location);
debug('redirecting to', redirectUrl);
Object.assign(this._options, url.parse(redirectUrl));
this._currentResponse = response;
this._performRequest();
} else {
// The response is not a redirect; return it as-is
response.responseUrl = this._currentUrl;
return this.emit('response', response);
}
};
function defaultMakeRequest(options, cb, res) {
if (res && res.statusCode !== 307) {
// This is a redirect, so use only GET methods, except for status 307,
// which must honor the previous request method.
options.method = 'GET';
}
// Aborts the current native request
RedirectableRequest.prototype.abort = function () {
this._currentRequest.abort();
};
var req = options.nativeProtocol.request(options, cb);
// Ends the current native request
RedirectableRequest.prototype.end = function (data, encoding, callback) {
this._currentRequest.end(data, encoding, callback);
};
if (res) {
// We leave the user to call `end` on the first request
req.end();
}
// Flushes the headers of the current native request
RedirectableRequest.prototype.flushHeaders = function () {
this._currentRequest.flushHeaders();
};
return req;
}
// Sets the noDelay option of the current native request
RedirectableRequest.prototype.setNoDelay = function (noDelay) {
this._currentRequest.setNoDelay(noDelay);
};
// send events through the proxy
function mirrorEvent(req, event) {
req.on(event, function (arg) {
requestProxy.emit(event, arg);
});
}
}
// Sets the socketKeepAlive option of the current native request
RedirectableRequest.prototype.setSocketKeepAlive = function (enable, initialDelay) {
this._currentRequest.setSocketKeepAlive(enable, initialDelay);
};
// returns a safe copy of options (or a parsed url object if options was a string).
// validates that the supplied callback is a function
function parseOptions(options, wrappedProtocol) {
if (typeof options === 'string') {
options = url.parse(options);
options.maxRedirects = publicApi.maxRedirects;
} else {
options = extend({
maxRedirects: publicApi.maxRedirects,
protocol: wrappedProtocol
}, options);
}
assert.equal(options.protocol, wrappedProtocol, 'protocol mismatch');
// Sets the timeout option of the current native request
RedirectableRequest.prototype.setTimeout = function (timeout, callback) {
this._currentRequest.setTimeout(timeout, callback);
};
debug('options', options);
return options;
}
// Writes buffered data to the current native request
RedirectableRequest.prototype._write = function (chunk, encoding, callback) {
this._currentRequest.write(chunk, encoding, callback);
};
// copies source's own properties onto destination and returns destination
function extend(destination, source) {
var keys = Object.keys(source);
for (var i = 0; i < keys.length; i++) {
var key = keys[i];
destination[key] = source[key];
}
return destination;
}
// Export a redirecting wrapper for each native protocol
Object.keys(nativeProtocols).forEach(function (protocol) {
var scheme = schemes[protocol] = protocol.substr(0, protocol.length - 1);
var nativeProtocol = nativeProtocols[protocol];
var wrappedProtocol = exports[scheme] = Object.create(nativeProtocol);
// to redirect the result must have
// a statusCode between 300-399
// and a `Location` header
function isRedirect(res) {
return (res.statusCode >= 300 && res.statusCode <= 399 &&
'location' in res.headers);
}
// Executes an HTTP request, following redirects
wrappedProtocol.request = function (options, callback) {
if (typeof options === 'string') {
options = url.parse(options);
options.maxRedirects = exports.maxRedirects;
} else {
options = Object.assign({
maxRedirects: exports.maxRedirects,
protocol: protocol
}, options);
}
assert.equal(options.protocol, protocol, 'protocol mismatch');
debug('options', options);
Object.keys(nativeProtocols).forEach(function (wrappedProtocol) {
var scheme = wrappedProtocol.substr(0, wrappedProtocol.length - 1);
var nativeProtocol = nativeProtocols[wrappedProtocol];
var protocol = publicApi[scheme] = Object.create(nativeProtocol);
protocol.request = function (options, callback) {
return execute(parseOptions(options, wrappedProtocol), callback);
return new RedirectableRequest(options, callback);
};
// see https://github.com/joyent/node/blob/master/lib/http.js#L1623
protocol.get = function (options, callback) {
var req = execute(parseOptions(options, wrappedProtocol), callback);
req.end();
return req;
// Executes a GET request, following redirects
wrappedProtocol.get = function (options, callback) {
var request = wrappedProtocol.request(options, callback);
request.end();
return request;
};
});
{
"name": "follow-redirects",
"version": "0.3.0",
"version": "1.0.0",
"description": "HTTP and HTTPS modules that follow redirects.",

@@ -32,3 +32,4 @@ "main": "index.js",

"contributors": [
"James Talmage <james@talmage.io>"
"James Talmage <james@talmage.io>",
"Ruben Verborgh <ruben@verborgh.org> (https://ruben.verborgh.org/)"
],

@@ -51,3 +52,2 @@ "files": [

"nyc": "^8.3.1",
"semver": "^5.3.0",
"xo": "^0.17.0"

@@ -54,0 +54,0 @@ },

## Follow Redirects
Drop in replacement for Nodes `http` and `https` that automatically follows redirects.
Drop-in replacement for Nodes `http` and `https` that automatically follows redirects.

@@ -22,4 +22,4 @@ [![npm version](https://badge.fury.io/js/follow-redirects.svg)](https://www.npmjs.com/package/follow-redirects)

http.get('http://bit.ly/900913', function (res) {
res.on('data', function (chunk) {
http.get('http://bit.ly/900913', function (response) {
response.on('data', function (chunk) {
console.log(chunk);

@@ -32,32 +32,54 @@ });

By default the number of redirects is limited to 5, but you can modify that globally or per request.
You can inspect the final redirected URL through the `responseUrl` property on the `response`.
If no redirection happened, `responseUrl` is the original request URL.
```javascript
require('follow-redirects').maxRedirects = 10; // Has global affect (be careful!)
https.request({
host: 'bitly.com',
path: '/UHfDGO',
maxRedirects: 3 // per request setting
}, function (res) {/* ... */});
}, function (response) {
console.log(response.responseUrl);
// 'http://duckduckgo.com/robots.txt'
});
```
You can inspect the redirection chain from the `fetchedUrls` array on the `response`.
The array is populated in reverse order, so the original url you requested will be the
last element, while the final redirection point will be at index 0.
## Options
### Global options
Global options are set directly on the `follow-redirects` module:
```javascript
https.request({
host: 'bitly.com',
path: '/UHfDGO',
}, function (res) {
console.log(res.fetchedUrls);
// [ 'http://duckduckgo.com/robots.txt', 'http://bitly.com/UHfDGO' ]
});
var followRedirects = require('follow-redirects');
followRedirects.maxRedirects = 10;
```
The following global options are supported:
- `maxRedirects` (default: `21`) – sets the maximum number of allowed redirects; if exceeded, an error will be emitted.
### Per-request options
Per-request options are set by passing an `options` object:
```javascript
var url = require('url');
var followRedirects = require('follow-redirects');
var options = url.parse('http://bit.ly/900913');
options.maxRedirects = 10;
http.request(options);
```
In addition to the [standard HTTP](https://nodejs.org/api/http.html#http_http_request_options_callback) and [HTTPS options](https://nodejs.org/api/https.html#https_https_request_options_callback),
the following per-request options are supported:
- `followRedirects` (default: `true`) – whether redirects should be followed.
- `maxRedirects` (default: `21`) – sets the maximum number of allowed redirects; if exceeded, an error will be emitted.
- `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() }`
## Browserify Usage
Due to the way `XMLHttpRequest` works, the `browserify` versions of `http` and `https` already follow redirects.
If you are *only* targetting the browser, then this library has little value for you. If you want to write cross
If you are *only* targeting the browser, then this library has little value for you. If you want to write cross
platform code for node and the browser, `follow-redirects` provides a great solution for making the native node

@@ -109,8 +131,8 @@ modules behave the same as they do in browserified builds in the browser. To avoid bundling unnecessary code

Olivier Lalonde (olalonde@gmail.com)
- Olivier Lalonde (olalonde@gmail.com)
- James Talmage (james@talmage.io)
- [Ruben Verborgh](https://ruben.verborgh.org/)
James Talmage (james@talmage.io)
## License
MIT: [http://olalonde.mit-license.org](http://olalonde.mit-license.org)
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc