[Fix] parse: handle nested bracket groups and add regression tests · ljharb/qs@a419ce5 (original) (raw)
`@@ -214,9 +214,12 @@ var parseObject = function (chain, val, options, valuesParsed) {
`
214
214
`return leaf;
`
215
215
`};
`
216
216
``
217
``
`-
var splitKeyIntoSegments = function splitKeyIntoSegments(givenKey, options) {
`
218
``
`-
var key = options.allowDots ? givenKey.replace(/.([^.[]+)/g, '[$1]') : givenKey;
`
``
217
`+
// Split a key like "a[b][c[]]" into ['a', '[b]', '[c[]]'] while preserving
`
``
218
`+
// qs parse semantics for depth/prototype guards.
`
``
219
`+
var splitKeyIntoSegments = function splitKeyIntoSegments(originalKey, options) {
`
``
220
`+
var key = options.allowDots ? originalKey.replace(/.([^.[]+)/g, '[$1]') : originalKey;
`
219
221
``
``
222
`+
// depth <= 0 keeps the whole key as one segment
`
220
223
`if (options.depth <= 0) {
`
221
224
`if (!options.plainObjects && has.call(Object.prototype, key)) {
`
222
225
`if (!options.allowPrototypes) {
`
`@@ -227,47 +230,74 @@ var splitKeyIntoSegments = function splitKeyIntoSegments(givenKey, options) {
`
227
230
`return [key];
`
228
231
`}
`
229
232
``
230
``
`-
var brackets = /([[^[]]*])/;
`
231
``
`-
var child = /([[^[]]*])/g;
`
232
``
-
233
``
`-
var segment = brackets.exec(key);
`
234
``
`-
var parent = segment ? key.slice(0, segment.index) : key;
`
235
``
-
236
``
`-
var keys = [];
`
``
233
`+
var segments = [];
`
237
234
``
``
235
`+
// parent before the first '[' (may be empty if key starts with '[')
`
``
236
`+
var first = key.indexOf('[');
`
``
237
`+
var parent = first >= 0 ? key.slice(0, first) : key;
`
238
238
`if (parent) {
`
239
239
`if (!options.plainObjects && has.call(Object.prototype, parent)) {
`
240
240
`if (!options.allowPrototypes) {
`
241
241
`return;
`
242
242
`}
`
243
243
`}
`
244
244
``
245
``
`-
keys[keys.length] = parent;
`
``
245
`+
segments[segments.length] = parent;
`
246
246
`}
`
247
247
``
248
``
`-
var i = 0;
`
249
``
`-
while ((segment = child.exec(key)) !== null && i < options.depth) {
`
250
``
`-
i += 1;
`
251
``
-
252
``
`-
var segmentContent = segment[1].slice(1, -1);
`
253
``
`-
if (!options.plainObjects && has.call(Object.prototype, segmentContent)) {
`
254
``
`-
if (!options.allowPrototypes) {
`
255
``
`-
return;
`
``
248
`+
var n = key.length;
`
``
249
`+
var open = first;
`
``
250
`+
var collected = 0;
`
``
251
+
``
252
`+
while (open >= 0 && collected < options.depth) {
`
``
253
`+
var level = 1;
`
``
254
`+
var i = open + 1;
`
``
255
`+
var close = -1;
`
``
256
+
``
257
`+
// balance nested '[' and ']' inside this bracket group using a nesting level counter
`
``
258
`+
while (i < n && close < 0) {
`
``
259
`+
var cu = key.charCodeAt(i);
`
``
260
`+
if (cu === 0x5B) { // '['
`
``
261
`+
level += 1;
`
``
262
`+
} else if (cu === 0x5D) { // ']'
`
``
263
`+
level -= 1;
`
``
264
`+
if (level === 0) {
`
``
265
`+
close = i; // found matching close; loop will exit by condition
`
``
266
`+
}
`
256
267
`}
`
``
268
`+
i += 1;
`
257
269
`}
`
258
270
``
259
``
`-
keys[keys.length] = segment[1];
`
``
271
`+
if (close < 0) {
`
``
272
`+
// Unterminated group: wrap the raw remainder in one bracket pair so it stays
`
``
273
`+
// a single literal segment (e.g. "[[]b" -> "[[]b]"); we do not infer missing ']'.
`
``
274
`+
segments[segments.length] = '[' + key.slice(open) + ']';
`
``
275
`+
return segments;
`
``
276
`+
}
`
``
277
+
``
278
`+
var seg = key.slice(open, close + 1);
`
``
279
`+
// prototype guard for the content of this group
`
``
280
`+
var content = seg.slice(1, -1);
`
``
281
`+
if (!options.plainObjects && has.call(Object.prototype, content) && !options.allowPrototypes) {
`
``
282
`+
return;
`
``
283
`+
}
`
``
284
+
``
285
`+
segments[segments.length] = seg;
`
``
286
`+
collected += 1;
`
``
287
+
``
288
`+
// find the next '[' after this balanced group
`
``
289
`+
open = key.indexOf('[', close + 1);
`
260
290
`}
`
261
291
``
262
``
`-
if (segment) {
`
``
292
`+
if (open >= 0) {
`
263
293
`if (options.strictDepth === true) {
`
264
294
`throw new RangeError('Input depth exceeded depth option of ' + options.depth + ' and strictDepth is true');
`
265
295
`}
`
266
296
``
267
``
`-
keys[keys.length] = '[' + key.slice(segment.index) + ']';
`
``
297
`+
segments[segments.length] = '[' + key.slice(open) + ']';
`
268
298
`}
`
269
299
``
270
``
`-
return keys;
`
``
300
`+
return segments;
`
271
301
`};
`
272
302
``
273
303
`var parseKeys = function parseQueryStringKeys(givenKey, val, options, valuesParsed) {
`