qs 2.4.0 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/.eslintignore ADDED
@@ -0,0 +1 @@
1
+ dist
package/.npmignore CHANGED
@@ -16,3 +16,4 @@ config.json
16
16
  coverage.*
17
17
  lib-cov
18
18
  complexity.md
19
+ dist
package/CHANGELOG.md CHANGED
@@ -1,13 +1,34 @@
1
1
 
2
- ## [**2.3.3**](https://github.com/hapijs/qs/issues?milestone=18&state=open)
3
- - [**#59**](https://github.com/hapijs/qs/issues/59) make sure array indexes are >= 0, closes #57
2
+ ## [**3.1.0**](https://github.com/hapijs/qs/issues?milestone=24&state=open)
3
+ - [**#89**](https://github.com/hapijs/qs/issues/89) Add option to disable "Transform dot notation to bracket notation"
4
+
5
+ ## [**3.0.0**](https://github.com/hapijs/qs/issues?milestone=23&state=closed)
6
+ - [**#77**](https://github.com/hapijs/qs/issues/77) Perf boost
7
+ - [**#60**](https://github.com/hapijs/qs/issues/60) Add explicit option to disable array parsing
8
+ - [**#80**](https://github.com/hapijs/qs/issues/80) qs.parse silently drops properties
9
+ - [**#74**](https://github.com/hapijs/qs/issues/74) Bad parse when turning array into object
10
+ - [**#81**](https://github.com/hapijs/qs/issues/81) Add a `filter` option
11
+ - [**#68**](https://github.com/hapijs/qs/issues/68) Fixed issue with recursion and passing strings into objects.
12
+ - [**#66**](https://github.com/hapijs/qs/issues/66) Add mixed array and object dot notation support Closes: #47
13
+ - [**#76**](https://github.com/hapijs/qs/issues/76) RFC 3986
14
+ - [**#85**](https://github.com/hapijs/qs/issues/85) No equal sign
15
+ - [**#84**](https://github.com/hapijs/qs/issues/84) update license attribute
16
+
17
+ ## [**2.4.1**](https://github.com/hapijs/qs/issues?milestone=20&state=closed)
18
+ - [**#73**](https://github.com/hapijs/qs/issues/73) Property 'hasOwnProperty' of object #<Object> is not a function
19
+
20
+ ## [**2.4.0**](https://github.com/hapijs/qs/issues?milestone=19&state=closed)
21
+ - [**#70**](https://github.com/hapijs/qs/issues/70) Add arrayFormat option
22
+
23
+ ## [**2.3.3**](https://github.com/hapijs/qs/issues?milestone=18&state=closed)
24
+ - [**#59**](https://github.com/hapijs/qs/issues/59) make sure array indexes are >= 0, closes #57
4
25
  - [**#58**](https://github.com/hapijs/qs/issues/58) make qs usable for browser loader
5
26
 
6
27
  ## [**2.3.2**](https://github.com/hapijs/qs/issues?milestone=17&state=closed)
7
28
  - [**#55**](https://github.com/hapijs/qs/issues/55) allow merging a string into an object
8
29
 
9
30
  ## [**2.3.1**](https://github.com/hapijs/qs/issues?milestone=16&state=closed)
10
- - [**#52**](https://github.com/hapijs/qs/issues/52) Return &quot;undefined&quot; and &quot;false&quot; instead of throwing &quot;TypeError&quot;.
31
+ - [**#52**](https://github.com/hapijs/qs/issues/52) Return "undefined" and "false" instead of throwing "TypeError".
11
32
 
12
33
  ## [**2.3.0**](https://github.com/hapijs/qs/issues?milestone=15&state=closed)
13
34
  - [**#50**](https://github.com/hapijs/qs/issues/50) add option to omit array indices, closes #46
@@ -34,9 +55,9 @@
34
55
  - [**#31**](https://github.com/hapijs/qs/issues/31) qs.parse stackoverflow on circular objects
35
56
 
36
57
  ## [**2.2.0**](https://github.com/hapijs/qs/issues?milestone=9&state=closed)
37
- - [**#26**](https://github.com/hapijs/qs/issues/26) Don&#39;t use Buffer global if it&#39;s not present
58
+ - [**#26**](https://github.com/hapijs/qs/issues/26) Don't use Buffer global if it's not present
38
59
  - [**#30**](https://github.com/hapijs/qs/issues/30) Bug when merging non-object values into arrays
39
- - [**#29**](https://github.com/hapijs/qs/issues/29) Don&#39;t call Utils.clone at the top of Utils.merge
60
+ - [**#29**](https://github.com/hapijs/qs/issues/29) Don't call Utils.clone at the top of Utils.merge
40
61
  - [**#23**](https://github.com/hapijs/qs/issues/23) Ability to not limit parameters?
41
62
 
42
63
  ## [**2.1.0**](https://github.com/hapijs/qs/issues?milestone=8&state=closed)
@@ -48,7 +69,7 @@
48
69
  - [**#21**](https://github.com/hapijs/qs/issues/21) make all limits optional, for #18, for #20
49
70
 
50
71
  ## [**1.2.2**](https://github.com/hapijs/qs/issues?milestone=6&state=closed)
51
- - [**#19**](https://github.com/hapijs/qs/issues/19) Don&#39;t overwrite null values
72
+ - [**#19**](https://github.com/hapijs/qs/issues/19) Don't overwrite null values
52
73
 
53
74
  ## [**1.2.1**](https://github.com/hapijs/qs/issues?milestone=5&state=closed)
54
75
  - [**#16**](https://github.com/hapijs/qs/issues/16) ignore non-string delimiters
@@ -65,4 +86,3 @@
65
86
 
66
87
  ## [**1.0.2**](https://github.com/hapijs/qs/issues?milestone=2&state=closed)
67
88
  - [**#5**](https://github.com/hapijs/qs/issues/5) array holes incorrectly copied into object on large index
68
-
package/LICENSE CHANGED
File without changes
package/README.md CHANGED
@@ -23,7 +23,7 @@ var str = Qs.stringify(obj); // 'a=c'
23
23
  Qs.parse(string, [options]);
24
24
  ```
25
25
 
26
- **qs** allows you to create nested objects within your query strings, by surrounding the name of sub-keys with square brackets `[]`.
26
+ **qs** allows you to create nested objects within your query strings, by surrounding the name of sub-keys with square brackets `[]`, or prefixing the sub-key with a dot `.`.
27
27
  For example, the string `'foo[bar]=baz'` converts to:
28
28
 
29
29
  ```javascript
@@ -34,6 +34,13 @@ For example, the string `'foo[bar]=baz'` converts to:
34
34
  }
35
35
  ```
36
36
 
37
+ The parsed value is returned as a plain object, created via `Object.create(null)` and as such you should be aware that prototype methods do not exist on it and a user may set those names to whatever value they like:
38
+
39
+ ```javascript
40
+ Qs.parse('a.hasOwnProperty=b');
41
+ // { a: { hasOwnProperty: 'b' } }
42
+ ```
43
+
37
44
  URI encoded strings work too:
38
45
 
39
46
  ```javascript
@@ -153,7 +160,12 @@ Qs.parse('a[1]=b', { arrayLimit: 0 });
153
160
  // { a: { '1': 'b' } }
154
161
  ```
155
162
 
156
- To disable array parsing entirely, set `arrayLimit` to `-1`.
163
+ To disable array parsing entirely, set `parseArrays` to `false`.
164
+
165
+ ```javascript
166
+ Qs.parse('a[]=b', { parseArrays: false });
167
+ // { a: { '0': 'b' } }
168
+ ```
157
169
 
158
170
  If you mix notations, **qs** will merge the two items into an object:
159
171
 
@@ -200,6 +212,17 @@ Qs.stringify({ a: ['b', 'c', 'd'] }, { indices: false });
200
212
  // 'a=b&a=c&a=d'
201
213
  ```
202
214
 
215
+ You may use the `arrayFormat` option to specify the format of the output array
216
+
217
+ ```javascript
218
+ Qs.stringify({ a: ['b', 'c'] }, { arrayFormat: 'indices' })
219
+ // 'a[0]=b&a[1]=c'
220
+ Qs.stringify({ a: ['b', 'c'] }, { arrayFormat: 'brackets' })
221
+ // 'a[]=b&a[]=c'
222
+ Qs.stringify({ a: ['b', 'c'] }, { arrayFormat: 'repeat' })
223
+ // 'a=b&a=c'
224
+ ```
225
+
203
226
  Empty strings and null values will omit the value, but the equals sign (=) remains in place:
204
227
 
205
228
  ```javascript
@@ -220,3 +243,61 @@ The delimiter may be overridden with stringify as well:
220
243
  Qs.stringify({ a: 'b', c: 'd' }, { delimiter: ';' });
221
244
  // 'a=b;c=d'
222
245
  ```
246
+
247
+ Finally, you can use the `filter` option to restrict which keys will be included in the stringified output.
248
+ If you pass a function, it will be called for each key to obtain the replacement value. Otherwise, if you
249
+ pass an array, it will be used to select properties and array indices for stringification:
250
+
251
+ ```javascript
252
+ function filterFunc(prefix, value) {
253
+ if (prefix == 'b') {
254
+ // Return an `undefined` value to omit a property.
255
+ return;
256
+ }
257
+ if (prefix == 'e[f]') {
258
+ return value.getTime();
259
+ }
260
+ if (prefix == 'e[g][0]') {
261
+ return value * 2;
262
+ }
263
+ return value;
264
+ }
265
+ Qs.stringify({ a: 'b', c: 'd', e: { f: new Date(123), g: [2] } }, { filter: filterFunc })
266
+ // 'a=b&c=d&e[f]=123&e[g][0]=4'
267
+ Qs.stringify({ a: 'b', c: 'd', e: 'f' }, { filter: ['a', 'e'] })
268
+ // 'a=b&e=f'
269
+ Qs.stringify({ a: ['b', 'c', 'd'], e: 'f' }, { filter: ['a', 0, 2] })
270
+ // 'a[0]=b&a[2]=d'
271
+ ```
272
+
273
+ ### Handling of `null` values
274
+
275
+ By default, `null` values are treated like empty strings:
276
+
277
+ ```javascript
278
+ Qs.stringify({ a: null, b: '' });
279
+ // 'a=&b='
280
+ ```
281
+
282
+ Parsing does not distinguish between parameters with and without equal signs. Both are converted to empty strings.
283
+
284
+ ```javascript
285
+ Qs.parse('a&b=')
286
+ // { a: '', b: '' }
287
+ ```
288
+
289
+ To distinguish between `null` values and empty strings use the `strictNullHandling` flag. In the result string the `null`
290
+ values have no `=` sign:
291
+
292
+ ```javascript
293
+ Qs.stringify({ a: null, b: '' }, { strictNullHandling: true });
294
+ // 'a&b='
295
+ ```
296
+
297
+ To parse values without `=` back to `null` use the `strictNullHandling` flag:
298
+
299
+ ```javascript
300
+ Qs.parse('a&b=', { strictNullHandling: true });
301
+ // { a: null, b: '' }
302
+
303
+ ```
package/bower.json ADDED
@@ -0,0 +1,22 @@
1
+ {
2
+ "name": "qs",
3
+ "main": "dist/qs.js",
4
+ "version": "3.0.0",
5
+ "homepage": "https://github.com/hapijs/qs",
6
+ "authors": [
7
+ "Nathan LaFreniere <quitlahok@gmail.com>"
8
+ ],
9
+ "description": "A querystring parser that supports nesting and arrays, with a depth limit",
10
+ "keywords": [
11
+ "querystring",
12
+ "qs"
13
+ ],
14
+ "license": "BSD-3-Clause",
15
+ "ignore": [
16
+ "**/.*",
17
+ "node_modules",
18
+ "bower_components",
19
+ "test",
20
+ "tests"
21
+ ]
22
+ }
package/lib/index.js CHANGED
File without changes
package/lib/parse.js CHANGED
@@ -9,7 +9,8 @@ var internals = {
9
9
  delimiter: '&',
10
10
  depth: 5,
11
11
  arrayLimit: 20,
12
- parameterLimit: 1000
12
+ parameterLimit: 1000,
13
+ strictNullHandling: false
13
14
  };
14
15
 
15
16
 
@@ -24,12 +25,16 @@ internals.parseValues = function (str, options) {
24
25
 
25
26
  if (pos === -1) {
26
27
  obj[Utils.decode(part)] = '';
28
+
29
+ if (options.strictNullHandling) {
30
+ obj[Utils.decode(part)] = null;
31
+ }
27
32
  }
28
33
  else {
29
34
  var key = Utils.decode(part.slice(0, pos));
30
35
  var val = Utils.decode(part.slice(pos + 1));
31
36
 
32
- if (!obj.hasOwnProperty(key)) {
37
+ if (!Object.prototype.hasOwnProperty.call(obj, key)) {
33
38
  obj[key] = val;
34
39
  }
35
40
  else {
@@ -50,12 +55,13 @@ internals.parseObject = function (chain, val, options) {
50
55
 
51
56
  var root = chain.shift();
52
57
 
53
- var obj = {};
58
+ var obj;
54
59
  if (root === '[]') {
55
60
  obj = [];
56
61
  obj = obj.concat(internals.parseObject(chain, val, options));
57
62
  }
58
63
  else {
64
+ obj = Object.create(null);
59
65
  var cleanRoot = root[0] === '[' && root[root.length - 1] === ']' ? root.slice(1, root.length - 1) : root;
60
66
  var index = parseInt(cleanRoot, 10);
61
67
  var indexString = '' + index;
@@ -63,7 +69,8 @@ internals.parseObject = function (chain, val, options) {
63
69
  root !== cleanRoot &&
64
70
  indexString === cleanRoot &&
65
71
  index >= 0 &&
66
- index <= options.arrayLimit) {
72
+ (options.parseArrays &&
73
+ index <= options.arrayLimit)) {
67
74
 
68
75
  obj = [];
69
76
  obj[index] = internals.parseObject(chain, val, options);
@@ -83,6 +90,12 @@ internals.parseKeys = function (key, val, options) {
83
90
  return;
84
91
  }
85
92
 
93
+ // Transform dot notation to bracket notation
94
+
95
+ if (options.allowDots) {
96
+ key = key.replace(/\.([^\.\[]+)/g, '[$1]');
97
+ }
98
+
86
99
  // The regex chunks
87
100
 
88
101
  var parent = /^([^\[\]]*)/;
@@ -92,12 +105,6 @@ internals.parseKeys = function (key, val, options) {
92
105
 
93
106
  var segment = parent.exec(key);
94
107
 
95
- // Don't allow them to overwrite object prototype properties
96
-
97
- if (Object.prototype.hasOwnProperty(segment[1])) {
98
- return;
99
- }
100
-
101
108
  // Stash the parent if it exists
102
109
 
103
110
  var keys = [];
@@ -111,9 +118,7 @@ internals.parseKeys = function (key, val, options) {
111
118
  while ((segment = child.exec(key)) !== null && i < options.depth) {
112
119
 
113
120
  ++i;
114
- if (!Object.prototype.hasOwnProperty(segment[1].replace(/\[|\]/g, ''))) {
115
- keys.push(segment[1]);
116
- }
121
+ keys.push(segment[1]);
117
122
  }
118
123
 
119
124
  // If there's a remainder, just add whatever is left
@@ -132,17 +137,21 @@ module.exports = function (str, options) {
132
137
  str === null ||
133
138
  typeof str === 'undefined') {
134
139
 
135
- return {};
140
+ return Object.create(null);
136
141
  }
137
142
 
138
143
  options = options || {};
139
144
  options.delimiter = typeof options.delimiter === 'string' || Utils.isRegExp(options.delimiter) ? options.delimiter : internals.delimiter;
140
145
  options.depth = typeof options.depth === 'number' ? options.depth : internals.depth;
141
146
  options.arrayLimit = typeof options.arrayLimit === 'number' ? options.arrayLimit : internals.arrayLimit;
147
+ options.parseArrays = options.parseArrays !== false;
148
+ options.allowDots = options.allowDots !== false;
142
149
  options.parameterLimit = typeof options.parameterLimit === 'number' ? options.parameterLimit : internals.parameterLimit;
150
+ options.strictNullHandling = typeof options.strictNullHandling === 'boolean' ? options.strictNullHandling : internals.strictNullHandling;
151
+
143
152
 
144
153
  var tempObj = typeof str === 'string' ? internals.parseValues(str, options) : str;
145
- var obj = {};
154
+ var obj = Object.create(null);
146
155
 
147
156
  // Iterate over the keys and setup the new object
148
157
 
package/lib/stringify.js CHANGED
@@ -9,27 +9,38 @@ var internals = {
9
9
  delimiter: '&',
10
10
  arrayPrefixGenerators: {
11
11
  brackets: function (prefix, key) {
12
+
12
13
  return prefix + '[]';
13
14
  },
14
15
  indices: function (prefix, key) {
16
+
15
17
  return prefix + '[' + key + ']';
16
18
  },
17
19
  repeat: function (prefix, key) {
20
+
18
21
  return prefix;
19
22
  }
20
- }
23
+ },
24
+ strictNullHandling: false
21
25
  };
22
26
 
23
27
 
24
- internals.stringify = function (obj, prefix, generateArrayPrefix) {
28
+ internals.stringify = function (obj, prefix, generateArrayPrefix, strictNullHandling, filter) {
25
29
 
26
- if (Utils.isBuffer(obj)) {
30
+ if (typeof filter === 'function') {
31
+ obj = filter(prefix, obj);
32
+ }
33
+ else if (Utils.isBuffer(obj)) {
27
34
  obj = obj.toString();
28
35
  }
29
36
  else if (obj instanceof Date) {
30
37
  obj = obj.toISOString();
31
38
  }
32
39
  else if (obj === null) {
40
+ if (strictNullHandling) {
41
+ return Utils.encode(prefix);
42
+ }
43
+
33
44
  obj = '';
34
45
  }
35
46
 
@@ -37,7 +48,7 @@ internals.stringify = function (obj, prefix, generateArrayPrefix) {
37
48
  typeof obj === 'number' ||
38
49
  typeof obj === 'boolean') {
39
50
 
40
- return [encodeURIComponent(prefix) + '=' + encodeURIComponent(obj)];
51
+ return [Utils.encode(prefix) + '=' + Utils.encode(obj)];
41
52
  }
42
53
 
43
54
  var values = [];
@@ -46,14 +57,15 @@ internals.stringify = function (obj, prefix, generateArrayPrefix) {
46
57
  return values;
47
58
  }
48
59
 
49
- var objKeys = Object.keys(obj);
60
+ var objKeys = Array.isArray(filter) ? filter : Object.keys(obj);
50
61
  for (var i = 0, il = objKeys.length; i < il; ++i) {
51
62
  var key = objKeys[i];
63
+
52
64
  if (Array.isArray(obj)) {
53
- values = values.concat(internals.stringify(obj[key], generateArrayPrefix(prefix, key), generateArrayPrefix));
65
+ values = values.concat(internals.stringify(obj[key], generateArrayPrefix(prefix, key), generateArrayPrefix, strictNullHandling, filter));
54
66
  }
55
67
  else {
56
- values = values.concat(internals.stringify(obj[key], prefix + '[' + key + ']', generateArrayPrefix));
68
+ values = values.concat(internals.stringify(obj[key], prefix + '[' + key + ']', generateArrayPrefix, strictNullHandling, filter));
57
69
  }
58
70
  }
59
71
 
@@ -65,6 +77,16 @@ module.exports = function (obj, options) {
65
77
 
66
78
  options = options || {};
67
79
  var delimiter = typeof options.delimiter === 'undefined' ? internals.delimiter : options.delimiter;
80
+ var strictNullHandling = typeof options.strictNullHandling === 'boolean' ? options.strictNullHandling : internals.strictNullHandling;
81
+ var objKeys;
82
+ var filter;
83
+ if (typeof options.filter === 'function') {
84
+ filter = options.filter;
85
+ obj = filter('', obj);
86
+ }
87
+ else if (Array.isArray(options.filter)) {
88
+ objKeys = filter = options.filter;
89
+ }
68
90
 
69
91
  var keys = [];
70
92
 
@@ -87,10 +109,12 @@ module.exports = function (obj, options) {
87
109
 
88
110
  var generateArrayPrefix = internals.arrayPrefixGenerators[arrayFormat];
89
111
 
90
- var objKeys = Object.keys(obj);
112
+ if (!objKeys) {
113
+ objKeys = Object.keys(obj);
114
+ }
91
115
  for (var i = 0, il = objKeys.length; i < il; ++i) {
92
116
  var key = objKeys[i];
93
- keys = keys.concat(internals.stringify(obj[key], key, generateArrayPrefix));
117
+ keys = keys.concat(internals.stringify(obj[key], key, generateArrayPrefix, strictNullHandling, filter));
94
118
  }
95
119
 
96
120
  return keys.join(delimiter);
package/lib/utils.js CHANGED
@@ -4,11 +4,15 @@
4
4
  // Declare internals
5
5
 
6
6
  var internals = {};
7
+ internals.hexTable = new Array(256);
8
+ for (var i = 0; i < 256; ++i) {
9
+ internals.hexTable[i] = '%' + ((i < 16 ? '0' : '') + i.toString(16)).toUpperCase();
10
+ }
7
11
 
8
12
 
9
13
  exports.arrayToObject = function (source) {
10
14
 
11
- var obj = {};
15
+ var obj = Object.create(null);
12
16
  for (var i = 0, il = source.length; i < il; ++i) {
13
17
  if (typeof source[i] !== 'undefined') {
14
18
 
@@ -30,9 +34,12 @@ exports.merge = function (target, source) {
30
34
  if (Array.isArray(target)) {
31
35
  target.push(source);
32
36
  }
33
- else {
37
+ else if (typeof target === 'object') {
34
38
  target[source] = true;
35
39
  }
40
+ else {
41
+ target = [target, source];
42
+ }
36
43
 
37
44
  return target;
38
45
  }
@@ -74,6 +81,56 @@ exports.decode = function (str) {
74
81
  }
75
82
  };
76
83
 
84
+ exports.encode = function (str) {
85
+
86
+ // This code was originally written by Brian White (mscdex) for the io.js core querystring library.
87
+ // It has been adapted here for stricter adherence to RFC 3986
88
+ if (str.length === 0) {
89
+ return str;
90
+ }
91
+
92
+ if (typeof str !== 'string') {
93
+ str = '' + str;
94
+ }
95
+
96
+ var out = '';
97
+ for (var i = 0, il = str.length; i < il; ++i) {
98
+ var c = str.charCodeAt(i);
99
+
100
+ if (c === 0x2D || // -
101
+ c === 0x2E || // .
102
+ c === 0x5F || // _
103
+ c === 0x7E || // ~
104
+ (c >= 0x30 && c <= 0x39) || // 0-9
105
+ (c >= 0x41 && c <= 0x5A) || // a-z
106
+ (c >= 0x61 && c <= 0x7A)) { // A-Z
107
+
108
+ out += str[i];
109
+ continue;
110
+ }
111
+
112
+ if (c < 0x80) {
113
+ out += internals.hexTable[c];
114
+ continue;
115
+ }
116
+
117
+ if (c < 0x800) {
118
+ out += internals.hexTable[0xC0 | (c >> 6)] + internals.hexTable[0x80 | (c & 0x3F)];
119
+ continue;
120
+ }
121
+
122
+ if (c < 0xD800 || c >= 0xE000) {
123
+ out += internals.hexTable[0xE0 | (c >> 12)] + internals.hexTable[0x80 | ((c >> 6) & 0x3F)] + internals.hexTable[0x80 | (c & 0x3F)];
124
+ continue;
125
+ }
126
+
127
+ ++i;
128
+ c = 0x10000 + (((c & 0x3FF) << 10) | (str.charCodeAt(i) & 0x3FF));
129
+ out += internals.hexTable[0xF0 | (c >> 18)] + internals.hexTable[0x80 | ((c >> 12) & 0x3F)] + internals.hexTable[0x80 | ((c >> 6) & 0x3F)] + internals.hexTable[0x80 | (c & 0x3F)];
130
+ }
131
+
132
+ return out;
133
+ };
77
134
 
78
135
  exports.compact = function (obj, refs) {
79
136
 
@@ -114,6 +171,7 @@ exports.compact = function (obj, refs) {
114
171
 
115
172
 
116
173
  exports.isRegExp = function (obj) {
174
+
117
175
  return Object.prototype.toString.call(obj) === '[object RegExp]';
118
176
  };
119
177
 
@@ -127,6 +185,6 @@ exports.isBuffer = function (obj) {
127
185
  }
128
186
 
129
187
  return !!(obj.constructor &&
130
- obj.constructor.isBuffer &&
131
- obj.constructor.isBuffer(obj));
188
+ obj.constructor.isBuffer &&
189
+ obj.constructor.isBuffer(obj));
132
190
  };
package/package.json CHANGED
@@ -1,16 +1,18 @@
1
1
  {
2
2
  "name": "qs",
3
- "version": "2.4.0",
3
+ "version": "3.1.0",
4
4
  "description": "A querystring parser that supports nesting and arrays, with a depth limit",
5
5
  "homepage": "https://github.com/hapijs/qs",
6
6
  "main": "index.js",
7
7
  "dependencies": {},
8
8
  "devDependencies": {
9
+ "browserify": "^10.2.1",
9
10
  "code": "1.x.x",
10
11
  "lab": "5.x.x"
11
12
  },
12
13
  "scripts": {
13
- "test": "make test-cov"
14
+ "test": "make test-cov",
15
+ "dist": "browserify --standalone Qs index.js > dist/qs.js"
14
16
  },
15
17
  "repository": {
16
18
  "type": "git",
@@ -20,10 +22,5 @@
20
22
  "querystring",
21
23
  "qs"
22
24
  ],
23
- "licenses": [
24
- {
25
- "type": "BSD",
26
- "url": "http://github.com/hapijs/qs/raw/master/LICENSE"
27
- }
28
- ]
25
+ "license": "BSD-3-Clause"
29
26
  }
package/test/parse.js CHANGED
@@ -23,174 +23,194 @@ describe('parse()', function () {
23
23
 
24
24
  it('parses a simple string', function (done) {
25
25
 
26
- expect(Qs.parse('0=foo')).to.deep.equal({ '0': 'foo' });
27
- expect(Qs.parse('foo=c++')).to.deep.equal({ foo: 'c ' });
28
- expect(Qs.parse('a[>=]=23')).to.deep.equal({ a: { '>=': '23' } });
29
- expect(Qs.parse('a[<=>]==23')).to.deep.equal({ a: { '<=>': '=23' } });
30
- expect(Qs.parse('a[==]=23')).to.deep.equal({ a: { '==': '23' } });
31
- expect(Qs.parse('foo')).to.deep.equal({ foo: '' });
32
- expect(Qs.parse('foo=bar')).to.deep.equal({ foo: 'bar' });
33
- expect(Qs.parse(' foo = bar = baz ')).to.deep.equal({ ' foo ': ' bar = baz ' });
34
- expect(Qs.parse('foo=bar=baz')).to.deep.equal({ foo: 'bar=baz' });
35
- expect(Qs.parse('foo=bar&bar=baz')).to.deep.equal({ foo: 'bar', bar: 'baz' });
36
- expect(Qs.parse('foo=bar&baz')).to.deep.equal({ foo: 'bar', baz: '' });
26
+ expect(Qs.parse('0=foo')).to.deep.equal({ '0': 'foo' }, { prototype: false });
27
+ expect(Qs.parse('foo=c++')).to.deep.equal({ foo: 'c ' }, { prototype: false });
28
+ expect(Qs.parse('a[>=]=23')).to.deep.equal({ a: { '>=': '23' } }, { prototype: false });
29
+ expect(Qs.parse('a[<=>]==23')).to.deep.equal({ a: { '<=>': '=23' } }, { prototype: false });
30
+ expect(Qs.parse('a[==]=23')).to.deep.equal({ a: { '==': '23' } }, { prototype: false });
31
+ expect(Qs.parse('foo', {strictNullHandling: true})).to.deep.equal({ foo: null }, { prototype: false });
32
+ expect(Qs.parse('foo' )).to.deep.equal({ foo: '' }, { prototype: false });
33
+ expect(Qs.parse('foo=')).to.deep.equal({ foo: '' }, { prototype: false });
34
+ expect(Qs.parse('foo=bar')).to.deep.equal({ foo: 'bar' }, { prototype: false });
35
+ expect(Qs.parse(' foo = bar = baz ')).to.deep.equal({ ' foo ': ' bar = baz ' }, { prototype: false });
36
+ expect(Qs.parse('foo=bar=baz')).to.deep.equal({ foo: 'bar=baz' }, { prototype: false });
37
+ expect(Qs.parse('foo=bar&bar=baz')).to.deep.equal({ foo: 'bar', bar: 'baz' }, { prototype: false });
38
+ expect(Qs.parse('foo2=bar2&baz2=')).to.deep.equal({ foo2: 'bar2', baz2: '' }, { prototype: false });
39
+ expect(Qs.parse('foo=bar&baz', {strictNullHandling: true})).to.deep.equal({ foo: 'bar', baz: null }, { prototype: false });
40
+ expect(Qs.parse('foo=bar&baz')).to.deep.equal({ foo: 'bar', baz: '' }, { prototype: false });
37
41
  expect(Qs.parse('cht=p3&chd=t:60,40&chs=250x100&chl=Hello|World')).to.deep.equal({
38
42
  cht: 'p3',
39
43
  chd: 't:60,40',
40
44
  chs: '250x100',
41
45
  chl: 'Hello|World'
42
- });
46
+ }, { prototype: false });
47
+ done();
48
+ });
49
+
50
+ it('allows disabling dot notation', function (done) {
51
+
52
+ expect(Qs.parse('a.b=c')).to.deep.equal({ a: { b: 'c' } }, { prototype: false });
53
+ expect(Qs.parse('a.b=c', { allowDots: false })).to.deep.equal({ 'a.b': 'c' }, { prototype: false });
43
54
  done();
44
55
  });
45
56
 
46
57
  it('parses a single nested string', function (done) {
47
58
 
48
- expect(Qs.parse('a[b]=c')).to.deep.equal({ a: { b: 'c' } });
59
+ expect(Qs.parse('a[b]=c')).to.deep.equal({ a: { b: 'c' } }, { prototype: false });
49
60
  done();
50
61
  });
51
62
 
52
63
  it('parses a double nested string', function (done) {
53
64
 
54
- expect(Qs.parse('a[b][c]=d')).to.deep.equal({ a: { b: { c: 'd' } } });
65
+ expect(Qs.parse('a[b][c]=d')).to.deep.equal({ a: { b: { c: 'd' } } }, { prototype: false });
55
66
  done();
56
67
  });
57
68
 
58
69
  it('defaults to a depth of 5', function (done) {
59
70
 
60
- expect(Qs.parse('a[b][c][d][e][f][g][h]=i')).to.deep.equal({ a: { b: { c: { d: { e: { f: { '[g][h]': 'i' } } } } } } });
71
+ expect(Qs.parse('a[b][c][d][e][f][g][h]=i')).to.deep.equal({ a: { b: { c: { d: { e: { f: { '[g][h]': 'i' } } } } } } }, { prototype: false });
61
72
  done();
62
73
  });
63
74
 
64
75
  it('only parses one level when depth = 1', function (done) {
65
76
 
66
- expect(Qs.parse('a[b][c]=d', { depth: 1 })).to.deep.equal({ a: { b: { '[c]': 'd' } } });
67
- expect(Qs.parse('a[b][c][d]=e', { depth: 1 })).to.deep.equal({ a: { b: { '[c][d]': 'e' } } });
77
+ expect(Qs.parse('a[b][c]=d', { depth: 1 })).to.deep.equal({ a: { b: { '[c]': 'd' } } }, { prototype: false });
78
+ expect(Qs.parse('a[b][c][d]=e', { depth: 1 })).to.deep.equal({ a: { b: { '[c][d]': 'e' } } }, { prototype: false });
68
79
  done();
69
80
  });
70
81
 
71
82
  it('parses a simple array', function (done) {
72
83
 
73
- expect(Qs.parse('a=b&a=c')).to.deep.equal({ a: ['b', 'c'] });
84
+ expect(Qs.parse('a=b&a=c')).to.deep.equal({ a: ['b', 'c'] }, { prototype: false });
74
85
  done();
75
86
  });
76
87
 
77
88
  it('parses an explicit array', function (done) {
78
89
 
79
- expect(Qs.parse('a[]=b')).to.deep.equal({ a: ['b'] });
80
- expect(Qs.parse('a[]=b&a[]=c')).to.deep.equal({ a: ['b', 'c'] });
81
- expect(Qs.parse('a[]=b&a[]=c&a[]=d')).to.deep.equal({ a: ['b', 'c', 'd'] });
90
+ expect(Qs.parse('a[]=b')).to.deep.equal({ a: ['b'] }, { prototype: false });
91
+ expect(Qs.parse('a[]=b&a[]=c')).to.deep.equal({ a: ['b', 'c'] }, { prototype: false });
92
+ expect(Qs.parse('a[]=b&a[]=c&a[]=d')).to.deep.equal({ a: ['b', 'c', 'd'] }, { prototype: false });
82
93
  done();
83
94
  });
84
95
 
85
96
  it('parses a mix of simple and explicit arrays', function (done) {
86
97
 
87
- expect(Qs.parse('a=b&a[]=c')).to.deep.equal({ a: ['b', 'c'] });
88
- expect(Qs.parse('a[]=b&a=c')).to.deep.equal({ a: ['b', 'c'] });
89
- expect(Qs.parse('a[0]=b&a=c')).to.deep.equal({ a: ['b', 'c'] });
90
- expect(Qs.parse('a=b&a[0]=c')).to.deep.equal({ a: ['b', 'c'] });
91
- expect(Qs.parse('a[1]=b&a=c')).to.deep.equal({ a: ['b', 'c'] });
92
- expect(Qs.parse('a=b&a[1]=c')).to.deep.equal({ a: ['b', 'c'] });
98
+ expect(Qs.parse('a=b&a[]=c')).to.deep.equal({ a: ['b', 'c'] }, { prototype: false });
99
+ expect(Qs.parse('a[]=b&a=c')).to.deep.equal({ a: ['b', 'c'] }, { prototype: false });
100
+ expect(Qs.parse('a[0]=b&a=c')).to.deep.equal({ a: ['b', 'c'] }, { prototype: false });
101
+ expect(Qs.parse('a=b&a[0]=c')).to.deep.equal({ a: ['b', 'c'] }, { prototype: false });
102
+ expect(Qs.parse('a[1]=b&a=c')).to.deep.equal({ a: ['b', 'c'] }, { prototype: false });
103
+ expect(Qs.parse('a=b&a[1]=c')).to.deep.equal({ a: ['b', 'c'] }, { prototype: false });
93
104
  done();
94
105
  });
95
106
 
96
107
  it('parses a nested array', function (done) {
97
108
 
98
- expect(Qs.parse('a[b][]=c&a[b][]=d')).to.deep.equal({ a: { b: ['c', 'd'] } });
99
- expect(Qs.parse('a[>=]=25')).to.deep.equal({ a: { '>=': '25' } });
109
+ expect(Qs.parse('a[b][]=c&a[b][]=d')).to.deep.equal({ a: { b: ['c', 'd'] } }, { prototype: false });
110
+ expect(Qs.parse('a[>=]=25')).to.deep.equal({ a: { '>=': '25' } }, { prototype: false });
100
111
  done();
101
112
  });
102
113
 
103
114
  it('allows to specify array indices', function (done) {
104
115
 
105
- expect(Qs.parse('a[1]=c&a[0]=b&a[2]=d')).to.deep.equal({ a: ['b', 'c', 'd'] });
106
- expect(Qs.parse('a[1]=c&a[0]=b')).to.deep.equal({ a: ['b', 'c'] });
107
- expect(Qs.parse('a[1]=c')).to.deep.equal({ a: ['c'] });
116
+ expect(Qs.parse('a[1]=c&a[0]=b&a[2]=d')).to.deep.equal({ a: ['b', 'c', 'd'] }, { prototype: false });
117
+ expect(Qs.parse('a[1]=c&a[0]=b')).to.deep.equal({ a: ['b', 'c'] }, { prototype: false });
118
+ expect(Qs.parse('a[1]=c')).to.deep.equal({ a: ['c'] }, { prototype: false });
108
119
  done();
109
120
  });
110
121
 
111
122
  it('limits specific array indices to 20', function (done) {
112
123
 
113
- expect(Qs.parse('a[20]=a')).to.deep.equal({ a: ['a'] });
114
- expect(Qs.parse('a[21]=a')).to.deep.equal({ a: { '21': 'a' } });
124
+ expect(Qs.parse('a[20]=a')).to.deep.equal({ a: ['a'] }, { prototype: false });
125
+ expect(Qs.parse('a[21]=a')).to.deep.equal({ a: { '21': 'a' } }, { prototype: false });
115
126
  done();
116
127
  });
117
128
 
118
129
  it('supports keys that begin with a number', function (done) {
119
130
 
120
- expect(Qs.parse('a[12b]=c')).to.deep.equal({ a: { '12b': 'c' } });
131
+ expect(Qs.parse('a[12b]=c')).to.deep.equal({ a: { '12b': 'c' } }, { prototype: false });
121
132
  done();
122
133
  });
123
134
 
124
135
  it('supports encoded = signs', function (done) {
125
136
 
126
- expect(Qs.parse('he%3Dllo=th%3Dere')).to.deep.equal({ 'he=llo': 'th=ere' });
137
+ expect(Qs.parse('he%3Dllo=th%3Dere')).to.deep.equal({ 'he=llo': 'th=ere' }, { prototype: false });
127
138
  done();
128
139
  });
129
140
 
130
141
  it('is ok with url encoded strings', function (done) {
131
142
 
132
- expect(Qs.parse('a[b%20c]=d')).to.deep.equal({ a: { 'b c': 'd' } });
133
- expect(Qs.parse('a[b]=c%20d')).to.deep.equal({ a: { b: 'c d' } });
143
+ expect(Qs.parse('a[b%20c]=d')).to.deep.equal({ a: { 'b c': 'd' } }, { prototype: false });
144
+ expect(Qs.parse('a[b]=c%20d')).to.deep.equal({ a: { b: 'c d' } }, { prototype: false });
134
145
  done();
135
146
  });
136
147
 
137
148
  it('allows brackets in the value', function (done) {
138
149
 
139
- expect(Qs.parse('pets=["tobi"]')).to.deep.equal({ pets: '["tobi"]' });
140
- expect(Qs.parse('operators=[">=", "<="]')).to.deep.equal({ operators: '[">=", "<="]' });
150
+ expect(Qs.parse('pets=["tobi"]')).to.deep.equal({ pets: '["tobi"]' }, { prototype: false });
151
+ expect(Qs.parse('operators=[">=", "<="]')).to.deep.equal({ operators: '[">=", "<="]' }, { prototype: false });
141
152
  done();
142
153
  });
143
154
 
144
155
  it('allows empty values', function (done) {
145
156
 
146
- expect(Qs.parse('')).to.deep.equal({});
147
- expect(Qs.parse(null)).to.deep.equal({});
148
- expect(Qs.parse(undefined)).to.deep.equal({});
157
+ expect(Qs.parse('')).to.deep.equal({}, { prototype: false });
158
+ expect(Qs.parse(null)).to.deep.equal({}, { prototype: false });
159
+ expect(Qs.parse(undefined)).to.deep.equal({}, { prototype: false });
149
160
  done();
150
161
  });
151
162
 
152
163
  it('transforms arrays to objects', function (done) {
153
164
 
154
- expect(Qs.parse('foo[0]=bar&foo[bad]=baz')).to.deep.equal({ foo: { '0': 'bar', bad: 'baz' } });
155
- expect(Qs.parse('foo[bad]=baz&foo[0]=bar')).to.deep.equal({ foo: { bad: 'baz', '0': 'bar' } });
156
- expect(Qs.parse('foo[bad]=baz&foo[]=bar')).to.deep.equal({ foo: { bad: 'baz', '0': 'bar' } });
157
- expect(Qs.parse('foo[]=bar&foo[bad]=baz')).to.deep.equal({ foo: { '0': 'bar', bad: 'baz' } });
158
- expect(Qs.parse('foo[bad]=baz&foo[]=bar&foo[]=foo')).to.deep.equal({ foo: { bad: 'baz', '0': 'bar', '1': 'foo' } });
159
- expect(Qs.parse('foo[0][a]=a&foo[0][b]=b&foo[1][a]=aa&foo[1][b]=bb')).to.deep.equal({foo: [ {a: 'a', b: 'b'}, {a: 'aa', b: 'bb'} ]});
165
+ expect(Qs.parse('foo[0]=bar&foo[bad]=baz')).to.deep.equal({ foo: { '0': 'bar', bad: 'baz' } }, { prototype: false });
166
+ expect(Qs.parse('foo[bad]=baz&foo[0]=bar')).to.deep.equal({ foo: { bad: 'baz', '0': 'bar' } }, { prototype: false });
167
+ expect(Qs.parse('foo[bad]=baz&foo[]=bar')).to.deep.equal({ foo: { bad: 'baz', '0': 'bar' } }, { prototype: false });
168
+ expect(Qs.parse('foo[]=bar&foo[bad]=baz')).to.deep.equal({ foo: { '0': 'bar', bad: 'baz' } }, { prototype: false });
169
+ expect(Qs.parse('foo[bad]=baz&foo[]=bar&foo[]=foo')).to.deep.equal({ foo: { bad: 'baz', '0': 'bar', '1': 'foo' } }, { prototype: false });
170
+ expect(Qs.parse('foo[0][a]=a&foo[0][b]=b&foo[1][a]=aa&foo[1][b]=bb')).to.deep.equal({foo: [ {a: 'a', b: 'b'}, {a: 'aa', b: 'bb'} ]}, { prototype: false });
171
+ expect(Qs.parse('a[]=b&a[t]=u&a[hasOwnProperty]=c')).to.deep.equal({ a: { '0': 'b', t: 'u', hasOwnProperty: 'c' } }, { prototype: false });
172
+ expect(Qs.parse('a[]=b&a[hasOwnProperty]=c&a[x]=y')).to.deep.equal({ a: { '0': 'b', hasOwnProperty: 'c', x: 'y' } }, { prototype: false });
160
173
  done();
161
174
  });
162
175
 
163
- it('can add keys to objects', function (done) {
176
+ it('transforms arrays to objects (dot notation)', function (done) {
164
177
 
165
- expect(Qs.parse('a[b]=c&a=d')).to.deep.equal({ a: { b: 'c', d: true } });
178
+ expect(Qs.parse('foo[0].baz=bar&fool.bad=baz')).to.deep.equal({ foo: [ { baz: 'bar'} ], fool: { bad: 'baz' } }, { prototype: false });
179
+ expect(Qs.parse('foo[0].baz=bar&fool.bad.boo=baz')).to.deep.equal({ foo: [ { baz: 'bar'} ], fool: { bad: { boo: 'baz' } } }, { prototype: false });
180
+ expect(Qs.parse('foo[0][0].baz=bar&fool.bad=baz')).to.deep.equal({ foo: [[ { baz: 'bar'} ]], fool: { bad: 'baz' } }, { prototype: false });
181
+ expect(Qs.parse('foo[0].baz[0]=15&foo[0].bar=2')).to.deep.equal({ foo: [{ baz: ['15'], bar: '2' }] }, { prototype: false });
182
+ expect(Qs.parse('foo[0].baz[0]=15&foo[0].baz[1]=16&foo[0].bar=2')).to.deep.equal({ foo: [{ baz: ['15', '16'], bar: '2' }] }, { prototype: false });
183
+ expect(Qs.parse('foo.bad=baz&foo[0]=bar')).to.deep.equal({ foo: { bad: 'baz', '0': 'bar' } }, { prototype: false });
184
+ expect(Qs.parse('foo.bad=baz&foo[]=bar')).to.deep.equal({ foo: { bad: 'baz', '0': 'bar' } }, { prototype: false });
185
+ expect(Qs.parse('foo[]=bar&foo.bad=baz')).to.deep.equal({ foo: { '0': 'bar', bad: 'baz' } }, { prototype: false });
186
+ expect(Qs.parse('foo.bad=baz&foo[]=bar&foo[]=foo')).to.deep.equal({ foo: { bad: 'baz', '0': 'bar', '1': 'foo' } }, { prototype: false });
187
+ expect(Qs.parse('foo[0].a=a&foo[0].b=b&foo[1].a=aa&foo[1].b=bb')).to.deep.equal({foo: [ {a: 'a', b: 'b'}, {a: 'aa', b: 'bb'} ]}, { prototype: false });
166
188
  done();
167
189
  });
168
190
 
169
- it('correctly prunes undefined values when converting an array to an object', function (done) {
191
+ it('can add keys to objects', function (done) {
170
192
 
171
- expect(Qs.parse('a[2]=b&a[99999999]=c')).to.deep.equal({ a: { '2': 'b', '99999999': 'c' } });
193
+ expect(Qs.parse('a[b]=c&a=d')).to.deep.equal({ a: { b: 'c', d: true } }, { prototype: false });
172
194
  done();
173
195
  });
174
196
 
175
- it('supports malformed uri characters', function (done) {
197
+ it('correctly prunes undefined values when converting an array to an object', function (done) {
176
198
 
177
- expect(Qs.parse('{%:%}')).to.deep.equal({ '{%:%}': '' });
178
- expect(Qs.parse('foo=%:%}')).to.deep.equal({ foo: '%:%}' });
199
+ expect(Qs.parse('a[2]=b&a[99999999]=c')).to.deep.equal({ a: { '2': 'b', '99999999': 'c' } }, { prototype: false });
179
200
  done();
180
201
  });
181
202
 
182
- it('doesn\'t produce empty keys', function (done) {
203
+ it('supports malformed uri characters', function (done) {
183
204
 
184
- expect(Qs.parse('_r=1&')).to.deep.equal({ '_r': '1' });
205
+ expect(Qs.parse('{%:%}', {strictNullHandling: true})).to.deep.equal({ '{%:%}': null }, { prototype: false });
206
+ expect(Qs.parse('{%:%}=')).to.deep.equal({ '{%:%}': '' }, { prototype: false });
207
+ expect(Qs.parse('foo=%:%}')).to.deep.equal({ foo: '%:%}' }, { prototype: false });
185
208
  done();
186
209
  });
187
210
 
188
- it('cannot override prototypes', function (done) {
211
+ it('doesn\'t produce empty keys', function (done) {
189
212
 
190
- var obj = Qs.parse('toString=bad&bad[toString]=bad&constructor=bad');
191
- expect(typeof obj.toString).to.equal('function');
192
- expect(typeof obj.bad.toString).to.equal('function');
193
- expect(typeof obj.constructor).to.equal('function');
213
+ expect(Qs.parse('_r=1&')).to.deep.equal({ '_r': '1' }, { prototype: false });
194
214
  done();
195
215
  });
196
216
 
@@ -204,43 +224,45 @@ describe('parse()', function () {
204
224
 
205
225
  it('parses arrays of objects', function (done) {
206
226
 
207
- expect(Qs.parse('a[][b]=c')).to.deep.equal({ a: [{ b: 'c' }] });
208
- expect(Qs.parse('a[0][b]=c')).to.deep.equal({ a: [{ b: 'c' }] });
227
+ expect(Qs.parse('a[][b]=c')).to.deep.equal({ a: [{ b: 'c' }] }, { prototype: false });
228
+ expect(Qs.parse('a[0][b]=c')).to.deep.equal({ a: [{ b: 'c' }] }, { prototype: false });
209
229
  done();
210
230
  });
211
231
 
212
232
  it('allows for empty strings in arrays', function (done) {
213
233
 
214
- expect(Qs.parse('a[]=b&a[]=&a[]=c')).to.deep.equal({ a: ['b', '', 'c'] });
215
- expect(Qs.parse('a[0]=b&a[1]=&a[2]=c&a[19]=')).to.deep.equal({ a: ['b', '', 'c', ''] });
216
- expect(Qs.parse('a[]=&a[]=b&a[]=c')).to.deep.equal({ a: ['', 'b', 'c'] });
234
+ expect(Qs.parse('a[]=b&a[]=&a[]=c')).to.deep.equal({ a: ['b', '', 'c'] }, { prototype: false });
235
+ expect(Qs.parse('a[0]=b&a[1]&a[2]=c&a[19]=', {strictNullHandling: true})).to.deep.equal({ a: ['b', null, 'c', ''] }, { prototype: false });
236
+ expect(Qs.parse('a[0]=b&a[1]=&a[2]=c&a[19]', {strictNullHandling: true})).to.deep.equal({ a: ['b', '', 'c', null] }, { prototype: false });
237
+ expect(Qs.parse('a[]=&a[]=b&a[]=c')).to.deep.equal({ a: ['', 'b', 'c'] }, { prototype: false });
217
238
  done();
218
239
  });
219
240
 
220
241
  it('compacts sparse arrays', function (done) {
221
242
 
222
- expect(Qs.parse('a[10]=1&a[2]=2')).to.deep.equal({ a: ['2', '1'] });
243
+ expect(Qs.parse('a[10]=1&a[2]=2')).to.deep.equal({ a: ['2', '1'] }, { prototype: false });
223
244
  done();
224
245
  });
225
246
 
226
247
  it('parses semi-parsed strings', function (done) {
227
248
 
228
- expect(Qs.parse({ 'a[b]': 'c' })).to.deep.equal({ a: { b: 'c' } });
229
- expect(Qs.parse({ 'a[b]': 'c', 'a[d]': 'e' })).to.deep.equal({ a: { b: 'c', d: 'e' } });
249
+ expect(Qs.parse({ 'a[b]': 'c' })).to.deep.equal({ a: { b: 'c' } }, { prototype: false });
250
+ expect(Qs.parse({ 'a[b]': 'c', 'a[d]': 'e' })).to.deep.equal({ a: { b: 'c', d: 'e' } }, { prototype: false });
230
251
  done();
231
252
  });
232
253
 
233
254
  it('parses buffers correctly', function (done) {
234
255
 
235
256
  var b = new Buffer('test');
236
- expect(Qs.parse({ a: b })).to.deep.equal({ a: b });
257
+ expect(Qs.parse({ a: b })).to.deep.equal({ a: b }, { prototype: false });
237
258
  done();
238
259
  });
239
260
 
240
261
  it('continues parsing when no parent is found', function (done) {
241
262
 
242
- expect(Qs.parse('[]&a=b')).to.deep.equal({ '0': '', a: 'b' });
243
- expect(Qs.parse('[foo]=bar')).to.deep.equal({ foo: 'bar' });
263
+ expect(Qs.parse('[]=&a=b')).to.deep.equal({ '0': '', a: 'b' }, { prototype: false });
264
+ expect(Qs.parse('[]&a=b', {strictNullHandling: true})).to.deep.equal({ '0': null, a: 'b' }, { prototype: false });
265
+ expect(Qs.parse('[foo]=bar')).to.deep.equal({ foo: 'bar' }, { prototype: false });
244
266
  done();
245
267
  });
246
268
 
@@ -264,9 +286,9 @@ describe('parse()', function () {
264
286
  Object.prototype.crash = '';
265
287
  Array.prototype.crash = '';
266
288
  expect(Qs.parse.bind(null, 'a=b')).to.not.throw();
267
- expect(Qs.parse('a=b')).to.deep.equal({ a: 'b' });
289
+ expect(Qs.parse('a=b')).to.deep.equal({ a: 'b' }, { prototype: false });
268
290
  expect(Qs.parse.bind(null, 'a[][b]=c')).to.not.throw();
269
- expect(Qs.parse('a[][b]=c')).to.deep.equal({ a: [{ b: 'c' }] });
291
+ expect(Qs.parse('a[][b]=c')).to.deep.equal({ a: [{ b: 'c' }] }, { prototype: false });
270
292
  delete Object.prototype.crash;
271
293
  delete Array.prototype.crash;
272
294
  done();
@@ -274,39 +296,45 @@ describe('parse()', function () {
274
296
 
275
297
  it('parses a string with an alternative string delimiter', function (done) {
276
298
 
277
- expect(Qs.parse('a=b;c=d', { delimiter: ';' })).to.deep.equal({ a: 'b', c: 'd' });
299
+ expect(Qs.parse('a=b;c=d', { delimiter: ';' })).to.deep.equal({ a: 'b', c: 'd' }, { prototype: false });
278
300
  done();
279
301
  });
280
302
 
281
303
  it('parses a string with an alternative RegExp delimiter', function (done) {
282
304
 
283
- expect(Qs.parse('a=b; c=d', { delimiter: /[;,] */ })).to.deep.equal({ a: 'b', c: 'd' });
305
+ expect(Qs.parse('a=b; c=d', { delimiter: /[;,] */ })).to.deep.equal({ a: 'b', c: 'd' }, { prototype: false });
284
306
  done();
285
307
  });
286
308
 
287
309
  it('does not use non-splittable objects as delimiters', function (done) {
288
310
 
289
- expect(Qs.parse('a=b&c=d', { delimiter: true })).to.deep.equal({ a: 'b', c: 'd' });
311
+ expect(Qs.parse('a=b&c=d', { delimiter: true })).to.deep.equal({ a: 'b', c: 'd' }, { prototype: false });
290
312
  done();
291
313
  });
292
314
 
293
315
  it('allows overriding parameter limit', function (done) {
294
316
 
295
- expect(Qs.parse('a=b&c=d', { parameterLimit: 1 })).to.deep.equal({ a: 'b' });
317
+ expect(Qs.parse('a=b&c=d', { parameterLimit: 1 })).to.deep.equal({ a: 'b' }, { prototype: false });
296
318
  done();
297
319
  });
298
320
 
299
321
  it('allows setting the parameter limit to Infinity', function (done) {
300
322
 
301
- expect(Qs.parse('a=b&c=d', { parameterLimit: Infinity })).to.deep.equal({ a: 'b', c: 'd' });
323
+ expect(Qs.parse('a=b&c=d', { parameterLimit: Infinity })).to.deep.equal({ a: 'b', c: 'd' }, { prototype: false });
302
324
  done();
303
325
  });
304
326
 
305
327
  it('allows overriding array limit', function (done) {
306
328
 
307
- expect(Qs.parse('a[0]=b', { arrayLimit: -1 })).to.deep.equal({ a: { '0': 'b' } });
308
- expect(Qs.parse('a[-1]=b', { arrayLimit: -1 })).to.deep.equal({ a: { '-1': 'b' } });
309
- expect(Qs.parse('a[0]=b&a[1]=c', { arrayLimit: 0 })).to.deep.equal({ a: { '0': 'b', '1': 'c' } });
329
+ expect(Qs.parse('a[0]=b', { arrayLimit: -1 })).to.deep.equal({ a: { '0': 'b' } }, { prototype: false });
330
+ expect(Qs.parse('a[-1]=b', { arrayLimit: -1 })).to.deep.equal({ a: { '-1': 'b' } }, { prototype: false });
331
+ expect(Qs.parse('a[0]=b&a[1]=c', { arrayLimit: 0 })).to.deep.equal({ a: { '0': 'b', '1': 'c' } }, { prototype: false });
332
+ done();
333
+ });
334
+
335
+ it('allows disabling array parsing', function (done) {
336
+
337
+ expect(Qs.parse('a[0]=b&a[1]=c', { parseArrays: false })).to.deep.equal({ a: { '0': 'b', '1': 'c' } }, { prototype: false });
310
338
  done();
311
339
  });
312
340
 
@@ -326,7 +354,27 @@ describe('parse()', function () {
326
354
 
327
355
  var result = Qs.parse(input);
328
356
 
329
- expect(result).to.deep.equal(expected);
357
+ expect(result).to.deep.equal(expected, { prototype: false });
358
+ done();
359
+ });
360
+
361
+ it('parses an object in dot notation', function (done) {
362
+
363
+ var input = {
364
+ 'user.name': {'pop[bob]': 3},
365
+ 'user.email.': null
366
+ };
367
+
368
+ var expected = {
369
+ 'user': {
370
+ 'name': {'pop[bob]': 3},
371
+ 'email': null
372
+ }
373
+ };
374
+
375
+ var result = Qs.parse(input);
376
+
377
+ expect(result).to.deep.equal(expected, { prototype: false });
330
378
  done();
331
379
  });
332
380
 
@@ -346,7 +394,7 @@ describe('parse()', function () {
346
394
 
347
395
  var result = Qs.parse(input);
348
396
 
349
- expect(result).to.deep.equal(expected);
397
+ expect(result).to.deep.equal(expected, { prototype: false });
350
398
  done();
351
399
  });
352
400
 
@@ -356,13 +404,7 @@ describe('parse()', function () {
356
404
  delete global.Buffer;
357
405
  var result = Qs.parse('a=b&c=d');
358
406
  global.Buffer = tempBuffer;
359
- expect(result).to.deep.equal({ a: 'b', c: 'd' });
360
- done();
361
- });
362
-
363
- it('does not crash when using invalid dot notation', function (done) {
364
-
365
- expect(Qs.parse('roomInfoList[0].childrenAges[0]=15&roomInfoList[0].numberOfAdults=2')).to.deep.equal({ roomInfoList: [['15', '2']] });
407
+ expect(result).to.deep.equal({ a: 'b', c: 'd' }, { prototype: false });
366
408
  done();
367
409
  });
368
410
 
@@ -381,7 +423,7 @@ describe('parse()', function () {
381
423
  expect(parsed).to.contain('foo');
382
424
  expect(parsed.foo).to.contain('bar', 'baz');
383
425
  expect(parsed.foo.bar).to.equal('baz');
384
- expect(parsed.foo.baz).to.deep.equal(a);
426
+ expect(parsed.foo.baz).to.deep.equal(a, { prototype: false });
385
427
  done();
386
428
  });
387
429
 
@@ -390,24 +432,24 @@ describe('parse()', function () {
390
432
  var a = Object.create(null);
391
433
  a.b = 'c';
392
434
 
393
- expect(Qs.parse(a)).to.deep.equal({ b: 'c' });
435
+ expect(Qs.parse(a)).to.deep.equal({ b: 'c' }, { prototype: false });
394
436
  var result = Qs.parse({ a: a });
395
437
  expect(result).to.contain('a');
396
- expect(result.a).to.deep.equal(a);
438
+ expect(result.a).to.deep.equal(a, { prototype: false });
397
439
  done();
398
440
  });
399
441
 
400
442
  it('parses dates correctly', function (done) {
401
443
 
402
444
  var now = new Date();
403
- expect(Qs.parse({ a: now })).to.deep.equal({ a: now });
445
+ expect(Qs.parse({ a: now })).to.deep.equal({ a: now }, { prototype: false });
404
446
  done();
405
447
  });
406
448
 
407
449
  it('parses regular expressions correctly', function (done) {
408
450
 
409
451
  var re = /^test$/;
410
- expect(Qs.parse({ a: re })).to.deep.equal({ a: re });
452
+ expect(Qs.parse({ a: re })).to.deep.equal({ a: re }, { prototype: false });
411
453
  done();
412
454
  });
413
455
  });
package/test/stringify.js CHANGED
@@ -26,6 +26,11 @@ describe('stringify()', function () {
26
26
  expect(Qs.stringify({ a: 'b' })).to.equal('a=b');
27
27
  expect(Qs.stringify({ a: 1 })).to.equal('a=1');
28
28
  expect(Qs.stringify({ a: 1, b: 2 })).to.equal('a=1&b=2');
29
+ expect(Qs.stringify({ a: 'A_Z' })).to.equal('a=A_Z');
30
+ expect(Qs.stringify({ a: '€' })).to.equal('a=%E2%82%AC');
31
+ expect(Qs.stringify({ a: '' })).to.equal('a=%EE%80%80');
32
+ expect(Qs.stringify({ a: 'א' })).to.equal('a=%D7%90');
33
+ expect(Qs.stringify({ a: '𐐷' })).to.equal('a=%F0%90%90%B7');
29
34
  done();
30
35
  });
31
36
 
@@ -106,9 +111,15 @@ describe('stringify()', function () {
106
111
  it('stringifies an empty value', function (done) {
107
112
 
108
113
  expect(Qs.stringify({ a: '' })).to.equal('a=');
114
+ expect(Qs.stringify({ a: null }, {strictNullHandling: true})).to.equal('a');
115
+
109
116
  expect(Qs.stringify({ a: '', b: '' })).to.equal('a=&b=');
110
- expect(Qs.stringify({ a: null })).to.equal('a=');
111
- expect(Qs.stringify({ a: { b: null } })).to.equal('a%5Bb%5D=');
117
+ expect(Qs.stringify({ a: null, b: '' }, {strictNullHandling: true})).to.equal('a&b=');
118
+
119
+ expect(Qs.stringify({ a: { b: '' } })).to.equal('a%5Bb%5D=');
120
+ expect(Qs.stringify({ a: { b: null } }, {strictNullHandling: true})).to.equal('a%5Bb%5D');
121
+ expect(Qs.stringify({ a: { b: null } }, {strictNullHandling: false})).to.equal('a%5Bb%5D=');
122
+
112
123
  done();
113
124
  });
114
125
 
@@ -143,7 +154,10 @@ describe('stringify()', function () {
143
154
  it('drops keys with a value of undefined', function (done) {
144
155
 
145
156
  expect(Qs.stringify({ a: undefined })).to.equal('');
146
- expect(Qs.stringify({ a: { b: undefined, c: null } })).to.equal('a%5Bc%5D=');
157
+
158
+ expect(Qs.stringify({ a: { b: undefined, c: null } }, {strictNullHandling: true})).to.equal('a%5Bc%5D');
159
+ expect(Qs.stringify({ a: { b: undefined, c: null } }, {strictNullHandling: false})).to.equal('a%5Bc%5D=');
160
+ expect(Qs.stringify({ a: { b: undefined, c: '' } })).to.equal('a%5Bc%5D=');
147
161
  done();
148
162
  });
149
163
 
@@ -163,7 +177,7 @@ describe('stringify()', function () {
163
177
 
164
178
  it('stringifies the weird object from qs', function (done) {
165
179
 
166
- 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');
180
+ expect(Qs.stringify({ 'my weird field': '~q1!2"\'w$5&7/z8)?' })).to.equal('my%20weird%20field=~q1%212%22%27w%245%267%2Fz8%29%3F');
167
181
  done();
168
182
  });
169
183
 
@@ -206,4 +220,40 @@ describe('stringify()', function () {
206
220
  global.Buffer = tempBuffer;
207
221
  done();
208
222
  });
223
+
224
+ it('selects properties when filter=array', function (done) {
225
+
226
+ expect(Qs.stringify({ a: 'b' }, { filter: ['a'] })).to.equal('a=b');
227
+ expect(Qs.stringify({ a: 1}, { filter: [] })).to.equal('');
228
+ expect(Qs.stringify({ a: { b: [1, 2, 3, 4], c: 'd' }, c: 'f' }, { filter: ['a', 'b', 0, 2]})).to.equal('a%5Bb%5D%5B0%5D=1&a%5Bb%5D%5B2%5D=3');
229
+ done();
230
+
231
+ });
232
+
233
+ it('supports custom representations when filter=function', function (done) {
234
+
235
+ var calls = 0;
236
+ var obj = { a: 'b', c: 'd', e: { f: new Date(1257894000000) } };
237
+ var filterFunc = function (prefix, value) {
238
+
239
+ calls++;
240
+ if (calls === 1) {
241
+ expect(prefix).to.be.empty();
242
+ expect(value).to.equal(obj);
243
+ }
244
+ else if (prefix === 'c') {
245
+ return;
246
+ }
247
+ else if (value instanceof Date) {
248
+ expect(prefix).to.equal('e[f]');
249
+ return value.getTime();
250
+ }
251
+ return value;
252
+ };
253
+
254
+ expect(Qs.stringify(obj, { filter: filterFunc })).to.equal('a=b&e%5Bf%5D=1257894000000');
255
+ expect(calls).to.equal(5);
256
+ done();
257
+
258
+ });
209
259
  });
package/test/utils.js ADDED
@@ -0,0 +1,28 @@
1
+ // Load modules
2
+
3
+ var Code = require('code');
4
+ var Lab = require('lab');
5
+ var Utils = require('../lib/utils');
6
+
7
+
8
+ // Declare internals
9
+
10
+ var internals = {};
11
+
12
+
13
+ // Test shortcuts
14
+
15
+ var lab = exports.lab = Lab.script();
16
+ var expect = Code.expect;
17
+ var describe = lab.experiment;
18
+ var it = lab.test;
19
+
20
+
21
+ describe('merge()', function () {
22
+
23
+ it('can merge two objects with the same key', function (done) {
24
+
25
+ expect(Utils.merge({ a: 'b' }, { a: 'c' })).to.deep.equal({ a: ['b', 'c'] });
26
+ done();
27
+ });
28
+ });