qs 6.6.0 → 6.6.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,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
@@ -7,11 +7,15 @@ end_of_line = lf
7
7
  charset = utf-8
8
8
  trim_trailing_whitespace = true
9
9
  insert_final_newline = true
10
- max_line_length = 140
10
+ max_line_length = 160
11
+ quote_type = single
11
12
 
12
13
  [test/*]
13
14
  max_line_length = off
14
15
 
16
+ [LICENSE.md]
17
+ indent_size = off
18
+
15
19
  [*.md]
16
20
  max_line_length = off
17
21
 
@@ -28,3 +32,12 @@ indent_size = 2
28
32
  [LICENSE]
29
33
  indent_size = 2
30
34
  max_line_length = off
35
+
36
+ [coverage/**/*]
37
+ indent_size = off
38
+ indent_style = off
39
+ indent = off
40
+ max_line_length = off
41
+
42
+ [.nycrc]
43
+ indent_style = tab
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 }],
@@ -15,7 +19,20 @@
15
19
  "multiline-comment-style": 0,
16
20
  "no-continue": 1,
17
21
  "no-magic-numbers": 0,
22
+ "no-param-reassign": 1,
18
23
  "no-restricted-syntax": [2, "BreakStatement", "DebuggerStatement", "ForInStatement", "LabeledStatement", "WithStatement"],
19
24
  "operator-linebreak": [2, "before"],
20
- }
25
+ },
26
+
27
+ "overrides": [
28
+ {
29
+ "files": "test/**",
30
+ "rules": {
31
+ "max-lines-per-function": 0,
32
+ "max-statements": 0,
33
+ "no-extend-native": 0,
34
+ "function-paren-newline": 0,
35
+ },
36
+ },
37
+ ],
21
38
  }
@@ -0,0 +1,12 @@
1
+ # These are supported funding model platforms
2
+
3
+ github: [ljharb]
4
+ patreon: # Replace with a single Patreon username
5
+ open_collective: # Replace with a single Open Collective username
6
+ ko_fi: # Replace with a single Ko-fi username
7
+ tidelift: npm/qs
8
+ community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
9
+ liberapay: # Replace with a single Liberapay username
10
+ issuehunt: # Replace with a single IssueHunt username
11
+ otechie: # Replace with a single Otechie username
12
+ custom: # Replace with a single custom sponsorship URL
package/.nycrc ADDED
@@ -0,0 +1,13 @@
1
+ {
2
+ "all": true,
3
+ "check-coverage": false,
4
+ "reporter": ["text-summary", "text", "html", "json"],
5
+ "lines": 86,
6
+ "statements": 85.93,
7
+ "functions": 82.43,
8
+ "branches": 76.06,
9
+ "exclude": [
10
+ "coverage",
11
+ "dist"
12
+ ]
13
+ }
package/CHANGELOG.md CHANGED
@@ -1,3 +1,36 @@
1
+ ## **6.6.2**
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.6.1**
9
+ - [Fix] `parse`: ignore `__proto__` keys (#428)
10
+ - [Fix] fix for an impossible situation: when the formatter is called with a non-string value
11
+ - [Fix] `utils.merge`: avoid a crash with a null target and an array source
12
+ - [Fix] `utils.merge`: avoid a crash with a null target and a truthy non-array source
13
+ - [Fix] correctly parse nested arrays
14
+ - [Robustness] `stringify`: avoid relying on a global `undefined` (#427)
15
+ - [Robustness] `stringify`: cache `Object.prototype.hasOwnProperty`
16
+ - [Refactor] `formats`: tiny bit of cleanup.
17
+ - [Refactor] `utils`: `isBuffer`: small tweak; add tests
18
+ - [Refactor]: `stringify`/`utils`: cache `Array.isArray`
19
+ - [Refactor] `utils`: reduce observable [[Get]]s
20
+ - [Refactor] use cached `Array.isArray`
21
+ - [Refactor] `parse`/`stringify`: make a function to normalize the options
22
+ - [readme] remove travis badge; add github actions/codecov badges; update URLs
23
+ - [Docs] Clarify the need for "arrayLimit" option
24
+ - [meta] fix README.md (#399)
25
+ - [meta] do not publish workflow files
26
+ - [meta] Clean up license text so it’s properly detected as BSD-3-Clause
27
+ - [meta] add FUNDING.yml
28
+ - [meta] Fixes typo in CHANGELOG.md
29
+ - [actions] backport actions from main
30
+ - [Tests] fix Buffer tests to work in node < 4.5 and node < 5.10
31
+ - [Tests] always use `String(x)` over `x.toString()`
32
+ - [Dev Deps] backport from main
33
+
1
34
  ## **6.6.0**
2
35
  - [New] Add support for iso-8859-1, utf8 "sentinel" and numeric entities (#268)
3
36
  - [New] move two-value combine to a `utils` function (#189)
@@ -11,7 +44,7 @@
11
44
  - [Refactor] add missing defaults
12
45
  - [Refactor] `parse`: one less `concat` call
13
46
  - [Refactor] `utils`: `compactQueue`: make it explicitly side-effecting
14
- - [Dev Deps] update `browserify, `eslint`, `@ljharb/eslint-config`, `iconv-lite`, `safe-publish-latest`, `tape`
47
+ - [Dev Deps] update `browserify`, `eslint`, `@ljharb/eslint-config`, `iconv-lite`, `safe-publish-latest`, `tape`
15
48
  - [Tests] up to `node` `v10.10`, `v9.11`, `v8.12`, `v6.14`, `v4.9`; pin included builds to LTS
16
49
 
17
50
  ## **6.5.2**
package/LICENSE.md ADDED
@@ -0,0 +1,29 @@
1
+ BSD 3-Clause License
2
+
3
+ Copyright (c) 2014, Nathan LaFreniere and other [contributors](https://github.com/ljharb/qs/graphs/contributors)
4
+ All rights reserved.
5
+
6
+ Redistribution and use in source and binary forms, with or without
7
+ modification, are permitted provided that the following conditions are met:
8
+
9
+ 1. Redistributions of source code must retain the above copyright notice, this
10
+ list of conditions and the following disclaimer.
11
+
12
+ 2. Redistributions in binary form must reproduce the above copyright notice,
13
+ this list of conditions and the following disclaimer in the documentation
14
+ and/or other materials provided with the distribution.
15
+
16
+ 3. Neither the name of the copyright holder nor the names of its
17
+ contributors may be used to endorse or promote products derived from
18
+ this software without specific prior written permission.
19
+
20
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
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,8 +238,8 @@ 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
- instead be converted to an object with the index as the key:
241
+ **qs** will also limit arrays to a maximum of `20` elements. Any array members with an index of `20` or greater will
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
244
245
  var withMaxIndex = qs.parse('a[100]=b');
@@ -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 });
@@ -323,6 +324,30 @@ var decoded = qs.parse('x=z', { decoder: function (str) {
323
324
  }})
324
325
  ```
325
326
 
327
+ You can encode keys and values using different logic by using the type argument provided to the encoder:
328
+
329
+ ```javascript
330
+ var encoded = qs.stringify({ a: { b: 'c' } }, { encoder: function (str, defaultEncoder, charset, type) {
331
+ if (type === 'key') {
332
+ return // Encoded key
333
+ } else if (type === 'value') {
334
+ return // Encoded value
335
+ }
336
+ }})
337
+ ```
338
+
339
+ The type argument is also provided to the decoder:
340
+
341
+ ```javascript
342
+ var decoded = qs.parse('x=z', { decoder: function (str, defaultDecoder, charset, type) {
343
+ if (type === 'key') {
344
+ return // Decoded key
345
+ } else if (type === 'value') {
346
+ return // Decoded value
347
+ }
348
+ }})
349
+ ```
350
+
326
351
  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.
327
352
 
328
353
  When arrays are stringified, by default they are given explicit indices:
@@ -392,6 +417,12 @@ The query string may optionally be prepended with a question mark:
392
417
  assert.equal(qs.stringify({ a: 'b', c: 'd' }, { addQueryPrefix: true }), '?a=b&c=d');
393
418
  ```
394
419
 
420
+ Note that when the output is an empty string, the prefix will not be added:
421
+
422
+ ```javascript
423
+ assert.equal(qs.stringify({}, { addQueryPrefix: true }), '');
424
+ ```
425
+
395
426
  The delimiter may be overridden with stringify as well:
396
427
 
397
428
  ```javascript
@@ -544,18 +575,28 @@ assert.equal(qs.stringify({ a: 'b c' }, { format : 'RFC3986' }), 'a=b%20c');
544
575
  assert.equal(qs.stringify({ a: 'b c' }, { format : 'RFC1738' }), 'a=b+c');
545
576
  ```
546
577
 
547
- [1]: https://npmjs.org/package/qs
548
- [2]: http://versionbadg.es/ljharb/qs.svg
549
- [3]: https://api.travis-ci.org/ljharb/qs.svg
550
- [4]: https://travis-ci.org/ljharb/qs
551
- [5]: https://david-dm.org/ljharb/qs.svg
552
- [6]: https://david-dm.org/ljharb/qs
553
- [7]: https://david-dm.org/ljharb/qs/dev-status.svg
554
- [8]: https://david-dm.org/ljharb/qs?type=dev
555
- [9]: https://ci.testling.com/ljharb/qs.png
556
- [10]: https://ci.testling.com/ljharb/qs
557
- [11]: https://nodei.co/npm/qs.png?downloads=true&stars=true
558
- [license-image]: http://img.shields.io/npm/l/qs.svg
578
+ ## Security
579
+
580
+ Please email [@ljharb](https://github.com/ljharb) or see https://tidelift.com/security if you have a potential security vulnerability to report.
581
+
582
+ ## qs for enterprise
583
+
584
+ Available as part of the Tidelift Subscription
585
+
586
+ 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)
587
+
588
+ [package-url]: https://npmjs.org/package/qs
589
+ [npm-version-svg]: https://versionbadg.es/ljharb/qs.svg
590
+ [deps-svg]: https://david-dm.org/ljharb/qs.svg
591
+ [deps-url]: https://david-dm.org/ljharb/qs
592
+ [dev-deps-svg]: https://david-dm.org/ljharb/qs/dev-status.svg
593
+ [dev-deps-url]: https://david-dm.org/ljharb/qs#info=devDependencies
594
+ [npm-badge-png]: https://nodei.co/npm/qs.png?downloads=true&stars=true
595
+ [license-image]: https://img.shields.io/npm/l/qs.svg
559
596
  [license-url]: LICENSE
560
- [downloads-image]: http://img.shields.io/npm/dm/qs.svg
561
- [downloads-url]: http://npm-stat.com/charts.html?package=qs
597
+ [downloads-image]: https://img.shields.io/npm/dm/qs.svg
598
+ [downloads-url]: https://npm-stat.com/charts.html?package=qs
599
+ [codecov-image]: https://codecov.io/gh/ljharb/qs/branch/main/graphs/badge.svg
600
+ [codecov-url]: https://app.codecov.io/gh/ljharb/qs/
601
+ [actions-image]: https://img.shields.io/github/check-runs/ljharb/qs/main
602
+ [actions-url]: https://github.com/ljharb/qs/actions