qs 1.0.0 → 1.2.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/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,48 @@ 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'
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 === 'undefined' ? internals.delimiter : 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);
package/lib/stringify.js CHANGED
@@ -3,26 +3,35 @@
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) {
10
12
 
11
- if (typeof obj === 'string' ||
12
- typeof obj === 'number') {
13
-
14
- return [prefix + '=' + encodeURIComponent(obj)];
13
+ if (Buffer.isBuffer(obj)) {
14
+ obj = obj.toString();
15
+ }
16
+ else if (obj instanceof Date) {
17
+ obj = obj.toISOString();
18
+ }
19
+ else if (obj === null) {
20
+ obj = '';
15
21
  }
16
22
 
17
- if (obj === null) {
18
- return [prefix];
23
+ if (typeof obj === 'string' ||
24
+ typeof obj === 'number' ||
25
+ typeof obj === 'boolean') {
26
+
27
+ return [encodeURIComponent(prefix) + '=' + encodeURIComponent(obj)];
19
28
  }
20
29
 
21
30
  var values = [];
22
31
 
23
32
  for (var key in obj) {
24
33
  if (obj.hasOwnProperty(key)) {
25
- values = values.concat(internals.stringify(obj[key], prefix + '[' + encodeURIComponent(key) + ']'));
34
+ values = values.concat(internals.stringify(obj[key], prefix + '[' + key + ']'));
26
35
  }
27
36
  }
28
37
 
@@ -30,16 +39,17 @@ internals.stringify = function (obj, prefix) {
30
39
  };
31
40
 
32
41
 
33
- module.exports = function (obj) {
42
+ module.exports = function (obj, delimiter) {
43
+
44
+ delimiter = typeof delimiter === 'undefined' ? internals.delimiter : delimiter;
34
45
 
35
46
  var keys = [];
36
- var value = JSON.parse(JSON.stringify(obj));
37
47
 
38
- for (var key in value) {
39
- if (value.hasOwnProperty(key)) {
40
- keys = keys.concat(internals.stringify(value[key], encodeURIComponent(key)));
48
+ for (var key in obj) {
49
+ if (obj.hasOwnProperty(key)) {
50
+ keys = keys.concat(internals.stringify(obj[key], key));
41
51
  }
42
52
  }
43
53
 
44
- return keys.join('&');
54
+ return keys.join(delimiter);
45
55
  };
package/lib/utils.js CHANGED
@@ -10,7 +10,10 @@ exports.arrayToObject = function (source) {
10
10
 
11
11
  var obj = {};
12
12
  for (var i = 0, il = source.length; i < il; ++i) {
13
- obj[i] = source[i];
13
+ if (typeof source[i] !== 'undefined') {
14
+
15
+ obj[i] = source[i];
16
+ }
14
17
  }
15
18
 
16
19
  return obj;
@@ -50,8 +53,13 @@ exports.merge = function (target, source) {
50
53
 
51
54
  if (Array.isArray(source)) {
52
55
  for (var i = 0, il = source.length; i < il; ++i) {
53
- if (source[i] !== undefined) {
54
- 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
+ }
55
63
  }
56
64
  }
57
65
 
@@ -110,9 +118,7 @@ exports.compact = function (obj) {
110
118
  compacted[key] = [];
111
119
 
112
120
  for (var i = 0, l = obj[key].length; i < l; i++) {
113
- if (obj[key].hasOwnProperty(i) &&
114
- obj[key][i]) {
115
-
121
+ if (typeof obj[key][i] !== 'undefined') {
116
122
  compacted[key].push(obj[key][i]);
117
123
  }
118
124
  }
package/package.json CHANGED
@@ -1,11 +1,10 @@
1
1
  {
2
2
  "name": "qs",
3
- "version": "1.0.0",
3
+ "version": "1.2.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
- "dependencies": {
8
- },
7
+ "dependencies": {},
9
8
  "devDependencies": {
10
9
  "lab": "3.x.x"
11
10
  },
package/test/parse.js CHANGED
@@ -138,6 +138,13 @@ 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'} ]});
142
+ done();
143
+ });
144
+
145
+ it('correctly prunes undefined values when converting an array to an object', function (done) {
146
+
147
+ expect(Qs.parse('a[2]=b&a[99999999]=c')).to.deep.equal({ a: { '2': 'b', '99999999': 'c' } });
141
148
  done();
142
149
  });
143
150
 
@@ -178,6 +185,13 @@ describe('#parse', function () {
178
185
  done();
179
186
  });
180
187
 
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
+
181
195
  it('should compact sparse arrays', function (done) {
182
196
 
183
197
  expect(Qs.parse('a[10]=1&a[2]=2')).to.deep.equal({ a: ['2', '1'] });
@@ -223,8 +237,19 @@ describe('#parse', function () {
223
237
  it('should not throw when a native prototype has an enumerable property', { parallel: false }, function (done) {
224
238
 
225
239
  Object.prototype.crash = '';
226
- 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' }] });
227
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' });
228
253
  done();
229
254
  });
230
255
  });
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,8 +100,30 @@ 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
  });
107
+
108
+ it('stringifies boolean values', function (done) {
109
+
110
+ expect(Qs.stringify({ a: true })).to.equal('a=true');
111
+ expect(Qs.stringify({ a: { b: true } })).to.equal('a%5Bb%5D=true');
112
+ expect(Qs.stringify({ b: false })).to.equal('b=false');
113
+ expect(Qs.stringify({ b: { c: false } })).to.equal('b%5Bc%5D=false');
114
+ done();
115
+ });
116
+
117
+ it('stringifies buffer values', function (done) {
118
+
119
+ expect(Qs.stringify({ a: new Buffer('test') })).to.equal('a=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');
127
+ done();
128
+ });
107
129
  });