/ node_modules / qs / test / parse.js
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: '&#9786;' });
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: '&#9786;' });
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  });