Skip to content

Commit

Permalink
normalize route/path, encoded/decode dynamic path segments
Browse files Browse the repository at this point in the history
This allows handlers like "/foo/:bar" to match a path with a url encoded
segment such as "/foo/abc%Fdef" -> { params: { bar: "abc/def" } }

See related issue emberjs/ember.js#11497
  • Loading branch information
bantic committed May 4, 2016
1 parent 2a21d5e commit 391ec7b
Show file tree
Hide file tree
Showing 3 changed files with 475 additions and 15 deletions.
38 changes: 35 additions & 3 deletions lib/route-recognizer.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import map from './route-recognizer/dsl';
import Normalizer from './route-recognizer/normalizer';

var normalizeRoute = Normalizer.normalizeRoute;
var normalizePath = Normalizer.normalizePath;

var specials = [
'/', '.', '*', '+', '?', '|',
Expand Down Expand Up @@ -61,7 +65,11 @@ DynamicSegment.prototype = {
},

generate: function(params) {
return params[this.name];
if (RouteRecognizer.ENCODE_AND_DECODE_PATH_SEGMENTS) {
return encodeURIComponent(params[this.name]);
} else {
return params[this.name];
}
}
};

Expand Down Expand Up @@ -94,6 +102,7 @@ function parse(route, names, specificity) {
// also normalize.
if (route.charAt(0) === "/") { route = route.substr(1); }

route = normalizeRoute(route);
var segments = route.split("/");
var results = new Array(segments.length);

Expand Down Expand Up @@ -167,6 +176,7 @@ function State(charSpec) {
this.regex = undefined;
this.handlers = undefined;
this.specificity = undefined;
this.hasStar = false;
}

State.prototype = {
Expand Down Expand Up @@ -284,7 +294,16 @@ function findHandler(state, path, queryParams) {
var handler = handlers[i], names = handler.names, params = {};

for (var j=0; j<names.length; j++) {
params[names[j]] = captures[currentCapture++];
if (RouteRecognizer.ENCODE_AND_DECODE_PATH_SEGMENTS) {
// Never decode the star segment glob capture
if (state.hasStar && currentCapture === (captures.length - 1)) {
params[names[j]] = captures[currentCapture++];
} else {
params[names[j]] = decodeURIComponent(captures[currentCapture++]);
}
} else {
params[names[j]] = captures[currentCapture++];
}
}

result[i] = { handler: handler.handler, params: params, isDynamic: !!names.length };
Expand Down Expand Up @@ -340,6 +359,11 @@ RouteRecognizer.prototype = {
// Add a representation of the segment to the NFA and regex
currentState = segment.eachChar(currentState);
regex += segment.regex();

// Mark the state as having a starSegment if the last segment is a star segment
if (segment instanceof StarSegment && j === segments.length - 1) {
currentState.hasStar = true;
}
}
var handler = { handler: route.handler, names: names };
handlers[i] = handler;
Expand Down Expand Up @@ -478,7 +502,11 @@ RouteRecognizer.prototype = {
queryParams = this.parseQueryString(queryString);
}

path = decodeURI(path);
if (RouteRecognizer.ENCODE_AND_DECODE_PATH_SEGMENTS) {
path = normalizePath(path);
} else {
path = decodeURI(path);
}

if (path.charAt(0) !== "/") { path = "/" + path; }

Expand Down Expand Up @@ -517,4 +545,8 @@ RouteRecognizer.prototype.map = map;

RouteRecognizer.VERSION = 'VERSION_STRING_PLACEHOLDER';

// Set to false to opt-out of encoding and decoding path segments.
// See https://github.com/tildeio/route-recognizer/pull/55
RouteRecognizer.ENCODE_AND_DECODE_PATH_SEGMENTS = true;

export default RouteRecognizer;
40 changes: 40 additions & 0 deletions lib/route-recognizer/normalizer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// Matches all percent-encoded values like %3a
var percentEncodedValueRegex = /%[a-fA-F0-9]{2}/g;

// The percent-encoding for the percent "%" character
var percentEncodedPercent = "%25";

function toUpper(str) { return str.toUpperCase(); }

// Turns all percent-encoded values to upper case
// "%3a" -> "%3A"
function percentEncodedValuesToUpper(string) {
return string.replace(percentEncodedValueRegex, toUpper);
}

function decodeURIWithoutPercents(string) {
return string.split(percentEncodedPercent)
.map(decodeURI)
.join(percentEncodedPercent);
}

// Normalizes percent-encoded values to upper-case and decodes percent-encoded
// values that are not reserved (like unicode characters).
// Safe to call multiple times on the same path.
function normalizePath(path) {
return decodeURIWithoutPercents(percentEncodedValuesToUpper(path));
}

// Normalizes percent-encoded values to upper-case and decodes percent-encoded
// values that are not reserved (like unicode characters).
// Safe to call multiple times on the same route.
function normalizeRoute(route) {
return decodeURIWithoutPercents(percentEncodedValuesToUpper(route));
}

var Normalizer = {
normalizeRoute: normalizeRoute,
normalizePath: normalizePath
};

export default Normalizer;
Loading

0 comments on commit 391ec7b

Please sign in to comment.