Skip to content

Commit

Permalink
Merge pull request #21 from hapijs/optional_limits
Browse files Browse the repository at this point in the history
make all limits optional, for #18, for #20
  • Loading branch information
geek committed Aug 25, 2014
2 parents bd9455f + a5422fc commit d15a581
Show file tree
Hide file tree
Showing 5 changed files with 60 additions and 34 deletions.
28 changes: 21 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ var str = Qs.stringify(obj); // 'a=c'
### Parsing Objects

```javascript
Qs.parse(string, [depth], [delimiter]);
Qs.parse(string, [options]);
```

**qs** allows you to create nested objects within your query strings, by surrounding the name of sub-keys with square brackets `[]`.
Expand Down Expand Up @@ -74,19 +74,26 @@ By default, when nesting objects **qs** will only parse up to 5 children deep. T
}
```

This depth can be overridden by passing a `depth` option to `Qs.parse(string, depth)`:
This depth can be overridden by passing a `depth` option to `Qs.parse(string, [options])`:

```javascript
Qs.parse('a[b][c][d][e][f][g][h][i]=j', 1);
Qs.parse('a[b][c][d][e][f][g][h][i]=j', { depth: 1 });
// { a: { b: { '[c][d][e][f][g][h][i]': 'j' } } }
```

The depth limit mitigate abuse when **qs** is used to parse user input, and it is recommended to keep it a reasonably small number.
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.

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

```javascript
Qs.parse('a=b&c=d', { parameterLimit: 1 });
// { a: 'b' }
```

An optional delimiter can also be passed:

```javascript
Qs.parse('a=b;c=d', ';');
Qs.parse('a=b;c=d', { delimiter: ';' });
// { a: 'b', c: 'd' }
```

Expand Down Expand Up @@ -132,6 +139,13 @@ Qs.parse('a[100]=b');
// { a: { '100': 'b' } }
```

This limit can be overridden by passing an `arrayLimit` option:

```javascript
Qs.parse('a[1]=b', { arrayLimit: 0 });
// { a: { '1': 'b' } }
```

If you mix notations, **qs** will merge the two items into an object:

```javascript
Expand All @@ -149,7 +163,7 @@ Qs.parse('a[][b]=c');
### Stringifying

```javascript
Qs.stringify(object, [delimiter]);
Qs.stringify(object, [options]);
```

When stringifying, **qs** always URI encodes output. Objects are stringified as you would expect:
Expand Down Expand Up @@ -187,6 +201,6 @@ Qs.stringify({ a: null, b: undefined });
The delimiter may be overridden with stringify as well:

```javascript
Qs.stringify({ a: 'b', c: 'd' }, ';');
Qs.stringify({ a: 'b', c: 'd' }, { delimiter: ';' });
// 'a=b;c=d'
```
39 changes: 19 additions & 20 deletions lib/parse.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,14 @@ var internals = {
delimiter: '&',
depth: 5,
arrayLimit: 20,
parametersLimit: 1000
parameterLimit: 1000
};


internals.parseValues = function (str, delimiter) {

delimiter = typeof delimiter === 'string' ? delimiter : internals.delimiter;
internals.parseValues = function (str, options) {

var obj = {};
var parts = str.split(delimiter, internals.parametersLimit);
var parts = str.split(options.delimiter, options.parameterLimit);

for (var i = 0, il = parts.length; i < il; ++i) {
var part = parts[i];
Expand All @@ -44,7 +42,7 @@ internals.parseValues = function (str, delimiter) {
};


internals.parseObject = function (chain, val) {
internals.parseObject = function (chain, val, options) {

if (!chain.length) {
return val;
Expand All @@ -55,28 +53,28 @@ internals.parseObject = function (chain, val) {
var obj = {};
if (root === '[]') {
obj = [];
obj = obj.concat(internals.parseObject(chain, val));
obj = obj.concat(internals.parseObject(chain, val, options));
}
else {
var cleanRoot = root[0] === '[' && root[root.length - 1] === ']' ? root.slice(1, root.length - 1) : root;
var index = parseInt(cleanRoot, 10);
if (!isNaN(index) &&
root !== cleanRoot &&
index <= internals.arrayLimit) {
index <= options.arrayLimit) {

obj = [];
obj[index] = internals.parseObject(chain, val);
obj[index] = internals.parseObject(chain, val, options);
}
else {
obj[cleanRoot] = internals.parseObject(chain, val);
obj[cleanRoot] = internals.parseObject(chain, val, options);
}
}

return obj;
};


internals.parseKeys = function (key, val, depth) {
internals.parseKeys = function (key, val, options) {

if (!key) {
return;
Expand Down Expand Up @@ -107,7 +105,7 @@ internals.parseKeys = function (key, val, depth) {
// Loop through children appending to the array until we hit depth

var i = 0;
while ((segment = child.exec(key)) !== null && i < depth) {
while ((segment = child.exec(key)) !== null && i < options.depth) {

++i;
if (!Object.prototype.hasOwnProperty(segment[1].replace(/\[|\]/g, ''))) {
Expand All @@ -121,11 +119,11 @@ internals.parseKeys = function (key, val, depth) {
keys.push('[' + key.slice(segment.index) + ']');
}

return internals.parseObject(keys, val);
return internals.parseObject(keys, val, options);
};


module.exports = function (str, depth, delimiter) {
module.exports = function (str, options) {

if (str === '' ||
str === null ||
Expand All @@ -134,19 +132,20 @@ module.exports = function (str, depth, delimiter) {
return {};
}

if (typeof depth !== 'number') {
delimiter = depth;
depth = internals.depth;
}
options = options || {};
options.delimiter = typeof options.delimiter === 'string' ? options.delimiter : internals.delimiter;
options.depth = typeof options.depth === 'number' ? options.depth : internals.depth;
options.arrayLimit = typeof options.arrayLimit === 'number' ? options.arrayLimit : internals.arrayLimit;
options.parameterLimit = typeof options.parameterLimit === 'number' ? options.parameterLimit : internals.parameterLimit;

var tempObj = typeof str === 'string' ? internals.parseValues(str, delimiter) : Utils.clone(str);
var tempObj = typeof str === 'string' ? internals.parseValues(str, options) : Utils.clone(str);
var obj = {};

// Iterate over the keys and setup the new object
//
for (var key in tempObj) {
if (tempObj.hasOwnProperty(key)) {
var newObj = internals.parseKeys(key, tempObj[key], depth);
var newObj = internals.parseKeys(key, tempObj[key], options);
obj = Utils.merge(obj, newObj);
}
}
Expand Down
5 changes: 3 additions & 2 deletions lib/stringify.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,10 @@ internals.stringify = function (obj, prefix) {
};


module.exports = function (obj, delimiter) {
module.exports = function (obj, options) {

delimiter = typeof delimiter === 'undefined' ? internals.delimiter : delimiter;
options = options || {};
var delimiter = typeof options.delimiter === 'undefined' ? internals.delimiter : options.delimiter;

var keys = [];

Expand Down
20 changes: 16 additions & 4 deletions test/parse.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,8 @@ describe('#parse', function () {

it('only parses one level when depth = 1', function (done) {

expect(Qs.parse('a[b][c]=d', 1)).to.deep.equal({ a: { b: { '[c]': 'd' } } });
expect(Qs.parse('a[b][c][d]=e', 1)).to.deep.equal({ a: { b: { '[c][d]': 'e' } } });
expect(Qs.parse('a[b][c]=d', { depth: 1 })).to.deep.equal({ a: { b: { '[c]': 'd' } } });
expect(Qs.parse('a[b][c][d]=e', { depth: 1 })).to.deep.equal({ a: { b: { '[c][d]': 'e' } } });
done();
});

Expand Down Expand Up @@ -249,13 +249,25 @@ describe('#parse', function () {

it('parses a string with an alternative delimiter', function (done) {

expect(Qs.parse('a=b;c=d', ';')).to.deep.equal({ a: 'b', c: 'd' });
expect(Qs.parse('a=b;c=d', { delimiter: ';' })).to.deep.equal({ a: 'b', c: 'd' });
done();
});

it('does not use non-string objects as delimiters', function (done) {

expect(Qs.parse('a=b&c=d', {})).to.deep.equal({ a: 'b', c: 'd' });
expect(Qs.parse('a=b&c=d', { delimiter: true })).to.deep.equal({ a: 'b', c: 'd' });
done();
});

it('allows overriding parameter limit', function (done) {

expect(Qs.parse('a=b&c=d', { parameterLimit: 1 })).to.deep.equal({ a: 'b' });
done();
});

it('allows overriding array limit', function (done) {

expect(Qs.parse('a[0]=b&a[1]=c', { arrayLimit: 0 })).to.deep.equal({ a: { '0': 'b', '1': 'c' } });
done();
});

Expand Down
2 changes: 1 addition & 1 deletion test/stringify.js
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ describe('#stringify', function () {

it('stringifies an object using an alternative delimiter', function (done) {

expect(Qs.stringify({ a: 'b', c: 'd' }, ';')).to.equal('a=b;c=d');
expect(Qs.stringify({ a: 'b', c: 'd' }, { delimiter: ';' })).to.equal('a=b;c=d');
done();
});
});

0 comments on commit d15a581

Please sign in to comment.