Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[New] add strictDepth option to throw when input is beyond depth #511

Merged
merged 1 commit into from
Aug 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,18 @@ var deep = qs.parse('a[b][c][d][e][f][g][h][i]=j', { depth: 1 });
assert.deepEqual(deep, { a: { b: { '[c][d][e][f][g][h][i]': 'j' } } });
```

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):

```javascript
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
7 changes: 6 additions & 1 deletion lib/parse.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ var defaults = {
parameterLimit: 1000,
parseArrays: true,
plainObjects: false,
strictDepth: false,
strictNullHandling: false
};

Expand Down Expand Up @@ -201,9 +202,12 @@ var parseKeys = function parseQueryStringKeys(givenKey, val, options, valuesPars
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 === true) {
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 @@ -260,6 +264,7 @@ var normalizeParseOptions = function normalizeParseOptions(opts) {
parameterLimit: typeof opts.parameterLimit === 'number' ? opts.parameterLimit : defaults.parameterLimit,
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 : defaults.strictNullHandling
};
};
Expand Down
100 changes: 100 additions & 0 deletions test/parse.js
Original file line number Diff line number Diff line change
Expand Up @@ -1068,3 +1068,103 @@ test('`duplicates` option', function (t) {

t.end();
});

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

t.test('throws an exception for multiple nested arrays with strictDepth: true', function (st) {
st['throws'](
function () {
qs.parse('a[0][1][2][3][4]=b', { depth: 3, strictDepth: true });
},
RangeError,
'Should throw RangeError'
);
st.end();
});

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

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

});

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

t.test('parses successfully when depth is within the limit with strictDepth: true', function (st) {
st.doesNotThrow(
function () {
var result = qs.parse('a[b]=c', { depth: 1, strictDepth: true });
st.deepEqual(result, { a: { b: 'c' } }, 'Should parse correctly');
}
);
st.end();
});

t.test('does not throw an exception when depth exceeds the limit with strictDepth: false', function (st) {
st.doesNotThrow(
function () {
var result = qs.parse('a[b][c][d][e][f][g][h][i]=j', { depth: 1 });
st.deepEqual(result, { a: { b: { '[c][d][e][f][g][h][i]': 'j' } } }, 'Should parse with depth limit');
}
);
st.end();
});

t.test('parses successfully when depth is within the limit with strictDepth: false', function (st) {
st.doesNotThrow(
function () {
var result = qs.parse('a[b]=c', { depth: 1 });
st.deepEqual(result, { a: { b: 'c' } }, 'Should parse correctly');
}
);
st.end();
});

t.test('does not throw when depth is exactly at the limit with strictDepth: true', function (st) {
st.doesNotThrow(
function () {
var result = qs.parse('a[b][c]=d', { depth: 2, strictDepth: true });
st.deepEqual(result, { a: { b: { c: 'd' } } }, 'Should parse correctly');
}
);
st.end();
});
});
Loading