Skip to content

Commit

Permalink
Closes #1291, Closes #1290
Browse files Browse the repository at this point in the history
  • Loading branch information
Eran Hammer committed Jan 4, 2014
1 parent e6443d9 commit 5a93f73
Show file tree
Hide file tree
Showing 9 changed files with 153 additions and 141 deletions.
3 changes: 0 additions & 3 deletions docs/Reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -232,9 +232,6 @@ When creating a server instance, the following options configure the server's be

- <a name="server.config.router"></a>`router` - controls how incoming request URIs are matched against the routing table:
- `isCaseSensitive` - determines whether the paths '/example' and '/EXAMPLE' are considered different resources. Defaults to `true`.
- `normalizeRequestPath` - determines whether request paths should be normalized prior to matching. Normalization percent-encodes reserved
characters, decodes unreserved characters, and capitalizes any percent encoded values. Useful when serving non-compliant HTTP clients.
Defaults to `false`.

- <a name="server.config.state"></a>`state` - HTTP state management (cookies) allows the server to store information on the client which is sent back to the server with every
request (as defined in [RFC 6265](https://tools.ietf.org/html/rfc6265)).
Expand Down
3 changes: 1 addition & 2 deletions lib/defaults.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,7 @@ exports.server = {
// Router

router: {
isCaseSensitive: true, // Case-seinsitive paths
normalizeRequestPath: false // Normalize incoming request path (Uppercase % encoding and decode non-reserved encoded characters)
isCaseSensitive: true // Case-seinsitive paths
},

// State
Expand Down
7 changes: 5 additions & 2 deletions lib/request.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ exports = module.exports = internals.Request = function (server, req, res, optio
this.setUrl = this._setUrl; // Decoration removed after 'onRequest'
this.setMethod = this._setMethod;

this.setUrl(req.url); // Sets: this.url, this.path, this.query
this._pathSegments = null;
this.setUrl(req.url); // Sets: this.url, this.path, this.query, this._pathSegments
this.setMethod(req.method); // Sets: this.method
this.headers = req.headers;

Expand Down Expand Up @@ -155,7 +156,7 @@ internals.Request.prototype._setUrl = function (url) {
this.path = this.url.pathname; // pathname excludes query

if (this.path &&
this.server.settings.router.normalizeRequestPath) {
this.path.indexOf('%') !== -1) {

// Uppercase %encoded values

Expand All @@ -176,6 +177,8 @@ internals.Request.prototype._setUrl = function (url) {

this.path = decoded;
}

this._pathSegments = this.path.split('/');
};


Expand Down
2 changes: 1 addition & 1 deletion lib/response/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ exports.send = function (request, callback) {
var response = new Plain(error.payload, request);
response.code(error.statusCode);
Utils.merge(response.headers, error.headers);
request._setResponse(response);
request.response = response; // Not using request._setResponse() to avoid double log

internals.setup(request, function (err) {

Expand Down
144 changes: 61 additions & 83 deletions lib/route.js
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,7 @@ internals.Route.prototype._parsePath = function () {
var segments = this.settings.path.split('/');
var params = {};
var fingers = [];
var isCaseSensitive = this.server.settings.router.isCaseSensitive;

for (var i = 1, il = segments.length; i < il; ++i) { // Skip first empty segment
var segment = segments[i];
Expand Down Expand Up @@ -281,7 +282,7 @@ internals.Route.prototype._parsePath = function () {
segmentMeta.mixed = true;
segmentMeta.pre = pre;
segmentMeta.post = post;
segmentMeta.extract = new RegExp('^' + Utils.escapeRegex(pre) + '(.' + (isEmptyOk ? '*' : '+') + ')' + Utils.escapeRegex(post) + '$');
segmentMeta.extract = new RegExp('^' + Utils.escapeRegex(pre) + '(.' + (isEmptyOk ? '*' : '+') + ')' + Utils.escapeRegex(post) + '$', (!isCaseSensitive ? 'i' : ''));
}

this._segments.push(segmentMeta);
Expand All @@ -292,7 +293,7 @@ internals.Route.prototype._parsePath = function () {
// Literal

if (segment) {
segment = this.server.settings.router.isCaseSensitive ? segment : segment.toLowerCase();
segment = isCaseSensitive ? segment : segment.toLowerCase();
path += '/' + segment;
fingers.push(segment);
this._segments.push({ literal: segment });
Expand All @@ -311,116 +312,93 @@ internals.Route.prototype._parsePath = function () {
};


internals.Route.prototype.match = function (request) {

var params = {};
var paramsArray = [];

var match = this._test(request.path, params, paramsArray);
if (match) {
request.params = params;
request._paramsArray = paramsArray;
}
internals.Route.prototype.test = function (path) {

return match;
return this.match({ path: path, _pathSegments: path.split('/') });
};


internals.setParam = function (name, value, params, paramsArray, isEmptyOk) {

var isValid = (isEmptyOk || value);
internals.Route.prototype.match = function (request) {

if (isValid &&
params) {
// Literal comparison

var decoded = internals.decodeURIComponent(value || '');
if (decoded === null) {
isValid = false;
}
else {
params[name] = decoded;
paramsArray.push(decoded);
}
if (!this.params.length) {
return (this.path === (this.server.settings.router.isCaseSensitive ? request.path : request.path.toLowerCase()));
}

return isValid;
};
// Mismatching segment count

var pl = request._pathSegments.length - 1;
var sl = this._segments.length;
var last = this._segments[sl - 1];

internals.decodeURIComponent = function (value) {
if (pl !== sl &&
(pl !== sl - 1 || (!last.isEmptyOk && !last.isWildcard)) &&
(pl < sl || !last.isWildcard)) {

try {
return decodeURIComponent(value);
return false;
}
catch (err) {
return null;
}
};

// Parameter matching

internals.Route.prototype._test = function (path, params, paramsArray) {
var params = {};
var paramsArray = [];

var match = true;

if (!this.params.length) {

// Literal path

match = (this.path === (this.server.settings.router.isCaseSensitive ? path : path.toLowerCase()));
}
else {

// Parameterized path

var paths = path.split('/');
var pl = paths.length - 1;
var sl = this._segments.length;
var lastSegment = this._segments[sl - 1];

if (pl === sl ||
(pl === sl - 1 && (lastSegment.isEmptyOk || lastSegment.isWildcard)) ||
(pl >= sl && lastSegment.isWildcard)) {

for (var i = 0; match && i < sl; ++i) {
var segment = this._segments[i];
if (segment.isWildcard) {
match = internals.setParam(segment.name, paths.slice(i + 1).join('/'), params, paramsArray, true);
}
else if (segment.count) {
match = internals.setParam(segment.name, paths.slice(i + 1, i + 1 + segment.count).join('/'), params, paramsArray);
i += (segment.count - 1);
}
else if (segment.name) {
if (segment.extract) {
var partial = paths[i + 1].match(segment.extract);
if (!partial) {
match = false;
}
else {
match = internals.setParam(segment.name, partial[1], params, paramsArray, segment.isEmptyOk);
}
}
else {
match = internals.setParam(segment.name, paths[i + 1], params, paramsArray, segment.isEmptyOk);
}
var pathSegments = request._pathSegments;
for (var i = 0; match && (match instanceof Error === false) && i < this._segments.length; ++i) {
var segment = this._segments[i];
if (segment.isWildcard) {
match = internals.setParam(segment.name, pathSegments.slice(i + 1).join('/'), params, paramsArray, true);
}
else if (segment.count) {
match = internals.setParam(segment.name, pathSegments.slice(i + 1, i + 1 + segment.count).join('/'), params, paramsArray);
i += (segment.count - 1);
}
else if (segment.name) {
if (segment.extract) {
var partial = pathSegments[i + 1].match(segment.extract);
if (!partial) {
match = false;
}
else {
match = (segment.literal === (this.server.settings.router.isCaseSensitive ? paths[i + 1] : paths[i + 1].toLowerCase()));
match = internals.setParam(segment.name, partial[1], params, paramsArray, segment.isEmptyOk);
}
}
else {
match = internals.setParam(segment.name, pathSegments[i + 1], params, paramsArray, segment.isEmptyOk);
}
}
else {
match = false;
match = (segment.literal === (this.server.settings.router.isCaseSensitive ? pathSegments[i + 1] : pathSegments[i + 1].toLowerCase()));
}
}

return match;
if (match === true) {
request.params = params;
request._paramsArray = paramsArray;
return true;
}

return false;
};


internals.Route.prototype.test = function (path) {
internals.setParam = function (name, value, params, paramsArray, isEmptyOk) {

return this._test(path, null, null);
if (!isEmptyOk && !value) {
return false;
}

try {
var decoded = decodeURIComponent(value || '');
params[name] = decoded;
paramsArray.push(decoded);
return true;
}
catch (err) {
return err;
}
};


Expand Down
3 changes: 1 addition & 2 deletions lib/schema.js
Original file line number Diff line number Diff line change
Expand Up @@ -101,8 +101,7 @@ internals.serverSchema = {
}),
plugins: Joi.object(),
router: Joi.object({
isCaseSensitive: Joi.boolean(),
normalizeRequestPath: Joi.boolean()
isCaseSensitive: Joi.boolean()
}).allow(false, true),
validation: Joi.object().allow(null),
state: Joi.object({
Expand Down
Loading

0 comments on commit 5a93f73

Please sign in to comment.