[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) {

`