Skip to content

Commit

Permalink
Fix URL encoding to mimic Symfony URL Generator (#387)
Browse files Browse the repository at this point in the history
  • Loading branch information
ajgarlag authored May 20, 2020
1 parent d9aecdc commit 7742a05
Show file tree
Hide file tree
Showing 4 changed files with 119 additions and 10 deletions.
56 changes: 52 additions & 4 deletions Resources/js/router.js
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,7 @@ class Router {

route.tokens.forEach((token) => {
if ('text' === token[0]) {
url = token[1] + url;
url = Router.encodePathComponent(token[1]) + url;
optional = false;

return;
Expand All @@ -248,7 +248,7 @@ class Router {
let empty = true === value || false === value || '' === value;

if (!empty || !optional) {
let encodedValue = encodeURIComponent(value).replace(/%2F/g, '/');
let encodedValue = Router.encodePathComponent(value);

if ('null' === encodedValue && null === value) {
encodedValue = '';
Expand Down Expand Up @@ -319,19 +319,67 @@ class Router {
// change null to empty string
value = (value === null) ? '' : value;

queryParams.push(encodeURIComponent(key) + '=' + encodeURIComponent(value));
queryParams.push(Router.encodeQueryComponent(key) + '=' + Router.encodeQueryComponent(value));
};

for (prefix in unusedParams) {
this.buildQueryParams(prefix, unusedParams[prefix], add);
}

url = url + '?' + queryParams.join('&').replace(/%20/g, '+');
url = url + '?' + queryParams.join('&');
}

return url;
}

/**
* Returns the given string encoded to mimic Symfony URL generator.
*
* @param {string} value
* @return {string}
*/
static customEncodeURIComponent(value) {
return encodeURIComponent(value)
.replace(/%2F/g, '/')
.replace(/%40/g, '@')
.replace(/%3A/g, ':')
.replace(/%21/g, '!')
.replace(/%3B/g, ';')
.replace(/%2C/g, ',')
.replace(/%2A/g, '*')
.replace(/\(/g, '%28')
.replace(/\)/g, '%29')
.replace(/'/g, '%27')
;
}

/**
* Returns the given path properly encoded to mimic Symfony URL generator.
*
* @param {string} value
* @return {string}
*/
static encodePathComponent(value) {
return Router.customEncodeURIComponent(value)
.replace(/%3D/g, '=')
.replace(/%2B/g, '+')
.replace(/%21/g, '!')
.replace(/%7C/g, '|')
;
}

/**
* Returns the given query parameter or value properly encoded to mimic Symfony URL generator.
*
* @param {string} value
* @return {string}
*/
static encodeQueryComponent(value) {
return Router.customEncodeURIComponent(value)
.replace(/%3F/g, '?')
;
}

}

/**
Expand Down
24 changes: 23 additions & 1 deletion Resources/js/router.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -359,7 +359,7 @@ function testGenerateWithExtraParamsDeep() {
}
});

assertEquals('/baz?foo%5B%5D=1&foo%5B1%5D%5B%5D=1&foo%5B1%5D%5B%5D=2&foo%5B1%5D%5B%5D=3&foo%5B1%5D%5B%5D=foo&foo%5B%5D=3&foo%5B%5D=4&foo%5B%5D=bar&foo%5B5%5D%5B%5D=1&foo%5B5%5D%5B%5D=2&foo%5B5%5D%5B%5D=3&foo%5B5%5D%5B%5D=baz&baz%5Bfoo%5D=bar+foo&baz%5Bbar%5D=baz&bob=cat', router.generate('foo', {
assertEquals('/baz?foo%5B%5D=1&foo%5B1%5D%5B%5D=1&foo%5B1%5D%5B%5D=2&foo%5B1%5D%5B%5D=3&foo%5B1%5D%5B%5D=foo&foo%5B%5D=3&foo%5B%5D=4&foo%5B%5D=bar&foo%5B5%5D%5B%5D=1&foo%5B5%5D%5B%5D=2&foo%5B5%5D%5B%5D=3&foo%5B5%5D%5B%5D=baz&baz%5Bfoo%5D=bar%20foo&baz%5Bbar%5D=baz&bob=cat', router.generate('foo', {
bar: 'baz', // valid param, not included in the query string
foo: [1, [1, 2, 3, 'foo'], 3, 4, 'bar', [1, 2, 3, 'baz']],
baz: {
Expand All @@ -370,6 +370,28 @@ function testGenerateWithExtraParamsDeep() {
}));
}

function testUrlEncoding() {
// This test was copied from Symfony URL Generator

// This tests the encoding of reserved characters that are used for delimiting of URI components (defined in RFC 3986)
// and other special ASCII chars. These chars are tested as static text path, variable path and query param.
var chars = '@:[]/()*\'" +,;-._~&$<>|{}%\\^`!?foo=bar#id';

var router = new fos.Router({base_url: '/app.php'}, {
posts: {
tokens: [['variable', '/', '.+', 'varpath'], ['text', '/'+chars]],
defaults: {},
requirements: {},
hosttokens: []
}
});

assertEquals(
'/app.php/@:%5B%5D/%28%29*%27%22%20+,;-._~%26%24%3C%3E|%7B%7D%25%5C%5E%60!%3Ffoo=bar%23id/@:%5B%5D/%28%29*%27%22%20+,;-._~%26%24%3C%3E|%7B%7D%25%5C%5E%60!%3Ffoo=bar%23id?query=@:%5B%5D/%28%29*%27%22%20%2B,;-._~%26%24%3C%3E%7C%7B%7D%25%5C%5E%60!?foo%3Dbar%23id',
router.generate('posts', {varpath: chars, query: chars})
);
}

function testGenerateThrowsErrorWhenRequiredParameterWasNotGiven() {
var router = new fos.Router({base_url: ''}, {
foo: {
Expand Down
47 changes: 43 additions & 4 deletions Resources/public/js/router.js
Original file line number Diff line number Diff line change
Expand Up @@ -296,7 +296,7 @@ var Router = function () {

route.tokens.forEach(function (token) {
if ('text' === token[0]) {
url = token[1] + url;
url = Router.encodePathComponent(token[1]) + url;
optional = false;

return;
Expand All @@ -321,7 +321,7 @@ var Router = function () {
var empty = true === value || false === value || '' === value;

if (!empty || !optional) {
var encodedValue = encodeURIComponent(value).replace(/%2F/g, '/');
var encodedValue = Router.encodePathComponent(value);

if ('null' === encodedValue && null === value) {
encodedValue = '';
Expand Down Expand Up @@ -392,18 +392,26 @@ var Router = function () {
// change null to empty string
value = value === null ? '' : value;

queryParams.push(encodeURIComponent(key) + '=' + encodeURIComponent(value));
queryParams.push(Router.encodeQueryComponent(key) + '=' + Router.encodeQueryComponent(value));
};

for (prefix in unusedParams) {
this.buildQueryParams(prefix, unusedParams[prefix], add);
}

url = url + '?' + queryParams.join('&').replace(/%20/g, '+');
url = url + '?' + queryParams.join('&');
}

return url;
}

/**
* Returns the given string encoded to mimic Symfony URL generator.
*
* @param {string} value
* @return {string}
*/

}], [{
key: 'getInstance',
value: function getInstance() {
Expand All @@ -422,6 +430,37 @@ var Router = function () {

router.setRoutingData(data);
}
}, {
key: 'customEncodeURIComponent',
value: function customEncodeURIComponent(value) {
return encodeURIComponent(value).replace(/%2F/g, '/').replace(/%40/g, '@').replace(/%3A/g, ':').replace(/%21/g, '!').replace(/%3B/g, ';').replace(/%2C/g, ',').replace(/%2A/g, '*').replace(/\(/g, '%28').replace(/\)/g, '%29').replace(/'/g, '%27');
}

/**
* Returns the given path properly encoded to mimic Symfony URL generator.
*
* @param {string} value
* @return {string}
*/

}, {
key: 'encodePathComponent',
value: function encodePathComponent(value) {
return Router.customEncodeURIComponent(value).replace(/%3D/g, '=').replace(/%2B/g, '+').replace(/%21/g, '!').replace(/%7C/g, '|');
}

/**
* Returns the given query parameter or value properly encoded to mimic Symfony URL generator.
*
* @param {string} value
* @return {string}
*/

}, {
key: 'encodeQueryComponent',
value: function encodeQueryComponent(value) {
return Router.customEncodeURIComponent(value).replace(/%3F/g, '?');
}
}]);

return Router;
Expand Down
2 changes: 1 addition & 1 deletion Resources/public/js/router.min.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 7742a05

Please sign in to comment.