qs 6.9.5 → 6.10.2

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
@@ -8,6 +8,7 @@ var isArray = Array.isArray;
8
8
  var defaults = {
9
9
  allowDots: false,
10
10
  allowPrototypes: false,
11
+ allowSparse: false,
11
12
  arrayLimit: 20,
12
13
  charset: 'utf-8',
13
14
  charsetSentinel: false,
@@ -217,6 +218,7 @@ var normalizeParseOptions = function normalizeParseOptions(opts) {
217
218
  return {
218
219
  allowDots: typeof opts.allowDots === 'undefined' ? defaults.allowDots : !!opts.allowDots,
219
220
  allowPrototypes: typeof opts.allowPrototypes === 'boolean' ? opts.allowPrototypes : defaults.allowPrototypes,
221
+ allowSparse: typeof opts.allowSparse === 'boolean' ? opts.allowSparse : defaults.allowSparse,
220
222
  arrayLimit: typeof opts.arrayLimit === 'number' ? opts.arrayLimit : defaults.arrayLimit,
221
223
  charset: charset,
222
224
  charsetSentinel: typeof opts.charsetSentinel === 'boolean' ? opts.charsetSentinel : defaults.charsetSentinel,
@@ -253,5 +255,9 @@ module.exports = function (str, opts) {
253
255
  obj = utils.merge(obj, newObj, options);
254
256
  }
255
257
 
258
+ if (options.allowSparse === true) {
259
+ return obj;
260
+ }
261
+
256
262
  return utils.compact(obj);
257
263
  };
package/lib/stringify.js CHANGED
@@ -1,5 +1,6 @@
1
1
  'use strict';
2
2
 
3
+ var getSideChannel = require('side-channel');
3
4
  var utils = require('./utils');
4
5
  var formats = require('./formats');
5
6
  var has = Object.prototype.hasOwnProperty;
@@ -18,6 +19,7 @@ var arrayPrefixGenerators = {
18
19
  };
19
20
 
20
21
  var isArray = Array.isArray;
22
+ var split = String.prototype.split;
21
23
  var push = Array.prototype.push;
22
24
  var pushToArray = function (arr, valueOrArray) {
23
25
  push.apply(arr, isArray(valueOrArray) ? valueOrArray : [valueOrArray]);
@@ -54,6 +56,8 @@ var isNonNullishPrimitive = function isNonNullishPrimitive(v) {
54
56
  || typeof v === 'bigint';
55
57
  };
56
58
 
59
+ var sentinel = {};
60
+
57
61
  var stringify = function stringify(
58
62
  object,
59
63
  prefix,
@@ -68,9 +72,30 @@ var stringify = function stringify(
68
72
  format,
69
73
  formatter,
70
74
  encodeValuesOnly,
71
- charset
75
+ charset,
76
+ sideChannel
72
77
  ) {
73
78
  var obj = object;
79
+
80
+ var tmpSc = sideChannel;
81
+ var step = 0;
82
+ var findFlag = false;
83
+ while ((tmpSc = tmpSc.get(sentinel)) !== undefined && !findFlag) {
84
+ // Where object last appeared in the ref tree
85
+ var pos = tmpSc.get(object);
86
+ step += 1;
87
+ if (typeof pos !== 'undefined') {
88
+ if (pos === step) {
89
+ throw new RangeError('Cyclic object value');
90
+ } else {
91
+ findFlag = true; // Break while
92
+ }
93
+ }
94
+ if (typeof tmpSc.get(sentinel) === 'undefined') {
95
+ step = 0;
96
+ }
97
+ }
98
+
74
99
  if (typeof filter === 'function') {
75
100
  obj = filter(prefix, obj);
76
101
  } else if (obj instanceof Date) {
@@ -95,6 +120,14 @@ var stringify = function stringify(
95
120
  if (isNonNullishPrimitive(obj) || utils.isBuffer(obj)) {
96
121
  if (encoder) {
97
122
  var keyValue = encodeValuesOnly ? prefix : encoder(prefix, defaults.encoder, charset, 'key', format);
123
+ if (generateArrayPrefix === 'comma' && encodeValuesOnly) {
124
+ var valuesArray = split.call(String(obj), ',');
125
+ var valuesJoined = '';
126
+ for (var i = 0; i < valuesArray.length; ++i) {
127
+ valuesJoined += (i === 0 ? '' : ',') + formatter(encoder(valuesArray[i], defaults.encoder, charset, 'value', format));
128
+ }
129
+ return [formatter(keyValue) + '=' + valuesJoined];
130
+ }
98
131
  return [formatter(keyValue) + '=' + formatter(encoder(obj, defaults.encoder, charset, 'value', format))];
99
132
  }
100
133
  return [formatter(prefix) + '=' + formatter(String(obj))];
@@ -117,8 +150,8 @@ var stringify = function stringify(
117
150
  objKeys = sort ? keys.sort(sort) : keys;
118
151
  }
119
152
 
120
- for (var i = 0; i < objKeys.length; ++i) {
121
- var key = objKeys[i];
153
+ for (var j = 0; j < objKeys.length; ++j) {
154
+ var key = objKeys[j];
122
155
  var value = typeof key === 'object' && key.value !== undefined ? key.value : obj[key];
123
156
 
124
157
  if (skipNulls && value === null) {
@@ -129,6 +162,9 @@ var stringify = function stringify(
129
162
  ? typeof generateArrayPrefix === 'function' ? generateArrayPrefix(prefix, key) : prefix
130
163
  : prefix + (allowDots ? '.' + key : '[' + key + ']');
131
164
 
165
+ sideChannel.set(object, step);
166
+ var valueSideChannel = getSideChannel();
167
+ valueSideChannel.set(sentinel, sideChannel);
132
168
  pushToArray(values, stringify(
133
169
  value,
134
170
  keyPrefix,
@@ -143,7 +179,8 @@ var stringify = function stringify(
143
179
  format,
144
180
  formatter,
145
181
  encodeValuesOnly,
146
- charset
182
+ charset,
183
+ valueSideChannel
147
184
  ));
148
185
  }
149
186
 
@@ -237,6 +274,7 @@ module.exports = function (object, opts) {
237
274
  objKeys.sort(options.sort);
238
275
  }
239
276
 
277
+ var sideChannel = getSideChannel();
240
278
  for (var i = 0; i < objKeys.length; ++i) {
241
279
  var key = objKeys[i];
242
280
 
@@ -257,7 +295,8 @@ module.exports = function (object, opts) {
257
295
  options.format,
258
296
  options.formatter,
259
297
  options.encodeValuesOnly,
260
- options.charset
298
+ options.charset,
299
+ sideChannel
261
300
  ));
262
301
  }
263
302
 
package/lib/utils.js CHANGED
@@ -177,6 +177,7 @@ var encode = function encode(str, defaultEncoder, charset, kind, format) {
177
177
 
178
178
  i += 1;
179
179
  c = 0x10000 + (((c & 0x3FF) << 10) | (string.charCodeAt(i) & 0x3FF));
180
+ /* eslint operator-linebreak: [2, "before"] */
180
181
  out += hexTable[0xF0 | (c >> 18)]
181
182
  + hexTable[0x80 | ((c >> 12) & 0x3F)]
182
183
  + hexTable[0x80 | ((c >> 6) & 0x3F)]
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.9.5",
5
+ "version": "6.10.2",
6
6
  "repository": {
7
7
  "type": "git",
8
8
  "url": "https://github.com/ljharb/qs.git"
@@ -29,34 +29,38 @@
29
29
  "engines": {
30
30
  "node": ">=0.6"
31
31
  },
32
- "dependencies": {},
32
+ "dependencies": {
33
+ "side-channel": "^1.0.4"
34
+ },
33
35
  "devDependencies": {
34
- "@ljharb/eslint-config": "^17.3.0",
35
- "aud": "^1.1.3",
36
+ "@ljharb/eslint-config": "^20.0.0",
37
+ "aud": "^1.1.5",
36
38
  "browserify": "^16.5.2",
37
39
  "eclint": "^2.8.1",
38
- "eslint": "^7.17.0",
40
+ "eslint": "^8.4.0",
39
41
  "evalmd": "^0.0.19",
40
42
  "for-each": "^0.3.3",
41
- "has-symbols": "^1.0.1",
43
+ "has-symbols": "^1.0.2",
42
44
  "iconv-lite": "^0.5.1",
45
+ "in-publish": "^2.0.1",
43
46
  "mkdirp": "^0.5.5",
44
47
  "nyc": "^10.3.2",
45
- "object-inspect": "^1.9.0",
48
+ "object-inspect": "^1.11.0",
46
49
  "qs-iconv": "^1.0.4",
47
- "safe-publish-latest": "^1.1.4",
50
+ "safe-publish-latest": "^2.0.0",
48
51
  "safer-buffer": "^2.1.2",
49
- "tape": "^5.1.1"
52
+ "tape": "^5.3.2"
50
53
  },
51
54
  "scripts": {
52
- "prepublish": "safe-publish-latest && npm run dist",
55
+ "prepublishOnly": "safe-publish-latest && npm run dist",
56
+ "prepublish": "not-in-publish || npm run prepublishOnly",
53
57
  "pretest": "npm run --silent readme && npm run --silent lint",
54
58
  "test": "npm run tests-only",
55
59
  "tests-only": "nyc tape 'test/**/*.js'",
56
60
  "posttest": "aud --production",
57
61
  "readme": "evalmd README.md",
58
- "postlint": "eclint check * lib/* test/*",
59
- "lint": "eslint lib/*.js test/*.js",
62
+ "postlint": "eclint check * lib/* test/* !dist/*",
63
+ "lint": "eslint .",
60
64
  "dist": "mkdirp dist && browserify --standalone Qs lib/index.js > dist/qs.js"
61
65
  },
62
66
  "license": "BSD-3-Clause",
package/test/parse.js CHANGED
@@ -269,6 +269,15 @@ test('parse()', function (t) {
269
269
  st.end();
270
270
  });
271
271
 
272
+ t.test('parses sparse arrays', function (st) {
273
+ /* eslint no-sparse-arrays: 0 */
274
+ st.deepEqual(qs.parse('a[4]=1&a[1]=2', { allowSparse: true }), { a: [, '2', , , '1'] });
275
+ st.deepEqual(qs.parse('a[1][b][2][c]=1', { allowSparse: true }), { a: [, { b: [, , { c: '1' }] }] });
276
+ st.deepEqual(qs.parse('a[1][2][3][c]=1', { allowSparse: true }), { a: [, [, , [, , , { c: '1' }]]] });
277
+ st.deepEqual(qs.parse('a[1][2][3][c][1]=1', { allowSparse: true }), { a: [, [, , [, , , { c: [, '1'] }]]] });
278
+ st.end();
279
+ });
280
+
272
281
  t.test('parses semi-parsed strings', function (st) {
273
282
  st.deepEqual(qs.parse({ 'a[b]': 'c' }), { a: { b: 'c' } });
274
283
  st.deepEqual(qs.parse({ 'a[b]': 'c', 'a[d]': 'e' }), { a: { b: 'c', d: 'e' } });
package/test/stringify.js CHANGED
@@ -132,10 +132,10 @@ test('stringify()', function (t) {
132
132
  });
133
133
 
134
134
  t.test('stringifies a nested array value', function (st) {
135
- st.equal(qs.stringify({ a: { b: ['c', 'd'] } }, { arrayFormat: 'indices' }), 'a%5Bb%5D%5B0%5D=c&a%5Bb%5D%5B1%5D=d');
136
- st.equal(qs.stringify({ a: { b: ['c', 'd'] } }, { arrayFormat: 'brackets' }), 'a%5Bb%5D%5B%5D=c&a%5Bb%5D%5B%5D=d');
137
- st.equal(qs.stringify({ a: { b: ['c', 'd'] } }, { arrayFormat: 'comma' }), 'a%5Bb%5D=c%2Cd'); // a[b]=c,d
138
- st.equal(qs.stringify({ a: { b: ['c', 'd'] } }), 'a%5Bb%5D%5B0%5D=c&a%5Bb%5D%5B1%5D=d');
135
+ st.equal(qs.stringify({ a: { b: ['c', 'd'] } }, { encodeValuesOnly: true, arrayFormat: 'indices' }), 'a[b][0]=c&a[b][1]=d');
136
+ st.equal(qs.stringify({ a: { b: ['c', 'd'] } }, { encodeValuesOnly: true, arrayFormat: 'brackets' }), 'a[b][]=c&a[b][]=d');
137
+ st.equal(qs.stringify({ a: { b: ['c', 'd'] } }, { encodeValuesOnly: true, arrayFormat: 'comma' }), 'a[b]=c,d');
138
+ st.equal(qs.stringify({ a: { b: ['c', 'd'] } }, { encodeValuesOnly: true }), 'a[b][0]=c&a[b][1]=d');
139
139
  st.end();
140
140
  });
141
141
 
@@ -143,7 +143,7 @@ test('stringify()', function (t) {
143
143
  st.equal(
144
144
  qs.stringify(
145
145
  { a: { b: ['c', 'd'] } },
146
- { allowDots: true, encode: false, arrayFormat: 'indices' }
146
+ { allowDots: true, encodeValuesOnly: true, arrayFormat: 'indices' }
147
147
  ),
148
148
  'a.b[0]=c&a.b[1]=d',
149
149
  'indices: stringifies with dots + indices'
@@ -151,7 +151,7 @@ test('stringify()', function (t) {
151
151
  st.equal(
152
152
  qs.stringify(
153
153
  { a: { b: ['c', 'd'] } },
154
- { allowDots: true, encode: false, arrayFormat: 'brackets' }
154
+ { allowDots: true, encodeValuesOnly: true, arrayFormat: 'brackets' }
155
155
  ),
156
156
  'a.b[]=c&a.b[]=d',
157
157
  'brackets: stringifies with dots + brackets'
@@ -159,7 +159,7 @@ test('stringify()', function (t) {
159
159
  st.equal(
160
160
  qs.stringify(
161
161
  { a: { b: ['c', 'd'] } },
162
- { allowDots: true, encode: false, arrayFormat: 'comma' }
162
+ { allowDots: true, encodeValuesOnly: true, arrayFormat: 'comma' }
163
163
  ),
164
164
  'a.b=c,d',
165
165
  'comma: stringifies with dots + comma'
@@ -167,7 +167,7 @@ test('stringify()', function (t) {
167
167
  st.equal(
168
168
  qs.stringify(
169
169
  { a: { b: ['c', 'd'] } },
170
- { allowDots: true, encode: false }
170
+ { allowDots: true, encodeValuesOnly: true }
171
171
  ),
172
172
  'a.b[0]=c&a.b[1]=d',
173
173
  'default: stringifies with dots + indices'
@@ -215,17 +215,23 @@ test('stringify()', function (t) {
215
215
 
216
216
  t.test('stringifies an array with mixed objects and primitives', function (st) {
217
217
  st.equal(
218
- qs.stringify({ a: [{ b: 1 }, 2, 3] }, { encode: false, arrayFormat: 'indices' }),
218
+ qs.stringify({ a: [{ b: 1 }, 2, 3] }, { encodeValuesOnly: true, arrayFormat: 'indices' }),
219
219
  'a[0][b]=1&a[1]=2&a[2]=3',
220
220
  'indices => indices'
221
221
  );
222
222
  st.equal(
223
- qs.stringify({ a: [{ b: 1 }, 2, 3] }, { encode: false, arrayFormat: 'brackets' }),
223
+ qs.stringify({ a: [{ b: 1 }, 2, 3] }, { encodeValuesOnly: true, arrayFormat: 'brackets' }),
224
224
  'a[][b]=1&a[]=2&a[]=3',
225
225
  'brackets => brackets'
226
226
  );
227
227
  st.equal(
228
- qs.stringify({ a: [{ b: 1 }, 2, 3] }, { encode: false }),
228
+ qs.stringify({ a: [{ b: 1 }, 2, 3] }, { encodeValuesOnly: true, arrayFormat: 'comma' }),
229
+ '???',
230
+ 'brackets => brackets',
231
+ { skip: 'TODO: figure out what this should do' }
232
+ );
233
+ st.equal(
234
+ qs.stringify({ a: [{ b: 1 }, 2, 3] }, { encodeValuesOnly: true }),
229
235
  'a[0][b]=1&a[1]=2&a[2]=3',
230
236
  'default => indices'
231
237
  );
@@ -433,7 +439,7 @@ test('stringify()', function (t) {
433
439
  st.end();
434
440
  });
435
441
 
436
- t.test('doesn\'t blow up when Buffer global is missing', function (st) {
442
+ t.test('does not blow up when Buffer global is missing', function (st) {
437
443
  var tempBuffer = global.Buffer;
438
444
  delete global.Buffer;
439
445
  var result = qs.stringify({ a: 'b', c: 'd' });
@@ -442,6 +448,57 @@ test('stringify()', function (t) {
442
448
  st.end();
443
449
  });
444
450
 
451
+ t.test('does not crash when parsing circular references', function (st) {
452
+ var a = {};
453
+ a.b = a;
454
+
455
+ st['throws'](
456
+ function () { qs.stringify({ 'foo[bar]': 'baz', 'foo[baz]': a }); },
457
+ /RangeError: Cyclic object value/,
458
+ 'cyclic values throw'
459
+ );
460
+
461
+ var circular = {
462
+ a: 'value'
463
+ };
464
+ circular.a = circular;
465
+ st['throws'](
466
+ function () { qs.stringify(circular); },
467
+ /RangeError: Cyclic object value/,
468
+ 'cyclic values throw'
469
+ );
470
+
471
+ var arr = ['a'];
472
+ st.doesNotThrow(
473
+ function () { qs.stringify({ x: arr, y: arr }); },
474
+ 'non-cyclic values do not throw'
475
+ );
476
+
477
+ st.end();
478
+ });
479
+
480
+ t.test('non-circular duplicated references can still work', function (st) {
481
+ var hourOfDay = {
482
+ 'function': 'hour_of_day'
483
+ };
484
+
485
+ var p1 = {
486
+ 'function': 'gte',
487
+ arguments: [hourOfDay, 0]
488
+ };
489
+ var p2 = {
490
+ 'function': 'lte',
491
+ arguments: [hourOfDay, 23]
492
+ };
493
+
494
+ st.equal(
495
+ qs.stringify({ filters: { $and: [p1, p2] } }, { encodeValuesOnly: true }),
496
+ 'filters[$and][0][function]=gte&filters[$and][0][arguments][0][function]=hour_of_day&filters[$and][0][arguments][1]=0&filters[$and][1][function]=lte&filters[$and][1][arguments][0][function]=hour_of_day&filters[$and][1][arguments][1]=23'
497
+ );
498
+
499
+ st.end();
500
+ });
501
+
445
502
  t.test('selects properties when filter=array', function (st) {
446
503
  st.equal(qs.stringify({ a: 'b' }, { filter: ['a'] }), 'a=b');
447
504
  st.equal(qs.stringify({ a: 1 }, { filter: [] }), '');
@@ -784,7 +841,22 @@ test('stringify()', function (t) {
784
841
  st.equal(qs.stringify(withArray, { encode: false }), 'a[b][0][c]=d&a[b][0][e]=f', 'array, no arrayFormat');
785
842
  st.equal(qs.stringify(withArray, { encode: false, arrayFormat: 'bracket' }), 'a[b][0][c]=d&a[b][0][e]=f', 'array, bracket');
786
843
  st.equal(qs.stringify(withArray, { encode: false, arrayFormat: 'indices' }), 'a[b][0][c]=d&a[b][0][e]=f', 'array, indices');
787
- st.equal(qs.stringify(obj, { encode: false, arrayFormat: 'comma' }), '???', 'array, comma (pending issue #378)', { skip: true });
844
+ st.equal(
845
+ qs.stringify(withArray, { encode: false, arrayFormat: 'comma' }),
846
+ '???',
847
+ 'array, comma',
848
+ { skip: 'TODO: figure out what this should do' }
849
+ );
850
+
851
+ st.end();
852
+ });
853
+
854
+ t.test('stringifies sparse arrays', function (st) {
855
+ /* eslint no-sparse-arrays: 0 */
856
+ st.equal(qs.stringify({ a: [, '2', , , '1'] }, { encodeValuesOnly: true }), 'a[1]=2&a[4]=1');
857
+ st.equal(qs.stringify({ a: [, { b: [, , { c: '1' }] }] }, { encodeValuesOnly: true }), 'a[1][b][2][c]=1');
858
+ st.equal(qs.stringify({ a: [, [, , [, , , { c: '1' }]]] }, { encodeValuesOnly: true }), 'a[1][2][3][c]=1');
859
+ st.equal(qs.stringify({ a: [, [, , [, , , { c: [, '1'] }]]] }, { encodeValuesOnly: true }), 'a[1][2][3][c][1]=1');
788
860
 
789
861
  st.end();
790
862
  });