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

make all limits optional, for #18, for #20 #21

Merged
merged 1 commit into from
Aug 25, 2014
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
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('should 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();
});
});
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();
});
});