Skip to content

Commit

Permalink
Do not encode chars that are allowed in path segments
Browse files Browse the repository at this point in the history
During route generation, for dynamic segments, do not percent-encode
characters that, according to RFC 3986, are allowed in path segments.

RFC 3986 defines "sub-delims" (these chars: `! $ & ' ( ) * + , ; =`) and
specifies that they along with `:` and `@` are allowed in path segments
in URIs. See: https://tools.ietf.org/html/rfc3986#section-3.3

This commit changes RouteRecognizers generation code for dynamic
segments to explicitly avoid encoding those characters.

Fixes emberjs/ember.js#14094
  • Loading branch information
bantic committed Aug 19, 2016
1 parent 11f4c1c commit 23455b6
Show file tree
Hide file tree
Showing 3 changed files with 91 additions and 2 deletions.
3 changes: 2 additions & 1 deletion lib/route-recognizer.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import Normalizer from './route-recognizer/normalizer';

var normalizePath = Normalizer.normalizePath;
var normalizeSegment = Normalizer.normalizeSegment;
var encodePathSegment = Normalizer.encodePathSegment;

var specials = [
'/', '.', '*', '+', '?', '|',
Expand Down Expand Up @@ -66,7 +67,7 @@ DynamicSegment.prototype = {

generate: function(params) {
if (RouteRecognizer.ENCODE_AND_DECODE_PATH_SEGMENTS) {
return encodeURIComponent(params[this.name]);
return encodePathSegment(params[this.name]);
} else {
return params[this.name];
}
Expand Down
45 changes: 44 additions & 1 deletion lib/route-recognizer/normalizer.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,9 +74,52 @@ function normalizeSegment(segment) {
return decodeURIComponentExcept(segment, reservedHex);
}

function encodeURIComponentExcept(string, reservedChars) {
var pieces = [];
var separators = [];
var currentPiece = '';
var idx = 0;
while (idx < string.length) {
var char = string[idx];
if (reservedChars.indexOf(char) === -1) {
currentPiece += char;
} else {
pieces.push(currentPiece);
separators.push(char);
}
idx++;
}
if (currentPiece.length) {
pieces.push(currentPiece);
}

pieces = pieces.map(encodeURIComponent);
var ret = '';
while (pieces.length) {
ret += pieces.shift();
if (separators.length) {
ret += separators.shift();
}
}

return ret;
}

// Do not encode these characters when generating dynamic path segments
// See https://tools.ietf.org/html/rfc3986#section-2.3
var reservedSegmentChars = [
"!", "$", "&", "'", "(", ")", "*", "+", ",", ";", "=", // sub-delims
":", "@" // others explicitly allowed by RFC 3986
];
function encodePathSegment(segment) {
segment = '' + segment; // coerce to string
return encodeURIComponentExcept(segment, reservedSegmentChars);
}

var Normalizer = {
normalizeSegment: normalizeSegment,
normalizePath: normalizePath
normalizePath: normalizePath,
encodePathSegment: encodePathSegment
};

export default Normalizer;
45 changes: 45 additions & 0 deletions tests/recognizer-tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -796,6 +796,51 @@ encodedCharGenerationExpectations.forEach(function(expectation) {
});
});

// See: https://tools.ietf.org/html/rfc3986#section-2.3
test("Generating a dynamic segment with unreserved chars does not encode them", function() {
var unreservedChars = ["-",".","_","~","a","A","0"]; // "ALPHA / DIGIT / "-" / "." / "_" / "~"";
unreservedChars.forEach(function(char) {
var route = "post";
var params = {id: char};
var expected = "/posts/" + char;

equal(router.generate(route, params), expected, "Unreserved char '" + char + "' generates correct route");
});
});

test("Generating a dynamic segment with sub-delims or ':' or '@' does not encode them", function() {
var subDelims = ["!", "$", "&", "'", "(", ")", "*", "+", ",", ";", "="];
var others = [":", "@"];

var chars = subDelims.concat(others);

chars.forEach(function(char) {
var route = "post";
var params = {id: char};
var expected = "/posts/" + char;

equal(router.generate(route, params), expected,
"Char '" + char + "' is not encoded when generating dynamic segment");
});
});

test("Generating a dynamic segment with general delimiters (except ':' and '@') encodes them", function() {
var genDelims = [":", "/", "?", "#", "[", "]", "@"];
var exclude = [":", "@"];
var chars = genDelims.filter(function(ch) { return exclude.indexOf(ch) === -1; });

chars.forEach(function(char) {
var route = "post";
var params = {id: char};
var encoded = encodeURIComponent(char);
ok(char !== encoded, "precond - encoded '" + char + "' is different ('" + encoded + "')");
var expected = "/posts/" + encodeURIComponent(char);

equal(router.generate(route, params), expected,
"Char '" + char + "' is encoded when generating dynamic segment");
});
});

var globGenerationValues = [
"abc/def",
"abc%2Fdef",
Expand Down

0 comments on commit 23455b6

Please sign in to comment.