parse.js
1 'use strict'; 2 3 var test = require('tape'); 4 var hasPropertyDescriptors = require('has-property-descriptors')(); 5 var iconv = require('iconv-lite'); 6 var mockProperty = require('mock-property'); 7 var hasOverrideMistake = require('has-override-mistake')(); 8 var SaferBuffer = require('safer-buffer').Buffer; 9 var v = require('es-value-fixtures'); 10 var inspect = require('object-inspect'); 11 var emptyTestCases = require('./empty-keys-cases').emptyTestCases; 12 var hasProto = require('has-proto')(); 13 14 var qs = require('../'); 15 var utils = require('../lib/utils'); 16 17 test('parse()', function (t) { 18 t.test('parses a simple string', function (st) { 19 st.deepEqual(qs.parse('0=foo'), { 0: 'foo' }); 20 st.deepEqual(qs.parse('foo=c++'), { foo: 'c ' }); 21 st.deepEqual(qs.parse('a[>=]=23'), { a: { '>=': '23' } }); 22 st.deepEqual(qs.parse('a[<=>]==23'), { a: { '<=>': '=23' } }); 23 st.deepEqual(qs.parse('a[==]=23'), { a: { '==': '23' } }); 24 st.deepEqual(qs.parse('foo', { strictNullHandling: true }), { foo: null }); 25 st.deepEqual(qs.parse('foo'), { foo: '' }); 26 st.deepEqual(qs.parse('foo='), { foo: '' }); 27 st.deepEqual(qs.parse('foo=bar'), { foo: 'bar' }); 28 st.deepEqual(qs.parse(' foo = bar = baz '), { ' foo ': ' bar = baz ' }); 29 st.deepEqual(qs.parse('foo=bar=baz'), { foo: 'bar=baz' }); 30 st.deepEqual(qs.parse('foo=bar&bar=baz'), { foo: 'bar', bar: 'baz' }); 31 st.deepEqual(qs.parse('foo2=bar2&baz2='), { foo2: 'bar2', baz2: '' }); 32 st.deepEqual(qs.parse('foo=bar&baz', { strictNullHandling: true }), { foo: 'bar', baz: null }); 33 st.deepEqual(qs.parse('foo=bar&baz'), { foo: 'bar', baz: '' }); 34 st.deepEqual(qs.parse('cht=p3&chd=t:60,40&chs=250x100&chl=Hello|World'), { 35 cht: 'p3', 36 chd: 't:60,40', 37 chs: '250x100', 38 chl: 'Hello|World' 39 }); 40 st.end(); 41 }); 42 43 t.test('comma: false', function (st) { 44 st.deepEqual(qs.parse('a[]=b&a[]=c'), { a: ['b', 'c'] }); 45 st.deepEqual(qs.parse('a[0]=b&a[1]=c'), { a: ['b', 'c'] }); 46 st.deepEqual(qs.parse('a=b,c'), { a: 'b,c' }); 47 st.deepEqual(qs.parse('a=b&a=c'), { a: ['b', 'c'] }); 48 st.end(); 49 }); 50 51 t.test('comma: true', function (st) { 52 st.deepEqual(qs.parse('a[]=b&a[]=c', { comma: true }), { a: ['b', 'c'] }); 53 st.deepEqual(qs.parse('a[0]=b&a[1]=c', { comma: true }), { a: ['b', 'c'] }); 54 st.deepEqual(qs.parse('a=b,c', { comma: true }), { a: ['b', 'c'] }); 55 st.deepEqual(qs.parse('a=b&a=c', { comma: true }), { a: ['b', 'c'] }); 56 st.end(); 57 }); 58 59 t.test('allows enabling dot notation', function (st) { 60 st.deepEqual(qs.parse('a.b=c'), { 'a.b': 'c' }); 61 st.deepEqual(qs.parse('a.b=c', { allowDots: true }), { a: { b: 'c' } }); 62 63 st.end(); 64 }); 65 66 t.test('decode dot keys correctly', function (st) { 67 st.deepEqual( 68 qs.parse('name%252Eobj.first=John&name%252Eobj.last=Doe', { allowDots: false, decodeDotInKeys: false }), 69 { 'name%2Eobj.first': 'John', 'name%2Eobj.last': 'Doe' }, 70 'with allowDots false and decodeDotInKeys false' 71 ); 72 st.deepEqual( 73 qs.parse('name.obj.first=John&name.obj.last=Doe', { allowDots: true, decodeDotInKeys: false }), 74 { name: { obj: { first: 'John', last: 'Doe' } } }, 75 'with allowDots false and decodeDotInKeys false' 76 ); 77 st.deepEqual( 78 qs.parse('name%252Eobj.first=John&name%252Eobj.last=Doe', { allowDots: true, decodeDotInKeys: false }), 79 { 'name%2Eobj': { first: 'John', last: 'Doe' } }, 80 'with allowDots true and decodeDotInKeys false' 81 ); 82 st.deepEqual( 83 qs.parse('name%252Eobj.first=John&name%252Eobj.last=Doe', { allowDots: true, decodeDotInKeys: true }), 84 { 'name.obj': { first: 'John', last: 'Doe' } }, 85 'with allowDots true and decodeDotInKeys true' 86 ); 87 88 st.deepEqual( 89 qs.parse( 90 'name%252Eobj%252Esubobject.first%252Egodly%252Ename=John&name%252Eobj%252Esubobject.last=Doe', 91 { allowDots: false, decodeDotInKeys: false } 92 ), 93 { 'name%2Eobj%2Esubobject.first%2Egodly%2Ename': 'John', 'name%2Eobj%2Esubobject.last': 'Doe' }, 94 'with allowDots false and decodeDotInKeys false' 95 ); 96 st.deepEqual( 97 qs.parse( 98 'name.obj.subobject.first.godly.name=John&name.obj.subobject.last=Doe', 99 { allowDots: true, decodeDotInKeys: false } 100 ), 101 { name: { obj: { subobject: { first: { godly: { name: 'John' } }, last: 'Doe' } } } }, 102 'with allowDots true and decodeDotInKeys false' 103 ); 104 st.deepEqual( 105 qs.parse( 106 'name%252Eobj%252Esubobject.first%252Egodly%252Ename=John&name%252Eobj%252Esubobject.last=Doe', 107 { allowDots: true, decodeDotInKeys: true } 108 ), 109 { 'name.obj.subobject': { 'first.godly.name': 'John', last: 'Doe' } }, 110 'with allowDots true and decodeDotInKeys true' 111 ); 112 st.deepEqual( 113 qs.parse('name%252Eobj.first=John&name%252Eobj.last=Doe'), 114 { 'name%2Eobj.first': 'John', 'name%2Eobj.last': 'Doe' }, 115 'with allowDots and decodeDotInKeys undefined' 116 ); 117 118 st.end(); 119 }); 120 121 t.test('decodes dot in key of object, and allow enabling dot notation when decodeDotInKeys is set to true and allowDots is undefined', function (st) { 122 st.deepEqual( 123 qs.parse( 124 'name%252Eobj%252Esubobject.first%252Egodly%252Ename=John&name%252Eobj%252Esubobject.last=Doe', 125 { decodeDotInKeys: true } 126 ), 127 { 'name.obj.subobject': { 'first.godly.name': 'John', last: 'Doe' } }, 128 'with allowDots undefined and decodeDotInKeys true' 129 ); 130 131 st.end(); 132 }); 133 134 t.test('throws when decodeDotInKeys is not of type boolean', function (st) { 135 st['throws']( 136 function () { qs.parse('foo[]&bar=baz', { decodeDotInKeys: 'foobar' }); }, 137 TypeError 138 ); 139 140 st['throws']( 141 function () { qs.parse('foo[]&bar=baz', { decodeDotInKeys: 0 }); }, 142 TypeError 143 ); 144 st['throws']( 145 function () { qs.parse('foo[]&bar=baz', { decodeDotInKeys: NaN }); }, 146 TypeError 147 ); 148 149 st['throws']( 150 function () { qs.parse('foo[]&bar=baz', { decodeDotInKeys: null }); }, 151 TypeError 152 ); 153 154 st.end(); 155 }); 156 157 t.test('allows empty arrays in obj values', function (st) { 158 st.deepEqual(qs.parse('foo[]&bar=baz', { allowEmptyArrays: true }), { foo: [], bar: 'baz' }); 159 st.deepEqual(qs.parse('foo[]&bar=baz', { allowEmptyArrays: false }), { foo: [''], bar: 'baz' }); 160 161 st.end(); 162 }); 163 164 t.test('throws when allowEmptyArrays is not of type boolean', function (st) { 165 st['throws']( 166 function () { qs.parse('foo[]&bar=baz', { allowEmptyArrays: 'foobar' }); }, 167 TypeError 168 ); 169 170 st['throws']( 171 function () { qs.parse('foo[]&bar=baz', { allowEmptyArrays: 0 }); }, 172 TypeError 173 ); 174 st['throws']( 175 function () { qs.parse('foo[]&bar=baz', { allowEmptyArrays: NaN }); }, 176 TypeError 177 ); 178 179 st['throws']( 180 function () { qs.parse('foo[]&bar=baz', { allowEmptyArrays: null }); }, 181 TypeError 182 ); 183 184 st.end(); 185 }); 186 187 t.test('allowEmptyArrays + strictNullHandling', function (st) { 188 st.deepEqual( 189 qs.parse('testEmptyArray[]', { strictNullHandling: true, allowEmptyArrays: true }), 190 { testEmptyArray: [] } 191 ); 192 193 st.end(); 194 }); 195 196 t.deepEqual(qs.parse('a[b]=c'), { a: { b: 'c' } }, 'parses a single nested string'); 197 t.deepEqual(qs.parse('a[b][c]=d'), { a: { b: { c: 'd' } } }, 'parses a double nested string'); 198 t.deepEqual( 199 qs.parse('a[b][c][d][e][f][g][h]=i'), 200 { a: { b: { c: { d: { e: { f: { '[g][h]': 'i' } } } } } } }, 201 'defaults to a depth of 5' 202 ); 203 204 t.test('only parses one level when depth = 1', function (st) { 205 st.deepEqual(qs.parse('a[b][c]=d', { depth: 1 }), { a: { b: { '[c]': 'd' } } }); 206 st.deepEqual(qs.parse('a[b][c][d]=e', { depth: 1 }), { a: { b: { '[c][d]': 'e' } } }); 207 st.end(); 208 }); 209 210 t.test('uses original key when depth = 0', function (st) { 211 st.deepEqual(qs.parse('a[0]=b&a[1]=c', { depth: 0 }), { 'a[0]': 'b', 'a[1]': 'c' }); 212 st.deepEqual(qs.parse('a[0][0]=b&a[0][1]=c&a[1]=d&e=2', { depth: 0 }), { 'a[0][0]': 'b', 'a[0][1]': 'c', 'a[1]': 'd', e: '2' }); 213 st.end(); 214 }); 215 216 t.test('uses original key when depth = false', function (st) { 217 st.deepEqual(qs.parse('a[0]=b&a[1]=c', { depth: false }), { 'a[0]': 'b', 'a[1]': 'c' }); 218 st.deepEqual(qs.parse('a[0][0]=b&a[0][1]=c&a[1]=d&e=2', { depth: false }), { 'a[0][0]': 'b', 'a[0][1]': 'c', 'a[1]': 'd', e: '2' }); 219 st.end(); 220 }); 221 222 t.deepEqual(qs.parse('a=b&a=c'), { a: ['b', 'c'] }, 'parses a simple array'); 223 224 t.test('parses an explicit array', function (st) { 225 st.deepEqual(qs.parse('a[]=b'), { a: ['b'] }); 226 st.deepEqual(qs.parse('a[]=b&a[]=c'), { a: ['b', 'c'] }); 227 st.deepEqual(qs.parse('a[]=b&a[]=c&a[]=d'), { a: ['b', 'c', 'd'] }); 228 st.end(); 229 }); 230 231 t.test('parses a mix of simple and explicit arrays', function (st) { 232 st.deepEqual(qs.parse('a=b&a[]=c'), { a: ['b', 'c'] }); 233 st.deepEqual(qs.parse('a[]=b&a=c'), { a: ['b', 'c'] }); 234 st.deepEqual(qs.parse('a[0]=b&a=c'), { a: ['b', 'c'] }); 235 st.deepEqual(qs.parse('a=b&a[0]=c'), { a: ['b', 'c'] }); 236 237 st.deepEqual(qs.parse('a[1]=b&a=c', { arrayLimit: 20 }), { a: ['b', 'c'] }); 238 st.deepEqual(qs.parse('a[]=b&a=c', { arrayLimit: 0 }), { a: ['b', 'c'] }); 239 st.deepEqual(qs.parse('a[]=b&a=c'), { a: ['b', 'c'] }); 240 241 st.deepEqual(qs.parse('a=b&a[1]=c', { arrayLimit: 20 }), { a: ['b', 'c'] }); 242 st.deepEqual(qs.parse('a=b&a[]=c', { arrayLimit: 0 }), { a: ['b', 'c'] }); 243 st.deepEqual(qs.parse('a=b&a[]=c'), { a: ['b', 'c'] }); 244 245 st.end(); 246 }); 247 248 t.test('parses a nested array', function (st) { 249 st.deepEqual(qs.parse('a[b][]=c&a[b][]=d'), { a: { b: ['c', 'd'] } }); 250 st.deepEqual(qs.parse('a[>=]=25'), { a: { '>=': '25' } }); 251 st.end(); 252 }); 253 254 t.test('allows to specify array indices', function (st) { 255 st.deepEqual(qs.parse('a[1]=c&a[0]=b&a[2]=d'), { a: ['b', 'c', 'd'] }); 256 st.deepEqual(qs.parse('a[1]=c&a[0]=b'), { a: ['b', 'c'] }); 257 st.deepEqual(qs.parse('a[1]=c', { arrayLimit: 20 }), { a: ['c'] }); 258 st.deepEqual(qs.parse('a[1]=c', { arrayLimit: 0 }), { a: { 1: 'c' } }); 259 st.deepEqual(qs.parse('a[1]=c'), { a: ['c'] }); 260 st.end(); 261 }); 262 263 t.test('limits specific array indices to arrayLimit', function (st) { 264 st.deepEqual(qs.parse('a[20]=a', { arrayLimit: 20 }), { a: ['a'] }); 265 st.deepEqual(qs.parse('a[21]=a', { arrayLimit: 20 }), { a: { 21: 'a' } }); 266 267 st.deepEqual(qs.parse('a[20]=a'), { a: ['a'] }); 268 st.deepEqual(qs.parse('a[21]=a'), { a: { 21: 'a' } }); 269 st.end(); 270 }); 271 272 t.deepEqual(qs.parse('a[12b]=c'), { a: { '12b': 'c' } }, 'supports keys that begin with a number'); 273 274 t.test('supports encoded = signs', function (st) { 275 st.deepEqual(qs.parse('he%3Dllo=th%3Dere'), { 'he=llo': 'th=ere' }); 276 st.end(); 277 }); 278 279 t.test('is ok with url encoded strings', function (st) { 280 st.deepEqual(qs.parse('a[b%20c]=d'), { a: { 'b c': 'd' } }); 281 st.deepEqual(qs.parse('a[b]=c%20d'), { a: { b: 'c d' } }); 282 st.end(); 283 }); 284 285 t.test('allows brackets in the value', function (st) { 286 st.deepEqual(qs.parse('pets=["tobi"]'), { pets: '["tobi"]' }); 287 st.deepEqual(qs.parse('operators=[">=", "<="]'), { operators: '[">=", "<="]' }); 288 st.end(); 289 }); 290 291 t.test('allows empty values', function (st) { 292 st.deepEqual(qs.parse(''), {}); 293 st.deepEqual(qs.parse(null), {}); 294 st.deepEqual(qs.parse(undefined), {}); 295 st.end(); 296 }); 297 298 t.test('transforms arrays to objects', function (st) { 299 st.deepEqual(qs.parse('foo[0]=bar&foo[bad]=baz'), { foo: { 0: 'bar', bad: 'baz' } }); 300 st.deepEqual(qs.parse('foo[bad]=baz&foo[0]=bar'), { foo: { bad: 'baz', 0: 'bar' } }); 301 st.deepEqual(qs.parse('foo[bad]=baz&foo[]=bar'), { foo: { bad: 'baz', 0: 'bar' } }); 302 st.deepEqual(qs.parse('foo[]=bar&foo[bad]=baz'), { foo: { 0: 'bar', bad: 'baz' } }); 303 st.deepEqual(qs.parse('foo[bad]=baz&foo[]=bar&foo[]=foo'), { foo: { bad: 'baz', 0: 'bar', 1: 'foo' } }); 304 st.deepEqual(qs.parse('foo[0][a]=a&foo[0][b]=b&foo[1][a]=aa&foo[1][b]=bb'), { foo: [{ a: 'a', b: 'b' }, { a: 'aa', b: 'bb' }] }); 305 306 st.deepEqual(qs.parse('a[]=b&a[t]=u&a[hasOwnProperty]=c', { allowPrototypes: false }), { a: { 0: 'b', t: 'u' } }); 307 st.deepEqual(qs.parse('a[]=b&a[t]=u&a[hasOwnProperty]=c', { allowPrototypes: true }), { a: { 0: 'b', t: 'u', hasOwnProperty: 'c' } }); 308 st.deepEqual(qs.parse('a[]=b&a[hasOwnProperty]=c&a[x]=y', { allowPrototypes: false }), { a: { 0: 'b', x: 'y' } }); 309 st.deepEqual(qs.parse('a[]=b&a[hasOwnProperty]=c&a[x]=y', { allowPrototypes: true }), { a: { 0: 'b', hasOwnProperty: 'c', x: 'y' } }); 310 st.end(); 311 }); 312 313 t.test('transforms arrays to objects (dot notation)', function (st) { 314 st.deepEqual(qs.parse('foo[0].baz=bar&fool.bad=baz', { allowDots: true }), { foo: [{ baz: 'bar' }], fool: { bad: 'baz' } }); 315 st.deepEqual(qs.parse('foo[0].baz=bar&fool.bad.boo=baz', { allowDots: true }), { foo: [{ baz: 'bar' }], fool: { bad: { boo: 'baz' } } }); 316 st.deepEqual(qs.parse('foo[0][0].baz=bar&fool.bad=baz', { allowDots: true }), { foo: [[{ baz: 'bar' }]], fool: { bad: 'baz' } }); 317 st.deepEqual(qs.parse('foo[0].baz[0]=15&foo[0].bar=2', { allowDots: true }), { foo: [{ baz: ['15'], bar: '2' }] }); 318 st.deepEqual(qs.parse('foo[0].baz[0]=15&foo[0].baz[1]=16&foo[0].bar=2', { allowDots: true }), { foo: [{ baz: ['15', '16'], bar: '2' }] }); 319 st.deepEqual(qs.parse('foo.bad=baz&foo[0]=bar', { allowDots: true }), { foo: { bad: 'baz', 0: 'bar' } }); 320 st.deepEqual(qs.parse('foo.bad=baz&foo[]=bar', { allowDots: true }), { foo: { bad: 'baz', 0: 'bar' } }); 321 st.deepEqual(qs.parse('foo[]=bar&foo.bad=baz', { allowDots: true }), { foo: { 0: 'bar', bad: 'baz' } }); 322 st.deepEqual(qs.parse('foo.bad=baz&foo[]=bar&foo[]=foo', { allowDots: true }), { foo: { bad: 'baz', 0: 'bar', 1: 'foo' } }); 323 st.deepEqual(qs.parse('foo[0].a=a&foo[0].b=b&foo[1].a=aa&foo[1].b=bb', { allowDots: true }), { foo: [{ a: 'a', b: 'b' }, { a: 'aa', b: 'bb' }] }); 324 st.end(); 325 }); 326 327 t.test('correctly prunes undefined values when converting an array to an object', function (st) { 328 st.deepEqual(qs.parse('a[2]=b&a[99999999]=c'), { a: { 2: 'b', 99999999: 'c' } }); 329 st.end(); 330 }); 331 332 t.test('supports malformed uri characters', function (st) { 333 st.deepEqual(qs.parse('{%:%}', { strictNullHandling: true }), { '{%:%}': null }); 334 st.deepEqual(qs.parse('{%:%}='), { '{%:%}': '' }); 335 st.deepEqual(qs.parse('foo=%:%}'), { foo: '%:%}' }); 336 st.end(); 337 }); 338 339 t.test('doesn\'t produce empty keys', function (st) { 340 st.deepEqual(qs.parse('_r=1&'), { _r: '1' }); 341 st.end(); 342 }); 343 344 t.test('cannot access Object prototype', function (st) { 345 qs.parse('constructor[prototype][bad]=bad'); 346 qs.parse('bad[constructor][prototype][bad]=bad'); 347 st.equal(typeof Object.prototype.bad, 'undefined'); 348 st.end(); 349 }); 350 351 t.test('parses arrays of objects', function (st) { 352 st.deepEqual(qs.parse('a[][b]=c'), { a: [{ b: 'c' }] }); 353 st.deepEqual(qs.parse('a[0][b]=c'), { a: [{ b: 'c' }] }); 354 st.end(); 355 }); 356 357 t.test('allows for empty strings in arrays', function (st) { 358 st.deepEqual(qs.parse('a[]=b&a[]=&a[]=c'), { a: ['b', '', 'c'] }); 359 360 st.deepEqual( 361 qs.parse('a[0]=b&a[1]&a[2]=c&a[19]=', { strictNullHandling: true, arrayLimit: 20 }), 362 { a: ['b', null, 'c', ''] }, 363 'with arrayLimit 20 + array indices: null then empty string works' 364 ); 365 st.deepEqual( 366 qs.parse('a[]=b&a[]&a[]=c&a[]=', { strictNullHandling: true, arrayLimit: 0 }), 367 { a: ['b', null, 'c', ''] }, 368 'with arrayLimit 0 + array brackets: null then empty string works' 369 ); 370 371 st.deepEqual( 372 qs.parse('a[0]=b&a[1]=&a[2]=c&a[19]', { strictNullHandling: true, arrayLimit: 20 }), 373 { a: ['b', '', 'c', null] }, 374 'with arrayLimit 20 + array indices: empty string then null works' 375 ); 376 st.deepEqual( 377 qs.parse('a[]=b&a[]=&a[]=c&a[]', { strictNullHandling: true, arrayLimit: 0 }), 378 { a: ['b', '', 'c', null] }, 379 'with arrayLimit 0 + array brackets: empty string then null works' 380 ); 381 382 st.deepEqual( 383 qs.parse('a[]=&a[]=b&a[]=c'), 384 { a: ['', 'b', 'c'] }, 385 'array brackets: empty strings work' 386 ); 387 st.end(); 388 }); 389 390 t.test('compacts sparse arrays', function (st) { 391 st.deepEqual(qs.parse('a[10]=1&a[2]=2', { arrayLimit: 20 }), { a: ['2', '1'] }); 392 st.deepEqual(qs.parse('a[1][b][2][c]=1', { arrayLimit: 20 }), { a: [{ b: [{ c: '1' }] }] }); 393 st.deepEqual(qs.parse('a[1][2][3][c]=1', { arrayLimit: 20 }), { a: [[[{ c: '1' }]]] }); 394 st.deepEqual(qs.parse('a[1][2][3][c][1]=1', { arrayLimit: 20 }), { a: [[[{ c: ['1'] }]]] }); 395 st.end(); 396 }); 397 398 t.test('parses sparse arrays', function (st) { 399 /* eslint no-sparse-arrays: 0 */ 400 st.deepEqual(qs.parse('a[4]=1&a[1]=2', { allowSparse: true }), { a: [, '2', , , '1'] }); 401 st.deepEqual(qs.parse('a[1][b][2][c]=1', { allowSparse: true }), { a: [, { b: [, , { c: '1' }] }] }); 402 st.deepEqual(qs.parse('a[1][2][3][c]=1', { allowSparse: true }), { a: [, [, , [, , , { c: '1' }]]] }); 403 st.deepEqual(qs.parse('a[1][2][3][c][1]=1', { allowSparse: true }), { a: [, [, , [, , , { c: [, '1'] }]]] }); 404 st.end(); 405 }); 406 407 t.test('parses semi-parsed strings', function (st) { 408 st.deepEqual(qs.parse({ 'a[b]': 'c' }), { a: { b: 'c' } }); 409 st.deepEqual(qs.parse({ 'a[b]': 'c', 'a[d]': 'e' }), { a: { b: 'c', d: 'e' } }); 410 st.end(); 411 }); 412 413 t.test('parses buffers correctly', function (st) { 414 var b = SaferBuffer.from('test'); 415 st.deepEqual(qs.parse({ a: b }), { a: b }); 416 st.end(); 417 }); 418 419 t.test('parses jquery-param strings', function (st) { 420 // readable = 'filter[0][]=int1&filter[0][]==&filter[0][]=77&filter[]=and&filter[2][]=int2&filter[2][]==&filter[2][]=8' 421 var encoded = 'filter%5B0%5D%5B%5D=int1&filter%5B0%5D%5B%5D=%3D&filter%5B0%5D%5B%5D=77&filter%5B%5D=and&filter%5B2%5D%5B%5D=int2&filter%5B2%5D%5B%5D=%3D&filter%5B2%5D%5B%5D=8'; 422 var expected = { filter: [['int1', '=', '77'], 'and', ['int2', '=', '8']] }; 423 st.deepEqual(qs.parse(encoded), expected); 424 st.end(); 425 }); 426 427 t.test('continues parsing when no parent is found', function (st) { 428 st.deepEqual(qs.parse('[]=&a=b'), { 0: '', a: 'b' }); 429 st.deepEqual(qs.parse('[]&a=b', { strictNullHandling: true }), { 0: null, a: 'b' }); 430 st.deepEqual(qs.parse('[foo]=bar'), { foo: 'bar' }); 431 st.end(); 432 }); 433 434 t.test('does not error when parsing a very long array', function (st) { 435 var str = 'a[]=a'; 436 while (Buffer.byteLength(str) < 128 * 1024) { 437 str = str + '&' + str; 438 } 439 440 st.doesNotThrow(function () { 441 qs.parse(str); 442 }); 443 444 st.end(); 445 }); 446 447 t.test('does not throw when a native prototype has an enumerable property', function (st) { 448 st.intercept(Object.prototype, 'crash', { value: '' }); 449 st.intercept(Array.prototype, 'crash', { value: '' }); 450 451 st.doesNotThrow(qs.parse.bind(null, 'a=b')); 452 st.deepEqual(qs.parse('a=b'), { a: 'b' }); 453 st.doesNotThrow(qs.parse.bind(null, 'a[][b]=c')); 454 st.deepEqual(qs.parse('a[][b]=c'), { a: [{ b: 'c' }] }); 455 456 st.end(); 457 }); 458 459 t.test('parses a string with an alternative string delimiter', function (st) { 460 st.deepEqual(qs.parse('a=b;c=d', { delimiter: ';' }), { a: 'b', c: 'd' }); 461 st.end(); 462 }); 463 464 t.test('parses a string with an alternative RegExp delimiter', function (st) { 465 st.deepEqual(qs.parse('a=b; c=d', { delimiter: /[;,] */ }), { a: 'b', c: 'd' }); 466 st.end(); 467 }); 468 469 t.test('does not use non-splittable objects as delimiters', function (st) { 470 st.deepEqual(qs.parse('a=b&c=d', { delimiter: true }), { a: 'b', c: 'd' }); 471 st.end(); 472 }); 473 474 t.test('allows overriding parameter limit', function (st) { 475 st.deepEqual(qs.parse('a=b&c=d', { parameterLimit: 1 }), { a: 'b' }); 476 st.end(); 477 }); 478 479 t.test('allows setting the parameter limit to Infinity', function (st) { 480 st.deepEqual(qs.parse('a=b&c=d', { parameterLimit: Infinity }), { a: 'b', c: 'd' }); 481 st.end(); 482 }); 483 484 t.test('allows overriding array limit', function (st) { 485 st.deepEqual(qs.parse('a[0]=b', { arrayLimit: -1 }), { a: { 0: 'b' } }); 486 st.deepEqual(qs.parse('a[0]=b', { arrayLimit: 0 }), { a: ['b'] }); 487 488 st.deepEqual(qs.parse('a[-1]=b', { arrayLimit: -1 }), { a: { '-1': 'b' } }); 489 st.deepEqual(qs.parse('a[-1]=b', { arrayLimit: 0 }), { a: { '-1': 'b' } }); 490 491 st.deepEqual(qs.parse('a[0]=b&a[1]=c', { arrayLimit: -1 }), { a: { 0: 'b', 1: 'c' } }); 492 st.deepEqual(qs.parse('a[0]=b&a[1]=c', { arrayLimit: 0 }), { a: { 0: 'b', 1: 'c' } }); 493 494 st.end(); 495 }); 496 497 t.test('allows disabling array parsing', function (st) { 498 var indices = qs.parse('a[0]=b&a[1]=c', { parseArrays: false }); 499 st.deepEqual(indices, { a: { 0: 'b', 1: 'c' } }); 500 st.equal(Array.isArray(indices.a), false, 'parseArrays:false, indices case is not an array'); 501 502 var emptyBrackets = qs.parse('a[]=b', { parseArrays: false }); 503 st.deepEqual(emptyBrackets, { a: { 0: 'b' } }); 504 st.equal(Array.isArray(emptyBrackets.a), false, 'parseArrays:false, empty brackets case is not an array'); 505 506 st.end(); 507 }); 508 509 t.test('allows for query string prefix', function (st) { 510 st.deepEqual(qs.parse('?foo=bar', { ignoreQueryPrefix: true }), { foo: 'bar' }); 511 st.deepEqual(qs.parse('foo=bar', { ignoreQueryPrefix: true }), { foo: 'bar' }); 512 st.deepEqual(qs.parse('?foo=bar', { ignoreQueryPrefix: false }), { '?foo': 'bar' }); 513 514 st.end(); 515 }); 516 517 t.test('parses an object', function (st) { 518 var input = { 519 'user[name]': { 'pop[bob]': 3 }, 520 'user[email]': null 521 }; 522 523 var expected = { 524 user: { 525 name: { 'pop[bob]': 3 }, 526 email: null 527 } 528 }; 529 530 var result = qs.parse(input); 531 532 st.deepEqual(result, expected); 533 st.end(); 534 }); 535 536 t.test('parses string with comma as array divider', function (st) { 537 st.deepEqual(qs.parse('foo=bar,tee', { comma: true }), { foo: ['bar', 'tee'] }); 538 st.deepEqual(qs.parse('foo[bar]=coffee,tee', { comma: true }), { foo: { bar: ['coffee', 'tee'] } }); 539 st.deepEqual(qs.parse('foo=', { comma: true }), { foo: '' }); 540 st.deepEqual(qs.parse('foo', { comma: true }), { foo: '' }); 541 st.deepEqual(qs.parse('foo', { comma: true, strictNullHandling: true }), { foo: null }); 542 543 // test cases inversed from from stringify tests 544 st.deepEqual(qs.parse('a[0]=c'), { a: ['c'] }); 545 st.deepEqual(qs.parse('a[]=c'), { a: ['c'] }); 546 st.deepEqual(qs.parse('a[]=c', { comma: true }), { a: ['c'] }); 547 548 st.deepEqual(qs.parse('a[0]=c&a[1]=d'), { a: ['c', 'd'] }); 549 st.deepEqual(qs.parse('a[]=c&a[]=d'), { a: ['c', 'd'] }); 550 st.deepEqual(qs.parse('a=c,d', { comma: true }), { a: ['c', 'd'] }); 551 552 st.end(); 553 }); 554 555 t.test('parses values with comma as array divider', function (st) { 556 st.deepEqual(qs.parse({ foo: 'bar,tee' }, { comma: false }), { foo: 'bar,tee' }); 557 st.deepEqual(qs.parse({ foo: 'bar,tee' }, { comma: true }), { foo: ['bar', 'tee'] }); 558 st.end(); 559 }); 560 561 t.test('use number decoder, parses string that has one number with comma option enabled', function (st) { 562 var decoder = function (str, defaultDecoder, charset, type) { 563 if (!isNaN(Number(str))) { 564 return parseFloat(str); 565 } 566 return defaultDecoder(str, defaultDecoder, charset, type); 567 }; 568 569 st.deepEqual(qs.parse('foo=1', { comma: true, decoder: decoder }), { foo: 1 }); 570 st.deepEqual(qs.parse('foo=0', { comma: true, decoder: decoder }), { foo: 0 }); 571 572 st.end(); 573 }); 574 575 t.test('parses brackets holds array of arrays when having two parts of strings with comma as array divider', function (st) { 576 st.deepEqual(qs.parse('foo[]=1,2,3&foo[]=4,5,6', { comma: true }), { foo: [['1', '2', '3'], ['4', '5', '6']] }); 577 st.deepEqual(qs.parse('foo[]=1,2,3&foo[]=', { comma: true }), { foo: [['1', '2', '3'], ''] }); 578 st.deepEqual(qs.parse('foo[]=1,2,3&foo[]=,', { comma: true }), { foo: [['1', '2', '3'], ['', '']] }); 579 st.deepEqual(qs.parse('foo[]=1,2,3&foo[]=a', { comma: true }), { foo: [['1', '2', '3'], 'a'] }); 580 581 st.end(); 582 }); 583 584 t.test('parses url-encoded brackets holds array of arrays when having two parts of strings with comma as array divider', function (st) { 585 st.deepEqual(qs.parse('foo%5B%5D=1,2,3&foo%5B%5D=4,5,6', { comma: true }), { foo: [['1', '2', '3'], ['4', '5', '6']] }); 586 st.deepEqual(qs.parse('foo%5B%5D=1,2,3&foo%5B%5D=', { comma: true }), { foo: [['1', '2', '3'], ''] }); 587 st.deepEqual(qs.parse('foo%5B%5D=1,2,3&foo%5B%5D=,', { comma: true }), { foo: [['1', '2', '3'], ['', '']] }); 588 st.deepEqual(qs.parse('foo%5B%5D=1,2,3&foo%5B%5D=a', { comma: true }), { foo: [['1', '2', '3'], 'a'] }); 589 590 st.end(); 591 }); 592 593 t.test('parses comma delimited array while having percent-encoded comma treated as normal text', function (st) { 594 st.deepEqual(qs.parse('foo=a%2Cb', { comma: true }), { foo: 'a,b' }); 595 st.deepEqual(qs.parse('foo=a%2C%20b,d', { comma: true }), { foo: ['a, b', 'd'] }); 596 st.deepEqual(qs.parse('foo=a%2C%20b,c%2C%20d', { comma: true }), { foo: ['a, b', 'c, d'] }); 597 598 st.end(); 599 }); 600 601 t.test('parses an object in dot notation', function (st) { 602 var input = { 603 'user.name': { 'pop[bob]': 3 }, 604 'user.email.': null 605 }; 606 607 var expected = { 608 user: { 609 name: { 'pop[bob]': 3 }, 610 email: null 611 } 612 }; 613 614 var result = qs.parse(input, { allowDots: true }); 615 616 st.deepEqual(result, expected); 617 st.end(); 618 }); 619 620 t.test('parses an object and not child values', function (st) { 621 var input = { 622 'user[name]': { 'pop[bob]': { test: 3 } }, 623 'user[email]': null 624 }; 625 626 var expected = { 627 user: { 628 name: { 'pop[bob]': { test: 3 } }, 629 email: null 630 } 631 }; 632 633 var result = qs.parse(input); 634 635 st.deepEqual(result, expected); 636 st.end(); 637 }); 638 639 t.test('does not blow up when Buffer global is missing', function (st) { 640 var restore = mockProperty(global, 'Buffer', { 'delete': true }); 641 642 var result = qs.parse('a=b&c=d'); 643 644 restore(); 645 646 st.deepEqual(result, { a: 'b', c: 'd' }); 647 st.end(); 648 }); 649 650 t.test('does not crash when parsing circular references', function (st) { 651 var a = {}; 652 a.b = a; 653 654 var parsed; 655 656 st.doesNotThrow(function () { 657 parsed = qs.parse({ 'foo[bar]': 'baz', 'foo[baz]': a }); 658 }); 659 660 st.equal('foo' in parsed, true, 'parsed has "foo" property'); 661 st.equal('bar' in parsed.foo, true); 662 st.equal('baz' in parsed.foo, true); 663 st.equal(parsed.foo.bar, 'baz'); 664 st.deepEqual(parsed.foo.baz, a); 665 st.end(); 666 }); 667 668 t.test('does not crash when parsing deep objects', function (st) { 669 var parsed; 670 var str = 'foo'; 671 672 for (var i = 0; i < 5000; i++) { 673 str += '[p]'; 674 } 675 676 str += '=bar'; 677 678 st.doesNotThrow(function () { 679 parsed = qs.parse(str, { depth: 5000 }); 680 }); 681 682 st.equal('foo' in parsed, true, 'parsed has "foo" property'); 683 684 var depth = 0; 685 var ref = parsed.foo; 686 while ((ref = ref.p)) { 687 depth += 1; 688 } 689 690 st.equal(depth, 5000, 'parsed is 5000 properties deep'); 691 692 st.end(); 693 }); 694 695 t.test('parses null objects correctly', { skip: !hasProto }, function (st) { 696 var a = { __proto__: null, b: 'c' }; 697 698 st.deepEqual(qs.parse(a), { b: 'c' }); 699 var result = qs.parse({ a: a }); 700 st.equal('a' in result, true, 'result has "a" property'); 701 st.deepEqual(result.a, a); 702 st.end(); 703 }); 704 705 t.test('parses dates correctly', function (st) { 706 var now = new Date(); 707 st.deepEqual(qs.parse({ a: now }), { a: now }); 708 st.end(); 709 }); 710 711 t.test('parses regular expressions correctly', function (st) { 712 var re = /^test$/; 713 st.deepEqual(qs.parse({ a: re }), { a: re }); 714 st.end(); 715 }); 716 717 t.test('does not allow overwriting prototype properties', function (st) { 718 st.deepEqual(qs.parse('a[hasOwnProperty]=b', { allowPrototypes: false }), {}); 719 st.deepEqual(qs.parse('hasOwnProperty=b', { allowPrototypes: false }), {}); 720 721 st.deepEqual( 722 qs.parse('toString', { allowPrototypes: false }), 723 {}, 724 'bare "toString" results in {}' 725 ); 726 727 st.end(); 728 }); 729 730 t.test('can allow overwriting prototype properties', function (st) { 731 st.deepEqual(qs.parse('a[hasOwnProperty]=b', { allowPrototypes: true }), { a: { hasOwnProperty: 'b' } }); 732 st.deepEqual(qs.parse('hasOwnProperty=b', { allowPrototypes: true }), { hasOwnProperty: 'b' }); 733 734 st.deepEqual( 735 qs.parse('toString', { allowPrototypes: true }), 736 { toString: '' }, 737 'bare "toString" results in { toString: "" }' 738 ); 739 740 st.end(); 741 }); 742 743 t.test('does not crash when the global Object prototype is frozen', { skip: !hasPropertyDescriptors || !hasOverrideMistake }, function (st) { 744 // We can't actually freeze the global Object prototype as that will interfere with other tests, and once an object is frozen, it 745 // can't be unfrozen. Instead, we add a new non-writable property to simulate this. 746 st.teardown(mockProperty(Object.prototype, 'frozenProp', { value: 'foo', nonWritable: true, nonEnumerable: true })); 747 748 st['throws']( 749 function () { 750 var obj = {}; 751 obj.frozenProp = 'bar'; 752 }, 753 // node < 6 has a different error message 754 /^TypeError: Cannot assign to read only property 'frozenProp' of (?:object '#<Object>'|#<Object>)/, 755 'regular assignment of an inherited non-writable property throws' 756 ); 757 758 var parsed; 759 st.doesNotThrow( 760 function () { 761 parsed = qs.parse('frozenProp', { allowPrototypes: false }); 762 }, 763 'parsing a nonwritable Object.prototype property does not throw' 764 ); 765 766 st.deepEqual(parsed, {}, 'bare "frozenProp" results in {}'); 767 768 st.end(); 769 }); 770 771 t.test('params starting with a closing bracket', function (st) { 772 st.deepEqual(qs.parse(']=toString'), { ']': 'toString' }); 773 st.deepEqual(qs.parse(']]=toString'), { ']]': 'toString' }); 774 st.deepEqual(qs.parse(']hello]=toString'), { ']hello]': 'toString' }); 775 st.end(); 776 }); 777 778 t.test('params starting with a starting bracket', function (st) { 779 st.deepEqual(qs.parse('[=toString'), { '[': 'toString' }); 780 st.deepEqual(qs.parse('[[=toString'), { '[[': 'toString' }); 781 st.deepEqual(qs.parse('[hello[=toString'), { '[hello[': 'toString' }); 782 st.end(); 783 }); 784 785 t.test('add keys to objects', function (st) { 786 st.deepEqual( 787 qs.parse('a[b]=c&a=d'), 788 { a: { b: 'c', d: true } }, 789 'can add keys to objects' 790 ); 791 792 st.deepEqual( 793 qs.parse('a[b]=c&a=toString'), 794 { a: { b: 'c' } }, 795 'can not overwrite prototype' 796 ); 797 798 st.deepEqual( 799 qs.parse('a[b]=c&a=toString', { allowPrototypes: true }), 800 { a: { b: 'c', toString: true } }, 801 'can overwrite prototype with allowPrototypes true' 802 ); 803 804 st.deepEqual( 805 qs.parse('a[b]=c&a=toString', { plainObjects: true }), 806 { __proto__: null, a: { __proto__: null, b: 'c', toString: true } }, 807 'can overwrite prototype with plainObjects true' 808 ); 809 810 st.end(); 811 }); 812 813 t.test('dunder proto is ignored', function (st) { 814 var payload = 'categories[__proto__]=login&categories[__proto__]&categories[length]=42'; 815 var result = qs.parse(payload, { allowPrototypes: true }); 816 817 st.deepEqual( 818 result, 819 { 820 categories: { 821 length: '42' 822 } 823 }, 824 'silent [[Prototype]] payload' 825 ); 826 827 var plainResult = qs.parse(payload, { allowPrototypes: true, plainObjects: true }); 828 829 st.deepEqual( 830 plainResult, 831 { 832 __proto__: null, 833 categories: { 834 __proto__: null, 835 length: '42' 836 } 837 }, 838 'silent [[Prototype]] payload: plain objects' 839 ); 840 841 var query = qs.parse('categories[__proto__]=cats&categories[__proto__]=dogs&categories[some][json]=toInject', { allowPrototypes: true }); 842 843 st.notOk(Array.isArray(query.categories), 'is not an array'); 844 st.notOk(query.categories instanceof Array, 'is not instanceof an array'); 845 st.deepEqual(query.categories, { some: { json: 'toInject' } }); 846 st.equal(JSON.stringify(query.categories), '{"some":{"json":"toInject"}}', 'stringifies as a non-array'); 847 848 st.deepEqual( 849 qs.parse('foo[__proto__][hidden]=value&foo[bar]=stuffs', { allowPrototypes: true }), 850 { 851 foo: { 852 bar: 'stuffs' 853 } 854 }, 855 'hidden values' 856 ); 857 858 st.deepEqual( 859 qs.parse('foo[__proto__][hidden]=value&foo[bar]=stuffs', { allowPrototypes: true, plainObjects: true }), 860 { 861 __proto__: null, 862 foo: { 863 __proto__: null, 864 bar: 'stuffs' 865 } 866 }, 867 'hidden values: plain objects' 868 ); 869 870 st.end(); 871 }); 872 873 t.test('can return null objects', { skip: !hasProto }, function (st) { 874 var expected = { 875 __proto__: null, 876 a: { 877 __proto__: null, 878 b: 'c', 879 hasOwnProperty: 'd' 880 } 881 }; 882 st.deepEqual(qs.parse('a[b]=c&a[hasOwnProperty]=d', { plainObjects: true }), expected); 883 st.deepEqual(qs.parse(null, { plainObjects: true }), { __proto__: null }); 884 var expectedArray = { 885 __proto__: null, 886 a: { 887 __proto__: null, 888 0: 'b', 889 c: 'd' 890 } 891 }; 892 st.deepEqual(qs.parse('a[]=b&a[c]=d', { plainObjects: true }), expectedArray); 893 st.end(); 894 }); 895 896 t.test('can parse with custom encoding', function (st) { 897 st.deepEqual(qs.parse('%8c%a7=%91%e5%8d%e3%95%7b', { 898 decoder: function (str) { 899 var reg = /%([0-9A-F]{2})/ig; 900 var result = []; 901 var parts = reg.exec(str); 902 while (parts) { 903 result.push(parseInt(parts[1], 16)); 904 parts = reg.exec(str); 905 } 906 return String(iconv.decode(SaferBuffer.from(result), 'shift_jis')); 907 } 908 }), { 県: '大阪府' }); 909 st.end(); 910 }); 911 912 t.test('receives the default decoder as a second argument', function (st) { 913 st.plan(1); 914 qs.parse('a', { 915 decoder: function (str, defaultDecoder) { 916 st.equal(defaultDecoder, utils.decode); 917 } 918 }); 919 st.end(); 920 }); 921 922 t.test('throws error with wrong decoder', function (st) { 923 st['throws'](function () { 924 qs.parse({}, { decoder: 'string' }); 925 }, new TypeError('Decoder has to be a function.')); 926 st.end(); 927 }); 928 929 t.test('does not mutate the options argument', function (st) { 930 var options = {}; 931 qs.parse('a[b]=true', options); 932 st.deepEqual(options, {}); 933 st.end(); 934 }); 935 936 t.test('throws if an invalid charset is specified', function (st) { 937 st['throws'](function () { 938 qs.parse('a=b', { charset: 'foobar' }); 939 }, new TypeError('The charset option must be either utf-8, iso-8859-1, or undefined')); 940 st.end(); 941 }); 942 943 t.test('parses an iso-8859-1 string if asked to', function (st) { 944 st.deepEqual(qs.parse('%A2=%BD', { charset: 'iso-8859-1' }), { '¢': '½' }); 945 st.end(); 946 }); 947 948 var urlEncodedCheckmarkInUtf8 = '%E2%9C%93'; 949 var urlEncodedOSlashInUtf8 = '%C3%B8'; 950 var urlEncodedNumCheckmark = '%26%2310003%3B'; 951 var urlEncodedNumSmiley = '%26%239786%3B'; 952 953 t.test('prefers an utf-8 charset specified by the utf8 sentinel to a default charset of iso-8859-1', function (st) { 954 st.deepEqual(qs.parse('utf8=' + urlEncodedCheckmarkInUtf8 + '&' + urlEncodedOSlashInUtf8 + '=' + urlEncodedOSlashInUtf8, { charsetSentinel: true, charset: 'iso-8859-1' }), { ø: 'ø' }); 955 st.end(); 956 }); 957 958 t.test('prefers an iso-8859-1 charset specified by the utf8 sentinel to a default charset of utf-8', function (st) { 959 st.deepEqual(qs.parse('utf8=' + urlEncodedNumCheckmark + '&' + urlEncodedOSlashInUtf8 + '=' + urlEncodedOSlashInUtf8, { charsetSentinel: true, charset: 'utf-8' }), { 'ø': 'ø' }); 960 st.end(); 961 }); 962 963 t.test('does not require the utf8 sentinel to be defined before the parameters whose decoding it affects', function (st) { 964 st.deepEqual(qs.parse('a=' + urlEncodedOSlashInUtf8 + '&utf8=' + urlEncodedNumCheckmark, { charsetSentinel: true, charset: 'utf-8' }), { a: 'ø' }); 965 st.end(); 966 }); 967 968 t.test('ignores an utf8 sentinel with an unknown value', function (st) { 969 st.deepEqual(qs.parse('utf8=foo&' + urlEncodedOSlashInUtf8 + '=' + urlEncodedOSlashInUtf8, { charsetSentinel: true, charset: 'utf-8' }), { ø: 'ø' }); 970 st.end(); 971 }); 972 973 t.test('uses the utf8 sentinel to switch to utf-8 when no default charset is given', function (st) { 974 st.deepEqual(qs.parse('utf8=' + urlEncodedCheckmarkInUtf8 + '&' + urlEncodedOSlashInUtf8 + '=' + urlEncodedOSlashInUtf8, { charsetSentinel: true }), { ø: 'ø' }); 975 st.end(); 976 }); 977 978 t.test('uses the utf8 sentinel to switch to iso-8859-1 when no default charset is given', function (st) { 979 st.deepEqual(qs.parse('utf8=' + urlEncodedNumCheckmark + '&' + urlEncodedOSlashInUtf8 + '=' + urlEncodedOSlashInUtf8, { charsetSentinel: true }), { 'ø': 'ø' }); 980 st.end(); 981 }); 982 983 t.test('interprets numeric entities in iso-8859-1 when `interpretNumericEntities`', function (st) { 984 st.deepEqual(qs.parse('foo=' + urlEncodedNumSmiley, { charset: 'iso-8859-1', interpretNumericEntities: true }), { foo: '☺' }); 985 st.end(); 986 }); 987 988 t.test('handles a custom decoder returning `null`, in the `iso-8859-1` charset, when `interpretNumericEntities`', function (st) { 989 st.deepEqual(qs.parse('foo=&bar=' + urlEncodedNumSmiley, { 990 charset: 'iso-8859-1', 991 decoder: function (str, defaultDecoder, charset) { 992 return str ? defaultDecoder(str, defaultDecoder, charset) : null; 993 }, 994 interpretNumericEntities: true 995 }), { foo: null, bar: '☺' }); 996 st.end(); 997 }); 998 999 t.test('does not interpret numeric entities in iso-8859-1 when `interpretNumericEntities` is absent', function (st) { 1000 st.deepEqual(qs.parse('foo=' + urlEncodedNumSmiley, { charset: 'iso-8859-1' }), { foo: '☺' }); 1001 st.end(); 1002 }); 1003 1004 t.test('does not interpret numeric entities when the charset is utf-8, even when `interpretNumericEntities`', function (st) { 1005 st.deepEqual(qs.parse('foo=' + urlEncodedNumSmiley, { charset: 'utf-8', interpretNumericEntities: true }), { foo: '☺' }); 1006 st.end(); 1007 }); 1008 1009 t.test('interpretNumericEntities with comma:true and iso charset does not crash', function (st) { 1010 st.deepEqual( 1011 qs.parse('b&a[]=1,' + urlEncodedNumSmiley, { comma: true, charset: 'iso-8859-1', interpretNumericEntities: true }), 1012 { b: '', a: ['1,☺'] } 1013 ); 1014 1015 st.end(); 1016 }); 1017 1018 t.test('does not interpret %uXXXX syntax in iso-8859-1 mode', function (st) { 1019 st.deepEqual(qs.parse('%u263A=%u263A', { charset: 'iso-8859-1' }), { '%u263A': '%u263A' }); 1020 st.end(); 1021 }); 1022 1023 t.test('allows for decoding keys and values differently', function (st) { 1024 var decoder = function (str, defaultDecoder, charset, type) { 1025 if (type === 'key') { 1026 return defaultDecoder(str, defaultDecoder, charset, type).toLowerCase(); 1027 } 1028 if (type === 'value') { 1029 return defaultDecoder(str, defaultDecoder, charset, type).toUpperCase(); 1030 } 1031 throw 'this should never happen! type: ' + type; 1032 }; 1033 1034 st.deepEqual(qs.parse('KeY=vAlUe', { decoder: decoder }), { key: 'VALUE' }); 1035 st.end(); 1036 }); 1037 1038 t.test('parameter limit tests', function (st) { 1039 st.test('does not throw error when within parameter limit', function (sst) { 1040 var result = qs.parse('a=1&b=2&c=3', { parameterLimit: 5, throwOnLimitExceeded: true }); 1041 sst.deepEqual(result, { a: '1', b: '2', c: '3' }, 'parses without errors'); 1042 sst.end(); 1043 }); 1044 1045 st.test('throws error when throwOnLimitExceeded is present but not boolean', function (sst) { 1046 sst['throws']( 1047 function () { 1048 qs.parse('a=1&b=2&c=3&d=4&e=5&f=6', { parameterLimit: 3, throwOnLimitExceeded: 'true' }); 1049 }, 1050 new TypeError('`throwOnLimitExceeded` option must be a boolean'), 1051 'throws error when throwOnLimitExceeded is present and not boolean' 1052 ); 1053 sst.end(); 1054 }); 1055 1056 st.test('throws error when parameter limit exceeded', function (sst) { 1057 sst['throws']( 1058 function () { 1059 qs.parse('a=1&b=2&c=3&d=4&e=5&f=6', { parameterLimit: 3, throwOnLimitExceeded: true }); 1060 }, 1061 new RangeError('Parameter limit exceeded. Only 3 parameters allowed.'), 1062 'throws error when parameter limit is exceeded' 1063 ); 1064 sst.end(); 1065 }); 1066 1067 st.test('silently truncates when throwOnLimitExceeded is not given', function (sst) { 1068 var result = qs.parse('a=1&b=2&c=3&d=4&e=5', { parameterLimit: 3 }); 1069 sst.deepEqual(result, { a: '1', b: '2', c: '3' }, 'parses and truncates silently'); 1070 sst.end(); 1071 }); 1072 1073 st.test('silently truncates when parameter limit exceeded without error', function (sst) { 1074 var result = qs.parse('a=1&b=2&c=3&d=4&e=5', { parameterLimit: 3, throwOnLimitExceeded: false }); 1075 sst.deepEqual(result, { a: '1', b: '2', c: '3' }, 'parses and truncates silently'); 1076 sst.end(); 1077 }); 1078 1079 st.test('allows unlimited parameters when parameterLimit set to Infinity', function (sst) { 1080 var result = qs.parse('a=1&b=2&c=3&d=4&e=5&f=6', { parameterLimit: Infinity }); 1081 sst.deepEqual(result, { a: '1', b: '2', c: '3', d: '4', e: '5', f: '6' }, 'parses all parameters without truncation'); 1082 sst.end(); 1083 }); 1084 1085 st.end(); 1086 }); 1087 1088 t.test('array limit tests', function (st) { 1089 st.test('does not throw error when array is within limit', function (sst) { 1090 var result = qs.parse('a[]=1&a[]=2&a[]=3', { arrayLimit: 5, throwOnLimitExceeded: true }); 1091 sst.deepEqual(result, { a: ['1', '2', '3'] }, 'parses array without errors'); 1092 sst.end(); 1093 }); 1094 1095 st.test('throws error when throwOnLimitExceeded is present but not boolean for array limit', function (sst) { 1096 sst['throws']( 1097 function () { 1098 qs.parse('a[]=1&a[]=2&a[]=3&a[]=4', { arrayLimit: 3, throwOnLimitExceeded: 'true' }); 1099 }, 1100 new TypeError('`throwOnLimitExceeded` option must be a boolean'), 1101 'throws error when throwOnLimitExceeded is present and not boolean for array limit' 1102 ); 1103 sst.end(); 1104 }); 1105 1106 st.test('throws error when array limit exceeded', function (sst) { 1107 sst['throws']( 1108 function () { 1109 qs.parse('a[]=1&a[]=2&a[]=3&a[]=4', { arrayLimit: 3, throwOnLimitExceeded: true }); 1110 }, 1111 new RangeError('Array limit exceeded. Only 3 elements allowed in an array.'), 1112 'throws error when array limit is exceeded' 1113 ); 1114 sst.end(); 1115 }); 1116 1117 st.test('converts array to object if length is greater than limit', function (sst) { 1118 var result = qs.parse('a[1]=1&a[2]=2&a[3]=3&a[4]=4&a[5]=5&a[6]=6', { arrayLimit: 5 }); 1119 1120 sst.deepEqual(result, { a: { 1: '1', 2: '2', 3: '3', 4: '4', 5: '5', 6: '6' } }, 'parses into object if array length is greater than limit'); 1121 sst.end(); 1122 }); 1123 1124 st.end(); 1125 }); 1126 1127 t.end(); 1128 }); 1129 1130 test('parses empty keys', function (t) { 1131 emptyTestCases.forEach(function (testCase) { 1132 t.test('skips empty string key with ' + testCase.input, function (st) { 1133 st.deepEqual(qs.parse(testCase.input), testCase.noEmptyKeys); 1134 1135 st.end(); 1136 }); 1137 }); 1138 }); 1139 1140 test('`duplicates` option', function (t) { 1141 v.nonStrings.concat('not a valid option').forEach(function (invalidOption) { 1142 if (typeof invalidOption !== 'undefined') { 1143 t['throws']( 1144 function () { qs.parse('', { duplicates: invalidOption }); }, 1145 TypeError, 1146 'throws on invalid option: ' + inspect(invalidOption) 1147 ); 1148 } 1149 }); 1150 1151 t.deepEqual( 1152 qs.parse('foo=bar&foo=baz'), 1153 { foo: ['bar', 'baz'] }, 1154 'duplicates: default, combine' 1155 ); 1156 1157 t.deepEqual( 1158 qs.parse('foo=bar&foo=baz', { duplicates: 'combine' }), 1159 { foo: ['bar', 'baz'] }, 1160 'duplicates: combine' 1161 ); 1162 1163 t.deepEqual( 1164 qs.parse('foo=bar&foo=baz', { duplicates: 'first' }), 1165 { foo: 'bar' }, 1166 'duplicates: first' 1167 ); 1168 1169 t.deepEqual( 1170 qs.parse('foo=bar&foo=baz', { duplicates: 'last' }), 1171 { foo: 'baz' }, 1172 'duplicates: last' 1173 ); 1174 1175 t.end(); 1176 }); 1177 1178 test('qs strictDepth option - throw cases', function (t) { 1179 t.test('throws an exception when depth exceeds the limit with strictDepth: true', function (st) { 1180 st['throws']( 1181 function () { 1182 qs.parse('a[b][c][d][e][f][g][h][i]=j', { depth: 1, strictDepth: true }); 1183 }, 1184 RangeError, 1185 'throws RangeError' 1186 ); 1187 st.end(); 1188 }); 1189 1190 t.test('throws an exception for multiple nested arrays with strictDepth: true', function (st) { 1191 st['throws']( 1192 function () { 1193 qs.parse('a[0][1][2][3][4]=b', { depth: 3, strictDepth: true }); 1194 }, 1195 RangeError, 1196 'throws RangeError' 1197 ); 1198 st.end(); 1199 }); 1200 1201 t.test('throws an exception for nested objects and arrays with strictDepth: true', function (st) { 1202 st['throws']( 1203 function () { 1204 qs.parse('a[b][c][0][d][e]=f', { depth: 3, strictDepth: true }); 1205 }, 1206 RangeError, 1207 'throws RangeError' 1208 ); 1209 st.end(); 1210 }); 1211 1212 t.test('throws an exception for different types of values with strictDepth: true', function (st) { 1213 st['throws']( 1214 function () { 1215 qs.parse('a[b][c][d][e]=true&a[b][c][d][f]=42', { depth: 3, strictDepth: true }); 1216 }, 1217 RangeError, 1218 'throws RangeError' 1219 ); 1220 st.end(); 1221 }); 1222 1223 }); 1224 1225 test('qs strictDepth option - non-throw cases', function (t) { 1226 t.test('when depth is 0 and strictDepth true, do not throw', function (st) { 1227 st.doesNotThrow( 1228 function () { 1229 qs.parse('a[b][c][d][e]=true&a[b][c][d][f]=42', { depth: 0, strictDepth: true }); 1230 }, 1231 RangeError, 1232 'does not throw RangeError' 1233 ); 1234 st.end(); 1235 }); 1236 1237 t.test('parses successfully when depth is within the limit with strictDepth: true', function (st) { 1238 st.doesNotThrow( 1239 function () { 1240 var result = qs.parse('a[b]=c', { depth: 1, strictDepth: true }); 1241 st.deepEqual(result, { a: { b: 'c' } }, 'parses correctly'); 1242 } 1243 ); 1244 st.end(); 1245 }); 1246 1247 t.test('does not throw an exception when depth exceeds the limit with strictDepth: false', function (st) { 1248 st.doesNotThrow( 1249 function () { 1250 var result = qs.parse('a[b][c][d][e][f][g][h][i]=j', { depth: 1 }); 1251 st.deepEqual(result, { a: { b: { '[c][d][e][f][g][h][i]': 'j' } } }, 'parses with depth limit'); 1252 } 1253 ); 1254 st.end(); 1255 }); 1256 1257 t.test('parses successfully when depth is within the limit with strictDepth: false', function (st) { 1258 st.doesNotThrow( 1259 function () { 1260 var result = qs.parse('a[b]=c', { depth: 1 }); 1261 st.deepEqual(result, { a: { b: 'c' } }, 'parses correctly'); 1262 } 1263 ); 1264 st.end(); 1265 }); 1266 1267 t.test('does not throw when depth is exactly at the limit with strictDepth: true', function (st) { 1268 st.doesNotThrow( 1269 function () { 1270 var result = qs.parse('a[b][c]=d', { depth: 2, strictDepth: true }); 1271 st.deepEqual(result, { a: { b: { c: 'd' } } }, 'parses correctly'); 1272 } 1273 ); 1274 st.end(); 1275 }); 1276 });