qs 6.8.2 → 6.8.4

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,79 @@
1
+ # Backport Record
2
+
3
+ Commits to backport from main (v6.14.1..v6.14.2, excluding version bump):
4
+
5
+ | # | SHA | Description |
6
+ |---|-----|-------------|
7
+ | 1 | 6744d30 | [Robustness] avoid `.push`, use `void` |
8
+ | 2 | 6bdfaf5 | [readme] replace runkit/travis CI badge with shields.io check-runs badge |
9
+ | 3 | 2a35775 | [meta] fix changelog typo |
10
+ | 4 | 1b9a8b4 | [actions] fix rebase workflow permissions |
11
+ | 5 | fbc5206 | [Fix] `parse`: fix error message |
12
+ | 6 | f6a7abf | [Fix] `parse`: enforce arrayLimit on comma-parsed values |
13
+ | 7 | febb644 | [Fix] `parse`: throw on arrayLimit exceeded with indexed notation |
14
+ | 8 | cfc108f | [Fix] arrayLimit means max count, not max index |
15
+ | 9 | 6addf8c | [Fix] `parse`: mark overflow objects for indexed notation |
16
+ | 10 | 5c308e5 | [readme] clarify parseArrays and arrayLimit documentation |
17
+ | 11 | 294db90 | [readme] document addQueryPrefix does not add ? to empty output |
18
+
19
+ ## Results
20
+
21
+ | Version | Base Tag | Base SHA | Result HEAD | Applied | Skipped |
22
+ |---------|----------|----------|-------------|---------|---------|
23
+ | 6.14 | v6.14.2 | bdcf0c7 | bdcf0c7 | all | — |
24
+ | 6.13 | v6.13.1 | f1ee037 | 625fa19 | 1,2,4,10,11 | 3,5,6,7,8,9 |
25
+ | 6.12 | v6.12.3 | f90cc35 | 8a1b294 | 1,2,4,10,11 | 3,5,6,7,8,9 |
26
+ | 6.11 | v6.11.2 | 410bdd3 | 3a5f714 | 1,2,4,10,11 | 3,5,6,7,8,9 |
27
+ | 6.10 | v6.10.5 | 95bc018 | 8189da8 | 1,2,4,10,11 | 3,5,6,7,8,9 |
28
+ | 6.9 | v6.9.7 | 4cd0032 | 6a7a3bf | 1,2,4,10,11 | 3,5,6,7,8,9 |
29
+ | 6.8 | v6.8.3 | 0db5538 | 76d53e9 | 1,2,4,10,11 | 3,5,6,7,8,9 |
30
+ | 6.7 | v6.7.3 | 834389a | 2991d8b | 1,2,4,10,11 | 3,5,6,7,8,9 |
31
+ | 6.6 | v6.6.1 | 4cc653c | 7093153 | 1,2,4,10,11 | 3,5,6,7,8,9 |
32
+ | 6.5 | v6.5.3 | 298bfa5 | 40b77c3 | 1,2,4,10,11 | 3,5,6,7,8,9 |
33
+ | 6.4 | v6.4.1 | 486aa46 | d9ffe2b | 1,2,4,10 | 3,5,6,7,8,9,11 |
34
+ | 6.3 | v6.3.3 | ff235b4 | b4824cb | 1,2,4,10 | 3,5,6,7,8,9,11 |
35
+ | 6.2 | v6.2.4 | 90d9f2b | db7b937 | 1,2,4,10 | 3,5,6,7,8,9,11 |
36
+ | 6.1 | v6.1.2 | 68ca039 | 3245e1f | 1,2,10 | 3,4,5,6,7,8,9,11 |
37
+ | 6.0 | v6.0.4 | 10233c9 | b779be4 | 1,2,10 | 3,4,5,6,7,8,9,11 |
38
+
39
+ ## Legend for Applied/Skipped columns
40
+
41
+ 1=Robustness, 2=readme badge, 3=changelog typo, 4=actions permissions,
42
+ 5=error msg fix, 6=comma arrayLimit, 7=indexed throw, 8=count semantics,
43
+ 9=mark overflow, 10=parseArrays/arrayLimit docs, 11=addQueryPrefix docs
44
+
45
+ ## Notes
46
+
47
+ - #2 (badge): v6.4+ had runkit badge replaced; v6.0-v6.3 had Travis badge replaced
48
+ - #4 (actions): v6.2-v6.10 had permissions block ADDED (not modified); v6.0-v6.1 have no rebase.yml
49
+
50
+ ## Skip Reasons
51
+
52
+ | Commit | Reason for skip |
53
+ |--------|----------------|
54
+ | 3 (2a35775) | v6.14.1-specific changelog — not applicable to other versions |
55
+ | 5 (fbc5206) | Requires `throwOnLimitExceeded` option (v6.14.0+) |
56
+ | 6 (f6a7abf) | Requires overflow system (`combine(a,b,arrayLimit,plainObjects)`) from v6.14.1 |
57
+ | 7 (febb644) | Requires `throwOnLimitExceeded` option (v6.14.0+) |
58
+ | 8 (cfc108f) | Requires overflow system from v6.14.1 |
59
+ | 9 (6addf8c) | Requires overflow system from v6.14.1 |
60
+ | 11 (294db90) | v6.4 and below lack `addQueryPrefix` in README |
61
+
62
+ ## Release Tags
63
+
64
+ | Tag | SHA | Changelog Entries |
65
+ |-----|-----|-------------------|
66
+ | v6.13.2 | d8a8ab3 | Robustness, readme #543, readme #418, readme badge, actions |
67
+ | v6.12.4 | a67173e | Robustness, readme #543, readme #418, readme badge, actions |
68
+ | v6.11.3 | 6302f35 | Robustness, readme #543, readme #418, readme badge, actions |
69
+ | v6.10.6 | 1aa4bd9 | Robustness, readme #543, readme #418, readme badge, actions |
70
+ | v6.9.8 | 479d4b1 | Robustness, readme #543, readme #418, readme badge, actions |
71
+ | v6.8.4 | 0f2b1e2 | Robustness, readme #543, readme #418, readme badge, actions |
72
+ | v6.7.4 | d38e43a | Robustness, readme #543, readme #418, readme badge, actions |
73
+ | v6.6.2 | 5e1c72c | Robustness, readme #543, readme #418, readme badge, actions |
74
+ | v6.5.4 | c190488 | Robustness, readme #543, readme #418, readme badge, actions |
75
+ | v6.4.2 | 9b50144 | Robustness, readme #543, readme badge (runkit+travis), actions |
76
+ | v6.3.4 | aa3f9f4 | Robustness, readme #543, readme badge (travis), actions |
77
+ | v6.2.5 | d68f354 | Robustness, readme #543, readme badge (travis), actions |
78
+ | v6.1.3 | cb45ba1 | Robustness, readme #543, readme badge (travis) |
79
+ | v6.0.5 | a73f299 | Robustness, readme #543, readme badge (travis) |
@@ -0,0 +1,7 @@
1
+ # GHSA-2856-wc6q-8xwc — Comment to post
2
+
3
+ This is not a vulnerability. The described behavior only modifies properties on the **result object** returned by `qs.parse()` — it does not modify `Object.prototype` or any shared state. Setting `toString` or `hasOwnProperty` on a specific result object is not prototype pollution; it's the expected outcome of parsing a key with that name.
4
+
5
+ The `allowPrototypes` option exists precisely to control whether keys that shadow `Object.prototype` properties are allowed on result objects. When `depth <= 0` and brackets are present, the prototype check is applied to the bracketed form of the key rather than the stripped form — this is a minor inconsistency in the check, but the result is equivalent to what would happen with `allowPrototypes: true`, which is an explicitly supported configuration.
6
+
7
+ Closing as not applicable.
@@ -0,0 +1,53 @@
1
+ # GHSA-6rw7-vpxm-498p — Edits to make
2
+
3
+ ## Severity
4
+ Change: High → Low
5
+
6
+ ## CVSS 3.1
7
+ Old: `CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H` (7.5 High)
8
+ New: `CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:L` (3.7 Low)
9
+
10
+ Changes:
11
+ - AC: L → H (requires non-default parameterLimit config for real impact)
12
+ - A: H → L (bounded memory increase, not a crash/full DoS under defaults)
13
+
14
+ ## Description — replace with:
15
+
16
+ ### Summary
17
+
18
+ The `arrayLimit` option in qs did not enforce limits for bracket notation (`a[]=1&a[]=2`), only for indexed notation (`a[0]=1`). This is a consistency bug; `arrayLimit` should apply uniformly across all array notations.
19
+
20
+ **Note:** The default `parameterLimit` of 1000 effectively mitigates the DoS scenario originally described. With default options, bracket notation cannot produce arrays larger than `parameterLimit` regardless of `arrayLimit`, because each `a[]=value` consumes one parameter slot. The severity has been reduced accordingly.
21
+
22
+ ### Details
23
+
24
+ The `arrayLimit` option only checked limits for indexed notation (`a[0]=1&a[1]=2`) but did not enforce it for bracket notation (`a[]=1&a[]=2`).
25
+
26
+ **Vulnerable code** (`lib/parse.js:159-162`):
27
+ ```javascript
28
+ if (root === '[]' && options.parseArrays) {
29
+ obj = utils.combine([], leaf); // No arrayLimit check
30
+ }
31
+ ```
32
+
33
+ **Working code** (`lib/parse.js:175`):
34
+ ```javascript
35
+ else if (index <= options.arrayLimit) { // Limit checked here
36
+ obj = [];
37
+ obj[index] = leaf;
38
+ }
39
+ ```
40
+
41
+ ### PoC
42
+
43
+ ```javascript
44
+ const qs = require('qs');
45
+ const result = qs.parse('a[]=1&a[]=2&a[]=3&a[]=4&a[]=5&a[]=6', { arrayLimit: 5 });
46
+ console.log(result.a.length); // Output: 6 (should be max 5)
47
+ ```
48
+
49
+ **Note on parameterLimit interaction:** The original advisory's "DoS demonstration" claimed a length of 10,000, but `parameterLimit` (default: 1000) caps parsing to 1,000 parameters. With default options, the actual output is 1,000, not 10,000.
50
+
51
+ ### Impact
52
+
53
+ Consistency bug in `arrayLimit` enforcement. With default `parameterLimit`, the practical DoS risk is negligible since `parameterLimit` already caps the total number of parsed parameters (and thus array elements from bracket notation). The risk increases only when `parameterLimit` is explicitly set to a very high value.
@@ -0,0 +1,10 @@
1
+ # GHSA-w7fw-mjwx-w883 — Comment to post
2
+
3
+ This is a valid report — thank you. The comma-split code path in `parseArrayValue` returned the split array immediately, bypassing the `arrayLimit` check. Unlike the bracket notation case (GHSA-6rw7-vpxm-498p), this genuinely bypasses `parameterLimit` as well, since a single parameter like `a=x,x,x,...` produces an arbitrarily large array from one `&`-delimited parameter.
4
+
5
+ A fix has been implemented: after the comma split and decode step, if the resulting array exceeds `arrayLimit`, it is converted to an object (consistent with other `arrayLimit` exceedances), and `throwOnLimitExceeded` is respected.
6
+
7
+ A few corrections to the advisory:
8
+ - **Severity should be Medium, not High**: This requires the non-default `comma: true` option to be explicitly enabled. HTTP server request size limits also bound the practical impact.
9
+ - **Vulnerable version range**: Should be `<= 6.14.1` (not `< 6.14.1`), since 6.14.1 is also affected.
10
+ - **Patched version**: Will be set once the fix is released.
@@ -0,0 +1,36 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "Bash(npm run lint:*)",
5
+ "Bash(npm test:*)",
6
+ "Bash(npm run tests-only:*)",
7
+ "Bash(npm show:*)",
8
+ "Bash(git log:*)",
9
+ "Bash(git tag:*)",
10
+ "Bash(npm view:*)",
11
+ "Bash(git fetch:*)",
12
+ "Bash(git cat-file:*)",
13
+ "Bash(git show:*)",
14
+ "Bash(git rev-parse:*)",
15
+ "Bash(git rev-list:*)",
16
+ "Bash(gh api:*)",
17
+ "Bash(node /tmp/qs-vuln-test.js:*)",
18
+ "Bash(git -C /Users/ljharb/git/ljharb/qs.git show 3086902 --stat)",
19
+ "Bash(git -C /Users/ljharb/git/ljharb/qs.git diff HEAD -- lib/parse.js lib/utils.js)",
20
+ "Bash(open:*)",
21
+ "WebSearch",
22
+ "WebFetch(domain:www.first.org)",
23
+ "WebFetch(domain:nvd.nist.gov)",
24
+ "WebFetch(domain:raw.githubusercontent.com)",
25
+ "Bash(git -C /Users/ljharb/git/ljharb/qs.git diff origin/main...HEAD)",
26
+ "Bash(for ver in v6.13.1 v6.12.3 v6.11.2 v6.10.5 v6.9.7 v6.8.3 v6.7.3 v6.6.1 v6.5.3 v6.4.1 v6.3.3 v6.2.4 v6.1.2 v6.0.4)",
27
+ "Bash(do echo '=== $ver ===')",
28
+ "Bash(done)",
29
+ "Bash(git checkout:*)",
30
+ "Bash(cd /Users/ljharb/git/ljharb/qs.git git add README.md git commit --no-verify --no-gpg-sign -m \"[readme] replace travis CI badge with shields.io check-runs badge\")",
31
+ "Bash(npm run dist:*)",
32
+ "Bash(for tag in v6.13.2 v6.12.4 v6.11.3 v6.10.6 v6.9.8 v6.8.4 v6.7.4 v6.6.2 v6.5.4 v6.4.2 v6.3.4 v6.2.5 v6.1.3 v6.0.5)",
33
+ "Bash(gh run view:*)"
34
+ ]
35
+ }
36
+ }
package/.editorconfig CHANGED
@@ -8,6 +8,7 @@ charset = utf-8
8
8
  trim_trailing_whitespace = true
9
9
  insert_final_newline = true
10
10
  max_line_length = 160
11
+ quote_type = single
11
12
 
12
13
  [test/*]
13
14
  max_line_length = off
package/.eslintrc CHANGED
@@ -3,10 +3,14 @@
3
3
 
4
4
  "extends": "@ljharb",
5
5
 
6
+ "ignorePatterns": [
7
+ "dist/",
8
+ ],
9
+
6
10
  "rules": {
7
11
  "complexity": 0,
8
12
  "consistent-return": 1,
9
- "func-name-matching": 0,
13
+ "func-name-matching": 0,
10
14
  "id-length": [2, { "min": 1, "max": 25, "properties": "never" }],
11
15
  "indent": [2, 4],
12
16
  "max-lines-per-function": [2, { "max": 150 }],
@@ -16,6 +20,17 @@
16
20
  "no-continue": 1,
17
21
  "no-magic-numbers": 0,
18
22
  "no-restricted-syntax": [2, "BreakStatement", "DebuggerStatement", "ForInStatement", "LabeledStatement", "WithStatement"],
19
- "operator-linebreak": [2, "before"],
20
- }
23
+ },
24
+
25
+ "overrides": [
26
+ {
27
+ "files": "test/**",
28
+ "rules": {
29
+ "function-paren-newline": 0,
30
+ "max-lines-per-function": 0,
31
+ "max-statements": 0,
32
+ "no-extend-native": 0,
33
+ },
34
+ },
35
+ ],
21
36
  }
package/.travis.yml ADDED
@@ -0,0 +1,12 @@
1
+ version: ~> 1.0
2
+ language: node_js
3
+ os:
4
+ - linux
5
+ import:
6
+ - ljharb/travis-ci:node/all.yml
7
+ - ljharb/travis-ci:node/pretest.yml
8
+ - ljharb/travis-ci:node/posttest.yml
9
+ - ljharb/travis-ci:node/coverage.yml
10
+ matrix:
11
+ allow_failures:
12
+ - env: COVERAGE=true # temporarily allow this to fail
package/CHANGELOG.md CHANGED
@@ -1,3 +1,23 @@
1
+ ## **6.8.4**
2
+ - [Robustness] avoid `.push`, use `void`
3
+ - [readme] clarify `parseArrays` and `arrayLimit` documentation (#543)
4
+ - [readme] document that `addQueryPrefix` does not add `?` to empty output (#418)
5
+ - [readme] replace runkit CI badge with shields.io check-runs badge
6
+ - [actions] fix rebase workflow permissions
7
+
8
+ ## **6.8.3**
9
+ - [Fix] `parse`: ignore `__proto__` keys (#428)
10
+ - [Robustness] `stringify`: avoid relying on a global `undefined` (#427)
11
+ - [Fix] `stringify`: avoid encoding arrayformat comma when `encodeValuesOnly = true` (#424)
12
+ - [readme] remove travis badge; add github actions/codecov badges; update URLs
13
+ - [Tests] clean up stringify tests slightly
14
+ - [Docs] add note and links for coercing primitive values (#408)
15
+ - [meta] fix README.md (#399)
16
+ - [actions] backport actions from main
17
+ - [Dev Deps] backport updates from main
18
+ - [Refactor] `stringify`: reduce branching
19
+ - [meta] do not publish workflow files
20
+
1
21
  ## **6.8.2**
2
22
  - [Fix] proper comma parsing of URL-encoded commas (#361)
3
23
  - [Fix] parses comma delimited array while having percent-encoded comma treated as normal text (#336)
package/README.md CHANGED
@@ -1,12 +1,13 @@
1
1
  # qs <sup>[![Version Badge][2]][1]</sup>
2
2
 
3
- [![Build Status][3]][4]
4
- [![dependency status][5]][6]
5
- [![dev dependency status][7]][8]
3
+ [![github actions][actions-image]][actions-url]
4
+ [![coverage][codecov-image]][codecov-url]
5
+ [![dependency status][deps-svg]][deps-url]
6
+ [![dev dependency status][dev-deps-svg]][dev-deps-url]
6
7
  [![License][license-image]][license-url]
7
8
  [![Downloads][downloads-image]][downloads-url]
8
9
 
9
- [![npm badge][11]][1]
10
+ [![npm badge][npm-badge-png]][package-url]
10
11
 
11
12
  A querystring parsing and stringifying library with some added security.
12
13
 
@@ -237,7 +238,7 @@ var withIndexedEmptyString = qs.parse('a[0]=b&a[1]=&a[2]=c');
237
238
  assert.deepEqual(withIndexedEmptyString, { a: ['b', '', 'c'] });
238
239
  ```
239
240
 
240
- **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
241
+ **qs** will also limit arrays to a maximum of `20` elements. Any array members with an index of `20` or greater will
241
242
  instead be converted to an object with the index as the key. This is needed to handle cases when someone sent, for example, `a[999999999]` and it will take significant time to iterate over this huge array.
242
243
 
243
244
  ```javascript
@@ -252,7 +253,7 @@ var withArrayLimit = qs.parse('a[1]=b', { arrayLimit: 0 });
252
253
  assert.deepEqual(withArrayLimit, { a: { '1': 'b' } });
253
254
  ```
254
255
 
255
- To disable array parsing entirely, set `parseArrays` to `false`.
256
+ To prevent array syntax (`a[]`, `a[0]`) from being parsed as arrays, set `parseArrays` to `false`.
256
257
 
257
258
  ```javascript
258
259
  var noParsingArrays = qs.parse('a[]=b', { parseArrays: false });
@@ -280,6 +281,17 @@ assert.deepEqual(arraysOfObjects, { a: ['b', 'c'] })
280
281
  ```
281
282
  (_this cannot convert nested objects, such as `a={b:1},{c:d}`_)
282
283
 
284
+ ### Parsing primitive/scalar values (numbers, booleans, null, etc)
285
+
286
+ By default, all values are parsed as strings. This behavior will not change and is explained in [issue #91](https://github.com/ljharb/qs/issues/91).
287
+
288
+ ```javascript
289
+ var primitiveValues = qs.parse('a=15&b=true&c=null');
290
+ assert.deepEqual(primitiveValues, { a: '15', b: 'true', c: 'null' });
291
+ ```
292
+
293
+ If you wish to auto-convert values which look like numbers, booleans, and other values into their primitive counterparts, you can use the [query-types Express JS middleware](https://github.com/xpepermint/query-types) which will auto-convert all request query parameters.
294
+
283
295
  ### Stringifying
284
296
 
285
297
  [](#preventEval)
@@ -330,6 +342,30 @@ var decoded = qs.parse('x=z', { decoder: function (str) {
330
342
  }})
331
343
  ```
332
344
 
345
+ You can encode keys and values using different logic by using the type argument provided to the encoder:
346
+
347
+ ```javascript
348
+ var encoded = qs.stringify({ a: { b: 'c' } }, { encoder: function (str, defaultEncoder, charset, type) {
349
+ if (type === 'key') {
350
+ return // Encoded key
351
+ } else if (type === 'value') {
352
+ return // Encoded value
353
+ }
354
+ }})
355
+ ```
356
+
357
+ The type argument is also provided to the decoder:
358
+
359
+ ```javascript
360
+ var decoded = qs.parse('x=z', { decoder: function (str, defaultDecoder, charset, type) {
361
+ if (type === 'key') {
362
+ return // Decoded key
363
+ } else if (type === 'value') {
364
+ return // Decoded value
365
+ }
366
+ }})
367
+ ```
368
+
333
369
  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.
334
370
 
335
371
  When arrays are stringified, by default they are given explicit indices:
@@ -401,6 +437,12 @@ The query string may optionally be prepended with a question mark:
401
437
  assert.equal(qs.stringify({ a: 'b', c: 'd' }, { addQueryPrefix: true }), '?a=b&c=d');
402
438
  ```
403
439
 
440
+ Note that when the output is an empty string, the prefix will not be added:
441
+
442
+ ```javascript
443
+ assert.equal(qs.stringify({}, { addQueryPrefix: true }), '');
444
+ ```
445
+
404
446
  The delimiter may be overridden with stringify as well:
405
447
 
406
448
  ```javascript
@@ -563,18 +605,18 @@ Available as part of the Tidelift Subscription
563
605
 
564
606
  The maintainers of qs and thousands of other packages are working with Tidelift to deliver commercial support and maintenance for the open source dependencies you use to build your applications. Save time, reduce risk, and improve code health, while paying the maintainers of the exact dependencies you use. [Learn more.](https://tidelift.com/subscription/pkg/npm-qs?utm_source=npm-qs&utm_medium=referral&utm_campaign=enterprise&utm_term=repo)
565
607
 
566
- [1]: https://npmjs.org/package/qs
567
- [2]: http://versionbadg.es/ljharb/qs.svg
568
- [3]: https://api.travis-ci.org/ljharb/qs.svg
569
- [4]: https://travis-ci.org/ljharb/qs
570
- [5]: https://david-dm.org/ljharb/qs.svg
571
- [6]: https://david-dm.org/ljharb/qs
572
- [7]: https://david-dm.org/ljharb/qs/dev-status.svg
573
- [8]: https://david-dm.org/ljharb/qs?type=dev
574
- [9]: https://ci.testling.com/ljharb/qs.png
575
- [10]: https://ci.testling.com/ljharb/qs
576
- [11]: https://nodei.co/npm/qs.png?downloads=true&stars=true
577
- [license-image]: http://img.shields.io/npm/l/qs.svg
608
+ [package-url]: https://npmjs.org/package/qs
609
+ [npm-version-svg]: https://versionbadg.es/ljharb/qs.svg
610
+ [deps-svg]: https://david-dm.org/ljharb/qs.svg
611
+ [deps-url]: https://david-dm.org/ljharb/qs
612
+ [dev-deps-svg]: https://david-dm.org/ljharb/qs/dev-status.svg
613
+ [dev-deps-url]: https://david-dm.org/ljharb/qs#info=devDependencies
614
+ [npm-badge-png]: https://nodei.co/npm/qs.png?downloads=true&stars=true
615
+ [license-image]: https://img.shields.io/npm/l/qs.svg
578
616
  [license-url]: LICENSE
579
- [downloads-image]: http://img.shields.io/npm/dm/qs.svg
580
- [downloads-url]: http://npm-stat.com/charts.html?package=qs
617
+ [downloads-image]: https://img.shields.io/npm/dm/qs.svg
618
+ [downloads-url]: https://npm-stat.com/charts.html?package=qs
619
+ [codecov-image]: https://codecov.io/gh/ljharb/qs/branch/main/graphs/badge.svg
620
+ [codecov-url]: https://app.codecov.io/gh/ljharb/qs/
621
+ [actions-image]: https://img.shields.io/github/check-runs/ljharb/qs/main
622
+ [actions-url]: https://github.com/ljharb/qs/actions
package/dist/qs.js CHANGED
@@ -83,7 +83,7 @@ var maybeMap = function maybeMap(val, fn) {
83
83
  if (isArray(val)) {
84
84
  var mapped = [];
85
85
  for (var i = 0; i < val.length; i += 1) {
86
- mapped.push(fn(val[i]));
86
+ mapped[mapped.length] = fn(val[i]);
87
87
  }
88
88
  return mapped;
89
89
  }
@@ -103,7 +103,7 @@ var charsetSentinel = 'utf8=%E2%9C%93'; // encodeURIComponent('✓')
103
103
  var parseValues = function parseQueryStringValues(str, options) {
104
104
  var obj = {};
105
105
  var cleanStr = options.ignoreQueryPrefix ? str.replace(/^\?/, '') : str;
106
- var limit = options.parameterLimit === Infinity ? undefined : options.parameterLimit;
106
+ var limit = options.parameterLimit === Infinity ? void undefined : options.parameterLimit;
107
107
  var parts = cleanStr.split(options.delimiter, limit);
108
108
  var skipIndex = -1; // Keep track of where the utf8 sentinel was found
109
109
  var i;
@@ -188,12 +188,12 @@ var parseObject = function (chain, val, options, valuesParsed) {
188
188
  ) {
189
189
  obj = [];
190
190
  obj[index] = leaf;
191
- } else {
191
+ } else if (cleanRoot !== '__proto__') {
192
192
  obj[cleanRoot] = leaf;
193
193
  }
194
194
  }
195
195
 
196
- leaf = obj; // eslint-disable-line no-param-reassign
196
+ leaf = obj;
197
197
  }
198
198
 
199
199
  return leaf;
@@ -228,7 +228,7 @@ var parseKeys = function parseQueryStringKeys(givenKey, val, options, valuesPars
228
228
  }
229
229
  }
230
230
 
231
- keys.push(parent);
231
+ keys[keys.length] = parent;
232
232
  }
233
233
 
234
234
  // Loop through children appending to the array until we hit depth
@@ -241,13 +241,13 @@ var parseKeys = function parseQueryStringKeys(givenKey, val, options, valuesPars
241
241
  return;
242
242
  }
243
243
  }
244
- keys.push(segment[1]);
244
+ keys[keys.length] = segment[1];
245
245
  }
246
246
 
247
247
  // If there's a remainder, just add whatever is left
248
248
 
249
249
  if (segment) {
250
- keys.push('[' + key.slice(segment.index) + ']');
250
+ keys[keys.length] = '[' + key.slice(segment.index + ']');
251
251
  }
252
252
 
253
253
  return parseObject(keys, val, options, valuesParsed);
@@ -330,6 +330,7 @@ var arrayPrefixGenerators = {
330
330
  };
331
331
 
332
332
  var isArray = Array.isArray;
333
+ var split = String.prototype.split;
333
334
  var push = Array.prototype.push;
334
335
  var pushToArray = function (arr, valueOrArray) {
335
336
  push.apply(arr, isArray(valueOrArray) ? valueOrArray : [valueOrArray]);
@@ -363,7 +364,7 @@ var isNonNullishPrimitive = function isNonNullishPrimitive(v) {
363
364
  || typeof v === 'number'
364
365
  || typeof v === 'boolean'
365
366
  || typeof v === 'symbol'
366
- || typeof v === 'bigint'; // eslint-disable-line valid-typeof
367
+ || typeof v === 'bigint';
367
368
  };
368
369
 
369
370
  var stringify = function stringify(
@@ -401,6 +402,14 @@ var stringify = function stringify(
401
402
  if (isNonNullishPrimitive(obj) || utils.isBuffer(obj)) {
402
403
  if (encoder) {
403
404
  var keyValue = encodeValuesOnly ? prefix : encoder(prefix, defaults.encoder, charset);
405
+ if (generateArrayPrefix === 'comma' && encodeValuesOnly) {
406
+ var valuesArray = split.call(String(obj), ',');
407
+ var valuesJoined = '';
408
+ for (var i = 0; i < valuesArray.length; ++i) {
409
+ valuesJoined += (i === 0 ? '' : ',') + formatter(encoder(valuesArray[i], defaults.encoder, charset));
410
+ }
411
+ return [formatter(keyValue) + '=' + valuesJoined];
412
+ }
404
413
  return [formatter(keyValue) + '=' + formatter(encoder(obj, defaults.encoder, charset))];
405
414
  }
406
415
  return [formatter(prefix) + '=' + formatter(String(obj))];
@@ -420,46 +429,33 @@ var stringify = function stringify(
420
429
  objKeys = sort ? keys.sort(sort) : keys;
421
430
  }
422
431
 
423
- for (var i = 0; i < objKeys.length; ++i) {
424
- var key = objKeys[i];
432
+ for (var j = 0; j < objKeys.length; ++j) {
433
+ var key = objKeys[j];
434
+ var value = typeof key === 'object' && typeof key.value !== 'undefined' ? key.value : obj[key];
425
435
 
426
- if (skipNulls && obj[key] === null) {
436
+ if (skipNulls && value === null) {
427
437
  continue;
428
438
  }
429
439
 
430
- if (isArray(obj)) {
431
- pushToArray(values, stringify(
432
- obj[key],
433
- typeof generateArrayPrefix === 'function' ? generateArrayPrefix(prefix, key) : prefix,
434
- generateArrayPrefix,
435
- strictNullHandling,
436
- skipNulls,
437
- encoder,
438
- filter,
439
- sort,
440
- allowDots,
441
- serializeDate,
442
- formatter,
443
- encodeValuesOnly,
444
- charset
445
- ));
446
- } else {
447
- pushToArray(values, stringify(
448
- obj[key],
449
- prefix + (allowDots ? '.' + key : '[' + key + ']'),
450
- generateArrayPrefix,
451
- strictNullHandling,
452
- skipNulls,
453
- encoder,
454
- filter,
455
- sort,
456
- allowDots,
457
- serializeDate,
458
- formatter,
459
- encodeValuesOnly,
460
- charset
461
- ));
462
- }
440
+ var keyPrefix = isArray(obj)
441
+ ? typeof generateArrayPrefix === 'function' ? generateArrayPrefix(prefix, key) : prefix
442
+ : prefix + (allowDots ? '.' + key : '[' + key + ']');
443
+
444
+ pushToArray(values, stringify(
445
+ value,
446
+ keyPrefix,
447
+ generateArrayPrefix,
448
+ strictNullHandling,
449
+ skipNulls,
450
+ encoder,
451
+ filter,
452
+ sort,
453
+ allowDots,
454
+ serializeDate,
455
+ formatter,
456
+ encodeValuesOnly,
457
+ charset
458
+ ));
463
459
  }
464
460
 
465
461
  return values;
@@ -470,7 +466,7 @@ var normalizeStringifyOptions = function normalizeStringifyOptions(opts) {
470
466
  return defaults;
471
467
  }
472
468
 
473
- if (opts.encoder !== null && opts.encoder !== undefined && typeof opts.encoder !== 'function') {
469
+ if (opts.encoder !== null && typeof opts.encoder !== 'undefined' && typeof opts.encoder !== 'function') {
474
470
  throw new TypeError('Encoder has to be a function.');
475
471
  }
476
472
 
@@ -599,7 +595,7 @@ var isArray = Array.isArray;
599
595
  var hexTable = (function () {
600
596
  var array = [];
601
597
  for (var i = 0; i < 256; ++i) {
602
- array.push('%' + ((i < 16 ? '0' : '') + i.toString(16)).toUpperCase());
598
+ array[array.length] = '%' + ((i < 16 ? '0' : '' + i.toString(16)).toUpperCase());
603
599
  }
604
600
 
605
601
  return array;
@@ -615,7 +611,7 @@ var compactQueue = function compactQueue(queue) {
615
611
 
616
612
  for (var j = 0; j < obj.length; ++j) {
617
613
  if (typeof obj[j] !== 'undefined') {
618
- compacted.push(obj[j]);
614
+ compacted[compacted.length] = obj[j];
619
615
  }
620
616
  }
621
617
 
@@ -642,7 +638,7 @@ var merge = function merge(target, source, options) {
642
638
 
643
639
  if (typeof source !== 'object') {
644
640
  if (isArray(target)) {
645
- target.push(source);
641
+ target[target.length] = source;
646
642
  } else if (target && typeof target === 'object') {
647
643
  if ((options && (options.plainObjects || options.allowPrototypes)) || !has.call(Object.prototype, source)) {
648
644
  target[source] = true; // eslint-disable-line no-param-reassign
@@ -670,7 +666,7 @@ var merge = function merge(target, source, options) {
670
666
  if (targetItem && typeof targetItem === 'object' && item && typeof item === 'object') {
671
667
  target[i] = merge(targetItem, item, options); // eslint-disable-line no-param-reassign
672
668
  } else {
673
- target.push(item);
669
+ target[target.length] = item;
674
670
  }
675
671
  } else {
676
672
  target[i] = item; // eslint-disable-line no-param-reassign
@@ -766,6 +762,7 @@ var encode = function encode(str, defaultEncoder, charset) {
766
762
 
767
763
  i += 1;
768
764
  c = 0x10000 + (((c & 0x3FF) << 10) | (string.charCodeAt(i) & 0x3FF));
765
+ /* eslint operator-linebreak: [2, "before"] */
769
766
  out += hexTable[0xF0 | (c >> 18)]
770
767
  + hexTable[0x80 | ((c >> 12) & 0x3F)]
771
768
  + hexTable[0x80 | ((c >> 6) & 0x3F)]
@@ -788,8 +785,8 @@ var compact = function compact(value) {
788
785
  var key = keys[j];
789
786
  var val = obj[key];
790
787
  if (typeof val === 'object' && val !== null && refs.indexOf(val) === -1) {
791
- queue.push({ obj: obj, prop: key });
792
- refs.push(val);
788
+ queue[queue.length] = { obj: obj, prop: key };
789
+ refs[refs.length] = val;
793
790
  }
794
791
  }
795
792
  }
package/lib/parse.js CHANGED
@@ -41,7 +41,7 @@ var maybeMap = function maybeMap(val, fn) {
41
41
  if (isArray(val)) {
42
42
  var mapped = [];
43
43
  for (var i = 0; i < val.length; i += 1) {
44
- mapped.push(fn(val[i]));
44
+ mapped[mapped.length] = fn(val[i]);
45
45
  }
46
46
  return mapped;
47
47
  }
@@ -61,7 +61,7 @@ var charsetSentinel = 'utf8=%E2%9C%93'; // encodeURIComponent('✓')
61
61
  var parseValues = function parseQueryStringValues(str, options) {
62
62
  var obj = {};
63
63
  var cleanStr = options.ignoreQueryPrefix ? str.replace(/^\?/, '') : str;
64
- var limit = options.parameterLimit === Infinity ? undefined : options.parameterLimit;
64
+ var limit = options.parameterLimit === Infinity ? void undefined : options.parameterLimit;
65
65
  var parts = cleanStr.split(options.delimiter, limit);
66
66
  var skipIndex = -1; // Keep track of where the utf8 sentinel was found
67
67
  var i;
@@ -146,12 +146,12 @@ var parseObject = function (chain, val, options, valuesParsed) {
146
146
  ) {
147
147
  obj = [];
148
148
  obj[index] = leaf;
149
- } else {
149
+ } else if (cleanRoot !== '__proto__') {
150
150
  obj[cleanRoot] = leaf;
151
151
  }
152
152
  }
153
153
 
154
- leaf = obj; // eslint-disable-line no-param-reassign
154
+ leaf = obj;
155
155
  }
156
156
 
157
157
  return leaf;
@@ -186,7 +186,7 @@ var parseKeys = function parseQueryStringKeys(givenKey, val, options, valuesPars
186
186
  }
187
187
  }
188
188
 
189
- keys.push(parent);
189
+ keys[keys.length] = parent;
190
190
  }
191
191
 
192
192
  // Loop through children appending to the array until we hit depth
@@ -199,13 +199,13 @@ var parseKeys = function parseQueryStringKeys(givenKey, val, options, valuesPars
199
199
  return;
200
200
  }
201
201
  }
202
- keys.push(segment[1]);
202
+ keys[keys.length] = segment[1];
203
203
  }
204
204
 
205
205
  // If there's a remainder, just add whatever is left
206
206
 
207
207
  if (segment) {
208
- keys.push('[' + key.slice(segment.index) + ']');
208
+ keys[keys.length] = '[' + key.slice(segment.index + ']');
209
209
  }
210
210
 
211
211
  return parseObject(keys, val, options, valuesParsed);
package/lib/stringify.js CHANGED
@@ -18,6 +18,7 @@ var arrayPrefixGenerators = {
18
18
  };
19
19
 
20
20
  var isArray = Array.isArray;
21
+ var split = String.prototype.split;
21
22
  var push = Array.prototype.push;
22
23
  var pushToArray = function (arr, valueOrArray) {
23
24
  push.apply(arr, isArray(valueOrArray) ? valueOrArray : [valueOrArray]);
@@ -51,7 +52,7 @@ var isNonNullishPrimitive = function isNonNullishPrimitive(v) {
51
52
  || typeof v === 'number'
52
53
  || typeof v === 'boolean'
53
54
  || typeof v === 'symbol'
54
- || typeof v === 'bigint'; // eslint-disable-line valid-typeof
55
+ || typeof v === 'bigint';
55
56
  };
56
57
 
57
58
  var stringify = function stringify(
@@ -89,6 +90,14 @@ var stringify = function stringify(
89
90
  if (isNonNullishPrimitive(obj) || utils.isBuffer(obj)) {
90
91
  if (encoder) {
91
92
  var keyValue = encodeValuesOnly ? prefix : encoder(prefix, defaults.encoder, charset);
93
+ if (generateArrayPrefix === 'comma' && encodeValuesOnly) {
94
+ var valuesArray = split.call(String(obj), ',');
95
+ var valuesJoined = '';
96
+ for (var i = 0; i < valuesArray.length; ++i) {
97
+ valuesJoined += (i === 0 ? '' : ',') + formatter(encoder(valuesArray[i], defaults.encoder, charset));
98
+ }
99
+ return [formatter(keyValue) + '=' + valuesJoined];
100
+ }
92
101
  return [formatter(keyValue) + '=' + formatter(encoder(obj, defaults.encoder, charset))];
93
102
  }
94
103
  return [formatter(prefix) + '=' + formatter(String(obj))];
@@ -108,46 +117,33 @@ var stringify = function stringify(
108
117
  objKeys = sort ? keys.sort(sort) : keys;
109
118
  }
110
119
 
111
- for (var i = 0; i < objKeys.length; ++i) {
112
- var key = objKeys[i];
120
+ for (var j = 0; j < objKeys.length; ++j) {
121
+ var key = objKeys[j];
122
+ var value = typeof key === 'object' && typeof key.value !== 'undefined' ? key.value : obj[key];
113
123
 
114
- if (skipNulls && obj[key] === null) {
124
+ if (skipNulls && value === null) {
115
125
  continue;
116
126
  }
117
127
 
118
- if (isArray(obj)) {
119
- pushToArray(values, stringify(
120
- obj[key],
121
- typeof generateArrayPrefix === 'function' ? generateArrayPrefix(prefix, key) : prefix,
122
- generateArrayPrefix,
123
- strictNullHandling,
124
- skipNulls,
125
- encoder,
126
- filter,
127
- sort,
128
- allowDots,
129
- serializeDate,
130
- formatter,
131
- encodeValuesOnly,
132
- charset
133
- ));
134
- } else {
135
- pushToArray(values, stringify(
136
- obj[key],
137
- prefix + (allowDots ? '.' + key : '[' + key + ']'),
138
- generateArrayPrefix,
139
- strictNullHandling,
140
- skipNulls,
141
- encoder,
142
- filter,
143
- sort,
144
- allowDots,
145
- serializeDate,
146
- formatter,
147
- encodeValuesOnly,
148
- charset
149
- ));
150
- }
128
+ var keyPrefix = isArray(obj)
129
+ ? typeof generateArrayPrefix === 'function' ? generateArrayPrefix(prefix, key) : prefix
130
+ : prefix + (allowDots ? '.' + key : '[' + key + ']');
131
+
132
+ pushToArray(values, stringify(
133
+ value,
134
+ keyPrefix,
135
+ generateArrayPrefix,
136
+ strictNullHandling,
137
+ skipNulls,
138
+ encoder,
139
+ filter,
140
+ sort,
141
+ allowDots,
142
+ serializeDate,
143
+ formatter,
144
+ encodeValuesOnly,
145
+ charset
146
+ ));
151
147
  }
152
148
 
153
149
  return values;
@@ -158,7 +154,7 @@ var normalizeStringifyOptions = function normalizeStringifyOptions(opts) {
158
154
  return defaults;
159
155
  }
160
156
 
161
- if (opts.encoder !== null && opts.encoder !== undefined && typeof opts.encoder !== 'function') {
157
+ if (opts.encoder !== null && typeof opts.encoder !== 'undefined' && typeof opts.encoder !== 'function') {
162
158
  throw new TypeError('Encoder has to be a function.');
163
159
  }
164
160
 
package/lib/utils.js CHANGED
@@ -6,7 +6,7 @@ var isArray = Array.isArray;
6
6
  var hexTable = (function () {
7
7
  var array = [];
8
8
  for (var i = 0; i < 256; ++i) {
9
- array.push('%' + ((i < 16 ? '0' : '') + i.toString(16)).toUpperCase());
9
+ array[array.length] = '%' + ((i < 16 ? '0' : '' + i.toString(16)).toUpperCase());
10
10
  }
11
11
 
12
12
  return array;
@@ -22,7 +22,7 @@ var compactQueue = function compactQueue(queue) {
22
22
 
23
23
  for (var j = 0; j < obj.length; ++j) {
24
24
  if (typeof obj[j] !== 'undefined') {
25
- compacted.push(obj[j]);
25
+ compacted[compacted.length] = obj[j];
26
26
  }
27
27
  }
28
28
 
@@ -49,7 +49,7 @@ var merge = function merge(target, source, options) {
49
49
 
50
50
  if (typeof source !== 'object') {
51
51
  if (isArray(target)) {
52
- target.push(source);
52
+ target[target.length] = source;
53
53
  } else if (target && typeof target === 'object') {
54
54
  if ((options && (options.plainObjects || options.allowPrototypes)) || !has.call(Object.prototype, source)) {
55
55
  target[source] = true; // eslint-disable-line no-param-reassign
@@ -77,7 +77,7 @@ var merge = function merge(target, source, options) {
77
77
  if (targetItem && typeof targetItem === 'object' && item && typeof item === 'object') {
78
78
  target[i] = merge(targetItem, item, options); // eslint-disable-line no-param-reassign
79
79
  } else {
80
- target.push(item);
80
+ target[target.length] = item;
81
81
  }
82
82
  } else {
83
83
  target[i] = item; // eslint-disable-line no-param-reassign
@@ -173,6 +173,7 @@ var encode = function encode(str, defaultEncoder, charset) {
173
173
 
174
174
  i += 1;
175
175
  c = 0x10000 + (((c & 0x3FF) << 10) | (string.charCodeAt(i) & 0x3FF));
176
+ /* eslint operator-linebreak: [2, "before"] */
176
177
  out += hexTable[0xF0 | (c >> 18)]
177
178
  + hexTable[0x80 | ((c >> 12) & 0x3F)]
178
179
  + hexTable[0x80 | ((c >> 6) & 0x3F)]
@@ -195,8 +196,8 @@ var compact = function compact(value) {
195
196
  var key = keys[j];
196
197
  var val = obj[key];
197
198
  if (typeof val === 'object' && val !== null && refs.indexOf(val) === -1) {
198
- queue.push({ obj: obj, prop: key });
199
- refs.push(val);
199
+ queue[queue.length] = { obj: obj, prop: key };
200
+ refs[refs.length] = val;
200
201
  }
201
202
  }
202
203
  }
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "qs",
3
3
  "description": "A querystring parser that supports nesting and arrays, with a depth limit",
4
4
  "homepage": "https://github.com/ljharb/qs",
5
- "version": "6.8.2",
5
+ "version": "6.8.4",
6
6
  "repository": {
7
7
  "type": "git",
8
8
  "url": "https://github.com/ljharb/qs.git"
@@ -29,33 +29,34 @@
29
29
  "engines": {
30
30
  "node": ">=0.6"
31
31
  },
32
- "dependencies": {},
33
32
  "devDependencies": {
34
- "@ljharb/eslint-config": "^16.0.0",
35
- "browserify": "^16.5.0",
33
+ "@ljharb/eslint-config": "^20.1.0",
34
+ "browserify": "^16.5.2",
36
35
  "covert": "^1.1.1",
37
36
  "eclint": "^2.8.1",
38
- "eslint": "^6.8.0",
37
+ "eslint": "^8.6.0",
39
38
  "evalmd": "^0.0.19",
40
39
  "for-each": "^0.3.3",
41
- "has-symbols": "^1.0.1",
40
+ "has-symbols": "^1.0.2",
42
41
  "iconv-lite": "^0.5.1",
42
+ "in-publish": "^2.0.1",
43
43
  "mkdirp": "^0.5.4",
44
- "object-inspect": "^1.7.0",
44
+ "object-inspect": "^1.12.0",
45
45
  "qs-iconv": "^1.0.4",
46
- "safe-publish-latest": "^1.1.4",
46
+ "safe-publish-latest": "^2.0.0",
47
47
  "safer-buffer": "^2.1.2",
48
- "tape": "^5.0.0-next.5"
48
+ "tape": "^5.4.0"
49
49
  },
50
50
  "scripts": {
51
- "prepublish": "safe-publish-latest && npm run dist",
51
+ "prepublishOnly": "safe-publish-latest && npm run dist",
52
+ "prepublish": "not-in-publish || npm run prepublishOnly",
52
53
  "pretest": "npm run --silent readme && npm run --silent lint",
53
54
  "test": "npm run --silent coverage",
54
55
  "tests-only": "node test",
55
56
  "posttest": "npx aud --production",
56
57
  "readme": "evalmd README.md",
57
- "postlint": "eclint check * lib/* test/*",
58
- "lint": "eslint lib/*.js test/*.js",
58
+ "postlint": "eclint check * lib/* test/* !dist/* '!coverage/**' '.nyc_output/**'",
59
+ "lint": "eslint .",
59
60
  "coverage": "covert test",
60
61
  "dist": "mkdirp dist && browserify --standalone Qs lib/index.js > dist/qs.js"
61
62
  },
package/test/parse.js CHANGED
@@ -620,6 +620,66 @@ test('parse()', function (t) {
620
620
  st.end();
621
621
  });
622
622
 
623
+ t.test('dunder proto is ignored', function (st) {
624
+ var payload = 'categories[__proto__]=login&categories[__proto__]&categories[length]=42';
625
+ var result = qs.parse(payload, { allowPrototypes: true });
626
+
627
+ st.deepEqual(
628
+ result,
629
+ {
630
+ categories: {
631
+ length: '42'
632
+ }
633
+ },
634
+ 'silent [[Prototype]] payload'
635
+ );
636
+
637
+ var plainResult = qs.parse(payload, { allowPrototypes: true, plainObjects: true });
638
+
639
+ st.deepEqual(
640
+ plainResult,
641
+ {
642
+ __proto__: null,
643
+ categories: {
644
+ __proto__: null,
645
+ length: '42'
646
+ }
647
+ },
648
+ 'silent [[Prototype]] payload: plain objects'
649
+ );
650
+
651
+ var query = qs.parse('categories[__proto__]=cats&categories[__proto__]=dogs&categories[some][json]=toInject', { allowPrototypes: true });
652
+
653
+ st.notOk(Array.isArray(query.categories), 'is not an array');
654
+ st.notOk(query.categories instanceof Array, 'is not instanceof an array');
655
+ st.deepEqual(query.categories, { some: { json: 'toInject' } });
656
+ st.equal(JSON.stringify(query.categories), '{"some":{"json":"toInject"}}', 'stringifies as a non-array');
657
+
658
+ st.deepEqual(
659
+ qs.parse('foo[__proto__][hidden]=value&foo[bar]=stuffs', { allowPrototypes: true }),
660
+ {
661
+ foo: {
662
+ bar: 'stuffs'
663
+ }
664
+ },
665
+ 'hidden values'
666
+ );
667
+
668
+ st.deepEqual(
669
+ qs.parse('foo[__proto__][hidden]=value&foo[bar]=stuffs', { allowPrototypes: true, plainObjects: true }),
670
+ {
671
+ __proto__: null,
672
+ foo: {
673
+ __proto__: null,
674
+ bar: 'stuffs'
675
+ }
676
+ },
677
+ 'hidden values: plain objects'
678
+ );
679
+
680
+ st.end();
681
+ });
682
+
623
683
  t.test('can return null objects', { skip: !Object.create }, function (st) {
624
684
  var expected = Object.create(null);
625
685
  expected.a = Object.create(null);
package/test/stringify.js CHANGED
@@ -132,10 +132,10 @@ test('stringify()', function (t) {
132
132
  });
133
133
 
134
134
  t.test('stringifies a nested array value', function (st) {
135
- st.equal(qs.stringify({ a: { b: ['c', 'd'] } }, { arrayFormat: 'indices' }), 'a%5Bb%5D%5B0%5D=c&a%5Bb%5D%5B1%5D=d');
136
- st.equal(qs.stringify({ a: { b: ['c', 'd'] } }, { arrayFormat: 'brackets' }), 'a%5Bb%5D%5B%5D=c&a%5Bb%5D%5B%5D=d');
137
- st.equal(qs.stringify({ a: { b: ['c', 'd'] } }, { arrayFormat: 'comma' }), 'a%5Bb%5D=c%2Cd'); // a[b]=c,d
138
- st.equal(qs.stringify({ a: { b: ['c', 'd'] } }), 'a%5Bb%5D%5B0%5D=c&a%5Bb%5D%5B1%5D=d');
135
+ st.equal(qs.stringify({ a: { b: ['c', 'd'] } }, { encodeValuesOnly: true, arrayFormat: 'indices' }), 'a[b][0]=c&a[b][1]=d');
136
+ st.equal(qs.stringify({ a: { b: ['c', 'd'] } }, { encodeValuesOnly: true, arrayFormat: 'brackets' }), 'a[b][]=c&a[b][]=d');
137
+ st.equal(qs.stringify({ a: { b: ['c', 'd'] } }, { encodeValuesOnly: true, arrayFormat: 'comma' }), 'a[b]=c,d');
138
+ st.equal(qs.stringify({ a: { b: ['c', 'd'] } }, { encodeValuesOnly: true }), 'a[b][0]=c&a[b][1]=d');
139
139
  st.end();
140
140
  });
141
141
 
@@ -143,7 +143,7 @@ test('stringify()', function (t) {
143
143
  st.equal(
144
144
  qs.stringify(
145
145
  { a: { b: ['c', 'd'] } },
146
- { allowDots: true, encode: false, arrayFormat: 'indices' }
146
+ { allowDots: true, encodeValuesOnly: true, arrayFormat: 'indices' }
147
147
  ),
148
148
  'a.b[0]=c&a.b[1]=d',
149
149
  'indices: stringifies with dots + indices'
@@ -151,7 +151,7 @@ test('stringify()', function (t) {
151
151
  st.equal(
152
152
  qs.stringify(
153
153
  { a: { b: ['c', 'd'] } },
154
- { allowDots: true, encode: false, arrayFormat: 'brackets' }
154
+ { allowDots: true, encodeValuesOnly: true, arrayFormat: 'brackets' }
155
155
  ),
156
156
  'a.b[]=c&a.b[]=d',
157
157
  'brackets: stringifies with dots + brackets'
@@ -159,7 +159,7 @@ test('stringify()', function (t) {
159
159
  st.equal(
160
160
  qs.stringify(
161
161
  { a: { b: ['c', 'd'] } },
162
- { allowDots: true, encode: false, arrayFormat: 'comma' }
162
+ { allowDots: true, encodeValuesOnly: true, arrayFormat: 'comma' }
163
163
  ),
164
164
  'a.b=c,d',
165
165
  'comma: stringifies with dots + comma'
@@ -167,7 +167,7 @@ test('stringify()', function (t) {
167
167
  st.equal(
168
168
  qs.stringify(
169
169
  { a: { b: ['c', 'd'] } },
170
- { allowDots: true, encode: false }
170
+ { allowDots: true, encodeValuesOnly: true }
171
171
  ),
172
172
  'a.b[0]=c&a.b[1]=d',
173
173
  'default: stringifies with dots + indices'
@@ -215,17 +215,23 @@ test('stringify()', function (t) {
215
215
 
216
216
  t.test('stringifies an array with mixed objects and primitives', function (st) {
217
217
  st.equal(
218
- qs.stringify({ a: [{ b: 1 }, 2, 3] }, { encode: false, arrayFormat: 'indices' }),
218
+ qs.stringify({ a: [{ b: 1 }, 2, 3] }, { encodeValuesOnly: true, arrayFormat: 'indices' }),
219
219
  'a[0][b]=1&a[1]=2&a[2]=3',
220
220
  'indices => indices'
221
221
  );
222
222
  st.equal(
223
- qs.stringify({ a: [{ b: 1 }, 2, 3] }, { encode: false, arrayFormat: 'brackets' }),
223
+ qs.stringify({ a: [{ b: 1 }, 2, 3] }, { encodeValuesOnly: true, arrayFormat: 'brackets' }),
224
224
  'a[][b]=1&a[]=2&a[]=3',
225
225
  'brackets => brackets'
226
226
  );
227
227
  st.equal(
228
- qs.stringify({ a: [{ b: 1 }, 2, 3] }, { encode: false }),
228
+ qs.stringify({ a: [{ b: 1 }, 2, 3] }, { encodeValuesOnly: true, arrayFormat: 'comma' }),
229
+ '???',
230
+ 'brackets => brackets',
231
+ { skip: 'TODO: figure out what this should do' }
232
+ );
233
+ st.equal(
234
+ qs.stringify({ a: [{ b: 1 }, 2, 3] }, { encodeValuesOnly: true }),
229
235
  'a[0][b]=1&a[1]=2&a[2]=3',
230
236
  'default => indices'
231
237
  );
package/.eslintignore DELETED
@@ -1 +0,0 @@
1
- dist
@@ -1,15 +0,0 @@
1
- name: Automatic Rebase
2
-
3
- on: [pull_request]
4
-
5
- jobs:
6
- _:
7
- name: "Automatic Rebase"
8
-
9
- runs-on: ubuntu-latest
10
-
11
- steps:
12
- - uses: actions/checkout@v1
13
- - uses: ljharb/rebase@master
14
- env:
15
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
package/test/.eslintrc DELETED
@@ -1,17 +0,0 @@
1
- {
2
- "rules": {
3
- "array-bracket-newline": 0,
4
- "array-element-newline": 0,
5
- "consistent-return": 2,
6
- "function-paren-newline": 0,
7
- "max-lines": 0,
8
- "max-lines-per-function": 0,
9
- "max-nested-callbacks": [2, 3],
10
- "max-statements": 0,
11
- "no-buffer-constructor": 0,
12
- "no-extend-native": 0,
13
- "no-magic-numbers": 0,
14
- "object-curly-newline": 0,
15
- "sort-keys": 0
16
- }
17
- }