qs 6.11.0 → 6.11.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/.eslintrc CHANGED
@@ -15,7 +15,7 @@
15
15
  "indent": [2, 4],
16
16
  "max-lines-per-function": [2, { "max": 150 }],
17
17
  "max-params": [2, 16],
18
- "max-statements": [2, 53],
18
+ "max-statements": [2, 100],
19
19
  "multiline-comment-style": 0,
20
20
  "no-continue": 1,
21
21
  "no-magic-numbers": 0,
package/CHANGELOG.md CHANGED
@@ -1,4 +1,17 @@
1
- ## **6.11.0
1
+ ## **6.11.2**
2
+ - [Fix] `parse`: Fix parsing when the global Object prototype is frozen (#473)
3
+ - [Tests] add passing test cases with empty keys (#473)
4
+
5
+ ## **6.11.1**
6
+ - [Fix] `stringify`: encode comma values more consistently (#463)
7
+ - [readme] add usage of `filter` option for injecting custom serialization, i.e. of custom types (#447)
8
+ - [meta] remove extraneous code backticks (#457)
9
+ - [meta] fix changelog markdown
10
+ - [actions] update checkout action
11
+ - [actions] restrict action permissions
12
+ - [Dev Deps] update `@ljharb/eslint-config`, `aud`, `object-inspect`, `tape`
13
+
14
+ ## **6.11.0**
2
15
  - [New] [Fix] `stringify`: revert 0e903c0; add `commaRoundTrip` option (#442)
3
16
  - [readme] fix version badge
4
17
 
@@ -238,7 +251,7 @@
238
251
 
239
252
  ## **6.5.3**
240
253
  - [Fix] `parse`: ignore `__proto__` keys (#428)
241
- - [Fix]` `utils.merge`: avoid a crash with a null target and a truthy non-array source
254
+ - [Fix] `utils.merge`: avoid a crash with a null target and a truthy non-array source
242
255
  - [Fix] correctly parse nested arrays
243
256
  - [Fix] `stringify`: fix a crash with `strictNullHandling` and a custom `filter`/`serializeDate` (#279)
244
257
  - [Fix] `utils`: `merge`: fix crash when `source` is a truthy primitive & no options are provided
@@ -291,7 +304,7 @@
291
304
  - [Fix] fix for an impossible situation: when the formatter is called with a non-string value
292
305
  - [Fix] use `safer-buffer` instead of `Buffer` constructor
293
306
  - [Fix] `utils.merge`: avoid a crash with a null target and an array source
294
- - [Fix]` `utils.merge`: avoid a crash with a null target and a truthy non-array source
307
+ - [Fix] `utils.merge`: avoid a crash with a null target and a truthy non-array source
295
308
  - [Fix] `stringify`: fix a crash with `strictNullHandling` and a custom `filter`/`serializeDate` (#279)
296
309
  - [Fix] `utils`: `merge`: fix crash when `source` is a truthy primitive & no options are provided
297
310
  - [Fix] when `parseArrays` is false, properly handle keys ending in `[]`
@@ -320,7 +333,7 @@
320
333
  - [Fix] `parse`: ignore `__proto__` keys (#428)
321
334
  - [Fix] fix for an impossible situation: when the formatter is called with a non-string value
322
335
  - [Fix] `utils.merge`: avoid a crash with a null target and an array source
323
- - [Fix]` `utils.merge`: avoid a crash with a null target and a truthy non-array source
336
+ - [Fix] `utils.merge`: avoid a crash with a null target and a truthy non-array source
324
337
  - [Fix] `stringify`: fix a crash with `strictNullHandling` and a custom `filter`/`serializeDate` (#279)
325
338
  - [Fix] `utils`: `merge`: fix crash when `source` is a truthy primitive & no options are provided
326
339
  - [Fix] when `parseArrays` is false, properly handle keys ending in `[]`
@@ -407,7 +420,7 @@
407
420
  - [New] add "encoder" and "decoder" options, for custom param encoding/decoding (#160)
408
421
  - [Fix] fix compacting of nested sparse arrays (#150)
409
422
 
410
- ## **6.1.2
423
+ ## **6.1.2**
411
424
  - [Fix] follow `allowPrototypes` option during merge (#201, #200)
412
425
  - [Fix] chmod a-x
413
426
  - [Fix] support keys starting with brackets (#202, #200)
package/README.md CHANGED
@@ -498,6 +498,44 @@ qs.stringify({ a: ['b', 'c', 'd'], e: 'f' }, { filter: ['a', 0, 2] });
498
498
  // 'a[0]=b&a[2]=d'
499
499
  ```
500
500
 
501
+ You could also use `filter` to inject custom serialization for user defined types. Consider you're working with
502
+ some api that expects query strings of the format for ranges:
503
+
504
+ ```
505
+ https://domain.com/endpoint?range=30...70
506
+ ```
507
+
508
+ For which you model as:
509
+
510
+ ```javascript
511
+ class Range {
512
+ constructor(from, to) {
513
+ this.from = from;
514
+ this.to = to;
515
+ }
516
+ }
517
+ ```
518
+
519
+ You could _inject_ a custom serializer to handle values of this type:
520
+
521
+ ```javascript
522
+ qs.stringify(
523
+ {
524
+ range: new Range(30, 70),
525
+ },
526
+ {
527
+ filter: (prefix, value) => {
528
+ if (value instanceof Range) {
529
+ return `${value.from}...${value.to}`;
530
+ }
531
+ // serialize the usual way
532
+ return value;
533
+ },
534
+ }
535
+ );
536
+ // range=30...70
537
+ ```
538
+
501
539
  ### Handling of `null` values
502
540
 
503
541
  By default, `null` values are treated like empty strings:
package/dist/qs.js CHANGED
@@ -88,7 +88,8 @@ var isoSentinel = 'utf8=%26%2310003%3B'; // encodeURIComponent('✓')
88
88
  var charsetSentinel = 'utf8=%E2%9C%93'; // encodeURIComponent('✓')
89
89
 
90
90
  var parseValues = function parseQueryStringValues(str, options) {
91
- var obj = {};
91
+ var obj = { __proto__: null };
92
+
92
93
  var cleanStr = options.ignoreQueryPrefix ? str.replace(/^\?/, '') : str;
93
94
  var limit = options.parameterLimit === Infinity ? undefined : options.parameterLimit;
94
95
  var parts = cleanStr.split(options.delimiter, limit);
@@ -323,7 +324,6 @@ var arrayPrefixGenerators = {
323
324
  };
324
325
 
325
326
  var isArray = Array.isArray;
326
- var split = String.prototype.split;
327
327
  var push = Array.prototype.push;
328
328
  var pushToArray = function (arr, valueOrArray) {
329
329
  push.apply(arr, isArray(valueOrArray) ? valueOrArray : [valueOrArray]);
@@ -425,14 +425,6 @@ var stringify = function stringify(
425
425
  if (isNonNullishPrimitive(obj) || utils.isBuffer(obj)) {
426
426
  if (encoder) {
427
427
  var keyValue = encodeValuesOnly ? prefix : encoder(prefix, defaults.encoder, charset, 'key', format);
428
- if (generateArrayPrefix === 'comma' && encodeValuesOnly) {
429
- var valuesArray = split.call(String(obj), ',');
430
- var valuesJoined = '';
431
- for (var i = 0; i < valuesArray.length; ++i) {
432
- valuesJoined += (i === 0 ? '' : ',') + formatter(encoder(valuesArray[i], defaults.encoder, charset, 'value', format));
433
- }
434
- return [formatter(keyValue) + (commaRoundTrip && isArray(obj) && valuesArray.length === 1 ? '[]' : '') + '=' + valuesJoined];
435
- }
436
428
  return [formatter(keyValue) + '=' + formatter(encoder(obj, defaults.encoder, charset, 'value', format))];
437
429
  }
438
430
  return [formatter(prefix) + '=' + formatter(String(obj))];
@@ -447,6 +439,9 @@ var stringify = function stringify(
447
439
  var objKeys;
448
440
  if (generateArrayPrefix === 'comma' && isArray(obj)) {
449
441
  // we need to join elements in
442
+ if (encodeValuesOnly && encoder) {
443
+ obj = utils.maybeMap(obj, encoder);
444
+ }
450
445
  objKeys = [{ value: obj.length > 0 ? obj.join(',') || null : void undefined }];
451
446
  } else if (isArray(filter)) {
452
447
  objKeys = filter;
@@ -479,7 +474,7 @@ var stringify = function stringify(
479
474
  commaRoundTrip,
480
475
  strictNullHandling,
481
476
  skipNulls,
482
- encoder,
477
+ generateArrayPrefix === 'comma' && encodeValuesOnly && isArray(obj) ? null : encoder,
483
478
  filter,
484
479
  sort,
485
480
  allowDots,
@@ -629,7 +624,7 @@ module.exports = function (object, opts) {
629
624
  return joined.length > 0 ? prefix + joined : '';
630
625
  };
631
626
 
632
- },{"./formats":1,"./utils":5,"side-channel":16}],5:[function(require,module,exports){
627
+ },{"./formats":1,"./utils":5,"side-channel":17}],5:[function(require,module,exports){
633
628
  'use strict';
634
629
 
635
630
  var formats = require('./formats');
@@ -1058,18 +1053,23 @@ var ThrowTypeError = $gOPD
1058
1053
  : throwTypeError;
1059
1054
 
1060
1055
  var hasSymbols = require('has-symbols')();
1056
+ var hasProto = require('has-proto')();
1061
1057
 
1062
- var getProto = Object.getPrototypeOf || function (x) { return x.__proto__; }; // eslint-disable-line no-proto
1058
+ var getProto = Object.getPrototypeOf || (
1059
+ hasProto
1060
+ ? function (x) { return x.__proto__; } // eslint-disable-line no-proto
1061
+ : null
1062
+ );
1063
1063
 
1064
1064
  var needsEval = {};
1065
1065
 
1066
- var TypedArray = typeof Uint8Array === 'undefined' ? undefined : getProto(Uint8Array);
1066
+ var TypedArray = typeof Uint8Array === 'undefined' || !getProto ? undefined : getProto(Uint8Array);
1067
1067
 
1068
1068
  var INTRINSICS = {
1069
1069
  '%AggregateError%': typeof AggregateError === 'undefined' ? undefined : AggregateError,
1070
1070
  '%Array%': Array,
1071
1071
  '%ArrayBuffer%': typeof ArrayBuffer === 'undefined' ? undefined : ArrayBuffer,
1072
- '%ArrayIteratorPrototype%': hasSymbols ? getProto([][Symbol.iterator]()) : undefined,
1072
+ '%ArrayIteratorPrototype%': hasSymbols && getProto ? getProto([][Symbol.iterator]()) : undefined,
1073
1073
  '%AsyncFromSyncIteratorPrototype%': undefined,
1074
1074
  '%AsyncFunction%': needsEval,
1075
1075
  '%AsyncGenerator%': needsEval,
@@ -1077,6 +1077,8 @@ var INTRINSICS = {
1077
1077
  '%AsyncIteratorPrototype%': needsEval,
1078
1078
  '%Atomics%': typeof Atomics === 'undefined' ? undefined : Atomics,
1079
1079
  '%BigInt%': typeof BigInt === 'undefined' ? undefined : BigInt,
1080
+ '%BigInt64Array%': typeof BigInt64Array === 'undefined' ? undefined : BigInt64Array,
1081
+ '%BigUint64Array%': typeof BigUint64Array === 'undefined' ? undefined : BigUint64Array,
1080
1082
  '%Boolean%': Boolean,
1081
1083
  '%DataView%': typeof DataView === 'undefined' ? undefined : DataView,
1082
1084
  '%Date%': Date,
@@ -1097,10 +1099,10 @@ var INTRINSICS = {
1097
1099
  '%Int32Array%': typeof Int32Array === 'undefined' ? undefined : Int32Array,
1098
1100
  '%isFinite%': isFinite,
1099
1101
  '%isNaN%': isNaN,
1100
- '%IteratorPrototype%': hasSymbols ? getProto(getProto([][Symbol.iterator]())) : undefined,
1102
+ '%IteratorPrototype%': hasSymbols && getProto ? getProto(getProto([][Symbol.iterator]())) : undefined,
1101
1103
  '%JSON%': typeof JSON === 'object' ? JSON : undefined,
1102
1104
  '%Map%': typeof Map === 'undefined' ? undefined : Map,
1103
- '%MapIteratorPrototype%': typeof Map === 'undefined' || !hasSymbols ? undefined : getProto(new Map()[Symbol.iterator]()),
1105
+ '%MapIteratorPrototype%': typeof Map === 'undefined' || !hasSymbols || !getProto ? undefined : getProto(new Map()[Symbol.iterator]()),
1104
1106
  '%Math%': Math,
1105
1107
  '%Number%': Number,
1106
1108
  '%Object%': Object,
@@ -1113,10 +1115,10 @@ var INTRINSICS = {
1113
1115
  '%Reflect%': typeof Reflect === 'undefined' ? undefined : Reflect,
1114
1116
  '%RegExp%': RegExp,
1115
1117
  '%Set%': typeof Set === 'undefined' ? undefined : Set,
1116
- '%SetIteratorPrototype%': typeof Set === 'undefined' || !hasSymbols ? undefined : getProto(new Set()[Symbol.iterator]()),
1118
+ '%SetIteratorPrototype%': typeof Set === 'undefined' || !hasSymbols || !getProto ? undefined : getProto(new Set()[Symbol.iterator]()),
1117
1119
  '%SharedArrayBuffer%': typeof SharedArrayBuffer === 'undefined' ? undefined : SharedArrayBuffer,
1118
1120
  '%String%': String,
1119
- '%StringIteratorPrototype%': hasSymbols ? getProto(''[Symbol.iterator]()) : undefined,
1121
+ '%StringIteratorPrototype%': hasSymbols && getProto ? getProto(''[Symbol.iterator]()) : undefined,
1120
1122
  '%Symbol%': hasSymbols ? Symbol : undefined,
1121
1123
  '%SyntaxError%': $SyntaxError,
1122
1124
  '%ThrowTypeError%': ThrowTypeError,
@@ -1132,6 +1134,16 @@ var INTRINSICS = {
1132
1134
  '%WeakSet%': typeof WeakSet === 'undefined' ? undefined : WeakSet
1133
1135
  };
1134
1136
 
1137
+ if (getProto) {
1138
+ try {
1139
+ null.error; // eslint-disable-line no-unused-expressions
1140
+ } catch (e) {
1141
+ // https://github.com/tc39/proposal-shadowrealm/pull/384#issuecomment-1364264229
1142
+ var errorProto = getProto(getProto(e));
1143
+ INTRINSICS['%Error.prototype%'] = errorProto;
1144
+ }
1145
+ }
1146
+
1135
1147
  var doEval = function doEval(name) {
1136
1148
  var value;
1137
1149
  if (name === '%AsyncFunction%') {
@@ -1147,7 +1159,7 @@ var doEval = function doEval(name) {
1147
1159
  }
1148
1160
  } else if (name === '%AsyncIteratorPrototype%') {
1149
1161
  var gen = doEval('%AsyncGenerator%');
1150
- if (gen) {
1162
+ if (gen && getProto) {
1151
1163
  value = getProto(gen.prototype);
1152
1164
  }
1153
1165
  }
@@ -1217,6 +1229,7 @@ var $concat = bind.call(Function.call, Array.prototype.concat);
1217
1229
  var $spliceApply = bind.call(Function.apply, Array.prototype.splice);
1218
1230
  var $replace = bind.call(Function.call, String.prototype.replace);
1219
1231
  var $strSlice = bind.call(Function.call, String.prototype.slice);
1232
+ var $exec = bind.call(Function.call, RegExp.prototype.exec);
1220
1233
 
1221
1234
  /* adapted from https://github.com/lodash/lodash/blob/4.17.15/dist/lodash.js#L6735-L6744 */
1222
1235
  var rePropName = /[^%.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\\]|\\.)*?)\2)\]|(?=(?:\.|\[\])(?:\.|\[\]|%$))/g;
@@ -1272,6 +1285,9 @@ module.exports = function GetIntrinsic(name, allowMissing) {
1272
1285
  throw new $TypeError('"allowMissing" argument must be a boolean');
1273
1286
  }
1274
1287
 
1288
+ if ($exec(/^%?[^%]*%?$/, name) === null) {
1289
+ throw new $SyntaxError('`%` may not be present anywhere but at the beginning and end of the intrinsic name');
1290
+ }
1275
1291
  var parts = stringToPath(name);
1276
1292
  var intrinsicBaseName = parts.length > 0 ? parts[0] : '';
1277
1293
 
@@ -1344,7 +1360,20 @@ module.exports = function GetIntrinsic(name, allowMissing) {
1344
1360
  return value;
1345
1361
  };
1346
1362
 
1347
- },{"function-bind":10,"has":14,"has-symbols":12}],12:[function(require,module,exports){
1363
+ },{"function-bind":10,"has":15,"has-proto":12,"has-symbols":13}],12:[function(require,module,exports){
1364
+ 'use strict';
1365
+
1366
+ var test = {
1367
+ foo: {}
1368
+ };
1369
+
1370
+ var $Object = Object;
1371
+
1372
+ module.exports = function hasProto() {
1373
+ return { __proto__: test }.foo === test.foo && !({ __proto__: null } instanceof $Object);
1374
+ };
1375
+
1376
+ },{}],13:[function(require,module,exports){
1348
1377
  'use strict';
1349
1378
 
1350
1379
  var origSymbol = typeof Symbol !== 'undefined' && Symbol;
@@ -1359,7 +1388,7 @@ module.exports = function hasNativeSymbols() {
1359
1388
  return hasSymbolSham();
1360
1389
  };
1361
1390
 
1362
- },{"./shams":13}],13:[function(require,module,exports){
1391
+ },{"./shams":14}],14:[function(require,module,exports){
1363
1392
  'use strict';
1364
1393
 
1365
1394
  /* eslint complexity: [2, 18], max-statements: [2, 33] */
@@ -1403,14 +1432,14 @@ module.exports = function hasSymbols() {
1403
1432
  return true;
1404
1433
  };
1405
1434
 
1406
- },{}],14:[function(require,module,exports){
1435
+ },{}],15:[function(require,module,exports){
1407
1436
  'use strict';
1408
1437
 
1409
1438
  var bind = require('function-bind');
1410
1439
 
1411
1440
  module.exports = bind.call(Function.call, Object.prototype.hasOwnProperty);
1412
1441
 
1413
- },{"function-bind":10}],15:[function(require,module,exports){
1442
+ },{"function-bind":10}],16:[function(require,module,exports){
1414
1443
  var hasMap = typeof Map === 'function' && Map.prototype;
1415
1444
  var mapSizeDescriptor = Object.getOwnPropertyDescriptor && hasMap ? Object.getOwnPropertyDescriptor(Map.prototype, 'size') : null;
1416
1445
  var mapSize = hasMap && mapSizeDescriptor && typeof mapSizeDescriptor.get === 'function' ? mapSizeDescriptor.get : null;
@@ -1615,16 +1644,20 @@ module.exports = function inspect_(obj, options, depth, seen) {
1615
1644
  }
1616
1645
  if (isMap(obj)) {
1617
1646
  var mapParts = [];
1618
- mapForEach.call(obj, function (value, key) {
1619
- mapParts.push(inspect(key, obj, true) + ' => ' + inspect(value, obj));
1620
- });
1647
+ if (mapForEach) {
1648
+ mapForEach.call(obj, function (value, key) {
1649
+ mapParts.push(inspect(key, obj, true) + ' => ' + inspect(value, obj));
1650
+ });
1651
+ }
1621
1652
  return collectionOf('Map', mapSize.call(obj), mapParts, indent);
1622
1653
  }
1623
1654
  if (isSet(obj)) {
1624
1655
  var setParts = [];
1625
- setForEach.call(obj, function (value) {
1626
- setParts.push(inspect(value, obj));
1627
- });
1656
+ if (setForEach) {
1657
+ setForEach.call(obj, function (value) {
1658
+ setParts.push(inspect(value, obj));
1659
+ });
1660
+ }
1628
1661
  return collectionOf('Set', setSize.call(obj), setParts, indent);
1629
1662
  }
1630
1663
  if (isWeakMap(obj)) {
@@ -1924,7 +1957,7 @@ function arrObjKeys(obj, inspect) {
1924
1957
  return xs;
1925
1958
  }
1926
1959
 
1927
- },{"./util.inspect":6}],16:[function(require,module,exports){
1960
+ },{"./util.inspect":6}],17:[function(require,module,exports){
1928
1961
  'use strict';
1929
1962
 
1930
1963
  var GetIntrinsic = require('get-intrinsic');
@@ -2050,5 +2083,5 @@ module.exports = function getSideChannel() {
2050
2083
  return channel;
2051
2084
  };
2052
2085
 
2053
- },{"call-bind/callBound":7,"get-intrinsic":11,"object-inspect":15}]},{},[2])(2)
2086
+ },{"call-bind/callBound":7,"get-intrinsic":11,"object-inspect":16}]},{},[2])(2)
2054
2087
  });
package/lib/parse.js CHANGED
@@ -49,7 +49,8 @@ var isoSentinel = 'utf8=%26%2310003%3B'; // encodeURIComponent('&#10003;')
49
49
  var charsetSentinel = 'utf8=%E2%9C%93'; // encodeURIComponent('✓')
50
50
 
51
51
  var parseValues = function parseQueryStringValues(str, options) {
52
- var obj = {};
52
+ var obj = { __proto__: null };
53
+
53
54
  var cleanStr = options.ignoreQueryPrefix ? str.replace(/^\?/, '') : str;
54
55
  var limit = options.parameterLimit === Infinity ? undefined : options.parameterLimit;
55
56
  var parts = cleanStr.split(options.delimiter, limit);
package/lib/stringify.js CHANGED
@@ -19,7 +19,6 @@ var arrayPrefixGenerators = {
19
19
  };
20
20
 
21
21
  var isArray = Array.isArray;
22
- var split = String.prototype.split;
23
22
  var push = Array.prototype.push;
24
23
  var pushToArray = function (arr, valueOrArray) {
25
24
  push.apply(arr, isArray(valueOrArray) ? valueOrArray : [valueOrArray]);
@@ -121,14 +120,6 @@ var stringify = function stringify(
121
120
  if (isNonNullishPrimitive(obj) || utils.isBuffer(obj)) {
122
121
  if (encoder) {
123
122
  var keyValue = encodeValuesOnly ? prefix : encoder(prefix, defaults.encoder, charset, 'key', format);
124
- if (generateArrayPrefix === 'comma' && encodeValuesOnly) {
125
- var valuesArray = split.call(String(obj), ',');
126
- var valuesJoined = '';
127
- for (var i = 0; i < valuesArray.length; ++i) {
128
- valuesJoined += (i === 0 ? '' : ',') + formatter(encoder(valuesArray[i], defaults.encoder, charset, 'value', format));
129
- }
130
- return [formatter(keyValue) + (commaRoundTrip && isArray(obj) && valuesArray.length === 1 ? '[]' : '') + '=' + valuesJoined];
131
- }
132
123
  return [formatter(keyValue) + '=' + formatter(encoder(obj, defaults.encoder, charset, 'value', format))];
133
124
  }
134
125
  return [formatter(prefix) + '=' + formatter(String(obj))];
@@ -143,6 +134,9 @@ var stringify = function stringify(
143
134
  var objKeys;
144
135
  if (generateArrayPrefix === 'comma' && isArray(obj)) {
145
136
  // we need to join elements in
137
+ if (encodeValuesOnly && encoder) {
138
+ obj = utils.maybeMap(obj, encoder);
139
+ }
146
140
  objKeys = [{ value: obj.length > 0 ? obj.join(',') || null : void undefined }];
147
141
  } else if (isArray(filter)) {
148
142
  objKeys = filter;
@@ -175,7 +169,7 @@ var stringify = function stringify(
175
169
  commaRoundTrip,
176
170
  strictNullHandling,
177
171
  skipNulls,
178
- encoder,
172
+ generateArrayPrefix === 'comma' && encodeValuesOnly && isArray(obj) ? null : encoder,
179
173
  filter,
180
174
  sort,
181
175
  allowDots,
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.11.0",
5
+ "version": "6.11.2",
6
6
  "repository": {
7
7
  "type": "git",
8
8
  "url": "https://github.com/ljharb/qs.git"
@@ -33,24 +33,27 @@
33
33
  "side-channel": "^1.0.4"
34
34
  },
35
35
  "devDependencies": {
36
- "@ljharb/eslint-config": "^21.0.0",
37
- "aud": "^2.0.0",
36
+ "@ljharb/eslint-config": "^21.0.1",
37
+ "aud": "^2.0.2",
38
38
  "browserify": "^16.5.2",
39
39
  "eclint": "^2.8.1",
40
40
  "eslint": "=8.8.0",
41
41
  "evalmd": "^0.0.19",
42
42
  "for-each": "^0.3.3",
43
+ "has-override-mistake": "^1.0.0",
44
+ "has-property-descriptors": "^1.0.0",
43
45
  "has-symbols": "^1.0.3",
44
46
  "iconv-lite": "^0.5.1",
45
47
  "in-publish": "^2.0.1",
46
48
  "mkdirp": "^0.5.5",
49
+ "mock-property": "^1.0.0",
47
50
  "npmignore": "^0.3.0",
48
51
  "nyc": "^10.3.2",
49
- "object-inspect": "^1.12.2",
52
+ "object-inspect": "^1.12.3",
50
53
  "qs-iconv": "^1.0.4",
51
54
  "safe-publish-latest": "^2.0.0",
52
55
  "safer-buffer": "^2.1.2",
53
- "tape": "^5.5.3"
56
+ "tape": "^5.6.3"
54
57
  },
55
58
  "scripts": {
56
59
  "prepack": "npmignore --auto --commentLines=autogenerated",
@@ -0,0 +1,37 @@
1
+ 'use strict';
2
+
3
+ module.exports = {
4
+ emptyTestCases: [
5
+ { input: '&', withEmptyKeys: {}, stringifyOutput: '', noEmptyKeys: {} },
6
+ { input: '&&', withEmptyKeys: {}, stringifyOutput: '', noEmptyKeys: {} },
7
+ { input: '&=', withEmptyKeys: { '': '' }, stringifyOutput: '=', noEmptyKeys: {} },
8
+ { input: '&=&', withEmptyKeys: { '': '' }, stringifyOutput: '=', noEmptyKeys: {} },
9
+ { input: '&=&=', withEmptyKeys: { '': ['', ''] }, stringifyOutput: '[0]=&[1]=', noEmptyKeys: {} },
10
+ { input: '&=&=&', withEmptyKeys: { '': ['', ''] }, stringifyOutput: '[0]=&[1]=', noEmptyKeys: {} },
11
+
12
+ { input: '=', withEmptyKeys: { '': '' }, noEmptyKeys: {}, stringifyOutput: '=' },
13
+ { input: '=&', withEmptyKeys: { '': '' }, stringifyOutput: '=', noEmptyKeys: {} },
14
+ { input: '=&&&', withEmptyKeys: { '': '' }, stringifyOutput: '=', noEmptyKeys: {} },
15
+ { input: '=&=&=&', withEmptyKeys: { '': ['', '', ''] }, stringifyOutput: '[0]=&[1]=&[2]=', noEmptyKeys: {} },
16
+ { input: '=&a[]=b&a[1]=c', withEmptyKeys: { '': '', a: ['b', 'c'] }, stringifyOutput: '=&a[0]=b&a[1]=c', noEmptyKeys: { a: ['b', 'c'] } },
17
+ { input: '=a', withEmptyKeys: { '': 'a' }, noEmptyKeys: {}, stringifyOutput: '=a' },
18
+ { input: '=a', withEmptyKeys: { '': 'a' }, noEmptyKeys: {}, stringifyOutput: '=a' },
19
+ { input: 'a==a', withEmptyKeys: { a: '=a' }, noEmptyKeys: { a: '=a' }, stringifyOutput: 'a==a' },
20
+
21
+ { input: '=&a[]=b', withEmptyKeys: { '': '', a: ['b'] }, stringifyOutput: '=&a[0]=b', noEmptyKeys: { a: ['b'] } },
22
+ { input: '=&a[]=b&a[]=c&a[2]=d', withEmptyKeys: { '': '', a: ['b', 'c', 'd'] }, stringifyOutput: '=&a[0]=b&a[1]=c&a[2]=d', noEmptyKeys: { a: ['b', 'c', 'd'] } },
23
+ { input: '=a&=b', withEmptyKeys: { '': ['a', 'b'] }, stringifyOutput: '[0]=a&[1]=b', noEmptyKeys: {} },
24
+ { input: '=a&foo=b', withEmptyKeys: { '': 'a', foo: 'b' }, noEmptyKeys: { foo: 'b' }, stringifyOutput: '=a&foo=b' },
25
+
26
+ { input: 'a[]=b&a=c&=', withEmptyKeys: { '': '', a: ['b', 'c'] }, stringifyOutput: '=&a[0]=b&a[1]=c', noEmptyKeys: { a: ['b', 'c'] } },
27
+ { input: 'a[]=b&a=c&=', withEmptyKeys: { '': '', a: ['b', 'c'] }, stringifyOutput: '=&a[0]=b&a[1]=c', noEmptyKeys: { a: ['b', 'c'] } },
28
+ { input: 'a[0]=b&a=c&=', withEmptyKeys: { '': '', a: ['b', 'c'] }, stringifyOutput: '=&a[0]=b&a[1]=c', noEmptyKeys: { a: ['b', 'c'] } },
29
+ { input: 'a=b&a[]=c&=', withEmptyKeys: { '': '', a: ['b', 'c'] }, stringifyOutput: '=&a[0]=b&a[1]=c', noEmptyKeys: { a: ['b', 'c'] } },
30
+ { input: 'a=b&a[0]=c&=', withEmptyKeys: { '': '', a: ['b', 'c'] }, stringifyOutput: '=&a[0]=b&a[1]=c', noEmptyKeys: { a: ['b', 'c'] } },
31
+
32
+ { input: '[]=a&[]=b& []=1', withEmptyKeys: { '': ['a', 'b'], ' ': ['1'] }, stringifyOutput: '[0]=a&[1]=b& [0]=1', noEmptyKeys: { 0: 'a', 1: 'b', ' ': ['1'] } },
33
+ { input: '[0]=a&[1]=b&a[0]=1&a[1]=2', withEmptyKeys: { '': ['a', 'b'], a: ['1', '2'] }, noEmptyKeys: { 0: 'a', 1: 'b', a: ['1', '2'] }, stringifyOutput: '[0]=a&[1]=b&a[0]=1&a[1]=2' },
34
+ { input: '[deep]=a&[deep]=2', withEmptyKeys: { '': { deep: ['a', '2'] } }, stringifyOutput: '[deep][0]=a&[deep][1]=2', noEmptyKeys: { deep: ['a', '2'] } },
35
+ { input: '%5B0%5D=a&%5B1%5D=b', withEmptyKeys: { '': ['a', 'b'] }, stringifyOutput: '[0]=a&[1]=b', noEmptyKeys: { 0: 'a', 1: 'b' } }
36
+ ]
37
+ };
package/test/parse.js CHANGED
@@ -1,10 +1,15 @@
1
1
  'use strict';
2
2
 
3
3
  var test = require('tape');
4
- var qs = require('../');
5
- var utils = require('../lib/utils');
4
+ var hasPropertyDescriptors = require('has-property-descriptors')();
6
5
  var iconv = require('iconv-lite');
6
+ var mockProperty = require('mock-property');
7
+ var hasOverrideMistake = require('has-override-mistake')();
7
8
  var SaferBuffer = require('safer-buffer').Buffer;
9
+ var emptyTestCases = require('./empty-keys-cases').emptyTestCases;
10
+
11
+ var qs = require('../');
12
+ var utils = require('../lib/utils');
8
13
 
9
14
  test('parse()', function (t) {
10
15
  t.test('parses a simple string', function (st) {
@@ -601,6 +606,34 @@ test('parse()', function (t) {
601
606
  st.end();
602
607
  });
603
608
 
609
+ t.test('does not crash when the global Object prototype is frozen', { skip: !hasPropertyDescriptors || !hasOverrideMistake }, function (st) {
610
+ // We can't actually freeze the global Object prototype as that will interfere with other tests, and once an object is frozen, it
611
+ // can't be unfrozen. Instead, we add a new non-writable property to simulate this.
612
+ st.teardown(mockProperty(Object.prototype, 'frozenProp', { value: 'foo', nonWritable: true, nonEnumerable: true }));
613
+
614
+ st['throws'](
615
+ function () {
616
+ var obj = {};
617
+ obj.frozenProp = 'bar';
618
+ },
619
+ // node < 6 has a different error message
620
+ /^TypeError: Cannot assign to read only property 'frozenProp' of (?:object '#<Object>'|#<Object>)/,
621
+ 'regular assignment of an inherited non-writable property throws'
622
+ );
623
+
624
+ var parsed;
625
+ st.doesNotThrow(
626
+ function () {
627
+ parsed = qs.parse('frozenProp', { allowPrototypes: false });
628
+ },
629
+ 'parsing a nonwritable Object.prototype property does not throw'
630
+ );
631
+
632
+ st.deepEqual(parsed, {}, 'bare "frozenProp" results in {}');
633
+
634
+ st.end();
635
+ });
636
+
604
637
  t.test('params starting with a closing bracket', function (st) {
605
638
  st.deepEqual(qs.parse(']=toString'), { ']': 'toString' });
606
639
  st.deepEqual(qs.parse(']]=toString'), { ']]': 'toString' });
@@ -853,3 +886,13 @@ test('parse()', function (t) {
853
886
 
854
887
  t.end();
855
888
  });
889
+
890
+ test('parses empty keys', function (t) {
891
+ emptyTestCases.forEach(function (testCase) {
892
+ t.test('skips empty string key with ' + testCase.input, function (st) {
893
+ st.deepEqual(qs.parse(testCase.input), testCase.noEmptyKeys);
894
+
895
+ st.end();
896
+ });
897
+ });
898
+ });
package/test/stringify.js CHANGED
@@ -6,6 +6,7 @@ var utils = require('../lib/utils');
6
6
  var iconv = require('iconv-lite');
7
7
  var SaferBuffer = require('safer-buffer').Buffer;
8
8
  var hasSymbols = require('has-symbols');
9
+ var emptyTestCases = require('./empty-keys-cases').emptyTestCases;
9
10
  var hasBigInt = typeof BigInt === 'function';
10
11
 
11
12
  test('stringify()', function (t) {
@@ -160,6 +161,13 @@ test('stringify()', function (t) {
160
161
  s2t.end();
161
162
  });
162
163
 
164
+ st.test('array with multiple items with a comma inside', function (s2t) {
165
+ s2t.equal(qs.stringify({ a: ['c,d', 'e'] }, { encodeValuesOnly: true, arrayFormat: 'comma' }), 'a=c%2Cd,e');
166
+ s2t.equal(qs.stringify({ a: ['c,d', 'e'] }, { arrayFormat: 'comma' }), 'a=c%2Cd%2Ce');
167
+
168
+ s2t.end();
169
+ });
170
+
163
171
  st.end();
164
172
  });
165
173
 
@@ -171,6 +179,44 @@ test('stringify()', function (t) {
171
179
  st.end();
172
180
  });
173
181
 
182
+ t.test('stringifies comma and empty array values', function (st) {
183
+ st.equal(qs.stringify({ a: [',', '', 'c,d%'] }, { encode: false, arrayFormat: 'indices' }), 'a[0]=,&a[1]=&a[2]=c,d%');
184
+ st.equal(qs.stringify({ a: [',', '', 'c,d%'] }, { encode: false, arrayFormat: 'brackets' }), 'a[]=,&a[]=&a[]=c,d%');
185
+ st.equal(qs.stringify({ a: [',', '', 'c,d%'] }, { encode: false, arrayFormat: 'comma' }), 'a=,,,c,d%');
186
+ st.equal(qs.stringify({ a: [',', '', 'c,d%'] }, { encode: false, arrayFormat: 'repeat' }), 'a=,&a=&a=c,d%');
187
+
188
+ st.equal(qs.stringify({ a: [',', '', 'c,d%'] }, { encode: true, encodeValuesOnly: true, arrayFormat: 'indices' }), 'a[0]=%2C&a[1]=&a[2]=c%2Cd%25');
189
+ st.equal(qs.stringify({ a: [',', '', 'c,d%'] }, { encode: true, encodeValuesOnly: true, arrayFormat: 'brackets' }), 'a[]=%2C&a[]=&a[]=c%2Cd%25');
190
+ st.equal(qs.stringify({ a: [',', '', 'c,d%'] }, { encode: true, encodeValuesOnly: true, arrayFormat: 'comma' }), 'a=%2C,,c%2Cd%25');
191
+ st.equal(qs.stringify({ a: [',', '', 'c,d%'] }, { encode: true, encodeValuesOnly: true, arrayFormat: 'repeat' }), 'a=%2C&a=&a=c%2Cd%25');
192
+
193
+ st.equal(qs.stringify({ a: [',', '', 'c,d%'] }, { encode: true, encodeValuesOnly: false, arrayFormat: 'indices' }), 'a%5B0%5D=%2C&a%5B1%5D=&a%5B2%5D=c%2Cd%25');
194
+ st.equal(qs.stringify({ a: [',', '', 'c,d%'] }, { encode: true, encodeValuesOnly: false, arrayFormat: 'brackets' }), 'a%5B%5D=%2C&a%5B%5D=&a%5B%5D=c%2Cd%25');
195
+ st.equal(qs.stringify({ a: [',', '', 'c,d%'] }, { encode: true, encodeValuesOnly: false, arrayFormat: 'comma' }), 'a=%2C%2C%2Cc%2Cd%25');
196
+ st.equal(qs.stringify({ a: [',', '', 'c,d%'] }, { encode: true, encodeValuesOnly: false, arrayFormat: 'repeat' }), 'a=%2C&a=&a=c%2Cd%25');
197
+
198
+ st.end();
199
+ });
200
+
201
+ t.test('stringifies comma and empty non-array values', function (st) {
202
+ st.equal(qs.stringify({ a: ',', b: '', c: 'c,d%' }, { encode: false, arrayFormat: 'indices' }), 'a=,&b=&c=c,d%');
203
+ st.equal(qs.stringify({ a: ',', b: '', c: 'c,d%' }, { encode: false, arrayFormat: 'brackets' }), 'a=,&b=&c=c,d%');
204
+ st.equal(qs.stringify({ a: ',', b: '', c: 'c,d%' }, { encode: false, arrayFormat: 'comma' }), 'a=,&b=&c=c,d%');
205
+ st.equal(qs.stringify({ a: ',', b: '', c: 'c,d%' }, { encode: false, arrayFormat: 'repeat' }), 'a=,&b=&c=c,d%');
206
+
207
+ st.equal(qs.stringify({ a: ',', b: '', c: 'c,d%' }, { encode: true, encodeValuesOnly: true, arrayFormat: 'indices' }), 'a=%2C&b=&c=c%2Cd%25');
208
+ st.equal(qs.stringify({ a: ',', b: '', c: 'c,d%' }, { encode: true, encodeValuesOnly: true, arrayFormat: 'brackets' }), 'a=%2C&b=&c=c%2Cd%25');
209
+ st.equal(qs.stringify({ a: ',', b: '', c: 'c,d%' }, { encode: true, encodeValuesOnly: true, arrayFormat: 'comma' }), 'a=%2C&b=&c=c%2Cd%25');
210
+ st.equal(qs.stringify({ a: ',', b: '', c: 'c,d%' }, { encode: true, encodeValuesOnly: true, arrayFormat: 'repeat' }), 'a=%2C&b=&c=c%2Cd%25');
211
+
212
+ st.equal(qs.stringify({ a: ',', b: '', c: 'c,d%' }, { encode: true, encodeValuesOnly: false, arrayFormat: 'indices' }), 'a=%2C&b=&c=c%2Cd%25');
213
+ st.equal(qs.stringify({ a: ',', b: '', c: 'c,d%' }, { encode: true, encodeValuesOnly: false, arrayFormat: 'brackets' }), 'a=%2C&b=&c=c%2Cd%25');
214
+ st.equal(qs.stringify({ a: ',', b: '', c: 'c,d%' }, { encode: true, encodeValuesOnly: false, arrayFormat: 'comma' }), 'a=%2C&b=&c=c%2Cd%25');
215
+ st.equal(qs.stringify({ a: ',', b: '', c: 'c,d%' }, { encode: true, encodeValuesOnly: false, arrayFormat: 'repeat' }), 'a=%2C&b=&c=c%2Cd%25');
216
+
217
+ st.end();
218
+ });
219
+
174
220
  t.test('stringifies a nested array value with dots notation', function (st) {
175
221
  st.equal(
176
222
  qs.stringify(
@@ -907,3 +953,20 @@ test('stringify()', function (t) {
907
953
 
908
954
  t.end();
909
955
  });
956
+
957
+ test('stringifies empty keys', function (t) {
958
+ emptyTestCases.forEach(function (testCase) {
959
+ t.test('stringifies an object with empty string key with ' + testCase.input, function (st) {
960
+ st.deepEqual(qs.stringify(testCase.withEmptyKeys, { encode: false }), testCase.stringifyOutput);
961
+
962
+ st.end();
963
+ });
964
+ });
965
+
966
+ t.test('edge case with object/arrays', function (st) {
967
+ st.deepEqual(qs.stringify({ '': { '': [2, 3] } }, { encode: false }), '[][0]=2&[][1]=3');
968
+ st.deepEqual(qs.stringify({ '': { '': [2, 3], a: 2 } }, { encode: false }), '[][0]=2&[][1]=3&[a]=2');
969
+
970
+ st.end();
971
+ });
972
+ });