Skip to content

Commit 8d56df2

Browse files
committed
[New] parse: add strictDepth option
throw tests, readme update
1 parent c9a6694 commit 8d56df2

File tree

3 files changed

+118
-2
lines changed

3 files changed

+118
-2
lines changed

README.md

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,18 @@ var deep = qs.parse('a[b][c][d][e][f][g][h][i]=j', { depth: 1 });
115115
assert.deepEqual(deep, { a: { b: { '[c][d][e][f][g][h][i]': 'j' } } });
116116
```
117117

118-
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.
118+
You can configure **qs** to throw an error when parsing nested input beyond this depth using the `strictDepth` option (defaulted to false):
119+
120+
```javascript
121+
try {
122+
qs.parse('a[b][c][d][e][f][g][h][i]=j', { depth: 1, strictDepth: true });
123+
} catch (err) {
124+
assert(err instanceof RangeError);
125+
assert.strictEqual(err.message, 'Input depth exceeded depth option of 1 and strictDepth is true');
126+
}
127+
```
128+
129+
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.
119130

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

lib/parse.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ var defaults = {
2424
parameterLimit: 1000,
2525
parseArrays: true,
2626
plainObjects: false,
27+
strictDepth: false,
2728
strictNullHandling: false
2829
};
2930

@@ -201,9 +202,12 @@ var parseKeys = function parseQueryStringKeys(givenKey, val, options, valuesPars
201202
keys.push(segment[1]);
202203
}
203204

204-
// If there's a remainder, just add whatever is left
205+
// If there's a remainder, check strictDepth option for throw, else just add whatever is left
205206

206207
if (segment) {
208+
if (options.strictDepth === true) {
209+
throw new RangeError('Input depth exceeded depth option of ' + options.depth + ' and strictDepth is true');
210+
}
207211
keys.push('[' + key.slice(segment.index) + ']');
208212
}
209213

@@ -260,6 +264,7 @@ var normalizeParseOptions = function normalizeParseOptions(opts) {
260264
parameterLimit: typeof opts.parameterLimit === 'number' ? opts.parameterLimit : defaults.parameterLimit,
261265
parseArrays: opts.parseArrays !== false,
262266
plainObjects: typeof opts.plainObjects === 'boolean' ? opts.plainObjects : defaults.plainObjects,
267+
strictDepth: typeof opts.strictDepth === 'boolean' ? !!opts.strictDepth : defaults.strictDepth,
263268
strictNullHandling: typeof opts.strictNullHandling === 'boolean' ? opts.strictNullHandling : defaults.strictNullHandling
264269
};
265270
};

test/parse.js

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1068,3 +1068,103 @@ test('`duplicates` option', function (t) {
10681068

10691069
t.end();
10701070
});
1071+
1072+
test('qs strictDepth option - throw cases', function (t) {
1073+
t.test('throws an exception when depth exceeds the limit with strictDepth: true', function (st) {
1074+
st['throws'](
1075+
function () {
1076+
qs.parse('a[b][c][d][e][f][g][h][i]=j', { depth: 1, strictDepth: true });
1077+
},
1078+
RangeError,
1079+
'Should throw RangeError'
1080+
);
1081+
st.end();
1082+
});
1083+
1084+
t.test('throws an exception for multiple nested arrays with strictDepth: true', function (st) {
1085+
st['throws'](
1086+
function () {
1087+
qs.parse('a[0][1][2][3][4]=b', { depth: 3, strictDepth: true });
1088+
},
1089+
RangeError,
1090+
'Should throw RangeError'
1091+
);
1092+
st.end();
1093+
});
1094+
1095+
t.test('throws an exception for nested objects and arrays with strictDepth: true', function (st) {
1096+
st['throws'](
1097+
function () {
1098+
qs.parse('a[b][c][0][d][e]=f', { depth: 3, strictDepth: true });
1099+
},
1100+
RangeError,
1101+
'Should throw RangeError'
1102+
);
1103+
st.end();
1104+
});
1105+
1106+
t.test('throws an exception for different types of values with strictDepth: true', function (st) {
1107+
st['throws'](
1108+
function () {
1109+
qs.parse('a[b][c][d][e]=true&a[b][c][d][f]=42', { depth: 3, strictDepth: true });
1110+
},
1111+
RangeError,
1112+
'Should throw RangeError'
1113+
);
1114+
st.end();
1115+
});
1116+
1117+
});
1118+
1119+
test('qs strictDepth option - non-throw cases', function (t) {
1120+
t.test('when depth is 0 and strictDepth true, do not throw', function (st) {
1121+
st.doesNotThrow(
1122+
function () {
1123+
qs.parse('a[b][c][d][e]=true&a[b][c][d][f]=42', { depth: 0, strictDepth: true });
1124+
},
1125+
RangeError,
1126+
'Should not throw RangeError'
1127+
);
1128+
st.end();
1129+
});
1130+
1131+
t.test('parses successfully when depth is within the limit with strictDepth: true', function (st) {
1132+
st.doesNotThrow(
1133+
function () {
1134+
var result = qs.parse('a[b]=c', { depth: 1, strictDepth: true });
1135+
st.deepEqual(result, { a: { b: 'c' } }, 'Should parse correctly');
1136+
}
1137+
);
1138+
st.end();
1139+
});
1140+
1141+
t.test('does not throw an exception when depth exceeds the limit with strictDepth: false', function (st) {
1142+
st.doesNotThrow(
1143+
function () {
1144+
var result = qs.parse('a[b][c][d][e][f][g][h][i]=j', { depth: 1 });
1145+
st.deepEqual(result, { a: { b: { '[c][d][e][f][g][h][i]': 'j' } } }, 'Should parse with depth limit');
1146+
}
1147+
);
1148+
st.end();
1149+
});
1150+
1151+
t.test('parses successfully when depth is within the limit with strictDepth: false', function (st) {
1152+
st.doesNotThrow(
1153+
function () {
1154+
var result = qs.parse('a[b]=c', { depth: 1 });
1155+
st.deepEqual(result, { a: { b: 'c' } }, 'Should parse correctly');
1156+
}
1157+
);
1158+
st.end();
1159+
});
1160+
1161+
t.test('does not throw when depth is exactly at the limit with strictDepth: true', function (st) {
1162+
st.doesNotThrow(
1163+
function () {
1164+
var result = qs.parse('a[b][c]=d', { depth: 2, strictDepth: true });
1165+
st.deepEqual(result, { a: { b: { c: 'd' } } }, 'Should parse correctly');
1166+
}
1167+
);
1168+
st.end();
1169+
});
1170+
});

0 commit comments

Comments
 (0)