diff --git a/LICENSE b/LICENSE old mode 100644 new mode 100755 index 0f350c24..d4569487 --- a/LICENSE +++ b/LICENSE @@ -1,5 +1,4 @@ -Copyright (c) 2014 Nathan LaFreniere and other contributors - +Copyright (c) 2014 Nathan LaFreniere and other contributors. All rights reserved. Redistribution and use in source and binary forms, with or without @@ -9,9 +8,9 @@ modification, are permitted provided that the following conditions are met: * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - * Neither the name of Walmart nor the names of any contributors may be - used to endorse or promote products derived from this software without - specific prior written permission. + * The names of any contributors may not be used to endorse or promote + products derived from this software without specific prior written + permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED @@ -26,4 +25,4 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * * -The complete list of contributors can be found at: https://github.com/hapijs/riddler/graphs/contributors +The complete list of contributors can be found at: https://github.com/hapijs/qs/graphs/contributors diff --git a/README.md b/README.md old mode 100644 new mode 100755 index 99008ffd..f86ffc9b --- a/README.md +++ b/README.md @@ -1,29 +1,26 @@ - +# qs -Riddler is a querystring parsing and stringifying library with some added security +A querystring parsing and stringifying library with some added security. -[![Build Status](https://secure.travis-ci.org/hapijs/riddler.svg)](http://travis-ci.org/hapijs/riddler) +[![Build Status](https://secure.travis-ci.org/hapijs/qs.svg)](http://travis-ci.org/hapijs/qs) Lead Maintainer: [Nathan LaFreniere](https://github.com/nlf) +The **qs** module was original created and maintained by [TJ Holowaychuk](https://github.com/visionmedia/node-querystring). ## Usage ```javascript -var Riddler = require('riddler'); +var Qs = require('qs'); -var obj = Riddler.parse('a=c'); -console.log(obj); // { a: 'c' } - -var str = Riddler.stringify(obj); -console.log(str); // 'a=c' +var obj = Qs.parse('a=c'); // { a: 'c' } +var str = Qs.stringify(obj); // 'a=c' ``` ### Objects -Riddler allows you to create nested objects within your query strings, by surrounding the name of sub-keys with square brackets `[]`. - -For example, in order to create an object: +**qs** allows you to create nested objects within your query strings, by surrounding the name of sub-keys with square brackets `[]`. +For example, the string `'foo[bar]=baz'` converts to: ```javascript { @@ -33,16 +30,20 @@ For example, in order to create an object: } ``` -One would use the string `foo[bar]=baz`. - -You can also nest your objects: +You can also nest your objects, like `'foo[bar][baz]=foobarbaz'`: ```javascript -var obj = Riddler.parse('foo[bar][baz]=foobarbaz'); -// obj = { foo: { bar: { baz: 'foobarbaz' } } } +{ + foo: { + bar: { + baz: 'foobarbaz' + } + } +} ``` -By default, when nesting objects riddler will only parse up to 5 children deep. This means if you attempt to parse a string like `a[b][c][d][e][f][g][h][i]=j` your resulting object will be: +By default, when nesting objects **qs** will only parse up to 5 children deep. This means if you attempt to parse a string like +`'a[b][c][d][e][f][g][h][i]=j'` your resulting object will be: ```javascript { @@ -62,57 +63,58 @@ By default, when nesting objects riddler will only parse up to 5 children deep. } ``` -This depth can be overridden by passing a `depth` parameter to `riddler.parse()`: +This depth can be overridden by passing a `depth` option to `Qs.parse(string, depth)`: ```javascript -Riddler.parse('a[b][c][d][e][f][g][h][i]=j', 1); +Qs.parse('a[b][c][d][e][f][g][h][i]=j', 1); // { a: { b: { '[c][d][e][f][g][h][i]': 'j' } } } ``` -Having this limit helps mitigate abuse when riddler is used to parse user input, and it is recommended to keep it a reasonably small number. +The depth limit mitigate abuse when **qs** is used to parse user input, and it is recommended to keep it a reasonably small number. ### Arrays -Riddler can also parse arrays using a similar `[]` notation: +**qs** can also parse arrays using a similar `[]` notation: ```javascript -Riddler.parse('a[]=b&a[]=c'); +Qs.parse('a[]=b&a[]=c'); // { a: ['b', 'c'] } ``` You may specify an index as well: ```javascript -Riddler.parse('a[1]=c&a[0]=b'); +Qs.parse('a[1]=c&a[0]=b'); // { a: ['b', 'c'] } ``` -Note that the only difference between an index in an array and a key in an object is that the value between the brackets must be a number to create an array. - -When creating arrays with specific indices, riddler will compact a sparse array to only the existing values preserving their order: +Note that the only difference between an index in an array and a key in an object is that the value between the brackets must be a number +to create an array. When creating arrays with specific indices, **qs** will compact a sparse array to only the existing values preserving +their order: ```javascript -Riddler.parse('a[1]=b&a[15]=c'); +Qs.parse('a[1]=b&a[15]=c'); // { a: ['b', 'c'] } ``` -Riddler will also limit specifying indices in an array to a maximum index of `20`. Any array members with an index of greater than `20` will instead be converted to an object with the index as the key: +**qs** will also limit specifying indices in an array to a maximum index of `20`. Any array members with an index of greater than `20` will +instead be converted to an object with the index as the key: ```javascript -Riddler.parse('a[100]=b'); +Qs.parse('a[100]=b'); // { a: { '100': 'b' } } ``` -If you mix notations, riddler will merge the two items into an object: +If you mix notations, **qs** will merge the two items into an object: ```javascript -Riddler.parse('a[0]=b&a[b]=c'); +Qs.parse('a[0]=b&a[b]=c'); // { a: { '0': 'b', b: 'c' } } ``` You can also create arrays of objects: ```javascript -Riddler.parse('a[][b]=c'); +Qs.parse('a[][b]=c'); // { a: [{ b: 'c' }] } -``` +``` \ No newline at end of file diff --git a/coverage.html b/coverage.html new file mode 100644 index 00000000..321c2113 --- /dev/null +++ b/coverage.html @@ -0,0 +1,3158 @@ + + + + Coverage + + + + +
+

Code Coverage Report

+ +
+
99.15%
+
235
+
233
+
2
+
+
+ +
+

index.js

+
+
100%
+
1
+
1
+
0
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
LineHitsSource
11module.exports = require('./lib');
2
+
+ +
+

lib/index.js

+
+
100%
+
9
+
9
+
0
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
LineHitsSource
1// Load modules
2
31var Stringify = require('./stringify');
41var Parse = require('./parse');
5
6
7// Declare internals
8
91var internals = {};
10
11
121module.exports = {
13 stringify: Stringify,
14 parse: Parse
15};
16
+
+ +
+

lib/parse.js

+
+
100%
+
105
+
105
+
0
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
LineHitsSource
1// Load modules
2
31var Utils = require('./utils');
4
5
6// Declare internals
7
81var internals = {
9 depth: 5,
10 arrayLimit: 20,
11 parametersLimit: 1000
12};
13
14
151internals.parseValues = function (str) {
16
1751 var obj = {};
1851 var parts = str.split('&').slice(0, internals.parametersLimit);
19
2051 for (var i = 0, il = parts.length; i < il; ++i) {
211074 var part = parts[i];
221074 var pos = part.indexOf(']=') === -1 ? part.indexOf('=') : part.indexOf(']=') + 1;
23
241074 if (pos === -1) {
256 obj[Utils.decode(part)] = '';
26 }
27 else {
281068 var key = Utils.decode(part.slice(0, pos));
291068 var val = Utils.decode(part.slice(pos + 1));
30
311068 if (!obj[key]) {
3261 obj[key] = val;
33 }
34 else {
351007 obj[key] = [].concat(obj[key]).concat(val);
36 }
37 }
38 }
39
4051 return obj;
41};
42
43
441internals.parseObject = function (chain, val) {
45
46192 if (!chain.length) {
4769 return val;
48 }
49
50123 var root = chain.shift();
51
52123 var obj = {};
53123 if (root === '[]') {
5410 obj = [];
5510 obj = obj.concat(internals.parseObject(chain, val));
56 }
57 else {
58113 var cleanRoot = root[0] === '[' && root[root.length - 1] === ']' ? root.slice(1, root.length - 1) : root;
59113 var index = parseInt(cleanRoot, 10);
60113 if (!isNaN(index) &&
61 root !== cleanRoot &&
62 index <= internals.arrayLimit) {
63
6412 obj = [];
6512 obj[index] = internals.parseObject(chain, val);
66 }
67 else {
68101 obj[cleanRoot] = internals.parseObject(chain, val);
69 }
70 }
71
72123 return obj;
73};
74
75
761internals.parseKeys = function (key, val, depth) {
77
7873 if (!key) {
791 return;
80 }
81
8272 depth = typeof depth === 'undefined' ? internals.depth : depth;
83
84 // The regex chunks
85
8672 var parent = /^([^\[\]]*)/;
8772 var child = /(\[[^\[\]]*\])/g;
88
89 // Get the parent
90
9172 var segment = parent.exec(key);
92
93 // Don't allow them to overwrite object prototype properties
94
9572 if (Object.prototype.hasOwnProperty(segment[1])) {
963 return;
97 }
98
99 // Stash the parent if it exists
100
10169 var keys = [];
10269 if (segment[1]) {
10367 keys.push(segment[1]);
104 }
105
106 // Loop through children appending to the array until we hit depth
107
10869 var i = 0;
10969 while ((segment = child.exec(key)) !== null && i < depth) {
110
11155 ++i;
11255 if (!Object.prototype.hasOwnProperty(segment[1].replace(/\[|\]/g, ''))) {
11353 keys.push(segment[1]);
114 }
115 }
116
117 // If there's a remainder, just add whatever is left
118
11969 if (segment) {
1203 keys.push('[' + key.slice(segment.index) + ']');
121 }
122
12369 return internals.parseObject(keys, val);
124};
125
126
1271module.exports = function (str, depth) {
128
12957 if (str === '' ||
130 str === null ||
131 typeof str === 'undefined') {
132
1333 return {};
134 }
135
13654 var tempObj = typeof str === 'string' ? internals.parseValues(str) : Utils.clone(str);
13754 var obj = {};
138
139 // Iterate over the keys and setup the new object
140
14154 for (var key in tempObj) {
14274 if (tempObj.hasOwnProperty(key)) {
14373 var newObj = internals.parseKeys(key, tempObj[key], depth);
14473 obj = Utils.merge(obj, newObj);
145 }
146 }
147
14854 return Utils.compact(obj);
149};
150
151
152
+
+ +
+

lib/stringify.js

+
+
93.10%
+
29
+
27
+
2
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
LineHitsSource
1// Load modules
2
3
4// Declare internals
5
61var internals = {};
7
8
91internals.stringify = function (obj, prefix) {
10
1140 if (typeof obj === 'string' ||
12 typeof obj === 'number') {
13
1421 return [prefix + '=' + encodeURIComponent(obj)];
15 }
16
1719 if (obj === null) {
183 return [prefix];
19 }
20
2116 var values = [];
22
2316 for (var key in obj) {
24
if (
obj.hasOwnProperty(key)
) {
2520 values = values.concat(internals.stringify(obj[key], prefix + '[' + encodeURIComponent(key) + ']'));
26 }
27 }
28
2916 return values;
30};
31
32
331module.exports = function (obj) {
34
3519 var keys = [];
3619 var value = JSON.parse(JSON.stringify(obj));
37
3819 for (var key in value) {
39
if (
value.hasOwnProperty(key)
) {
4020 keys = keys.concat(internals.stringify(value[key], encodeURIComponent(key)));
41 }
42 }
43
4419 return keys.join('&');
45};
46
+
+ +
+

lib/utils.js

+
+
100%
+
91
+
91
+
0
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
LineHitsSource
1// Load modules
2
3
4// Declare internals
5
61var internals = {};
7
8
91exports.arrayToObject = function (source) {
10
112 var obj = {};
122 for (var i = 0, il = source.length; i < il; ++i) {
132 obj[i] = source[i];
14 }
15
162 return obj;
17};
18
19
201exports.clone = function (source) {
21
221209 if (typeof source !== 'object' ||
23 source === null) {
24
251071 return source;
26 }
27
28138 if (Buffer.isBuffer(source)) {
291 return source.toString();
30 }
31
32137 var obj = Array.isArray(source) ? [] : {};
33137 for (var i in source) {
341095 if (source.hasOwnProperty(i)) {
351094 obj[i] = exports.clone(source[i]);
36 }
37 }
38
39137 return obj;
40};
41
42
431exports.merge = function (target, source) {
44
4583 if (!source) {
464 return target;
47 }
48
4979 var obj = exports.clone(target);
50
5179 if (Array.isArray(source)) {
528 for (var i = 0, il = source.length; i < il; ++i) {
5313 if (source[i] !== undefined) {
549 obj[i] = source[i];
55 }
56 }
57
588 return obj;
59 }
60
6171 if (Array.isArray(obj)) {
622 obj = exports.arrayToObject(obj);
63 }
64
6571 var keys = Object.keys(source);
6671 for (var k = 0, kl = keys.length; k < kl; ++k) {
6771 var key = keys[k];
6871 var value = source[key];
69
7071 if (value &&
71 typeof value === 'object') {
72
7343 if (!obj[key]) {
7433 obj[key] = exports.clone(value);
75 }
76 else {
7710 obj[key] = exports.merge(obj[key], value);
78 }
79 }
80 else {
8128 obj[key] = value;
82 }
83 }
84
8571 return obj;
86};
87
88
891exports.decode = function (str) {
90
912142 try {
922142 return decodeURIComponent(str.replace(/\+/g, ' '));
93 } catch (e) {
942 return str;
95 }
96};
97
98
991exports.compact = function (obj) {
100
101137 if (typeof obj !== 'object') {
10253 return obj;
103 }
104
10584 var compacted = {};
106
10784 for (var key in obj) {
10897 if (obj.hasOwnProperty(key)) {
10996 if (Array.isArray(obj[key])) {
11013 compacted[key] = [];
111
11213 for (var i = 0, l = obj[key].length; i < l; i++) {
1131051 if (obj[key].hasOwnProperty(i) &&
114 obj[key][i]) {
115
1161021 compacted[key].push(obj[key][i]);
117 }
118 }
119 }
120 else {
12183 compacted[key] = exports.compact(obj[key]);
122 }
123 }
124 }
125
12684 return compacted;
127};
128
+
+ +
+
+ + \ No newline at end of file diff --git a/lib/index.js b/lib/index.js old mode 100644 new mode 100755 index 157d3833..0e094933 --- a/lib/index.js +++ b/lib/index.js @@ -1,6 +1,14 @@ +// Load modules + var Stringify = require('./stringify'); var Parse = require('./parse'); + +// Declare internals + +var internals = {}; + + module.exports = { stringify: Stringify, parse: Parse diff --git a/lib/parse.js b/lib/parse.js old mode 100644 new mode 100755 index 0bb6b094..fdd2a01b --- a/lib/parse.js +++ b/lib/parse.js @@ -1,10 +1,21 @@ +// Load modules + var Utils = require('./utils'); -var internals = {}; + + +// Declare internals + +var internals = { + depth: 5, + arrayLimit: 20, + parametersLimit: 1000 +}; + internals.parseValues = function (str) { var obj = {}; - var parts = str.split('&').slice(0, 1000); + var parts = str.split('&').slice(0, internals.parametersLimit); for (var i = 0, il = parts.length; i < il; ++i) { var part = parts[i]; @@ -29,6 +40,7 @@ internals.parseValues = function (str) { return obj; }; + internals.parseObject = function (chain, val) { if (!chain.length) { @@ -36,65 +48,74 @@ internals.parseObject = function (chain, val) { } var root = chain.shift(); - var obj = {}; - - var cleanRoot = root[0] === '[' && root[root.length - 1] === ']' ? root.slice(1, root.length - 1) : root; - - var index = parseInt(cleanRoot, 10); + var obj = {}; if (root === '[]') { obj = []; obj = obj.concat(internals.parseObject(chain, val)); } - else if (!isNaN(index) && root !== cleanRoot && index <= 20) { - obj = []; - obj[index] = internals.parseObject(chain, val); - } else { - obj[cleanRoot] = internals.parseObject(chain, val); + 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) { + + obj = []; + obj[index] = internals.parseObject(chain, val); + } + else { + obj[cleanRoot] = internals.parseObject(chain, val); + } } return obj; }; + internals.parseKeys = function (key, val, depth) { if (!key) { return; } - depth = typeof depth === 'undefined' ? 5 : depth; // default to 5 + depth = typeof depth === 'undefined' ? internals.depth : depth; - var keys = []; + // The regex chunks - // the regex chunks var parent = /^([^\[\]]*)/; var child = /(\[[^\[\]]*\])/g; - // get the parent + // Get the parent + var segment = parent.exec(key); - // don't allow them to overwrite object prototype properties + // Don't allow them to overwrite object prototype properties + if (Object.prototype.hasOwnProperty(segment[1])) { return; } - // stash the parent if it exists + // Stash the parent if it exists + + var keys = []; if (segment[1]) { keys.push(segment[1]); } - // loop through children appending to the array until we hit depth + // Loop through children appending to the array until we hit depth + var i = 0; while ((segment = child.exec(key)) !== null && i < depth) { - i += 1; + ++i; if (!Object.prototype.hasOwnProperty(segment[1].replace(/\[|\]/g, ''))) { keys.push(segment[1]); } } - // if there's a remainder, just add whatever's left + // If there's a remainder, just add whatever is left + if (segment) { keys.push('[' + key.slice(segment.index) + ']'); } @@ -102,18 +123,21 @@ internals.parseKeys = function (key, val, depth) { return internals.parseObject(keys, val); }; + module.exports = function (str, depth) { - // use node's native querystring module to do the initial parse - // this takes care of things like url decoding, as well as the splitting - if (str === '' || str === null || typeof str === 'undefined') { + if (str === '' || + str === null || + typeof str === 'undefined') { + return {}; } var tempObj = typeof str === 'string' ? internals.parseValues(str) : Utils.clone(str); var obj = {}; - // iterate over the keys and setup the new object + // 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); diff --git a/lib/stringify.js b/lib/stringify.js old mode 100644 new mode 100755 index 96ffdf35..7b66a3b8 --- a/lib/stringify.js +++ b/lib/stringify.js @@ -1,8 +1,16 @@ +// Load modules + + +// Declare internals + var internals = {}; + internals.stringify = function (obj, prefix) { - if (typeof obj === 'string' || typeof obj === 'number') { + if (typeof obj === 'string' || + typeof obj === 'number') { + return [prefix + '=' + encodeURIComponent(obj)]; } @@ -21,6 +29,7 @@ internals.stringify = function (obj, prefix) { return values; }; + module.exports = function (obj) { var keys = []; diff --git a/lib/utils.js b/lib/utils.js old mode 100644 new mode 100755 index d9290d6a..e8329ba9 --- a/lib/utils.js +++ b/lib/utils.js @@ -1,3 +1,11 @@ +// Load modules + + +// Declare internals + +var internals = {}; + + exports.arrayToObject = function (source) { var obj = {}; @@ -8,9 +16,12 @@ exports.arrayToObject = function (source) { return obj; }; + exports.clone = function (source) { - if (typeof source !== 'object' || source === null) { + if (typeof source !== 'object' || + source === null) { + return source; } @@ -28,6 +39,7 @@ exports.clone = function (source) { return obj; }; + exports.merge = function (target, source) { if (!source) { @@ -55,7 +67,8 @@ exports.merge = function (target, source) { var key = keys[k]; var value = source[key]; - if (value && typeof value === 'object') { + if (value && + typeof value === 'object') { if (!obj[key]) { obj[key] = exports.clone(value); @@ -72,6 +85,7 @@ exports.merge = function (target, source) { return obj; }; + exports.decode = function (str) { try { @@ -81,6 +95,7 @@ exports.decode = function (str) { } }; + exports.compact = function (obj) { if (typeof obj !== 'object') { @@ -95,7 +110,9 @@ exports.compact = function (obj) { compacted[key] = []; for (var i = 0, l = obj[key].length; i < l; i++) { - if (obj[key].hasOwnProperty(i) && obj[key][i]) { + if (obj[key].hasOwnProperty(i) && + obj[key][i]) { + compacted[key].push(obj[key][i]); } } diff --git a/package.json b/package.json old mode 100644 new mode 100755 index c4510f87..71d19224 --- a/package.json +++ b/package.json @@ -1,21 +1,20 @@ { - "name": "riddler", - "version": "1.0.5", - "description": "a querystring parser that supports nesting and arrays, with a depth limit", + "name": "qs", + "version": "1.0.0", + "description": "A querystring parser that supports nesting and arrays, with a depth limit", + "homepage": "https://github.com/hapijs/qs", "main": "index.js", - "directories": { - "test": "test" + "dependencies": { }, - "dependencies": {}, "devDependencies": { - "lab": "^3.2.1" + "lab": "3.x.x" }, "scripts": { "test": "make test-cov" }, "repository": { "type": "git", - "url": "https://github.com/hapijs/riddler.git" + "url": "https://github.com/hapijs/qs.git" }, "keywords": [ "querystring", @@ -25,11 +24,7 @@ "licenses": [ { "type": "BSD", - "url": "http://github.com/hapijs/riddler/raw/master/LICENSE" + "url": "http://github.com/hapijs/qs/raw/master/LICENSE" } - ], - "bugs": { - "url": "https://github.com/hapijs/riddler/issues" - }, - "homepage": "https://github.com/hapijs/riddler" + ] } diff --git a/test/parse.js b/test/parse.js old mode 100644 new mode 100755 index d002b928..6df1a998 --- a/test/parse.js +++ b/test/parse.js @@ -1,26 +1,39 @@ -var Riddler = require('../'); +// Load modules + var Lab = require('lab'); +var Qs = require('../'); + + +// Declare internals + +var internals = {}; + + +// Test shortcuts -var describe = Lab.experiment; var expect = Lab.expect; +var before = Lab.before; +var after = Lab.after; +var describe = Lab.experiment; var it = Lab.test; -describe('Riddler.parse()', function () { + +describe('#parse', function () { it('parses a simple string', function (done) { - expect(Riddler.parse('0=foo')).to.deep.equal({ '0': 'foo' }); - expect(Riddler.parse('foo=c++')).to.deep.equal({ foo: 'c ' }); - expect(Riddler.parse('a[>=]=23')).to.deep.equal({ a: { '>=': '23' } }); - expect(Riddler.parse('a[<=>]==23')).to.deep.equal({ a: { '<=>': '=23' } }); - expect(Riddler.parse('a[==]=23')).to.deep.equal({ a: { '==': '23' } }); - expect(Riddler.parse('foo')).to.deep.equal({ foo: '' }); - expect(Riddler.parse('foo=bar')).to.deep.equal({ foo: 'bar' }); - expect(Riddler.parse(' foo = bar = baz ')).to.deep.equal({ ' foo ': ' bar = baz ' }); - expect(Riddler.parse('foo=bar=baz')).to.deep.equal({ foo: 'bar=baz' }); - expect(Riddler.parse('foo=bar&bar=baz')).to.deep.equal({ foo: 'bar', bar: 'baz' }); - expect(Riddler.parse('foo=bar&baz')).to.deep.equal({ foo: 'bar', baz: '' }); - expect(Riddler.parse('cht=p3&chd=t:60,40&chs=250x100&chl=Hello|World')).to.deep.equal({ + expect(Qs.parse('0=foo')).to.deep.equal({ '0': 'foo' }); + expect(Qs.parse('foo=c++')).to.deep.equal({ foo: 'c ' }); + expect(Qs.parse('a[>=]=23')).to.deep.equal({ a: { '>=': '23' } }); + expect(Qs.parse('a[<=>]==23')).to.deep.equal({ a: { '<=>': '=23' } }); + expect(Qs.parse('a[==]=23')).to.deep.equal({ a: { '==': '23' } }); + expect(Qs.parse('foo')).to.deep.equal({ foo: '' }); + expect(Qs.parse('foo=bar')).to.deep.equal({ foo: 'bar' }); + expect(Qs.parse(' foo = bar = baz ')).to.deep.equal({ ' foo ': ' bar = baz ' }); + expect(Qs.parse('foo=bar=baz')).to.deep.equal({ foo: 'bar=baz' }); + expect(Qs.parse('foo=bar&bar=baz')).to.deep.equal({ foo: 'bar', bar: 'baz' }); + expect(Qs.parse('foo=bar&baz')).to.deep.equal({ foo: 'bar', baz: '' }); + expect(Qs.parse('cht=p3&chd=t:60,40&chs=250x100&chl=Hello|World')).to.deep.equal({ cht: 'p3', chd: 't:60,40', chs: '250x100', @@ -31,119 +44,119 @@ describe('Riddler.parse()', function () { it('parses a single nested string', function (done) { - expect(Riddler.parse('a[b]=c')).to.deep.equal({ a: { b: 'c' } }); + expect(Qs.parse('a[b]=c')).to.deep.equal({ a: { b: 'c' } }); done(); }); it('parses a double nested string', function (done) { - expect(Riddler.parse('a[b][c]=d')).to.deep.equal({ a: { b: { c: 'd' } } }); + expect(Qs.parse('a[b][c]=d')).to.deep.equal({ a: { b: { c: 'd' } } }); done(); }); it('defaults to a depth of 5', function (done) { - expect(Riddler.parse('a[b][c][d][e][f][g][h]=i')).to.deep.equal({ a: { b: { c: { d: { e: { f: { '[g][h]': 'i' } } } } } } }); + expect(Qs.parse('a[b][c][d][e][f][g][h]=i')).to.deep.equal({ a: { b: { c: { d: { e: { f: { '[g][h]': 'i' } } } } } } }); done(); }); it('only parses one level when depth = 1', function (done) { - expect(Riddler.parse('a[b][c]=d', 1)).to.deep.equal({ a: { b: { '[c]': 'd' } } }); - expect(Riddler.parse('a[b][c][d]=e', 1)).to.deep.equal({ a: { b: { '[c][d]': 'e' } } }); + 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' } } }); done(); }); it('parses a simple array', function (done) { - expect(Riddler.parse('a=b&a=c')).to.deep.equal({ a: ['b', 'c'] }); + expect(Qs.parse('a=b&a=c')).to.deep.equal({ a: ['b', 'c'] }); done(); }); it('parses an explicit array', function (done) { - expect(Riddler.parse('a[]=b')).to.deep.equal({ a: ['b'] }); - expect(Riddler.parse('a[]=b&a[]=c')).to.deep.equal({ a: ['b', 'c'] }); - expect(Riddler.parse('a[]=b&a[]=c&a[]=d')).to.deep.equal({ a: ['b', 'c', 'd'] }); + expect(Qs.parse('a[]=b')).to.deep.equal({ a: ['b'] }); + expect(Qs.parse('a[]=b&a[]=c')).to.deep.equal({ a: ['b', 'c'] }); + expect(Qs.parse('a[]=b&a[]=c&a[]=d')).to.deep.equal({ a: ['b', 'c', 'd'] }); done(); }); it('parses a nested array', function (done) { - expect(Riddler.parse('a[b][]=c&a[b][]=d')).to.deep.equal({ a: { b: ['c', 'd'] } }); - expect(Riddler.parse('a[>=]=25')).to.deep.equal({ a: { '>=': '25' } }); + expect(Qs.parse('a[b][]=c&a[b][]=d')).to.deep.equal({ a: { b: ['c', 'd'] } }); + expect(Qs.parse('a[>=]=25')).to.deep.equal({ a: { '>=': '25' } }); done(); }); it('allows to specify array indices', function (done) { - expect(Riddler.parse('a[1]=c&a[0]=b&a[2]=d')).to.deep.equal({ a: ['b', 'c', 'd'] }); - expect(Riddler.parse('a[1]=c&a[0]=b')).to.deep.equal({ a: ['b', 'c'] }); - expect(Riddler.parse('a[1]=c')).to.deep.equal({ a: ['c'] }); + expect(Qs.parse('a[1]=c&a[0]=b&a[2]=d')).to.deep.equal({ a: ['b', 'c', 'd'] }); + expect(Qs.parse('a[1]=c&a[0]=b')).to.deep.equal({ a: ['b', 'c'] }); + expect(Qs.parse('a[1]=c')).to.deep.equal({ a: ['c'] }); done(); }); it('limits specific array indices to 20', function (done) { - expect(Riddler.parse('a[20]=a')).to.deep.equal({ a: ['a'] }); - expect(Riddler.parse('a[21]=a')).to.deep.equal({ a: { '21': 'a' } }); + expect(Qs.parse('a[20]=a')).to.deep.equal({ a: ['a'] }); + expect(Qs.parse('a[21]=a')).to.deep.equal({ a: { '21': 'a' } }); done(); }); it('supports encoded = signs', function (done) { - expect(Riddler.parse('he%3Dllo=th%3Dere')).to.deep.equal({ 'he=llo': 'th=ere' }); + expect(Qs.parse('he%3Dllo=th%3Dere')).to.deep.equal({ 'he=llo': 'th=ere' }); done(); }); it('is ok with url encoded strings', function (done) { - expect(Riddler.parse('a[b%20c]=d')).to.deep.equal({ a: { 'b c': 'd' } }); - expect(Riddler.parse('a[b]=c%20d')).to.deep.equal({ a: { b: 'c d' } }); + expect(Qs.parse('a[b%20c]=d')).to.deep.equal({ a: { 'b c': 'd' } }); + expect(Qs.parse('a[b]=c%20d')).to.deep.equal({ a: { b: 'c d' } }); done(); }); it('allows brackets in the value', function (done) { - expect(Riddler.parse('pets=["tobi"]')).to.deep.equal({ pets: '["tobi"]' }); - expect(Riddler.parse('operators=[">=", "<="]')).to.deep.equal({ operators: '[">=", "<="]' }); + expect(Qs.parse('pets=["tobi"]')).to.deep.equal({ pets: '["tobi"]' }); + expect(Qs.parse('operators=[">=", "<="]')).to.deep.equal({ operators: '[">=", "<="]' }); done(); }); it('allows empty values', function (done) { - expect(Riddler.parse('')).to.deep.equal({}); - expect(Riddler.parse(null)).to.deep.equal({}); - expect(Riddler.parse(undefined)).to.deep.equal({}); + expect(Qs.parse('')).to.deep.equal({}); + expect(Qs.parse(null)).to.deep.equal({}); + expect(Qs.parse(undefined)).to.deep.equal({}); done(); }); it('transforms arrays to objects', function (done) { - expect(Riddler.parse('foo[0]=bar&foo[bad]=baz')).to.deep.equal({ foo: { '0': 'bar', bad: 'baz' } }); - expect(Riddler.parse('foo[bad]=baz&foo[0]=bar')).to.deep.equal({ foo: { bad: 'baz', '0': 'bar' } }); - expect(Riddler.parse('foo[bad]=baz&foo[]=bar')).to.deep.equal({ foo: { bad: 'baz', '0': 'bar' } }); - expect(Riddler.parse('foo[]=bar&foo[bad]=baz')).to.deep.equal({ foo: { '0': 'bar', bad: 'baz' } }); - expect(Riddler.parse('foo[bad]=baz&foo[]=bar&foo[]=foo')).to.deep.equal({ foo: { bad: 'baz', '0': 'bar', '1': 'foo' } }); + expect(Qs.parse('foo[0]=bar&foo[bad]=baz')).to.deep.equal({ foo: { '0': 'bar', bad: 'baz' } }); + expect(Qs.parse('foo[bad]=baz&foo[0]=bar')).to.deep.equal({ foo: { bad: 'baz', '0': 'bar' } }); + expect(Qs.parse('foo[bad]=baz&foo[]=bar')).to.deep.equal({ foo: { bad: 'baz', '0': 'bar' } }); + expect(Qs.parse('foo[]=bar&foo[bad]=baz')).to.deep.equal({ foo: { '0': 'bar', bad: 'baz' } }); + expect(Qs.parse('foo[bad]=baz&foo[]=bar&foo[]=foo')).to.deep.equal({ foo: { bad: 'baz', '0': 'bar', '1': 'foo' } }); done(); }); it('supports malformed uri characters', function (done) { - expect(Riddler.parse('{%:%}')).to.deep.equal({ '{%:%}': '' }); - expect(Riddler.parse('foo=%:%}')).to.deep.equal({ foo: '%:%}' }); + expect(Qs.parse('{%:%}')).to.deep.equal({ '{%:%}': '' }); + expect(Qs.parse('foo=%:%}')).to.deep.equal({ foo: '%:%}' }); done(); }); it('doesn\'t produce empty keys', function (done) { - expect(Riddler.parse('_r=1&')).to.deep.equal({ '_r': '1' }); + expect(Qs.parse('_r=1&')).to.deep.equal({ '_r': '1' }); done(); }); it('cannot override prototypes', function (done) { - var obj = Riddler.parse('toString=bad&bad[toString]=bad&constructor=bad'); + var obj = Qs.parse('toString=bad&bad[toString]=bad&constructor=bad'); expect(typeof obj.toString).to.equal('function'); expect(typeof obj.bad.toString).to.equal('function'); expect(typeof obj.constructor).to.equal('function'); @@ -152,43 +165,43 @@ describe('Riddler.parse()', function () { it('cannot access Object prototype', function (done) { - Riddler.parse('constructor[prototype][bad]=bad'); - Riddler.parse('bad[constructor][prototype][bad]=bad'); + Qs.parse('constructor[prototype][bad]=bad'); + Qs.parse('bad[constructor][prototype][bad]=bad'); expect(typeof Object.prototype.bad).to.equal('undefined'); done(); }); it('parses arrays of objects', function (done) { - expect(Riddler.parse('a[][b]=c')).to.deep.equal({ a: [{ b: 'c' }] }); - expect(Riddler.parse('a[0][b]=c')).to.deep.equal({ a: [{ b: 'c' }] }); + expect(Qs.parse('a[][b]=c')).to.deep.equal({ a: [{ b: 'c' }] }); + expect(Qs.parse('a[0][b]=c')).to.deep.equal({ a: [{ b: 'c' }] }); done(); }); it('should compact sparse arrays', function (done) { - expect(Riddler.parse('a[10]=1&a[2]=2')).to.deep.equal({ a: ['2', '1'] }); + expect(Qs.parse('a[10]=1&a[2]=2')).to.deep.equal({ a: ['2', '1'] }); done(); }); it('parses semi-parsed strings', function (done) { - expect(Riddler.parse({ 'a[b]': 'c' })).to.deep.equal({ a: { b: 'c' } }); - expect(Riddler.parse({ 'a[b]': 'c', 'a[d]': 'e' })).to.deep.equal({ a: { b: 'c', d: 'e' } }); + expect(Qs.parse({ 'a[b]': 'c' })).to.deep.equal({ a: { b: 'c' } }); + expect(Qs.parse({ 'a[b]': 'c', 'a[d]': 'e' })).to.deep.equal({ a: { b: 'c', d: 'e' } }); done(); }); it('parses buffers to strings', function (done) { var b = new Buffer('test'); - expect(Riddler.parse({ a: b })).to.deep.equal({ a: b.toString() }); + expect(Qs.parse({ a: b })).to.deep.equal({ a: b.toString() }); done(); }); it('continues parsing when no parent is found', function (done) { - expect(Riddler.parse('[]&a=b')).to.deep.equal({ '0': '', a: 'b' }); - expect(Riddler.parse('[foo]=bar')).to.deep.equal({ foo: 'bar' }); + expect(Qs.parse('[]&a=b')).to.deep.equal({ '0': '', a: 'b' }); + expect(Qs.parse('[foo]=bar')).to.deep.equal({ foo: 'bar' }); done(); }); @@ -201,16 +214,17 @@ describe('Riddler.parse()', function () { expect(function () { - Riddler.parse(str); - }).to.not.throw(Error); + Qs.parse(str); + }).to.not.throw(); done(); }); - it('should not throw when a native prototype has an enumerable property', function (done) { + it('should not throw when a native prototype has an enumerable property', { parallel: false }, function (done) { Object.prototype.crash = ''; - expect(Riddler.parse.bind(null, 'test')).to.not.throw(Error); + expect(Qs.parse.bind(null, 'test')).to.not.throw(); + delete Object.prototype.crash; done(); }); }); diff --git a/test/stringify.js b/test/stringify.js old mode 100644 new mode 100755 index 3e0007a4..f1b740c4 --- a/test/stringify.js +++ b/test/stringify.js @@ -1,85 +1,98 @@ -var Riddler = require('../'); +// Load modules + var Lab = require('lab'); +var Qs = require('../'); + + +// Declare internals + +var internals = {}; + + +// Test shortcuts -var describe = Lab.experiment; var expect = Lab.expect; +var before = Lab.before; +var after = Lab.after; +var describe = Lab.experiment; var it = Lab.test; -describe('Riddler.stringify()', function () { - it('can stringify a querystring object', function (done) { +describe('#stringify', function () { + + it('stringifies a querystring object', function (done) { - expect(Riddler.stringify({ a: 'b' })).to.equal('a=b'); - expect(Riddler.stringify({ a: 1 })).to.equal('a=1'); - expect(Riddler.stringify({ a: 1, b: 2 })).to.equal('a=1&b=2'); + expect(Qs.stringify({ a: 'b' })).to.equal('a=b'); + expect(Qs.stringify({ a: 1 })).to.equal('a=1'); + expect(Qs.stringify({ a: 1, b: 2 })).to.equal('a=1&b=2'); done(); }); - it('can stringify a nested object', function (done) { + it('stringifies a nested object', function (done) { - expect(Riddler.stringify({ a: { b: 'c' } })).to.equal('a[b]=c'); - expect(Riddler.stringify({ a: { b: { c: { d: 'e' } } } })).to.equal('a[b][c][d]=e'); + expect(Qs.stringify({ a: { b: 'c' } })).to.equal('a[b]=c'); + expect(Qs.stringify({ a: { b: { c: { d: 'e' } } } })).to.equal('a[b][c][d]=e'); done(); }); - it('can stringify an array value', function (done) { + it('stringifies an array value', function (done) { - expect(Riddler.stringify({ a: ['b', 'c', 'd'] })).to.equal('a[0]=b&a[1]=c&a[2]=d'); + expect(Qs.stringify({ a: ['b', 'c', 'd'] })).to.equal('a[0]=b&a[1]=c&a[2]=d'); done(); }); - it('can stringify a nested array value', function (done) { + it('stringifies a nested array value', function (done) { - expect(Riddler.stringify({ a: { b: ['c', 'd'] } })).to.equal('a[b][0]=c&a[b][1]=d'); + expect(Qs.stringify({ a: { b: ['c', 'd'] } })).to.equal('a[b][0]=c&a[b][1]=d'); done(); }); - it('can stringify an object inside an array', function (done) { + it('stringifies an object inside an array', function (done) { - expect(Riddler.stringify({ a: [{ b: 'c' }] })).to.equal('a[0][b]=c'); - expect(Riddler.stringify({ a: [{ b: { c: [1] } }] })).to.equal('a[0][b][c][0]=1'); + expect(Qs.stringify({ a: [{ b: 'c' }] })).to.equal('a[0][b]=c'); + expect(Qs.stringify({ a: [{ b: { c: [1] } }] })).to.equal('a[0][b][c][0]=1'); done(); }); - it('can stringify a complicated object', function (done) { + it('stringifies a complicated object', function (done) { - expect(Riddler.stringify({ a: { b: 'c', d: 'e' } })).to.equal('a[b]=c&a[d]=e'); + expect(Qs.stringify({ a: { b: 'c', d: 'e' } })).to.equal('a[b]=c&a[d]=e'); done(); }); - it('can stringify an empty value', function (done) { + it('stringifies an empty value', function (done) { - expect(Riddler.stringify({ a: '' })).to.equal('a='); - expect(Riddler.stringify({ a: '', b: '' })).to.equal('a=&b='); - expect(Riddler.stringify({ a: null })).to.equal('a'); - expect(Riddler.stringify({ a: { b: null } })).to.equal('a[b]'); + expect(Qs.stringify({ a: '' })).to.equal('a='); + expect(Qs.stringify({ a: '', b: '' })).to.equal('a=&b='); + expect(Qs.stringify({ a: null })).to.equal('a'); + expect(Qs.stringify({ a: { b: null } })).to.equal('a[b]'); done(); }); it('drops keys with a value of undefined', function (done) { - expect(Riddler.stringify({ a: undefined })).to.equal(''); - expect(Riddler.stringify({ a: { b: undefined, c: null } })).to.equal('a[c]'); + expect(Qs.stringify({ a: undefined })).to.equal(''); + expect(Qs.stringify({ a: { b: undefined, c: null } })).to.equal('a[c]'); done(); }); it('url encodes values', function (done) { - expect(Riddler.stringify({ a: 'b c' })).to.equal('a=b%20c'); + expect(Qs.stringify({ a: 'b c' })).to.equal('a=b%20c'); done(); }); - it('can stringify a date', function (done) { + it('stringifies a date', function (done) { var now = new Date(); var str = 'a=' + encodeURIComponent(now.toISOString()); - expect(Riddler.stringify({ a: now })).to.equal(str); + expect(Qs.stringify({ a: now })).to.equal(str); done(); }); - it('can stringify the weird object from qs', function (done) { + it('stringifies the weird object from qs', function (done) { - expect(Riddler.stringify({ 'my weird field': 'q1!2"\'w$5&7/z8)?' })).to.equal('my%20weird%20field=q1!2%22\'w%245%267%2Fz8)%3F'); + expect(Qs.stringify({ 'my weird field': 'q1!2"\'w$5&7/z8)?' })).to.equal('my%20weird%20field=q1!2%22\'w%245%267%2Fz8)%3F'); done(); }); });