Skip to content
This repository was archived by the owner on Apr 12, 2024. It is now read-only.

Commit 8073940

Browse files
joshkurzpetebacondarwin
authored andcommitted
fix(Angular.js): handle duplicate params in parseKeyValue/toKeyValue
parseKeyValue and toKeyValue can now handle duplicate values in the query. ``` ?x=1&x=2 <-> {x:[1,2]} ``` The algorithm looks like: 1)parseKeyValue looks for presence of obj[key] 2)detects and replaces obj[key] with [obj[key],val] 3)then pushes more duplicates if necessary 4)toKeyValue decodes array correctly 5)(not changed)$location.search({param: 'key'}) still replaces if necessary 6)(not changed)$location.search({param: ['key1', 'key2']}) sets the url with duplicates BREAKING CHANGE: Before this change: - `parseKeyValue` only took the last key overwriting all the previous keys; - `toKeyValue` joined the keys together in a comma delimited string. This was deemed buggy behavior. If your server relied on this behavior then either the server should be fixed or a simple serialization of the array should be done on the client before passing it to $location.
1 parent a258817 commit 8073940

File tree

4 files changed

+58
-4
lines changed

4 files changed

+58
-4
lines changed

src/Angular.js

+14-1
Original file line numberDiff line numberDiff line change
@@ -850,7 +850,14 @@ function parseKeyValue(/**string*/keyValue) {
850850
key_value = keyValue.split('=');
851851
key = tryDecodeURIComponent(key_value[0]);
852852
if ( isDefined(key) ) {
853-
obj[key] = isDefined(key_value[1]) ? tryDecodeURIComponent(key_value[1]) : true;
853+
var val = isDefined(key_value[1]) ? tryDecodeURIComponent(key_value[1]) : true;
854+
if (!obj[key]) {
855+
obj[key] = val;
856+
} else if(isArray(obj[key])) {
857+
obj[key].push(val);
858+
} else {
859+
obj[key] = [obj[key],val];
860+
}
854861
}
855862
}
856863
});
@@ -860,7 +867,13 @@ function parseKeyValue(/**string*/keyValue) {
860867
function toKeyValue(obj) {
861868
var parts = [];
862869
forEach(obj, function(value, key) {
870+
if (isArray(value)) {
871+
forEach(value, function(arrayValue) {
872+
parts.push(encodeUriQuery(key, true) + (arrayValue === true ? '' : '=' + encodeUriQuery(arrayValue, true)));
873+
});
874+
} else {
863875
parts.push(encodeUriQuery(key, true) + (value === true ? '' : '=' + encodeUriQuery(value, true)));
876+
}
864877
});
865878
return parts.length ? parts.join('&') : '';
866879
}

src/ng/location.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -344,7 +344,8 @@ LocationHashbangInHtml5Url.prototype =
344344
*
345345
* Change search part when called with parameter and return `$location`.
346346
*
347-
* @param {string|object<string,string>=} search New search params - string or hash object
347+
* @param {string|Object.<string>|Object.<Array.<string>>} search New search params - string or hash object. Hash object
348+
* may contain an array of values, which will be decoded as duplicates in the url.
348349
* @param {string=} paramValue If `search` is a string, then `paramValue` will override only a
349350
* single search parameter. If the value is `null`, the parameter will be deleted.
350351
*

test/AngularSpec.js

+19-2
Original file line numberDiff line numberDiff line change
@@ -318,10 +318,21 @@ describe('angular', function() {
318318
expect(parseKeyValue('invalid=%')).toEqual({ invalid: undefined });
319319
expect(parseKeyValue('invalid=%&valid=good')).toEqual({ invalid: undefined, valid: 'good' });
320320
});
321+
it('should parse a string into key-value pairs with duplicates grouped in an array', function() {
322+
expect(parseKeyValue('')).toEqual({});
323+
expect(parseKeyValue('duplicate=pair')).toEqual({duplicate: 'pair'});
324+
expect(parseKeyValue('first=1&first=2')).toEqual({first: ['1','2']});
325+
expect(parseKeyValue('escaped%20key=escaped%20value&&escaped%20key=escaped%20value2')).
326+
toEqual({'escaped key': ['escaped value','escaped value2']});
327+
expect(parseKeyValue('flag1&key=value&flag1')).
328+
toEqual({flag1: [true,true], key: 'value'});
329+
expect(parseKeyValue('flag1&flag1=value&flag1=value2&flag1')).
330+
toEqual({flag1: [true,'value','value2',true]});
331+
});
321332
});
322333

323334
describe('toKeyValue', function() {
324-
it('should parse key-value pairs into string', function() {
335+
it('should serialize key-value pairs into string', function() {
325336
expect(toKeyValue({})).toEqual('');
326337
expect(toKeyValue({simple: 'pair'})).toEqual('simple=pair');
327338
expect(toKeyValue({first: '1', second: '2'})).toEqual('first=1&second=2');
@@ -330,9 +341,15 @@ describe('angular', function() {
330341
expect(toKeyValue({emptyKey: ''})).toEqual('emptyKey=');
331342
});
332343

333-
it('should parse true values into flags', function() {
344+
it('should serialize true values into flags', function() {
334345
expect(toKeyValue({flag1: true, key: 'value', flag2: true})).toEqual('flag1&key=value&flag2');
335346
});
347+
348+
it('should serialize duplicates into duplicate param strings', function() {
349+
expect(toKeyValue({key: [323,'value',true]})).toEqual('key=323&key=value&key');
350+
expect(toKeyValue({key: [323,'value',true, 1234]})).
351+
toEqual('key=323&key=value&key&key=1234');
352+
});
336353
});
337354

338355

test/ng/locationSpec.js

+23
Original file line numberDiff line numberDiff line change
@@ -368,6 +368,29 @@ describe('$location', function() {
368368
locationUrl.search('q', '1/2 3');
369369
expect(locationUrl.search()).toEqual({'q': '1/2 3'});
370370
});
371+
372+
it('should return an array for duplicate params', function() {
373+
var locationUrl = new LocationHtml5Url('http://host.com');
374+
locationUrl.$$parse('http://host.com')
375+
locationUrl.search('q', ['1/2 3','4/5 6']);
376+
expect(locationUrl.search()).toEqual({'q': ['1/2 3','4/5 6']});
377+
});
378+
379+
it('should encode an array correctly from search and add to url', function() {
380+
var locationUrl = new LocationHtml5Url('http://host.com');
381+
locationUrl.$$parse('http://host.com')
382+
locationUrl.search({'q': ['1/2 3','4/5 6']});
383+
expect(locationUrl.absUrl()).toEqual('http://host.com?q=1%2F2%203&q=4%2F5%206');
384+
});
385+
386+
it('should rewrite params when specifing a single param in search', function() {
387+
var locationUrl = new LocationHtml5Url('http://host.com');
388+
locationUrl.$$parse('http://host.com')
389+
locationUrl.search({'q': '1/2 3'});
390+
expect(locationUrl.absUrl()).toEqual('http://host.com?q=1%2F2%203');
391+
locationUrl.search({'q': '4/5 6'});
392+
expect(locationUrl.absUrl()).toEqual('http://host.com?q=4%2F5%206');
393+
});
371394
});
372395
});
373396

0 commit comments

Comments
 (0)