@nocobase/plugin-multi-keyword-filter 2.2.16 → 2.2.18
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/dist/client/103.b39ee70786cb13a7.js +10 -0
- package/dist/client/index.js +1 -1
- package/dist/client-v2/103.dfed67c95bcd8a1d.js +10 -0
- package/dist/client-v2/index.js +1 -1
- package/dist/externalVersion.js +4 -4
- package/dist/node_modules/qs/.editorconfig +4 -1
- package/dist/node_modules/qs/.github/SECURITY.md +11 -0
- package/dist/node_modules/qs/.github/THREAT_MODEL.md +78 -0
- package/dist/node_modules/qs/dist/qs.js +90 -2036
- package/dist/node_modules/qs/lib/index.js +1 -1
- package/dist/node_modules/qs/lib/parse.js +215 -66
- package/dist/node_modules/qs/lib/stringify.js +90 -32
- package/dist/node_modules/qs/lib/utils.js +189 -53
- package/dist/node_modules/qs/node_modules/es-define-property/.eslintrc +13 -0
- package/dist/node_modules/qs/node_modules/es-define-property/.github/FUNDING.yml +12 -0
- package/dist/node_modules/qs/node_modules/es-define-property/.nycrc +9 -0
- package/dist/node_modules/qs/node_modules/es-define-property/index.d.ts +3 -0
- package/dist/node_modules/qs/node_modules/es-define-property/index.js +14 -0
- package/dist/node_modules/qs/node_modules/es-define-property/package.json +81 -0
- package/dist/node_modules/qs/node_modules/es-define-property/test/index.js +56 -0
- package/dist/node_modules/qs/node_modules/side-channel/.editorconfig +9 -0
- package/dist/node_modules/qs/node_modules/side-channel/.eslintrc +12 -0
- package/dist/node_modules/qs/node_modules/side-channel/.github/FUNDING.yml +12 -0
- package/dist/node_modules/qs/node_modules/side-channel/.nycrc +13 -0
- package/dist/node_modules/qs/node_modules/side-channel/index.d.ts +14 -0
- package/dist/node_modules/qs/node_modules/side-channel/index.js +46 -0
- package/dist/node_modules/qs/node_modules/side-channel/package.json +85 -0
- package/dist/node_modules/qs/node_modules/side-channel/test/index.js +120 -0
- package/dist/node_modules/qs/node_modules/side-channel-list/.editorconfig +9 -0
- package/dist/node_modules/qs/node_modules/side-channel-list/.eslintrc +11 -0
- package/dist/node_modules/qs/node_modules/side-channel-list/.github/FUNDING.yml +12 -0
- package/dist/node_modules/qs/node_modules/side-channel-list/.nycrc +13 -0
- package/dist/node_modules/qs/node_modules/side-channel-list/index.d.ts +13 -0
- package/dist/node_modules/qs/node_modules/side-channel-list/index.js +111 -0
- package/dist/node_modules/qs/node_modules/side-channel-list/list.d.ts +14 -0
- package/dist/node_modules/qs/node_modules/side-channel-list/package.json +77 -0
- package/dist/node_modules/qs/node_modules/side-channel-list/test/index.js +154 -0
- package/dist/node_modules/qs/package.json +1 -1
- package/dist/node_modules/qs/test/empty-keys-cases.js +261 -31
- package/dist/node_modules/qs/test/parse.js +1281 -57
- package/dist/node_modules/qs/test/stringify.js +851 -58
- package/dist/node_modules/qs/test/utils.js +505 -3
- package/package.json +5 -2
- package/dist/client/103.513149b773e262dc.js +0 -10
- package/dist/client-v2/103.8bcbf628d0af8457.js +0 -10
- package/dist/node_modules/qs/.eslintrc +0 -38
|
@@ -6,11 +6,20 @@ var iconv = require('iconv-lite');
|
|
|
6
6
|
var mockProperty = require('mock-property');
|
|
7
7
|
var hasOverrideMistake = require('has-override-mistake')();
|
|
8
8
|
var SaferBuffer = require('safer-buffer').Buffer;
|
|
9
|
+
var v = require('es-value-fixtures');
|
|
10
|
+
var inspect = require('object-inspect');
|
|
9
11
|
var emptyTestCases = require('./empty-keys-cases').emptyTestCases;
|
|
12
|
+
var hasProto = require('has-proto')();
|
|
10
13
|
|
|
11
14
|
var qs = require('../');
|
|
12
15
|
var utils = require('../lib/utils');
|
|
13
16
|
|
|
17
|
+
var characterizeParse = function characterizeParse(st, input, opts, expected, label) {
|
|
18
|
+
var result;
|
|
19
|
+
st.doesNotThrow(function () { result = qs.parse(input, opts); }, label + ': does not throw');
|
|
20
|
+
st.deepEqual(result, expected, label + ': parses to the current lenient output');
|
|
21
|
+
};
|
|
22
|
+
|
|
14
23
|
test('parse()', function (t) {
|
|
15
24
|
t.test('parses a simple string', function (st) {
|
|
16
25
|
st.deepEqual(qs.parse('0=foo'), { 0: 'foo' });
|
|
@@ -37,41 +46,156 @@ test('parse()', function (t) {
|
|
|
37
46
|
st.end();
|
|
38
47
|
});
|
|
39
48
|
|
|
40
|
-
t.test('
|
|
41
|
-
st.deepEqual(qs.parse('a[]=b&a[]=c'
|
|
42
|
-
st.deepEqual(qs.parse('a[0]=b&a[1]=c'
|
|
43
|
-
st.deepEqual(qs.parse('a=b,c'
|
|
44
|
-
st.deepEqual(qs.parse('a=b&a=c'
|
|
49
|
+
t.test('comma: false', function (st) {
|
|
50
|
+
st.deepEqual(qs.parse('a[]=b&a[]=c'), { a: ['b', 'c'] });
|
|
51
|
+
st.deepEqual(qs.parse('a[0]=b&a[1]=c'), { a: ['b', 'c'] });
|
|
52
|
+
st.deepEqual(qs.parse('a=b,c'), { a: 'b,c' });
|
|
53
|
+
st.deepEqual(qs.parse('a=b&a=c'), { a: ['b', 'c'] });
|
|
45
54
|
st.end();
|
|
46
55
|
});
|
|
47
56
|
|
|
48
|
-
t.test('
|
|
49
|
-
st.deepEqual(qs.parse('a[]=b&a[]=c', {
|
|
50
|
-
st.deepEqual(qs.parse('a[0]=b&a[1]=c', {
|
|
51
|
-
st.deepEqual(qs.parse('a=b,c', {
|
|
52
|
-
st.deepEqual(qs.parse('a=b&a=c', {
|
|
57
|
+
t.test('comma: true', function (st) {
|
|
58
|
+
st.deepEqual(qs.parse('a[]=b&a[]=c', { comma: true }), { a: ['b', 'c'] });
|
|
59
|
+
st.deepEqual(qs.parse('a[0]=b&a[1]=c', { comma: true }), { a: ['b', 'c'] });
|
|
60
|
+
st.deepEqual(qs.parse('a=b,c', { comma: true }), { a: ['b', 'c'] });
|
|
61
|
+
st.deepEqual(qs.parse('a=b&a=c', { comma: true }), { a: ['b', 'c'] });
|
|
53
62
|
st.end();
|
|
54
63
|
});
|
|
55
64
|
|
|
56
|
-
t.test('
|
|
57
|
-
st.deepEqual(qs.parse('a
|
|
58
|
-
st.deepEqual(qs.parse('a
|
|
59
|
-
|
|
60
|
-
st.deepEqual(qs.parse('a=b&a=c', { arrayFormat: 'comma' }), { a: ['b', 'c'] });
|
|
65
|
+
t.test('allows enabling dot notation', function (st) {
|
|
66
|
+
st.deepEqual(qs.parse('a.b=c'), { 'a.b': 'c' });
|
|
67
|
+
st.deepEqual(qs.parse('a.b=c', { allowDots: true }), { a: { b: 'c' } });
|
|
68
|
+
|
|
61
69
|
st.end();
|
|
62
70
|
});
|
|
63
71
|
|
|
64
|
-
t.test('
|
|
65
|
-
st.deepEqual(
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
72
|
+
t.test('decode dot keys correctly', function (st) {
|
|
73
|
+
st.deepEqual(
|
|
74
|
+
qs.parse('name%252Eobj.first=John&name%252Eobj.last=Doe', { allowDots: false, decodeDotInKeys: false }),
|
|
75
|
+
{ 'name%2Eobj.first': 'John', 'name%2Eobj.last': 'Doe' },
|
|
76
|
+
'with allowDots false and decodeDotInKeys false'
|
|
77
|
+
);
|
|
78
|
+
st.deepEqual(
|
|
79
|
+
qs.parse('name.obj.first=John&name.obj.last=Doe', { allowDots: true, decodeDotInKeys: false }),
|
|
80
|
+
{ name: { obj: { first: 'John', last: 'Doe' } } },
|
|
81
|
+
'with allowDots false and decodeDotInKeys false'
|
|
82
|
+
);
|
|
83
|
+
st.deepEqual(
|
|
84
|
+
qs.parse('name%252Eobj.first=John&name%252Eobj.last=Doe', { allowDots: true, decodeDotInKeys: false }),
|
|
85
|
+
{ 'name%2Eobj': { first: 'John', last: 'Doe' } },
|
|
86
|
+
'with allowDots true and decodeDotInKeys false'
|
|
87
|
+
);
|
|
88
|
+
st.deepEqual(
|
|
89
|
+
qs.parse('name%252Eobj.first=John&name%252Eobj.last=Doe', { allowDots: true, decodeDotInKeys: true }),
|
|
90
|
+
{ 'name.obj': { first: 'John', last: 'Doe' } },
|
|
91
|
+
'with allowDots true and decodeDotInKeys true'
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
st.deepEqual(
|
|
95
|
+
qs.parse(
|
|
96
|
+
'name%252Eobj%252Esubobject.first%252Egodly%252Ename=John&name%252Eobj%252Esubobject.last=Doe',
|
|
97
|
+
{ allowDots: false, decodeDotInKeys: false }
|
|
98
|
+
),
|
|
99
|
+
{ 'name%2Eobj%2Esubobject.first%2Egodly%2Ename': 'John', 'name%2Eobj%2Esubobject.last': 'Doe' },
|
|
100
|
+
'with allowDots false and decodeDotInKeys false'
|
|
101
|
+
);
|
|
102
|
+
st.deepEqual(
|
|
103
|
+
qs.parse(
|
|
104
|
+
'name.obj.subobject.first.godly.name=John&name.obj.subobject.last=Doe',
|
|
105
|
+
{ allowDots: true, decodeDotInKeys: false }
|
|
106
|
+
),
|
|
107
|
+
{ name: { obj: { subobject: { first: { godly: { name: 'John' } }, last: 'Doe' } } } },
|
|
108
|
+
'with allowDots true and decodeDotInKeys false'
|
|
109
|
+
);
|
|
110
|
+
st.deepEqual(
|
|
111
|
+
qs.parse(
|
|
112
|
+
'name%252Eobj%252Esubobject.first%252Egodly%252Ename=John&name%252Eobj%252Esubobject.last=Doe',
|
|
113
|
+
{ allowDots: true, decodeDotInKeys: true }
|
|
114
|
+
),
|
|
115
|
+
{ 'name.obj.subobject': { 'first.godly.name': 'John', last: 'Doe' } },
|
|
116
|
+
'with allowDots true and decodeDotInKeys true'
|
|
117
|
+
);
|
|
118
|
+
st.deepEqual(
|
|
119
|
+
qs.parse('name%252Eobj.first=John&name%252Eobj.last=Doe'),
|
|
120
|
+
{ 'name%2Eobj.first': 'John', 'name%2Eobj.last': 'Doe' },
|
|
121
|
+
'with allowDots and decodeDotInKeys undefined'
|
|
122
|
+
);
|
|
123
|
+
|
|
69
124
|
st.end();
|
|
70
125
|
});
|
|
71
126
|
|
|
72
|
-
t.test('
|
|
73
|
-
st.deepEqual(
|
|
74
|
-
|
|
127
|
+
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) {
|
|
128
|
+
st.deepEqual(
|
|
129
|
+
qs.parse(
|
|
130
|
+
'name%252Eobj%252Esubobject.first%252Egodly%252Ename=John&name%252Eobj%252Esubobject.last=Doe',
|
|
131
|
+
{ decodeDotInKeys: true }
|
|
132
|
+
),
|
|
133
|
+
{ 'name.obj.subobject': { 'first.godly.name': 'John', last: 'Doe' } },
|
|
134
|
+
'with allowDots undefined and decodeDotInKeys true'
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
st.end();
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
t.test('throws when decodeDotInKeys is not of type boolean', function (st) {
|
|
141
|
+
st['throws'](
|
|
142
|
+
function () { qs.parse('foo[]&bar=baz', { decodeDotInKeys: 'foobar' }); },
|
|
143
|
+
TypeError
|
|
144
|
+
);
|
|
145
|
+
|
|
146
|
+
st['throws'](
|
|
147
|
+
function () { qs.parse('foo[]&bar=baz', { decodeDotInKeys: 0 }); },
|
|
148
|
+
TypeError
|
|
149
|
+
);
|
|
150
|
+
st['throws'](
|
|
151
|
+
function () { qs.parse('foo[]&bar=baz', { decodeDotInKeys: NaN }); },
|
|
152
|
+
TypeError
|
|
153
|
+
);
|
|
154
|
+
|
|
155
|
+
st['throws'](
|
|
156
|
+
function () { qs.parse('foo[]&bar=baz', { decodeDotInKeys: null }); },
|
|
157
|
+
TypeError
|
|
158
|
+
);
|
|
159
|
+
|
|
160
|
+
st.end();
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
t.test('allows empty arrays in obj values', function (st) {
|
|
164
|
+
st.deepEqual(qs.parse('foo[]&bar=baz', { allowEmptyArrays: true }), { foo: [], bar: 'baz' });
|
|
165
|
+
st.deepEqual(qs.parse('foo[]&bar=baz', { allowEmptyArrays: false }), { foo: [''], bar: 'baz' });
|
|
166
|
+
|
|
167
|
+
st.end();
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
t.test('throws when allowEmptyArrays is not of type boolean', function (st) {
|
|
171
|
+
st['throws'](
|
|
172
|
+
function () { qs.parse('foo[]&bar=baz', { allowEmptyArrays: 'foobar' }); },
|
|
173
|
+
TypeError
|
|
174
|
+
);
|
|
175
|
+
|
|
176
|
+
st['throws'](
|
|
177
|
+
function () { qs.parse('foo[]&bar=baz', { allowEmptyArrays: 0 }); },
|
|
178
|
+
TypeError
|
|
179
|
+
);
|
|
180
|
+
st['throws'](
|
|
181
|
+
function () { qs.parse('foo[]&bar=baz', { allowEmptyArrays: NaN }); },
|
|
182
|
+
TypeError
|
|
183
|
+
);
|
|
184
|
+
|
|
185
|
+
st['throws'](
|
|
186
|
+
function () { qs.parse('foo[]&bar=baz', { allowEmptyArrays: null }); },
|
|
187
|
+
TypeError
|
|
188
|
+
);
|
|
189
|
+
|
|
190
|
+
st.end();
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
t.test('allowEmptyArrays + strictNullHandling', function (st) {
|
|
194
|
+
st.deepEqual(
|
|
195
|
+
qs.parse('testEmptyArray[]', { strictNullHandling: true, allowEmptyArrays: true }),
|
|
196
|
+
{ testEmptyArray: [] }
|
|
197
|
+
);
|
|
198
|
+
|
|
75
199
|
st.end();
|
|
76
200
|
});
|
|
77
201
|
|
|
@@ -92,6 +216,21 @@ test('parse()', function (t) {
|
|
|
92
216
|
t.test('uses original key when depth = 0', function (st) {
|
|
93
217
|
st.deepEqual(qs.parse('a[0]=b&a[1]=c', { depth: 0 }), { 'a[0]': 'b', 'a[1]': 'c' });
|
|
94
218
|
st.deepEqual(qs.parse('a[0][0]=b&a[0][1]=c&a[1]=d&e=2', { depth: 0 }), { 'a[0][0]': 'b', 'a[0][1]': 'c', 'a[1]': 'd', e: '2' });
|
|
219
|
+
st.deepEqual(qs.parse('a.b=c', { depth: 0, allowDots: true }), { 'a[b]': 'c' }, 'normalizes dots before applying depth-0 behavior');
|
|
220
|
+
st.deepEqual(qs.parse('toString=foo', { depth: 0 }), {}, 'respects prototype guard at depth 0');
|
|
221
|
+
st.deepEqual(qs.parse('toString=foo', { depth: 0, allowPrototypes: true }), { toString: 'foo' }, 'allows prototypes at depth 0 when enabled');
|
|
222
|
+
st.end();
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
t.test('ignores prototype keys when depth = 0 and allowPrototypes is false', function (st) {
|
|
226
|
+
st.deepEqual(qs.parse('toString=foo', { depth: 0 }), {});
|
|
227
|
+
st.deepEqual(qs.parse('hasOwnProperty=bar', { depth: 0 }), {});
|
|
228
|
+
st.deepEqual(qs.parse('toString=foo&a=b', { depth: 0 }), { a: 'b' });
|
|
229
|
+
st.end();
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
t.test('allows prototype keys when depth = 0 and allowPrototypes is true', function (st) {
|
|
233
|
+
st.deepEqual(qs.parse('toString=foo', { depth: 0, allowPrototypes: true }), { toString: 'foo' });
|
|
95
234
|
st.end();
|
|
96
235
|
});
|
|
97
236
|
|
|
@@ -117,11 +256,11 @@ test('parse()', function (t) {
|
|
|
117
256
|
st.deepEqual(qs.parse('a=b&a[0]=c'), { a: ['b', 'c'] });
|
|
118
257
|
|
|
119
258
|
st.deepEqual(qs.parse('a[1]=b&a=c', { arrayLimit: 20 }), { a: ['b', 'c'] });
|
|
120
|
-
st.deepEqual(qs.parse('a[]=b&a=c', { arrayLimit: 0 }), { a:
|
|
259
|
+
st.deepEqual(qs.parse('a[]=b&a=c', { arrayLimit: 0 }), { a: { 0: 'b', 1: 'c' } });
|
|
121
260
|
st.deepEqual(qs.parse('a[]=b&a=c'), { a: ['b', 'c'] });
|
|
122
261
|
|
|
123
262
|
st.deepEqual(qs.parse('a=b&a[1]=c', { arrayLimit: 20 }), { a: ['b', 'c'] });
|
|
124
|
-
st.deepEqual(qs.parse('a=b&a[]=c', { arrayLimit: 0 }), { a:
|
|
263
|
+
st.deepEqual(qs.parse('a=b&a[]=c', { arrayLimit: 0 }), { a: { 0: 'b', 1: 'c' } });
|
|
125
264
|
st.deepEqual(qs.parse('a=b&a[]=c'), { a: ['b', 'c'] });
|
|
126
265
|
|
|
127
266
|
st.end();
|
|
@@ -133,6 +272,94 @@ test('parse()', function (t) {
|
|
|
133
272
|
st.end();
|
|
134
273
|
});
|
|
135
274
|
|
|
275
|
+
t.test('parses keys with literal [] inside a bracket group (#493)', function (st) {
|
|
276
|
+
// A bracket pair inside a bracket group should be treated literally as part of the key
|
|
277
|
+
st.deepEqual(
|
|
278
|
+
qs.parse('search[withbracket[]]=foobar'),
|
|
279
|
+
{ search: { 'withbracket[]': 'foobar' } },
|
|
280
|
+
'treats inner [] literally when inside a bracket group'
|
|
281
|
+
);
|
|
282
|
+
|
|
283
|
+
// Single-level variant
|
|
284
|
+
st.deepEqual(
|
|
285
|
+
qs.parse('a[b[]]=c'),
|
|
286
|
+
{ a: { 'b[]': 'c' } },
|
|
287
|
+
'keeps "b[]" as a literal key'
|
|
288
|
+
);
|
|
289
|
+
|
|
290
|
+
// Nested with an array push on the outer level
|
|
291
|
+
st.deepEqual(
|
|
292
|
+
qs.parse('list[][x[]]=y'),
|
|
293
|
+
{ list: [{ 'x[]': 'y' }] },
|
|
294
|
+
'preserves inner [] while still treating outer [] as array push'
|
|
295
|
+
);
|
|
296
|
+
|
|
297
|
+
// Multiple nested bracket pairs: inner [] remains literal as part of the key
|
|
298
|
+
st.deepEqual(
|
|
299
|
+
qs.parse('a[b[c[]]]=d'),
|
|
300
|
+
{ a: { 'b[c[]]': 'd' } },
|
|
301
|
+
'treats "b[c[]]" as a literal key inside the bracket group'
|
|
302
|
+
);
|
|
303
|
+
|
|
304
|
+
// Depth limits with literal brackets: preserve inner [] while limiting bracket-group parsing
|
|
305
|
+
st.deepEqual(
|
|
306
|
+
qs.parse('a[b[c[]]][d]=e', { depth: 1 }),
|
|
307
|
+
{ a: { 'b[c[]]': { '[d]': 'e' } } },
|
|
308
|
+
'respects depth: 1 and preserves literal inner [] in the parsed key'
|
|
309
|
+
);
|
|
310
|
+
|
|
311
|
+
// Unterminated inner bracket group is wrapped as a literal remainder segment
|
|
312
|
+
st.deepEqual(
|
|
313
|
+
qs.parse('a[[]b=c'),
|
|
314
|
+
{ a: { '[[]b': 'c' } },
|
|
315
|
+
'handles unterminated inner bracket groups without throwing'
|
|
316
|
+
);
|
|
317
|
+
|
|
318
|
+
st.end();
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
t.test('currently parses unbalanced bracket keys after a parent leniently to literal segments (issue #558)', function (st) {
|
|
322
|
+
characterizeParse(st, 'a[bc=v', undefined, { a: { '[bc': 'v' } }, 'unclosed group after a parent');
|
|
323
|
+
characterizeParse(st, 'a[=v', undefined, { a: { '[': 'v' } }, 'bare unclosed bracket after a parent');
|
|
324
|
+
characterizeParse(st, 'a[b][c=v', undefined, { a: { b: { '[c': 'v' } } }, 'unclosed group after a valid one');
|
|
325
|
+
characterizeParse(st, 'a[b]c[d=v', undefined, { a: { b: { '[d': 'v' } } }, 'unclosed group after text following a valid one');
|
|
326
|
+
characterizeParse(st, 'filters[customtags:Env: Prod=v', undefined, { filters: { '[customtags:Env: Prod': 'v' } }, 'the issue #558 reproduction');
|
|
327
|
+
characterizeParse(st, '][a=v', undefined, { ']': { '[a': 'v' } }, 'stray close bracket before an unclosed group');
|
|
328
|
+
characterizeParse(st, 'a][b=v', undefined, { 'a]': { '[b': 'v' } }, 'stray close bracket inside the parent');
|
|
329
|
+
st.end();
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
t.test('currently parses unbalanced bracket keys containing inner brackets leniently (issue #558)', function (st) {
|
|
333
|
+
characterizeParse(st, 'a[b[c=v', undefined, { a: { '[b[c': 'v' } }, 'unclosed group containing an inner bracket');
|
|
334
|
+
characterizeParse(st, 'a[b[c]=v', undefined, { a: { '[b[c]': 'v' } }, 'unbalanced group with an inner bracket and one close');
|
|
335
|
+
characterizeParse(st, 'a[b][c[d=v', undefined, { a: { b: { '[c[d': 'v' } } }, 'unclosed inner-bracket group after a valid one');
|
|
336
|
+
st.end();
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
t.test('currently parses bracket-prefixed unbalanced keys leniently (issue #558)', function (st) {
|
|
340
|
+
characterizeParse(st, '[abc=v', undefined, { '[abc': 'v' }, 'key starting with an unclosed bracket');
|
|
341
|
+
characterizeParse(st, '[[]b=v', undefined, { '[[]b': 'v' }, 'key starting with an unbalanced bracket group');
|
|
342
|
+
st.end();
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
t.test('lenient unbalanced-bracket handling currently depends on the depth option (issue #558)', function (st) {
|
|
346
|
+
characterizeParse(st, 'a[b]c[d]e[f=v', { depth: 5 }, { a: { b: { d: { '[f': 'v' } } } }, 'consumes groups up to the depth budget then keeps the unclosed remainder literal');
|
|
347
|
+
characterizeParse(st, 'a[b]c[d]e[f=v', { depth: 1 }, { a: { b: { '[d]e[f': 'v' } } }, 'a lower depth keeps more of the unclosed remainder literal');
|
|
348
|
+
characterizeParse(st, 'a[bc=v', { depth: 0 }, { 'a[bc': 'v' }, 'depth 0 keeps the entire key literal');
|
|
349
|
+
st.end();
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
t.test('currently parses an allowDots key with a trailing unclosed bracket leniently (issue #558)', function (st) {
|
|
353
|
+
characterizeParse(st, 'a.b[c=v', { allowDots: true }, { a: { b: { '[c': 'v' } } }, 'allowDots expands the dot then keeps the unclosed bracket literal');
|
|
354
|
+
st.end();
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
t.test('valid and stray-close bracket keys are unaffected by unbalanced-bracket handling', function (st) {
|
|
358
|
+
characterizeParse(st, 'a]b=v', undefined, { 'a]b': 'v' }, 'stray close bracket with no open bracket stays a flat key');
|
|
359
|
+
characterizeParse(st, 'a[b]extra=v', undefined, { a: { b: 'v' } }, 'text after a balanced group is ignored');
|
|
360
|
+
st.end();
|
|
361
|
+
});
|
|
362
|
+
|
|
136
363
|
t.test('allows to specify array indices', function (st) {
|
|
137
364
|
st.deepEqual(qs.parse('a[1]=c&a[0]=b&a[2]=d'), { a: ['b', 'c', 'd'] });
|
|
138
365
|
st.deepEqual(qs.parse('a[1]=c&a[0]=b'), { a: ['b', 'c'] });
|
|
@@ -143,11 +370,11 @@ test('parse()', function (t) {
|
|
|
143
370
|
});
|
|
144
371
|
|
|
145
372
|
t.test('limits specific array indices to arrayLimit', function (st) {
|
|
146
|
-
st.deepEqual(qs.parse('a[
|
|
147
|
-
st.deepEqual(qs.parse('a[
|
|
373
|
+
st.deepEqual(qs.parse('a[19]=a', { arrayLimit: 20 }), { a: ['a'] });
|
|
374
|
+
st.deepEqual(qs.parse('a[20]=a', { arrayLimit: 20 }), { a: { 20: 'a' } });
|
|
148
375
|
|
|
149
|
-
st.deepEqual(qs.parse('a[
|
|
150
|
-
st.deepEqual(qs.parse('a[
|
|
376
|
+
st.deepEqual(qs.parse('a[19]=a'), { a: ['a'] });
|
|
377
|
+
st.deepEqual(qs.parse('a[20]=a'), { a: { 20: 'a' } });
|
|
151
378
|
st.end();
|
|
152
379
|
});
|
|
153
380
|
|
|
@@ -246,7 +473,7 @@ test('parse()', function (t) {
|
|
|
246
473
|
);
|
|
247
474
|
st.deepEqual(
|
|
248
475
|
qs.parse('a[]=b&a[]&a[]=c&a[]=', { strictNullHandling: true, arrayLimit: 0 }),
|
|
249
|
-
{ a:
|
|
476
|
+
{ a: { 0: 'b', 1: null, 2: 'c', 3: '' } },
|
|
250
477
|
'with arrayLimit 0 + array brackets: null then empty string works'
|
|
251
478
|
);
|
|
252
479
|
|
|
@@ -257,7 +484,7 @@ test('parse()', function (t) {
|
|
|
257
484
|
);
|
|
258
485
|
st.deepEqual(
|
|
259
486
|
qs.parse('a[]=b&a[]=&a[]=c&a[]', { strictNullHandling: true, arrayLimit: 0 }),
|
|
260
|
-
{ a:
|
|
487
|
+
{ a: { 0: 'b', 1: '', 2: 'c', 3: null } },
|
|
261
488
|
'with arrayLimit 0 + array brackets: empty string then null works'
|
|
262
489
|
);
|
|
263
490
|
|
|
@@ -326,15 +553,15 @@ test('parse()', function (t) {
|
|
|
326
553
|
st.end();
|
|
327
554
|
});
|
|
328
555
|
|
|
329
|
-
t.test('
|
|
330
|
-
Object.prototype
|
|
331
|
-
Array.prototype
|
|
556
|
+
t.test('does not throw when a native prototype has an enumerable property', function (st) {
|
|
557
|
+
st.intercept(Object.prototype, 'crash', { value: '' });
|
|
558
|
+
st.intercept(Array.prototype, 'crash', { value: '' });
|
|
559
|
+
|
|
332
560
|
st.doesNotThrow(qs.parse.bind(null, 'a=b'));
|
|
333
561
|
st.deepEqual(qs.parse('a=b'), { a: 'b' });
|
|
334
562
|
st.doesNotThrow(qs.parse.bind(null, 'a[][b]=c'));
|
|
335
563
|
st.deepEqual(qs.parse('a[][b]=c'), { a: [{ b: 'c' }] });
|
|
336
|
-
|
|
337
|
-
delete Array.prototype.crash;
|
|
564
|
+
|
|
338
565
|
st.end();
|
|
339
566
|
});
|
|
340
567
|
|
|
@@ -365,8 +592,14 @@ test('parse()', function (t) {
|
|
|
365
592
|
|
|
366
593
|
t.test('allows overriding array limit', function (st) {
|
|
367
594
|
st.deepEqual(qs.parse('a[0]=b', { arrayLimit: -1 }), { a: { 0: 'b' } });
|
|
595
|
+
st.deepEqual(qs.parse('a[0]=b', { arrayLimit: 0 }), { a: { 0: 'b' } });
|
|
596
|
+
|
|
368
597
|
st.deepEqual(qs.parse('a[-1]=b', { arrayLimit: -1 }), { a: { '-1': 'b' } });
|
|
598
|
+
st.deepEqual(qs.parse('a[-1]=b', { arrayLimit: 0 }), { a: { '-1': 'b' } });
|
|
599
|
+
|
|
600
|
+
st.deepEqual(qs.parse('a[0]=b&a[1]=c', { arrayLimit: -1 }), { a: { 0: 'b', 1: 'c' } });
|
|
369
601
|
st.deepEqual(qs.parse('a[0]=b&a[1]=c', { arrayLimit: 0 }), { a: { 0: 'b', 1: 'c' } });
|
|
602
|
+
|
|
370
603
|
st.end();
|
|
371
604
|
});
|
|
372
605
|
|
|
@@ -457,6 +690,15 @@ test('parse()', function (t) {
|
|
|
457
690
|
st.end();
|
|
458
691
|
});
|
|
459
692
|
|
|
693
|
+
t.test('parses url-encoded brackets holds array of arrays when having two parts of strings with comma as array divider', function (st) {
|
|
694
|
+
st.deepEqual(qs.parse('foo%5B%5D=1,2,3&foo%5B%5D=4,5,6', { comma: true }), { foo: [['1', '2', '3'], ['4', '5', '6']] });
|
|
695
|
+
st.deepEqual(qs.parse('foo%5B%5D=1,2,3&foo%5B%5D=', { comma: true }), { foo: [['1', '2', '3'], ''] });
|
|
696
|
+
st.deepEqual(qs.parse('foo%5B%5D=1,2,3&foo%5B%5D=,', { comma: true }), { foo: [['1', '2', '3'], ['', '']] });
|
|
697
|
+
st.deepEqual(qs.parse('foo%5B%5D=1,2,3&foo%5B%5D=a', { comma: true }), { foo: [['1', '2', '3'], 'a'] });
|
|
698
|
+
|
|
699
|
+
st.end();
|
|
700
|
+
});
|
|
701
|
+
|
|
460
702
|
t.test('parses comma delimited array while having percent-encoded comma treated as normal text', function (st) {
|
|
461
703
|
st.deepEqual(qs.parse('foo=a%2Cb', { comma: true }), { foo: 'a,b' });
|
|
462
704
|
st.deepEqual(qs.parse('foo=a%2C%20b,d', { comma: true }), { foo: ['a, b', 'd'] });
|
|
@@ -504,10 +746,12 @@ test('parse()', function (t) {
|
|
|
504
746
|
});
|
|
505
747
|
|
|
506
748
|
t.test('does not blow up when Buffer global is missing', function (st) {
|
|
507
|
-
var
|
|
508
|
-
|
|
749
|
+
var restore = mockProperty(global, 'Buffer', { 'delete': true });
|
|
750
|
+
|
|
509
751
|
var result = qs.parse('a=b&c=d');
|
|
510
|
-
|
|
752
|
+
|
|
753
|
+
restore();
|
|
754
|
+
|
|
511
755
|
st.deepEqual(result, { a: 'b', c: 'd' });
|
|
512
756
|
st.end();
|
|
513
757
|
});
|
|
@@ -530,6 +774,21 @@ test('parse()', function (t) {
|
|
|
530
774
|
st.end();
|
|
531
775
|
});
|
|
532
776
|
|
|
777
|
+
t.test('does not crash on multi-step circular references', function (st) {
|
|
778
|
+
var a = {};
|
|
779
|
+
a.b = { c: { d: a } };
|
|
780
|
+
|
|
781
|
+
var parsed;
|
|
782
|
+
|
|
783
|
+
st.doesNotThrow(function () {
|
|
784
|
+
parsed = qs.parse({ foo: a });
|
|
785
|
+
});
|
|
786
|
+
|
|
787
|
+
st.equal('foo' in parsed, true, 'parsed has "foo" property');
|
|
788
|
+
st.equal(parsed.foo.b.c.d, parsed.foo, 'the multi-step cycle is preserved');
|
|
789
|
+
st.end();
|
|
790
|
+
});
|
|
791
|
+
|
|
533
792
|
t.test('does not crash when parsing deep objects', function (st) {
|
|
534
793
|
var parsed;
|
|
535
794
|
var str = 'foo';
|
|
@@ -557,9 +816,8 @@ test('parse()', function (t) {
|
|
|
557
816
|
st.end();
|
|
558
817
|
});
|
|
559
818
|
|
|
560
|
-
t.test('parses null objects correctly', { skip: !
|
|
561
|
-
var a =
|
|
562
|
-
a.b = 'c';
|
|
819
|
+
t.test('parses null objects correctly', { skip: !hasProto }, function (st) {
|
|
820
|
+
var a = { __proto__: null, b: 'c' };
|
|
563
821
|
|
|
564
822
|
st.deepEqual(qs.parse(a), { b: 'c' });
|
|
565
823
|
var result = qs.parse({ a: a });
|
|
@@ -650,25 +908,25 @@ test('parse()', function (t) {
|
|
|
650
908
|
|
|
651
909
|
t.test('add keys to objects', function (st) {
|
|
652
910
|
st.deepEqual(
|
|
653
|
-
qs.parse('a[b]=c&a=d'),
|
|
911
|
+
qs.parse('a[b]=c&a=d', { strictMerge: false }),
|
|
654
912
|
{ a: { b: 'c', d: true } },
|
|
655
913
|
'can add keys to objects'
|
|
656
914
|
);
|
|
657
915
|
|
|
658
916
|
st.deepEqual(
|
|
659
|
-
qs.parse('a[b]=c&a=toString'),
|
|
917
|
+
qs.parse('a[b]=c&a=toString', { strictMerge: false }),
|
|
660
918
|
{ a: { b: 'c' } },
|
|
661
919
|
'can not overwrite prototype'
|
|
662
920
|
);
|
|
663
921
|
|
|
664
922
|
st.deepEqual(
|
|
665
|
-
qs.parse('a[b]=c&a=toString', { allowPrototypes: true }),
|
|
923
|
+
qs.parse('a[b]=c&a=toString', { strictMerge: false, allowPrototypes: true }),
|
|
666
924
|
{ a: { b: 'c', toString: true } },
|
|
667
925
|
'can overwrite prototype with allowPrototypes true'
|
|
668
926
|
);
|
|
669
927
|
|
|
670
928
|
st.deepEqual(
|
|
671
|
-
qs.parse('a[b]=c&a=toString', { plainObjects: true }),
|
|
929
|
+
qs.parse('a[b]=c&a=toString', { strictMerge: false, plainObjects: true }),
|
|
672
930
|
{ __proto__: null, a: { __proto__: null, b: 'c', toString: true } },
|
|
673
931
|
'can overwrite prototype with plainObjects true'
|
|
674
932
|
);
|
|
@@ -676,6 +934,34 @@ test('parse()', function (t) {
|
|
|
676
934
|
st.end();
|
|
677
935
|
});
|
|
678
936
|
|
|
937
|
+
t.test('strictMerge wraps object and primitive into an array', function (st) {
|
|
938
|
+
st.deepEqual(
|
|
939
|
+
qs.parse('a[b]=c&a=d'),
|
|
940
|
+
{ a: [{ b: 'c' }, 'd'] },
|
|
941
|
+
'object then primitive produces array'
|
|
942
|
+
);
|
|
943
|
+
|
|
944
|
+
st.deepEqual(
|
|
945
|
+
qs.parse('a=d&a[b]=c'),
|
|
946
|
+
{ a: ['d', { b: 'c' }] },
|
|
947
|
+
'primitive then object produces array'
|
|
948
|
+
);
|
|
949
|
+
|
|
950
|
+
st.deepEqual(
|
|
951
|
+
qs.parse('a[b]=c&a=toString'),
|
|
952
|
+
{ a: [{ b: 'c' }, 'toString'] },
|
|
953
|
+
'prototype-colliding value is preserved in array'
|
|
954
|
+
);
|
|
955
|
+
|
|
956
|
+
st.deepEqual(
|
|
957
|
+
qs.parse('a[b]=c&a=toString', { plainObjects: true }),
|
|
958
|
+
{ __proto__: null, a: [{ __proto__: null, b: 'c' }, 'toString'] },
|
|
959
|
+
'plainObjects preserved in array wrapping'
|
|
960
|
+
);
|
|
961
|
+
|
|
962
|
+
st.end();
|
|
963
|
+
});
|
|
964
|
+
|
|
679
965
|
t.test('dunder proto is ignored', function (st) {
|
|
680
966
|
var payload = 'categories[__proto__]=login&categories[__proto__]&categories[length]=42';
|
|
681
967
|
var result = qs.parse(payload, { allowPrototypes: true });
|
|
@@ -736,17 +1022,41 @@ test('parse()', function (t) {
|
|
|
736
1022
|
st.end();
|
|
737
1023
|
});
|
|
738
1024
|
|
|
739
|
-
t.test('
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
1025
|
+
t.test('object-valued input with own `__proto__` does not mutate sub-object [[Prototype]]', function (st) {
|
|
1026
|
+
// JSON.parse creates own data `__proto__` properties (via CreateDataProperty),
|
|
1027
|
+
// which would trigger the Object.prototype.__proto__ accessor if merged via `acc[key] = value`.
|
|
1028
|
+
var out = qs.parse({
|
|
1029
|
+
'user[name]': 'alice',
|
|
1030
|
+
user: JSON.parse('{"__proto__":{"isAdmin":true}}')
|
|
1031
|
+
}, { allowPrototypes: false });
|
|
1032
|
+
|
|
1033
|
+
st.equal(out.user.name, 'alice', 'name from bracket key is preserved');
|
|
1034
|
+
st.equal(out.user.isAdmin, undefined, 'attacker-controlled inherited property is not exposed');
|
|
1035
|
+
st.equal(Object.getPrototypeOf(out.user), Object.prototype, 'sub-object [[Prototype]] is unchanged');
|
|
1036
|
+
st.equal(Object.prototype.isAdmin, undefined, 'Object.prototype is not polluted');
|
|
1037
|
+
|
|
1038
|
+
st.end();
|
|
1039
|
+
});
|
|
1040
|
+
|
|
1041
|
+
t.test('can return null objects', { skip: !hasProto }, function (st) {
|
|
1042
|
+
var expected = {
|
|
1043
|
+
__proto__: null,
|
|
1044
|
+
a: {
|
|
1045
|
+
__proto__: null,
|
|
1046
|
+
b: 'c',
|
|
1047
|
+
hasOwnProperty: 'd'
|
|
1048
|
+
}
|
|
1049
|
+
};
|
|
744
1050
|
st.deepEqual(qs.parse('a[b]=c&a[hasOwnProperty]=d', { plainObjects: true }), expected);
|
|
745
|
-
st.deepEqual(qs.parse(null, { plainObjects: true }),
|
|
746
|
-
var expectedArray =
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
1051
|
+
st.deepEqual(qs.parse(null, { plainObjects: true }), { __proto__: null });
|
|
1052
|
+
var expectedArray = {
|
|
1053
|
+
__proto__: null,
|
|
1054
|
+
a: {
|
|
1055
|
+
__proto__: null,
|
|
1056
|
+
0: 'b',
|
|
1057
|
+
c: 'd'
|
|
1058
|
+
}
|
|
1059
|
+
};
|
|
750
1060
|
st.deepEqual(qs.parse('a[]=b&a[c]=d', { plainObjects: true }), expectedArray);
|
|
751
1061
|
st.end();
|
|
752
1062
|
});
|
|
@@ -823,7 +1133,7 @@ test('parse()', function (t) {
|
|
|
823
1133
|
st.end();
|
|
824
1134
|
});
|
|
825
1135
|
|
|
826
|
-
t.test('
|
|
1136
|
+
t.test('ignores an utf8 sentinel with an unknown value', function (st) {
|
|
827
1137
|
st.deepEqual(qs.parse('utf8=foo&' + urlEncodedOSlashInUtf8 + '=' + urlEncodedOSlashInUtf8, { charsetSentinel: true, charset: 'utf-8' }), { ø: 'ø' });
|
|
828
1138
|
st.end();
|
|
829
1139
|
});
|
|
@@ -854,6 +1164,20 @@ test('parse()', function (t) {
|
|
|
854
1164
|
st.end();
|
|
855
1165
|
});
|
|
856
1166
|
|
|
1167
|
+
t.test('handles a custom decoder returning `null`, with a string key of `null`', function (st) {
|
|
1168
|
+
st.deepEqual(
|
|
1169
|
+
qs.parse('null=1&ToNull=2', {
|
|
1170
|
+
decoder: function (str, defaultDecoder, charset) {
|
|
1171
|
+
return str === 'ToNull' ? null : defaultDecoder(str, defaultDecoder, charset);
|
|
1172
|
+
}
|
|
1173
|
+
}),
|
|
1174
|
+
{ 'null': '1' },
|
|
1175
|
+
'"null" key is not overridden by `null` decoder result'
|
|
1176
|
+
);
|
|
1177
|
+
|
|
1178
|
+
st.end();
|
|
1179
|
+
});
|
|
1180
|
+
|
|
857
1181
|
t.test('does not interpret numeric entities in iso-8859-1 when `interpretNumericEntities` is absent', function (st) {
|
|
858
1182
|
st.deepEqual(qs.parse('foo=' + urlEncodedNumSmiley, { charset: 'iso-8859-1' }), { foo: '☺' });
|
|
859
1183
|
st.end();
|
|
@@ -864,6 +1188,15 @@ test('parse()', function (t) {
|
|
|
864
1188
|
st.end();
|
|
865
1189
|
});
|
|
866
1190
|
|
|
1191
|
+
t.test('interpretNumericEntities with comma:true and iso charset does not crash', function (st) {
|
|
1192
|
+
st.deepEqual(
|
|
1193
|
+
qs.parse('b&a[]=1,' + urlEncodedNumSmiley, { comma: true, charset: 'iso-8859-1', interpretNumericEntities: true }),
|
|
1194
|
+
{ b: '', a: ['1,☺'] }
|
|
1195
|
+
);
|
|
1196
|
+
|
|
1197
|
+
st.end();
|
|
1198
|
+
});
|
|
1199
|
+
|
|
867
1200
|
t.test('does not interpret %uXXXX syntax in iso-8859-1 mode', function (st) {
|
|
868
1201
|
st.deepEqual(qs.parse('%u263A=%u263A', { charset: 'iso-8859-1' }), { '%u263A': '%u263A' });
|
|
869
1202
|
st.end();
|
|
@@ -881,6 +1214,520 @@ test('parse()', function (t) {
|
|
|
881
1214
|
};
|
|
882
1215
|
|
|
883
1216
|
st.deepEqual(qs.parse('KeY=vAlUe', { decoder: decoder }), { key: 'VALUE' });
|
|
1217
|
+
|
|
1218
|
+
var noopDecoder = function () { return 'x'; };
|
|
1219
|
+
noopDecoder();
|
|
1220
|
+
st['throws'](
|
|
1221
|
+
function () { decoder('x', noopDecoder, 'utf-8', 'unknown'); },
|
|
1222
|
+
'this should never happen! type: unknown',
|
|
1223
|
+
'decoder throws for unexpected type'
|
|
1224
|
+
);
|
|
1225
|
+
|
|
1226
|
+
st.end();
|
|
1227
|
+
});
|
|
1228
|
+
|
|
1229
|
+
t.test('parameter limit tests', function (st) {
|
|
1230
|
+
st.test('does not throw error when within parameter limit', function (sst) {
|
|
1231
|
+
var result = qs.parse('a=1&b=2&c=3', { parameterLimit: 5, throwOnLimitExceeded: true });
|
|
1232
|
+
sst.deepEqual(result, { a: '1', b: '2', c: '3' }, 'parses without errors');
|
|
1233
|
+
sst.end();
|
|
1234
|
+
});
|
|
1235
|
+
|
|
1236
|
+
st.test('throws error when throwOnLimitExceeded is present but not boolean', function (sst) {
|
|
1237
|
+
sst['throws'](
|
|
1238
|
+
function () {
|
|
1239
|
+
qs.parse('a=1&b=2&c=3&d=4&e=5&f=6', { parameterLimit: 3, throwOnLimitExceeded: 'true' });
|
|
1240
|
+
},
|
|
1241
|
+
new TypeError('`throwOnLimitExceeded` option must be a boolean'),
|
|
1242
|
+
'throws error when throwOnLimitExceeded is present and not boolean'
|
|
1243
|
+
);
|
|
1244
|
+
sst.end();
|
|
1245
|
+
});
|
|
1246
|
+
|
|
1247
|
+
st.test('throws error when parameter limit exceeded', function (sst) {
|
|
1248
|
+
sst['throws'](
|
|
1249
|
+
function () {
|
|
1250
|
+
qs.parse('a=1&b=2&c=3&d=4&e=5&f=6', { parameterLimit: 3, throwOnLimitExceeded: true });
|
|
1251
|
+
},
|
|
1252
|
+
new RangeError('Parameter limit exceeded. Only 3 parameters allowed.'),
|
|
1253
|
+
'throws error when parameter limit is exceeded'
|
|
1254
|
+
);
|
|
1255
|
+
|
|
1256
|
+
sst['throws'](
|
|
1257
|
+
function () {
|
|
1258
|
+
qs.parse('a=1&b=2', { parameterLimit: 1, throwOnLimitExceeded: true });
|
|
1259
|
+
},
|
|
1260
|
+
new RangeError('Parameter limit exceeded. Only 1 parameter allowed.'),
|
|
1261
|
+
'throws error with singular "parameter" when parameterLimit is 1'
|
|
1262
|
+
);
|
|
1263
|
+
sst.end();
|
|
1264
|
+
});
|
|
1265
|
+
|
|
1266
|
+
st.test('silently truncates when throwOnLimitExceeded is not given', function (sst) {
|
|
1267
|
+
var result = qs.parse('a=1&b=2&c=3&d=4&e=5', { parameterLimit: 3 });
|
|
1268
|
+
sst.deepEqual(result, { a: '1', b: '2', c: '3' }, 'parses and truncates silently');
|
|
1269
|
+
sst.end();
|
|
1270
|
+
});
|
|
1271
|
+
|
|
1272
|
+
st.test('silently truncates when parameter limit exceeded without error', function (sst) {
|
|
1273
|
+
var result = qs.parse('a=1&b=2&c=3&d=4&e=5', { parameterLimit: 3, throwOnLimitExceeded: false });
|
|
1274
|
+
sst.deepEqual(result, { a: '1', b: '2', c: '3' }, 'parses and truncates silently');
|
|
1275
|
+
sst.end();
|
|
1276
|
+
});
|
|
1277
|
+
|
|
1278
|
+
st.test('allows unlimited parameters when parameterLimit set to Infinity', function (sst) {
|
|
1279
|
+
var result = qs.parse('a=1&b=2&c=3&d=4&e=5&f=6', { parameterLimit: Infinity });
|
|
1280
|
+
sst.deepEqual(result, { a: '1', b: '2', c: '3', d: '4', e: '5', f: '6' }, 'parses all parameters without truncation');
|
|
1281
|
+
sst.end();
|
|
1282
|
+
});
|
|
1283
|
+
|
|
1284
|
+
st.test('allows unlimited parameters when parameterLimit is Infinity and throwOnLimitExceeded is true', function (sst) {
|
|
1285
|
+
var result = qs.parse('a=1&b=2&c=3&d=4&e=5&f=6', { parameterLimit: Infinity, throwOnLimitExceeded: true });
|
|
1286
|
+
sst.deepEqual(result, { a: '1', b: '2', c: '3', d: '4', e: '5', f: '6' }, 'parses all parameters without truncation or throwing');
|
|
1287
|
+
sst.end();
|
|
1288
|
+
});
|
|
1289
|
+
|
|
1290
|
+
st.end();
|
|
1291
|
+
});
|
|
1292
|
+
|
|
1293
|
+
t.test('array limit tests', function (st) {
|
|
1294
|
+
st.test('does not throw error when array is within limit', function (sst) {
|
|
1295
|
+
var result = qs.parse('a[]=1&a[]=2&a[]=3', { arrayLimit: 5, throwOnLimitExceeded: true });
|
|
1296
|
+
sst.deepEqual(result, { a: ['1', '2', '3'] }, 'parses array without errors');
|
|
1297
|
+
sst.end();
|
|
1298
|
+
});
|
|
1299
|
+
|
|
1300
|
+
st.test('throws error when throwOnLimitExceeded is present but not boolean for array limit', function (sst) {
|
|
1301
|
+
sst['throws'](
|
|
1302
|
+
function () {
|
|
1303
|
+
qs.parse('a[]=1&a[]=2&a[]=3&a[]=4', { arrayLimit: 3, throwOnLimitExceeded: 'true' });
|
|
1304
|
+
},
|
|
1305
|
+
new TypeError('`throwOnLimitExceeded` option must be a boolean'),
|
|
1306
|
+
'throws error when throwOnLimitExceeded is present and not boolean for array limit'
|
|
1307
|
+
);
|
|
1308
|
+
sst.end();
|
|
1309
|
+
});
|
|
1310
|
+
|
|
1311
|
+
st.test('throws error when array limit exceeded', function (sst) {
|
|
1312
|
+
// 4 elements exceeds limit of 3
|
|
1313
|
+
sst['throws'](
|
|
1314
|
+
function () {
|
|
1315
|
+
qs.parse('a[]=1&a[]=2&a[]=3&a[]=4', { arrayLimit: 3, throwOnLimitExceeded: true });
|
|
1316
|
+
},
|
|
1317
|
+
new RangeError('Array limit exceeded. Only 3 elements allowed in an array.'),
|
|
1318
|
+
'throws error when array limit is exceeded'
|
|
1319
|
+
);
|
|
1320
|
+
sst.end();
|
|
1321
|
+
});
|
|
1322
|
+
|
|
1323
|
+
st.test('does not throw when at limit', function (sst) {
|
|
1324
|
+
// 3 elements = limit of 3, should not throw
|
|
1325
|
+
var result = qs.parse('a[]=1&a[]=2&a[]=3', { arrayLimit: 3, throwOnLimitExceeded: true });
|
|
1326
|
+
sst.ok(Array.isArray(result.a), 'result is an array');
|
|
1327
|
+
sst.deepEqual(result.a, ['1', '2', '3'], 'all values present');
|
|
1328
|
+
sst.end();
|
|
1329
|
+
});
|
|
1330
|
+
|
|
1331
|
+
st.test('converts array to object if length is greater than limit', function (sst) {
|
|
1332
|
+
var result = qs.parse('a[1]=1&a[2]=2&a[3]=3&a[4]=4&a[5]=5&a[6]=6', { arrayLimit: 5 });
|
|
1333
|
+
|
|
1334
|
+
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');
|
|
1335
|
+
sst.end();
|
|
1336
|
+
});
|
|
1337
|
+
|
|
1338
|
+
st.test('throws error when indexed notation exceeds arrayLimit with throwOnLimitExceeded', function (sst) {
|
|
1339
|
+
sst['throws'](
|
|
1340
|
+
function () {
|
|
1341
|
+
qs.parse('a[1001]=b', { arrayLimit: 1000, throwOnLimitExceeded: true });
|
|
1342
|
+
},
|
|
1343
|
+
new RangeError('Array limit exceeded. Only 1000 elements allowed in an array.'),
|
|
1344
|
+
'throws error for a single index exceeding arrayLimit'
|
|
1345
|
+
);
|
|
1346
|
+
|
|
1347
|
+
sst['throws'](
|
|
1348
|
+
function () {
|
|
1349
|
+
qs.parse('a[0]=1&a[1]=2&a[2]=3&a[10]=4', { arrayLimit: 6, throwOnLimitExceeded: true, allowSparse: true });
|
|
1350
|
+
},
|
|
1351
|
+
new RangeError('Array limit exceeded. Only 6 elements allowed in an array.'),
|
|
1352
|
+
'throws error when a sparse index exceeds arrayLimit'
|
|
1353
|
+
);
|
|
1354
|
+
|
|
1355
|
+
sst['throws'](
|
|
1356
|
+
function () {
|
|
1357
|
+
qs.parse('a[2]=b', { arrayLimit: 1, throwOnLimitExceeded: true });
|
|
1358
|
+
},
|
|
1359
|
+
new RangeError('Array limit exceeded. Only 1 element allowed in an array.'),
|
|
1360
|
+
'throws error with singular "element" when arrayLimit is 1'
|
|
1361
|
+
);
|
|
1362
|
+
|
|
1363
|
+
sst.end();
|
|
1364
|
+
});
|
|
1365
|
+
|
|
1366
|
+
st.test('does not throw for indexed notation within arrayLimit with throwOnLimitExceeded', function (sst) {
|
|
1367
|
+
var result = qs.parse('a[4]=b', { arrayLimit: 5, throwOnLimitExceeded: true, allowSparse: true });
|
|
1368
|
+
sst.ok(Array.isArray(result.a), 'result is an array');
|
|
1369
|
+
sst.equal(result.a.length, 5, 'array has correct length');
|
|
1370
|
+
sst.equal(result.a[4], 'b', 'value at index 4 is correct');
|
|
1371
|
+
sst.end();
|
|
1372
|
+
});
|
|
1373
|
+
|
|
1374
|
+
st.test('silently converts to object for indexed notation exceeding arrayLimit without throwOnLimitExceeded', function (sst) {
|
|
1375
|
+
var result = qs.parse('a[1001]=b', { arrayLimit: 1000 });
|
|
1376
|
+
sst.deepEqual(result, { a: { 1001: 'b' } }, 'converts to object without throwing');
|
|
1377
|
+
sst.end();
|
|
1378
|
+
});
|
|
1379
|
+
|
|
1380
|
+
st.test('throws when duplicate bracket keys exceed arrayLimit with throwOnLimitExceeded', function (sst) {
|
|
1381
|
+
sst['throws'](
|
|
1382
|
+
function () {
|
|
1383
|
+
qs.parse('a[]=1&a[]=2&a[]=3&a[]=4&a[]=5&a[]=6', { arrayLimit: 5, throwOnLimitExceeded: true });
|
|
1384
|
+
},
|
|
1385
|
+
new RangeError('Array limit exceeded. Only 5 elements allowed in an array.'),
|
|
1386
|
+
'throws error when duplicate bracket notation exceeds array limit'
|
|
1387
|
+
);
|
|
1388
|
+
sst.end();
|
|
1389
|
+
});
|
|
1390
|
+
|
|
1391
|
+
st.test('throws when cumulative comma + duplicate-key combine exceeds arrayLimit', function (sst) {
|
|
1392
|
+
sst['throws'](
|
|
1393
|
+
function () {
|
|
1394
|
+
qs.parse('a=1,2,3&a=4,5,6', { comma: true, arrayLimit: 5, throwOnLimitExceeded: true });
|
|
1395
|
+
},
|
|
1396
|
+
new RangeError('Array limit exceeded. Only 5 elements allowed in an array.'),
|
|
1397
|
+
'throws when comma groups within the limit cumulatively exceed it across duplicate keys'
|
|
1398
|
+
);
|
|
1399
|
+
|
|
1400
|
+
sst['throws'](
|
|
1401
|
+
function () {
|
|
1402
|
+
qs.parse('a=v,v,v,v,v&a=v,v,v,v,v&a=v,v,v,v,v', { comma: true, arrayLimit: 5, throwOnLimitExceeded: true });
|
|
1403
|
+
},
|
|
1404
|
+
new RangeError('Array limit exceeded. Only 5 elements allowed in an array.'),
|
|
1405
|
+
'throws on a subsequent part once the cumulative array is already over the limit'
|
|
1406
|
+
);
|
|
1407
|
+
sst.end();
|
|
1408
|
+
});
|
|
1409
|
+
|
|
1410
|
+
st.test('throws when plain duplicate keys combine past arrayLimit at the boundary', function (sst) {
|
|
1411
|
+
sst['throws'](
|
|
1412
|
+
function () { qs.parse('a=x&a=y', { arrayLimit: 1, throwOnLimitExceeded: true }); },
|
|
1413
|
+
new RangeError('Array limit exceeded. Only 1 element allowed in an array.'),
|
|
1414
|
+
'duplicate scalar keys'
|
|
1415
|
+
);
|
|
1416
|
+
|
|
1417
|
+
sst['throws'](
|
|
1418
|
+
function () { qs.parse('a[]=x&a[]=y', { arrayLimit: 1, throwOnLimitExceeded: true }); },
|
|
1419
|
+
new RangeError('Array limit exceeded. Only 1 element allowed in an array.'),
|
|
1420
|
+
'duplicate bracket keys'
|
|
1421
|
+
);
|
|
1422
|
+
sst.end();
|
|
1423
|
+
});
|
|
1424
|
+
|
|
1425
|
+
st.test('throws when mixed index and key notation merge past arrayLimit', function (sst) {
|
|
1426
|
+
sst['throws'](
|
|
1427
|
+
function () { qs.parse('a=x&a[0]=y', { arrayLimit: 1, throwOnLimitExceeded: true }); },
|
|
1428
|
+
new RangeError('Array limit exceeded. Only 1 element allowed in an array.'),
|
|
1429
|
+
'scalar then index that overflows on merge'
|
|
1430
|
+
);
|
|
1431
|
+
|
|
1432
|
+
sst['throws'](
|
|
1433
|
+
function () { qs.parse('a[0]=1&a[1]=2&a=3', { arrayLimit: 1, throwOnLimitExceeded: true }); },
|
|
1434
|
+
new RangeError('Array limit exceeded. Only 1 element allowed in an array.'),
|
|
1435
|
+
'indexed array then scalar that overflows on merge'
|
|
1436
|
+
);
|
|
1437
|
+
sst.end();
|
|
1438
|
+
});
|
|
1439
|
+
|
|
1440
|
+
st.test('enforces arrayLimit on merge at the boundary, consistently with combine', function (sst) {
|
|
1441
|
+
sst['throws'](
|
|
1442
|
+
function () { qs.parse('a[0]=x&a=y', { arrayLimit: 1, throwOnLimitExceeded: true }); },
|
|
1443
|
+
new RangeError('Array limit exceeded. Only 1 element allowed in an array.'),
|
|
1444
|
+
'a trailing scalar merged into an at-limit array throws'
|
|
1445
|
+
);
|
|
1446
|
+
sst.deepEqual(
|
|
1447
|
+
qs.parse('a[0]=x&a=y', { arrayLimit: 1 }),
|
|
1448
|
+
{ a: { 0: 'x', 1: 'y' } },
|
|
1449
|
+
'and converts to an overflow object without throwOnLimitExceeded'
|
|
1450
|
+
);
|
|
1451
|
+
|
|
1452
|
+
sst['throws'](
|
|
1453
|
+
function () { qs.parse('a[0]=x&a[]=y', { arrayLimit: 1, throwOnLimitExceeded: true }); },
|
|
1454
|
+
new RangeError('Array limit exceeded. Only 1 element allowed in an array.'),
|
|
1455
|
+
'mixed index and bracket notation merged past the limit throws'
|
|
1456
|
+
);
|
|
1457
|
+
sst.deepEqual(
|
|
1458
|
+
qs.parse('a[0]=x&a[]=y', { arrayLimit: 1 }),
|
|
1459
|
+
{ a: { 0: 'x', 1: 'y' } },
|
|
1460
|
+
'mixed index and bracket notation converts like duplicate-bracket combine'
|
|
1461
|
+
);
|
|
1462
|
+
|
|
1463
|
+
sst.end();
|
|
1464
|
+
});
|
|
1465
|
+
|
|
1466
|
+
st.test('does not throw when cumulative comma combine stays within arrayLimit', function (sst) {
|
|
1467
|
+
var result = qs.parse('a=1,2,3&a=4', { comma: true, arrayLimit: 5, throwOnLimitExceeded: true });
|
|
1468
|
+
sst.deepEqual(result, { a: ['1', '2', '3', '4'] }, 'combined array within limit is preserved');
|
|
1469
|
+
sst.end();
|
|
1470
|
+
});
|
|
1471
|
+
|
|
1472
|
+
st.test('silently combines to an overflow object when throwOnLimitExceeded is not set', function (sst) {
|
|
1473
|
+
var result = qs.parse('a=1,2,3&a=4,5,6', { comma: true, arrayLimit: 5 });
|
|
1474
|
+
sst.deepEqual(result, { a: { 0: '1', 1: '2', 2: '3', 3: '4', 4: '5', 5: '6' } }, 'converts to object without throwing');
|
|
1475
|
+
sst.end();
|
|
1476
|
+
});
|
|
1477
|
+
|
|
1478
|
+
st.test('spreads a comma group appended to an already-overflowed object', function (sst) {
|
|
1479
|
+
var result = qs.parse('a=1,2,3,4,5,6&a=7,8', { comma: true, arrayLimit: 5 });
|
|
1480
|
+
sst.deepEqual(
|
|
1481
|
+
result,
|
|
1482
|
+
{ a: { 0: '1', 1: '2', 2: '3', 3: '4', 4: '5', 5: '6', 6: '7', 7: '8' } },
|
|
1483
|
+
'appended comma values are flattened, not nested'
|
|
1484
|
+
);
|
|
1485
|
+
sst.end();
|
|
1486
|
+
});
|
|
1487
|
+
|
|
1488
|
+
st.test('keeps a `[]=` comma group as one element when appended to an already-overflowed key', function (sst) {
|
|
1489
|
+
var result = qs.parse('a[]=1&a[]=2&a[]=3&a[]=4,5', { comma: true, arrayLimit: 2 });
|
|
1490
|
+
sst.deepEqual(
|
|
1491
|
+
result,
|
|
1492
|
+
{ a: { 0: '1', 1: '2', 2: '3', 3: ['4', '5'] } },
|
|
1493
|
+
'nested comma group stays a single element, not double-nested'
|
|
1494
|
+
);
|
|
1495
|
+
sst.end();
|
|
1496
|
+
});
|
|
1497
|
+
|
|
1498
|
+
st.test('spreads every later comma group appended to an already-overflowed object', function (sst) {
|
|
1499
|
+
var result = qs.parse('a=1,2,3,4,5,6&a=7,8&a=9,10', { comma: true, arrayLimit: 5 });
|
|
1500
|
+
sst.deepEqual(
|
|
1501
|
+
result,
|
|
1502
|
+
{ a: { 0: '1', 1: '2', 2: '3', 3: '4', 4: '5', 5: '6', 6: '7', 7: '8', 8: '9', 9: '10' } },
|
|
1503
|
+
'each appended group is flattened in order'
|
|
1504
|
+
);
|
|
1505
|
+
sst.end();
|
|
1506
|
+
});
|
|
1507
|
+
|
|
1508
|
+
st.test('spreads a comma group appended to an object overflowed under the default arrayLimit', function (sst) {
|
|
1509
|
+
var values = [];
|
|
1510
|
+
for (var i = 0; i <= 20; i += 1) {
|
|
1511
|
+
values[values.length] = String(i);
|
|
1512
|
+
}
|
|
1513
|
+
var expected = {};
|
|
1514
|
+
for (var j = 0; j < values.length; j += 1) {
|
|
1515
|
+
expected[j] = values[j];
|
|
1516
|
+
}
|
|
1517
|
+
expected[21] = 'x';
|
|
1518
|
+
expected[22] = 'y';
|
|
1519
|
+
|
|
1520
|
+
var result = qs.parse('a=' + values.join(',') + '&a=x,y', { comma: true });
|
|
1521
|
+
sst.deepEqual(result, { a: expected }, 'appended values land at indices 21 and 22');
|
|
1522
|
+
sst.end();
|
|
1523
|
+
});
|
|
1524
|
+
|
|
1525
|
+
st.test('spreads a comma group appended to an already-overflowed object with plainObjects', function (sst) {
|
|
1526
|
+
var result = qs.parse('a=1,2,3,4,5,6&a=7,8', { comma: true, arrayLimit: 5, plainObjects: true });
|
|
1527
|
+
sst.deepEqual(
|
|
1528
|
+
result,
|
|
1529
|
+
{ __proto__: null, a: { __proto__: null, 0: '1', 1: '2', 2: '3', 3: '4', 4: '5', 5: '6', 6: '7', 7: '8' } },
|
|
1530
|
+
'appended comma values are flattened into the null object'
|
|
1531
|
+
);
|
|
1532
|
+
sst.end();
|
|
1533
|
+
});
|
|
1534
|
+
|
|
1535
|
+
st.test('still throws with throwOnLimitExceeded when a comma group is appended to an already-overflowed object', function (sst) {
|
|
1536
|
+
sst['throws'](
|
|
1537
|
+
function () {
|
|
1538
|
+
qs.parse('a=1,2,3,4,5,6&a=7,8', { comma: true, arrayLimit: 5, throwOnLimitExceeded: true });
|
|
1539
|
+
},
|
|
1540
|
+
/Array limit exceeded/,
|
|
1541
|
+
'throws before spreading the appended group'
|
|
1542
|
+
);
|
|
1543
|
+
sst.end();
|
|
1544
|
+
});
|
|
1545
|
+
st.test('does not throw for comma groups nested under bracket notation, counting each group as one element', function (sst) {
|
|
1546
|
+
var result = qs.parse('a[]=1,2,3&a[]=4,5,6', { comma: true, arrayLimit: 5, throwOnLimitExceeded: true });
|
|
1547
|
+
sst.deepEqual(result, { a: [['1', '2', '3'], ['4', '5', '6']] }, 'nested comma groups count as one element each');
|
|
1548
|
+
sst.end();
|
|
1549
|
+
});
|
|
1550
|
+
|
|
1551
|
+
st.test('throws before splitting when a single comma value exceeds arrayLimit', function (sst) {
|
|
1552
|
+
sst['throws'](
|
|
1553
|
+
function () {
|
|
1554
|
+
qs.parse('a=1,2,3,4,5,6', { comma: true, arrayLimit: 5, throwOnLimitExceeded: true });
|
|
1555
|
+
},
|
|
1556
|
+
new RangeError('Array limit exceeded. Only 5 elements allowed in an array.'),
|
|
1557
|
+
'a flat comma value over the limit throws'
|
|
1558
|
+
);
|
|
1559
|
+
|
|
1560
|
+
sst['throws'](
|
|
1561
|
+
function () {
|
|
1562
|
+
qs.parse('a=1,2', { comma: true, arrayLimit: 1, throwOnLimitExceeded: true });
|
|
1563
|
+
},
|
|
1564
|
+
new RangeError('Array limit exceeded. Only 1 element allowed in an array.'),
|
|
1565
|
+
'singular message at arrayLimit 1'
|
|
1566
|
+
);
|
|
1567
|
+
|
|
1568
|
+
sst['throws'](
|
|
1569
|
+
function () {
|
|
1570
|
+
qs.parse('a[b]=1,2,3,4,5,6', { comma: true, arrayLimit: 5, throwOnLimitExceeded: true });
|
|
1571
|
+
},
|
|
1572
|
+
new RangeError('Array limit exceeded. Only 5 elements allowed in an array.'),
|
|
1573
|
+
'a non-bracket nested key comma value over the limit throws'
|
|
1574
|
+
);
|
|
1575
|
+
sst.end();
|
|
1576
|
+
});
|
|
1577
|
+
|
|
1578
|
+
st.test('does not throw for a single comma value within arrayLimit', function (sst) {
|
|
1579
|
+
sst.deepEqual(
|
|
1580
|
+
qs.parse('a=1,2,3', { comma: true, arrayLimit: 5, throwOnLimitExceeded: true }),
|
|
1581
|
+
{ a: ['1', '2', '3'] },
|
|
1582
|
+
'within the limit'
|
|
1583
|
+
);
|
|
1584
|
+
sst.deepEqual(
|
|
1585
|
+
qs.parse('a=1,2,3,4,5', { comma: true, arrayLimit: 5, throwOnLimitExceeded: true }),
|
|
1586
|
+
{ a: ['1', '2', '3', '4', '5'] },
|
|
1587
|
+
'exactly at the limit'
|
|
1588
|
+
);
|
|
1589
|
+
sst.end();
|
|
1590
|
+
});
|
|
1591
|
+
|
|
1592
|
+
st.test('throws before splitting when a bracketed comma group exceeds arrayLimit', function (sst) {
|
|
1593
|
+
sst['throws'](
|
|
1594
|
+
function () {
|
|
1595
|
+
qs.parse('a[]=1,2,3,4,5,6', { comma: true, arrayLimit: 5, throwOnLimitExceeded: true });
|
|
1596
|
+
},
|
|
1597
|
+
new RangeError('Array limit exceeded. Only 5 elements allowed in an array.'),
|
|
1598
|
+
'the inner group is itself an array and is subject to arrayLimit'
|
|
1599
|
+
);
|
|
1600
|
+
sst.end();
|
|
1601
|
+
});
|
|
1602
|
+
|
|
1603
|
+
st.test('throws before splitting when a comma group under `[]=` exceeds arrayLimit', function (sst) {
|
|
1604
|
+
sst['throws'](
|
|
1605
|
+
function () {
|
|
1606
|
+
qs.parse('a[]=1,2,3,4', { comma: true, arrayLimit: 3, throwOnLimitExceeded: true });
|
|
1607
|
+
},
|
|
1608
|
+
new RangeError('Array limit exceeded. Only 3 elements allowed in an array.'),
|
|
1609
|
+
'a bracket-push comma group over the limit throws like the flat form'
|
|
1610
|
+
);
|
|
1611
|
+
|
|
1612
|
+
sst['throws'](
|
|
1613
|
+
function () {
|
|
1614
|
+
qs.parse('a[]=1,2', { comma: true, arrayLimit: 1, throwOnLimitExceeded: true });
|
|
1615
|
+
},
|
|
1616
|
+
new RangeError('Array limit exceeded. Only 1 element allowed in an array.'),
|
|
1617
|
+
'singular message at arrayLimit 1'
|
|
1618
|
+
);
|
|
1619
|
+
|
|
1620
|
+
sst['throws'](
|
|
1621
|
+
function () {
|
|
1622
|
+
qs.parse('a[b][]=1,2,3,4', { comma: true, arrayLimit: 3, throwOnLimitExceeded: true });
|
|
1623
|
+
},
|
|
1624
|
+
new RangeError('Array limit exceeded. Only 3 elements allowed in an array.'),
|
|
1625
|
+
'a nested bracket-push key throws'
|
|
1626
|
+
);
|
|
1627
|
+
|
|
1628
|
+
sst['throws'](
|
|
1629
|
+
function () {
|
|
1630
|
+
qs.parse('a[][]=1,2,3,4', { comma: true, arrayLimit: 3, throwOnLimitExceeded: true });
|
|
1631
|
+
},
|
|
1632
|
+
new RangeError('Array limit exceeded. Only 3 elements allowed in an array.'),
|
|
1633
|
+
'a doubly bracket-pushed key throws'
|
|
1634
|
+
);
|
|
1635
|
+
|
|
1636
|
+
sst['throws'](
|
|
1637
|
+
function () {
|
|
1638
|
+
qs.parse('a[]=5&a[]=1,2,3,4', { comma: true, arrayLimit: 3, throwOnLimitExceeded: true });
|
|
1639
|
+
},
|
|
1640
|
+
new RangeError('Array limit exceeded. Only 3 elements allowed in an array.'),
|
|
1641
|
+
'an oversized group appended after a scalar throws'
|
|
1642
|
+
);
|
|
1643
|
+
|
|
1644
|
+
sst['throws'](
|
|
1645
|
+
function () {
|
|
1646
|
+
qs.parse('a[]=1,2,3,4&a[]=5', { comma: true, arrayLimit: 3, throwOnLimitExceeded: true });
|
|
1647
|
+
},
|
|
1648
|
+
new RangeError('Array limit exceeded. Only 3 elements allowed in an array.'),
|
|
1649
|
+
'an oversized group throws even when a later part would fit'
|
|
1650
|
+
);
|
|
1651
|
+
|
|
1652
|
+
sst['throws'](
|
|
1653
|
+
function () {
|
|
1654
|
+
qs.parse('a[]=1,2,3,4&a[]=5', { comma: true, arrayLimit: 3, throwOnLimitExceeded: true, duplicates: 'first' });
|
|
1655
|
+
},
|
|
1656
|
+
new RangeError('Array limit exceeded. Only 3 elements allowed in an array.'),
|
|
1657
|
+
'throws with duplicates: first'
|
|
1658
|
+
);
|
|
1659
|
+
|
|
1660
|
+
sst['throws'](
|
|
1661
|
+
function () {
|
|
1662
|
+
qs.parse('a[]=5&a[]=1,2,3,4', { comma: true, arrayLimit: 3, throwOnLimitExceeded: true, duplicates: 'last' });
|
|
1663
|
+
},
|
|
1664
|
+
new RangeError('Array limit exceeded. Only 3 elements allowed in an array.'),
|
|
1665
|
+
'throws with duplicates: last'
|
|
1666
|
+
);
|
|
1667
|
+
|
|
1668
|
+
sst['throws'](
|
|
1669
|
+
function () {
|
|
1670
|
+
qs.parse({ a: '1,2,3,4' }, { comma: true, arrayLimit: 3, throwOnLimitExceeded: true });
|
|
1671
|
+
},
|
|
1672
|
+
new RangeError('Array limit exceeded. Only 3 elements allowed in an array.'),
|
|
1673
|
+
'an oversized comma value in object input throws'
|
|
1674
|
+
);
|
|
1675
|
+
|
|
1676
|
+
var big = 'a[]=' + new Array(1e5 + 1).join('1,') + '1';
|
|
1677
|
+
sst['throws'](
|
|
1678
|
+
function () {
|
|
1679
|
+
qs.parse(big, { comma: true, arrayLimit: 3, throwOnLimitExceeded: true });
|
|
1680
|
+
},
|
|
1681
|
+
new RangeError('Array limit exceeded. Only 3 elements allowed in an array.'),
|
|
1682
|
+
'a very long bracket-push comma group throws'
|
|
1683
|
+
);
|
|
1684
|
+
sst.end();
|
|
1685
|
+
});
|
|
1686
|
+
|
|
1687
|
+
st.test('does not throw for a comma group under `[]=` within arrayLimit', function (sst) {
|
|
1688
|
+
sst.deepEqual(
|
|
1689
|
+
qs.parse('a[]=1,2,3', { comma: true, arrayLimit: 3, throwOnLimitExceeded: true }),
|
|
1690
|
+
{ a: [['1', '2', '3']] },
|
|
1691
|
+
'an inner group exactly at the limit stays a single nested element'
|
|
1692
|
+
);
|
|
1693
|
+
sst.deepEqual(
|
|
1694
|
+
qs.parse('a[]=1,2,3&a[]=4,5,6&a[]=7', { comma: true, arrayLimit: 3, throwOnLimitExceeded: true }),
|
|
1695
|
+
{ a: [['1', '2', '3'], ['4', '5', '6'], '7'] },
|
|
1696
|
+
'each group counts as one element of the outer array'
|
|
1697
|
+
);
|
|
1698
|
+
sst.deepEqual(
|
|
1699
|
+
qs.parse('a[b][]=1,2,3', { comma: true, arrayLimit: 3, throwOnLimitExceeded: true }),
|
|
1700
|
+
{ a: { b: [['1', '2', '3']] } },
|
|
1701
|
+
'nested bracket-push key within the limit'
|
|
1702
|
+
);
|
|
1703
|
+
sst.deepEqual(
|
|
1704
|
+
qs.parse({ a: '1,2,3' }, { comma: true, arrayLimit: 3, throwOnLimitExceeded: true }),
|
|
1705
|
+
{ a: ['1', '2', '3'] },
|
|
1706
|
+
'object input within the limit'
|
|
1707
|
+
);
|
|
1708
|
+
sst.end();
|
|
1709
|
+
});
|
|
1710
|
+
|
|
1711
|
+
st.test('keeps an oversized comma group under `[]=` as a nested array without throwOnLimitExceeded', function (sst) {
|
|
1712
|
+
sst.deepEqual(
|
|
1713
|
+
qs.parse('a[]=1,2,3,4', { comma: true, arrayLimit: 3 }),
|
|
1714
|
+
{ a: [['1', '2', '3', '4']] },
|
|
1715
|
+
'the inner group is not converted to an overflow object'
|
|
1716
|
+
);
|
|
1717
|
+
sst.end();
|
|
1718
|
+
});
|
|
1719
|
+
|
|
1720
|
+
st.test('throws for a bracketed comma group when arrayLimit is 0', function (sst) {
|
|
1721
|
+
sst['throws'](
|
|
1722
|
+
function () {
|
|
1723
|
+
qs.parse('a[]=1,2,3', { comma: true, arrayLimit: 0, throwOnLimitExceeded: true });
|
|
1724
|
+
},
|
|
1725
|
+
new RangeError('Array limit exceeded. Only 0 elements allowed in an array.'),
|
|
1726
|
+
'a single bracketed element still exceeds arrayLimit 0'
|
|
1727
|
+
);
|
|
1728
|
+
sst.end();
|
|
1729
|
+
});
|
|
1730
|
+
|
|
884
1731
|
st.end();
|
|
885
1732
|
});
|
|
886
1733
|
|
|
@@ -896,3 +1743,380 @@ test('parses empty keys', function (t) {
|
|
|
896
1743
|
});
|
|
897
1744
|
});
|
|
898
1745
|
});
|
|
1746
|
+
|
|
1747
|
+
test('`duplicates` option', function (t) {
|
|
1748
|
+
v.nonStrings.concat('not a valid option').forEach(function (invalidOption) {
|
|
1749
|
+
if (typeof invalidOption !== 'undefined') {
|
|
1750
|
+
t['throws'](
|
|
1751
|
+
function () { qs.parse('', { duplicates: invalidOption }); },
|
|
1752
|
+
TypeError,
|
|
1753
|
+
'throws on invalid option: ' + inspect(invalidOption)
|
|
1754
|
+
);
|
|
1755
|
+
}
|
|
1756
|
+
});
|
|
1757
|
+
|
|
1758
|
+
t.deepEqual(
|
|
1759
|
+
qs.parse('foo=bar&foo=baz'),
|
|
1760
|
+
{ foo: ['bar', 'baz'] },
|
|
1761
|
+
'duplicates: default, combine'
|
|
1762
|
+
);
|
|
1763
|
+
|
|
1764
|
+
t.deepEqual(
|
|
1765
|
+
qs.parse('foo=bar&foo=baz', { duplicates: 'combine' }),
|
|
1766
|
+
{ foo: ['bar', 'baz'] },
|
|
1767
|
+
'duplicates: combine'
|
|
1768
|
+
);
|
|
1769
|
+
|
|
1770
|
+
t.deepEqual(
|
|
1771
|
+
qs.parse('foo=bar&foo=baz', { duplicates: 'first' }),
|
|
1772
|
+
{ foo: 'bar' },
|
|
1773
|
+
'duplicates: first'
|
|
1774
|
+
);
|
|
1775
|
+
|
|
1776
|
+
t.deepEqual(
|
|
1777
|
+
qs.parse('foo=bar&foo=baz', { duplicates: 'last' }),
|
|
1778
|
+
{ foo: 'baz' },
|
|
1779
|
+
'duplicates: last'
|
|
1780
|
+
);
|
|
1781
|
+
|
|
1782
|
+
t.test('bracket notation always combines regardless of duplicates', function (st) {
|
|
1783
|
+
st.deepEqual(
|
|
1784
|
+
qs.parse('a=1&a=2&b[]=1&b[]=2', { duplicates: 'last' }),
|
|
1785
|
+
{ a: '2', b: ['1', '2'] },
|
|
1786
|
+
'duplicates last: unbracketed takes last, bracketed combines'
|
|
1787
|
+
);
|
|
1788
|
+
|
|
1789
|
+
st.deepEqual(
|
|
1790
|
+
qs.parse('b[]=1&b[]=2', { duplicates: 'last' }),
|
|
1791
|
+
{ b: ['1', '2'] },
|
|
1792
|
+
'duplicates last: bracketed always combines'
|
|
1793
|
+
);
|
|
1794
|
+
|
|
1795
|
+
st.deepEqual(
|
|
1796
|
+
qs.parse('b[]=1&b[]=2', { duplicates: 'first' }),
|
|
1797
|
+
{ b: ['1', '2'] },
|
|
1798
|
+
'duplicates first: bracketed always combines'
|
|
1799
|
+
);
|
|
1800
|
+
|
|
1801
|
+
st.deepEqual(
|
|
1802
|
+
qs.parse('a=1&a=2&b[]=1&b[]=2', { duplicates: 'first' }),
|
|
1803
|
+
{ a: '1', b: ['1', '2'] },
|
|
1804
|
+
'duplicates first: unbracketed takes first, bracketed combines'
|
|
1805
|
+
);
|
|
1806
|
+
|
|
1807
|
+
st.end();
|
|
1808
|
+
});
|
|
1809
|
+
|
|
1810
|
+
t.end();
|
|
1811
|
+
});
|
|
1812
|
+
|
|
1813
|
+
test('qs strictDepth option - throw cases', function (t) {
|
|
1814
|
+
t.test('throws an exception when depth exceeds the limit with strictDepth: true', function (st) {
|
|
1815
|
+
st['throws'](
|
|
1816
|
+
function () {
|
|
1817
|
+
qs.parse('a[b][c][d][e][f][g][h][i]=j', { depth: 1, strictDepth: true });
|
|
1818
|
+
},
|
|
1819
|
+
RangeError,
|
|
1820
|
+
'throws RangeError'
|
|
1821
|
+
);
|
|
1822
|
+
st.end();
|
|
1823
|
+
});
|
|
1824
|
+
|
|
1825
|
+
t.test('throws an exception for multiple nested arrays with strictDepth: true', function (st) {
|
|
1826
|
+
st['throws'](
|
|
1827
|
+
function () {
|
|
1828
|
+
qs.parse('a[0][1][2][3][4]=b', { depth: 3, strictDepth: true });
|
|
1829
|
+
},
|
|
1830
|
+
RangeError,
|
|
1831
|
+
'throws RangeError'
|
|
1832
|
+
);
|
|
1833
|
+
st.end();
|
|
1834
|
+
});
|
|
1835
|
+
|
|
1836
|
+
t.test('throws an exception for nested objects and arrays with strictDepth: true', function (st) {
|
|
1837
|
+
st['throws'](
|
|
1838
|
+
function () {
|
|
1839
|
+
qs.parse('a[b][c][0][d][e]=f', { depth: 3, strictDepth: true });
|
|
1840
|
+
},
|
|
1841
|
+
RangeError,
|
|
1842
|
+
'throws RangeError'
|
|
1843
|
+
);
|
|
1844
|
+
st.end();
|
|
1845
|
+
});
|
|
1846
|
+
|
|
1847
|
+
t.test('throws an exception for different types of values with strictDepth: true', function (st) {
|
|
1848
|
+
st['throws'](
|
|
1849
|
+
function () {
|
|
1850
|
+
qs.parse('a[b][c][d][e]=true&a[b][c][d][f]=42', { depth: 3, strictDepth: true });
|
|
1851
|
+
},
|
|
1852
|
+
RangeError,
|
|
1853
|
+
'throws RangeError'
|
|
1854
|
+
);
|
|
1855
|
+
st.end();
|
|
1856
|
+
});
|
|
1857
|
+
|
|
1858
|
+
});
|
|
1859
|
+
|
|
1860
|
+
test('qs strictDepth option - non-throw cases', function (t) {
|
|
1861
|
+
t.test('when depth is 0 and strictDepth true, do not throw', function (st) {
|
|
1862
|
+
st.doesNotThrow(
|
|
1863
|
+
function () {
|
|
1864
|
+
qs.parse('a[b][c][d][e]=true&a[b][c][d][f]=42', { depth: 0, strictDepth: true });
|
|
1865
|
+
},
|
|
1866
|
+
RangeError,
|
|
1867
|
+
'does not throw RangeError'
|
|
1868
|
+
);
|
|
1869
|
+
st.end();
|
|
1870
|
+
});
|
|
1871
|
+
|
|
1872
|
+
t.test('parses successfully when depth is within the limit with strictDepth: true', function (st) {
|
|
1873
|
+
st.doesNotThrow(
|
|
1874
|
+
function () {
|
|
1875
|
+
var result = qs.parse('a[b]=c', { depth: 1, strictDepth: true });
|
|
1876
|
+
st.deepEqual(result, { a: { b: 'c' } }, 'parses correctly');
|
|
1877
|
+
}
|
|
1878
|
+
);
|
|
1879
|
+
st.end();
|
|
1880
|
+
});
|
|
1881
|
+
|
|
1882
|
+
t.test('does not throw an exception when depth exceeds the limit with strictDepth: false', function (st) {
|
|
1883
|
+
st.doesNotThrow(
|
|
1884
|
+
function () {
|
|
1885
|
+
var result = qs.parse('a[b][c][d][e][f][g][h][i]=j', { depth: 1 });
|
|
1886
|
+
st.deepEqual(result, { a: { b: { '[c][d][e][f][g][h][i]': 'j' } } }, 'parses with depth limit');
|
|
1887
|
+
}
|
|
1888
|
+
);
|
|
1889
|
+
st.end();
|
|
1890
|
+
});
|
|
1891
|
+
|
|
1892
|
+
t.test('parses successfully when depth is within the limit with strictDepth: false', function (st) {
|
|
1893
|
+
st.doesNotThrow(
|
|
1894
|
+
function () {
|
|
1895
|
+
var result = qs.parse('a[b]=c', { depth: 1 });
|
|
1896
|
+
st.deepEqual(result, { a: { b: 'c' } }, 'parses correctly');
|
|
1897
|
+
}
|
|
1898
|
+
);
|
|
1899
|
+
st.end();
|
|
1900
|
+
});
|
|
1901
|
+
|
|
1902
|
+
t.test('does not throw when depth is exactly at the limit with strictDepth: true', function (st) {
|
|
1903
|
+
st.doesNotThrow(
|
|
1904
|
+
function () {
|
|
1905
|
+
var result = qs.parse('a[b][c]=d', { depth: 2, strictDepth: true });
|
|
1906
|
+
st.deepEqual(result, { a: { b: { c: 'd' } } }, 'parses correctly');
|
|
1907
|
+
}
|
|
1908
|
+
);
|
|
1909
|
+
st.end();
|
|
1910
|
+
});
|
|
1911
|
+
});
|
|
1912
|
+
|
|
1913
|
+
test('DOS', function (t) {
|
|
1914
|
+
var arr = [];
|
|
1915
|
+
for (var i = 0; i < 105; i++) {
|
|
1916
|
+
arr[arr.length] = 'x';
|
|
1917
|
+
}
|
|
1918
|
+
var attack = 'a[]=' + arr.join('&a[]=');
|
|
1919
|
+
var result = qs.parse(attack, { arrayLimit: 100 });
|
|
1920
|
+
|
|
1921
|
+
t.notOk(Array.isArray(result.a), 'arrayLimit is respected: result is an object, not an array');
|
|
1922
|
+
t.equal(Object.keys(result.a).length, 105, 'all values are preserved');
|
|
1923
|
+
|
|
1924
|
+
t.end();
|
|
1925
|
+
});
|
|
1926
|
+
|
|
1927
|
+
test('arrayLimit boundary conditions', function (t) {
|
|
1928
|
+
// arrayLimit is the max number of elements allowed in an array
|
|
1929
|
+
t.test('exactly at the limit stays as array', function (st) {
|
|
1930
|
+
// 3 elements = limit of 3
|
|
1931
|
+
var result = qs.parse('a[]=1&a[]=2&a[]=3', { arrayLimit: 3 });
|
|
1932
|
+
st.ok(Array.isArray(result.a), 'result is an array when count equals limit');
|
|
1933
|
+
st.deepEqual(result.a, ['1', '2', '3'], 'all values present');
|
|
1934
|
+
st.end();
|
|
1935
|
+
});
|
|
1936
|
+
|
|
1937
|
+
t.test('one over the limit converts to object', function (st) {
|
|
1938
|
+
// 4 elements exceeds limit of 3
|
|
1939
|
+
var result = qs.parse('a[]=1&a[]=2&a[]=3&a[]=4', { arrayLimit: 3 });
|
|
1940
|
+
st.notOk(Array.isArray(result.a), 'result is not an array when over limit');
|
|
1941
|
+
st.deepEqual(result.a, { 0: '1', 1: '2', 2: '3', 3: '4' }, 'all values preserved as object');
|
|
1942
|
+
st.end();
|
|
1943
|
+
});
|
|
1944
|
+
|
|
1945
|
+
t.test('arrayLimit 1 with one value', function (st) {
|
|
1946
|
+
// 1 element = limit of 1
|
|
1947
|
+
var result = qs.parse('a[]=1', { arrayLimit: 1 });
|
|
1948
|
+
st.ok(Array.isArray(result.a), 'result is an array when count equals limit');
|
|
1949
|
+
st.deepEqual(result.a, ['1'], 'value preserved as array');
|
|
1950
|
+
st.end();
|
|
1951
|
+
});
|
|
1952
|
+
|
|
1953
|
+
t.test('arrayLimit 1 with two values converts to object', function (st) {
|
|
1954
|
+
// 2 elements exceeds limit of 1
|
|
1955
|
+
var result = qs.parse('a[]=1&a[]=2', { arrayLimit: 1 });
|
|
1956
|
+
st.notOk(Array.isArray(result.a), 'result is not an array');
|
|
1957
|
+
st.deepEqual(result.a, { 0: '1', 1: '2' }, 'all values preserved as object');
|
|
1958
|
+
st.end();
|
|
1959
|
+
});
|
|
1960
|
+
|
|
1961
|
+
t.end();
|
|
1962
|
+
});
|
|
1963
|
+
|
|
1964
|
+
test('comma + arrayLimit', function (t) {
|
|
1965
|
+
t.test('comma-separated values within arrayLimit stay as array', function (st) {
|
|
1966
|
+
var result = qs.parse('a=1,2,3', { comma: true, arrayLimit: 5 });
|
|
1967
|
+
st.ok(Array.isArray(result.a), 'result is an array');
|
|
1968
|
+
st.deepEqual(result.a, ['1', '2', '3'], 'all values present');
|
|
1969
|
+
st.end();
|
|
1970
|
+
});
|
|
1971
|
+
|
|
1972
|
+
t.test('comma-separated values exceeding arrayLimit convert to object', function (st) {
|
|
1973
|
+
var result = qs.parse('a=1,2,3,4', { comma: true, arrayLimit: 3 });
|
|
1974
|
+
st.notOk(Array.isArray(result.a), 'result is not an array when over limit');
|
|
1975
|
+
st.deepEqual(result.a, { 0: '1', 1: '2', 2: '3', 3: '4' }, 'all values preserved as object');
|
|
1976
|
+
st.end();
|
|
1977
|
+
});
|
|
1978
|
+
|
|
1979
|
+
t.test('comma-separated values exceeding arrayLimit with throwOnLimitExceeded throws', function (st) {
|
|
1980
|
+
st['throws'](
|
|
1981
|
+
function () {
|
|
1982
|
+
qs.parse('a=1,2,3,4', { comma: true, arrayLimit: 3, throwOnLimitExceeded: true });
|
|
1983
|
+
},
|
|
1984
|
+
new RangeError('Array limit exceeded. Only 3 elements allowed in an array.'),
|
|
1985
|
+
'throws error when comma-split exceeds array limit'
|
|
1986
|
+
);
|
|
1987
|
+
|
|
1988
|
+
st['throws'](
|
|
1989
|
+
function () {
|
|
1990
|
+
qs.parse('a=1,2,3', { comma: true, arrayLimit: 1, throwOnLimitExceeded: true });
|
|
1991
|
+
},
|
|
1992
|
+
new RangeError('Array limit exceeded. Only 1 element allowed in an array.'),
|
|
1993
|
+
'throws error with singular "element" when arrayLimit is 1'
|
|
1994
|
+
);
|
|
1995
|
+
st.end();
|
|
1996
|
+
});
|
|
1997
|
+
|
|
1998
|
+
t.test('comma-separated values at exactly arrayLimit stay as array', function (st) {
|
|
1999
|
+
var result = qs.parse('a=1,2,3', { comma: true, arrayLimit: 3 });
|
|
2000
|
+
st.ok(Array.isArray(result.a), 'result is an array when exactly at limit');
|
|
2001
|
+
st.deepEqual(result.a, ['1', '2', '3'], 'all values present');
|
|
2002
|
+
st.end();
|
|
2003
|
+
});
|
|
2004
|
+
|
|
2005
|
+
t.end();
|
|
2006
|
+
});
|
|
2007
|
+
|
|
2008
|
+
test('mixed array and object notation', function (t) {
|
|
2009
|
+
t.test('array brackets with object key - under limit', function (st) {
|
|
2010
|
+
st.deepEqual(
|
|
2011
|
+
qs.parse('a[]=b&a[c]=d'),
|
|
2012
|
+
{ a: { 0: 'b', c: 'd' } },
|
|
2013
|
+
'mixing [] and [key] converts to object'
|
|
2014
|
+
);
|
|
2015
|
+
st.end();
|
|
2016
|
+
});
|
|
2017
|
+
|
|
2018
|
+
t.test('array index with object key - under limit', function (st) {
|
|
2019
|
+
st.deepEqual(
|
|
2020
|
+
qs.parse('a[0]=b&a[c]=d'),
|
|
2021
|
+
{ a: { 0: 'b', c: 'd' } },
|
|
2022
|
+
'mixing [0] and [key] produces object'
|
|
2023
|
+
);
|
|
2024
|
+
st.end();
|
|
2025
|
+
});
|
|
2026
|
+
|
|
2027
|
+
t.test('plain value with array brackets - under limit', function (st) {
|
|
2028
|
+
st.deepEqual(
|
|
2029
|
+
qs.parse('a=b&a[]=c', { arrayLimit: 20 }),
|
|
2030
|
+
{ a: ['b', 'c'] },
|
|
2031
|
+
'plain value combined with [] stays as array under limit'
|
|
2032
|
+
);
|
|
2033
|
+
st.end();
|
|
2034
|
+
});
|
|
2035
|
+
|
|
2036
|
+
t.test('array brackets with plain value - under limit', function (st) {
|
|
2037
|
+
st.deepEqual(
|
|
2038
|
+
qs.parse('a[]=b&a=c', { arrayLimit: 20 }),
|
|
2039
|
+
{ a: ['b', 'c'] },
|
|
2040
|
+
'[] combined with plain value stays as array under limit'
|
|
2041
|
+
);
|
|
2042
|
+
st.end();
|
|
2043
|
+
});
|
|
2044
|
+
|
|
2045
|
+
t.test('plain value with array index - under limit', function (st) {
|
|
2046
|
+
st.deepEqual(
|
|
2047
|
+
qs.parse('a=b&a[0]=c', { arrayLimit: 20 }),
|
|
2048
|
+
{ a: ['b', 'c'] },
|
|
2049
|
+
'plain value combined with [0] stays as array under limit'
|
|
2050
|
+
);
|
|
2051
|
+
st.end();
|
|
2052
|
+
});
|
|
2053
|
+
|
|
2054
|
+
t.test('multiple plain values with duplicates combine', function (st) {
|
|
2055
|
+
st.deepEqual(
|
|
2056
|
+
qs.parse('a=b&a=c&a=d', { arrayLimit: 20 }),
|
|
2057
|
+
{ a: ['b', 'c', 'd'] },
|
|
2058
|
+
'duplicate plain keys combine into array'
|
|
2059
|
+
);
|
|
2060
|
+
st.end();
|
|
2061
|
+
});
|
|
2062
|
+
|
|
2063
|
+
t.test('multiple plain values exceeding limit', function (st) {
|
|
2064
|
+
// 3 elements (indices 0-2), max index 2 > limit 1
|
|
2065
|
+
st.deepEqual(
|
|
2066
|
+
qs.parse('a=b&a=c&a=d', { arrayLimit: 1 }),
|
|
2067
|
+
{ a: { 0: 'b', 1: 'c', 2: 'd' } },
|
|
2068
|
+
'duplicate plain keys convert to object when exceeding limit'
|
|
2069
|
+
);
|
|
2070
|
+
st.end();
|
|
2071
|
+
});
|
|
2072
|
+
|
|
2073
|
+
t.test('mixed notation produces consistent results when arrayLimit is exceeded', function (st) {
|
|
2074
|
+
var expected = { a: { 0: 'b', 1: 'c', 2: 'd' } };
|
|
2075
|
+
|
|
2076
|
+
st.deepEqual(
|
|
2077
|
+
qs.parse('a[]=b&a[1]=c&a=d', { arrayLimit: -1 }),
|
|
2078
|
+
expected,
|
|
2079
|
+
'arrayLimit -1'
|
|
2080
|
+
);
|
|
2081
|
+
|
|
2082
|
+
st.deepEqual(
|
|
2083
|
+
qs.parse('a[]=b&a[1]=c&a=d', { arrayLimit: 0 }),
|
|
2084
|
+
expected,
|
|
2085
|
+
'arrayLimit 0'
|
|
2086
|
+
);
|
|
2087
|
+
|
|
2088
|
+
st.deepEqual(
|
|
2089
|
+
qs.parse('a[]=b&a[1]=c&a=d', { arrayLimit: 1 }),
|
|
2090
|
+
expected,
|
|
2091
|
+
'arrayLimit 1'
|
|
2092
|
+
);
|
|
2093
|
+
|
|
2094
|
+
st.end();
|
|
2095
|
+
});
|
|
2096
|
+
|
|
2097
|
+
t.test('uses existing array length for currentArrayLength when parsing object input with bracket keys', function (st) {
|
|
2098
|
+
var input = {};
|
|
2099
|
+
var arr = ['x', 'y'];
|
|
2100
|
+
arr.a = ['z', 'w'];
|
|
2101
|
+
input['a[]'] = arr;
|
|
2102
|
+
st.deepEqual(qs.parse(input), { a: ['x', 'y'] }, 'parses object input with bracket keys using existing array values');
|
|
2103
|
+
st.end();
|
|
2104
|
+
});
|
|
2105
|
+
|
|
2106
|
+
t.test('throws with singular message when object input bracket key exceeds arrayLimit of 1', function (st) {
|
|
2107
|
+
var input = {};
|
|
2108
|
+
var arr = ['x'];
|
|
2109
|
+
arr.a = ['z', 'w'];
|
|
2110
|
+
input['a[]'] = arr;
|
|
2111
|
+
st['throws'](
|
|
2112
|
+
function () {
|
|
2113
|
+
qs.parse(input, { throwOnLimitExceeded: true, arrayLimit: 1 });
|
|
2114
|
+
},
|
|
2115
|
+
new RangeError('Array limit exceeded. Only 1 element allowed in an array.'),
|
|
2116
|
+
'throws singular error for object input exceeding arrayLimit 1'
|
|
2117
|
+
);
|
|
2118
|
+
st.end();
|
|
2119
|
+
});
|
|
2120
|
+
|
|
2121
|
+
t.end();
|
|
2122
|
+
});
|