Skip to content

Commit b8ff2c9

Browse files
TimothyGuevanlucas
authored andcommitted
url: spec-compliant URLSearchParams serializer
PR-URL: #12507 Reviewed-By: James M Snell <[email protected]>
1 parent d6fe916 commit b8ff2c9

File tree

7 files changed

+113
-32
lines changed

7 files changed

+113
-32
lines changed

‎benchmark/url/legacy-vs-whatwg-url-searchparams-serialize.js‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ const inputs = require('../fixtures/url-inputs.js').searchParams;
77
constbench=common.createBenchmark(main,{
88
type: Object.keys(inputs),
99
method: ['legacy','whatwg'],
10-
n: [1e5]
10+
n: [1e6]
1111
});
1212

1313
functionuseLegacy(n,input,prop){

‎lib/internal/url.js‎

Lines changed: 93 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
'use strict';
22

33
constutil=require('util');
4-
const{ StorageObject }=require('internal/querystring');
4+
const{hexTable,StorageObject }=require('internal/querystring');
55
constbinding=process.binding('url');
66
constcontext=Symbol('context');
77
constcannotBeBase=Symbol('cannot-be-base');
@@ -594,18 +594,99 @@ function getParamsFromObject(obj){
594594
returnvalues;
595595
}
596596

597-
functiongetObjectFromParams(array){
598-
constobj=newStorageObject();
599-
for(vari=0;i<array.length;i+=2){
600-
constname=array[i];
601-
constvalue=array[i+1];
602-
if(obj[name]){
603-
obj[name].push(value);
604-
}else{
605-
obj[name]=[value];
597+
// Adapted from querystring's implementation.
598+
// Ref: https://url.spec.whatwg.org/#concept-urlencoded-byte-serializer
599+
constnoEscape=[
600+
//0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E, F
601+
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,// 0x00 - 0x0F
602+
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,// 0x10 - 0x1F
603+
0,0,0,0,0,0,0,0,0,0,1,0,0,1,1,0,// 0x20 - 0x2F
604+
1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,// 0x30 - 0x3F
605+
0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,// 0x40 - 0x4F
606+
1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,1,// 0x50 - 0x5F
607+
0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,// 0x60 - 0x6F
608+
1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0// 0x70 - 0x7F
609+
];
610+
611+
// Special version of hexTable that uses `+` for U+0020 SPACE.
612+
constparamHexTable=hexTable.slice();
613+
paramHexTable[0x20]='+';
614+
615+
functionescapeParam(str){
616+
constlen=str.length;
617+
if(len===0)
618+
return'';
619+
620+
varout='';
621+
varlastPos=0;
622+
623+
for(vari=0;i<len;i++){
624+
varc=str.charCodeAt(i);
625+
626+
// ASCII
627+
if(c<0x80){
628+
if(noEscape[c]===1)
629+
continue;
630+
if(lastPos<i)
631+
out+=str.slice(lastPos,i);
632+
lastPos=i+1;
633+
out+=paramHexTable[c];
634+
continue;
635+
}
636+
637+
if(lastPos<i)
638+
out+=str.slice(lastPos,i);
639+
640+
// Multi-byte characters ...
641+
if(c<0x800){
642+
lastPos=i+1;
643+
out+=paramHexTable[0xC0|(c>>6)]+
644+
paramHexTable[0x80|(c&0x3F)];
645+
continue;
646+
}
647+
if(c<0xD800||c>=0xE000){
648+
lastPos=i+1;
649+
out+=paramHexTable[0xE0|(c>>12)]+
650+
paramHexTable[0x80|((c>>6)&0x3F)]+
651+
paramHexTable[0x80|(c&0x3F)];
652+
continue;
606653
}
654+
// Surrogate pair
655+
++i;
656+
varc2;
657+
if(i<len)
658+
c2=str.charCodeAt(i)&0x3FF;
659+
else{
660+
// This branch should never happen because all URLSearchParams entries
661+
// should already be converted to USVString. But, included for
662+
// completion's sake anyway.
663+
c2=0;
664+
}
665+
lastPos=i+1;
666+
c=0x10000+(((c&0x3FF)<<10)|c2);
667+
out+=paramHexTable[0xF0|(c>>18)]+
668+
paramHexTable[0x80|((c>>12)&0x3F)]+
669+
paramHexTable[0x80|((c>>6)&0x3F)]+
670+
paramHexTable[0x80|(c&0x3F)];
607671
}
608-
returnobj;
672+
if(lastPos===0)
673+
returnstr;
674+
if(lastPos<len)
675+
returnout+str.slice(lastPos);
676+
returnout;
677+
}
678+
679+
// application/x-www-form-urlencoded serializer
680+
// Ref: https://url.spec.whatwg.org/#concept-urlencoded-serializer
681+
functionserializeParams(array){
682+
constlen=array.length;
683+
if(len===0)
684+
return'';
685+
686+
varoutput=`${escapeParam(array[0])}=${escapeParam(array[1])}`;
687+
for(vari=2;i<len;i+=2)
688+
output+=`&${escapeParam(array[i])}=${escapeParam(array[i+1])}`;
689+
returnoutput;
609690
}
610691

611692
// Mainly to mitigate func-name-matching ESLint rule
@@ -990,7 +1071,7 @@ defineIDLClass(URLSearchParams.prototype, 'URLSearchParams',{
9901071
thrownewTypeError('Value of `this` is not a URLSearchParams');
9911072
}
9921073

993-
returnquerystring.stringify(getObjectFromParams(this[searchParams]));
1074+
returnserializeParams(this[searchParams]);
9941075
}
9951076
});
9961077

‎test/fixtures/url-tests.js‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4639,7 +4639,7 @@ module.exports =
46394639
"port": "",
46404640
"pathname": "/foo/bar",
46414641
"search": "??a=b&c=d",
4642-
// "searchParams": "%3Fa=b&c=d",
4642+
"searchParams": "%3Fa=b&c=d",
46434643
"hash": ""
46444644
},
46454645
"# Scheme only",

‎test/parallel/test-whatwg-url-constructor.js‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -120,12 +120,12 @@ function runURLSearchParamTests(){
120120
// And in the other direction, altering searchParams propagates
121121
// back to 'search'.
122122
searchParams.append('i',' j ')
123-
// assert_equals(url.search, '?e=f&g=h&i=+j+')
124-
// assert_equals(url.searchParams.toString(), 'e=f&g=h&i=+j+')
123+
assert_equals(url.search,'?e=f&g=h&i=+j+')
124+
assert_equals(url.searchParams.toString(),'e=f&g=h&i=+j+')
125125
assert_equals(searchParams.get('i'),' j ')
126126

127127
searchParams.set('e','updated')
128-
// assert_equals(url.search, '?e=updated&g=h&i=+j+')
128+
assert_equals(url.search,'?e=updated&g=h&i=+j+')
129129
assert_equals(searchParams.get('e'),'updated')
130130

131131
varurl2=bURL('http://example.org/file??a=b&c=d')

‎test/parallel/test-whatwg-url-searchparams-constructor.js‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ const{
1111
/* eslint-disable */
1212
varparams;// Strict mode fix for WPT.
1313
/* WPT Refs:
14-
https://github.com/w3c/web-platform-tests/blob/405394a/url/urlsearchparams-constructor.html
14+
https://github.com/w3c/web-platform-tests/blob/e94c604916/url/urlsearchparams-constructor.html
1515
License: http://www.w3.org/Consortium/Legal/2008/04-testsuite-copyright.html
1616
*/
1717
test(function(){
@@ -154,7 +154,7 @@ test(function(){
154154
},"Constructor with sequence of sequences of strings");
155155

156156
[
157-
// {"input":{"+": "%C2"}, "output": [["", "\uFFFD"]], "name": "object with +" },
157+
{"input": {"+": "%C2"},"output": [["+","%C2"]],"name": "object with +"},
158158
{"input": {c: "x",a: "?"},"output": [["c","x"],["a","?"]],"name": "object with two keys"},
159159
{"input": [["c","x"],["a","?"]],"output": [["c","x"],["a","?"]],"name": "array with two keys"}
160160
].forEach((val)=>{

‎test/parallel/test-whatwg-url-searchparams-stringifier.js‎

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,14 @@ const{test, assert_equals } = common.WPT;
1010
https://github.com/w3c/web-platform-tests/blob/8791bed/url/urlsearchparams-stringifier.html
1111
License: http://www.w3.org/Consortium/Legal/2008/04-testsuite-copyright.html
1212
*/
13-
// test(function(){
14-
// var params = new URLSearchParams();
15-
// params.append('a', 'b c');
16-
// assert_equals(params + '', 'a=b+c');
17-
// params.delete('a');
18-
// params.append('a b', 'c');
19-
// assert_equals(params + '', 'a+b=c');
20-
// }, 'Serialize space');
13+
test(function(){
14+
varparams=newURLSearchParams();
15+
params.append('a','b c');
16+
assert_equals(params+'','a=b+c');
17+
params.delete('a');
18+
params.append('a b','c');
19+
assert_equals(params+'','a+b=c');
20+
},'Serialize space');
2121

2222
test(function(){
2323
varparams=newURLSearchParams();
@@ -112,10 +112,10 @@ test(function(){
112112

113113
test(function(){
114114
varparams;
115-
// params = new URLSearchParams('a=b&c=d&&e&&');
116-
// assert_equals(params.toString(), 'a=b&c=d&e=');
117-
// params = new URLSearchParams('a = b &a=b&c=d%20');
118-
// assert_equals(params.toString(), 'a+=+b+&a=b&c=d+');
115+
params=newURLSearchParams('a=b&c=d&&e&&');
116+
assert_equals(params.toString(),'a=b&c=d&e=');
117+
params=newURLSearchParams('a = b &a=b&c=d%20');
118+
assert_equals(params.toString(),'a+=+b+&a=b&c=d+');
119119
// The lone '=' _does_ survive the roundtrip.
120120
params=newURLSearchParams('a=&a=b');
121121
assert_equals(params.toString(),'a=&a=b');

‎test/parallel/test-whatwg-url-searchparams.js‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ const URL = require('url').URL;
77
// Tests below are not from WPT.
88
constserialized='a=a&a=1&a=true&a=undefined&a=null&a=%EF%BF%BD'+
99
'&a=%EF%BF%BD&a=%F0%9F%98%80&a=%EF%BF%BD%EF%BF%BD'+
10-
'&a=%5Bobject%20Object%5D';
10+
'&a=%5Bobject+Object%5D';
1111
constvalues=['a',1,true,undefined,null,'\uD83D','\uDE00',
1212
'\uD83D\uDE00','\uDE00\uD83D',{}];
1313
constnormalizedValues=['a','1','true','undefined','null','\uFFFD',

0 commit comments

Comments
(0)