qs 6.13.0 → 6.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/parse.js CHANGED
@@ -25,7 +25,8 @@ var defaults = {
25
25
  parseArrays: true,
26
26
  plainObjects: false,
27
27
  strictDepth: false,
28
- strictNullHandling: false
28
+ strictNullHandling: false,
29
+ throwOnLimitExceeded: false
29
30
  };
30
31
 
31
32
  var interpretNumericEntities = function (str) {
@@ -34,11 +35,15 @@ var interpretNumericEntities = function (str) {
34
35
  });
35
36
  };
36
37
 
37
- var parseArrayValue = function (val, options) {
38
+ var parseArrayValue = function (val, options, currentArrayLength) {
38
39
  if (val && typeof val === 'string' && options.comma && val.indexOf(',') > -1) {
39
40
  return val.split(',');
40
41
  }
41
42
 
43
+ if (options.throwOnLimitExceeded && currentArrayLength >= options.arrayLimit) {
44
+ throw new RangeError('Array limit exceeded. Only ' + options.arrayLimit + ' element' + (options.arrayLimit === 1 ? '' : 's') + ' allowed in an array.');
45
+ }
46
+
42
47
  return val;
43
48
  };
44
49
 
@@ -57,8 +62,17 @@ var parseValues = function parseQueryStringValues(str, options) {
57
62
 
58
63
  var cleanStr = options.ignoreQueryPrefix ? str.replace(/^\?/, '') : str;
59
64
  cleanStr = cleanStr.replace(/%5B/gi, '[').replace(/%5D/gi, ']');
65
+
60
66
  var limit = options.parameterLimit === Infinity ? undefined : options.parameterLimit;
61
- var parts = cleanStr.split(options.delimiter, limit);
67
+ var parts = cleanStr.split(
68
+ options.delimiter,
69
+ options.throwOnLimitExceeded ? limit + 1 : limit
70
+ );
71
+
72
+ if (options.throwOnLimitExceeded && parts.length > limit) {
73
+ throw new RangeError('Parameter limit exceeded. Only ' + limit + ' parameter' + (limit === 1 ? '' : 's') + ' allowed.');
74
+ }
75
+
62
76
  var skipIndex = -1; // Keep track of where the utf8 sentinel was found
63
77
  var i;
64
78
 
@@ -86,14 +100,20 @@ var parseValues = function parseQueryStringValues(str, options) {
86
100
  var bracketEqualsPos = part.indexOf(']=');
87
101
  var pos = bracketEqualsPos === -1 ? part.indexOf('=') : bracketEqualsPos + 1;
88
102
 
89
- var key, val;
103
+ var key;
104
+ var val;
90
105
  if (pos === -1) {
91
106
  key = options.decoder(part, defaults.decoder, charset, 'key');
92
107
  val = options.strictNullHandling ? null : '';
93
108
  } else {
94
109
  key = options.decoder(part.slice(0, pos), defaults.decoder, charset, 'key');
110
+
95
111
  val = utils.maybeMap(
96
- parseArrayValue(part.slice(pos + 1), options),
112
+ parseArrayValue(
113
+ part.slice(pos + 1),
114
+ options,
115
+ isArray(obj[key]) ? obj[key].length : 0
116
+ ),
97
117
  function (encodedVal) {
98
118
  return options.decoder(encodedVal, defaults.decoder, charset, 'value');
99
119
  }
@@ -101,7 +121,7 @@ var parseValues = function parseQueryStringValues(str, options) {
101
121
  }
102
122
 
103
123
  if (val && options.interpretNumericEntities && charset === 'iso-8859-1') {
104
- val = interpretNumericEntities(val);
124
+ val = interpretNumericEntities(String(val));
105
125
  }
106
126
 
107
127
  if (part.indexOf('[]=') > -1) {
@@ -120,7 +140,13 @@ var parseValues = function parseQueryStringValues(str, options) {
120
140
  };
121
141
 
122
142
  var parseObject = function (chain, val, options, valuesParsed) {
123
- var leaf = valuesParsed ? val : parseArrayValue(val, options);
143
+ var currentArrayLength = 0;
144
+ if (chain.length > 0 && chain[chain.length - 1] === '[]') {
145
+ var parentKey = chain.slice(0, -1).join('');
146
+ currentArrayLength = Array.isArray(val) && val[parentKey] ? val[parentKey].length : 0;
147
+ }
148
+
149
+ var leaf = valuesParsed ? val : parseArrayValue(val, options, currentArrayLength);
124
150
 
125
151
  for (var i = chain.length - 1; i >= 0; --i) {
126
152
  var obj;
@@ -129,9 +155,9 @@ var parseObject = function (chain, val, options, valuesParsed) {
129
155
  if (root === '[]' && options.parseArrays) {
130
156
  obj = options.allowEmptyArrays && (leaf === '' || (options.strictNullHandling && leaf === null))
131
157
  ? []
132
- : [].concat(leaf);
158
+ : utils.combine([], leaf);
133
159
  } else {
134
- obj = options.plainObjects ? Object.create(null) : {};
160
+ obj = options.plainObjects ? { __proto__: null } : {};
135
161
  var cleanRoot = root.charAt(0) === '[' && root.charAt(root.length - 1) === ']' ? root.slice(1, -1) : root;
136
162
  var decodedRoot = options.decodeDotInKeys ? cleanRoot.replace(/%2E/g, '.') : cleanRoot;
137
163
  var index = parseInt(decodedRoot, 10);
@@ -234,6 +260,11 @@ var normalizeParseOptions = function normalizeParseOptions(opts) {
234
260
  if (typeof opts.charset !== 'undefined' && opts.charset !== 'utf-8' && opts.charset !== 'iso-8859-1') {
235
261
  throw new TypeError('The charset option must be either utf-8, iso-8859-1, or undefined');
236
262
  }
263
+
264
+ if (typeof opts.throwOnLimitExceeded !== 'undefined' && typeof opts.throwOnLimitExceeded !== 'boolean') {
265
+ throw new TypeError('`throwOnLimitExceeded` option must be a boolean');
266
+ }
267
+
237
268
  var charset = typeof opts.charset === 'undefined' ? defaults.charset : opts.charset;
238
269
 
239
270
  var duplicates = typeof opts.duplicates === 'undefined' ? defaults.duplicates : opts.duplicates;
@@ -265,7 +296,8 @@ var normalizeParseOptions = function normalizeParseOptions(opts) {
265
296
  parseArrays: opts.parseArrays !== false,
266
297
  plainObjects: typeof opts.plainObjects === 'boolean' ? opts.plainObjects : defaults.plainObjects,
267
298
  strictDepth: typeof opts.strictDepth === 'boolean' ? !!opts.strictDepth : defaults.strictDepth,
268
- strictNullHandling: typeof opts.strictNullHandling === 'boolean' ? opts.strictNullHandling : defaults.strictNullHandling
299
+ strictNullHandling: typeof opts.strictNullHandling === 'boolean' ? opts.strictNullHandling : defaults.strictNullHandling,
300
+ throwOnLimitExceeded: typeof opts.throwOnLimitExceeded === 'boolean' ? opts.throwOnLimitExceeded : false
269
301
  };
270
302
  };
271
303
 
@@ -273,11 +305,11 @@ module.exports = function (str, opts) {
273
305
  var options = normalizeParseOptions(opts);
274
306
 
275
307
  if (str === '' || str === null || typeof str === 'undefined') {
276
- return options.plainObjects ? Object.create(null) : {};
308
+ return options.plainObjects ? { __proto__: null } : {};
277
309
  }
278
310
 
279
311
  var tempObj = typeof str === 'string' ? parseValues(str, options) : str;
280
- var obj = options.plainObjects ? Object.create(null) : {};
312
+ var obj = options.plainObjects ? { __proto__: null } : {};
281
313
 
282
314
  // Iterate over the keys and setup the new object
283
315
 
package/lib/stringify.js CHANGED
@@ -34,11 +34,13 @@ var defaults = {
34
34
  arrayFormat: 'indices',
35
35
  charset: 'utf-8',
36
36
  charsetSentinel: false,
37
+ commaRoundTrip: false,
37
38
  delimiter: '&',
38
39
  encode: true,
39
40
  encodeDotInKeys: false,
40
41
  encoder: utils.encode,
41
42
  encodeValuesOnly: false,
43
+ filter: void undefined,
42
44
  format: defaultFormat,
43
45
  formatter: formats.formatters[defaultFormat],
44
46
  // deprecated
@@ -150,7 +152,7 @@ var stringify = function stringify(
150
152
  objKeys = sort ? keys.sort(sort) : keys;
151
153
  }
152
154
 
153
- var encodedPrefix = encodeDotInKeys ? prefix.replace(/\./g, '%2E') : prefix;
155
+ var encodedPrefix = encodeDotInKeys ? String(prefix).replace(/\./g, '%2E') : String(prefix);
154
156
 
155
157
  var adjustedPrefix = commaRoundTrip && isArray(obj) && obj.length === 1 ? encodedPrefix + '[]' : encodedPrefix;
156
158
 
@@ -160,13 +162,15 @@ var stringify = function stringify(
160
162
 
161
163
  for (var j = 0; j < objKeys.length; ++j) {
162
164
  var key = objKeys[j];
163
- var value = typeof key === 'object' && typeof key.value !== 'undefined' ? key.value : obj[key];
165
+ var value = typeof key === 'object' && key && typeof key.value !== 'undefined'
166
+ ? key.value
167
+ : obj[key];
164
168
 
165
169
  if (skipNulls && value === null) {
166
170
  continue;
167
171
  }
168
172
 
169
- var encodedKey = allowDots && encodeDotInKeys ? key.replace(/\./g, '%2E') : key;
173
+ var encodedKey = allowDots && encodeDotInKeys ? String(key).replace(/\./g, '%2E') : String(key);
170
174
  var keyPrefix = isArray(obj)
171
175
  ? typeof generateArrayPrefix === 'function' ? generateArrayPrefix(adjustedPrefix, encodedKey) : adjustedPrefix
172
176
  : adjustedPrefix + (allowDots ? '.' + encodedKey : '[' + encodedKey + ']');
@@ -257,7 +261,7 @@ var normalizeStringifyOptions = function normalizeStringifyOptions(opts) {
257
261
  arrayFormat: arrayFormat,
258
262
  charset: charset,
259
263
  charsetSentinel: typeof opts.charsetSentinel === 'boolean' ? opts.charsetSentinel : defaults.charsetSentinel,
260
- commaRoundTrip: opts.commaRoundTrip,
264
+ commaRoundTrip: !!opts.commaRoundTrip,
261
265
  delimiter: typeof opts.delimiter === 'undefined' ? defaults.delimiter : opts.delimiter,
262
266
  encode: typeof opts.encode === 'boolean' ? opts.encode : defaults.encode,
263
267
  encodeDotInKeys: typeof opts.encodeDotInKeys === 'boolean' ? opts.encodeDotInKeys : defaults.encodeDotInKeys,
@@ -308,12 +312,13 @@ module.exports = function (object, opts) {
308
312
  var sideChannel = getSideChannel();
309
313
  for (var i = 0; i < objKeys.length; ++i) {
310
314
  var key = objKeys[i];
315
+ var value = obj[key];
311
316
 
312
- if (options.skipNulls && obj[key] === null) {
317
+ if (options.skipNulls && value === null) {
313
318
  continue;
314
319
  }
315
320
  pushToArray(keys, stringify(
316
- obj[key],
321
+ value,
317
322
  key,
318
323
  generateArrayPrefix,
319
324
  commaRoundTrip,
package/lib/utils.js CHANGED
@@ -34,7 +34,7 @@ var compactQueue = function compactQueue(queue) {
34
34
  };
35
35
 
36
36
  var arrayToObject = function arrayToObject(source, options) {
37
- var obj = options && options.plainObjects ? Object.create(null) : {};
37
+ var obj = options && options.plainObjects ? { __proto__: null } : {};
38
38
  for (var i = 0; i < source.length; ++i) {
39
39
  if (typeof source[i] !== 'undefined') {
40
40
  obj[i] = source[i];
@@ -50,11 +50,14 @@ var merge = function merge(target, source, options) {
50
50
  return target;
51
51
  }
52
52
 
53
- if (typeof source !== 'object') {
53
+ if (typeof source !== 'object' && typeof source !== 'function') {
54
54
  if (isArray(target)) {
55
55
  target.push(source);
56
56
  } else if (target && typeof target === 'object') {
57
- if ((options && (options.plainObjects || options.allowPrototypes)) || !has.call(Object.prototype, source)) {
57
+ if (
58
+ (options && (options.plainObjects || options.allowPrototypes))
59
+ || !has.call(Object.prototype, source)
60
+ ) {
58
61
  target[source] = true;
59
62
  }
60
63
  } else {
@@ -108,7 +111,7 @@ var assign = function assignSingleSource(target, source) {
108
111
  }, target);
109
112
  };
110
113
 
111
- var decode = function (str, decoder, charset) {
114
+ var decode = function (str, defaultDecoder, charset) {
112
115
  var strWithoutPlus = str.replace(/\+/g, ' ');
113
116
  if (charset === 'iso-8859-1') {
114
117
  // unescape never throws, no try...catch needed:
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "qs",
3
3
  "description": "A querystring parser that supports nesting and arrays, with a depth limit",
4
4
  "homepage": "https://github.com/ljharb/qs",
5
- "version": "6.13.0",
5
+ "version": "6.14.0",
6
6
  "repository": {
7
7
  "type": "git",
8
8
  "url": "https://github.com/ljharb/qs.git"
@@ -31,7 +31,7 @@
31
31
  "node": ">=0.6"
32
32
  },
33
33
  "dependencies": {
34
- "side-channel": "^1.0.6"
34
+ "side-channel": "^1.1.0"
35
35
  },
36
36
  "devDependencies": {
37
37
  "@browserify/envify": "^6.0.0",
@@ -41,27 +41,29 @@
41
41
  "bundle-collapser": "^1.4.0",
42
42
  "common-shakeify": "~1.0.0",
43
43
  "eclint": "^2.8.1",
44
- "es-value-fixtures": "^1.4.2",
44
+ "es-value-fixtures": "^1.7.0",
45
45
  "eslint": "=8.8.0",
46
46
  "evalmd": "^0.0.19",
47
47
  "for-each": "^0.3.3",
48
48
  "glob": "=10.3.7",
49
+ "has-bigints": "^1.1.0",
49
50
  "has-override-mistake": "^1.0.1",
50
51
  "has-property-descriptors": "^1.0.2",
51
- "has-symbols": "^1.0.3",
52
+ "has-proto": "^1.2.0",
53
+ "has-symbols": "^1.1.0",
52
54
  "iconv-lite": "^0.5.1",
53
55
  "in-publish": "^2.0.1",
54
56
  "jackspeak": "=2.1.1",
55
57
  "mkdirp": "^0.5.5",
56
- "mock-property": "^1.0.3",
58
+ "mock-property": "^1.1.0",
57
59
  "module-deps": "^6.2.3",
58
60
  "npmignore": "^0.3.1",
59
61
  "nyc": "^10.3.2",
60
- "object-inspect": "^1.13.2",
62
+ "object-inspect": "^1.13.3",
61
63
  "qs-iconv": "^1.0.4",
62
64
  "safe-publish-latest": "^2.0.0",
63
65
  "safer-buffer": "^2.1.2",
64
- "tape": "^5.8.1",
66
+ "tape": "^5.9.0",
65
67
  "unassertify": "^3.0.1"
66
68
  },
67
69
  "scripts": {
package/test/parse.js CHANGED
@@ -9,6 +9,7 @@ var SaferBuffer = require('safer-buffer').Buffer;
9
9
  var v = require('es-value-fixtures');
10
10
  var inspect = require('object-inspect');
11
11
  var emptyTestCases = require('./empty-keys-cases').emptyTestCases;
12
+ var hasProto = require('has-proto')();
12
13
 
13
14
  var qs = require('../');
14
15
  var utils = require('../lib/utils');
@@ -117,7 +118,7 @@ test('parse()', function (t) {
117
118
  st.end();
118
119
  });
119
120
 
120
- t.test('should decode dot in key of object, and allow enabling dot notation when decodeDotInKeys is set to true and allowDots is undefined', function (st) {
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) {
121
122
  st.deepEqual(
122
123
  qs.parse(
123
124
  'name%252Eobj%252Esubobject.first%252Egodly%252Ename=John&name%252Eobj%252Esubobject.last=Doe',
@@ -130,7 +131,7 @@ test('parse()', function (t) {
130
131
  st.end();
131
132
  });
132
133
 
133
- t.test('should throw when decodeDotInKeys is not of type boolean', function (st) {
134
+ t.test('throws when decodeDotInKeys is not of type boolean', function (st) {
134
135
  st['throws'](
135
136
  function () { qs.parse('foo[]&bar=baz', { decodeDotInKeys: 'foobar' }); },
136
137
  TypeError
@@ -160,7 +161,7 @@ test('parse()', function (t) {
160
161
  st.end();
161
162
  });
162
163
 
163
- t.test('should throw when allowEmptyArrays is not of type boolean', function (st) {
164
+ t.test('throws when allowEmptyArrays is not of type boolean', function (st) {
164
165
  st['throws'](
165
166
  function () { qs.parse('foo[]&bar=baz', { allowEmptyArrays: 'foobar' }); },
166
167
  TypeError
@@ -443,7 +444,7 @@ test('parse()', function (t) {
443
444
  st.end();
444
445
  });
445
446
 
446
- t.test('should not throw when a native prototype has an enumerable property', function (st) {
447
+ t.test('does not throw when a native prototype has an enumerable property', function (st) {
447
448
  st.intercept(Object.prototype, 'crash', { value: '' });
448
449
  st.intercept(Array.prototype, 'crash', { value: '' });
449
450
 
@@ -691,9 +692,8 @@ test('parse()', function (t) {
691
692
  st.end();
692
693
  });
693
694
 
694
- t.test('parses null objects correctly', { skip: !Object.create }, function (st) {
695
- var a = Object.create(null);
696
- a.b = 'c';
695
+ t.test('parses null objects correctly', { skip: !hasProto }, function (st) {
696
+ var a = { __proto__: null, b: 'c' };
697
697
 
698
698
  st.deepEqual(qs.parse(a), { b: 'c' });
699
699
  var result = qs.parse({ a: a });
@@ -870,17 +870,25 @@ test('parse()', function (t) {
870
870
  st.end();
871
871
  });
872
872
 
873
- t.test('can return null objects', { skip: !Object.create }, function (st) {
874
- var expected = Object.create(null);
875
- expected.a = Object.create(null);
876
- expected.a.b = 'c';
877
- expected.a.hasOwnProperty = 'd';
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
+ };
878
882
  st.deepEqual(qs.parse('a[b]=c&a[hasOwnProperty]=d', { plainObjects: true }), expected);
879
- st.deepEqual(qs.parse(null, { plainObjects: true }), Object.create(null));
880
- var expectedArray = Object.create(null);
881
- expectedArray.a = Object.create(null);
882
- expectedArray.a[0] = 'b';
883
- expectedArray.a.c = 'd';
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
+ };
884
892
  st.deepEqual(qs.parse('a[]=b&a[c]=d', { plainObjects: true }), expectedArray);
885
893
  st.end();
886
894
  });
@@ -957,7 +965,7 @@ test('parse()', function (t) {
957
965
  st.end();
958
966
  });
959
967
 
960
- t.test('should ignore an utf8 sentinel with an unknown value', function (st) {
968
+ t.test('ignores an utf8 sentinel with an unknown value', function (st) {
961
969
  st.deepEqual(qs.parse('utf8=foo&' + urlEncodedOSlashInUtf8 + '=' + urlEncodedOSlashInUtf8, { charsetSentinel: true, charset: 'utf-8' }), { ø: 'ø' });
962
970
  st.end();
963
971
  });
@@ -998,6 +1006,15 @@ test('parse()', function (t) {
998
1006
  st.end();
999
1007
  });
1000
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
+
1001
1018
  t.test('does not interpret %uXXXX syntax in iso-8859-1 mode', function (st) {
1002
1019
  st.deepEqual(qs.parse('%u263A=%u263A', { charset: 'iso-8859-1' }), { '%u263A': '%u263A' });
1003
1020
  st.end();
@@ -1018,6 +1035,95 @@ test('parse()', function (t) {
1018
1035
  st.end();
1019
1036
  });
1020
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
+
1021
1127
  t.end();
1022
1128
  });
1023
1129
 
@@ -1076,7 +1182,7 @@ test('qs strictDepth option - throw cases', function (t) {
1076
1182
  qs.parse('a[b][c][d][e][f][g][h][i]=j', { depth: 1, strictDepth: true });
1077
1183
  },
1078
1184
  RangeError,
1079
- 'Should throw RangeError'
1185
+ 'throws RangeError'
1080
1186
  );
1081
1187
  st.end();
1082
1188
  });
@@ -1087,7 +1193,7 @@ test('qs strictDepth option - throw cases', function (t) {
1087
1193
  qs.parse('a[0][1][2][3][4]=b', { depth: 3, strictDepth: true });
1088
1194
  },
1089
1195
  RangeError,
1090
- 'Should throw RangeError'
1196
+ 'throws RangeError'
1091
1197
  );
1092
1198
  st.end();
1093
1199
  });
@@ -1098,7 +1204,7 @@ test('qs strictDepth option - throw cases', function (t) {
1098
1204
  qs.parse('a[b][c][0][d][e]=f', { depth: 3, strictDepth: true });
1099
1205
  },
1100
1206
  RangeError,
1101
- 'Should throw RangeError'
1207
+ 'throws RangeError'
1102
1208
  );
1103
1209
  st.end();
1104
1210
  });
@@ -1109,7 +1215,7 @@ test('qs strictDepth option - throw cases', function (t) {
1109
1215
  qs.parse('a[b][c][d][e]=true&a[b][c][d][f]=42', { depth: 3, strictDepth: true });
1110
1216
  },
1111
1217
  RangeError,
1112
- 'Should throw RangeError'
1218
+ 'throws RangeError'
1113
1219
  );
1114
1220
  st.end();
1115
1221
  });
@@ -1123,7 +1229,7 @@ test('qs strictDepth option - non-throw cases', function (t) {
1123
1229
  qs.parse('a[b][c][d][e]=true&a[b][c][d][f]=42', { depth: 0, strictDepth: true });
1124
1230
  },
1125
1231
  RangeError,
1126
- 'Should not throw RangeError'
1232
+ 'does not throw RangeError'
1127
1233
  );
1128
1234
  st.end();
1129
1235
  });
@@ -1132,7 +1238,7 @@ test('qs strictDepth option - non-throw cases', function (t) {
1132
1238
  st.doesNotThrow(
1133
1239
  function () {
1134
1240
  var result = qs.parse('a[b]=c', { depth: 1, strictDepth: true });
1135
- st.deepEqual(result, { a: { b: 'c' } }, 'Should parse correctly');
1241
+ st.deepEqual(result, { a: { b: 'c' } }, 'parses correctly');
1136
1242
  }
1137
1243
  );
1138
1244
  st.end();
@@ -1142,7 +1248,7 @@ test('qs strictDepth option - non-throw cases', function (t) {
1142
1248
  st.doesNotThrow(
1143
1249
  function () {
1144
1250
  var result = qs.parse('a[b][c][d][e][f][g][h][i]=j', { depth: 1 });
1145
- st.deepEqual(result, { a: { b: { '[c][d][e][f][g][h][i]': 'j' } } }, 'Should parse with depth limit');
1251
+ st.deepEqual(result, { a: { b: { '[c][d][e][f][g][h][i]': 'j' } } }, 'parses with depth limit');
1146
1252
  }
1147
1253
  );
1148
1254
  st.end();
@@ -1152,7 +1258,7 @@ test('qs strictDepth option - non-throw cases', function (t) {
1152
1258
  st.doesNotThrow(
1153
1259
  function () {
1154
1260
  var result = qs.parse('a[b]=c', { depth: 1 });
1155
- st.deepEqual(result, { a: { b: 'c' } }, 'Should parse correctly');
1261
+ st.deepEqual(result, { a: { b: 'c' } }, 'parses correctly');
1156
1262
  }
1157
1263
  );
1158
1264
  st.end();
@@ -1162,7 +1268,7 @@ test('qs strictDepth option - non-throw cases', function (t) {
1162
1268
  st.doesNotThrow(
1163
1269
  function () {
1164
1270
  var result = qs.parse('a[b][c]=d', { depth: 2, strictDepth: true });
1165
- st.deepEqual(result, { a: { b: { c: 'd' } } }, 'Should parse correctly');
1271
+ st.deepEqual(result, { a: { b: { c: 'd' } } }, 'parses correctly');
1166
1272
  }
1167
1273
  );
1168
1274
  st.end();
package/test/stringify.js CHANGED
@@ -8,7 +8,8 @@ var SaferBuffer = require('safer-buffer').Buffer;
8
8
  var hasSymbols = require('has-symbols');
9
9
  var mockProperty = require('mock-property');
10
10
  var emptyTestCases = require('./empty-keys-cases').emptyTestCases;
11
- var hasBigInt = typeof BigInt === 'function';
11
+ var hasProto = require('has-proto')();
12
+ var hasBigInt = require('has-bigints')();
12
13
 
13
14
  test('stringify()', function (t) {
14
15
  t.test('stringifies a querystring object', function (st) {
@@ -650,10 +651,8 @@ test('stringify()', function (t) {
650
651
  st.end();
651
652
  });
652
653
 
653
- t.test('stringifies a null object', { skip: !Object.create }, function (st) {
654
- var obj = Object.create(null);
655
- obj.a = 'b';
656
- st.equal(qs.stringify(obj), 'a=b');
654
+ t.test('stringifies a null object', { skip: !hasProto }, function (st) {
655
+ st.equal(qs.stringify({ __proto__: null, a: 'b' }), 'a=b');
657
656
  st.end();
658
657
  });
659
658
 
@@ -665,11 +664,8 @@ test('stringify()', function (t) {
665
664
  st.end();
666
665
  });
667
666
 
668
- t.test('stringifies an object with a null object as a child', { skip: !Object.create }, function (st) {
669
- var obj = { a: Object.create(null) };
670
-
671
- obj.a.b = 'c';
672
- st.equal(qs.stringify(obj), 'a%5Bb%5D=c');
667
+ t.test('stringifies an object with a null object as a child', { skip: !hasProto }, function (st) {
668
+ st.equal(qs.stringify({ a: { __proto__: null, b: 'c' } }), 'a%5Bb%5D=c');
673
669
  st.end();
674
670
  });
675
671
 
@@ -1254,7 +1250,7 @@ test('stringify()', function (t) {
1254
1250
  };
1255
1251
 
1256
1252
  st.equal(
1257
- qs.stringify(obj, { arrayFormat: 'bracket', charset: 'utf-8' }),
1253
+ qs.stringify(obj, { arrayFormat: 'brackets', charset: 'utf-8' }),
1258
1254
  'foo=' + expected.join('')
1259
1255
  );
1260
1256
 
@@ -1295,4 +1291,16 @@ test('stringifies empty keys', function (t) {
1295
1291
 
1296
1292
  st.end();
1297
1293
  });
1294
+
1295
+ t.test('stringifies non-string keys', function (st) {
1296
+ var actual = qs.stringify({ a: 'b', 'false': {} }, {
1297
+ filter: ['a', false, null],
1298
+ allowDots: true,
1299
+ encodeDotInKeys: true
1300
+ });
1301
+
1302
+ st.equal(actual, 'a=b', 'stringifies correctly');
1303
+
1304
+ st.end();
1305
+ });
1298
1306
  });