1
- // Query String Utilities
2
-
3
1
'use strict' ;
4
2
5
- const QueryString = exports ;
3
+ const QueryString = module . exports = {
4
+ unescapeBuffer,
5
+ // `unescape()` is a JS global, so we need to use a different local name
6
+ unescape : qsUnescape ,
7
+
8
+ // `escape()` is a JS global, so we need to use a different local name
9
+ escape : qsEscape ,
10
+
11
+ stringify,
12
+ encode : stringify ,
13
+
14
+ parse,
15
+ decode : parse
16
+ } ;
6
17
const Buffer = require ( 'buffer' ) . Buffer ;
7
18
8
19
// This constructor is used to store parsed query string values. Instantiating
@@ -13,7 +24,7 @@ ParsedQueryString.prototype = Object.create(null);
13
24
14
25
15
26
// a safe fast alternative to decodeURIComponent
16
- QueryString . unescapeBuffer = function ( s , decodeSpaces ) {
27
+ function unescapeBuffer ( s , decodeSpaces ) {
17
28
var out = Buffer . allocUnsafe ( s . length ) ;
18
29
var state = 0 ;
19
30
var n , m , hexchar ;
@@ -77,7 +88,7 @@ QueryString.unescapeBuffer = function(s, decodeSpaces) {
77
88
// TODO support returning arbitrary buffers.
78
89
79
90
return out . slice ( 0 , outIndex - 1 ) ;
80
- } ;
91
+ }
81
92
82
93
83
94
function qsUnescape ( s , decodeSpaces ) {
@@ -87,13 +98,12 @@ function qsUnescape(s, decodeSpaces) {
87
98
return QueryString . unescapeBuffer ( s , decodeSpaces ) . toString ( ) ;
88
99
}
89
100
}
90
- QueryString . unescape = qsUnescape ;
91
101
92
102
93
103
var hexTable = new Array ( 256 ) ;
94
104
for ( var i = 0 ; i < 256 ; ++ i )
95
105
hexTable [ i ] = '%' + ( ( i < 16 ? '0' : '' ) + i . toString ( 16 ) ) . toUpperCase ( ) ;
96
- QueryString . escape = function ( str ) {
106
+ function qsEscape ( str ) {
97
107
// replaces encodeURIComponent
98
108
// http://www.ecma-international.org/ecma-262/5.1/#sec-15.1.3.4
99
109
if ( typeof str !== 'string' ) {
@@ -164,20 +174,20 @@ QueryString.escape = function(str) {
164
174
if ( lastPos < str . length )
165
175
return out + str . slice ( lastPos ) ;
166
176
return out ;
167
- } ;
177
+ }
168
178
169
- var stringifyPrimitive = function ( v ) {
179
+ function stringifyPrimitive ( v ) {
170
180
if ( typeof v === 'string' )
171
181
return v ;
172
182
if ( typeof v === 'number' && isFinite ( v ) )
173
183
return '' + v ;
174
184
if ( typeof v === 'boolean' )
175
185
return v ? 'true' : 'false' ;
176
186
return '' ;
177
- } ;
187
+ }
178
188
179
189
180
- QueryString . stringify = QueryString . encode = function ( obj , sep , eq , options ) {
190
+ function stringify ( obj , sep , eq , options ) {
181
191
sep = sep || '&' ;
182
192
eq = eq || '=' ;
183
193
@@ -215,34 +225,43 @@ QueryString.stringify = QueryString.encode = function(obj, sep, eq, options) {
215
225
return fields ;
216
226
}
217
227
return '' ;
218
- } ;
228
+ }
219
229
220
- // Parse a key/val string.
221
- QueryString . parse = QueryString . decode = function ( qs , sep , eq , options ) {
222
- sep = sep || '&' ;
223
- eq = eq || '=' ;
230
+ function charCodes ( str ) {
231
+ if ( str . length === 0 ) return [ ] ;
232
+ if ( str . length === 1 ) return [ str . charCodeAt ( 0 ) ] ;
233
+ const ret = [ ] ;
234
+ for ( var i = 0 ; i < str . length ; ++ i )
235
+ ret [ ret . length ] = str . charCodeAt ( i ) ;
236
+ return ret ;
237
+ }
238
+ const defSepCodes = [ 38 ] ; // &
239
+ const defEqCodes = [ 61 ] ; // =
224
240
241
+ // Parse a key/val string.
242
+ function parse ( qs , sep , eq , options ) {
225
243
const obj = new ParsedQueryString ( ) ;
226
244
227
245
if ( typeof qs !== 'string' || qs . length === 0 ) {
228
246
return obj ;
229
247
}
230
248
231
- if ( typeof sep !== 'string' )
232
- sep += '' ;
233
-
234
- const eqLen = eq . length ;
235
- const sepLen = sep . length ;
249
+ var sepCodes = ( ! sep ? defSepCodes : charCodes ( sep + '' ) ) ;
250
+ var eqCodes = ( ! eq ? defEqCodes : charCodes ( eq + '' ) ) ;
251
+ const sepLen = sepCodes . length ;
252
+ const eqLen = eqCodes . length ;
236
253
237
- var maxKeys = 1000 ;
254
+ var pairs = 1000 ;
238
255
if ( options && typeof options . maxKeys === 'number' ) {
239
- maxKeys = options . maxKeys ;
256
+ // -1 is used in place of a value like Infinity for meaning
257
+ // "unlimited pairs" because of additional checks V8 (at least as of v5.4)
258
+ // has to do when using variables that contain values like Infinity. Since
259
+ // `pairs` is always decremented and checked explicitly for 0, -1 works
260
+ // effectively the same as Infinity, while providing a significant
261
+ // performance boost.
262
+ pairs = ( options . maxKeys > 0 ? options . maxKeys : - 1 ) ;
240
263
}
241
264
242
- var pairs = Infinity ;
243
- if ( maxKeys > 0 )
244
- pairs = maxKeys ;
245
-
246
265
var decode = QueryString . unescape ;
247
266
if ( options && typeof options . decodeURIComponent === 'function' ) {
248
267
decode = options . decodeURIComponent ;
@@ -262,7 +281,7 @@ QueryString.parse = QueryString.decode = function(qs, sep, eq, options) {
262
281
const code = qs . charCodeAt ( i ) ;
263
282
264
283
// Try matching key/value pair separator (e.g. '&')
265
- if ( code === sep . charCodeAt ( sepIdx ) ) {
284
+ if ( code === sepCodes [ sepIdx ] ) {
266
285
if ( ++ sepIdx === sepLen ) {
267
286
// Key/value pair separator match!
268
287
const end = i - sepIdx + 1 ;
@@ -284,10 +303,10 @@ QueryString.parse = QueryString.decode = function(qs, sep, eq, options) {
284
303
keys [ keys . length ] = key ;
285
304
} else {
286
305
const curValue = obj [ key ] ;
287
- // `instanceof Array` is used instead of Array.isArray() because it
288
- // is ~15-20% faster with v8 4.7 and is safe to use because we are
289
- // using it with values being created within this function
290
- if ( curValue instanceof Array )
306
+ // A simple Array-specific property check is enough here to
307
+ // distinguish from a string value and is faster and still safe since
308
+ // we are generating all of the values being assigned.
309
+ if ( curValue . pop )
291
310
curValue [ curValue . length ] = value ;
292
311
else
293
312
obj [ key ] = [ curValue , value ] ;
@@ -322,7 +341,7 @@ QueryString.parse = QueryString.decode = function(qs, sep, eq, options) {
322
341
323
342
// Try matching key/value separator (e.g. '=') if we haven't already
324
343
if ( eqIdx < eqLen ) {
325
- if ( code === eq . charCodeAt ( eqIdx ) ) {
344
+ if ( code === eqCodes [ eqIdx ] ) {
326
345
if ( ++ eqIdx === eqLen ) {
327
346
// Key/value separator match!
328
347
const end = i - eqIdx + 1 ;
@@ -354,12 +373,12 @@ QueryString.parse = QueryString.decode = function(qs, sep, eq, options) {
354
373
355
374
if ( code === 43 /*+*/ ) {
356
375
if ( eqIdx < eqLen ) {
357
- if ( i - lastPos > 0 )
376
+ if ( lastPos < i )
358
377
key += qs . slice ( lastPos , i ) ;
359
378
key += '%20' ;
360
379
keyEncoded = true ;
361
380
} else {
362
- if ( i - lastPos > 0 )
381
+ if ( lastPos < i )
363
382
value += qs . slice ( lastPos , i ) ;
364
383
value += '%20' ;
365
384
valEncoded = true ;
@@ -369,7 +388,7 @@ QueryString.parse = QueryString.decode = function(qs, sep, eq, options) {
369
388
}
370
389
371
390
// Check if we have leftover key or value data
372
- if ( pairs > 0 && ( lastPos < qs . length || eqIdx > 0 ) ) {
391
+ if ( pairs !== 0 && ( lastPos < qs . length || eqIdx > 0 ) ) {
373
392
if ( lastPos < qs . length ) {
374
393
if ( eqIdx < eqLen )
375
394
key += qs . slice ( lastPos ) ;
@@ -387,22 +406,23 @@ QueryString.parse = QueryString.decode = function(qs, sep, eq, options) {
387
406
keys [ keys . length ] = key ;
388
407
} else {
389
408
const curValue = obj [ key ] ;
390
- // `instanceof Array` is used instead of Array.isArray() because it
391
- // is ~15-20% faster with v8 4.7 and is safe to use because we are
392
- // using it with values being created within this function
393
- if ( curValue instanceof Array )
409
+ // A simple Array-specific property check is enough here to
410
+ // distinguish from a string value and is faster and still safe since
411
+ // we are generating all of the values being assigned.
412
+ if ( curValue . pop )
394
413
curValue [ curValue . length ] = value ;
395
414
else
396
415
obj [ key ] = [ curValue , value ] ;
397
416
}
398
417
}
399
418
400
419
return obj ;
401
- } ;
420
+ }
402
421
403
422
404
423
// v8 does not optimize functions with try-catch blocks, so we isolate them here
405
- // to minimize the damage
424
+ // to minimize the damage (Note: no longer true as of V8 5.4 -- but still will
425
+ // not be inlined).
406
426
function decodeStr ( s , decoder ) {
407
427
try {
408
428
return decoder ( s ) ;
0 commit comments