Skip to content

Commit

Permalink
Push
Browse files Browse the repository at this point in the history
  • Loading branch information
PuruVJ committed Aug 14, 2024
1 parent 06ef44f commit baca908
Show file tree
Hide file tree
Showing 4 changed files with 82 additions and 2 deletions.
13 changes: 12 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,18 @@ console.log(deep);
// }
```

The depth limit helps mitigate abuse when **qs** is used to parse user input, and it is recommended to keep it a reasonably small number.
You can configure **qs** to throw an error when parsing nested input beyond this depth using the `strictDepth` option (defaulted to false):

```js
try {
qs.parse('a[b][c][d][e][f][g][h][i]=j', { depth: 1, strictDepth: true });
} catch (err) {
assert(err instanceof RangeError);
assert.strictEqual(err.message, 'Input depth exceeded depth option of 1 and strictDepth is true');
}
```

The depth limit helps mitigate abuse when **qs** is used to parse user input, and it is recommended to keep it a reasonably small number. The strictDepth option adds a layer of protection by throwing an error when the limit is exceeded, allowing you to catch and handle such cases.

For similar reasons, by default **qs** will only parse up to 1000 parameters. This can be overridden by passing a `parameterLimit` option:

Expand Down
9 changes: 8 additions & 1 deletion src/parse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ const defaults = {
parameterLimit: 1000,
parseArrays: true,
plainObjects: false,
strictDepth: false,
strictNullHandling: false,
} as NonNullableProperties<ParseOptions>;

Expand Down Expand Up @@ -219,9 +220,14 @@ function parseKeys(
keys.push(segment[1]);
}

// If there's a remainder, just add whatever is left
// If there's a remainder, check strictDepth option for throw, else just add whatever is left

if (segment) {
if (options.strictDepth) {
throw new RangeError(
'Input depth exceeded depth option of ' + options.depth + ' and strictDepth is true',
);
}
keys.push('[' + key.slice(segment.index) + ']');
}

Expand Down Expand Up @@ -307,6 +313,7 @@ function normalize_parse_options(
parseArrays: opts.parseArrays !== false,
plainObjects:
typeof opts.plainObjects === 'boolean' ? opts.plainObjects : defaults.plainObjects,
strictDepth: typeof opts.strictDepth === 'boolean' ? !!opts.strictDepth : defaults.strictDepth,
strictNullHandling:
typeof opts.strictNullHandling === 'boolean'
? opts.strictNullHandling
Expand Down
1 change: 1 addition & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ export type ParseBaseOptions = {
allowPrototypes?: boolean;
allowSparse?: boolean;
parameterLimit?: number;
strictDepth?: boolean;
strictNullHandling?: boolean;
ignoreQueryPrefix?: boolean;
charset?: 'utf-8' | 'iso-8859-1';
Expand Down
61 changes: 61 additions & 0 deletions test/parse.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1267,3 +1267,64 @@ test('`duplicates` option', function (t) {
// );
expect(parse('foo=bar&foo=baz', { duplicates: 'last' })).toEqual({ foo: 'baz' });
});

describe('qs strictDepth option - throw cases', function (t) {
test('throws an exception when depth exceeds the limit with strictDepth: true', function () {
expect(() => {
parse('a[b][c][d][e][f][g][h][i]=j', { depth: 1, strictDepth: true });
}).toThrowError(RangeError);
});

test('throws an exception for multiple nested arrays with strictDepth: true', function () {
expect(() => {
parse('a[0][1][2][3][4]=b', { depth: 3, strictDepth: true });
}).toThrowError(RangeError);
});

test('throws an exception for nested objects and arrays with strictDepth: true', function (st) {
expect(() => {
parse('a[b][c][0][d][e]=f', { depth: 3, strictDepth: true });
}).toThrowError(RangeError);
});

test('throws an exception for different types of values with strictDepth: true', function (st) {
expect(() => {
parse('a[b][c][d][e]=true&a[b][c][d][f]=42', { depth: 3, strictDepth: true });
}).toThrowError(RangeError);
});
});

describe('qs strictDepth option - non-throw cases', function (t) {
test('when depth is 0 and strictDepth true, do not throw', function (st) {
expect(() => {
parse('a[b][c][d][e]=true&a[b][c][d][f]=42', { depth: 0, strictDepth: true });
}).not.toThrow();
});

test('parses successfully when depth is within the limit with strictDepth: true', function (st) {
expect(() => {
parse('a[b]=c', { depth: 1, strictDepth: true });
}).not.toThrow();
});

test('does not throw an exception when depth exceeds the limit with strictDepth: false', function (st) {
expect(() => {
const result = parse('a[b][c][d][e][f][g][h][i]=j', { depth: 1 });
expect(result).toEqual({ a: { b: { '[c][d][e][f][g][h][i]': 'j' } } });
}).not.toThrow();
});

test('parses successfully when depth is within the limit with strictDepth: false', function (st) {
expect(() => {
const result = parse('a[b]=c', { depth: 1 });
expect(result).toEqual({ a: { b: 'c' } });
}).not.toThrow();
});

test('does not throw when depth is exactly at the limit with strictDepth: true', function (st) {
expect(() => {
const result = parse('a[b][c]=d', { depth: 2, strictDepth: true });
expect(result).toEqual({ a: { b: { c: 'd' } } });
}).not.toThrow();
});
});

0 comments on commit baca908

Please sign in to comment.