Skip to content

Commit

Permalink
feat($location): parse query parameters delimited by ; or &
Browse files Browse the repository at this point in the history
In accordance with recomendation in http://www.w3.org/TR/1999/REC-html401-19991224/appendix/notes.html#h-B.2.2, the
query parameters should support encoding and decoding using & or ; as a delimiter.

Angular will consistently encode search queries using either '&' or ';' as the delimiter, with '&' being the default.
This can be configured like so:

```js
$locationProvider.queryDelimiter(';'); // any other value will be treated as '&'
```

Closes angular#6140
  • Loading branch information
caitp committed Feb 7, 2014
1 parent 95be253 commit 7e0288c
Show file tree
Hide file tree
Showing 3 changed files with 96 additions and 9 deletions.
9 changes: 6 additions & 3 deletions src/Angular.js
Expand Up @@ -1021,7 +1021,7 @@ function tryDecodeURIComponent(value) {
*/
function parseKeyValue(/**string*/keyValue) {
var obj = {}, key_value, key;
forEach((keyValue || "").split('&'), function(keyValue){
forEach((keyValue || "").split(/[&;]/), function(keyValue){
if ( keyValue ) {
key_value = keyValue.split('=');
key = tryDecodeURIComponent(key_value[0]);
Expand All @@ -1040,8 +1040,11 @@ function parseKeyValue(/**string*/keyValue) {
return obj;
}

function toKeyValue(obj) {
function toKeyValue(obj, delimiter) {
var parts = [];
if (delimiter !== '&' && delimiter !== ';') {
delimiter = '&';
}
forEach(obj, function(value, key) {
if (isArray(value)) {
forEach(value, function(arrayValue) {
Expand All @@ -1053,7 +1056,7 @@ function toKeyValue(obj) {
(value === true ? '' : '=' + encodeUriQuery(value, true)));
}
});
return parts.length ? parts.join('&') : '';
return parts.length ? parts.join(delimiter) : '';
}


Expand Down
33 changes: 27 additions & 6 deletions src/ng/location.js
Expand Up @@ -87,7 +87,7 @@ function serverBase(url) {
* @param {string} appBase application base URL
* @param {string} basePrefix url path prefix
*/
function LocationHtml5Url(appBase, basePrefix) {
function LocationHtml5Url(appBase, basePrefix, queryDelimiter) {
this.$$html5 = true;
basePrefix = basePrefix || '';
var appBaseNoFile = stripFile(appBase);
Expand Down Expand Up @@ -120,7 +120,7 @@ function LocationHtml5Url(appBase, basePrefix) {
* @private
*/
this.$$compose = function() {
var search = toKeyValue(this.$$search),
var search = toKeyValue(this.$$search, queryDelimiter),
hash = this.$$hash ? '#' + encodeUriSegment(this.$$hash) : '';

this.$$url = encodePath(this.$$path) + (search ? '?' + search : '') + hash;
Expand Down Expand Up @@ -155,7 +155,7 @@ function LocationHtml5Url(appBase, basePrefix) {
* @param {string} appBase application base URL
* @param {string} hashPrefix hashbang prefix
*/
function LocationHashbangUrl(appBase, hashPrefix) {
function LocationHashbangUrl(appBase, hashPrefix, queryDelimiter) {
var appBaseNoFile = stripFile(appBase);

parseAbsoluteUrl(appBase, this, appBase);
Expand Down Expand Up @@ -227,7 +227,7 @@ function LocationHashbangUrl(appBase, hashPrefix) {
* @private
*/
this.$$compose = function() {
var search = toKeyValue(this.$$search),
var search = toKeyValue(this.$$search, queryDelimiter),
hash = this.$$hash ? '#' + encodeUriSegment(this.$$hash) : '';

this.$$url = encodePath(this.$$path) + (search ? '?' + search : '') + hash;
Expand Down Expand Up @@ -531,7 +531,8 @@ function locationGetterSetter(property, preprocess) {
*/
function $LocationProvider(){
var hashPrefix = '',
html5Mode = false;
html5Mode = false,
queryDelimiter = '&';

/**
* @ngdoc property
Expand Down Expand Up @@ -567,6 +568,26 @@ function $LocationProvider(){
}
};

/**
* @ngdoc property
* @name ng.$locationProvider#queryDelimiter
* @methodOf ng.$locationProvider
* @description
* @param {string=} delimiter String to use as a delimiter for query parameters. Must be '&' or
* ';'
* @returns {*} current value if used as getter or itself (chaining) if used as setter
*/
this.queryDelimiter = function(delimiter) {
if (arguments.length > 0) {
if (delimiter !== ';' && delimiter !== '&') {
delimiter = '&';
}
queryDelimiter = delimiter;
return this;
}
return queryDelimiter;
};

/**
* @ngdoc event
* @name ng.$location#$locationChangeStart
Expand Down Expand Up @@ -611,7 +632,7 @@ function $LocationProvider(){
appBase = stripHash(initialUrl);
LocationMode = LocationHashbangUrl;
}
$location = new LocationMode(appBase, '#' + hashPrefix);
$location = new LocationMode(appBase, '#' + hashPrefix, queryDelimiter);
$location.$$parse($location.$$rewrite(initialUrl));

$rootElement.on('click', function(event) {
Expand Down
63 changes: 63 additions & 0 deletions test/ng/locationSpec.js
Expand Up @@ -313,6 +313,38 @@ describe('$location', function() {
expect(url.search()).toEqual({'i j': '<>#'});
expect(url.hash()).toBe('x <>#');
});


it('should decode query params delimited interchangeably by & and ;', function() {
var url = new LocationHtml5Url('http://host.com/');
url.$$parse('http://host.com/?foo=1&bar=2;baz=3');
expect(url.search()).toEqual({
'foo': '1',
'bar': '2',
'baz': '3'
});
});


it('should honor configured query param delimiter if ; --- otherwise use &', function() {
url = new LocationHtml5Url('http://host.com/', '#', ';');
url.$$parse('http://host.com/');
url.search({
"foo": "1",
"bar": "2",
"baz": "3"
});
expect(url.absUrl()).toMatch(/\?foo=1;bar=2;baz=3$/);

url = new LocationHtml5Url('http://host.com/', '#', '*');
url.$$parse('http://host.com/');
url.search({
"foo": "1",
"bar": "2",
"baz": "3"
});
expect(url.absUrl()).toMatch(/\?foo=1&bar=2&baz=3$/);
});
});
});

Expand Down Expand Up @@ -435,6 +467,17 @@ describe('$location', function() {
});


it('should decode query params delimited interchangeably by & and ;', function() {
var url = new LocationHashbangUrl('http://host.com/', '#');
url.$$parse('http://host.com/#?foo=1&bar=2;baz=3');
expect(url.search()).toEqual({
'foo': '1',
'bar': '2',
'baz': '3'
});
});


it('should return decoded characters for search specified with setter', function() {
var locationUrl = new LocationHtml5Url('http://host.com/');
locationUrl.$$parse('http://host.com/')
Expand Down Expand Up @@ -464,6 +507,26 @@ describe('$location', function() {
locationUrl.search({'q': '4/5 6'});
expect(locationUrl.absUrl()).toEqual('http://host.com?q=4%2F5%206');
});

it('should honor configured query param delimiter if ; --- otherwise use &', function() {
url = new LocationHashbangUrl('http://host.com/', '#', ';');
url.$$parse('http://host.com/');
url.search({
"foo": "1",
"bar": "2",
"baz": "3"
});
expect(url.absUrl()).toMatch(/\?foo=1;bar=2;baz=3$/);

url = new LocationHashbangUrl('http://host.com/', '#', '*');
url.$$parse('http://host.com/');
url.search({
"foo": "1",
"bar": "2",
"baz": "3"
});
expect(url.absUrl()).toMatch(/\?foo=1&bar=2&baz=3$/);
});
});
});

Expand Down

0 comments on commit 7e0288c

Please sign in to comment.