qs 1.0.2 → 1.2.2

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.
@@ -0,0 +1 @@
1
+ Please view our [hapijs contributing guide](https://github.com/hapijs/hapi/blob/master/CONTRIBUTING.md).
package/README.md CHANGED
@@ -6,7 +6,7 @@ A querystring parsing and stringifying library with some added security.
6
6
 
7
7
  Lead Maintainer: [Nathan LaFreniere](https://github.com/nlf)
8
8
 
9
- The **qs** module was original created and maintained by [TJ Holowaychuk](https://github.com/visionmedia/node-querystring).
9
+ The **qs** module was originally created and maintained by [TJ Holowaychuk](https://github.com/visionmedia/node-querystring).
10
10
 
11
11
  ## Usage
12
12
 
@@ -17,7 +17,11 @@ var obj = Qs.parse('a=c'); // { a: 'c' }
17
17
  var str = Qs.stringify(obj); // 'a=c'
18
18
  ```
19
19
 
20
- ### Objects
20
+ ### Parsing Objects
21
+
22
+ ```javascript
23
+ Qs.parse(string, [depth], [delimiter]);
24
+ ```
21
25
 
22
26
  **qs** allows you to create nested objects within your query strings, by surrounding the name of sub-keys with square brackets `[]`.
23
27
  For example, the string `'foo[bar]=baz'` converts to:
@@ -30,6 +34,13 @@ For example, the string `'foo[bar]=baz'` converts to:
30
34
  }
31
35
  ```
32
36
 
37
+ URI encoded strings work too:
38
+
39
+ ```javascript
40
+ Qs.parse('a%5Bb%5D=c');
41
+ // { a: { b: 'c' } }
42
+ ```
43
+
33
44
  You can also nest your objects, like `'foo[bar][baz]=foobarbaz'`:
34
45
 
35
46
  ```javascript
@@ -72,7 +83,14 @@ Qs.parse('a[b][c][d][e][f][g][h][i]=j', 1);
72
83
 
73
84
  The depth limit mitigate abuse when **qs** is used to parse user input, and it is recommended to keep it a reasonably small number.
74
85
 
75
- ### Arrays
86
+ An optional delimiter can also be passed:
87
+
88
+ ```javascript
89
+ Qs.parse('a=b;c=d', ';');
90
+ // { a: 'b', c: 'd' }
91
+ ```
92
+
93
+ ### Parsing Arrays
76
94
 
77
95
  **qs** can also parse arrays using a similar `[]` notation:
78
96
 
@@ -97,6 +115,15 @@ Qs.parse('a[1]=b&a[15]=c');
97
115
  // { a: ['b', 'c'] }
98
116
  ```
99
117
 
118
+ Note that an empty string is also a value, and will be preserved:
119
+
120
+ ```javascript
121
+ Qs.parse('a[]=&a[]=b');
122
+ // { a: ['', 'b'] }
123
+ Qs.parse('a[0]=b&a[1]=&a[2]=c');
124
+ // { a: ['b', '', 'c'] }
125
+ ```
126
+
100
127
  **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
101
128
  instead be converted to an object with the index as the key:
102
129
 
@@ -117,4 +144,49 @@ You can also create arrays of objects:
117
144
  ```javascript
118
145
  Qs.parse('a[][b]=c');
119
146
  // { a: [{ b: 'c' }] }
120
- ```
147
+ ```
148
+
149
+ ### Stringifying
150
+
151
+ ```javascript
152
+ Qs.stringify(object, [delimiter]);
153
+ ```
154
+
155
+ When stringifying, **qs** always URI encodes output. Objects are stringified as you would expect:
156
+
157
+ ```javascript
158
+ Qs.stringify({ a: 'b' });
159
+ // 'a=b'
160
+ Qs.stringify({ a: { b: 'c' } });
161
+ // 'a%5Bb%5D=c'
162
+ ```
163
+
164
+ Examples beyond this point will be shown as though the output is not URI encoded for clarity. Please note that the return values in these cases *will* be URI encoded during real usage.
165
+
166
+ When arrays are stringified, they are always given explicit indices:
167
+
168
+ ```javascript
169
+ Qs.stringify({ a: ['b', 'c', 'd'] });
170
+ // 'a[0]=b&a[1]=c&a[2]=d'
171
+ ```
172
+
173
+ Empty strings and null values will omit the value, but the equals sign (=) remains in place:
174
+
175
+ ```javascript
176
+ Qs.stringify({ a: '' });
177
+ // 'a='
178
+ ```
179
+
180
+ Properties that are set to `undefined` will be omitted entirely:
181
+
182
+ ```javascript
183
+ Qs.stringify({ a: null, b: undefined });
184
+ // 'a='
185
+ ```
186
+
187
+ The delimiter may be overridden with stringify as well:
188
+
189
+ ```javascript
190
+ Qs.stringify({ a: 'b', c: 'd' }, ';');
191
+ // 'a=b;c=d'
192
+ ```
package/lib/parse.js CHANGED
@@ -6,16 +6,19 @@ var Utils = require('./utils');
6
6
  // Declare internals
7
7
 
8
8
  var internals = {
9
+ delimiter: '&',
9
10
  depth: 5,
10
11
  arrayLimit: 20,
11
12
  parametersLimit: 1000
12
13
  };
13
14
 
14
15
 
15
- internals.parseValues = function (str) {
16
+ internals.parseValues = function (str, delimiter) {
17
+
18
+ delimiter = typeof delimiter === 'string' ? delimiter : internals.delimiter;
16
19
 
17
20
  var obj = {};
18
- var parts = str.split('&').slice(0, internals.parametersLimit);
21
+ var parts = str.split(delimiter, internals.parametersLimit);
19
22
 
20
23
  for (var i = 0, il = parts.length; i < il; ++i) {
21
24
  var part = parts[i];
@@ -79,8 +82,6 @@ internals.parseKeys = function (key, val, depth) {
79
82
  return;
80
83
  }
81
84
 
82
- depth = typeof depth === 'undefined' ? internals.depth : depth;
83
-
84
85
  // The regex chunks
85
86
 
86
87
  var parent = /^([^\[\]]*)/;
@@ -124,7 +125,7 @@ internals.parseKeys = function (key, val, depth) {
124
125
  };
125
126
 
126
127
 
127
- module.exports = function (str, depth) {
128
+ module.exports = function (str, depth, delimiter) {
128
129
 
129
130
  if (str === '' ||
130
131
  str === null ||
@@ -133,11 +134,16 @@ module.exports = function (str, depth) {
133
134
  return {};
134
135
  }
135
136
 
136
- var tempObj = typeof str === 'string' ? internals.parseValues(str) : Utils.clone(str);
137
+ if (typeof depth !== 'number') {
138
+ delimiter = depth;
139
+ depth = internals.depth;
140
+ }
141
+
142
+ var tempObj = typeof str === 'string' ? internals.parseValues(str, delimiter) : Utils.clone(str);
137
143
  var obj = {};
138
144
 
139
145
  // Iterate over the keys and setup the new object
140
-
146
+ //
141
147
  for (var key in tempObj) {
142
148
  if (tempObj.hasOwnProperty(key)) {
143
149
  var newObj = internals.parseKeys(key, tempObj[key], depth);
@@ -147,5 +153,3 @@ module.exports = function (str, depth) {
147
153
 
148
154
  return Utils.compact(obj);
149
155
  };
150
-
151
-
package/lib/stringify.js CHANGED
@@ -3,7 +3,9 @@
3
3
 
4
4
  // Declare internals
5
5
 
6
- var internals = {};
6
+ var internals = {
7
+ delimiter: '&'
8
+ };
7
9
 
8
10
 
9
11
  internals.stringify = function (obj, prefix) {
@@ -14,23 +16,22 @@ internals.stringify = function (obj, prefix) {
14
16
  else if (obj instanceof Date) {
15
17
  obj = obj.toISOString();
16
18
  }
19
+ else if (obj === null) {
20
+ obj = '';
21
+ }
17
22
 
18
23
  if (typeof obj === 'string' ||
19
24
  typeof obj === 'number' ||
20
25
  typeof obj === 'boolean') {
21
26
 
22
- return [prefix + '=' + encodeURIComponent(obj)];
23
- }
24
-
25
- if (obj === null) {
26
- return [prefix];
27
+ return [encodeURIComponent(prefix) + '=' + encodeURIComponent(obj)];
27
28
  }
28
29
 
29
30
  var values = [];
30
31
 
31
32
  for (var key in obj) {
32
33
  if (obj.hasOwnProperty(key)) {
33
- values = values.concat(internals.stringify(obj[key], prefix + '[' + encodeURIComponent(key) + ']'));
34
+ values = values.concat(internals.stringify(obj[key], prefix + '[' + key + ']'));
34
35
  }
35
36
  }
36
37
 
@@ -38,15 +39,17 @@ internals.stringify = function (obj, prefix) {
38
39
  };
39
40
 
40
41
 
41
- module.exports = function (obj) {
42
+ module.exports = function (obj, delimiter) {
43
+
44
+ delimiter = typeof delimiter === 'undefined' ? internals.delimiter : delimiter;
42
45
 
43
46
  var keys = [];
44
47
 
45
48
  for (var key in obj) {
46
49
  if (obj.hasOwnProperty(key)) {
47
- keys = keys.concat(internals.stringify(obj[key], encodeURIComponent(key)));
50
+ keys = keys.concat(internals.stringify(obj[key], key));
48
51
  }
49
52
  }
50
53
 
51
- return keys.join('&');
54
+ return keys.join(delimiter);
52
55
  };
package/lib/utils.js CHANGED
@@ -10,8 +10,7 @@ exports.arrayToObject = function (source) {
10
10
 
11
11
  var obj = {};
12
12
  for (var i = 0, il = source.length; i < il; ++i) {
13
- if (source[i] !== undefined &&
14
- source[i] !== null) {
13
+ if (typeof source[i] !== 'undefined') {
15
14
 
16
15
  obj[i] = source[i];
17
16
  }
@@ -54,8 +53,13 @@ exports.merge = function (target, source) {
54
53
 
55
54
  if (Array.isArray(source)) {
56
55
  for (var i = 0, il = source.length; i < il; ++i) {
57
- if (source[i] !== undefined) {
58
- obj[i] = source[i];
56
+ if (typeof source[i] !== 'undefined') {
57
+ if (typeof obj[i] === 'object') {
58
+ obj[i] = exports.merge(obj[i], source[i]);
59
+ }
60
+ else {
61
+ obj[i] = source[i];
62
+ }
59
63
  }
60
64
  }
61
65
 
@@ -102,7 +106,7 @@ exports.decode = function (str) {
102
106
 
103
107
  exports.compact = function (obj) {
104
108
 
105
- if (typeof obj !== 'object') {
109
+ if (typeof obj !== 'object' || obj === null) {
106
110
  return obj;
107
111
  }
108
112
 
@@ -114,9 +118,7 @@ exports.compact = function (obj) {
114
118
  compacted[key] = [];
115
119
 
116
120
  for (var i = 0, l = obj[key].length; i < l; i++) {
117
- if (obj[key].hasOwnProperty(i) &&
118
- obj[key][i]) {
119
-
121
+ if (typeof obj[key][i] !== 'undefined') {
120
122
  compacted[key].push(obj[key][i]);
121
123
  }
122
124
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "qs",
3
- "version": "1.0.2",
3
+ "version": "1.2.2",
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",
package/test/parse.js CHANGED
@@ -138,6 +138,7 @@ describe('#parse', function () {
138
138
  expect(Qs.parse('foo[bad]=baz&foo[]=bar')).to.deep.equal({ foo: { bad: 'baz', '0': 'bar' } });
139
139
  expect(Qs.parse('foo[]=bar&foo[bad]=baz')).to.deep.equal({ foo: { '0': 'bar', bad: 'baz' } });
140
140
  expect(Qs.parse('foo[bad]=baz&foo[]=bar&foo[]=foo')).to.deep.equal({ foo: { bad: 'baz', '0': 'bar', '1': 'foo' } });
141
+ 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'} ]});
141
142
  done();
142
143
  });
143
144
 
@@ -184,7 +185,14 @@ describe('#parse', function () {
184
185
  done();
185
186
  });
186
187
 
187
- it('should compact sparse arrays', function (done) {
188
+ it('allows for empty strings in arrays', function (done) {
189
+
190
+ expect(Qs.parse('a[]=b&a[]=&a[]=c')).to.deep.equal({ a: ['b', '', 'c'] });
191
+ expect(Qs.parse('a[0]=b&a[1]=&a[2]=c&a[19]=')).to.deep.equal({ a: ['b', '', 'c', ''] });
192
+ done();
193
+ });
194
+
195
+ it('compacts sparse arrays', function (done) {
188
196
 
189
197
  expect(Qs.parse('a[10]=1&a[2]=2')).to.deep.equal({ a: ['2', '1'] });
190
198
  done();
@@ -229,8 +237,65 @@ describe('#parse', function () {
229
237
  it('should not throw when a native prototype has an enumerable property', { parallel: false }, function (done) {
230
238
 
231
239
  Object.prototype.crash = '';
232
- expect(Qs.parse.bind(null, 'test')).to.not.throw();
240
+ Array.prototype.crash = '';
241
+ expect(Qs.parse.bind(null, 'a=b')).to.not.throw();
242
+ expect(Qs.parse('a=b')).to.deep.equal({ a: 'b' });
243
+ expect(Qs.parse.bind(null, 'a[][b]=c')).to.not.throw();
244
+ expect(Qs.parse('a[][b]=c')).to.deep.equal({ a: [{ b: 'c' }] });
233
245
  delete Object.prototype.crash;
246
+ delete Array.prototype.crash;
247
+ done();
248
+ });
249
+
250
+ it('parses a string with an alternative delimiter', function (done) {
251
+
252
+ expect(Qs.parse('a=b;c=d', ';')).to.deep.equal({ a: 'b', c: 'd' });
253
+ done();
254
+ });
255
+
256
+ it('does not use non-string objects as delimiters', function (done) {
257
+
258
+ expect(Qs.parse('a=b&c=d', {})).to.deep.equal({ a: 'b', c: 'd' });
259
+ done();
260
+ });
261
+
262
+ it('parses an object', function (done) {
263
+
264
+ var input = {
265
+ "user[name]": {"pop[bob]": 3},
266
+ "user[email]": null
267
+ };
268
+
269
+ var expected = {
270
+ "user": {
271
+ "name": {"pop[bob]": 3},
272
+ "email": null
273
+ }
274
+ };
275
+
276
+ var result = Qs.parse(input);
277
+
278
+ expect(result).to.deep.equal(expected);
279
+ done();
280
+ });
281
+
282
+ it('parses an object and not child values', function (done) {
283
+
284
+ var input = {
285
+ "user[name]": {"pop[bob]": { "test": 3 }},
286
+ "user[email]": null
287
+ };
288
+
289
+ var expected = {
290
+ "user": {
291
+ "name": {"pop[bob]": { "test": 3 }},
292
+ "email": null
293
+ }
294
+ };
295
+
296
+ var result = Qs.parse(input);
297
+
298
+ expect(result).to.deep.equal(expected);
234
299
  done();
235
300
  });
236
301
  });
package/test/stringify.js CHANGED
@@ -30,33 +30,33 @@ describe('#stringify', function () {
30
30
 
31
31
  it('stringifies a nested object', function (done) {
32
32
 
33
- expect(Qs.stringify({ a: { b: 'c' } })).to.equal('a[b]=c');
34
- expect(Qs.stringify({ a: { b: { c: { d: 'e' } } } })).to.equal('a[b][c][d]=e');
33
+ expect(Qs.stringify({ a: { b: 'c' } })).to.equal('a%5Bb%5D=c');
34
+ expect(Qs.stringify({ a: { b: { c: { d: 'e' } } } })).to.equal('a%5Bb%5D%5Bc%5D%5Bd%5D=e');
35
35
  done();
36
36
  });
37
37
 
38
38
  it('stringifies an array value', function (done) {
39
39
 
40
- expect(Qs.stringify({ a: ['b', 'c', 'd'] })).to.equal('a[0]=b&a[1]=c&a[2]=d');
40
+ expect(Qs.stringify({ a: ['b', 'c', 'd'] })).to.equal('a%5B0%5D=b&a%5B1%5D=c&a%5B2%5D=d');
41
41
  done();
42
42
  });
43
43
 
44
44
  it('stringifies a nested array value', function (done) {
45
45
 
46
- expect(Qs.stringify({ a: { b: ['c', 'd'] } })).to.equal('a[b][0]=c&a[b][1]=d');
46
+ expect(Qs.stringify({ a: { b: ['c', 'd'] } })).to.equal('a%5Bb%5D%5B0%5D=c&a%5Bb%5D%5B1%5D=d');
47
47
  done();
48
48
  });
49
49
 
50
50
  it('stringifies an object inside an array', function (done) {
51
51
 
52
- expect(Qs.stringify({ a: [{ b: 'c' }] })).to.equal('a[0][b]=c');
53
- expect(Qs.stringify({ a: [{ b: { c: [1] } }] })).to.equal('a[0][b][c][0]=1');
52
+ expect(Qs.stringify({ a: [{ b: 'c' }] })).to.equal('a%5B0%5D%5Bb%5D=c');
53
+ expect(Qs.stringify({ a: [{ b: { c: [1] } }] })).to.equal('a%5B0%5D%5Bb%5D%5Bc%5D%5B0%5D=1');
54
54
  done();
55
55
  });
56
56
 
57
57
  it('stringifies a complicated object', function (done) {
58
58
 
59
- expect(Qs.stringify({ a: { b: 'c', d: 'e' } })).to.equal('a[b]=c&a[d]=e');
59
+ expect(Qs.stringify({ a: { b: 'c', d: 'e' } })).to.equal('a%5Bb%5D=c&a%5Bd%5D=e');
60
60
  done();
61
61
  });
62
62
 
@@ -64,15 +64,15 @@ describe('#stringify', function () {
64
64
 
65
65
  expect(Qs.stringify({ a: '' })).to.equal('a=');
66
66
  expect(Qs.stringify({ a: '', b: '' })).to.equal('a=&b=');
67
- expect(Qs.stringify({ a: null })).to.equal('a');
68
- expect(Qs.stringify({ a: { b: null } })).to.equal('a[b]');
67
+ expect(Qs.stringify({ a: null })).to.equal('a=');
68
+ expect(Qs.stringify({ a: { b: null } })).to.equal('a%5Bb%5D=');
69
69
  done();
70
70
  });
71
71
 
72
72
  it('drops keys with a value of undefined', function (done) {
73
73
 
74
74
  expect(Qs.stringify({ a: undefined })).to.equal('');
75
- expect(Qs.stringify({ a: { b: undefined, c: null } })).to.equal('a[c]');
75
+ expect(Qs.stringify({ a: { b: undefined, c: null } })).to.equal('a%5Bc%5D=');
76
76
  done();
77
77
  });
78
78
 
@@ -100,7 +100,7 @@ describe('#stringify', function () {
100
100
 
101
101
  Object.prototype.crash = 'test';
102
102
  expect(Qs.stringify({ a: 'b'})).to.equal('a=b');
103
- expect(Qs.stringify({ a: { b: 'c' } })).to.equal('a[b]=c');
103
+ expect(Qs.stringify({ a: { b: 'c' } })).to.equal('a%5Bb%5D=c');
104
104
  delete Object.prototype.crash;
105
105
  done();
106
106
  });
@@ -108,16 +108,22 @@ describe('#stringify', function () {
108
108
  it('stringifies boolean values', function (done) {
109
109
 
110
110
  expect(Qs.stringify({ a: true })).to.equal('a=true');
111
- expect(Qs.stringify({ a: { b: true } })).to.equal('a[b]=true');
111
+ expect(Qs.stringify({ a: { b: true } })).to.equal('a%5Bb%5D=true');
112
112
  expect(Qs.stringify({ b: false })).to.equal('b=false');
113
- expect(Qs.stringify({ b: { c: false } })).to.equal('b[c]=false');
113
+ expect(Qs.stringify({ b: { c: false } })).to.equal('b%5Bc%5D=false');
114
114
  done();
115
115
  });
116
116
 
117
117
  it('stringifies buffer values', function (done) {
118
118
 
119
119
  expect(Qs.stringify({ a: new Buffer('test') })).to.equal('a=test');
120
- expect(Qs.stringify({ a: { b: new Buffer('test') } })).to.equal('a[b]=test');
120
+ expect(Qs.stringify({ a: { b: new Buffer('test') } })).to.equal('a%5Bb%5D=test');
121
+ done();
122
+ });
123
+
124
+ it('stringifies an object using an alternative delimiter', function (done) {
125
+
126
+ expect(Qs.stringify({ a: 'b', c: 'd' }, ';')).to.equal('a=b;c=d');
121
127
  done();
122
128
  });
123
129
  });