Skip to content
This repository has been archived by the owner on Mar 8, 2023. It is now read-only.

Commit

Permalink
redirect: rewrite as class
Browse files Browse the repository at this point in the history
  • Loading branch information
skenqbx committed Apr 24, 2015
1 parent 041bda6 commit 914a0d5
Show file tree
Hide file tree
Showing 4 changed files with 164 additions and 106 deletions.
8 changes: 4 additions & 4 deletions README.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -169,10 +169,10 @@ firefox coverage/lcov-report/index.html
### Coverage

```
Statements : 94.41% ( 608/644 )
Branches : 87.40% ( 333/381 )
Functions : 97.56% ( 80/82 )
Lines : 94.41% ( 608/644 )
Statements : 94.79% ( 637/672 )
Branches : 88.83% ( 342/385 )
Functions : 97.70% ( 85/87 )
Lines : 94.79% ( 637/672 )
```

[back to top](#table-of-contents)
2 changes: 1 addition & 1 deletion doc/plugins.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ A configurable redirect mechanism.

**request options**

- `{Object} redirect`
- `{Object|boolean} redirect` Set to `false` to disable the plugin
- `{number} limit` See `options`
- `{boolean} sameHost` See `options`
- `{boolean} allowUpgrade` See `options`
Expand Down
244 changes: 143 additions & 101 deletions lib/plugins/redirect.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,136 +3,178 @@ var parseURL = require('url').parse;
var path = require('path');


function redirectPlugin(rail, opt_options) {
opt_options = opt_options || {};

var pluginOptions = {
limit: opt_options.limit || 1,
codes: opt_options.codes || [301, 302, 308],
sameHost: opt_options.sameHost || false,
allowUpgrade: opt_options.allowUpgrade === false ? false : true,
allowDowngrade: opt_options.allowDowngrade || false
};


function interceptRequest(call, options, request) {
// send the request made in `interceptResponse` below
request.end();
call.emit('redirect', options);
function RedirectPlugin(rail, options) {
if (!(this instanceof RedirectPlugin)) {
return new RedirectPlugin(rail, options);
}
this._rail = rail;

this._interceptrequest = null;
this._interceptresponse = null;

function interceptResponse(call, options, response) {
var p, config, url, proto1, proto2;
var location = response.headers.location;
this.limit = options.limit || 1;
this.codes = options.codes || [301, 302, 308];
this.sameHost = options.sameHost || false;
this.allowUpgrade = options.allowUpgrade === false ? false : true;
this.allowDowngrade = options.allowDowngrade || false;

if (!location) {
return call.__emit('response', response);
this._setup();
}
module.exports = RedirectPlugin;


RedirectPlugin.prototype._setup = function() {
var self = this;
var rail = this._rail;

rail.on('plugin-configure', function(call, options) {
self._configure(options);
});

rail.on('plugin-response', function(call, options, response) {
// check if request has been redirected
if (options.request.method === 'GET' &&
response.headers.location &&
self.codes.indexOf(response.statusCode) > -1) {

options.redirect = options.redirect || {};

if (options.redirect.limit > 0) {
--options.redirect.limit;
// intercept to give other plugins a chance to do their work
// as we are going to kill the response in the intercept
call.__intercept('response', self._interceptresponse);
} else {
call.emit('warn', 'redirect', 'blocked', 'limit reached');
}
}
});

// create a new request configuration on the stack
p = location.indexOf('://');
this._interceptrequest = function(call, options, request) {
self._interceptRequest(call, options, request);
};

if (p > 0) {
url = parseURL(location);
proto1 = options.proto;
proto2 = url.protocol.substr(0, url.protocol.length - 1);
this._interceptresponse = function(call, options, response) {
self._interceptResponse(call, options, response);
};
};

if (url.hostname !== options.request.host &&
(options.redirect.sameHost === true ||
pluginOptions.sameHost === true &&
options.redirect.sameHost !== false)) {
call.emit('warn', 'redirect', 'blocked', 'different host');
return call.__emit('response', response);

} else if (proto1 !== proto2) {
RedirectPlugin.prototype._interceptRequest = function(call, options, request) {
// send the request made in `interceptResponse` below
request.end();
call.emit('redirect', options);
};

if (proto2 === 'http' &&
(options.redirect.allowDowngrade === false ||
pluginOptions.allowDowngrade === false &&
options.redirect.allowDowngrade !== true)) {
call.emit('warn', 'redirect', 'blocked', 'protocol downgrade');
return call.__emit('response', response);

} else if (proto1 === 'http' &&
(options.redirect.allowUpgrade === false ||
pluginOptions.allowUpgrade === false &&
options.redirect.allowUpgrade !== true)) {
call.emit('warn', 'redirect', 'blocked', 'protocol upgrade');
return call.__emit('response', response);
}
RedirectPlugin.prototype._interceptResponse = function(call, options, response) {
var self = this;
var p, config, url, proto1, proto2;
var location = response.headers.location;

options.proto = proto2;
}
if (!location) {
return call.__emit('response', response);
}

config = call.__configure(options);
config.request.host = url.hostname;
config.request.port = url.port; // TODO: vaildate port
config.request.path = url.path;
// XXX: revert options to original proto
options.proto = proto1;
// create a new request configuration on the stack
p = location.indexOf('://');

} else {
config = call.__configure(options);
config.request.path = path.resolve(options.request.path, location);
}
if (p > 0) {
url = parseURL(location);
proto1 = options.proto;
proto2 = url.protocol.substr(0, url.protocol.length - 1);

function onEnd() {
var req;
if (url.hostname !== options.request.host &&
options.redirect.sameHost === true) {
call.emit('warn', 'redirect', 'blocked', 'different host');
return call.__emit('response', response);

call.__clear();
req = call.__request();
} else if (proto1 !== proto2) {

if (req && req !== true) {
call.__intercept('request', interceptRequest);
} else {
call.emit('warn', 'redirect', 'failed', 'could not create request');
call.__emit('response', response);
if (proto2 === 'http' &&
options.redirect.allowDowngrade === false) {
call.emit('warn', 'redirect', 'blocked', 'protocol downgrade');
return call.__emit('response', response);

} else if (proto1 === 'http' &&
options.redirect.allowUpgrade === false) {
call.emit('warn', 'redirect', 'blocked', 'protocol upgrade');
return call.__emit('response', response);
}

options.proto = proto2;
}

// check if buffer plugin already handled the response body
if (response.buffer !== undefined) {
onEnd();
config = call.__configure(options);
config.request.host = url.hostname;
config.request.port = url.port; // TODO: vaildate port
config.request.path = url.path;
// XXX: revert options to original proto
options.proto = proto1;

} else {
config = call.__configure(options);
config.request.path = path.resolve(options.request.path, location);
}

function onEnd() {
var req;

call.__clear();
req = call.__request();

if (req && req !== true) {
call.__intercept('request', self._interceptrequest);
} else {
// dump obsolete response
response.on('readable', function() {
// TODO: check & warn (a redirect shouldn't have a body)
response.read();
});

// send next request
response.once('end', onEnd);
call.emit('warn', 'redirect', 'failed', 'could not create request');
call.__emit('response', response);
}
}

// check if buffer plugin already handled the response body
if (response.buffer !== undefined) {
onEnd();

// on response
rail.on('plugin-response', function(call, options, response) {
} else {
// dump obsolete response
response.on('readable', function() {
// TODO: check & warn (a redirect shouldn't have a body)
response.read();
});

// check if request has been redirected
if (options.request.method === 'GET' &&
response.headers.location &&
pluginOptions.codes.indexOf(response.statusCode) > -1) {
// send next request
response.once('end', onEnd);
}
};

options.redirect = options.redirect || {};

// set initial redirect limit
if (options.redirect.limit === undefined) {
options.redirect.limit = pluginOptions.limit;
}
if (options.redirect.limit > 0) {
--options.redirect.limit;
// intercept to give other plugins a chance to do their work
// as we are going to kill the response in the intercept
call.__intercept('response', interceptResponse);
} else {
call.emit('warn', 'redirect', 'blocked', 'limit reached');
}
RedirectPlugin.prototype._configure = function(options) {
if (options.redirect) {
if (options.redirect.limit === undefined) {
options.redirect.limit = this.limit;
}
if (options.redirect.sameHost === undefined) {
options.redirect.sameHost = this.sameHost;
}
if (options.redirect.allowDowngrade === undefined) {
options.redirect.allowDowngrade = this.allowDowngrade;
}
if (options.redirect.allowUpgrade === undefined) {
options.redirect.allowUpgrade = this.allowUpgrade;
}
});

return pluginOptions;
}
module.exports = redirectPlugin;
} else if (options.redirect === false) {
options.redirect = {
limit: 0
};

} else {
options.redirect = {
limit: this.limit,
sameHost: this.sameHost,
allowDowngrade: this.allowDowngrade,
allowUpgrade: this.allowUpgrade
};
}
};
16 changes: 16 additions & 0 deletions test/test-http-redirect.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,22 @@ suite('http:redirect', function() {
});


test('configure', function() {
var options = {
redirect: {}
};
rail.plugins.redirect._configure(options);
assert.deepEqual(options,
{redirect: {limit: 1, sameHost: false, allowDowngrade: false, allowUpgrade: false}});

options = {
redirect: false
};
rail.plugins.redirect._configure(options);
assert.deepEqual(options, {redirect: {limit: 0}});
});


test('relative', function(done) {
var c = 0;
var r = [
Expand Down

0 comments on commit 914a0d5

Please sign in to comment.