qs 6.7.2 → 6.7.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.
- package/.claude/backport-record.md +79 -0
- package/.claude/ghsa-2856-comment.md +7 -0
- package/.claude/ghsa-6rw7-update.md +53 -0
- package/.claude/ghsa-w7fw-comment.md +10 -0
- package/.claude/settings.local.json +36 -0
- package/.editorconfig +4 -0
- package/.eslintrc +18 -3
- package/.nycrc +13 -0
- package/CHANGELOG.md +20 -0
- package/README.md +62 -20
- package/dist/qs.js +28 -17
- package/lib/parse.js +6 -6
- package/lib/stringify.js +15 -5
- package/lib/utils.js +7 -6
- package/package.json +15 -14
- package/test/parse.js +60 -0
- package/test/stringify.js +17 -11
- package/.eslintignore +0 -1
- package/.github/workflows/rebase.yml +0 -15
- package/test/.eslintrc +0 -17
|
@@ -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
|
|
@@ -31,3 +32,6 @@ indent_size = 2
|
|
|
31
32
|
[LICENSE]
|
|
32
33
|
indent_size = 2
|
|
33
34
|
max_line_length = off
|
|
35
|
+
|
|
36
|
+
[.nycrc]
|
|
37
|
+
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
|
-
|
|
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
|
-
|
|
20
|
-
|
|
23
|
+
},
|
|
24
|
+
|
|
25
|
+
"overrides": [
|
|
26
|
+
{
|
|
27
|
+
"files": "test/**",
|
|
28
|
+
"rules": {
|
|
29
|
+
"max-lines-per-function": 0,
|
|
30
|
+
"max-statements": 0,
|
|
31
|
+
"no-extend-native": 0,
|
|
32
|
+
"function-paren-newline": 0,
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
],
|
|
21
36
|
}
|
package/.nycrc
ADDED
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,23 @@
|
|
|
1
|
+
## **6.7.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.7.3**
|
|
9
|
+
- [Fix] `parse`: ignore `__proto__` keys (#428)
|
|
10
|
+
- [Fix] `stringify`: avoid encoding arrayformat comma when `encodeValuesOnly = true` (#424)
|
|
11
|
+
- [Robustness] `stringify`: avoid relying on a global `undefined` (#427)
|
|
12
|
+
- [readme] remove travis badge; add github actions/codecov badges; update URLs
|
|
13
|
+
- [Docs] add note and links for coercing primitive values (#408)
|
|
14
|
+
- [meta] fix README.md (#399)
|
|
15
|
+
- [meta] do not publish workflow files
|
|
16
|
+
- [actions] backport actions from main
|
|
17
|
+
- [Dev Deps] backport updates from main
|
|
18
|
+
- [Tests] use `nyc` for coverage
|
|
19
|
+
- [Tests] clean up stringify tests slightly
|
|
20
|
+
|
|
1
21
|
## **6.7.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
|
-
[![
|
|
4
|
-
[![
|
|
5
|
-
[![
|
|
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][
|
|
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
|
|
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
|
|
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
|
-
[
|
|
567
|
-
[
|
|
568
|
-
[
|
|
569
|
-
[
|
|
570
|
-
[
|
|
571
|
-
[
|
|
572
|
-
[
|
|
573
|
-
[
|
|
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]:
|
|
580
|
-
[downloads-url]:
|
|
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.
|
|
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,7 +188,7 @@ 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
|
}
|
|
@@ -228,7 +228,7 @@ var parseKeys = function parseQueryStringKeys(givenKey, val, options, valuesPars
|
|
|
228
228
|
}
|
|
229
229
|
}
|
|
230
230
|
|
|
231
|
-
keys.
|
|
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.
|
|
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.
|
|
250
|
+
keys[keys.length] = '[' + key.slice(segment.index + ']');
|
|
251
251
|
}
|
|
252
252
|
|
|
253
253
|
return parseObject(keys, val, options, valuesParsed);
|
|
@@ -329,6 +329,7 @@ var arrayPrefixGenerators = {
|
|
|
329
329
|
};
|
|
330
330
|
|
|
331
331
|
var isArray = Array.isArray;
|
|
332
|
+
var split = String.prototype.split;
|
|
332
333
|
var push = Array.prototype.push;
|
|
333
334
|
var pushToArray = function (arr, valueOrArray) {
|
|
334
335
|
push.apply(arr, isArray(valueOrArray) ? valueOrArray : [valueOrArray]);
|
|
@@ -392,6 +393,14 @@ var stringify = function stringify(
|
|
|
392
393
|
if (typeof obj === 'string' || typeof obj === 'number' || typeof obj === 'boolean' || utils.isBuffer(obj)) {
|
|
393
394
|
if (encoder) {
|
|
394
395
|
var keyValue = encodeValuesOnly ? prefix : encoder(prefix, defaults.encoder, charset);
|
|
396
|
+
if (generateArrayPrefix === 'comma' && encodeValuesOnly) {
|
|
397
|
+
var valuesArray = split.call(String(obj), ',');
|
|
398
|
+
var valuesJoined = '';
|
|
399
|
+
for (var i = 0; i < valuesArray.length; ++i) {
|
|
400
|
+
valuesJoined += (i === 0 ? '' : ',') + formatter(encoder(valuesArray[i], defaults.encoder, charset));
|
|
401
|
+
}
|
|
402
|
+
return [formatter(keyValue) + '=' + valuesJoined];
|
|
403
|
+
}
|
|
395
404
|
return [formatter(keyValue) + '=' + formatter(encoder(obj, defaults.encoder, charset))];
|
|
396
405
|
}
|
|
397
406
|
return [formatter(prefix) + '=' + formatter(String(obj))];
|
|
@@ -411,8 +420,9 @@ var stringify = function stringify(
|
|
|
411
420
|
objKeys = sort ? keys.sort(sort) : keys;
|
|
412
421
|
}
|
|
413
422
|
|
|
414
|
-
for (var
|
|
415
|
-
var key = objKeys[
|
|
423
|
+
for (var j = 0; j < objKeys.length; ++j) {
|
|
424
|
+
var key = objKeys[j];
|
|
425
|
+
var value = typeof key === 'object' && typeof key.value !== 'undefined' ? key.value : obj[key];
|
|
416
426
|
|
|
417
427
|
if (skipNulls && obj[key] === null) {
|
|
418
428
|
continue;
|
|
@@ -420,7 +430,7 @@ var stringify = function stringify(
|
|
|
420
430
|
|
|
421
431
|
if (isArray(obj)) {
|
|
422
432
|
pushToArray(values, stringify(
|
|
423
|
-
|
|
433
|
+
value,
|
|
424
434
|
typeof generateArrayPrefix === 'function' ? generateArrayPrefix(prefix, key) : prefix,
|
|
425
435
|
generateArrayPrefix,
|
|
426
436
|
strictNullHandling,
|
|
@@ -436,7 +446,7 @@ var stringify = function stringify(
|
|
|
436
446
|
));
|
|
437
447
|
} else {
|
|
438
448
|
pushToArray(values, stringify(
|
|
439
|
-
|
|
449
|
+
value,
|
|
440
450
|
prefix + (allowDots ? '.' + key : '[' + key + ']'),
|
|
441
451
|
generateArrayPrefix,
|
|
442
452
|
strictNullHandling,
|
|
@@ -461,7 +471,7 @@ var normalizeStringifyOptions = function normalizeStringifyOptions(opts) {
|
|
|
461
471
|
return defaults;
|
|
462
472
|
}
|
|
463
473
|
|
|
464
|
-
if (opts.encoder !== null && opts.encoder !== undefined && typeof opts.encoder !== 'function') {
|
|
474
|
+
if (opts.encoder !== null && typeof opts.encoder !== 'undefined' && typeof opts.encoder !== 'function') {
|
|
465
475
|
throw new TypeError('Encoder has to be a function.');
|
|
466
476
|
}
|
|
467
477
|
|
|
@@ -590,7 +600,7 @@ var isArray = Array.isArray;
|
|
|
590
600
|
var hexTable = (function () {
|
|
591
601
|
var array = [];
|
|
592
602
|
for (var i = 0; i < 256; ++i) {
|
|
593
|
-
array.
|
|
603
|
+
array[array.length] = '%' + ((i < 16 ? '0' : '' + i.toString(16)).toUpperCase());
|
|
594
604
|
}
|
|
595
605
|
|
|
596
606
|
return array;
|
|
@@ -606,7 +616,7 @@ var compactQueue = function compactQueue(queue) {
|
|
|
606
616
|
|
|
607
617
|
for (var j = 0; j < obj.length; ++j) {
|
|
608
618
|
if (typeof obj[j] !== 'undefined') {
|
|
609
|
-
compacted.
|
|
619
|
+
compacted[compacted.length] = obj[j];
|
|
610
620
|
}
|
|
611
621
|
}
|
|
612
622
|
|
|
@@ -633,7 +643,7 @@ var merge = function merge(target, source, options) {
|
|
|
633
643
|
|
|
634
644
|
if (typeof source !== 'object') {
|
|
635
645
|
if (isArray(target)) {
|
|
636
|
-
target.
|
|
646
|
+
target[target.length] = source;
|
|
637
647
|
} else if (target && typeof target === 'object') {
|
|
638
648
|
if ((options && (options.plainObjects || options.allowPrototypes)) || !has.call(Object.prototype, source)) {
|
|
639
649
|
target[source] = true; // eslint-disable-line no-param-reassign
|
|
@@ -661,7 +671,7 @@ var merge = function merge(target, source, options) {
|
|
|
661
671
|
if (targetItem && typeof targetItem === 'object' && item && typeof item === 'object') {
|
|
662
672
|
target[i] = merge(targetItem, item, options); // eslint-disable-line no-param-reassign
|
|
663
673
|
} else {
|
|
664
|
-
target.
|
|
674
|
+
target[target.length] = item;
|
|
665
675
|
}
|
|
666
676
|
} else {
|
|
667
677
|
target[i] = item; // eslint-disable-line no-param-reassign
|
|
@@ -752,6 +762,7 @@ var encode = function encode(str, defaultEncoder, charset) {
|
|
|
752
762
|
|
|
753
763
|
i += 1;
|
|
754
764
|
c = 0x10000 + (((c & 0x3FF) << 10) | (string.charCodeAt(i) & 0x3FF));
|
|
765
|
+
/* eslint operator-linebreak: [2, "before"] */
|
|
755
766
|
out += hexTable[0xF0 | (c >> 18)]
|
|
756
767
|
+ hexTable[0x80 | ((c >> 12) & 0x3F)]
|
|
757
768
|
+ hexTable[0x80 | ((c >> 6) & 0x3F)]
|
|
@@ -774,8 +785,8 @@ var compact = function compact(value) {
|
|
|
774
785
|
var key = keys[j];
|
|
775
786
|
var val = obj[key];
|
|
776
787
|
if (typeof val === 'object' && val !== null && refs.indexOf(val) === -1) {
|
|
777
|
-
queue.
|
|
778
|
-
refs.
|
|
788
|
+
queue[queue.length] = { obj: obj, prop: key };
|
|
789
|
+
refs[refs.length] = val;
|
|
779
790
|
}
|
|
780
791
|
}
|
|
781
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.
|
|
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,7 +146,7 @@ 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
|
}
|
|
@@ -186,7 +186,7 @@ var parseKeys = function parseQueryStringKeys(givenKey, val, options, valuesPars
|
|
|
186
186
|
}
|
|
187
187
|
}
|
|
188
188
|
|
|
189
|
-
keys.
|
|
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.
|
|
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.
|
|
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]);
|
|
@@ -81,6 +82,14 @@ var stringify = function stringify(
|
|
|
81
82
|
if (typeof obj === 'string' || typeof obj === 'number' || typeof obj === 'boolean' || utils.isBuffer(obj)) {
|
|
82
83
|
if (encoder) {
|
|
83
84
|
var keyValue = encodeValuesOnly ? prefix : encoder(prefix, defaults.encoder, charset);
|
|
85
|
+
if (generateArrayPrefix === 'comma' && encodeValuesOnly) {
|
|
86
|
+
var valuesArray = split.call(String(obj), ',');
|
|
87
|
+
var valuesJoined = '';
|
|
88
|
+
for (var i = 0; i < valuesArray.length; ++i) {
|
|
89
|
+
valuesJoined += (i === 0 ? '' : ',') + formatter(encoder(valuesArray[i], defaults.encoder, charset));
|
|
90
|
+
}
|
|
91
|
+
return [formatter(keyValue) + '=' + valuesJoined];
|
|
92
|
+
}
|
|
84
93
|
return [formatter(keyValue) + '=' + formatter(encoder(obj, defaults.encoder, charset))];
|
|
85
94
|
}
|
|
86
95
|
return [formatter(prefix) + '=' + formatter(String(obj))];
|
|
@@ -100,8 +109,9 @@ var stringify = function stringify(
|
|
|
100
109
|
objKeys = sort ? keys.sort(sort) : keys;
|
|
101
110
|
}
|
|
102
111
|
|
|
103
|
-
for (var
|
|
104
|
-
var key = objKeys[
|
|
112
|
+
for (var j = 0; j < objKeys.length; ++j) {
|
|
113
|
+
var key = objKeys[j];
|
|
114
|
+
var value = typeof key === 'object' && typeof key.value !== 'undefined' ? key.value : obj[key];
|
|
105
115
|
|
|
106
116
|
if (skipNulls && obj[key] === null) {
|
|
107
117
|
continue;
|
|
@@ -109,7 +119,7 @@ var stringify = function stringify(
|
|
|
109
119
|
|
|
110
120
|
if (isArray(obj)) {
|
|
111
121
|
pushToArray(values, stringify(
|
|
112
|
-
|
|
122
|
+
value,
|
|
113
123
|
typeof generateArrayPrefix === 'function' ? generateArrayPrefix(prefix, key) : prefix,
|
|
114
124
|
generateArrayPrefix,
|
|
115
125
|
strictNullHandling,
|
|
@@ -125,7 +135,7 @@ var stringify = function stringify(
|
|
|
125
135
|
));
|
|
126
136
|
} else {
|
|
127
137
|
pushToArray(values, stringify(
|
|
128
|
-
|
|
138
|
+
value,
|
|
129
139
|
prefix + (allowDots ? '.' + key : '[' + key + ']'),
|
|
130
140
|
generateArrayPrefix,
|
|
131
141
|
strictNullHandling,
|
|
@@ -150,7 +160,7 @@ var normalizeStringifyOptions = function normalizeStringifyOptions(opts) {
|
|
|
150
160
|
return defaults;
|
|
151
161
|
}
|
|
152
162
|
|
|
153
|
-
if (opts.encoder !== null && opts.encoder !== undefined && typeof opts.encoder !== 'function') {
|
|
163
|
+
if (opts.encoder !== null && typeof opts.encoder !== 'undefined' && typeof opts.encoder !== 'function') {
|
|
154
164
|
throw new TypeError('Encoder has to be a function.');
|
|
155
165
|
}
|
|
156
166
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
80
|
+
target[target.length] = item;
|
|
81
81
|
}
|
|
82
82
|
} else {
|
|
83
83
|
target[i] = item; // eslint-disable-line no-param-reassign
|
|
@@ -168,6 +168,7 @@ var encode = function encode(str, defaultEncoder, charset) {
|
|
|
168
168
|
|
|
169
169
|
i += 1;
|
|
170
170
|
c = 0x10000 + (((c & 0x3FF) << 10) | (string.charCodeAt(i) & 0x3FF));
|
|
171
|
+
/* eslint operator-linebreak: [2, "before"] */
|
|
171
172
|
out += hexTable[0xF0 | (c >> 18)]
|
|
172
173
|
+ hexTable[0x80 | ((c >> 12) & 0x3F)]
|
|
173
174
|
+ hexTable[0x80 | ((c >> 6) & 0x3F)]
|
|
@@ -190,8 +191,8 @@ var compact = function compact(value) {
|
|
|
190
191
|
var key = keys[j];
|
|
191
192
|
var val = obj[key];
|
|
192
193
|
if (typeof val === 'object' && val !== null && refs.indexOf(val) === -1) {
|
|
193
|
-
queue.
|
|
194
|
-
refs.
|
|
194
|
+
queue[queue.length] = { obj: obj, prop: key };
|
|
195
|
+
refs[refs.length] = val;
|
|
195
196
|
}
|
|
196
197
|
}
|
|
197
198
|
}
|
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.7.
|
|
5
|
+
"version": "6.7.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": "^
|
|
35
|
-
"browserify": "^16.5.
|
|
36
|
-
"covert": "^1.1.1",
|
|
33
|
+
"@ljharb/eslint-config": "^20.1.0",
|
|
34
|
+
"browserify": "^16.5.2",
|
|
37
35
|
"eclint": "^2.8.1",
|
|
38
|
-
"eslint": "^6.
|
|
36
|
+
"eslint": "^8.6.0",
|
|
39
37
|
"evalmd": "^0.0.19",
|
|
40
38
|
"for-each": "^0.3.3",
|
|
39
|
+
"has-symbols": "^1.0.2",
|
|
41
40
|
"iconv-lite": "^0.5.1",
|
|
41
|
+
"in-publish": "^2.0.1",
|
|
42
42
|
"mkdirp": "^0.5.4",
|
|
43
|
-
"
|
|
43
|
+
"nyc": "^10.3.2",
|
|
44
|
+
"object-inspect": "^1.12.0",
|
|
44
45
|
"qs-iconv": "^1.0.4",
|
|
45
|
-
"safe-publish-latest": "^
|
|
46
|
+
"safe-publish-latest": "^2.0.0",
|
|
46
47
|
"safer-buffer": "^2.1.2",
|
|
47
|
-
"tape": "^5.
|
|
48
|
+
"tape": "^5.4.0"
|
|
48
49
|
},
|
|
49
50
|
"scripts": {
|
|
50
|
-
"
|
|
51
|
+
"prepublishOnly": "safe-publish-latest && npm run dist",
|
|
52
|
+
"prepublish": "not-in-publish || npm run prepublishOnly",
|
|
51
53
|
"pretest": "npm run --silent readme && npm run --silent lint",
|
|
52
|
-
"test": "npm run --silent
|
|
53
|
-
"tests-only": "
|
|
54
|
+
"test": "npm run --silent tests-only",
|
|
55
|
+
"tests-only": "nyc tape 'test/**/*.js'",
|
|
54
56
|
"posttest": "npx aud --production",
|
|
55
57
|
"readme": "evalmd README.md",
|
|
56
|
-
"postlint": "eclint check
|
|
58
|
+
"postlint": "eclint check $(git ls-files | xargs find 2> /dev/null | grep -vE 'node_modules|\\.git')",
|
|
57
59
|
"lint": "eslint lib/*.js test/*.js",
|
|
58
|
-
"coverage": "covert test",
|
|
59
60
|
"dist": "mkdirp dist && browserify --standalone Qs lib/index.js > dist/qs.js"
|
|
60
61
|
},
|
|
61
62
|
"license": "BSD-3-Clause"
|
package/test/parse.js
CHANGED
|
@@ -632,6 +632,66 @@ test('parse()', function (t) {
|
|
|
632
632
|
st.end();
|
|
633
633
|
});
|
|
634
634
|
|
|
635
|
+
t.test('dunder proto is ignored', function (st) {
|
|
636
|
+
var payload = 'categories[__proto__]=login&categories[__proto__]&categories[length]=42';
|
|
637
|
+
var result = qs.parse(payload, { allowPrototypes: true });
|
|
638
|
+
|
|
639
|
+
st.deepEqual(
|
|
640
|
+
result,
|
|
641
|
+
{
|
|
642
|
+
categories: {
|
|
643
|
+
length: '42'
|
|
644
|
+
}
|
|
645
|
+
},
|
|
646
|
+
'silent [[Prototype]] payload'
|
|
647
|
+
);
|
|
648
|
+
|
|
649
|
+
var plainResult = qs.parse(payload, { allowPrototypes: true, plainObjects: true });
|
|
650
|
+
|
|
651
|
+
st.deepEqual(
|
|
652
|
+
plainResult,
|
|
653
|
+
{
|
|
654
|
+
__proto__: null,
|
|
655
|
+
categories: {
|
|
656
|
+
__proto__: null,
|
|
657
|
+
length: '42'
|
|
658
|
+
}
|
|
659
|
+
},
|
|
660
|
+
'silent [[Prototype]] payload: plain objects'
|
|
661
|
+
);
|
|
662
|
+
|
|
663
|
+
var query = qs.parse('categories[__proto__]=cats&categories[__proto__]=dogs&categories[some][json]=toInject', { allowPrototypes: true });
|
|
664
|
+
|
|
665
|
+
st.notOk(Array.isArray(query.categories), 'is not an array');
|
|
666
|
+
st.notOk(query.categories instanceof Array, 'is not instanceof an array');
|
|
667
|
+
st.deepEqual(query.categories, { some: { json: 'toInject' } });
|
|
668
|
+
st.equal(JSON.stringify(query.categories), '{"some":{"json":"toInject"}}', 'stringifies as a non-array');
|
|
669
|
+
|
|
670
|
+
st.deepEqual(
|
|
671
|
+
qs.parse('foo[__proto__][hidden]=value&foo[bar]=stuffs', { allowPrototypes: true }),
|
|
672
|
+
{
|
|
673
|
+
foo: {
|
|
674
|
+
bar: 'stuffs'
|
|
675
|
+
}
|
|
676
|
+
},
|
|
677
|
+
'hidden values'
|
|
678
|
+
);
|
|
679
|
+
|
|
680
|
+
st.deepEqual(
|
|
681
|
+
qs.parse('foo[__proto__][hidden]=value&foo[bar]=stuffs', { allowPrototypes: true, plainObjects: true }),
|
|
682
|
+
{
|
|
683
|
+
__proto__: null,
|
|
684
|
+
foo: {
|
|
685
|
+
__proto__: null,
|
|
686
|
+
bar: 'stuffs'
|
|
687
|
+
}
|
|
688
|
+
},
|
|
689
|
+
'hidden values: plain objects'
|
|
690
|
+
);
|
|
691
|
+
|
|
692
|
+
st.end();
|
|
693
|
+
});
|
|
694
|
+
|
|
635
695
|
t.test('can return null objects', { skip: !Object.create }, function (st) {
|
|
636
696
|
var expected = Object.create(null);
|
|
637
697
|
expected.a = Object.create(null);
|
package/test/stringify.js
CHANGED
|
@@ -97,10 +97,10 @@ test('stringify()', function (t) {
|
|
|
97
97
|
});
|
|
98
98
|
|
|
99
99
|
t.test('stringifies a nested array value', function (st) {
|
|
100
|
-
st.equal(qs.stringify({ a: { b: ['c', 'd'] } }, { arrayFormat: 'indices' }), 'a
|
|
101
|
-
st.equal(qs.stringify({ a: { b: ['c', 'd'] } }, { arrayFormat: 'brackets' }), 'a
|
|
102
|
-
st.equal(qs.stringify({ a: { b: ['c', 'd'] } }, { arrayFormat: 'comma' }), 'a
|
|
103
|
-
st.equal(qs.stringify({ a: { b: ['c', 'd'] } }), 'a
|
|
100
|
+
st.equal(qs.stringify({ a: { b: ['c', 'd'] } }, { encodeValuesOnly: true, arrayFormat: 'indices' }), 'a[b][0]=c&a[b][1]=d');
|
|
101
|
+
st.equal(qs.stringify({ a: { b: ['c', 'd'] } }, { encodeValuesOnly: true, arrayFormat: 'brackets' }), 'a[b][]=c&a[b][]=d');
|
|
102
|
+
st.equal(qs.stringify({ a: { b: ['c', 'd'] } }, { encodeValuesOnly: true, arrayFormat: 'comma' }), 'a[b]=c,d');
|
|
103
|
+
st.equal(qs.stringify({ a: { b: ['c', 'd'] } }, { encodeValuesOnly: true }), 'a[b][0]=c&a[b][1]=d');
|
|
104
104
|
st.end();
|
|
105
105
|
});
|
|
106
106
|
|
|
@@ -108,7 +108,7 @@ test('stringify()', function (t) {
|
|
|
108
108
|
st.equal(
|
|
109
109
|
qs.stringify(
|
|
110
110
|
{ a: { b: ['c', 'd'] } },
|
|
111
|
-
{ allowDots: true,
|
|
111
|
+
{ allowDots: true, encodeValuesOnly: true, arrayFormat: 'indices' }
|
|
112
112
|
),
|
|
113
113
|
'a.b[0]=c&a.b[1]=d',
|
|
114
114
|
'indices: stringifies with dots + indices'
|
|
@@ -116,7 +116,7 @@ test('stringify()', function (t) {
|
|
|
116
116
|
st.equal(
|
|
117
117
|
qs.stringify(
|
|
118
118
|
{ a: { b: ['c', 'd'] } },
|
|
119
|
-
{ allowDots: true,
|
|
119
|
+
{ allowDots: true, encodeValuesOnly: true, arrayFormat: 'brackets' }
|
|
120
120
|
),
|
|
121
121
|
'a.b[]=c&a.b[]=d',
|
|
122
122
|
'brackets: stringifies with dots + brackets'
|
|
@@ -124,7 +124,7 @@ test('stringify()', function (t) {
|
|
|
124
124
|
st.equal(
|
|
125
125
|
qs.stringify(
|
|
126
126
|
{ a: { b: ['c', 'd'] } },
|
|
127
|
-
{ allowDots: true,
|
|
127
|
+
{ allowDots: true, encodeValuesOnly: true, arrayFormat: 'comma' }
|
|
128
128
|
),
|
|
129
129
|
'a.b=c,d',
|
|
130
130
|
'comma: stringifies with dots + comma'
|
|
@@ -132,7 +132,7 @@ test('stringify()', function (t) {
|
|
|
132
132
|
st.equal(
|
|
133
133
|
qs.stringify(
|
|
134
134
|
{ a: { b: ['c', 'd'] } },
|
|
135
|
-
{ allowDots: true,
|
|
135
|
+
{ allowDots: true, encodeValuesOnly: true }
|
|
136
136
|
),
|
|
137
137
|
'a.b[0]=c&a.b[1]=d',
|
|
138
138
|
'default: stringifies with dots + indices'
|
|
@@ -180,17 +180,23 @@ test('stringify()', function (t) {
|
|
|
180
180
|
|
|
181
181
|
t.test('stringifies an array with mixed objects and primitives', function (st) {
|
|
182
182
|
st.equal(
|
|
183
|
-
qs.stringify({ a: [{ b: 1 }, 2, 3] }, {
|
|
183
|
+
qs.stringify({ a: [{ b: 1 }, 2, 3] }, { encodeValuesOnly: true, arrayFormat: 'indices' }),
|
|
184
184
|
'a[0][b]=1&a[1]=2&a[2]=3',
|
|
185
185
|
'indices => indices'
|
|
186
186
|
);
|
|
187
187
|
st.equal(
|
|
188
|
-
qs.stringify({ a: [{ b: 1 }, 2, 3] }, {
|
|
188
|
+
qs.stringify({ a: [{ b: 1 }, 2, 3] }, { encodeValuesOnly: true, arrayFormat: 'brackets' }),
|
|
189
189
|
'a[][b]=1&a[]=2&a[]=3',
|
|
190
190
|
'brackets => brackets'
|
|
191
191
|
);
|
|
192
192
|
st.equal(
|
|
193
|
-
qs.stringify({ a: [{ b: 1 }, 2, 3] }, {
|
|
193
|
+
qs.stringify({ a: [{ b: 1 }, 2, 3] }, { encodeValuesOnly: true, arrayFormat: 'comma' }),
|
|
194
|
+
'???',
|
|
195
|
+
'brackets => brackets',
|
|
196
|
+
{ skip: 'TODO: figure out what this should do' }
|
|
197
|
+
);
|
|
198
|
+
st.equal(
|
|
199
|
+
qs.stringify({ a: [{ b: 1 }, 2, 3] }, { encodeValuesOnly: true }),
|
|
194
200
|
'a[0][b]=1&a[1]=2&a[2]=3',
|
|
195
201
|
'default => indices'
|
|
196
202
|
);
|
package/.eslintignore
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
dist
|
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
|
-
}
|