qs 6.9.6 → 6.9.8
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 +1 -0
- package/.eslintrc +6 -3
- package/CHANGELOG.md +19 -0
- package/README.md +39 -21
- package/dist/qs.js +27 -17
- package/lib/parse.js +5 -5
- package/lib/stringify.js +14 -5
- package/lib/utils.js +8 -7
- package/package.json +13 -12
- package/test/parse.js +60 -0
- package/test/stringify.js +23 -12
- package/.eslintignore +0 -2
|
@@ -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
package/.eslintrc
CHANGED
|
@@ -3,20 +3,23 @@
|
|
|
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 }],
|
|
13
|
-
"max-params": [2,
|
|
17
|
+
"max-params": [2, 15],
|
|
14
18
|
"max-statements": [2, 52],
|
|
15
19
|
"multiline-comment-style": 0,
|
|
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
|
},
|
|
21
24
|
|
|
22
25
|
"overrides": [
|
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,22 @@
|
|
|
1
|
+
## **6.9.8**
|
|
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.9.7**
|
|
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
|
+
- [Tests] clean up stringify tests slightly
|
|
15
|
+
- [meta] fix README.md (#399)
|
|
16
|
+
- Revert "[meta] ignore eclint transitive audit warning"
|
|
17
|
+
- [actions] backport actions from main
|
|
18
|
+
- [Dev Deps] backport updates from main
|
|
19
|
+
|
|
1
20
|
## **6.9.6**
|
|
2
21
|
- [Fix] restore `dist` dir; mistakenly removed in d4f6c32
|
|
3
22
|
|
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)
|
|
@@ -345,7 +357,7 @@ var encoded = qs.stringify({ a: { b: 'c' } }, { encoder: function (str, defaultE
|
|
|
345
357
|
The type argument is also provided to the decoder:
|
|
346
358
|
|
|
347
359
|
```javascript
|
|
348
|
-
var decoded = qs.parse('x=z', { decoder: function (str,
|
|
360
|
+
var decoded = qs.parse('x=z', { decoder: function (str, defaultDecoder, charset, type) {
|
|
349
361
|
if (type === 'key') {
|
|
350
362
|
return // Decoded key
|
|
351
363
|
} else if (type === 'value') {
|
|
@@ -425,6 +437,12 @@ The query string may optionally be prepended with a question mark:
|
|
|
425
437
|
assert.equal(qs.stringify({ a: 'b', c: 'd' }, { addQueryPrefix: true }), '?a=b&c=d');
|
|
426
438
|
```
|
|
427
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
|
+
|
|
428
446
|
The delimiter may be overridden with stringify as well:
|
|
429
447
|
|
|
430
448
|
```javascript
|
|
@@ -587,18 +605,18 @@ Available as part of the Tidelift Subscription
|
|
|
587
605
|
|
|
588
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)
|
|
589
607
|
|
|
590
|
-
[
|
|
591
|
-
[
|
|
592
|
-
[
|
|
593
|
-
[
|
|
594
|
-
[
|
|
595
|
-
[
|
|
596
|
-
[
|
|
597
|
-
[
|
|
598
|
-
[9]: https://ci.testling.com/ljharb/qs.png
|
|
599
|
-
[10]: https://ci.testling.com/ljharb/qs
|
|
600
|
-
[11]: https://nodei.co/npm/qs.png?downloads=true&stars=true
|
|
601
|
-
[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
|
|
602
616
|
[license-url]: LICENSE
|
|
603
|
-
[downloads-image]:
|
|
604
|
-
[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
|
@@ -89,7 +89,7 @@ var charsetSentinel = 'utf8=%E2%9C%93'; // encodeURIComponent('✓')
|
|
|
89
89
|
var parseValues = function parseQueryStringValues(str, options) {
|
|
90
90
|
var obj = {};
|
|
91
91
|
var cleanStr = options.ignoreQueryPrefix ? str.replace(/^\?/, '') : str;
|
|
92
|
-
var limit = options.parameterLimit === Infinity ? undefined : options.parameterLimit;
|
|
92
|
+
var limit = options.parameterLimit === Infinity ? void undefined : options.parameterLimit;
|
|
93
93
|
var parts = cleanStr.split(options.delimiter, limit);
|
|
94
94
|
var skipIndex = -1; // Keep track of where the utf8 sentinel was found
|
|
95
95
|
var i;
|
|
@@ -174,7 +174,7 @@ var parseObject = function (chain, val, options, valuesParsed) {
|
|
|
174
174
|
) {
|
|
175
175
|
obj = [];
|
|
176
176
|
obj[index] = leaf;
|
|
177
|
-
} else {
|
|
177
|
+
} else if (cleanRoot !== '__proto__') {
|
|
178
178
|
obj[cleanRoot] = leaf;
|
|
179
179
|
}
|
|
180
180
|
}
|
|
@@ -214,7 +214,7 @@ var parseKeys = function parseQueryStringKeys(givenKey, val, options, valuesPars
|
|
|
214
214
|
}
|
|
215
215
|
}
|
|
216
216
|
|
|
217
|
-
keys.
|
|
217
|
+
keys[keys.length] = parent;
|
|
218
218
|
}
|
|
219
219
|
|
|
220
220
|
// Loop through children appending to the array until we hit depth
|
|
@@ -227,13 +227,13 @@ var parseKeys = function parseQueryStringKeys(givenKey, val, options, valuesPars
|
|
|
227
227
|
return;
|
|
228
228
|
}
|
|
229
229
|
}
|
|
230
|
-
keys.
|
|
230
|
+
keys[keys.length] = segment[1];
|
|
231
231
|
}
|
|
232
232
|
|
|
233
233
|
// If there's a remainder, just add whatever is left
|
|
234
234
|
|
|
235
235
|
if (segment) {
|
|
236
|
-
keys.
|
|
236
|
+
keys[keys.length] = '[' + key.slice(segment.index + ']');
|
|
237
237
|
}
|
|
238
238
|
|
|
239
239
|
return parseObject(keys, val, options, valuesParsed);
|
|
@@ -316,6 +316,7 @@ var arrayPrefixGenerators = {
|
|
|
316
316
|
};
|
|
317
317
|
|
|
318
318
|
var isArray = Array.isArray;
|
|
319
|
+
var split = String.prototype.split;
|
|
319
320
|
var push = Array.prototype.push;
|
|
320
321
|
var pushToArray = function (arr, valueOrArray) {
|
|
321
322
|
push.apply(arr, isArray(valueOrArray) ? valueOrArray : [valueOrArray]);
|
|
@@ -393,6 +394,14 @@ var stringify = function stringify(
|
|
|
393
394
|
if (isNonNullishPrimitive(obj) || utils.isBuffer(obj)) {
|
|
394
395
|
if (encoder) {
|
|
395
396
|
var keyValue = encodeValuesOnly ? prefix : encoder(prefix, defaults.encoder, charset, 'key', format);
|
|
397
|
+
if (generateArrayPrefix === 'comma' && encodeValuesOnly) {
|
|
398
|
+
var valuesArray = split.call(String(obj), ',');
|
|
399
|
+
var valuesJoined = '';
|
|
400
|
+
for (var i = 0; i < valuesArray.length; ++i) {
|
|
401
|
+
valuesJoined += (i === 0 ? '' : ',') + formatter(encoder(valuesArray[i], defaults.encoder, charset, 'value', format));
|
|
402
|
+
}
|
|
403
|
+
return [formatter(keyValue) + '=' + valuesJoined];
|
|
404
|
+
}
|
|
396
405
|
return [formatter(keyValue) + '=' + formatter(encoder(obj, defaults.encoder, charset, 'value', format))];
|
|
397
406
|
}
|
|
398
407
|
return [formatter(prefix) + '=' + formatter(String(obj))];
|
|
@@ -407,7 +416,7 @@ var stringify = function stringify(
|
|
|
407
416
|
var objKeys;
|
|
408
417
|
if (generateArrayPrefix === 'comma' && isArray(obj)) {
|
|
409
418
|
// we need to join elements in
|
|
410
|
-
objKeys = [{ value: obj.length > 0 ? obj.join(',') || null : undefined }];
|
|
419
|
+
objKeys = [{ value: obj.length > 0 ? obj.join(',') || null : void undefined }];
|
|
411
420
|
} else if (isArray(filter)) {
|
|
412
421
|
objKeys = filter;
|
|
413
422
|
} else {
|
|
@@ -415,9 +424,9 @@ var stringify = function stringify(
|
|
|
415
424
|
objKeys = sort ? keys.sort(sort) : keys;
|
|
416
425
|
}
|
|
417
426
|
|
|
418
|
-
for (var
|
|
419
|
-
var key = objKeys[
|
|
420
|
-
var value = typeof key === 'object' && key.value !== undefined ? key.value : obj[key];
|
|
427
|
+
for (var j = 0; j < objKeys.length; ++j) {
|
|
428
|
+
var key = objKeys[j];
|
|
429
|
+
var value = typeof key === 'object' && typeof key.value !== 'undefined' ? key.value : obj[key];
|
|
421
430
|
|
|
422
431
|
if (skipNulls && value === null) {
|
|
423
432
|
continue;
|
|
@@ -453,7 +462,7 @@ var normalizeStringifyOptions = function normalizeStringifyOptions(opts) {
|
|
|
453
462
|
return defaults;
|
|
454
463
|
}
|
|
455
464
|
|
|
456
|
-
if (opts.encoder !== null && opts.encoder !== undefined && typeof opts.encoder !== 'function') {
|
|
465
|
+
if (opts.encoder !== null && typeof opts.encoder !== 'undefined' && typeof opts.encoder !== 'function') {
|
|
457
466
|
throw new TypeError('Encoder has to be a function.');
|
|
458
467
|
}
|
|
459
468
|
|
|
@@ -586,7 +595,7 @@ var isArray = Array.isArray;
|
|
|
586
595
|
var hexTable = (function () {
|
|
587
596
|
var array = [];
|
|
588
597
|
for (var i = 0; i < 256; ++i) {
|
|
589
|
-
array.
|
|
598
|
+
array[array.length] = '%' + ((i < 16 ? '0' : '' + i.toString(16)).toUpperCase());
|
|
590
599
|
}
|
|
591
600
|
|
|
592
601
|
return array;
|
|
@@ -602,7 +611,7 @@ var compactQueue = function compactQueue(queue) {
|
|
|
602
611
|
|
|
603
612
|
for (var j = 0; j < obj.length; ++j) {
|
|
604
613
|
if (typeof obj[j] !== 'undefined') {
|
|
605
|
-
compacted.
|
|
614
|
+
compacted[compacted.length] = obj[j];
|
|
606
615
|
}
|
|
607
616
|
}
|
|
608
617
|
|
|
@@ -630,7 +639,7 @@ var merge = function merge(target, source, options) {
|
|
|
630
639
|
|
|
631
640
|
if (typeof source !== 'object') {
|
|
632
641
|
if (isArray(target)) {
|
|
633
|
-
target.
|
|
642
|
+
target[target.length] = source;
|
|
634
643
|
} else if (target && typeof target === 'object') {
|
|
635
644
|
if ((options && (options.plainObjects || options.allowPrototypes)) || !has.call(Object.prototype, source)) {
|
|
636
645
|
target[source] = true;
|
|
@@ -658,7 +667,7 @@ var merge = function merge(target, source, options) {
|
|
|
658
667
|
if (targetItem && typeof targetItem === 'object' && item && typeof item === 'object') {
|
|
659
668
|
target[i] = merge(targetItem, item, options);
|
|
660
669
|
} else {
|
|
661
|
-
target.
|
|
670
|
+
target[target.length] = item;
|
|
662
671
|
}
|
|
663
672
|
} else {
|
|
664
673
|
target[i] = item;
|
|
@@ -755,6 +764,7 @@ var encode = function encode(str, defaultEncoder, charset, kind, format) {
|
|
|
755
764
|
|
|
756
765
|
i += 1;
|
|
757
766
|
c = 0x10000 + (((c & 0x3FF) << 10) | (string.charCodeAt(i) & 0x3FF));
|
|
767
|
+
/* eslint operator-linebreak: [2, "before"] */
|
|
758
768
|
out += hexTable[0xF0 | (c >> 18)]
|
|
759
769
|
+ hexTable[0x80 | ((c >> 12) & 0x3F)]
|
|
760
770
|
+ hexTable[0x80 | ((c >> 6) & 0x3F)]
|
|
@@ -777,8 +787,8 @@ var compact = function compact(value) {
|
|
|
777
787
|
var key = keys[j];
|
|
778
788
|
var val = obj[key];
|
|
779
789
|
if (typeof val === 'object' && val !== null && refs.indexOf(val) === -1) {
|
|
780
|
-
queue.
|
|
781
|
-
refs.
|
|
790
|
+
queue[queue.length] = { obj: obj, prop: key };
|
|
791
|
+
refs[refs.length] = val;
|
|
782
792
|
}
|
|
783
793
|
}
|
|
784
794
|
}
|
|
@@ -808,7 +818,7 @@ var maybeMap = function maybeMap(val, fn) {
|
|
|
808
818
|
if (isArray(val)) {
|
|
809
819
|
var mapped = [];
|
|
810
820
|
for (var i = 0; i < val.length; i += 1) {
|
|
811
|
-
mapped.
|
|
821
|
+
mapped[mapped.length] = fn(val[i]);
|
|
812
822
|
}
|
|
813
823
|
return mapped;
|
|
814
824
|
}
|
package/lib/parse.js
CHANGED
|
@@ -50,7 +50,7 @@ var charsetSentinel = 'utf8=%E2%9C%93'; // encodeURIComponent('✓')
|
|
|
50
50
|
var parseValues = function parseQueryStringValues(str, options) {
|
|
51
51
|
var obj = {};
|
|
52
52
|
var cleanStr = options.ignoreQueryPrefix ? str.replace(/^\?/, '') : str;
|
|
53
|
-
var limit = options.parameterLimit === Infinity ? undefined : options.parameterLimit;
|
|
53
|
+
var limit = options.parameterLimit === Infinity ? void undefined : options.parameterLimit;
|
|
54
54
|
var parts = cleanStr.split(options.delimiter, limit);
|
|
55
55
|
var skipIndex = -1; // Keep track of where the utf8 sentinel was found
|
|
56
56
|
var i;
|
|
@@ -135,7 +135,7 @@ var parseObject = function (chain, val, options, valuesParsed) {
|
|
|
135
135
|
) {
|
|
136
136
|
obj = [];
|
|
137
137
|
obj[index] = leaf;
|
|
138
|
-
} else {
|
|
138
|
+
} else if (cleanRoot !== '__proto__') {
|
|
139
139
|
obj[cleanRoot] = leaf;
|
|
140
140
|
}
|
|
141
141
|
}
|
|
@@ -175,7 +175,7 @@ var parseKeys = function parseQueryStringKeys(givenKey, val, options, valuesPars
|
|
|
175
175
|
}
|
|
176
176
|
}
|
|
177
177
|
|
|
178
|
-
keys.
|
|
178
|
+
keys[keys.length] = parent;
|
|
179
179
|
}
|
|
180
180
|
|
|
181
181
|
// Loop through children appending to the array until we hit depth
|
|
@@ -188,13 +188,13 @@ var parseKeys = function parseQueryStringKeys(givenKey, val, options, valuesPars
|
|
|
188
188
|
return;
|
|
189
189
|
}
|
|
190
190
|
}
|
|
191
|
-
keys.
|
|
191
|
+
keys[keys.length] = segment[1];
|
|
192
192
|
}
|
|
193
193
|
|
|
194
194
|
// If there's a remainder, just add whatever is left
|
|
195
195
|
|
|
196
196
|
if (segment) {
|
|
197
|
-
keys.
|
|
197
|
+
keys[keys.length] = '[' + key.slice(segment.index + ']');
|
|
198
198
|
}
|
|
199
199
|
|
|
200
200
|
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]);
|
|
@@ -95,6 +96,14 @@ var stringify = function stringify(
|
|
|
95
96
|
if (isNonNullishPrimitive(obj) || utils.isBuffer(obj)) {
|
|
96
97
|
if (encoder) {
|
|
97
98
|
var keyValue = encodeValuesOnly ? prefix : encoder(prefix, defaults.encoder, charset, 'key', format);
|
|
99
|
+
if (generateArrayPrefix === 'comma' && encodeValuesOnly) {
|
|
100
|
+
var valuesArray = split.call(String(obj), ',');
|
|
101
|
+
var valuesJoined = '';
|
|
102
|
+
for (var i = 0; i < valuesArray.length; ++i) {
|
|
103
|
+
valuesJoined += (i === 0 ? '' : ',') + formatter(encoder(valuesArray[i], defaults.encoder, charset, 'value', format));
|
|
104
|
+
}
|
|
105
|
+
return [formatter(keyValue) + '=' + valuesJoined];
|
|
106
|
+
}
|
|
98
107
|
return [formatter(keyValue) + '=' + formatter(encoder(obj, defaults.encoder, charset, 'value', format))];
|
|
99
108
|
}
|
|
100
109
|
return [formatter(prefix) + '=' + formatter(String(obj))];
|
|
@@ -109,7 +118,7 @@ var stringify = function stringify(
|
|
|
109
118
|
var objKeys;
|
|
110
119
|
if (generateArrayPrefix === 'comma' && isArray(obj)) {
|
|
111
120
|
// we need to join elements in
|
|
112
|
-
objKeys = [{ value: obj.length > 0 ? obj.join(',') || null : undefined }];
|
|
121
|
+
objKeys = [{ value: obj.length > 0 ? obj.join(',') || null : void undefined }];
|
|
113
122
|
} else if (isArray(filter)) {
|
|
114
123
|
objKeys = filter;
|
|
115
124
|
} else {
|
|
@@ -117,9 +126,9 @@ var stringify = function stringify(
|
|
|
117
126
|
objKeys = sort ? keys.sort(sort) : keys;
|
|
118
127
|
}
|
|
119
128
|
|
|
120
|
-
for (var
|
|
121
|
-
var key = objKeys[
|
|
122
|
-
var value = typeof key === 'object' && key.value !== undefined ? key.value : obj[key];
|
|
129
|
+
for (var j = 0; j < objKeys.length; ++j) {
|
|
130
|
+
var key = objKeys[j];
|
|
131
|
+
var value = typeof key === 'object' && typeof key.value !== 'undefined' ? key.value : obj[key];
|
|
123
132
|
|
|
124
133
|
if (skipNulls && value === null) {
|
|
125
134
|
continue;
|
|
@@ -155,7 +164,7 @@ var normalizeStringifyOptions = function normalizeStringifyOptions(opts) {
|
|
|
155
164
|
return defaults;
|
|
156
165
|
}
|
|
157
166
|
|
|
158
|
-
if (opts.encoder !== null && opts.encoder !== undefined && typeof opts.encoder !== 'function') {
|
|
167
|
+
if (opts.encoder !== null && typeof opts.encoder !== 'undefined' && typeof opts.encoder !== 'function') {
|
|
159
168
|
throw new TypeError('Encoder has to be a function.');
|
|
160
169
|
}
|
|
161
170
|
|
package/lib/utils.js
CHANGED
|
@@ -8,7 +8,7 @@ var isArray = Array.isArray;
|
|
|
8
8
|
var hexTable = (function () {
|
|
9
9
|
var array = [];
|
|
10
10
|
for (var i = 0; i < 256; ++i) {
|
|
11
|
-
array.
|
|
11
|
+
array[array.length] = '%' + ((i < 16 ? '0' : '' + i.toString(16)).toUpperCase());
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
return array;
|
|
@@ -24,7 +24,7 @@ var compactQueue = function compactQueue(queue) {
|
|
|
24
24
|
|
|
25
25
|
for (var j = 0; j < obj.length; ++j) {
|
|
26
26
|
if (typeof obj[j] !== 'undefined') {
|
|
27
|
-
compacted.
|
|
27
|
+
compacted[compacted.length] = obj[j];
|
|
28
28
|
}
|
|
29
29
|
}
|
|
30
30
|
|
|
@@ -52,7 +52,7 @@ var merge = function merge(target, source, options) {
|
|
|
52
52
|
|
|
53
53
|
if (typeof source !== 'object') {
|
|
54
54
|
if (isArray(target)) {
|
|
55
|
-
target.
|
|
55
|
+
target[target.length] = source;
|
|
56
56
|
} else if (target && typeof target === 'object') {
|
|
57
57
|
if ((options && (options.plainObjects || options.allowPrototypes)) || !has.call(Object.prototype, source)) {
|
|
58
58
|
target[source] = true;
|
|
@@ -80,7 +80,7 @@ var merge = function merge(target, source, options) {
|
|
|
80
80
|
if (targetItem && typeof targetItem === 'object' && item && typeof item === 'object') {
|
|
81
81
|
target[i] = merge(targetItem, item, options);
|
|
82
82
|
} else {
|
|
83
|
-
target.
|
|
83
|
+
target[target.length] = item;
|
|
84
84
|
}
|
|
85
85
|
} else {
|
|
86
86
|
target[i] = item;
|
|
@@ -177,6 +177,7 @@ var encode = function encode(str, defaultEncoder, charset, kind, format) {
|
|
|
177
177
|
|
|
178
178
|
i += 1;
|
|
179
179
|
c = 0x10000 + (((c & 0x3FF) << 10) | (string.charCodeAt(i) & 0x3FF));
|
|
180
|
+
/* eslint operator-linebreak: [2, "before"] */
|
|
180
181
|
out += hexTable[0xF0 | (c >> 18)]
|
|
181
182
|
+ hexTable[0x80 | ((c >> 12) & 0x3F)]
|
|
182
183
|
+ hexTable[0x80 | ((c >> 6) & 0x3F)]
|
|
@@ -199,8 +200,8 @@ var compact = function compact(value) {
|
|
|
199
200
|
var key = keys[j];
|
|
200
201
|
var val = obj[key];
|
|
201
202
|
if (typeof val === 'object' && val !== null && refs.indexOf(val) === -1) {
|
|
202
|
-
queue.
|
|
203
|
-
refs.
|
|
203
|
+
queue[queue.length] = { obj: obj, prop: key };
|
|
204
|
+
refs[refs.length] = val;
|
|
204
205
|
}
|
|
205
206
|
}
|
|
206
207
|
}
|
|
@@ -230,7 +231,7 @@ var maybeMap = function maybeMap(val, fn) {
|
|
|
230
231
|
if (isArray(val)) {
|
|
231
232
|
var mapped = [];
|
|
232
233
|
for (var i = 0; i < val.length; i += 1) {
|
|
233
|
-
mapped.
|
|
234
|
+
mapped[mapped.length] = fn(val[i]);
|
|
234
235
|
}
|
|
235
236
|
return mapped;
|
|
236
237
|
}
|
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.9.
|
|
5
|
+
"version": "6.9.8",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
8
8
|
"url": "https://github.com/ljharb/qs.git"
|
|
@@ -29,34 +29,35 @@
|
|
|
29
29
|
"engines": {
|
|
30
30
|
"node": ">=0.6"
|
|
31
31
|
},
|
|
32
|
-
"dependencies": {},
|
|
33
32
|
"devDependencies": {
|
|
34
|
-
"@ljharb/eslint-config": "^
|
|
35
|
-
"aud": "^1.1.
|
|
33
|
+
"@ljharb/eslint-config": "^20.1.0",
|
|
34
|
+
"aud": "^1.1.5",
|
|
36
35
|
"browserify": "^16.5.2",
|
|
37
36
|
"eclint": "^2.8.1",
|
|
38
|
-
"eslint": "^
|
|
37
|
+
"eslint": "^8.6.0",
|
|
39
38
|
"evalmd": "^0.0.19",
|
|
40
39
|
"for-each": "^0.3.3",
|
|
41
|
-
"has-symbols": "^1.0.
|
|
40
|
+
"has-symbols": "^1.0.2",
|
|
42
41
|
"iconv-lite": "^0.5.1",
|
|
42
|
+
"in-publish": "^2.0.1",
|
|
43
43
|
"mkdirp": "^0.5.5",
|
|
44
44
|
"nyc": "^10.3.2",
|
|
45
|
-
"object-inspect": "^1.
|
|
45
|
+
"object-inspect": "^1.12.0",
|
|
46
46
|
"qs-iconv": "^1.0.4",
|
|
47
|
-
"safe-publish-latest": "^
|
|
47
|
+
"safe-publish-latest": "^2.0.0",
|
|
48
48
|
"safer-buffer": "^2.1.2",
|
|
49
|
-
"tape": "^5.
|
|
49
|
+
"tape": "^5.4.0"
|
|
50
50
|
},
|
|
51
51
|
"scripts": {
|
|
52
|
-
"
|
|
52
|
+
"prepublishOnly": "safe-publish-latest && npm run dist",
|
|
53
|
+
"prepublish": "not-in-publish || npm run prepublishOnly",
|
|
53
54
|
"pretest": "npm run --silent readme && npm run --silent lint",
|
|
54
55
|
"test": "npm run tests-only",
|
|
55
56
|
"tests-only": "nyc tape 'test/**/*.js'",
|
|
56
57
|
"posttest": "aud --production",
|
|
57
58
|
"readme": "evalmd README.md",
|
|
58
|
-
"postlint": "eclint check * lib/* test/*",
|
|
59
|
-
"lint": "eslint
|
|
59
|
+
"postlint": "eclint check * lib/* test/* !dist/*",
|
|
60
|
+
"lint": "eslint .",
|
|
60
61
|
"dist": "mkdirp dist && browserify --standalone Qs lib/index.js > dist/qs.js"
|
|
61
62
|
},
|
|
62
63
|
"license": "BSD-3-Clause",
|
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
|
|
136
|
-
st.equal(qs.stringify({ a: { b: ['c', 'd'] } }, { arrayFormat: 'brackets' }), 'a
|
|
137
|
-
st.equal(qs.stringify({ a: { b: ['c', 'd'] } }, { arrayFormat: 'comma' }), 'a
|
|
138
|
-
st.equal(qs.stringify({ a: { b: ['c', 'd'] } }), 'a
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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] }, {
|
|
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] }, {
|
|
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] }, {
|
|
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
|
);
|
|
@@ -784,7 +790,12 @@ test('stringify()', function (t) {
|
|
|
784
790
|
st.equal(qs.stringify(withArray, { encode: false }), 'a[b][0][c]=d&a[b][0][e]=f', 'array, no arrayFormat');
|
|
785
791
|
st.equal(qs.stringify(withArray, { encode: false, arrayFormat: 'bracket' }), 'a[b][0][c]=d&a[b][0][e]=f', 'array, bracket');
|
|
786
792
|
st.equal(qs.stringify(withArray, { encode: false, arrayFormat: 'indices' }), 'a[b][0][c]=d&a[b][0][e]=f', 'array, indices');
|
|
787
|
-
st.equal(
|
|
793
|
+
st.equal(
|
|
794
|
+
qs.stringify(withArray, { encode: false, arrayFormat: 'comma' }),
|
|
795
|
+
'???',
|
|
796
|
+
'array, comma',
|
|
797
|
+
{ skip: 'TODO: figure out what this should do' }
|
|
798
|
+
);
|
|
788
799
|
|
|
789
800
|
st.end();
|
|
790
801
|
});
|
package/.eslintignore
DELETED