step-node-agent 3.29.0 → 3.29.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.
Files changed (64) hide show
  1. package/node_modules/body-parser/HISTORY.md +8 -0
  2. package/node_modules/body-parser/lib/types/urlencoded.js +2 -9
  3. package/node_modules/body-parser/package.json +9 -10
  4. package/node_modules/cookie/index.js +2 -1
  5. package/node_modules/cookie/package.json +1 -1
  6. package/node_modules/cookie-signature/History.md +5 -1
  7. package/node_modules/cookie-signature/index.js +6 -6
  8. package/node_modules/cookie-signature/package.json +2 -2
  9. package/node_modules/express/History.md +11 -0
  10. package/node_modules/express/package.json +17 -17
  11. package/node_modules/finalhandler/HISTORY.md +6 -0
  12. package/node_modules/finalhandler/package.json +3 -3
  13. package/node_modules/http-errors/HISTORY.md +6 -0
  14. package/node_modules/http-errors/index.js +4 -3
  15. package/node_modules/http-errors/package.json +12 -8
  16. package/node_modules/qs/.github/SECURITY.md +11 -0
  17. package/node_modules/qs/.github/THREAT_MODEL.md +78 -0
  18. package/node_modules/qs/CHANGELOG.md +31 -0
  19. package/node_modules/qs/README.md +25 -1
  20. package/node_modules/qs/dist/qs.js +95 -44
  21. package/node_modules/qs/eslint.config.mjs +56 -0
  22. package/node_modules/qs/lib/parse.js +107 -43
  23. package/node_modules/qs/lib/stringify.js +11 -6
  24. package/node_modules/qs/lib/utils.js +61 -6
  25. package/node_modules/qs/package.json +15 -12
  26. package/node_modules/qs/test/parse.js +257 -31
  27. package/node_modules/qs/test/stringify.js +23 -11
  28. package/node_modules/qs/test/utils.js +245 -0
  29. package/node_modules/raw-body/package.json +5 -7
  30. package/node_modules/send/HISTORY.md +19 -7
  31. package/node_modules/send/package.json +6 -6
  32. package/node_modules/serve-static/HISTORY.md +6 -0
  33. package/node_modules/serve-static/package.json +2 -2
  34. package/node_modules/statuses/HISTORY.md +5 -0
  35. package/node_modules/statuses/README.md +3 -0
  36. package/node_modules/statuses/package.json +7 -7
  37. package/node_modules/yaml/browser/dist/compose/compose-collection.js +1 -1
  38. package/node_modules/yaml/browser/dist/compose/resolve-block-seq.js +1 -1
  39. package/node_modules/yaml/browser/dist/compose/resolve-flow-collection.js +2 -2
  40. package/node_modules/yaml/browser/dist/errors.js +1 -1
  41. package/node_modules/yaml/browser/dist/nodes/Alias.js +1 -1
  42. package/node_modules/yaml/browser/dist/parse/parser.js +2 -2
  43. package/node_modules/yaml/browser/dist/stringify/stringifyNumber.js +1 -1
  44. package/node_modules/yaml/browser/dist/stringify/stringifyPair.js +1 -1
  45. package/node_modules/yaml/dist/compose/compose-collection.js +1 -1
  46. package/node_modules/yaml/dist/compose/resolve-block-seq.js +1 -1
  47. package/node_modules/yaml/dist/compose/resolve-flow-collection.js +2 -2
  48. package/node_modules/yaml/dist/errors.js +1 -1
  49. package/node_modules/yaml/dist/nodes/Alias.js +1 -1
  50. package/node_modules/yaml/dist/parse/parser.js +2 -2
  51. package/node_modules/yaml/dist/stringify/stringifyNumber.js +1 -1
  52. package/node_modules/yaml/dist/stringify/stringifyPair.js +1 -1
  53. package/node_modules/yaml/package.json +4 -4
  54. package/package.json +1 -1
  55. package/node_modules/body-parser/SECURITY.md +0 -25
  56. package/node_modules/cookie-signature/.npmignore +0 -4
  57. package/node_modules/qs/.eslintrc +0 -38
  58. package/node_modules/raw-body/HISTORY.md +0 -308
  59. package/node_modules/raw-body/SECURITY.md +0 -24
  60. package/node_modules/send/node_modules/encodeurl/HISTORY.md +0 -14
  61. package/node_modules/send/node_modules/encodeurl/LICENSE +0 -22
  62. package/node_modules/send/node_modules/encodeurl/README.md +0 -128
  63. package/node_modules/send/node_modules/encodeurl/index.js +0 -60
  64. package/node_modules/send/node_modules/encodeurl/package.json +0 -40
@@ -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
@@ -234,11 +235,11 @@ test('parse()', function (t) {
234
235
  st.deepEqual(qs.parse('a=b&a[0]=c'), { a: ['b', 'c'] });
235
236
 
236
237
  st.deepEqual(qs.parse('a[1]=b&a=c', { arrayLimit: 20 }), { a: ['b', 'c'] });
237
- st.deepEqual(qs.parse('a[]=b&a=c', { arrayLimit: 0 }), { a: ['b', 'c'] });
238
+ st.deepEqual(qs.parse('a[]=b&a=c', { arrayLimit: 0 }), { a: { 0: 'b', 1: 'c' } });
238
239
  st.deepEqual(qs.parse('a[]=b&a=c'), { a: ['b', 'c'] });
239
240
 
240
241
  st.deepEqual(qs.parse('a=b&a[1]=c', { arrayLimit: 20 }), { a: ['b', 'c'] });
241
- st.deepEqual(qs.parse('a=b&a[]=c', { arrayLimit: 0 }), { a: ['b', 'c'] });
242
+ st.deepEqual(qs.parse('a=b&a[]=c', { arrayLimit: 0 }), { a: { 0: 'b', 1: 'c' } });
242
243
  st.deepEqual(qs.parse('a=b&a[]=c'), { a: ['b', 'c'] });
243
244
 
244
245
  st.end();
@@ -363,7 +364,7 @@ test('parse()', function (t) {
363
364
  );
364
365
  st.deepEqual(
365
366
  qs.parse('a[]=b&a[]&a[]=c&a[]=', { strictNullHandling: true, arrayLimit: 0 }),
366
- { a: ['b', null, 'c', ''] },
367
+ { a: { 0: 'b', 1: null, 2: 'c', 3: '' } },
367
368
  'with arrayLimit 0 + array brackets: null then empty string works'
368
369
  );
369
370
 
@@ -374,7 +375,7 @@ test('parse()', function (t) {
374
375
  );
375
376
  st.deepEqual(
376
377
  qs.parse('a[]=b&a[]=&a[]=c&a[]', { strictNullHandling: true, arrayLimit: 0 }),
377
- { a: ['b', '', 'c', null] },
378
+ { a: { 0: 'b', 1: '', 2: 'c', 3: null } },
378
379
  'with arrayLimit 0 + array brackets: empty string then null works'
379
380
  );
380
381
 
@@ -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
  });
@@ -988,6 +996,20 @@ test('parse()', function (t) {
988
996
  st.end();
989
997
  });
990
998
 
999
+ t.test('handles a custom decoder returning `null`, with a string key of `null`', function (st) {
1000
+ st.deepEqual(
1001
+ qs.parse('null=1&ToNull=2', {
1002
+ decoder: function (str, defaultDecoder, charset) {
1003
+ return str === 'ToNull' ? null : defaultDecoder(str, defaultDecoder, charset);
1004
+ }
1005
+ }),
1006
+ { 'null': '1' },
1007
+ '"null" key is not overridden by `null` decoder result'
1008
+ );
1009
+
1010
+ st.end();
1011
+ });
1012
+
991
1013
  t.test('does not interpret numeric entities in iso-8859-1 when `interpretNumericEntities` is absent', function (st) {
992
1014
  st.deepEqual(qs.parse('foo=' + urlEncodedNumSmiley, { charset: 'iso-8859-1' }), { foo: '☺' });
993
1015
  st.end();
@@ -998,6 +1020,15 @@ test('parse()', function (t) {
998
1020
  st.end();
999
1021
  });
1000
1022
 
1023
+ t.test('interpretNumericEntities with comma:true and iso charset does not crash', function (st) {
1024
+ st.deepEqual(
1025
+ qs.parse('b&a[]=1,' + urlEncodedNumSmiley, { comma: true, charset: 'iso-8859-1', interpretNumericEntities: true }),
1026
+ { b: '', a: ['1,☺'] }
1027
+ );
1028
+
1029
+ st.end();
1030
+ });
1031
+
1001
1032
  t.test('does not interpret %uXXXX syntax in iso-8859-1 mode', function (st) {
1002
1033
  st.deepEqual(qs.parse('%u263A=%u263A', { charset: 'iso-8859-1' }), { '%u263A': '%u263A' });
1003
1034
  st.end();
@@ -1018,6 +1049,95 @@ test('parse()', function (t) {
1018
1049
  st.end();
1019
1050
  });
1020
1051
 
1052
+ t.test('parameter limit tests', function (st) {
1053
+ st.test('does not throw error when within parameter limit', function (sst) {
1054
+ var result = qs.parse('a=1&b=2&c=3', { parameterLimit: 5, throwOnLimitExceeded: true });
1055
+ sst.deepEqual(result, { a: '1', b: '2', c: '3' }, 'parses without errors');
1056
+ sst.end();
1057
+ });
1058
+
1059
+ st.test('throws error when throwOnLimitExceeded is present but not boolean', function (sst) {
1060
+ sst['throws'](
1061
+ function () {
1062
+ qs.parse('a=1&b=2&c=3&d=4&e=5&f=6', { parameterLimit: 3, throwOnLimitExceeded: 'true' });
1063
+ },
1064
+ new TypeError('`throwOnLimitExceeded` option must be a boolean'),
1065
+ 'throws error when throwOnLimitExceeded is present and not boolean'
1066
+ );
1067
+ sst.end();
1068
+ });
1069
+
1070
+ st.test('throws error when parameter limit exceeded', function (sst) {
1071
+ sst['throws'](
1072
+ function () {
1073
+ qs.parse('a=1&b=2&c=3&d=4&e=5&f=6', { parameterLimit: 3, throwOnLimitExceeded: true });
1074
+ },
1075
+ new RangeError('Parameter limit exceeded. Only 3 parameters allowed.'),
1076
+ 'throws error when parameter limit is exceeded'
1077
+ );
1078
+ sst.end();
1079
+ });
1080
+
1081
+ st.test('silently truncates when throwOnLimitExceeded is not given', function (sst) {
1082
+ var result = qs.parse('a=1&b=2&c=3&d=4&e=5', { parameterLimit: 3 });
1083
+ sst.deepEqual(result, { a: '1', b: '2', c: '3' }, 'parses and truncates silently');
1084
+ sst.end();
1085
+ });
1086
+
1087
+ st.test('silently truncates when parameter limit exceeded without error', function (sst) {
1088
+ var result = qs.parse('a=1&b=2&c=3&d=4&e=5', { parameterLimit: 3, throwOnLimitExceeded: false });
1089
+ sst.deepEqual(result, { a: '1', b: '2', c: '3' }, 'parses and truncates silently');
1090
+ sst.end();
1091
+ });
1092
+
1093
+ st.test('allows unlimited parameters when parameterLimit set to Infinity', function (sst) {
1094
+ var result = qs.parse('a=1&b=2&c=3&d=4&e=5&f=6', { parameterLimit: Infinity });
1095
+ sst.deepEqual(result, { a: '1', b: '2', c: '3', d: '4', e: '5', f: '6' }, 'parses all parameters without truncation');
1096
+ sst.end();
1097
+ });
1098
+
1099
+ st.end();
1100
+ });
1101
+
1102
+ t.test('array limit tests', function (st) {
1103
+ st.test('does not throw error when array is within limit', function (sst) {
1104
+ var result = qs.parse('a[]=1&a[]=2&a[]=3', { arrayLimit: 5, throwOnLimitExceeded: true });
1105
+ sst.deepEqual(result, { a: ['1', '2', '3'] }, 'parses array without errors');
1106
+ sst.end();
1107
+ });
1108
+
1109
+ st.test('throws error when throwOnLimitExceeded is present but not boolean for array limit', function (sst) {
1110
+ sst['throws'](
1111
+ function () {
1112
+ qs.parse('a[]=1&a[]=2&a[]=3&a[]=4', { arrayLimit: 3, throwOnLimitExceeded: 'true' });
1113
+ },
1114
+ new TypeError('`throwOnLimitExceeded` option must be a boolean'),
1115
+ 'throws error when throwOnLimitExceeded is present and not boolean for array limit'
1116
+ );
1117
+ sst.end();
1118
+ });
1119
+
1120
+ st.test('throws error when array limit exceeded', function (sst) {
1121
+ sst['throws'](
1122
+ function () {
1123
+ qs.parse('a[]=1&a[]=2&a[]=3&a[]=4', { arrayLimit: 3, throwOnLimitExceeded: true });
1124
+ },
1125
+ new RangeError('Array limit exceeded. Only 3 elements allowed in an array.'),
1126
+ 'throws error when array limit is exceeded'
1127
+ );
1128
+ sst.end();
1129
+ });
1130
+
1131
+ st.test('converts array to object if length is greater than limit', function (sst) {
1132
+ var result = qs.parse('a[1]=1&a[2]=2&a[3]=3&a[4]=4&a[5]=5&a[6]=6', { arrayLimit: 5 });
1133
+
1134
+ 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');
1135
+ sst.end();
1136
+ });
1137
+
1138
+ st.end();
1139
+ });
1140
+
1021
1141
  t.end();
1022
1142
  });
1023
1143
 
@@ -1076,7 +1196,7 @@ test('qs strictDepth option - throw cases', function (t) {
1076
1196
  qs.parse('a[b][c][d][e][f][g][h][i]=j', { depth: 1, strictDepth: true });
1077
1197
  },
1078
1198
  RangeError,
1079
- 'Should throw RangeError'
1199
+ 'throws RangeError'
1080
1200
  );
1081
1201
  st.end();
1082
1202
  });
@@ -1087,7 +1207,7 @@ test('qs strictDepth option - throw cases', function (t) {
1087
1207
  qs.parse('a[0][1][2][3][4]=b', { depth: 3, strictDepth: true });
1088
1208
  },
1089
1209
  RangeError,
1090
- 'Should throw RangeError'
1210
+ 'throws RangeError'
1091
1211
  );
1092
1212
  st.end();
1093
1213
  });
@@ -1098,7 +1218,7 @@ test('qs strictDepth option - throw cases', function (t) {
1098
1218
  qs.parse('a[b][c][0][d][e]=f', { depth: 3, strictDepth: true });
1099
1219
  },
1100
1220
  RangeError,
1101
- 'Should throw RangeError'
1221
+ 'throws RangeError'
1102
1222
  );
1103
1223
  st.end();
1104
1224
  });
@@ -1109,7 +1229,7 @@ test('qs strictDepth option - throw cases', function (t) {
1109
1229
  qs.parse('a[b][c][d][e]=true&a[b][c][d][f]=42', { depth: 3, strictDepth: true });
1110
1230
  },
1111
1231
  RangeError,
1112
- 'Should throw RangeError'
1232
+ 'throws RangeError'
1113
1233
  );
1114
1234
  st.end();
1115
1235
  });
@@ -1123,7 +1243,7 @@ test('qs strictDepth option - non-throw cases', function (t) {
1123
1243
  qs.parse('a[b][c][d][e]=true&a[b][c][d][f]=42', { depth: 0, strictDepth: true });
1124
1244
  },
1125
1245
  RangeError,
1126
- 'Should not throw RangeError'
1246
+ 'does not throw RangeError'
1127
1247
  );
1128
1248
  st.end();
1129
1249
  });
@@ -1132,7 +1252,7 @@ test('qs strictDepth option - non-throw cases', function (t) {
1132
1252
  st.doesNotThrow(
1133
1253
  function () {
1134
1254
  var result = qs.parse('a[b]=c', { depth: 1, strictDepth: true });
1135
- st.deepEqual(result, { a: { b: 'c' } }, 'Should parse correctly');
1255
+ st.deepEqual(result, { a: { b: 'c' } }, 'parses correctly');
1136
1256
  }
1137
1257
  );
1138
1258
  st.end();
@@ -1142,7 +1262,7 @@ test('qs strictDepth option - non-throw cases', function (t) {
1142
1262
  st.doesNotThrow(
1143
1263
  function () {
1144
1264
  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');
1265
+ st.deepEqual(result, { a: { b: { '[c][d][e][f][g][h][i]': 'j' } } }, 'parses with depth limit');
1146
1266
  }
1147
1267
  );
1148
1268
  st.end();
@@ -1152,7 +1272,7 @@ test('qs strictDepth option - non-throw cases', function (t) {
1152
1272
  st.doesNotThrow(
1153
1273
  function () {
1154
1274
  var result = qs.parse('a[b]=c', { depth: 1 });
1155
- st.deepEqual(result, { a: { b: 'c' } }, 'Should parse correctly');
1275
+ st.deepEqual(result, { a: { b: 'c' } }, 'parses correctly');
1156
1276
  }
1157
1277
  );
1158
1278
  st.end();
@@ -1162,9 +1282,115 @@ test('qs strictDepth option - non-throw cases', function (t) {
1162
1282
  st.doesNotThrow(
1163
1283
  function () {
1164
1284
  var result = qs.parse('a[b][c]=d', { depth: 2, strictDepth: true });
1165
- st.deepEqual(result, { a: { b: { c: 'd' } } }, 'Should parse correctly');
1285
+ st.deepEqual(result, { a: { b: { c: 'd' } } }, 'parses correctly');
1166
1286
  }
1167
1287
  );
1168
1288
  st.end();
1169
1289
  });
1170
1290
  });
1291
+
1292
+ test('DOS', function (t) {
1293
+ var arr = [];
1294
+ for (var i = 0; i < 105; i++) {
1295
+ arr[arr.length] = 'x';
1296
+ }
1297
+ var attack = 'a[]=' + arr.join('&a[]=');
1298
+ var result = qs.parse(attack, { arrayLimit: 100 });
1299
+
1300
+ t.notOk(Array.isArray(result.a), 'arrayLimit is respected: result is an object, not an array');
1301
+ t.equal(Object.keys(result.a).length, 105, 'all values are preserved');
1302
+
1303
+ t.end();
1304
+ });
1305
+
1306
+ test('arrayLimit boundary conditions', function (t) {
1307
+ t.test('exactly at the limit stays as array', function (st) {
1308
+ var result = qs.parse('a[]=1&a[]=2&a[]=3', { arrayLimit: 3 });
1309
+ st.ok(Array.isArray(result.a), 'result is an array when exactly at limit');
1310
+ st.deepEqual(result.a, ['1', '2', '3'], 'all values present');
1311
+ st.end();
1312
+ });
1313
+
1314
+ t.test('one over the limit converts to object', function (st) {
1315
+ var result = qs.parse('a[]=1&a[]=2&a[]=3&a[]=4', { arrayLimit: 3 });
1316
+ st.notOk(Array.isArray(result.a), 'result is not an array when over limit');
1317
+ st.deepEqual(result.a, { 0: '1', 1: '2', 2: '3', 3: '4' }, 'all values preserved as object');
1318
+ st.end();
1319
+ });
1320
+
1321
+ t.test('arrayLimit 1 with two values', function (st) {
1322
+ var result = qs.parse('a[]=1&a[]=2', { arrayLimit: 1 });
1323
+ st.notOk(Array.isArray(result.a), 'result is not an array');
1324
+ st.deepEqual(result.a, { 0: '1', 1: '2' }, 'both values preserved');
1325
+ st.end();
1326
+ });
1327
+
1328
+ t.end();
1329
+ });
1330
+
1331
+ test('mixed array and object notation', function (t) {
1332
+ t.test('array brackets with object key - under limit', function (st) {
1333
+ st.deepEqual(
1334
+ qs.parse('a[]=b&a[c]=d'),
1335
+ { a: { 0: 'b', c: 'd' } },
1336
+ 'mixing [] and [key] converts to object'
1337
+ );
1338
+ st.end();
1339
+ });
1340
+
1341
+ t.test('array index with object key - under limit', function (st) {
1342
+ st.deepEqual(
1343
+ qs.parse('a[0]=b&a[c]=d'),
1344
+ { a: { 0: 'b', c: 'd' } },
1345
+ 'mixing [0] and [key] produces object'
1346
+ );
1347
+ st.end();
1348
+ });
1349
+
1350
+ t.test('plain value with array brackets - under limit', function (st) {
1351
+ st.deepEqual(
1352
+ qs.parse('a=b&a[]=c', { arrayLimit: 20 }),
1353
+ { a: ['b', 'c'] },
1354
+ 'plain value combined with [] stays as array under limit'
1355
+ );
1356
+ st.end();
1357
+ });
1358
+
1359
+ t.test('array brackets with plain value - under limit', function (st) {
1360
+ st.deepEqual(
1361
+ qs.parse('a[]=b&a=c', { arrayLimit: 20 }),
1362
+ { a: ['b', 'c'] },
1363
+ '[] combined with plain value stays as array under limit'
1364
+ );
1365
+ st.end();
1366
+ });
1367
+
1368
+ t.test('plain value with array index - under limit', function (st) {
1369
+ st.deepEqual(
1370
+ qs.parse('a=b&a[0]=c', { arrayLimit: 20 }),
1371
+ { a: ['b', 'c'] },
1372
+ 'plain value combined with [0] stays as array under limit'
1373
+ );
1374
+ st.end();
1375
+ });
1376
+
1377
+ t.test('multiple plain values with duplicates combine', function (st) {
1378
+ st.deepEqual(
1379
+ qs.parse('a=b&a=c&a=d', { arrayLimit: 20 }),
1380
+ { a: ['b', 'c', 'd'] },
1381
+ 'duplicate plain keys combine into array'
1382
+ );
1383
+ st.end();
1384
+ });
1385
+
1386
+ t.test('multiple plain values exceeding limit', function (st) {
1387
+ st.deepEqual(
1388
+ qs.parse('a=b&a=c&a=d', { arrayLimit: 2 }),
1389
+ { a: { 0: 'b', 1: 'c', 2: 'd' } },
1390
+ 'duplicate plain keys convert to object when exceeding limit'
1391
+ );
1392
+ st.end();
1393
+ });
1394
+
1395
+ t.end();
1396
+ });
@@ -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,20 @@ 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 S = Object('abc');
1297
+ S.toString = function () {
1298
+ return 'd';
1299
+ };
1300
+ var actual = qs.stringify({ a: 'b', 'false': {}, 1e+22: 'c', d: 'e' }, {
1301
+ filter: ['a', false, null, 10000000000000000000000, S],
1302
+ allowDots: true,
1303
+ encodeDotInKeys: true
1304
+ });
1305
+
1306
+ st.equal(actual, 'a=b&1e%2B22=c&d=e', 'stringifies correctly');
1307
+
1308
+ st.end();
1309
+ });
1298
1310
  });