qs 6.3.2 → 6.5.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/.editorconfig ADDED
@@ -0,0 +1,30 @@
1
+ root = true
2
+
3
+ [*]
4
+ indent_style = space
5
+ indent_size = 4
6
+ end_of_line = lf
7
+ charset = utf-8
8
+ trim_trailing_whitespace = true
9
+ insert_final_newline = true
10
+ max_line_length = 140
11
+
12
+ [test/*]
13
+ max_line_length = off
14
+
15
+ [*.md]
16
+ max_line_length = off
17
+
18
+ [*.json]
19
+ max_line_length = off
20
+
21
+ [Makefile]
22
+ max_line_length = off
23
+
24
+ [CHANGELOG.md]
25
+ indent_style = space
26
+ indent_size = 2
27
+
28
+ [LICENSE]
29
+ indent_size = 2
30
+ max_line_length = off
package/.eslintrc CHANGED
@@ -4,16 +4,15 @@
4
4
  "extends": "@ljharb",
5
5
 
6
6
  "rules": {
7
- "complexity": [2, 25],
7
+ "complexity": [2, 28],
8
8
  "consistent-return": 1,
9
9
  "id-length": [2, { "min": 1, "max": 25, "properties": "never" }],
10
10
  "indent": [2, 4],
11
- "max-params": [2, 11],
12
- "max-statements": [2, 42],
13
- "no-extra-parens": 1,
11
+ "max-params": [2, 12],
12
+ "max-statements": [2, 45],
14
13
  "no-continue": 1,
15
14
  "no-magic-numbers": 0,
16
15
  "no-restricted-syntax": [2, "BreakStatement", "DebuggerStatement", "ForInStatement", "LabeledStatement", "WithStatement"],
17
- "operator-linebreak": 1
16
+ "operator-linebreak": [2, "before"],
18
17
  }
19
18
  }
package/CHANGELOG.md CHANGED
@@ -1,3 +1,24 @@
1
+ ## **6.5.0**
2
+ - [New] add `utils.assign`
3
+ - [New] pass default encoder/decoder to custom encoder/decoder functions (#206)
4
+ - [New] `parse`/`stringify`: add `ignoreQueryPrefix`/`addQueryPrefix` options, respectively (#213)
5
+ - [Fix] Handle stringifying empty objects with addQueryPrefix (#217)
6
+ - [Fix] do not mutate `options` argument (#207)
7
+ - [Refactor] `parse`: cache index to reuse in else statement (#182)
8
+ - [Docs] add various badges to readme (#208)
9
+ - [Dev Deps] update `eslint`, `browserify`, `iconv-lite`, `tape`
10
+ - [Tests] up to `node` `v8.1`, `v7.10`, `v6.11`; npm v4.6 breaks on node < v1; npm v5+ breaks on node < v4
11
+ - [Tests] add `editorconfig-tools`
12
+
13
+ ## **6.4.0**
14
+ - [New] `qs.stringify`: add `encodeValuesOnly` option
15
+ - [Fix] follow `allowPrototypes` option during merge (#201, #201)
16
+ - [Fix] support keys starting with brackets (#202, #200)
17
+ - [Fix] chmod a-x
18
+ - [Dev Deps] update `eslint`
19
+ - [Tests] up to `node` `v7.7`, `v6.10`,` v4.8`; disable osx builds since they block linux builds
20
+ - [eslint] reduce warnings
21
+
1
22
  ## **6.3.2**
2
23
  - [Fix] follow `allowPrototypes` option during merge (#201, #200)
3
24
  - [Dev Deps] update `eslint`
@@ -31,6 +52,15 @@
31
52
  - [Tests] skip Object.create tests when null objects are not available
32
53
  - [Tests] Turn on eslint for test files (#175)
33
54
 
55
+ ## **6.2.3**
56
+ - [Fix] follow `allowPrototypes` option during merge (#201, #200)
57
+ - [Fix] chmod a-x
58
+ - [Fix] support keys starting with brackets (#202, #200)
59
+ - [Tests] up to `node` `v7.7`, `v6.10`,` v4.8`; disable osx builds since they block linux builds
60
+
61
+ ## **6.2.2**
62
+ - [Fix] ensure that `allowPrototypes: false` does not ever shadow Object.prototype properties
63
+
34
64
  ## **6.2.1**
35
65
  - [Fix] ensure `key[]=x&key[]&key[]=y` results in 3, not 2, values
36
66
  - [Refactor] Be explicit and use `Object.prototype.hasOwnProperty.call`
@@ -43,11 +73,30 @@
43
73
  - [New] add "encoder" and "decoder" options, for custom param encoding/decoding (#160)
44
74
  - [Fix] fix compacting of nested sparse arrays (#150)
45
75
 
76
+ ## **6.1.2
77
+ - [Fix] follow `allowPrototypes` option during merge (#201, #200)
78
+ - [Fix] chmod a-x
79
+ - [Fix] support keys starting with brackets (#202, #200)
80
+ - [Tests] up to `node` `v7.7`, `v6.10`,` v4.8`; disable osx builds since they block linux builds
81
+
82
+ ## **6.1.1**
83
+ - [Fix] ensure that `allowPrototypes: false` does not ever shadow Object.prototype properties
84
+
46
85
  ## [**6.1.0**](https://github.com/ljharb/qs/issues?milestone=35&state=closed)
47
86
  - [New] allowDots option for `stringify` (#151)
48
87
  - [Fix] "sort" option should work at a depth of 3 or more (#151)
49
88
  - [Fix] Restore `dist` directory; will be removed in v7 (#148)
50
89
 
90
+ ## **6.0.4**
91
+ - [Fix] follow `allowPrototypes` option during merge (#201, #200)
92
+ - [Fix] chmod a-x
93
+ - [Fix] support keys starting with brackets (#202, #200)
94
+ - [Tests] up to `node` `v7.7`, `v6.10`,` v4.8`; disable osx builds since they block linux builds
95
+
96
+ ## **6.0.3**
97
+ - [Fix] ensure that `allowPrototypes: false` does not ever shadow Object.prototype properties
98
+ - [Fix] Restore `dist` directory; will be removed in v7 (#148)
99
+
51
100
  ## [**6.0.2**](https://github.com/ljharb/qs/issues?milestone=33&state=closed)
52
101
  - Revert ES6 requirement and restore support for node down to v0.8.
53
102
 
package/README.md CHANGED
@@ -1,8 +1,14 @@
1
- # qs
1
+ # qs <sup>[![Version Badge][2]][1]</sup>
2
2
 
3
- A querystring parsing and stringifying library with some added security.
3
+ [![Build Status][3]][4]
4
+ [![dependency status][5]][6]
5
+ [![dev dependency status][7]][8]
6
+ [![License][license-image]][license-url]
7
+ [![Downloads][downloads-image]][downloads-url]
8
+
9
+ [![npm badge][11]][1]
4
10
 
5
- [![Build Status](https://api.travis-ci.org/ljharb/qs.svg)](http://travis-ci.org/ljharb/qs)
11
+ A querystring parsing and stringifying library with some added security.
6
12
 
7
13
  Lead Maintainer: [Jordan Harband](https://github.com/ljharb)
8
14
 
@@ -33,9 +39,9 @@ For example, the string `'foo[bar]=baz'` converts to:
33
39
 
34
40
  ```javascript
35
41
  assert.deepEqual(qs.parse('foo[bar]=baz'), {
36
- foo: {
37
- bar: 'baz'
38
- }
42
+ foo: {
43
+ bar: 'baz'
44
+ }
39
45
  });
40
46
  ```
41
47
 
@@ -57,7 +63,7 @@ URI encoded strings work too:
57
63
 
58
64
  ```javascript
59
65
  assert.deepEqual(qs.parse('a%5Bb%5D=c'), {
60
- a: { b: 'c' }
66
+ a: { b: 'c' }
61
67
  });
62
68
  ```
63
69
 
@@ -65,11 +71,11 @@ You can also nest your objects, like `'foo[bar][baz]=foobarbaz'`:
65
71
 
66
72
  ```javascript
67
73
  assert.deepEqual(qs.parse('foo[bar][baz]=foobarbaz'), {
68
- foo: {
69
- bar: {
70
- baz: 'foobarbaz'
74
+ foo: {
75
+ bar: {
76
+ baz: 'foobarbaz'
77
+ }
71
78
  }
72
- }
73
79
  });
74
80
  ```
75
81
 
@@ -78,19 +84,19 @@ By default, when nesting objects **qs** will only parse up to 5 children deep. T
78
84
 
79
85
  ```javascript
80
86
  var expected = {
81
- a: {
82
- b: {
83
- c: {
84
- d: {
85
- e: {
86
- f: {
87
- '[g][h][i]': 'j'
87
+ a: {
88
+ b: {
89
+ c: {
90
+ d: {
91
+ e: {
92
+ f: {
93
+ '[g][h][i]': 'j'
94
+ }
95
+ }
96
+ }
88
97
  }
89
- }
90
98
  }
91
- }
92
99
  }
93
- }
94
100
  };
95
101
  var string = 'a[b][c][d][e][f][g][h][i]=j';
96
102
  assert.deepEqual(qs.parse(string), expected);
@@ -112,6 +118,13 @@ var limited = qs.parse('a=b&c=d', { parameterLimit: 1 });
112
118
  assert.deepEqual(limited, { a: 'b' });
113
119
  ```
114
120
 
121
+ To bypass the leading question mark, use `ignoreQueryPrefix`:
122
+
123
+ ```javascript
124
+ var prefixed = qs.parse('?a=b&c=d', { ignoreQueryPrefix: true });
125
+ assert.deepEqual(prefixed, { a: 'b', c: 'd' });
126
+ ```
127
+
115
128
  An optional delimiter can also be passed:
116
129
 
117
130
  ```javascript
@@ -225,12 +238,21 @@ var unencoded = qs.stringify({ a: { b: 'c' } }, { encode: false });
225
238
  assert.equal(unencoded, 'a[b]=c');
226
239
  ```
227
240
 
241
+ Encoding can be disabled for keys by setting the `encodeValuesOnly` option to `true`:
242
+ ```javascript
243
+ var encodedValues = qs.stringify(
244
+ { a: 'b', c: ['d', 'e=f'], f: [['g'], ['h']] },
245
+ { encodeValuesOnly: true }
246
+ );
247
+ assert.equal(encodedValues,'a=b&c[0]=d&c[1]=e%3Df&f[0][0]=g&f[1][0]=h');
248
+ ```
249
+
228
250
  This encoding can also be replaced by a custom encoding method set as `encoder` option:
229
251
 
230
252
  ```javascript
231
253
  var encoded = qs.stringify({ a: { b: 'c' } }, { encoder: function (str) {
232
- // Passed in values `a`, `b`, `c`
233
- return // Return encoded string
254
+ // Passed in values `a`, `b`, `c`
255
+ return // Return encoded string
234
256
  }})
235
257
  ```
236
258
 
@@ -240,8 +262,8 @@ Analogue to the `encoder` there is a `decoder` option for `parse` to override de
240
262
 
241
263
  ```javascript
242
264
  var decoded = qs.parse('x=z', { decoder: function (str) {
243
- // Passed in values `x`, `z`
244
- return // Return decoded string
265
+ // Passed in values `x`, `z`
266
+ return // Return decoded string
245
267
  }})
246
268
  ```
247
269
 
@@ -308,6 +330,12 @@ Properties that are set to `undefined` will be omitted entirely:
308
330
  assert.equal(qs.stringify({ a: null, b: undefined }), 'a=');
309
331
  ```
310
332
 
333
+ The query string may optionally be prepended with a question mark:
334
+
335
+ ```javascript
336
+ assert.equal(qs.stringify({ a: 'b', c: 'd' }, { addQueryPrefix: true }), '?a=b&c=d');
337
+ ```
338
+
311
339
  The delimiter may be overridden with stringify as well:
312
340
 
313
341
  ```javascript
@@ -329,7 +357,7 @@ You may use the `sort` option to affect the order of parameter keys:
329
357
 
330
358
  ```javascript
331
359
  function alphabeticalSort(a, b) {
332
- return a.localeCompare(b);
360
+ return a.localeCompare(b);
333
361
  }
334
362
  assert.equal(qs.stringify({ a: 'c', z: 'y', b : 'f' }, { sort: alphabeticalSort }), 'a=c&b=f&z=y');
335
363
  ```
@@ -340,17 +368,17 @@ pass an array, it will be used to select properties and array indices for string
340
368
 
341
369
  ```javascript
342
370
  function filterFunc(prefix, value) {
343
- if (prefix == 'b') {
344
- // Return an `undefined` value to omit a property.
345
- return;
346
- }
347
- if (prefix == 'e[f]') {
348
- return value.getTime();
349
- }
350
- if (prefix == 'e[g][0]') {
351
- return value * 2;
352
- }
353
- return value;
371
+ if (prefix == 'b') {
372
+ // Return an `undefined` value to omit a property.
373
+ return;
374
+ }
375
+ if (prefix == 'e[f]') {
376
+ return value.getTime();
377
+ }
378
+ if (prefix == 'e[g][0]') {
379
+ return value * 2;
380
+ }
381
+ return value;
354
382
  }
355
383
  qs.stringify({ a: 'b', c: 'd', e: { f: new Date(123), g: [2] } }, { filter: filterFunc });
356
384
  // 'a=b&c=d&e[f]=123&e[g][0]=4'
@@ -400,7 +428,7 @@ assert.equal(nullsSkipped, 'a=b');
400
428
 
401
429
  ### Dealing with special character sets
402
430
 
403
- By default the encoding and decoding of characters is done in `utf-8`. If you
431
+ By default the encoding and decoding of characters is done in `utf-8`. If you
404
432
  wish to encode querystrings to a different character set (i.e.
405
433
  [Shift JIS](https://en.wikipedia.org/wiki/Shift_JIS)) you can use the
406
434
  [`qs-iconv`](https://github.com/martinheidegger/qs-iconv) library:
@@ -429,3 +457,19 @@ assert.equal(qs.stringify({ a: 'b c' }), 'a=b%20c');
429
457
  assert.equal(qs.stringify({ a: 'b c' }, { format : 'RFC3986' }), 'a=b%20c');
430
458
  assert.equal(qs.stringify({ a: 'b c' }, { format : 'RFC1738' }), 'a=b+c');
431
459
  ```
460
+
461
+ [1]: https://npmjs.org/package/qs
462
+ [2]: http://versionbadg.es/ljharb/qs.svg
463
+ [3]: https://api.travis-ci.org/ljharb/qs.svg
464
+ [4]: https://travis-ci.org/ljharb/qs
465
+ [5]: https://david-dm.org/ljharb/qs.svg
466
+ [6]: https://david-dm.org/ljharb/qs
467
+ [7]: https://david-dm.org/ljharb/qs/dev-status.svg
468
+ [8]: https://david-dm.org/ljharb/qs?type=dev
469
+ [9]: https://ci.testling.com/ljharb/qs.png
470
+ [10]: https://ci.testling.com/ljharb/qs
471
+ [11]: https://nodei.co/npm/qs.png?downloads=true&stars=true
472
+ [license-image]: http://img.shields.io/npm/l/qs.svg
473
+ [license-url]: LICENSE
474
+ [downloads-image]: http://img.shields.io/npm/dm/qs.svg
475
+ [downloads-url]: http://npm-stat.com/charts.html?package=qs
package/dist/qs.js CHANGED
@@ -52,19 +52,23 @@ var defaults = {
52
52
 
53
53
  var parseValues = function parseQueryStringValues(str, options) {
54
54
  var obj = {};
55
- var parts = str.split(options.delimiter, options.parameterLimit === Infinity ? undefined : options.parameterLimit);
55
+ var cleanStr = options.ignoreQueryPrefix ? str.replace(/^\?/, '') : str;
56
+ var limit = options.parameterLimit === Infinity ? undefined : options.parameterLimit;
57
+ var parts = cleanStr.split(options.delimiter, limit);
56
58
 
57
59
  for (var i = 0; i < parts.length; ++i) {
58
60
  var part = parts[i];
59
- var pos = part.indexOf(']=') === -1 ? part.indexOf('=') : part.indexOf(']=') + 1;
61
+
62
+ var bracketEqualsPos = part.indexOf(']=');
63
+ var pos = bracketEqualsPos === -1 ? part.indexOf('=') : bracketEqualsPos + 1;
60
64
 
61
65
  var key, val;
62
66
  if (pos === -1) {
63
- key = options.decoder(part);
67
+ key = options.decoder(part, defaults.decoder);
64
68
  val = options.strictNullHandling ? null : '';
65
69
  } else {
66
- key = options.decoder(part.slice(0, pos));
67
- val = options.decoder(part.slice(pos + 1));
70
+ key = options.decoder(part.slice(0, pos), defaults.decoder);
71
+ val = options.decoder(part.slice(pos + 1), defaults.decoder);
68
72
  }
69
73
  if (has.call(obj, key)) {
70
74
  obj[key] = [].concat(obj[key]).concat(val);
@@ -92,11 +96,11 @@ var parseObject = function parseObjectRecursive(chain, val, options) {
92
96
  var cleanRoot = root.charAt(0) === '[' && root.charAt(root.length - 1) === ']' ? root.slice(1, -1) : root;
93
97
  var index = parseInt(cleanRoot, 10);
94
98
  if (
95
- !isNaN(index) &&
96
- root !== cleanRoot &&
97
- String(index) === cleanRoot &&
98
- index >= 0 &&
99
- (options.parseArrays && index <= options.arrayLimit)
99
+ !isNaN(index)
100
+ && root !== cleanRoot
101
+ && String(index) === cleanRoot
102
+ && index >= 0
103
+ && (options.parseArrays && index <= options.arrayLimit)
100
104
  ) {
101
105
  obj = [];
102
106
  obj[index] = parseObject(chain, val, options);
@@ -164,12 +168,13 @@ var parseKeys = function parseQueryStringKeys(givenKey, val, options) {
164
168
  };
165
169
 
166
170
  module.exports = function (str, opts) {
167
- var options = opts || {};
171
+ var options = opts ? utils.assign({}, opts) : {};
168
172
 
169
173
  if (options.decoder !== null && options.decoder !== undefined && typeof options.decoder !== 'function') {
170
174
  throw new TypeError('Decoder has to be a function.');
171
175
  }
172
176
 
177
+ options.ignoreQueryPrefix = options.ignoreQueryPrefix === true;
173
178
  options.delimiter = typeof options.delimiter === 'string' || utils.isRegExp(options.delimiter) ? options.delimiter : defaults.delimiter;
174
179
  options.depth = typeof options.depth === 'number' ? options.depth : defaults.depth;
175
180
  options.arrayLimit = typeof options.arrayLimit === 'number' ? options.arrayLimit : defaults.arrayLimit;
@@ -224,6 +229,7 @@ var defaults = {
224
229
  delimiter: '&',
225
230
  encode: true,
226
231
  encoder: utils.encode,
232
+ encodeValuesOnly: false,
227
233
  serializeDate: function serializeDate(date) { // eslint-disable-line func-name-matching
228
234
  return toISO.call(date);
229
235
  },
@@ -242,7 +248,8 @@ var stringify = function stringify( // eslint-disable-line func-name-matching
242
248
  sort,
243
249
  allowDots,
244
250
  serializeDate,
245
- formatter
251
+ formatter,
252
+ encodeValuesOnly
246
253
  ) {
247
254
  var obj = object;
248
255
  if (typeof filter === 'function') {
@@ -251,7 +258,7 @@ var stringify = function stringify( // eslint-disable-line func-name-matching
251
258
  obj = serializeDate(obj);
252
259
  } else if (obj === null) {
253
260
  if (strictNullHandling) {
254
- return encoder ? encoder(prefix) : prefix;
261
+ return encoder && !encodeValuesOnly ? encoder(prefix, defaults.encoder) : prefix;
255
262
  }
256
263
 
257
264
  obj = '';
@@ -259,7 +266,8 @@ var stringify = function stringify( // eslint-disable-line func-name-matching
259
266
 
260
267
  if (typeof obj === 'string' || typeof obj === 'number' || typeof obj === 'boolean' || utils.isBuffer(obj)) {
261
268
  if (encoder) {
262
- return [formatter(encoder(prefix)) + '=' + formatter(encoder(obj))];
269
+ var keyValue = encodeValuesOnly ? prefix : encoder(prefix, defaults.encoder);
270
+ return [formatter(keyValue) + '=' + formatter(encoder(obj, defaults.encoder))];
263
271
  }
264
272
  return [formatter(prefix) + '=' + formatter(String(obj))];
265
273
  }
@@ -297,7 +305,8 @@ var stringify = function stringify( // eslint-disable-line func-name-matching
297
305
  sort,
298
306
  allowDots,
299
307
  serializeDate,
300
- formatter
308
+ formatter,
309
+ encodeValuesOnly
301
310
  ));
302
311
  } else {
303
312
  values = values.concat(stringify(
@@ -311,7 +320,8 @@ var stringify = function stringify( // eslint-disable-line func-name-matching
311
320
  sort,
312
321
  allowDots,
313
322
  serializeDate,
314
- formatter
323
+ formatter,
324
+ encodeValuesOnly
315
325
  ));
316
326
  }
317
327
  }
@@ -321,7 +331,7 @@ var stringify = function stringify( // eslint-disable-line func-name-matching
321
331
 
322
332
  module.exports = function (object, opts) {
323
333
  var obj = object;
324
- var options = opts || {};
334
+ var options = opts ? utils.assign({}, opts) : {};
325
335
 
326
336
  if (options.encoder !== null && options.encoder !== undefined && typeof options.encoder !== 'function') {
327
337
  throw new TypeError('Encoder has to be a function.');
@@ -331,10 +341,11 @@ module.exports = function (object, opts) {
331
341
  var strictNullHandling = typeof options.strictNullHandling === 'boolean' ? options.strictNullHandling : defaults.strictNullHandling;
332
342
  var skipNulls = typeof options.skipNulls === 'boolean' ? options.skipNulls : defaults.skipNulls;
333
343
  var encode = typeof options.encode === 'boolean' ? options.encode : defaults.encode;
334
- var encoder = encode ? (typeof options.encoder === 'function' ? options.encoder : defaults.encoder) : null;
344
+ var encoder = typeof options.encoder === 'function' ? options.encoder : defaults.encoder;
335
345
  var sort = typeof options.sort === 'function' ? options.sort : null;
336
346
  var allowDots = typeof options.allowDots === 'undefined' ? false : options.allowDots;
337
347
  var serializeDate = typeof options.serializeDate === 'function' ? options.serializeDate : defaults.serializeDate;
348
+ var encodeValuesOnly = typeof options.encodeValuesOnly === 'boolean' ? options.encodeValuesOnly : defaults.encodeValuesOnly;
338
349
  if (typeof options.format === 'undefined') {
339
350
  options.format = formats.default;
340
351
  } else if (!Object.prototype.hasOwnProperty.call(formats.formatters, options.format)) {
@@ -390,16 +401,20 @@ module.exports = function (object, opts) {
390
401
  generateArrayPrefix,
391
402
  strictNullHandling,
392
403
  skipNulls,
393
- encoder,
404
+ encode ? encoder : null,
394
405
  filter,
395
406
  sort,
396
407
  allowDots,
397
408
  serializeDate,
398
- formatter
409
+ formatter,
410
+ encodeValuesOnly
399
411
  ));
400
412
  }
401
413
 
402
- return keys.join(delimiter);
414
+ var joined = keys.join(delimiter);
415
+ var prefix = options.addQueryPrefix === true ? '?' : '';
416
+
417
+ return joined.length > 0 ? prefix + joined : '';
403
418
  };
404
419
 
405
420
  },{"./formats":1,"./utils":5}],5:[function(require,module,exports){
@@ -473,7 +488,7 @@ exports.merge = function (target, source, options) {
473
488
  return Object.keys(source).reduce(function (acc, key) {
474
489
  var value = source[key];
475
490
 
476
- if (Object.prototype.hasOwnProperty.call(acc, key)) {
491
+ if (has.call(acc, key)) {
477
492
  acc[key] = exports.merge(acc[key], value, options);
478
493
  } else {
479
494
  acc[key] = value;
@@ -482,6 +497,13 @@ exports.merge = function (target, source, options) {
482
497
  }, mergeTarget);
483
498
  };
484
499
 
500
+ exports.assign = function assignSingleSource(target, source) {
501
+ return Object.keys(source).reduce(function (acc, key) {
502
+ acc[key] = source[key];
503
+ return acc;
504
+ }, target);
505
+ };
506
+
485
507
  exports.decode = function (str) {
486
508
  try {
487
509
  return decodeURIComponent(str.replace(/\+/g, ' '));
@@ -504,13 +526,13 @@ exports.encode = function (str) {
504
526
  var c = string.charCodeAt(i);
505
527
 
506
528
  if (
507
- c === 0x2D || // -
508
- c === 0x2E || // .
509
- c === 0x5F || // _
510
- c === 0x7E || // ~
511
- (c >= 0x30 && c <= 0x39) || // 0-9
512
- (c >= 0x41 && c <= 0x5A) || // a-z
513
- (c >= 0x61 && c <= 0x7A) // A-Z
529
+ c === 0x2D // -
530
+ || c === 0x2E // .
531
+ || c === 0x5F // _
532
+ || c === 0x7E // ~
533
+ || (c >= 0x30 && c <= 0x39) // 0-9
534
+ || (c >= 0x41 && c <= 0x5A) // a-z
535
+ || (c >= 0x61 && c <= 0x7A) // A-Z
514
536
  ) {
515
537
  out += string.charAt(i);
516
538
  continue;
@@ -533,7 +555,10 @@ exports.encode = function (str) {
533
555
 
534
556
  i += 1;
535
557
  c = 0x10000 + (((c & 0x3FF) << 10) | (string.charCodeAt(i) & 0x3FF));
536
- out += hexTable[0xF0 | (c >> 18)] + hexTable[0x80 | ((c >> 12) & 0x3F)] + hexTable[0x80 | ((c >> 6) & 0x3F)] + hexTable[0x80 | (c & 0x3F)]; // eslint-disable-line max-len
558
+ out += hexTable[0xF0 | (c >> 18)]
559
+ + hexTable[0x80 | ((c >> 12) & 0x3F)]
560
+ + hexTable[0x80 | ((c >> 6) & 0x3F)]
561
+ + hexTable[0x80 | (c & 0x3F)];
537
562
  }
538
563
 
539
564
  return out;
package/lib/parse.js CHANGED
@@ -18,19 +18,23 @@ var defaults = {
18
18
 
19
19
  var parseValues = function parseQueryStringValues(str, options) {
20
20
  var obj = {};
21
- var parts = str.split(options.delimiter, options.parameterLimit === Infinity ? undefined : options.parameterLimit);
21
+ var cleanStr = options.ignoreQueryPrefix ? str.replace(/^\?/, '') : str;
22
+ var limit = options.parameterLimit === Infinity ? undefined : options.parameterLimit;
23
+ var parts = cleanStr.split(options.delimiter, limit);
22
24
 
23
25
  for (var i = 0; i < parts.length; ++i) {
24
26
  var part = parts[i];
25
- var pos = part.indexOf(']=') === -1 ? part.indexOf('=') : part.indexOf(']=') + 1;
27
+
28
+ var bracketEqualsPos = part.indexOf(']=');
29
+ var pos = bracketEqualsPos === -1 ? part.indexOf('=') : bracketEqualsPos + 1;
26
30
 
27
31
  var key, val;
28
32
  if (pos === -1) {
29
- key = options.decoder(part);
33
+ key = options.decoder(part, defaults.decoder);
30
34
  val = options.strictNullHandling ? null : '';
31
35
  } else {
32
- key = options.decoder(part.slice(0, pos));
33
- val = options.decoder(part.slice(pos + 1));
36
+ key = options.decoder(part.slice(0, pos), defaults.decoder);
37
+ val = options.decoder(part.slice(pos + 1), defaults.decoder);
34
38
  }
35
39
  if (has.call(obj, key)) {
36
40
  obj[key] = [].concat(obj[key]).concat(val);
@@ -58,11 +62,11 @@ var parseObject = function parseObjectRecursive(chain, val, options) {
58
62
  var cleanRoot = root.charAt(0) === '[' && root.charAt(root.length - 1) === ']' ? root.slice(1, -1) : root;
59
63
  var index = parseInt(cleanRoot, 10);
60
64
  if (
61
- !isNaN(index) &&
62
- root !== cleanRoot &&
63
- String(index) === cleanRoot &&
64
- index >= 0 &&
65
- (options.parseArrays && index <= options.arrayLimit)
65
+ !isNaN(index)
66
+ && root !== cleanRoot
67
+ && String(index) === cleanRoot
68
+ && index >= 0
69
+ && (options.parseArrays && index <= options.arrayLimit)
66
70
  ) {
67
71
  obj = [];
68
72
  obj[index] = parseObject(chain, val, options);
@@ -130,12 +134,13 @@ var parseKeys = function parseQueryStringKeys(givenKey, val, options) {
130
134
  };
131
135
 
132
136
  module.exports = function (str, opts) {
133
- var options = opts || {};
137
+ var options = opts ? utils.assign({}, opts) : {};
134
138
 
135
139
  if (options.decoder !== null && options.decoder !== undefined && typeof options.decoder !== 'function') {
136
140
  throw new TypeError('Decoder has to be a function.');
137
141
  }
138
142
 
143
+ options.ignoreQueryPrefix = options.ignoreQueryPrefix === true;
139
144
  options.delimiter = typeof options.delimiter === 'string' || utils.isRegExp(options.delimiter) ? options.delimiter : defaults.delimiter;
140
145
  options.depth = typeof options.depth === 'number' ? options.depth : defaults.depth;
141
146
  options.arrayLimit = typeof options.arrayLimit === 'number' ? options.arrayLimit : defaults.arrayLimit;
package/lib/stringify.js CHANGED
@@ -21,6 +21,7 @@ var defaults = {
21
21
  delimiter: '&',
22
22
  encode: true,
23
23
  encoder: utils.encode,
24
+ encodeValuesOnly: false,
24
25
  serializeDate: function serializeDate(date) { // eslint-disable-line func-name-matching
25
26
  return toISO.call(date);
26
27
  },
@@ -39,7 +40,8 @@ var stringify = function stringify( // eslint-disable-line func-name-matching
39
40
  sort,
40
41
  allowDots,
41
42
  serializeDate,
42
- formatter
43
+ formatter,
44
+ encodeValuesOnly
43
45
  ) {
44
46
  var obj = object;
45
47
  if (typeof filter === 'function') {
@@ -48,7 +50,7 @@ var stringify = function stringify( // eslint-disable-line func-name-matching
48
50
  obj = serializeDate(obj);
49
51
  } else if (obj === null) {
50
52
  if (strictNullHandling) {
51
- return encoder ? encoder(prefix) : prefix;
53
+ return encoder && !encodeValuesOnly ? encoder(prefix, defaults.encoder) : prefix;
52
54
  }
53
55
 
54
56
  obj = '';
@@ -56,7 +58,8 @@ var stringify = function stringify( // eslint-disable-line func-name-matching
56
58
 
57
59
  if (typeof obj === 'string' || typeof obj === 'number' || typeof obj === 'boolean' || utils.isBuffer(obj)) {
58
60
  if (encoder) {
59
- return [formatter(encoder(prefix)) + '=' + formatter(encoder(obj))];
61
+ var keyValue = encodeValuesOnly ? prefix : encoder(prefix, defaults.encoder);
62
+ return [formatter(keyValue) + '=' + formatter(encoder(obj, defaults.encoder))];
60
63
  }
61
64
  return [formatter(prefix) + '=' + formatter(String(obj))];
62
65
  }
@@ -94,7 +97,8 @@ var stringify = function stringify( // eslint-disable-line func-name-matching
94
97
  sort,
95
98
  allowDots,
96
99
  serializeDate,
97
- formatter
100
+ formatter,
101
+ encodeValuesOnly
98
102
  ));
99
103
  } else {
100
104
  values = values.concat(stringify(
@@ -108,7 +112,8 @@ var stringify = function stringify( // eslint-disable-line func-name-matching
108
112
  sort,
109
113
  allowDots,
110
114
  serializeDate,
111
- formatter
115
+ formatter,
116
+ encodeValuesOnly
112
117
  ));
113
118
  }
114
119
  }
@@ -118,7 +123,7 @@ var stringify = function stringify( // eslint-disable-line func-name-matching
118
123
 
119
124
  module.exports = function (object, opts) {
120
125
  var obj = object;
121
- var options = opts || {};
126
+ var options = opts ? utils.assign({}, opts) : {};
122
127
 
123
128
  if (options.encoder !== null && options.encoder !== undefined && typeof options.encoder !== 'function') {
124
129
  throw new TypeError('Encoder has to be a function.');
@@ -128,10 +133,11 @@ module.exports = function (object, opts) {
128
133
  var strictNullHandling = typeof options.strictNullHandling === 'boolean' ? options.strictNullHandling : defaults.strictNullHandling;
129
134
  var skipNulls = typeof options.skipNulls === 'boolean' ? options.skipNulls : defaults.skipNulls;
130
135
  var encode = typeof options.encode === 'boolean' ? options.encode : defaults.encode;
131
- var encoder = encode ? (typeof options.encoder === 'function' ? options.encoder : defaults.encoder) : null;
136
+ var encoder = typeof options.encoder === 'function' ? options.encoder : defaults.encoder;
132
137
  var sort = typeof options.sort === 'function' ? options.sort : null;
133
138
  var allowDots = typeof options.allowDots === 'undefined' ? false : options.allowDots;
134
139
  var serializeDate = typeof options.serializeDate === 'function' ? options.serializeDate : defaults.serializeDate;
140
+ var encodeValuesOnly = typeof options.encodeValuesOnly === 'boolean' ? options.encodeValuesOnly : defaults.encodeValuesOnly;
135
141
  if (typeof options.format === 'undefined') {
136
142
  options.format = formats.default;
137
143
  } else if (!Object.prototype.hasOwnProperty.call(formats.formatters, options.format)) {
@@ -187,14 +193,18 @@ module.exports = function (object, opts) {
187
193
  generateArrayPrefix,
188
194
  strictNullHandling,
189
195
  skipNulls,
190
- encoder,
196
+ encode ? encoder : null,
191
197
  filter,
192
198
  sort,
193
199
  allowDots,
194
200
  serializeDate,
195
- formatter
201
+ formatter,
202
+ encodeValuesOnly
196
203
  ));
197
204
  }
198
205
 
199
- return keys.join(delimiter);
206
+ var joined = keys.join(delimiter);
207
+ var prefix = options.addQueryPrefix === true ? '?' : '';
208
+
209
+ return joined.length > 0 ? prefix + joined : '';
200
210
  };
package/lib/utils.js CHANGED
@@ -68,7 +68,7 @@ exports.merge = function (target, source, options) {
68
68
  return Object.keys(source).reduce(function (acc, key) {
69
69
  var value = source[key];
70
70
 
71
- if (Object.prototype.hasOwnProperty.call(acc, key)) {
71
+ if (has.call(acc, key)) {
72
72
  acc[key] = exports.merge(acc[key], value, options);
73
73
  } else {
74
74
  acc[key] = value;
@@ -77,6 +77,13 @@ exports.merge = function (target, source, options) {
77
77
  }, mergeTarget);
78
78
  };
79
79
 
80
+ exports.assign = function assignSingleSource(target, source) {
81
+ return Object.keys(source).reduce(function (acc, key) {
82
+ acc[key] = source[key];
83
+ return acc;
84
+ }, target);
85
+ };
86
+
80
87
  exports.decode = function (str) {
81
88
  try {
82
89
  return decodeURIComponent(str.replace(/\+/g, ' '));
@@ -99,13 +106,13 @@ exports.encode = function (str) {
99
106
  var c = string.charCodeAt(i);
100
107
 
101
108
  if (
102
- c === 0x2D || // -
103
- c === 0x2E || // .
104
- c === 0x5F || // _
105
- c === 0x7E || // ~
106
- (c >= 0x30 && c <= 0x39) || // 0-9
107
- (c >= 0x41 && c <= 0x5A) || // a-z
108
- (c >= 0x61 && c <= 0x7A) // A-Z
109
+ c === 0x2D // -
110
+ || c === 0x2E // .
111
+ || c === 0x5F // _
112
+ || c === 0x7E // ~
113
+ || (c >= 0x30 && c <= 0x39) // 0-9
114
+ || (c >= 0x41 && c <= 0x5A) // a-z
115
+ || (c >= 0x61 && c <= 0x7A) // A-Z
109
116
  ) {
110
117
  out += string.charAt(i);
111
118
  continue;
@@ -128,7 +135,10 @@ exports.encode = function (str) {
128
135
 
129
136
  i += 1;
130
137
  c = 0x10000 + (((c & 0x3FF) << 10) | (string.charCodeAt(i) & 0x3FF));
131
- out += hexTable[0xF0 | (c >> 18)] + hexTable[0x80 | ((c >> 12) & 0x3F)] + hexTable[0x80 | ((c >> 6) & 0x3F)] + hexTable[0x80 | (c & 0x3F)]; // eslint-disable-line max-len
138
+ out += hexTable[0xF0 | (c >> 18)]
139
+ + hexTable[0x80 | ((c >> 12) & 0x3F)]
140
+ + hexTable[0x80 | ((c >> 6) & 0x3F)]
141
+ + hexTable[0x80 | (c & 0x3F)];
132
142
  }
133
143
 
134
144
  return out;
package/package.json CHANGED
@@ -1,50 +1,51 @@
1
1
  {
2
- "name": "qs",
3
- "description": "A querystring parser that supports nesting and arrays, with a depth limit",
4
- "homepage": "https://github.com/ljharb/qs",
5
- "version": "6.3.2",
6
- "repository": {
7
- "type": "git",
8
- "url": "https://github.com/ljharb/qs.git"
9
- },
10
- "main": "lib/index.js",
11
- "contributors": [
12
- {
13
- "name": "Jordan Harband",
14
- "email": "ljharb@gmail.com",
15
- "url": "http://ljharb.codes"
16
- }
17
- ],
18
- "keywords": [
19
- "querystring",
20
- "qs"
21
- ],
22
- "engines": {
23
- "node": ">=0.6"
24
- },
25
- "dependencies": {},
26
- "devDependencies": {
27
- "@ljharb/eslint-config": "^11.0.0",
28
- "browserify": "^14.1.0",
29
- "covert": "^1.1.0",
30
- "eslint": "^3.17.0",
31
- "evalmd": "^0.0.17",
32
- "iconv-lite": "^0.4.15",
33
- "mkdirp": "^0.5.1",
34
- "parallelshell": "^2.0.0",
35
- "qs-iconv": "^1.0.4",
36
- "safe-publish-latest": "^1.1.1",
37
- "tape": "^4.6.3"
38
- },
39
- "scripts": {
40
- "prepublish": "safe-publish-latest && npm run dist",
41
- "pretest": "npm run --silent readme && npm run --silent lint",
42
- "test": "npm run --silent coverage",
43
- "tests-only": "node test",
44
- "readme": "evalmd README.md",
45
- "lint": "eslint lib/*.js test/*.js",
46
- "coverage": "covert test",
47
- "dist": "mkdirp dist && browserify --standalone Qs lib/index.js > dist/qs.js"
48
- },
49
- "license": "BSD-3-Clause"
2
+ "name": "qs",
3
+ "description": "A querystring parser that supports nesting and arrays, with a depth limit",
4
+ "homepage": "https://github.com/ljharb/qs",
5
+ "version": "6.5.0",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://github.com/ljharb/qs.git"
9
+ },
10
+ "main": "lib/index.js",
11
+ "contributors": [
12
+ {
13
+ "name": "Jordan Harband",
14
+ "email": "ljharb@gmail.com",
15
+ "url": "http://ljharb.codes"
16
+ }
17
+ ],
18
+ "keywords": [
19
+ "querystring",
20
+ "qs"
21
+ ],
22
+ "engines": {
23
+ "node": ">=0.6"
24
+ },
25
+ "dependencies": {},
26
+ "devDependencies": {
27
+ "@ljharb/eslint-config": "^11.0.0",
28
+ "browserify": "^14.4.0",
29
+ "covert": "^1.1.0",
30
+ "editorconfig-tools": "^0.1.1",
31
+ "eslint": "^3.19.0",
32
+ "evalmd": "^0.0.17",
33
+ "iconv-lite": "^0.4.18",
34
+ "mkdirp": "^0.5.1",
35
+ "qs-iconv": "^1.0.4",
36
+ "safe-publish-latest": "^1.1.1",
37
+ "tape": "^4.7.0"
38
+ },
39
+ "scripts": {
40
+ "prepublish": "safe-publish-latest && npm run dist",
41
+ "pretest": "npm run --silent readme && npm run --silent lint",
42
+ "test": "npm run --silent coverage",
43
+ "tests-only": "node test",
44
+ "readme": "evalmd README.md",
45
+ "prelint": "editorconfig-tools check * lib/* test/*",
46
+ "lint": "eslint lib/*.js test/*.js",
47
+ "coverage": "covert test",
48
+ "dist": "mkdirp dist && browserify --standalone Qs lib/index.js > dist/qs.js"
49
+ },
50
+ "license": "BSD-3-Clause"
50
51
  }
package/test/.eslintrc CHANGED
@@ -5,6 +5,7 @@
5
5
  "max-nested-callbacks": [2, 3],
6
6
  "max-statements": 0,
7
7
  "no-extend-native": 0,
8
- "sort-keys": 1
8
+ "no-magic-numbers": 0,
9
+ "sort-keys": 0
9
10
  }
10
11
  }
package/test/parse.js CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  var test = require('tape');
4
4
  var qs = require('../');
5
+ var utils = require('../lib/utils');
5
6
  var iconv = require('iconv-lite');
6
7
 
7
8
  test('parse()', function (t) {
@@ -304,6 +305,13 @@ test('parse()', function (t) {
304
305
  st.end();
305
306
  });
306
307
 
308
+ t.test('allows for query string prefix', function (st) {
309
+ st.deepEqual(qs.parse('?foo=bar', { ignoreQueryPrefix: true }), { foo: 'bar' });
310
+ st.deepEqual(qs.parse('foo=bar', { ignoreQueryPrefix: true }), { foo: 'bar' });
311
+ st.deepEqual(qs.parse('?foo=bar', { ignoreQueryPrefix: false }), { '?foo': 'bar' });
312
+ st.end();
313
+ });
314
+
307
315
  t.test('parses an object', function (st) {
308
316
  var input = {
309
317
  'user[name]': { 'pop[bob]': 3 },
@@ -510,10 +518,29 @@ test('parse()', function (t) {
510
518
  st.end();
511
519
  });
512
520
 
521
+ t.test('receives the default decoder as a second argument', function (st) {
522
+ st.plan(1);
523
+ qs.parse('a', {
524
+ decoder: function (str, defaultDecoder) {
525
+ st.equal(defaultDecoder, utils.decode);
526
+ }
527
+ });
528
+ st.end();
529
+ });
530
+
513
531
  t.test('throws error with wrong decoder', function (st) {
514
532
  st.throws(function () {
515
533
  qs.parse({}, { decoder: 'string' });
516
534
  }, new TypeError('Decoder has to be a function.'));
517
535
  st.end();
518
536
  });
537
+
538
+ t.test('does not mutate the options argument', function (st) {
539
+ var options = {};
540
+ qs.parse('a[b]=true', options);
541
+ st.deepEqual(options, {});
542
+ st.end();
543
+ });
544
+
545
+ t.end();
519
546
  });
package/test/stringify.js CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  var test = require('tape');
4
4
  var qs = require('../');
5
+ var utils = require('../lib/utils');
5
6
  var iconv = require('iconv-lite');
6
7
 
7
8
  test('stringify()', function (t) {
@@ -17,6 +18,16 @@ test('stringify()', function (t) {
17
18
  st.end();
18
19
  });
19
20
 
21
+ t.test('adds query prefix', function (st) {
22
+ st.equal(qs.stringify({ a: 'b' }, { addQueryPrefix: true }), '?a=b');
23
+ st.end();
24
+ });
25
+
26
+ t.test('with query prefix, outputs blank string given an empty object', function (st) {
27
+ st.equal(qs.stringify({}, { addQueryPrefix: true }), '');
28
+ st.end();
29
+ });
30
+
20
31
  t.test('stringifies a nested object', function (st) {
21
32
  st.equal(qs.stringify({ a: { b: 'c' } }), 'a%5Bb%5D=c');
22
33
  st.equal(qs.stringify({ a: { b: { c: { d: 'e' } } } }), 'a%5Bb%5D%5Bc%5D%5Bd%5D=e');
@@ -452,6 +463,16 @@ test('stringify()', function (t) {
452
463
  st.end();
453
464
  });
454
465
 
466
+ t.test('receives the default encoder as a second argument', function (st) {
467
+ st.plan(2);
468
+ qs.stringify({ a: 1 }, {
469
+ encoder: function (str, defaultEncoder) {
470
+ st.equal(defaultEncoder, utils.encode);
471
+ }
472
+ });
473
+ st.end();
474
+ });
475
+
455
476
  t.test('throws error with wrong encoder', function (st) {
456
477
  st.throws(function () {
457
478
  qs.stringify({}, { encoder: 'string' });
@@ -535,4 +556,41 @@ test('stringify()', function (t) {
535
556
  );
536
557
  st.end();
537
558
  });
559
+
560
+ t.test('encodeValuesOnly', function (st) {
561
+ st.equal(
562
+ qs.stringify(
563
+ { a: 'b', c: ['d', 'e=f'], f: [['g'], ['h']] },
564
+ { encodeValuesOnly: true }
565
+ ),
566
+ 'a=b&c[0]=d&c[1]=e%3Df&f[0][0]=g&f[1][0]=h'
567
+ );
568
+ st.equal(
569
+ qs.stringify(
570
+ { a: 'b', c: ['d', 'e'], f: [['g'], ['h']] }
571
+ ),
572
+ 'a=b&c%5B0%5D=d&c%5B1%5D=e&f%5B0%5D%5B0%5D=g&f%5B1%5D%5B0%5D=h'
573
+ );
574
+ st.end();
575
+ });
576
+
577
+ t.test('encodeValuesOnly - strictNullHandling', function (st) {
578
+ st.equal(
579
+ qs.stringify(
580
+ { a: { b: null } },
581
+ { encodeValuesOnly: true, strictNullHandling: true }
582
+ ),
583
+ 'a[b]'
584
+ );
585
+ st.end();
586
+ });
587
+
588
+ t.test('does not mutate the options argument', function (st) {
589
+ var options = {};
590
+ qs.stringify({}, options);
591
+ st.deepEqual(options, {});
592
+ st.end();
593
+ });
594
+
595
+ t.end();
538
596
  });
package/test/utils.js CHANGED
@@ -20,3 +20,15 @@ test('merge()', function (t) {
20
20
 
21
21
  t.end();
22
22
  });
23
+
24
+ test('assign()', function (t) {
25
+ var target = { a: 1, b: 2 };
26
+ var source = { b: 3, c: 4 };
27
+ var result = utils.assign(target, source);
28
+
29
+ t.equal(result, target, 'returns the target');
30
+ t.deepEqual(target, { a: 1, b: 3, c: 4 }, 'target and source are merged');
31
+ t.deepEqual(source, { b: 3, c: 4 }, 'source is untouched');
32
+
33
+ t.end();
34
+ });