aboutsummaryrefslogtreecommitdiffstats
path: root/node_modules/qs
diff options
context:
space:
mode:
Diffstat (limited to 'node_modules/qs')
-rw-r--r--node_modules/qs/.editorconfig46
-rw-r--r--node_modules/qs/.github/FUNDING.yml12
-rw-r--r--node_modules/qs/.github/SECURITY.md11
-rw-r--r--node_modules/qs/.github/THREAT_MODEL.md78
-rw-r--r--node_modules/qs/.nycrc13
-rw-r--r--node_modules/qs/CHANGELOG.md833
-rw-r--r--node_modules/qs/LICENSE.md29
-rw-r--r--node_modules/qs/README.md758
-rw-r--r--node_modules/qs/dist/qs.js141
-rw-r--r--node_modules/qs/eslint.config.mjs57
-rw-r--r--node_modules/qs/lib/formats.js23
-rw-r--r--node_modules/qs/lib/index.js11
-rw-r--r--node_modules/qs/lib/parse.js414
-rw-r--r--node_modules/qs/lib/stringify.js363
-rw-r--r--node_modules/qs/lib/utils.js381
-rw-r--r--node_modules/qs/package.json95
-rw-r--r--node_modules/qs/test/empty-keys-cases.js267
-rw-r--r--node_modules/qs/test/parse.js1933
-rw-r--r--node_modules/qs/test/stringify.js1448
-rw-r--r--node_modules/qs/test/utils.js626
20 files changed, 7539 insertions, 0 deletions
diff --git a/node_modules/qs/.editorconfig b/node_modules/qs/.editorconfig
new file mode 100644
index 0000000..dd5a8d8
--- /dev/null
+++ b/node_modules/qs/.editorconfig
@@ -0,0 +1,46 @@
+root = true
+
+[*]
+indent_style = space
+indent_size = 4
+end_of_line = lf
+charset = utf-8
+trim_trailing_whitespace = true
+insert_final_newline = true
+max_line_length = 180
+quote_type = single
+
+[test/*]
+max_line_length = off
+
+[LICENSE.md]
+indent_size = off
+
+[*.md]
+max_line_length = off
+
+[*.json]
+max_line_length = off
+
+[Makefile]
+max_line_length = off
+
+[CHANGELOG.md]
+indent_style = space
+indent_size = 2
+
+[LICENSE]
+indent_size = 2
+max_line_length = off
+
+[coverage/**/*]
+indent_size = off
+indent_style = off
+indent = off
+max_line_length = off
+
+[.nycrc]
+indent_style = tab
+
+[tea.yaml]
+indent_size = 2
diff --git a/node_modules/qs/.github/FUNDING.yml b/node_modules/qs/.github/FUNDING.yml
new file mode 100644
index 0000000..0355f4f
--- /dev/null
+++ b/node_modules/qs/.github/FUNDING.yml
@@ -0,0 +1,12 @@
+# These are supported funding model platforms
+
+github: [ljharb]
+patreon: # Replace with a single Patreon username
+open_collective: # Replace with a single Open Collective username
+ko_fi: # Replace with a single Ko-fi username
+tidelift: npm/qs
+community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
+liberapay: # Replace with a single Liberapay username
+issuehunt: # Replace with a single IssueHunt username
+otechie: # Replace with a single Otechie username
+custom: # Replace with a single custom sponsorship URL
diff --git a/node_modules/qs/.github/SECURITY.md b/node_modules/qs/.github/SECURITY.md
new file mode 100644
index 0000000..b499cb6
--- /dev/null
+++ b/node_modules/qs/.github/SECURITY.md
@@ -0,0 +1,11 @@
+# Security
+
+Please file a private vulnerability report via GitHub, email [@ljharb](https://github.com/ljharb), or see https://tidelift.com/security if you have a potential security vulnerability to report.
+
+## Incident Response Plan
+
+Please see our [Incident Response Plan](https://github.com/ljharb/.github/blob/main/INCIDENT_RESPONSE_PLAN.md).
+
+## Threat Model
+
+Please see [THREAT_MODEL.md](./THREAT_MODEL.md).
diff --git a/node_modules/qs/.github/THREAT_MODEL.md b/node_modules/qs/.github/THREAT_MODEL.md
new file mode 100644
index 0000000..7e6fef1
--- /dev/null
+++ b/node_modules/qs/.github/THREAT_MODEL.md
@@ -0,0 +1,78 @@
+## Threat Model for qs (querystring parsing library)
+
+### 1. Library Overview
+
+- **Library Name:** qs
+- **Brief Description:** A JavaScript library for parsing and stringifying URL query strings, supporting nested objects and arrays. It is widely used in Node.js and web applications for processing query parameters[2][6][8].
+- **Key Public APIs/Functions:** `qs.parse()`, `qs.stringify()`
+
+### 2. Define Scope
+
+This threat model focuses on the core parsing and stringifying functionality, specifically the handling of nested objects and arrays, option validation, and cycle management in stringification.
+
+### 3. Conceptual System Diagram
+
+```
+Caller Application → qs.parse(input, options) → Parsing Engine → Output Object
+ │
+ └→ Options Handling
+
+Caller Application → qs.stringify(obj, options) → Stringifying Engine → Output String
+ │
+ └→ Options Handling
+ └→ Cycle Tracking
+```
+
+**Trust Boundaries:**
+- **Input string (parse):** May come from untrusted sources (e.g., user input, network requests)
+- **Input object (stringify):** May contain cycles, which can lead to infinite loops during stringification
+- **Options:** Provided by the caller
+- **Cycle Tracking:** Used only during stringification to detect and handle circular references
+
+### 4. Identify Assets
+
+- **Integrity of parsed output:** Prevent malicious manipulation of the output object structure, especially ensuring builtins/globals are not modified as a result of parse[3][4][8].
+- **Confidentiality of processed data:** Avoid leaking sensitive information through errors or output.
+- **Availability/performance for host application:** Prevent crashes or resource exhaustion in the consuming application.
+- **Security of host application:** Prevent the library from being a vector for attacks (e.g., prototype pollution, DoS).
+- **Reputation of library:** Maintain trust by avoiding supply chain attacks and vulnerabilities[1].
+
+### 5. Identify Threats
+
+| Component / API / Interaction | S | T | R | I | D | E |
+|---------------------------------------|----|----|----|----|----|----|
+| Public API Call (`parse`) | – | ✓ | – | ✓ | ✓ | ✓ |
+| Public API Call (`stringify`) | – | ✓ | – | ✓ | ✓ | – |
+| Options Handling | ✓ | ✓ | – | ✓ | – | ✓ |
+| Dependency Interaction | – | – | – | – | ✓ | – |
+
+**Key Threats:**
+- **Tampering:** Malicious input can, if not prevented, alter parsed output (e.g., prototype pollution via `__proto__`, modification of builtins/globals)[3][4][8].
+- **Information Disclosure:** Error messages may expose internal details or sensitive data.
+- **Denial of Service:** Large or malformed input can exhaust memory or CPU.
+- **Elevation of Privilege:** Prototype pollution can lead to unintended privilege escalation in the host application[3][4][8].
+
+### 6. Mitigation/Countermeasures
+
+| Threat Identified | Proposed Mitigation |
+|---------------------------------------------------|---------------------|
+| Tampering (malicious input, prototype pollution) | Strict input validation; keep `allowPrototypes: false` by default; use `plainObjects` for output; ensure builtins/globals are never modified by parse[4][8]. |
+| Information Disclosure (error messages) | Generic error messages without stack traces or internal paths. |
+| Denial of Service (memory/CPU exhaustion) | Enforce `arrayLimit` and `parameterLimit` with safe defaults; enable `throwOnLimitExceeded`; limit nesting depth[7]. |
+| Elevation of Privilege (prototype pollution) | Keep `allowPrototypes: false`; validate options against allowlist; use `plainObjects` to avoid prototype pollution[4][8]. |
+
+### 7. Risk Ranking
+
+- **High:** Denial of Service via array parsing or malformed input (historical vulnerability)
+- **Medium:** Prototype pollution via options or input (if `allowPrototypes` enabled)
+- **Low:** Information disclosure in errors
+
+### 8. Next Steps & Review
+
+1. **Audit option validation logic.**
+2. **Add depth limiting to nested parsing and stringification.**
+3. **Implement fuzz testing for parser and stringifier edge cases.**
+4. **Regularly review dependencies for vulnerabilities.**
+5. **Keep documentation and threat model up to date.**
+6. **Ensure builtins/globals are never modified as a result of parse.**
+7. **Support round-trip consistency between parse and stringify as a non-security goal, with the right options[5][9].**
diff --git a/node_modules/qs/.nycrc b/node_modules/qs/.nycrc
new file mode 100644
index 0000000..1d57cab
--- /dev/null
+++ b/node_modules/qs/.nycrc
@@ -0,0 +1,13 @@
+{
+ "all": true,
+ "check-coverage": false,
+ "reporter": ["text-summary", "text", "html", "json"],
+ "lines": 86,
+ "statements": 85.93,
+ "functions": 82.43,
+ "branches": 76.06,
+ "exclude": [
+ "coverage",
+ "dist"
+ ]
+}
diff --git a/node_modules/qs/CHANGELOG.md b/node_modules/qs/CHANGELOG.md
new file mode 100644
index 0000000..14953a4
--- /dev/null
+++ b/node_modules/qs/CHANGELOG.md
@@ -0,0 +1,833 @@
+## **6.15.3**
+- [Fix] `parse`: enforce `throwOnLimitExceeded` for cumulative array growth via `combine`/`merge`
+- [Fix] `utils`: respect encoding of surrogate pairs across chunks (#559)
+- [Robustness] `parse`: throw the `arrayLimit` error before splitting oversized comma values
+- [Robustness] `utils.merge` / `utils.assign`: avoid invoking `__proto__` setter when copying own properties
+- [Robustness] `utils`: enforce `arrayLimit` consistently across `merge`'s array paths
+- [Perf] `utils`: make `compact` O(n) via a side-channel visited-set instead of `Array.indexOf`
+- [Deps] update `side-channel`
+- [Dev Deps] update `eslint`, `mock-property`, `tape`
+- [Tests] `parse`: characterize current lenient handling of unbalanced bracket keys (#558)
+
+## **6.15.2**
+- [Fix] `stringify`: skip null/undefined entries in `arrayFormat: 'comma'` + `encodeValuesOnly` instead of crashing in `encoder`
+- [Fix] `stringify`: use configured `delimiter` after `charsetSentinel` (#555)
+- [Fix] `stringify`: apply `formatter` to encoded key under `strictNullHandling` (#554)
+- [Fix] `stringify`: skip null/undefined filter-array entries instead of crashing in `encoder` (#551)
+- [Fix] `parse`: handle nested bracket groups and add regression tests (#530)
+- [readme] fix grammar (#550)
+- [Dev Deps] update `@ljharb/eslint-config`
+- [Tests] add regression tests for keys containing percent-encoded bracket text
+
+## **6.15.1**
+- [Fix] `parse`: `parameterLimit: Infinity` with `throwOnLimitExceeded: true` silently drops all parameters
+- [Deps] update `@ljharb/eslint-config`
+- [Dev Deps] update `@ljharb/eslint-config`, `iconv-lite`
+- [Tests] increase coverage
+
+## **6.15.0**
+- [New] `parse`: add `strictMerge` option to wrap object/primitive conflicts in an array (#425, #122)
+- [Fix] `duplicates` option should not apply to bracket notation keys (#514)
+
+## **6.14.2**
+- [Fix] `parse`: mark overflow objects for indexed notation exceeding `arrayLimit` (#546)
+- [Fix] `arrayLimit` means max count, not max index, in `combine`/`merge`/`parseArrayValue`
+- [Fix] `parse`: throw on `arrayLimit` exceeded with indexed notation when `throwOnLimitExceeded` is true (#529)
+- [Fix] `parse`: enforce `arrayLimit` on `comma`-parsed values
+- [Fix] `parse`: fix error message to reflect arrayLimit as max index; remove extraneous comments (#545)
+- [Robustness] avoid `.push`, use `void`
+- [readme] document that `addQueryPrefix` does not add `?` to empty output (#418)
+- [readme] clarify `parseArrays` and `arrayLimit` documentation (#543)
+- [readme] replace runkit CI badge with shields.io check-runs badge
+- [meta] fix changelog typo (`arrayLength` → `arrayLimit`)
+- [actions] fix rebase workflow permissions
+
+## **6.14.1**
+- [Fix] ensure `arrayLimit` applies to `[]` notation as well
+- [Fix] `parse`: when a custom decoder returns `null` for a key, ignore that key
+- [Refactor] `parse`: extract key segment splitting helper
+- [meta] add threat model
+- [actions] add workflow permissions
+- [Tests] `stringify`: increase coverage
+- [Dev Deps] update `eslint`, `@ljharb/eslint-config`, `npmignore`, `es-value-fixtures`, `for-each`, `object-inspect`
+
+## **6.14.0**
+- [New] `parse`: add `throwOnParameterLimitExceeded` option (#517)
+- [Refactor] `parse`: use `utils.combine` more
+- [patch] `parse`: add explicit `throwOnLimitExceeded` default
+- [actions] use shared action; re-add finishers
+- [meta] Fix changelog formatting bug
+- [Deps] update `side-channel`
+- [Dev Deps] update `es-value-fixtures`, `has-bigints`, `has-proto`, `has-symbols`
+- [Tests] increase coverage
+
+## **6.13.3**
+[Fix] fix regressions from robustness refactor
+[actions] update reusable workflows
+
+## **6.13.2**
+- [Robustness] avoid `.push`, use `void`
+- [readme] clarify `parseArrays` and `arrayLimit` documentation (#543)
+- [readme] document that `addQueryPrefix` does not add `?` to empty output (#418)
+- [readme] replace runkit CI badge with shields.io check-runs badge
+- [actions] fix rebase workflow permissions
+
+## **6.13.1**
+- [Fix] `stringify`: avoid a crash when a `filter` key is `null`
+- [Fix] `utils.merge`: functions should not be stringified into keys
+- [Fix] `parse`: avoid a crash with interpretNumericEntities: true, comma: true, and iso charset
+- [Fix] `stringify`: ensure a non-string `filter` does not crash
+- [Refactor] use `__proto__` syntax instead of `Object.create` for null objects
+- [Refactor] misc cleanup
+- [Tests] `utils.merge`: add some coverage
+- [Tests] fix a test case
+- [actions] split out node 10-20, and 20+
+- [Dev Deps] update `es-value-fixtures`, `mock-property`, `object-inspect`, `tape`
+
+## **6.13.0**
+- [New] `parse`: add `strictDepth` option (#511)
+- [Tests] use `npm audit` instead of `aud`
+
+## **6.12.5**
+- [Fix] fix regressions from robustness refactor
+- [actions] update reusable workflows
+
+## **6.12.4**
+- [Robustness] avoid `.push`, use `void`
+- [readme] clarify `parseArrays` and `arrayLimit` documentation (#543)
+- [readme] document that `addQueryPrefix` does not add `?` to empty output (#418)
+- [readme] replace runkit CI badge with shields.io check-runs badge
+- [actions] fix rebase workflow permissions
+
+## **6.12.3**
+- [Fix] `parse`: properly account for `strictNullHandling` when `allowEmptyArrays`
+- [meta] fix changelog indentation
+
+## **6.12.2**
+- [Fix] `parse`: parse encoded square brackets (#506)
+- [readme] add CII best practices badge
+
+## **6.12.1**
+- [Fix] `parse`: Disable `decodeDotInKeys` by default to restore previous behavior (#501)
+- [Performance] `utils`: Optimize performance under large data volumes, reduce memory usage, and speed up processing (#502)
+- [Refactor] `utils`: use `+=`
+- [Tests] increase coverage
+
+## **6.12.0**
+
+- [New] `parse`/`stringify`: add `decodeDotInKeys`/`encodeDotKeys` options (#488)
+- [New] `parse`: add `duplicates` option
+- [New] `parse`/`stringify`: add `allowEmptyArrays` option to allow [] in object values (#487)
+- [Refactor] `parse`/`stringify`: move allowDots config logic to its own variable
+- [Refactor] `stringify`: move option-handling code into `normalizeStringifyOptions`
+- [readme] update readme, add logos (#484)
+- [readme] `stringify`: clarify default `arrayFormat` behavior
+- [readme] fix line wrapping
+- [readme] remove dead badges
+- [Deps] update `side-channel`
+- [meta] make the dist build 50% smaller
+- [meta] add `sideEffects` flag
+- [meta] run build in prepack, not prepublish
+- [Tests] `parse`: remove useless tests; add coverage
+- [Tests] `stringify`: increase coverage
+- [Tests] use `mock-property`
+- [Tests] `stringify`: improve coverage
+- [Dev Deps] update `@ljharb/eslint-config `, `aud`, `has-override-mistake`, `has-property-descriptors`, `mock-property`, `npmignore`, `object-inspect`, `tape`
+- [Dev Deps] pin `glob`, since v10.3.8+ requires a broken `jackspeak`
+- [Dev Deps] pin `jackspeak` since 2.1.2+ depends on npm aliases, which kill the install process in npm < 6
+
+## **6.11.4**
+- [Fix] fix regressions from robustness refactor
+- [actions] update reusable workflows
+
+## **6.11.3**
+- [Robustness] avoid `.push`, use `void`
+- [readme] clarify `parseArrays` and `arrayLimit` documentation (#543)
+- [readme] document that `addQueryPrefix` does not add `?` to empty output (#418)
+- [readme] replace runkit CI badge with shields.io check-runs badge
+- [actions] fix rebase workflow permissions
+
+## **6.11.2**
+- [Fix] `parse`: Fix parsing when the global Object prototype is frozen (#473)
+- [Tests] add passing test cases with empty keys (#473)
+
+## **6.11.1**
+- [Fix] `stringify`: encode comma values more consistently (#463)
+- [readme] add usage of `filter` option for injecting custom serialization, i.e. of custom types (#447)
+- [meta] remove extraneous code backticks (#457)
+- [meta] fix changelog markdown
+- [actions] update checkout action
+- [actions] restrict action permissions
+- [Dev Deps] update `@ljharb/eslint-config`, `aud`, `object-inspect`, `tape`
+
+## **6.11.0**
+- [New] [Fix] `stringify`: revert 0e903c0; add `commaRoundTrip` option (#442)
+- [readme] fix version badge
+
+## **6.10.7**
+- [Fix] fix regressions from robustness refactor
+- [actions] update reusable workflows
+
+## **6.10.6**
+- [Robustness] avoid `.push`, use `void`
+- [readme] clarify `parseArrays` and `arrayLimit` documentation (#543)
+- [readme] document that `addQueryPrefix` does not add `?` to empty output (#418)
+- [readme] replace runkit CI badge with shields.io check-runs badge
+- [actions] fix rebase workflow permissions
+
+## **6.10.5**
+- [Fix] `stringify`: with `arrayFormat: comma`, properly include an explicit `[]` on a single-item array (#434)
+
+## **6.10.4**
+- [Fix] `stringify`: with `arrayFormat: comma`, include an explicit `[]` on a single-item array (#441)
+- [meta] use `npmignore` to autogenerate an npmignore file
+- [Dev Deps] update `eslint`, `@ljharb/eslint-config`, `aud`, `has-symbol`, `object-inspect`, `tape`
+
+## **6.10.3**
+- [Fix] `parse`: ignore `__proto__` keys (#428)
+- [Robustness] `stringify`: avoid relying on a global `undefined` (#427)
+- [actions] reuse common workflows
+- [Dev Deps] update `eslint`, `@ljharb/eslint-config`, `object-inspect`, `tape`
+
+## **6.10.2**
+- [Fix] `stringify`: actually fix cyclic references (#426)
+- [Fix] `stringify`: avoid encoding arrayformat comma when `encodeValuesOnly = true` (#424)
+- [readme] remove travis badge; add github actions/codecov badges; update URLs
+- [Docs] add note and links for coercing primitive values (#408)
+- [actions] update codecov uploader
+- [actions] update workflows
+- [Tests] clean up stringify tests slightly
+- [Dev Deps] update `eslint`, `@ljharb/eslint-config`, `aud`, `object-inspect`, `safe-publish-latest`, `tape`
+
+## **6.10.1**
+- [Fix] `stringify`: avoid exception on repeated object values (#402)
+
+## **6.10.0**
+- [New] `stringify`: throw on cycles, instead of an infinite loop (#395, #394, #393)
+- [New] `parse`: add `allowSparse` option for collapsing arrays with missing indices (#312)
+- [meta] fix README.md (#399)
+- [meta] only run `npm run dist` in publish, not install
+- [Dev Deps] update `eslint`, `@ljharb/eslint-config`, `aud`, `has-symbols`, `tape`
+- [Tests] fix tests on node v0.6
+- [Tests] use `ljharb/actions/node/install` instead of `ljharb/actions/node/run`
+- [Tests] Revert "[meta] ignore eclint transitive audit warning"
+
+## **6.9.9**
+- [Fix] fix regressions from robustness refactor
+- [meta] add `npmignore` to autogenerate an npmignore file
+- [actions] update reusable workflows
+
+## **6.9.8**
+- [Robustness] avoid `.push`, use `void`
+- [readme] clarify `parseArrays` and `arrayLimit` documentation (#543)
+- [readme] document that `addQueryPrefix` does not add `?` to empty output (#418)
+- [readme] replace runkit CI badge with shields.io check-runs badge
+- [actions] fix rebase workflow permissions
+
+## **6.9.7**
+- [Fix] `parse`: ignore `__proto__` keys (#428)
+- [Fix] `stringify`: avoid encoding arrayformat comma when `encodeValuesOnly = true` (#424)
+- [Robustness] `stringify`: avoid relying on a global `undefined` (#427)
+- [readme] remove travis badge; add github actions/codecov badges; update URLs
+- [Docs] add note and links for coercing primitive values (#408)
+- [Tests] clean up stringify tests slightly
+- [meta] fix README.md (#399)
+- Revert "[meta] ignore eclint transitive audit warning"
+- [actions] backport actions from main
+- [Dev Deps] backport updates from main
+
+## **6.9.6**
+- [Fix] restore `dist` dir; mistakenly removed in d4f6c32
+
+## **6.9.5**
+- [Fix] `stringify`: do not encode parens for RFC1738
+- [Fix] `stringify`: fix arrayFormat comma with empty array/objects (#350)
+- [Refactor] `format`: remove `util.assign` call
+- [meta] add "Allow Edits" workflow; update rebase workflow
+- [actions] switch Automatic Rebase workflow to `pull_request_target` event
+- [Tests] `stringify`: add tests for #378
+- [Tests] migrate tests to Github Actions
+- [Tests] run `nyc` on all tests; use `tape` runner
+- [Dev Deps] update `eslint`, `@ljharb/eslint-config`, `browserify`, `mkdirp`, `object-inspect`, `tape`; add `aud`
+
+## **6.9.4**
+- [Fix] `stringify`: when `arrayFormat` is `comma`, respect `serializeDate` (#364)
+- [Refactor] `stringify`: reduce branching (part of #350)
+- [Refactor] move `maybeMap` to `utils`
+- [Dev Deps] update `browserify`, `tape`
+
+## **6.9.3**
+- [Fix] proper comma parsing of URL-encoded commas (#361)
+- [Fix] parses comma delimited array while having percent-encoded comma treated as normal text (#336)
+
+## **6.9.2**
+- [Fix] `parse`: Fix parsing array from object with `comma` true (#359)
+- [Fix] `parse`: throw a TypeError instead of an Error for bad charset (#349)
+- [meta] ignore eclint transitive audit warning
+- [meta] fix indentation in package.json
+- [meta] add tidelift marketing copy
+- [Dev Deps] update `eslint`, `@ljharb/eslint-config`, `object-inspect`, `has-symbols`, `tape`, `mkdirp`, `iconv-lite`
+- [actions] add automatic rebasing / merge commit blocking
+
+## **6.9.1**
+- [Fix] `parse`: with comma true, handle field that holds an array of arrays (#335)
+- [Fix] `parse`: with comma true, do not split non-string values (#334)
+- [meta] add `funding` field
+- [Dev Deps] update `eslint`, `@ljharb/eslint-config`
+- [Tests] use shared travis-ci config
+
+## **6.9.0**
+- [New] `parse`/`stringify`: Pass extra key/value argument to `decoder` (#333)
+- [Dev Deps] update `eslint`, `@ljharb/eslint-config`, `evalmd`
+- [Tests] `parse`: add passing `arrayFormat` tests
+- [Tests] add `posttest` using `npx aud` to run `npm audit` without a lockfile
+- [Tests] up to `node` `v12.10`, `v11.15`, `v10.16`, `v8.16`
+- [Tests] `Buffer.from` in node v5.0-v5.9 and v4.0-v4.4 requires a TypedArray
+
+## **6.8.5**
+- [Fix] fix regressions from robustness refactor
+- [meta] add `npmignore` to autogenerate an npmignore file
+- [actions] update reusable workflows
+
+## **6.8.4**
+- [Robustness] avoid `.push`, use `void`
+- [readme] clarify `parseArrays` and `arrayLimit` documentation (#543)
+- [readme] document that `addQueryPrefix` does not add `?` to empty output (#418)
+- [readme] replace runkit CI badge with shields.io check-runs badge
+- [actions] fix rebase workflow permissions
+
+## **6.8.3**
+- [Fix] `parse`: ignore `__proto__` keys (#428)
+- [Robustness] `stringify`: avoid relying on a global `undefined` (#427)
+- [Fix] `stringify`: avoid encoding arrayformat comma when `encodeValuesOnly = true` (#424)
+- [readme] remove travis badge; add github actions/codecov badges; update URLs
+- [Tests] clean up stringify tests slightly
+- [Docs] add note and links for coercing primitive values (#408)
+- [meta] fix README.md (#399)
+- [actions] backport actions from main
+- [Dev Deps] backport updates from main
+- [Refactor] `stringify`: reduce branching
+- [meta] do not publish workflow files
+
+## **6.8.2**
+- [Fix] proper comma parsing of URL-encoded commas (#361)
+- [Fix] parses comma delimited array while having percent-encoded comma treated as normal text (#336)
+
+## **6.8.1**
+- [Fix] `parse`: Fix parsing array from object with `comma` true (#359)
+- [Fix] `parse`: throw a TypeError instead of an Error for bad charset (#349)
+- [Fix] `parse`: with comma true, handle field that holds an array of arrays (#335)
+- [fix] `parse`: with comma true, do not split non-string values (#334)
+- [meta] add tidelift marketing copy
+- [meta] add `funding` field
+- [Dev Deps] update `eslint`, `@ljharb/eslint-config`, `tape`, `safe-publish-latest`, `evalmd`, `has-symbols`, `iconv-lite`, `mkdirp`, `object-inspect`
+- [Tests] `parse`: add passing `arrayFormat` tests
+- [Tests] use shared travis-ci configs
+- [Tests] `Buffer.from` in node v5.0-v5.9 and v4.0-v4.4 requires a TypedArray
+- [actions] add automatic rebasing / merge commit blocking
+
+## **6.8.0**
+- [New] add `depth=false` to preserve the original key; [Fix] `depth=0` should preserve the original key (#326)
+- [New] [Fix] stringify symbols and bigints
+- [Fix] ensure node 0.12 can stringify Symbols
+- [Fix] fix for an impossible situation: when the formatter is called with a non-string value
+- [Refactor] `formats`: tiny bit of cleanup.
+- [Dev Deps] update `eslint`, `@ljharb/eslint-config`, `browserify`, `safe-publish-latest`, `iconv-lite`, `tape`
+- [Tests] add tests for `depth=0` and `depth=false` behavior, both current and intuitive/intended (#326)
+- [Tests] use `eclint` instead of `editorconfig-tools`
+- [docs] readme: add security note
+- [meta] add github sponsorship
+- [meta] add FUNDING.yml
+- [meta] Clean up license text so it’s properly detected as BSD-3-Clause
+
+## **6.7.5**
+- [Fix] fix regressions from robustness refactor
+- [meta] add `npmignore` to autogenerate an npmignore file
+- [actions] update reusable workflows
+
+## **6.7.4**
+- [Robustness] avoid `.push`, use `void`
+- [readme] clarify `parseArrays` and `arrayLimit` documentation (#543)
+- [readme] document that `addQueryPrefix` does not add `?` to empty output (#418)
+- [readme] replace runkit CI badge with shields.io check-runs badge
+- [actions] fix rebase workflow permissions
+
+## **6.7.3**
+- [Fix] `parse`: ignore `__proto__` keys (#428)
+- [Fix] `stringify`: avoid encoding arrayformat comma when `encodeValuesOnly = true` (#424)
+- [Robustness] `stringify`: avoid relying on a global `undefined` (#427)
+- [readme] remove travis badge; add github actions/codecov badges; update URLs
+- [Docs] add note and links for coercing primitive values (#408)
+- [meta] fix README.md (#399)
+- [meta] do not publish workflow files
+- [actions] backport actions from main
+- [Dev Deps] backport updates from main
+- [Tests] use `nyc` for coverage
+- [Tests] clean up stringify tests slightly
+
+## **6.7.2**
+- [Fix] proper comma parsing of URL-encoded commas (#361)
+- [Fix] parses comma delimited array while having percent-encoded comma treated as normal text (#336)
+
+## **6.7.1**
+- [Fix] `parse`: Fix parsing array from object with `comma` true (#359)
+- [Fix] `parse`: with comma true, handle field that holds an array of arrays (#335)
+- [fix] `parse`: with comma true, do not split non-string values (#334)
+- [Fix] `parse`: throw a TypeError instead of an Error for bad charset (#349)
+- [Fix] fix for an impossible situation: when the formatter is called with a non-string value
+- [Refactor] `formats`: tiny bit of cleanup.
+- readme: add security note
+- [meta] add tidelift marketing copy
+- [meta] add `funding` field
+- [meta] add FUNDING.yml
+- [meta] Clean up license text so it’s properly detected as BSD-3-Clause
+- [Dev Deps] update `eslint`, `@ljharb/eslint-config`, `tape`, `safe-publish-latest`, `evalmd`, `iconv-lite`, `mkdirp`, `object-inspect`, `browserify`
+- [Tests] `parse`: add passing `arrayFormat` tests
+- [Tests] use shared travis-ci configs
+- [Tests] `Buffer.from` in node v5.0-v5.9 and v4.0-v4.4 requires a TypedArray
+- [Tests] add tests for `depth=0` and `depth=false` behavior, both current and intuitive/intended
+- [Tests] use `eclint` instead of `editorconfig-tools`
+- [actions] add automatic rebasing / merge commit blocking
+
+## **6.7.0**
+- [New] `stringify`/`parse`: add `comma` as an `arrayFormat` option (#276, #219)
+- [Fix] correctly parse nested arrays (#212)
+- [Fix] `utils.merge`: avoid a crash with a null target and a truthy non-array source, also with an array source
+- [Robustness] `stringify`: cache `Object.prototype.hasOwnProperty`
+- [Refactor] `utils`: `isBuffer`: small tweak; add tests
+- [Refactor] use cached `Array.isArray`
+- [Refactor] `parse`/`stringify`: make a function to normalize the options
+- [Refactor] `utils`: reduce observable [[Get]]s
+- [Refactor] `stringify`/`utils`: cache `Array.isArray`
+- [Tests] always use `String(x)` over `x.toString()`
+- [Tests] fix Buffer tests to work in node < 4.5 and node < 5.10
+- [Tests] temporarily allow coverage to fail
+
+## **6.6.3**
+- [Fix] fix regressions from robustness refactor
+- [meta] add `npmignore` to autogenerate an npmignore file
+- [actions] update reusable workflows
+
+## **6.6.2**
+- [Robustness] avoid `.push`, use `void`
+- [readme] clarify `parseArrays` and `arrayLimit` documentation (#543)
+- [readme] document that `addQueryPrefix` does not add `?` to empty output (#418)
+- [readme] replace runkit CI badge with shields.io check-runs badge
+- [actions] fix rebase workflow permissions
+
+## **6.6.1**
+- [Fix] `parse`: ignore `__proto__` keys (#428)
+- [Fix] fix for an impossible situation: when the formatter is called with a non-string value
+- [Fix] `utils.merge`: avoid a crash with a null target and an array source
+- [Fix] `utils.merge`: avoid a crash with a null target and a truthy non-array source
+- [Fix] correctly parse nested arrays
+- [Robustness] `stringify`: avoid relying on a global `undefined` (#427)
+- [Robustness] `stringify`: cache `Object.prototype.hasOwnProperty`
+- [Refactor] `formats`: tiny bit of cleanup.
+- [Refactor] `utils`: `isBuffer`: small tweak; add tests
+- [Refactor]: `stringify`/`utils`: cache `Array.isArray`
+- [Refactor] `utils`: reduce observable [[Get]]s
+- [Refactor] use cached `Array.isArray`
+- [Refactor] `parse`/`stringify`: make a function to normalize the options
+- [readme] remove travis badge; add github actions/codecov badges; update URLs
+- [Docs] Clarify the need for "arrayLimit" option
+- [meta] fix README.md (#399)
+- [meta] do not publish workflow files
+- [meta] Clean up license text so it’s properly detected as BSD-3-Clause
+- [meta] add FUNDING.yml
+- [meta] Fixes typo in CHANGELOG.md
+- [actions] backport actions from main
+- [Tests] fix Buffer tests to work in node < 4.5 and node < 5.10
+- [Tests] always use `String(x)` over `x.toString()`
+- [Dev Deps] backport from main
+
+## **6.6.0**
+- [New] Add support for iso-8859-1, utf8 "sentinel" and numeric entities (#268)
+- [New] move two-value combine to a `utils` function (#189)
+- [Fix] `stringify`: fix a crash with `strictNullHandling` and a custom `filter`/`serializeDate` (#279)
+- [Fix] when `parseArrays` is false, properly handle keys ending in `[]` (#260)
+- [Fix] `stringify`: do not crash in an obscure combo of `interpretNumericEntities`, a bad custom `decoder`, & `iso-8859-1`
+- [Fix] `utils`: `merge`: fix crash when `source` is a truthy primitive & no options are provided
+- [refactor] `stringify`: Avoid arr = arr.concat(...), push to the existing instance (#269)
+- [Refactor] `parse`: only need to reassign the var once
+- [Refactor] `parse`/`stringify`: clean up `charset` options checking; fix defaults
+- [Refactor] add missing defaults
+- [Refactor] `parse`: one less `concat` call
+- [Refactor] `utils`: `compactQueue`: make it explicitly side-effecting
+- [Dev Deps] update `browserify`, `eslint`, `@ljharb/eslint-config`, `iconv-lite`, `safe-publish-latest`, `tape`
+- [Tests] up to `node` `v10.10`, `v9.11`, `v8.12`, `v6.14`, `v4.9`; pin included builds to LTS
+
+## **6.5.5**
+- [Fix] fix regressions from robustness refactor
+- [meta] add `npmignore` to autogenerate an npmignore file
+- [actions] update reusable workflows
+
+## **6.5.4**
+- [Robustness] avoid `.push`, use `void`
+- [readme] clarify `parseArrays` and `arrayLimit` documentation (#543)
+- [readme] document that `addQueryPrefix` does not add `?` to empty output (#418)
+- [readme] replace runkit CI badge with shields.io check-runs badge
+- [actions] fix rebase workflow permissions
+
+## **6.5.3**
+- [Fix] `parse`: ignore `__proto__` keys (#428)
+- [Fix] `utils.merge`: avoid a crash with a null target and a truthy non-array source
+- [Fix] correctly parse nested arrays
+- [Fix] `stringify`: fix a crash with `strictNullHandling` and a custom `filter`/`serializeDate` (#279)
+- [Fix] `utils`: `merge`: fix crash when `source` is a truthy primitive & no options are provided
+- [Fix] when `parseArrays` is false, properly handle keys ending in `[]`
+- [Fix] fix for an impossible situation: when the formatter is called with a non-string value
+- [Fix] `utils.merge`: avoid a crash with a null target and an array source
+- [Refactor] `utils`: reduce observable [[Get]]s
+- [Refactor] use cached `Array.isArray`
+- [Refactor] `stringify`: Avoid arr = arr.concat(...), push to the existing instance (#269)
+- [Refactor] `parse`: only need to reassign the var once
+- [Robustness] `stringify`: avoid relying on a global `undefined` (#427)
+- [readme] remove travis badge; add github actions/codecov badges; update URLs
+- [Docs] Clean up license text so it’s properly detected as BSD-3-Clause
+- [Docs] Clarify the need for "arrayLimit" option
+- [meta] fix README.md (#399)
+- [meta] add FUNDING.yml
+- [actions] backport actions from main
+- [Tests] always use `String(x)` over `x.toString()`
+- [Tests] remove nonexistent tape option
+- [Dev Deps] backport from main
+
+## **6.5.2**
+- [Fix] use `safer-buffer` instead of `Buffer` constructor
+- [Refactor] utils: `module.exports` one thing, instead of mutating `exports` (#230)
+- [Dev Deps] update `browserify`, `eslint`, `iconv-lite`, `safer-buffer`, `tape`, `browserify`
+
+## **6.5.1**
+- [Fix] Fix parsing & compacting very deep objects (#224)
+- [Refactor] name utils functions
+- [Dev Deps] update `eslint`, `@ljharb/eslint-config`, `tape`
+- [Tests] up to `node` `v8.4`; use `nvm install-latest-npm` so newer npm doesn’t break older node
+- [Tests] Use precise dist for Node.js 0.6 runtime (#225)
+- [Tests] make 0.6 required, now that it’s passing
+- [Tests] on `node` `v8.2`; fix npm on node 0.6
+
+## **6.5.0**
+- [New] add `utils.assign`
+- [New] pass default encoder/decoder to custom encoder/decoder functions (#206)
+- [New] `parse`/`stringify`: add `ignoreQueryPrefix`/`addQueryPrefix` options, respectively (#213)
+- [Fix] Handle stringifying empty objects with addQueryPrefix (#217)
+- [Fix] do not mutate `options` argument (#207)
+- [Refactor] `parse`: cache index to reuse in else statement (#182)
+- [Docs] add various badges to readme (#208)
+- [Dev Deps] update `eslint`, `browserify`, `iconv-lite`, `tape`
+- [Tests] up to `node` `v8.1`, `v7.10`, `v6.11`; npm v4.6 breaks on node < v1; npm v5+ breaks on node < v4
+- [Tests] add `editorconfig-tools`
+
+## **6.4.3**
+- [Fix] fix regressions from robustness refactor
+- [meta] add `npmignore` to autogenerate an npmignore file
+- [actions] update reusable workflows
+
+## **6.4.2**
+- [Robustness] avoid `.push`, use `void`
+- [readme] clarify `parseArrays` and `arrayLimit` documentation (#543)
+- [readme] replace runkit CI badge with shields.io check-runs badge
+- [readme] replace travis CI badge with shields.io check-runs badge
+- [actions] fix rebase workflow permissions
+
+## **6.4.1**
+- [Fix] `parse`: ignore `__proto__` keys (#428)
+- [Fix] fix for an impossible situation: when the formatter is called with a non-string value
+- [Fix] use `safer-buffer` instead of `Buffer` constructor
+- [Fix] `utils.merge`: avoid a crash with a null target and an array source
+- [Fix] `utils.merge`: avoid a crash with a null target and a truthy non-array source
+- [Fix] `stringify`: fix a crash with `strictNullHandling` and a custom `filter`/`serializeDate` (#279)
+- [Fix] `utils`: `merge`: fix crash when `source` is a truthy primitive & no options are provided
+- [Fix] when `parseArrays` is false, properly handle keys ending in `[]`
+- [Robustness] `stringify`: avoid relying on a global `undefined` (#427)
+- [Refactor] use cached `Array.isArray`
+- [Refactor] `stringify`: Avoid arr = arr.concat(...), push to the existing instance (#269)
+- [readme] remove travis badge; add github actions/codecov badges; update URLs
+- [Docs] Clarify the need for "arrayLimit" option
+- [meta] fix README.md (#399)
+- [meta] Clean up license text so it’s properly detected as BSD-3-Clause
+- [meta] add FUNDING.yml
+- [actions] backport actions from main
+- [Tests] remove nonexistent tape option
+- [Dev Deps] backport from main
+
+## **6.4.0**
+- [New] `qs.stringify`: add `encodeValuesOnly` option
+- [Fix] follow `allowPrototypes` option during merge (#201, #201)
+- [Fix] support keys starting with brackets (#202, #200)
+- [Fix] chmod a-x
+- [Dev Deps] update `eslint`
+- [Tests] up to `node` `v7.7`, `v6.10`,` v4.8`; disable osx builds since they block linux builds
+- [eslint] reduce warnings
+
+## **6.3.5**
+- [Fix] fix regressions from robustness refactor
+- [meta] add `npmignore` to autogenerate an npmignore file
+- [actions] update reusable workflows
+
+## **6.3.4**
+- [Robustness] avoid `.push`, use `void`
+- [readme] clarify `parseArrays` and `arrayLimit` documentation (#543)
+- [readme] replace travis CI badge with shields.io check-runs badge
+- [actions] fix rebase workflow permissions
+
+## **6.3.3**
+- [Fix] `parse`: ignore `__proto__` keys (#428)
+- [Fix] fix for an impossible situation: when the formatter is called with a non-string value
+- [Fix] `utils.merge`: avoid a crash with a null target and an array source
+- [Fix] `utils.merge`: avoid a crash with a null target and a truthy non-array source
+- [Fix] `stringify`: fix a crash with `strictNullHandling` and a custom `filter`/`serializeDate` (#279)
+- [Fix] `utils`: `merge`: fix crash when `source` is a truthy primitive & no options are provided
+- [Fix] when `parseArrays` is false, properly handle keys ending in `[]`
+- [Robustness] `stringify`: avoid relying on a global `undefined` (#427)
+- [Refactor] use cached `Array.isArray`
+- [Refactor] `stringify`: Avoid arr = arr.concat(...), push to the existing instance (#269)
+- [Docs] Clarify the need for "arrayLimit" option
+- [meta] fix README.md (#399)
+- [meta] Clean up license text so it’s properly detected as BSD-3-Clause
+- [meta] add FUNDING.yml
+- [actions] backport actions from main
+- [Tests] use `safer-buffer` instead of `Buffer` constructor
+- [Tests] remove nonexistent tape option
+- [Dev Deps] backport from main
+
+## **6.3.2**
+- [Fix] follow `allowPrototypes` option during merge (#201, #200)
+- [Dev Deps] update `eslint`
+- [Fix] chmod a-x
+- [Fix] support keys starting with brackets (#202, #200)
+- [Tests] up to `node` `v7.7`, `v6.10`,` v4.8`; disable osx builds since they block linux builds
+
+## **6.3.1**
+- [Fix] ensure that `allowPrototypes: false` does not ever shadow Object.prototype properties (thanks, @snyk!)
+- [Dev Deps] update `eslint`, `@ljharb/eslint-config`, `browserify`, `iconv-lite`, `qs-iconv`, `tape`
+- [Tests] on all node minors; improve test matrix
+- [Docs] document stringify option `allowDots` (#195)
+- [Docs] add empty object and array values example (#195)
+- [Docs] Fix minor inconsistency/typo (#192)
+- [Docs] document stringify option `sort` (#191)
+- [Refactor] `stringify`: throw faster with an invalid encoder
+- [Refactor] remove unnecessary escapes (#184)
+- Remove contributing.md, since `qs` is no longer part of `hapi` (#183)
+
+## **6.3.0**
+- [New] Add support for RFC 1738 (#174, #173)
+- [New] `stringify`: Add `serializeDate` option to customize Date serialization (#159)
+- [Fix] ensure `utils.merge` handles merging two arrays
+- [Refactor] only constructors should be capitalized
+- [Refactor] capitalized var names are for constructors only
+- [Refactor] avoid using a sparse array
+- [Robustness] `formats`: cache `String#replace`
+- [Dev Deps] update `browserify`, `eslint`, `@ljharb/eslint-config`; add `safe-publish-latest`
+- [Tests] up to `node` `v6.8`, `v4.6`; improve test matrix
+- [Tests] flesh out arrayLimit/arrayFormat tests (#107)
+- [Tests] skip Object.create tests when null objects are not available
+- [Tests] Turn on eslint for test files (#175)
+
+## **6.2.6**
+- [Fix] fix regression from robustness refactor
+- [meta] add `npmignore` to autogenerate an npmignore file
+- [actions] update reusable workflows
+
+## **6.2.5**
+- [Robustness] avoid `.push`, use `void`
+- [readme] clarify `parseArrays` and `arrayLimit` documentation (#543)
+- [readme] replace travis CI badge with shields.io check-runs badge
+- [actions] fix rebase workflow permissions
+
+## **6.2.4**
+- [Fix] `parse`: ignore `__proto__` keys (#428)
+- [Fix] `utils.merge`: avoid a crash with a null target and an array source
+- [Fix] `utils.merge`: avoid a crash with a null target and a truthy non-array source
+- [Fix] `utils`: `merge`: fix crash when `source` is a truthy primitive & no options are provided
+- [Fix] when `parseArrays` is false, properly handle keys ending in `[]`
+- [Robustness] `stringify`: avoid relying on a global `undefined` (#427)
+- [Refactor] use cached `Array.isArray`
+- [Docs] Clarify the need for "arrayLimit" option
+- [meta] fix README.md (#399)
+- [meta] Clean up license text so it’s properly detected as BSD-3-Clause
+- [meta] add FUNDING.yml
+- [actions] backport actions from main
+- [Tests] use `safer-buffer` instead of `Buffer` constructor
+- [Tests] remove nonexistent tape option
+- [Dev Deps] backport from main
+
+## **6.2.3**
+- [Fix] follow `allowPrototypes` option during merge (#201, #200)
+- [Fix] chmod a-x
+- [Fix] support keys starting with brackets (#202, #200)
+- [Tests] up to `node` `v7.7`, `v6.10`,` v4.8`; disable osx builds since they block linux builds
+
+## **6.2.2**
+- [Fix] ensure that `allowPrototypes: false` does not ever shadow Object.prototype properties
+
+## **6.2.1**
+- [Fix] ensure `key[]=x&key[]&key[]=y` results in 3, not 2, values
+- [Refactor] Be explicit and use `Object.prototype.hasOwnProperty.call`
+- [Tests] remove `parallelshell` since it does not reliably report failures
+- [Tests] up to `node` `v6.3`, `v5.12`
+- [Dev Deps] update `tape`, `eslint`, `@ljharb/eslint-config`, `qs-iconv`
+
+## [**6.2.0**](https://github.com/ljharb/qs/issues?milestone=36&state=closed)
+- [New] pass Buffers to the encoder/decoder directly (#161)
+- [New] add "encoder" and "decoder" options, for custom param encoding/decoding (#160)
+- [Fix] fix compacting of nested sparse arrays (#150)
+
+## **6.1.4**
+- [Fix] fix regression from robustness refactor
+- [meta] add `npmignore` to autogenerate an npmignore file
+- [actions] update reusable workflows
+
+## **6.1.3**
+- [Robustness] avoid `.push`, use `void`
+- [readme] clarify `parseArrays` and `arrayLimit` documentation (#543)
+- [readme] replace travis CI badge with shields.io check-runs badge
+
+## **6.1.2**
+- [Fix] follow `allowPrototypes` option during merge (#201, #200)
+- [Fix] chmod a-x
+- [Fix] support keys starting with brackets (#202, #200)
+- [Tests] up to `node` `v7.7`, `v6.10`,` v4.8`; disable osx builds since they block linux builds
+
+## **6.1.1**
+- [Fix] ensure that `allowPrototypes: false` does not ever shadow Object.prototype properties
+
+## [**6.1.0**](https://github.com/ljharb/qs/issues?milestone=35&state=closed)
+- [New] allowDots option for `stringify` (#151)
+- [Fix] "sort" option should work at a depth of 3 or more (#151)
+- [Fix] Restore `dist` directory; will be removed in v7 (#148)
+
+## **6.0.6**
+- [Fix] fix regression from robustness refactor
+- [meta] add `npmignore` to autogenerate an npmignore file
+- [actions] update reusable workflows
+
+## **6.0.5**
+- [Robustness] avoid `.push`, use `void`
+- [readme] clarify `parseArrays` and `arrayLimit` documentation (#543)
+- [readme] replace travis CI badge with shields.io check-runs badge
+
+## **6.0.4**
+- [Fix] follow `allowPrototypes` option during merge (#201, #200)
+- [Fix] chmod a-x
+- [Fix] support keys starting with brackets (#202, #200)
+- [Tests] up to `node` `v7.7`, `v6.10`,` v4.8`; disable osx builds since they block linux builds
+
+## **6.0.3**
+- [Fix] ensure that `allowPrototypes: false` does not ever shadow Object.prototype properties
+- [Fix] Restore `dist` directory; will be removed in v7 (#148)
+
+## [**6.0.2**](https://github.com/ljharb/qs/issues?milestone=33&state=closed)
+- Revert ES6 requirement and restore support for node down to v0.8.
+
+## [**6.0.1**](https://github.com/ljharb/qs/issues?milestone=32&state=closed)
+- [**#127**](https://github.com/ljharb/qs/pull/127) Fix engines definition in package.json
+
+## [**6.0.0**](https://github.com/ljharb/qs/issues?milestone=31&state=closed)
+- [**#124**](https://github.com/ljharb/qs/issues/124) Use ES6 and drop support for node < v4
+
+## **5.2.1**
+- [Fix] ensure `key[]=x&key[]&key[]=y` results in 3, not 2, values
+
+## [**5.2.0**](https://github.com/ljharb/qs/issues?milestone=30&state=closed)
+- [**#64**](https://github.com/ljharb/qs/issues/64) Add option to sort object keys in the query string
+
+## [**5.1.0**](https://github.com/ljharb/qs/issues?milestone=29&state=closed)
+- [**#117**](https://github.com/ljharb/qs/issues/117) make URI encoding stringified results optional
+- [**#106**](https://github.com/ljharb/qs/issues/106) Add flag `skipNulls` to optionally skip null values in stringify
+
+## [**5.0.0**](https://github.com/ljharb/qs/issues?milestone=28&state=closed)
+- [**#114**](https://github.com/ljharb/qs/issues/114) default allowDots to false
+- [**#100**](https://github.com/ljharb/qs/issues/100) include dist to npm
+
+## [**4.0.0**](https://github.com/ljharb/qs/issues?milestone=26&state=closed)
+- [**#98**](https://github.com/ljharb/qs/issues/98) make returning plain objects and allowing prototype overwriting properties optional
+
+## [**3.1.0**](https://github.com/ljharb/qs/issues?milestone=24&state=closed)
+- [**#89**](https://github.com/ljharb/qs/issues/89) Add option to disable "Transform dot notation to bracket notation"
+
+## [**3.0.0**](https://github.com/ljharb/qs/issues?milestone=23&state=closed)
+- [**#80**](https://github.com/ljharb/qs/issues/80) qs.parse silently drops properties
+- [**#77**](https://github.com/ljharb/qs/issues/77) Perf boost
+- [**#60**](https://github.com/ljharb/qs/issues/60) Add explicit option to disable array parsing
+- [**#74**](https://github.com/ljharb/qs/issues/74) Bad parse when turning array into object
+- [**#81**](https://github.com/ljharb/qs/issues/81) Add a `filter` option
+- [**#68**](https://github.com/ljharb/qs/issues/68) Fixed issue with recursion and passing strings into objects.
+- [**#66**](https://github.com/ljharb/qs/issues/66) Add mixed array and object dot notation support Closes: #47
+- [**#76**](https://github.com/ljharb/qs/issues/76) RFC 3986
+- [**#85**](https://github.com/ljharb/qs/issues/85) No equal sign
+- [**#84**](https://github.com/ljharb/qs/issues/84) update license attribute
+
+## [**2.4.1**](https://github.com/ljharb/qs/issues?milestone=20&state=closed)
+- [**#73**](https://github.com/ljharb/qs/issues/73) Property 'hasOwnProperty' of object #<Object> is not a function
+
+## [**2.4.0**](https://github.com/ljharb/qs/issues?milestone=19&state=closed)
+- [**#70**](https://github.com/ljharb/qs/issues/70) Add arrayFormat option
+
+## [**2.3.3**](https://github.com/ljharb/qs/issues?milestone=18&state=closed)
+- [**#59**](https://github.com/ljharb/qs/issues/59) make sure array indexes are >= 0, closes #57
+- [**#58**](https://github.com/ljharb/qs/issues/58) make qs usable for browser loader
+
+## [**2.3.2**](https://github.com/ljharb/qs/issues?milestone=17&state=closed)
+- [**#55**](https://github.com/ljharb/qs/issues/55) allow merging a string into an object
+
+## [**2.3.1**](https://github.com/ljharb/qs/issues?milestone=16&state=closed)
+- [**#52**](https://github.com/ljharb/qs/issues/52) Return "undefined" and "false" instead of throwing "TypeError".
+
+## [**2.3.0**](https://github.com/ljharb/qs/issues?milestone=15&state=closed)
+- [**#50**](https://github.com/ljharb/qs/issues/50) add option to omit array indices, closes #46
+
+## [**2.2.5**](https://github.com/ljharb/qs/issues?milestone=14&state=closed)
+- [**#39**](https://github.com/ljharb/qs/issues/39) Is there an alternative to Buffer.isBuffer?
+- [**#49**](https://github.com/ljharb/qs/issues/49) refactor utils.merge, fixes #45
+- [**#41**](https://github.com/ljharb/qs/issues/41) avoid browserifying Buffer, for #39
+
+## [**2.2.4**](https://github.com/ljharb/qs/issues?milestone=13&state=closed)
+- [**#38**](https://github.com/ljharb/qs/issues/38) how to handle object keys beginning with a number
+
+## [**2.2.3**](https://github.com/ljharb/qs/issues?milestone=12&state=closed)
+- [**#37**](https://github.com/ljharb/qs/issues/37) parser discards first empty value in array
+- [**#36**](https://github.com/ljharb/qs/issues/36) Update to lab 4.x
+
+## [**2.2.2**](https://github.com/ljharb/qs/issues?milestone=11&state=closed)
+- [**#33**](https://github.com/ljharb/qs/issues/33) Error when plain object in a value
+- [**#34**](https://github.com/ljharb/qs/issues/34) use Object.prototype.hasOwnProperty.call instead of obj.hasOwnProperty
+- [**#24**](https://github.com/ljharb/qs/issues/24) Changelog? Semver?
+
+## [**2.2.1**](https://github.com/ljharb/qs/issues?milestone=10&state=closed)
+- [**#32**](https://github.com/ljharb/qs/issues/32) account for circular references properly, closes #31
+- [**#31**](https://github.com/ljharb/qs/issues/31) qs.parse stackoverflow on circular objects
+
+## [**2.2.0**](https://github.com/ljharb/qs/issues?milestone=9&state=closed)
+- [**#26**](https://github.com/ljharb/qs/issues/26) Don't use Buffer global if it's not present
+- [**#30**](https://github.com/ljharb/qs/issues/30) Bug when merging non-object values into arrays
+- [**#29**](https://github.com/ljharb/qs/issues/29) Don't call Utils.clone at the top of Utils.merge
+- [**#23**](https://github.com/ljharb/qs/issues/23) Ability to not limit parameters?
+
+## [**2.1.0**](https://github.com/ljharb/qs/issues?milestone=8&state=closed)
+- [**#22**](https://github.com/ljharb/qs/issues/22) Enable using a RegExp as delimiter
+
+## [**2.0.0**](https://github.com/ljharb/qs/issues?milestone=7&state=closed)
+- [**#18**](https://github.com/ljharb/qs/issues/18) Why is there arrayLimit?
+- [**#20**](https://github.com/ljharb/qs/issues/20) Configurable parametersLimit
+- [**#21**](https://github.com/ljharb/qs/issues/21) make all limits optional, for #18, for #20
+
+## [**1.2.2**](https://github.com/ljharb/qs/issues?milestone=6&state=closed)
+- [**#19**](https://github.com/ljharb/qs/issues/19) Don't overwrite null values
+
+## [**1.2.1**](https://github.com/ljharb/qs/issues?milestone=5&state=closed)
+- [**#16**](https://github.com/ljharb/qs/issues/16) ignore non-string delimiters
+- [**#15**](https://github.com/ljharb/qs/issues/15) Close code block
+
+## [**1.2.0**](https://github.com/ljharb/qs/issues?milestone=4&state=closed)
+- [**#12**](https://github.com/ljharb/qs/issues/12) Add optional delim argument
+- [**#13**](https://github.com/ljharb/qs/issues/13) fix #11: flattened keys in array are now correctly parsed
+
+## [**1.1.0**](https://github.com/ljharb/qs/issues?milestone=3&state=closed)
+- [**#7**](https://github.com/ljharb/qs/issues/7) Empty values of a POST array disappear after being submitted
+- [**#9**](https://github.com/ljharb/qs/issues/9) Should not omit equals signs (=) when value is null
+- [**#6**](https://github.com/ljharb/qs/issues/6) Minor grammar fix in README
+
+## [**1.0.2**](https://github.com/ljharb/qs/issues?milestone=2&state=closed)
+- [**#5**](https://github.com/ljharb/qs/issues/5) array holes incorrectly copied into object on large index
diff --git a/node_modules/qs/LICENSE.md b/node_modules/qs/LICENSE.md
new file mode 100644
index 0000000..fecf6b6
--- /dev/null
+++ b/node_modules/qs/LICENSE.md
@@ -0,0 +1,29 @@
+BSD 3-Clause License
+
+Copyright (c) 2014, Nathan LaFreniere and other [contributors](https://github.com/ljharb/qs/graphs/contributors)
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+3. Neither the name of the copyright holder nor the names of its
+ contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/node_modules/qs/README.md b/node_modules/qs/README.md
new file mode 100644
index 0000000..fd64e3c
--- /dev/null
+++ b/node_modules/qs/README.md
@@ -0,0 +1,758 @@
+<p align="center">
+ <img alt="qs" src="./logos/banner_default.png" width="800" />
+</p>
+
+# qs <sup>[![Version Badge][npm-version-svg]][package-url]</sup>
+
+[![github actions][actions-image]][actions-url]
+[![coverage][codecov-image]][codecov-url]
+[![License][license-image]][license-url]
+[![Downloads][downloads-image]][downloads-url]
+[![CII Best Practices](https://bestpractices.coreinfrastructure.org/projects/9058/badge)](https://bestpractices.coreinfrastructure.org/projects/9058)
+
+[![npm badge][npm-badge-png]][package-url]
+
+A querystring parsing and stringifying library with some added security.
+
+Lead Maintainer: [Jordan Harband](https://github.com/ljharb)
+
+The **qs** module was originally created and maintained by [TJ Holowaychuk](https://github.com/visionmedia/node-querystring).
+
+## Usage
+
+```javascript
+var qs = require('qs');
+var assert = require('assert');
+
+var obj = qs.parse('a=c');
+assert.deepEqual(obj, { a: 'c' });
+
+var str = qs.stringify(obj);
+assert.equal(str, 'a=c');
+```
+
+### Parsing Objects
+
+[](#preventEval)
+```javascript
+qs.parse(string, [options]);
+```
+
+**qs** allows you to create nested objects within your query strings, by surrounding the name of sub-keys with square brackets `[]`.
+For example, the string `'foo[bar]=baz'` converts to:
+
+```javascript
+assert.deepEqual(qs.parse('foo[bar]=baz'), {
+ foo: {
+ bar: 'baz'
+ }
+});
+```
+
+When using the `plainObjects` option the parsed value is returned as a null object, created via `{ __proto__: null }` and as such you should be aware that prototype methods will not exist on it and a user may set those names to whatever value they like:
+
+```javascript
+var nullObject = qs.parse('a[hasOwnProperty]=b', { plainObjects: true });
+assert.deepEqual(nullObject, { a: { hasOwnProperty: 'b' } });
+```
+
+By default parameters that would overwrite properties on the object prototype are ignored, if you wish to keep the data from those fields either use `plainObjects` as mentioned above, or set `allowPrototypes` to `true` which will allow user input to overwrite those properties.
+*WARNING* It is generally a bad idea to enable this option as it can cause problems when attempting to use the properties that have been overwritten.
+Always be careful with this option.
+
+```javascript
+var protoObject = qs.parse('a[hasOwnProperty]=b', { allowPrototypes: true });
+assert.deepEqual(protoObject, { a: { hasOwnProperty: 'b' } });
+```
+
+URI encoded strings work too:
+
+```javascript
+assert.deepEqual(qs.parse('a%5Bb%5D=c'), {
+ a: { b: 'c' }
+});
+```
+
+You can also nest your objects, like `'foo[bar][baz]=foobarbaz'`:
+
+```javascript
+assert.deepEqual(qs.parse('foo[bar][baz]=foobarbaz'), {
+ foo: {
+ bar: {
+ baz: 'foobarbaz'
+ }
+ }
+});
+```
+
+By default, when nesting objects **qs** will only parse up to 5 children deep.
+This means if you attempt to parse a string like `'a[b][c][d][e][f][g][h][i]=j'` your resulting object will be:
+
+```javascript
+var expected = {
+ a: {
+ b: {
+ c: {
+ d: {
+ e: {
+ f: {
+ '[g][h][i]': 'j'
+ }
+ }
+ }
+ }
+ }
+ }
+};
+var string = 'a[b][c][d][e][f][g][h][i]=j';
+assert.deepEqual(qs.parse(string), expected);
+```
+
+This depth can be overridden by passing a `depth` option to `qs.parse(string, [options])`:
+
+```javascript
+var deep = qs.parse('a[b][c][d][e][f][g][h][i]=j', { depth: 1 });
+assert.deepEqual(deep, { a: { b: { '[c][d][e][f][g][h][i]': 'j' } } });
+```
+
+You can configure **qs** to throw an error when parsing nested input beyond this depth using the `strictDepth` option (defaulted to false):
+
+```javascript
+try {
+ qs.parse('a[b][c][d][e][f][g][h][i]=j', { depth: 1, strictDepth: true });
+} catch (err) {
+ assert(err instanceof RangeError);
+ assert.strictEqual(err.message, 'Input depth exceeded depth option of 1 and strictDepth is true');
+}
+```
+
+The depth limit helps mitigate abuse when **qs** is used to parse user input, and it is recommended to keep it a reasonably small number. The strictDepth option adds a layer of protection by throwing an error when the limit is exceeded, allowing you to catch and handle such cases.
+
+For similar reasons, by default **qs** will only parse up to 1000 parameters. This can be overridden by passing a `parameterLimit` option:
+
+```javascript
+var limited = qs.parse('a=b&c=d', { parameterLimit: 1 });
+assert.deepEqual(limited, { a: 'b' });
+```
+
+If you want an error to be thrown whenever the a limit is exceeded (eg, `parameterLimit`, `arrayLimit`), set the `throwOnLimitExceeded` option to `true`. This option will generate a descriptive error if the query string exceeds a configured limit.
+```javascript
+try {
+ qs.parse('a=1&b=2&c=3&d=4', { parameterLimit: 3, throwOnLimitExceeded: true });
+} catch (err) {
+ assert(err instanceof Error);
+ assert.strictEqual(err.message, 'Parameter limit exceeded. Only 3 parameters allowed.');
+}
+```
+
+When `throwOnLimitExceeded` is set to `false` (default), **qs** will parse up to the specified `parameterLimit` and ignore the rest without throwing an error.
+
+To bypass the leading question mark, use `ignoreQueryPrefix`:
+
+```javascript
+var prefixed = qs.parse('?a=b&c=d', { ignoreQueryPrefix: true });
+assert.deepEqual(prefixed, { a: 'b', c: 'd' });
+```
+
+An optional delimiter can also be passed:
+
+```javascript
+var delimited = qs.parse('a=b;c=d', { delimiter: ';' });
+assert.deepEqual(delimited, { a: 'b', c: 'd' });
+```
+
+Delimiters can be a regular expression too:
+
+```javascript
+var regexed = qs.parse('a=b;c=d,e=f', { delimiter: /[;,]/ });
+assert.deepEqual(regexed, { a: 'b', c: 'd', e: 'f' });
+```
+
+Option `allowDots` can be used to enable dot notation:
+
+```javascript
+var withDots = qs.parse('a.b=c', { allowDots: true });
+assert.deepEqual(withDots, { a: { b: 'c' } });
+```
+
+Option `decodeDotInKeys` can be used to decode dots in keys
+Note: it implies `allowDots`, so `parse` will error if you set `decodeDotInKeys` to `true`, and `allowDots` to `false`.
+
+```javascript
+var withDots = qs.parse('name%252Eobj.first=John&name%252Eobj.last=Doe', { decodeDotInKeys: true });
+assert.deepEqual(withDots, { 'name.obj': { first: 'John', last: 'Doe' }});
+```
+
+Option `allowEmptyArrays` can be used to allow empty array values in an object
+```javascript
+var withEmptyArrays = qs.parse('foo[]&bar=baz', { allowEmptyArrays: true });
+assert.deepEqual(withEmptyArrays, { foo: [], bar: 'baz' });
+```
+
+Option `duplicates` can be used to change the behavior when duplicate keys are encountered
+```javascript
+assert.deepEqual(qs.parse('foo=bar&foo=baz'), { foo: ['bar', 'baz'] });
+assert.deepEqual(qs.parse('foo=bar&foo=baz', { duplicates: 'combine' }), { foo: ['bar', 'baz'] });
+assert.deepEqual(qs.parse('foo=bar&foo=baz', { duplicates: 'first' }), { foo: 'bar' });
+assert.deepEqual(qs.parse('foo=bar&foo=baz', { duplicates: 'last' }), { foo: 'baz' });
+```
+
+Note that keys with bracket notation (`[]`) always combine into arrays, regardless of the `duplicates` setting:
+```javascript
+assert.deepEqual(qs.parse('a=1&a=2&b[]=1&b[]=2', { duplicates: 'last' }), { a: '2', b: ['1', '2'] });
+```
+
+If you have to deal with legacy browsers or services, there's also support for decoding percent-encoded octets as iso-8859-1:
+
+```javascript
+var oldCharset = qs.parse('a=%A7', { charset: 'iso-8859-1' });
+assert.deepEqual(oldCharset, { a: '§' });
+```
+
+Some services add an initial `utf8=✓` value to forms so that old Internet Explorer versions are more likely to submit the form as utf-8.
+Additionally, the server can check the value against wrong encodings of the checkmark character and detect that a query string or `application/x-www-form-urlencoded` body was *not* sent as utf-8, eg. if the form had an `accept-charset` parameter or the containing page had a different character set.
+
+**qs** supports this mechanism via the `charsetSentinel` option.
+If specified, the `utf8` parameter will be omitted from the returned object.
+It will be used to switch to `iso-8859-1`/`utf-8` mode depending on how the checkmark is encoded.
+
+**Important**: When you specify both the `charset` option and the `charsetSentinel` option, the `charset` will be overridden when the request contains a `utf8` parameter from which the actual charset can be deduced.
+In that sense the `charset` will behave as the default charset rather than the authoritative charset.
+
+```javascript
+var detectedAsUtf8 = qs.parse('utf8=%E2%9C%93&a=%C3%B8', {
+ charset: 'iso-8859-1',
+ charsetSentinel: true
+});
+assert.deepEqual(detectedAsUtf8, { a: 'ø' });
+
+// Browsers encode the checkmark as &#10003; when submitting as iso-8859-1:
+var detectedAsIso8859_1 = qs.parse('utf8=%26%2310003%3B&a=%F8', {
+ charset: 'utf-8',
+ charsetSentinel: true
+});
+assert.deepEqual(detectedAsIso8859_1, { a: 'ø' });
+```
+
+If you want to decode the `&#...;` syntax to the actual character, you can specify the `interpretNumericEntities` option as well:
+
+```javascript
+var detectedAsIso8859_1 = qs.parse('a=%26%239786%3B', {
+ charset: 'iso-8859-1',
+ interpretNumericEntities: true
+});
+assert.deepEqual(detectedAsIso8859_1, { a: '☺' });
+```
+
+It also works when the charset has been detected in `charsetSentinel` mode.
+
+### Parsing Arrays
+
+**qs** can also parse arrays using a similar `[]` notation:
+
+```javascript
+var withArray = qs.parse('a[]=b&a[]=c');
+assert.deepEqual(withArray, { a: ['b', 'c'] });
+```
+
+You may specify an index as well:
+
+```javascript
+var withIndexes = qs.parse('a[1]=c&a[0]=b');
+assert.deepEqual(withIndexes, { a: ['b', 'c'] });
+```
+
+Note that the only difference between an index in an array and a key in an object is that the value between the brackets must be a number to create an array.
+When creating arrays with specific indices, **qs** will compact a sparse array to only the existing values preserving their order:
+
+```javascript
+var noSparse = qs.parse('a[1]=b&a[15]=c');
+assert.deepEqual(noSparse, { a: ['b', 'c'] });
+```
+
+You may also use `allowSparse` option to parse sparse arrays:
+
+```javascript
+var sparseArray = qs.parse('a[1]=2&a[3]=5', { allowSparse: true });
+assert.deepEqual(sparseArray, { a: [, '2', , '5'] });
+```
+
+Note that an empty string is also a value, and will be preserved:
+
+```javascript
+var withEmptyString = qs.parse('a[]=&a[]=b');
+assert.deepEqual(withEmptyString, { a: ['', 'b'] });
+
+var withIndexedEmptyString = qs.parse('a[0]=b&a[1]=&a[2]=c');
+assert.deepEqual(withIndexedEmptyString, { a: ['b', '', 'c'] });
+```
+
+**qs** will also limit arrays to a maximum of `20` elements.
+Any array members with an index of `20` or greater will 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.
+
+```javascript
+var withMaxIndex = qs.parse('a[100]=b');
+assert.deepEqual(withMaxIndex, { a: { '100': 'b' } });
+```
+
+This limit can be overridden by passing an `arrayLimit` option:
+
+```javascript
+var withArrayLimit = qs.parse('a[1]=b', { arrayLimit: 0 });
+assert.deepEqual(withArrayLimit, { a: { '1': 'b' } });
+```
+
+If you want to throw an error whenever the array limit is exceeded, set the `throwOnLimitExceeded` option to `true`. This option will generate a descriptive error if the query string exceeds a configured limit.
+```javascript
+try {
+ qs.parse('a[1]=b', { arrayLimit: 0, throwOnLimitExceeded: true });
+} catch (err) {
+ assert(err instanceof Error);
+ assert.strictEqual(err.message, 'Array limit exceeded. Only 0 elements allowed in an array.');
+}
+```
+
+When `throwOnLimitExceeded` is set to `false` (default), **qs** will parse up to the specified `arrayLimit` and if the limit is exceeded, the array will instead be converted to an object with the index as the key
+
+To prevent array syntax (`a[]`, `a[0]`) from being parsed as arrays, set `parseArrays` to `false`.
+Note that duplicate keys (e.g. `a=b&a=c`) may still produce arrays when `duplicates` is `'combine'` (the default).
+
+```javascript
+var noParsingArrays = qs.parse('a[]=b', { parseArrays: false });
+assert.deepEqual(noParsingArrays, { a: { '0': 'b' } });
+```
+
+If you mix notations, **qs** will merge the two items into an object:
+
+```javascript
+var mixedNotation = qs.parse('a[0]=b&a[b]=c');
+assert.deepEqual(mixedNotation, { a: { '0': 'b', b: 'c' } });
+```
+
+When a key appears as both a plain value and an object, **qs** will by default wrap the conflicting values in an array (`strictMerge` defaults to `true`):
+
+```javascript
+assert.deepEqual(qs.parse('a[b]=c&a=d'), { a: [{ b: 'c' }, 'd'] });
+assert.deepEqual(qs.parse('a=d&a[b]=c'), { a: ['d', { b: 'c' }] });
+```
+
+To restore the legacy behavior (where the primitive is used as a key with value `true`), set `strictMerge` to `false`:
+
+```javascript
+assert.deepEqual(qs.parse('a[b]=c&a=d', { strictMerge: false }), { a: { b: 'c', d: true } });
+```
+
+You can also create arrays of objects:
+
+```javascript
+var arraysOfObjects = qs.parse('a[][b]=c');
+assert.deepEqual(arraysOfObjects, { a: [{ b: 'c' }] });
+```
+
+Some people use comma to join array, **qs** can parse it:
+```javascript
+var arraysOfObjects = qs.parse('a=b,c', { comma: true })
+assert.deepEqual(arraysOfObjects, { a: ['b', 'c'] })
+```
+(_this cannot convert nested objects, such as `a={b:1},{c:d}`_)
+
+### Parsing primitive/scalar values (numbers, booleans, null, etc)
+
+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).
+
+```javascript
+var primitiveValues = qs.parse('a=15&b=true&c=null');
+assert.deepEqual(primitiveValues, { a: '15', b: 'true', c: 'null' });
+```
+
+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.
+
+### Stringifying
+
+[](#preventEval)
+```javascript
+qs.stringify(object, [options]);
+```
+
+When stringifying, **qs** by default URI encodes output. Objects are stringified as you would expect:
+
+```javascript
+assert.equal(qs.stringify({ a: 'b' }), 'a=b');
+assert.equal(qs.stringify({ a: { b: 'c' } }), 'a%5Bb%5D=c');
+```
+
+This encoding can be disabled by setting the `encode` option to `false`:
+
+```javascript
+var unencoded = qs.stringify({ a: { b: 'c' } }, { encode: false });
+assert.equal(unencoded, 'a[b]=c');
+```
+
+Encoding can be disabled for keys by setting the `encodeValuesOnly` option to `true`:
+```javascript
+var encodedValues = qs.stringify(
+ { a: 'b', c: ['d', 'e=f'], f: [['g'], ['h']] },
+ { encodeValuesOnly: true }
+);
+assert.equal(encodedValues,'a=b&c[0]=d&c[1]=e%3Df&f[0][0]=g&f[1][0]=h');
+```
+
+This encoding can also be replaced by a custom encoding method set as `encoder` option:
+
+```javascript
+var encoded = qs.stringify({ a: { b: 'c' } }, { encoder: function (str) {
+ // Passed in values `a`, `b`, `c`
+ return // Return encoded string
+}})
+```
+
+_(Note: the `encoder` option does not apply if `encode` is `false`)_
+
+Analogue to the `encoder` there is a `decoder` option for `parse` to override decoding of properties and values:
+
+```javascript
+var decoded = qs.parse('x=z', { decoder: function (str) {
+ // Passed in values `x`, `z`
+ return // Return decoded string
+}})
+```
+
+You can encode keys and values using different logic by using the type argument provided to the encoder:
+
+```javascript
+var encoded = qs.stringify({ a: { b: 'c' } }, { encoder: function (str, defaultEncoder, charset, type) {
+ if (type === 'key') {
+ return // Encoded key
+ } else if (type === 'value') {
+ return // Encoded value
+ }
+}})
+```
+
+The type argument is also provided to the decoder:
+
+```javascript
+var decoded = qs.parse('x=z', { decoder: function (str, defaultDecoder, charset, type) {
+ if (type === 'key') {
+ return // Decoded key
+ } else if (type === 'value') {
+ return // Decoded value
+ }
+}})
+```
+
+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.
+
+When arrays are stringified, they follow the `arrayFormat` option, which defaults to `indices`:
+
+```javascript
+qs.stringify({ a: ['b', 'c', 'd'] });
+// 'a[0]=b&a[1]=c&a[2]=d'
+```
+
+You may override this by setting the `indices` option to `false`, or to be more explicit, the `arrayFormat` option to `repeat`:
+
+```javascript
+qs.stringify({ a: ['b', 'c', 'd'] }, { indices: false });
+// 'a=b&a=c&a=d'
+```
+
+You may use the `arrayFormat` option to specify the format of the output array:
+
+```javascript
+qs.stringify({ a: ['b', 'c'] }, { arrayFormat: 'indices' })
+// 'a[0]=b&a[1]=c'
+qs.stringify({ a: ['b', 'c'] }, { arrayFormat: 'brackets' })
+// 'a[]=b&a[]=c'
+qs.stringify({ a: ['b', 'c'] }, { arrayFormat: 'repeat' })
+// 'a=b&a=c'
+qs.stringify({ a: ['b', 'c'] }, { arrayFormat: 'comma' })
+// 'a=b,c'
+```
+
+Note: when using `arrayFormat` set to `'comma'`, you can also pass the `commaRoundTrip` option set to `true` or `false`, to append `[]` on single-item arrays, so that they can round trip through a parse.
+
+When objects are stringified, by default they use bracket notation:
+
+```javascript
+qs.stringify({ a: { b: { c: 'd', e: 'f' } } });
+// 'a[b][c]=d&a[b][e]=f'
+```
+
+You may override this to use dot notation by setting the `allowDots` option to `true`:
+
+```javascript
+qs.stringify({ a: { b: { c: 'd', e: 'f' } } }, { allowDots: true });
+// 'a.b.c=d&a.b.e=f'
+```
+
+You may encode the dot notation in the keys of object with option `encodeDotInKeys` by setting it to `true`:
+Note: it implies `allowDots`, so `stringify` will error if you set `decodeDotInKeys` to `true`, and `allowDots` to `false`.
+Caveat: when `encodeValuesOnly` is `true` as well as `encodeDotInKeys`, only dots in keys and nothing else will be encoded.
+```javascript
+qs.stringify({ "name.obj": { "first": "John", "last": "Doe" } }, { allowDots: true, encodeDotInKeys: true })
+// 'name%252Eobj.first=John&name%252Eobj.last=Doe'
+```
+
+You may allow empty array values by setting the `allowEmptyArrays` option to `true`:
+```javascript
+qs.stringify({ foo: [], bar: 'baz' }, { allowEmptyArrays: true });
+// 'foo[]&bar=baz'
+```
+
+Empty strings and null values will omit the value, but the equals sign (=) remains in place:
+
+```javascript
+assert.equal(qs.stringify({ a: '' }), 'a=');
+```
+
+Key with no values (such as an empty object or array) will return nothing:
+
+```javascript
+assert.equal(qs.stringify({ a: [] }), '');
+assert.equal(qs.stringify({ a: {} }), '');
+assert.equal(qs.stringify({ a: [{}] }), '');
+assert.equal(qs.stringify({ a: { b: []} }), '');
+assert.equal(qs.stringify({ a: { b: {}} }), '');
+```
+
+Properties that are set to `undefined` will be omitted entirely:
+
+```javascript
+assert.equal(qs.stringify({ a: null, b: undefined }), 'a=');
+```
+
+The query string may optionally be prepended with a question mark:
+
+```javascript
+assert.equal(qs.stringify({ a: 'b', c: 'd' }, { addQueryPrefix: true }), '?a=b&c=d');
+```
+
+Note that when the output is an empty string, the prefix will not be added:
+
+```javascript
+assert.equal(qs.stringify({}, { addQueryPrefix: true }), '');
+```
+
+The delimiter may be overridden with stringify as well:
+
+```javascript
+assert.equal(qs.stringify({ a: 'b', c: 'd' }, { delimiter: ';' }), 'a=b;c=d');
+```
+
+If you only want to override the serialization of `Date` objects, you can provide a `serializeDate` option:
+
+```javascript
+var date = new Date(7);
+assert.equal(qs.stringify({ a: date }), 'a=1970-01-01T00:00:00.007Z'.replace(/:/g, '%3A'));
+assert.equal(
+ qs.stringify({ a: date }, { serializeDate: function (d) { return d.getTime(); } }),
+ 'a=7'
+);
+```
+
+You may use the `sort` option to affect the order of parameter keys:
+
+```javascript
+function alphabeticalSort(a, b) {
+ return a.localeCompare(b);
+}
+assert.equal(qs.stringify({ a: 'c', z: 'y', b : 'f' }, { sort: alphabeticalSort }), 'a=c&b=f&z=y');
+```
+
+Finally, you can use the `filter` option to restrict which keys will be included in the stringified output.
+If you pass a function, it will be called for each key to obtain the replacement value.
+Otherwise, if you pass an array, it will be used to select properties and array indices for stringification:
+
+```javascript
+function filterFunc(prefix, value) {
+ if (prefix == 'b') {
+ // Return an `undefined` value to omit a property.
+ return;
+ }
+ if (prefix == 'e[f]') {
+ return value.getTime();
+ }
+ if (prefix == 'e[g][0]') {
+ return value * 2;
+ }
+ return value;
+}
+qs.stringify({ a: 'b', c: 'd', e: { f: new Date(123), g: [2] } }, { filter: filterFunc });
+// 'a=b&c=d&e[f]=123&e[g][0]=4'
+qs.stringify({ a: 'b', c: 'd', e: 'f' }, { filter: ['a', 'e'] });
+// 'a=b&e=f'
+qs.stringify({ a: ['b', 'c', 'd'], e: 'f' }, { filter: ['a', 0, 2] });
+// 'a[0]=b&a[2]=d'
+```
+
+You could also use `filter` to inject custom serialization for user defined types.
+Consider you're working with some api that expects query strings of the format for ranges:
+
+```
+https://domain.com/endpoint?range=30...70
+```
+
+For which you model as:
+
+```javascript
+class Range {
+ constructor(from, to) {
+ this.from = from;
+ this.to = to;
+ }
+}
+```
+
+You could _inject_ a custom serializer to handle values of this type:
+
+```javascript
+qs.stringify(
+ {
+ range: new Range(30, 70),
+ },
+ {
+ filter: (prefix, value) => {
+ if (value instanceof Range) {
+ return `${value.from}...${value.to}`;
+ }
+ // serialize the usual way
+ return value;
+ },
+ }
+);
+// range=30...70
+```
+
+### Handling of `null` values
+
+By default, `null` values are treated like empty strings:
+
+```javascript
+var withNull = qs.stringify({ a: null, b: '' });
+assert.equal(withNull, 'a=&b=');
+```
+
+Parsing does not distinguish between parameters with and without equal signs.
+Both are converted to empty strings.
+
+```javascript
+var equalsInsensitive = qs.parse('a&b=');
+assert.deepEqual(equalsInsensitive, { a: '', b: '' });
+```
+
+To distinguish between `null` values and empty strings use the `strictNullHandling` flag. In the result string the `null`
+values have no `=` sign:
+
+```javascript
+var strictNull = qs.stringify({ a: null, b: '' }, { strictNullHandling: true });
+assert.equal(strictNull, 'a&b=');
+```
+
+To parse values without `=` back to `null` use the `strictNullHandling` flag:
+
+```javascript
+var parsedStrictNull = qs.parse('a&b=', { strictNullHandling: true });
+assert.deepEqual(parsedStrictNull, { a: null, b: '' });
+```
+
+To completely skip rendering keys with `null` values, use the `skipNulls` flag:
+
+```javascript
+var nullsSkipped = qs.stringify({ a: 'b', c: null}, { skipNulls: true });
+assert.equal(nullsSkipped, 'a=b');
+```
+
+If you're communicating with legacy systems, you can switch to `iso-8859-1` using the `charset` option:
+
+```javascript
+var iso = qs.stringify({ æ: 'æ' }, { charset: 'iso-8859-1' });
+assert.equal(iso, '%E6=%E6');
+```
+
+Characters that don't exist in `iso-8859-1` will be converted to numeric entities, similar to what browsers do:
+
+```javascript
+var numeric = qs.stringify({ a: '☺' }, { charset: 'iso-8859-1' });
+assert.equal(numeric, 'a=%26%239786%3B');
+```
+
+You can use the `charsetSentinel` option to announce the character by including an `utf8=✓` parameter with the proper encoding if the checkmark, similar to what Ruby on Rails and others do when submitting forms.
+
+```javascript
+var sentinel = qs.stringify({ a: '☺' }, { charsetSentinel: true });
+assert.equal(sentinel, 'utf8=%E2%9C%93&a=%E2%98%BA');
+
+var isoSentinel = qs.stringify({ a: 'æ' }, { charsetSentinel: true, charset: 'iso-8859-1' });
+assert.equal(isoSentinel, 'utf8=%26%2310003%3B&a=%E6');
+```
+
+### Dealing with special character sets
+
+By default the encoding and decoding of characters is done in `utf-8`, and `iso-8859-1` support is also built in via the `charset` parameter.
+
+If you wish to encode querystrings to a different character set (i.e.
+[Shift JIS](https://en.wikipedia.org/wiki/Shift_JIS)) you can use the
+[`qs-iconv`](https://github.com/martinheidegger/qs-iconv) library:
+
+```javascript
+var encoder = require('qs-iconv/encoder')('shift_jis');
+var shiftJISEncoded = qs.stringify({ a: 'こんにちは!' }, { encoder: encoder });
+assert.equal(shiftJISEncoded, 'a=%82%B1%82%F1%82%C9%82%BF%82%CD%81I');
+```
+
+This also works for decoding of query strings:
+
+```javascript
+var decoder = require('qs-iconv/decoder')('shift_jis');
+var obj = qs.parse('a=%82%B1%82%F1%82%C9%82%BF%82%CD%81I', { decoder: decoder });
+assert.deepEqual(obj, { a: 'こんにちは!' });
+```
+
+### RFC 3986 and RFC 1738 space encoding
+
+RFC3986 used as default option and encodes ' ' to *%20* which is backward compatible.
+In the same time, output can be stringified as per RFC1738 with ' ' equal to '+'.
+
+```
+assert.equal(qs.stringify({ a: 'b c' }), 'a=b%20c');
+assert.equal(qs.stringify({ a: 'b c' }, { format : 'RFC3986' }), 'a=b%20c');
+assert.equal(qs.stringify({ a: 'b c' }, { format : 'RFC1738' }), 'a=b+c');
+```
+
+## Security
+
+Please email [@ljharb](https://github.com/ljharb) or see https://tidelift.com/security if you have a potential security vulnerability to report.
+
+## qs for enterprise
+
+Available as part of the Tidelift Subscription
+
+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)
+
+[package-url]: https://npmjs.org/package/qs
+[npm-version-svg]: https://versionbadg.es/ljharb/qs.svg
+[deps-svg]: https://david-dm.org/ljharb/qs.svg
+[deps-url]: https://david-dm.org/ljharb/qs
+[dev-deps-svg]: https://david-dm.org/ljharb/qs/dev-status.svg
+[dev-deps-url]: https://david-dm.org/ljharb/qs#info=devDependencies
+[npm-badge-png]: https://nodei.co/npm/qs.png?downloads=true&stars=true
+[license-image]: https://img.shields.io/npm/l/qs.svg
+[license-url]: LICENSE
+[downloads-image]: https://img.shields.io/npm/dm/qs.svg
+[downloads-url]: https://npm-stat.com/charts.html?package=qs
+[codecov-image]: https://codecov.io/gh/ljharb/qs/branch/main/graphs/badge.svg
+[codecov-url]: https://app.codecov.io/gh/ljharb/qs/
+[actions-image]: https://img.shields.io/github/check-runs/ljharb/qs/main
+[actions-url]: https://github.com/ljharb/qs/actions
+
+## Acknowledgements
+
+qs logo by [NUMI](https://github.com/numi-hq/open-design):
+
+[<img src="https://raw.githubusercontent.com/numi-hq/open-design/main/assets/numi-lockup.png" alt="NUMI Logo" style="width: 200px;"/>](https://numi.tech/?ref=qs)
diff --git a/node_modules/qs/dist/qs.js b/node_modules/qs/dist/qs.js
new file mode 100644
index 0000000..829e020
--- /dev/null
+++ b/node_modules/qs/dist/qs.js
@@ -0,0 +1,141 @@
+(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.Qs = f()}})(function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){
+"use strict";var replace=String.prototype.replace,percentTwenties=/%20/g,Format={RFC1738:"RFC1738",RFC3986:"RFC3986"};module.exports={default:Format.RFC3986,formatters:{RFC1738:function(e){return replace.call(e,percentTwenties,"+")},RFC3986:function(e){return String(e)}},RFC1738:Format.RFC1738,RFC3986:Format.RFC3986};
+
+},{}],2:[function(require,module,exports){
+"use strict";var stringify=require(4),parse=require(3),formats=require(1);module.exports={formats:formats,parse:parse,stringify:stringify};
+
+},{"1":1,"3":3,"4":4}],3:[function(require,module,exports){
+"use strict";var utils=require(5),has=Object.prototype.hasOwnProperty,isArray=Array.isArray,defaults={allowDots:!1,allowEmptyArrays:!1,allowPrototypes:!1,allowSparse:!1,arrayLimit:20,charset:"utf-8",charsetSentinel:!1,comma:!1,decodeDotInKeys:!1,decoder:utils.decode,delimiter:"&",depth:5,duplicates:"combine",ignoreQueryPrefix:!1,interpretNumericEntities:!1,parameterLimit:1e3,parseArrays:!0,plainObjects:!1,strictDepth:!1,strictMerge:!0,strictNullHandling:!1,throwOnLimitExceeded:!1},interpretNumericEntities=function(e){return e.replace(/&#(\d+);/g,function(e,t){return String.fromCharCode(parseInt(t,10))})},parseArrayValue=function(e,t,r,i){if(e&&"string"==typeof e&&t.comma&&e.indexOf(",")>-1){if(i&&t.throwOnLimitExceeded)for(var a=0,o=e.indexOf(",");o>-1;){if((a+=1)>=t.arrayLimit)throw new RangeError("Array limit exceeded. Only "+t.arrayLimit+" element"+(1===t.arrayLimit?"":"s")+" allowed in an array.");o=e.indexOf(",",o+1)}return e.split(",")}if(t.throwOnLimitExceeded&&r>=t.arrayLimit)throw new RangeError("Array limit exceeded. Only "+t.arrayLimit+" element"+(1===t.arrayLimit?"":"s")+" allowed in an array.");return e},isoSentinel="utf8=%26%2310003%3B",charsetSentinel="utf8=%E2%9C%93",parseValues=function parseQueryStringValues(e,t){var r={__proto__:null},i=t.ignoreQueryPrefix?e.replace(/^\?/,""):e;i=i.replace(/%5B/gi,"[").replace(/%5D/gi,"]");var a=t.parameterLimit===1/0?void 0:t.parameterLimit,o=i.split(t.delimiter,t.throwOnLimitExceeded&&void 0!==a?a+1:a);if(t.throwOnLimitExceeded&&void 0!==a&&o.length>a)throw new RangeError("Parameter limit exceeded. Only "+a+" parameter"+(1===a?"":"s")+" allowed.");var l,n=-1,s=t.charset;if(t.charsetSentinel)for(l=0;l<o.length;++l)0===o[l].indexOf("utf8=")&&(o[l]===charsetSentinel?s="utf-8":o[l]===isoSentinel&&(s="iso-8859-1"),n=l,l=o.length);for(l=0;l<o.length;++l)if(l!==n){var d,c,p=o[l],u=p.indexOf("]="),y=-1===u?p.indexOf("="):u+1;if(-1===y?(d=t.decoder(p,defaults.decoder,s,"key"),c=t.strictNullHandling?null:""):null!==(d=t.decoder(p.slice(0,y),defaults.decoder,s,"key"))&&(c=utils.maybeMap(parseArrayValue(p.slice(y+1),t,isArray(r[d])?r[d].length:0,-1===p.indexOf("[]=")),function(e){return t.decoder(e,defaults.decoder,s,"value")})),c&&t.interpretNumericEntities&&"iso-8859-1"===s&&(c=interpretNumericEntities(String(c))),p.indexOf("[]=")>-1&&(c=isArray(c)?[c]:c),t.comma&&isArray(c)&&c.length>t.arrayLimit&&(c=utils.combine([],c,t.arrayLimit,t.plainObjects,t.throwOnLimitExceeded)),null!==d){var f=has.call(r,d);f&&("combine"===t.duplicates||p.indexOf("[]=")>-1)?r[d]=utils.combine(r[d],c,t.arrayLimit,t.plainObjects,t.throwOnLimitExceeded):f&&"last"!==t.duplicates||(r[d]=c)}}return r},parseObject=function(e,t,r,i){var a=0;if(e.length>0&&"[]"===e[e.length-1]){var o=e.slice(0,-1).join("");a=Array.isArray(t)&&t[o]?t[o].length:0}for(var l=i?t:parseArrayValue(t,r,a),n=e.length-1;n>=0;--n){var s,d=e[n];if("[]"===d&&r.parseArrays)s=utils.isOverflow(l)?l:r.allowEmptyArrays&&(""===l||r.strictNullHandling&&null===l)?[]:utils.combine([],l,r.arrayLimit,r.plainObjects,r.throwOnLimitExceeded);else{s=r.plainObjects?{__proto__:null}:{};var c="["===d.charAt(0)&&"]"===d.charAt(d.length-1)?d.slice(1,-1):d,p=r.decodeDotInKeys?c.replace(/%2E/g,"."):c,u=parseInt(p,10),y=!isNaN(u)&&d!==p&&String(u)===p&&u>=0&&r.parseArrays;if(r.parseArrays||""!==p)if(y&&u<r.arrayLimit)(s=[])[u]=l;else{if(y&&r.throwOnLimitExceeded)throw new RangeError("Array limit exceeded. Only "+r.arrayLimit+" element"+(1===r.arrayLimit?"":"s")+" allowed in an array.");y?(s[u]=l,utils.markOverflow(s,u)):"__proto__"!==p&&(s[p]=l)}else s={0:l}}l=s}return l},splitKeyIntoSegments=function splitKeyIntoSegments(e,t){var r=t.allowDots?e.replace(/\.([^.[]+)/g,"[$1]"):e;if(t.depth<=0){if(!t.plainObjects&&has.call(Object.prototype,r)&&!t.allowPrototypes)return;return[r]}var i=[],a=r.indexOf("["),o=a>=0?r.slice(0,a):r;if(o){if(!t.plainObjects&&has.call(Object.prototype,o)&&!t.allowPrototypes)return;i[i.length]=o}for(var l=r.length,n=a,s=0;n>=0&&s<t.depth;){for(var d=1,c=n+1,p=-1;c<l&&p<0;){var u=r.charCodeAt(c);91===u?d+=1:93===u&&0==(d-=1)&&(p=c),c+=1}if(p<0)return i[i.length]="["+r.slice(n)+"]",i;var y=r.slice(n,p+1),f=y.slice(1,-1);if(!t.plainObjects&&has.call(Object.prototype,f)&&!t.allowPrototypes)return;i[i.length]=y,s+=1,n=r.indexOf("[",p+1)}if(n>=0){if(!0===t.strictDepth)throw new RangeError("Input depth exceeded depth option of "+t.depth+" and strictDepth is true");i[i.length]="["+r.slice(n)+"]"}return i},parseKeys=function parseQueryStringKeys(e,t,r,i){if(e){var a=splitKeyIntoSegments(e,r);if(a)return parseObject(a,t,r,i)}},normalizeParseOptions=function normalizeParseOptions(e){if(!e)return defaults;if(void 0!==e.allowEmptyArrays&&"boolean"!=typeof e.allowEmptyArrays)throw new TypeError("`allowEmptyArrays` option can only be `true` or `false`, when provided");if(void 0!==e.decodeDotInKeys&&"boolean"!=typeof e.decodeDotInKeys)throw new TypeError("`decodeDotInKeys` option can only be `true` or `false`, when provided");if(null!==e.decoder&&void 0!==e.decoder&&"function"!=typeof e.decoder)throw new TypeError("Decoder has to be a function.");if(void 0!==e.charset&&"utf-8"!==e.charset&&"iso-8859-1"!==e.charset)throw new TypeError("The charset option must be either utf-8, iso-8859-1, or undefined");if(void 0!==e.throwOnLimitExceeded&&"boolean"!=typeof e.throwOnLimitExceeded)throw new TypeError("`throwOnLimitExceeded` option must be a boolean");var t=void 0===e.charset?defaults.charset:e.charset,r=void 0===e.duplicates?defaults.duplicates:e.duplicates;if("combine"!==r&&"first"!==r&&"last"!==r)throw new TypeError("The duplicates option must be either combine, first, or last");return{allowDots:void 0===e.allowDots?!0===e.decodeDotInKeys||defaults.allowDots:!!e.allowDots,allowEmptyArrays:"boolean"==typeof e.allowEmptyArrays?!!e.allowEmptyArrays:defaults.allowEmptyArrays,allowPrototypes:"boolean"==typeof e.allowPrototypes?e.allowPrototypes:defaults.allowPrototypes,allowSparse:"boolean"==typeof e.allowSparse?e.allowSparse:defaults.allowSparse,arrayLimit:"number"==typeof e.arrayLimit?e.arrayLimit:defaults.arrayLimit,charset:t,charsetSentinel:"boolean"==typeof e.charsetSentinel?e.charsetSentinel:defaults.charsetSentinel,comma:"boolean"==typeof e.comma?e.comma:defaults.comma,decodeDotInKeys:"boolean"==typeof e.decodeDotInKeys?e.decodeDotInKeys:defaults.decodeDotInKeys,decoder:"function"==typeof e.decoder?e.decoder:defaults.decoder,delimiter:"string"==typeof e.delimiter||utils.isRegExp(e.delimiter)?e.delimiter:defaults.delimiter,depth:"number"==typeof e.depth||!1===e.depth?+e.depth:defaults.depth,duplicates:r,ignoreQueryPrefix:!0===e.ignoreQueryPrefix,interpretNumericEntities:"boolean"==typeof e.interpretNumericEntities?e.interpretNumericEntities:defaults.interpretNumericEntities,parameterLimit:"number"==typeof e.parameterLimit?e.parameterLimit:defaults.parameterLimit,parseArrays:!1!==e.parseArrays,plainObjects:"boolean"==typeof e.plainObjects?e.plainObjects:defaults.plainObjects,strictDepth:"boolean"==typeof e.strictDepth?!!e.strictDepth:defaults.strictDepth,strictMerge:"boolean"==typeof e.strictMerge?!!e.strictMerge:defaults.strictMerge,strictNullHandling:"boolean"==typeof e.strictNullHandling?e.strictNullHandling:defaults.strictNullHandling,throwOnLimitExceeded:"boolean"==typeof e.throwOnLimitExceeded&&e.throwOnLimitExceeded}};module.exports=function(e,t){var r=normalizeParseOptions(t);if(""===e||null==e)return r.plainObjects?{__proto__:null}:{};for(var i="string"==typeof e?parseValues(e,r):e,a=r.plainObjects?{__proto__:null}:{},o=Object.keys(i),l=0;l<o.length;++l){var n=o[l],s=parseKeys(n,i[n],r,"string"==typeof e);a=utils.merge(a,s,r)}return!0===r.allowSparse?a:utils.compact(a)};
+
+},{"5":5}],4:[function(require,module,exports){
+"use strict";var getSideChannel=require(46),utils=require(5),formats=require(1),has=Object.prototype.hasOwnProperty,arrayPrefixGenerators={brackets:function brackets(e){return e+"[]"},comma:"comma",indices:function indices(e,r){return e+"["+r+"]"},repeat:function repeat(e){return e}},isArray=Array.isArray,push=Array.prototype.push,pushToArray=function(e,r){push.apply(e,isArray(r)?r:[r])},toISO=Date.prototype.toISOString,defaultFormat=formats.default,defaults={addQueryPrefix:!1,allowDots:!1,allowEmptyArrays:!1,arrayFormat:"indices",charset:"utf-8",charsetSentinel:!1,commaRoundTrip:!1,delimiter:"&",encode:!0,encodeDotInKeys:!1,encoder:utils.encode,encodeValuesOnly:!1,filter:void 0,format:defaultFormat,formatter:formats.formatters[defaultFormat],indices:!1,serializeDate:function serializeDate(e){return toISO.call(e)},skipNulls:!1,strictNullHandling:!1},isNonNullishPrimitive=function isNonNullishPrimitive(e){return"string"==typeof e||"number"==typeof e||"boolean"==typeof e||"symbol"==typeof e||"bigint"==typeof e},sentinel={},stringify=function stringify(e,r,t,o,a,n,i,l,s,f,u,d,y,c,p,m,h,v){for(var g=e,w=v,b=0,A=!1;void 0!==(w=w.get(sentinel))&&!A;){var D=w.get(e);if(b+=1,void 0!==D){if(D===b)throw new RangeError("Cyclic object value");A=!0}void 0===w.get(sentinel)&&(b=0)}if("function"==typeof f?g=f(r,g):g instanceof Date?g=y(g):"comma"===t&&isArray(g)&&(g=utils.maybeMap(g,function(e){return e instanceof Date?y(e):e})),null===g){if(n)return p(s&&!m?s(r,defaults.encoder,h,"key",c):r);g=""}if(isNonNullishPrimitive(g)||utils.isBuffer(g))return s?[p(m?r:s(r,defaults.encoder,h,"key",c))+"="+p(s(g,defaults.encoder,h,"value",c))]:[p(r)+"="+p(String(g))];var S,E=[];if(void 0===g)return E;if("comma"===t&&isArray(g))m&&s&&(g=utils.maybeMap(g,function(e){return null==e?e:s(e)})),S=[{value:g.length>0?g.join(",")||null:void 0}];else if(isArray(f))S=f;else{var N=Object.keys(g);S=u?N.sort(u):N}var T=l?String(r).replace(/\./g,"%2E"):String(r),O=o&&isArray(g)&&1===g.length?T+"[]":T;if(a&&isArray(g)&&0===g.length)return O+"[]";for(var k=0;k<S.length;++k){var I=S[k],P="object"==typeof I&&I&&void 0!==I.value?I.value:g[I];if(!i||null!==P){var x=d&&l?String(I).replace(/\./g,"%2E"):String(I),z=isArray(g)?"function"==typeof t?t(O,x):O:O+(d?"."+x:"["+x+"]");v.set(e,b);var K=getSideChannel();K.set(sentinel,v),pushToArray(E,stringify(P,z,t,o,a,n,i,l,"comma"===t&&m&&isArray(g)?null:s,f,u,d,y,c,p,m,h,K))}}return E},normalizeStringifyOptions=function normalizeStringifyOptions(e){if(!e)return defaults;if(void 0!==e.allowEmptyArrays&&"boolean"!=typeof e.allowEmptyArrays)throw new TypeError("`allowEmptyArrays` option can only be `true` or `false`, when provided");if(void 0!==e.encodeDotInKeys&&"boolean"!=typeof e.encodeDotInKeys)throw new TypeError("`encodeDotInKeys` option can only be `true` or `false`, when provided");if(null!==e.encoder&&void 0!==e.encoder&&"function"!=typeof e.encoder)throw new TypeError("Encoder has to be a function.");var r=e.charset||defaults.charset;if(void 0!==e.charset&&"utf-8"!==e.charset&&"iso-8859-1"!==e.charset)throw new TypeError("The charset option must be either utf-8, iso-8859-1, or undefined");var t=formats.default;if(void 0!==e.format){if(!has.call(formats.formatters,e.format))throw new TypeError("Unknown format option provided.");t=e.format}var o,a=formats.formatters[t],n=defaults.filter;if(("function"==typeof e.filter||isArray(e.filter))&&(n=e.filter),o=e.arrayFormat in arrayPrefixGenerators?e.arrayFormat:"indices"in e?e.indices?"indices":"repeat":defaults.arrayFormat,"commaRoundTrip"in e&&"boolean"!=typeof e.commaRoundTrip)throw new TypeError("`commaRoundTrip` must be a boolean, or absent");var i=void 0===e.allowDots?!0===e.encodeDotInKeys||defaults.allowDots:!!e.allowDots;return{addQueryPrefix:"boolean"==typeof e.addQueryPrefix?e.addQueryPrefix:defaults.addQueryPrefix,allowDots:i,allowEmptyArrays:"boolean"==typeof e.allowEmptyArrays?!!e.allowEmptyArrays:defaults.allowEmptyArrays,arrayFormat:o,charset:r,charsetSentinel:"boolean"==typeof e.charsetSentinel?e.charsetSentinel:defaults.charsetSentinel,commaRoundTrip:!!e.commaRoundTrip,delimiter:void 0===e.delimiter?defaults.delimiter:e.delimiter,encode:"boolean"==typeof e.encode?e.encode:defaults.encode,encodeDotInKeys:"boolean"==typeof e.encodeDotInKeys?e.encodeDotInKeys:defaults.encodeDotInKeys,encoder:"function"==typeof e.encoder?e.encoder:defaults.encoder,encodeValuesOnly:"boolean"==typeof e.encodeValuesOnly?e.encodeValuesOnly:defaults.encodeValuesOnly,filter:n,format:t,formatter:a,serializeDate:"function"==typeof e.serializeDate?e.serializeDate:defaults.serializeDate,skipNulls:"boolean"==typeof e.skipNulls?e.skipNulls:defaults.skipNulls,sort:"function"==typeof e.sort?e.sort:null,strictNullHandling:"boolean"==typeof e.strictNullHandling?e.strictNullHandling:defaults.strictNullHandling}};module.exports=function(e,r){var t,o=e,a=normalizeStringifyOptions(r);"function"==typeof a.filter?o=(0,a.filter)("",o):isArray(a.filter)&&(t=a.filter);var n=[];if("object"!=typeof o||null===o)return"";var i=arrayPrefixGenerators[a.arrayFormat],l="comma"===i&&a.commaRoundTrip;t||(t=Object.keys(o)),a.sort&&t.sort(a.sort);for(var s=getSideChannel(),f=0;f<t.length;++f){var u=t[f];if(null!=u){var d=o[u];a.skipNulls&&null===d||pushToArray(n,stringify(d,u,i,l,a.allowEmptyArrays,a.strictNullHandling,a.skipNulls,a.encodeDotInKeys,a.encode?a.encoder:null,a.filter,a.sort,a.allowDots,a.serializeDate,a.format,a.formatter,a.encodeValuesOnly,a.charset,s))}}var y=n.join(a.delimiter),c=!0===a.addQueryPrefix?"?":"";return a.charsetSentinel&&("iso-8859-1"===a.charset?c+="utf8=%26%2310003%3B"+a.delimiter:c+="utf8=%E2%9C%93"+a.delimiter),y.length>0?c+y:""};
+
+},{"1":1,"46":46,"5":5}],5:[function(require,module,exports){
+"use strict";var formats=require(1),getSideChannel=require(46),defineProperty=require(14),has=Object.prototype.hasOwnProperty,isArray=Array.isArray,overflowChannel=getSideChannel(),markOverflow=function markOverflow(e,r){return overflowChannel.set(e,r),e},isOverflow=function isOverflow(e){return overflowChannel.has(e)},getMaxIndex=function getMaxIndex(e){return overflowChannel.get(e)},setMaxIndex=function setMaxIndex(e,r){overflowChannel.set(e,r)},hexTable=function(){for(var e=[],r=0;r<256;++r)e[e.length]="%"+((r<16?"0":"")+r.toString(16)).toUpperCase();return e}(),compactQueue=function compactQueue(e){for(;e.length>1;){var r=e.pop(),t=r.obj[r.prop];if(isArray(t)){for(var n=[],a=0;a<t.length;++a)void 0!==t[a]&&(n[n.length]=t[a]);r.obj[r.prop]=n}}},arrayToObject=function arrayToObject(e,r){for(var t=r&&r.plainObjects?{__proto__:null}:{},n=0;n<e.length;++n)void 0!==e[n]&&(t[n]=e[n]);return t},setProperty=function setProperty(e,r,t){"__proto__"===r&&defineProperty?defineProperty(e,r,{configurable:!0,enumerable:!0,value:t,writable:!0}):e[r]=t},merge=function merge(e,r,t){if(!r)return e;if("object"!=typeof r&&"function"!=typeof r){if(isArray(e)){var n=e.length;if(t&&"number"==typeof t.arrayLimit&&n>=t.arrayLimit){if(t.throwOnLimitExceeded)throw new RangeError("Array limit exceeded. Only "+t.arrayLimit+" element"+(1===t.arrayLimit?"":"s")+" allowed in an array.");return markOverflow(arrayToObject(e.concat(r),t),n)}e[n]=r}else{if(!e||"object"!=typeof e)return[e,r];if(isOverflow(e)){var a=getMaxIndex(e)+1;e[a]=r,setMaxIndex(e,a)}else{if(t&&t.strictMerge)return[e,r];(t&&(t.plainObjects||t.allowPrototypes)||!has.call(Object.prototype,r))&&(e[r]=!0)}}return e}if(!e||"object"!=typeof e){if(isOverflow(r)){for(var o=Object.keys(r),i=t&&t.plainObjects?{__proto__:null,0:e}:{0:e},l=0;l<o.length;l++)i[parseInt(o[l],10)+1]=r[o[l]];return markOverflow(i,getMaxIndex(r)+1)}var c=[e].concat(r);if(t&&"number"==typeof t.arrayLimit&&c.length>t.arrayLimit){if(t.throwOnLimitExceeded)throw new RangeError("Array limit exceeded. Only "+t.arrayLimit+" element"+(1===t.arrayLimit?"":"s")+" allowed in an array.");return markOverflow(arrayToObject(c,t),c.length-1)}return c}var f=e;if(isArray(e)&&!isArray(r)&&(f=arrayToObject(e,t)),isArray(e)&&isArray(r)){if(r.forEach(function(r,n){if(has.call(e,n)){var a=e[n];a&&"object"==typeof a&&r&&"object"==typeof r?e[n]=merge(a,r,t):e[e.length]=r}else e[n]=r}),t&&"number"==typeof t.arrayLimit&&e.length>t.arrayLimit){if(t.throwOnLimitExceeded)throw new RangeError("Array limit exceeded. Only "+t.arrayLimit+" element"+(1===t.arrayLimit?"":"s")+" allowed in an array.");return markOverflow(arrayToObject(e,t),e.length-1)}return e}return Object.keys(r).reduce(function(e,n){var a=r[n];if(has.call(e,n)?setProperty(e,n,merge(e[n],a,t)):setProperty(e,n,a),isOverflow(r)&&!isOverflow(e)&&markOverflow(e,getMaxIndex(r)),isOverflow(e)){var o=parseInt(n,10);String(o)===n&&o>=0&&o>getMaxIndex(e)&&setMaxIndex(e,o)}return e},f)},assign=function assignSingleSource(e,r){return Object.keys(r).reduce(function(e,t){return setProperty(e,t,r[t]),e},e)},decode=function(e,r,t){var n=e.replace(/\+/g," ");if("iso-8859-1"===t)return n.replace(/%[0-9a-f]{2}/gi,unescape);try{return decodeURIComponent(n)}catch(e){return n}},limit=1024,encode=function encode(e,r,t,n,a){if(0===e.length)return e;var o=e;if("symbol"==typeof e?o=Symbol.prototype.toString.call(e):"string"!=typeof e&&(o=String(e)),"iso-8859-1"===t)return escape(o).replace(/%u[0-9a-f]{4}/gi,function(e){return"%26%23"+parseInt(e.slice(2),16)+"%3B"});for(var i="",l=0;l<o.length;l+=limit){var c=o.length>=limit?o.slice(l,l+limit):o;if(l+limit<o.length){var f=c.charCodeAt(c.length-1);f>=55296&&f<=56319&&(c=c.slice(0,-1),l-=1)}for(var s=[],u=0;u<c.length;++u){var y=c.charCodeAt(u);45===y||46===y||95===y||126===y||y>=48&&y<=57||y>=65&&y<=90||y>=97&&y<=122||a===formats.RFC1738&&(40===y||41===y)?s[s.length]=c.charAt(u):y<128?s[s.length]=hexTable[y]:y<2048?s[s.length]=hexTable[192|y>>6]+hexTable[128|63&y]:y<55296||y>=57344?s[s.length]=hexTable[224|y>>12]+hexTable[128|y>>6&63]+hexTable[128|63&y]:(u+=1,y=65536+((1023&y)<<10|1023&c.charCodeAt(u)),s[s.length]=hexTable[240|y>>18]+hexTable[128|y>>12&63]+hexTable[128|y>>6&63]+hexTable[128|63&y])}i+=s.join("")}return i},compact=function compact(e){for(var r=[{obj:{o:e},prop:"o"}],t=getSideChannel(),n=0;n<r.length;++n)for(var a=r[n],o=a.obj[a.prop],i=Object.keys(o),l=0;l<i.length;++l){var c=i[l],f=o[c];"object"!=typeof f||null===f||t.has(f)||(r[r.length]={obj:o,prop:c},t.set(f,!0))}return compactQueue(r),e},isRegExp=function isRegExp(e){return"[object RegExp]"===Object.prototype.toString.call(e)},isBuffer=function isBuffer(e){return!(!e||"object"!=typeof e||!(e.constructor&&e.constructor.isBuffer&&e.constructor.isBuffer(e)))},combine=function combine(e,r,t,n,a){if(isOverflow(e)){if(a)throw new RangeError("Array limit exceeded. Only "+t+" element"+(1===t?"":"s")+" allowed in an array.");var o=getMaxIndex(e)+1;return e[o]=r,setMaxIndex(e,o),e}var i=[].concat(e,r);if(i.length>t){if(a)throw new RangeError("Array limit exceeded. Only "+t+" element"+(1===t?"":"s")+" allowed in an array.");return markOverflow(arrayToObject(i,{plainObjects:n}),i.length-1)}return i},maybeMap=function maybeMap(e,r){if(isArray(e)){for(var t=[],n=0;n<e.length;n+=1)t[t.length]=r(e[n]);return t}return r(e)};module.exports={/* common-shake removed: arrayToObject:arrayToObject *//* common-shake removed: assign:assign */combine:combine,compact:compact,decode:decode,encode:encode,isBuffer:isBuffer,isOverflow:isOverflow,isRegExp:isRegExp,markOverflow:markOverflow,maybeMap:maybeMap,merge:merge};
+
+},{"1":1,"14":14,"46":46}],46:[function(require,module,exports){
+"use strict";var $TypeError=require(20),inspect=require(42),getSideChannelList=require(43),getSideChannelMap=require(44),getSideChannelWeakMap=require(45),makeChannel=getSideChannelWeakMap||getSideChannelMap||getSideChannelList;module.exports=function getSideChannel(){var e,n={assert:function(e){if(!n.has(e)){var t=e&&Object(e)===e?"the given object key":inspect(e);throw new $TypeError("Side channel does not contain "+t)}},delete:function(n){return!!e&&e.delete(n)},get:function(n){return e&&e.get(n)},has:function(n){return!!e&&e.has(n)},set:function(n,t){e||(e=makeChannel()),e.set(n,t)}};return n};
+
+},{"20":20,"42":42,"43":43,"44":44,"45":45}],14:[function(require,module,exports){
+"use strict";var $defineProperty=Object.defineProperty||!1;if($defineProperty)try{$defineProperty({},"a",{value:1})}catch(e){$defineProperty=!1}module.exports=$defineProperty;
+
+},{}],6:[function(require,module,exports){
+
+},{}],7:[function(require,module,exports){
+"use strict";var bind=require(24),$apply=require(8),$call=require(9),$reflectApply=require(11);module.exports=$reflectApply||bind.call($call,$apply);
+
+},{"11":11,"24":24,"8":8,"9":9}],9:[function(require,module,exports){
+"use strict";module.exports=Function.prototype.call;
+
+},{}],8:[function(require,module,exports){
+"use strict";module.exports=Function.prototype.apply;
+
+},{}],24:[function(require,module,exports){
+"use strict";var implementation=require(23);module.exports=Function.prototype.bind||implementation;
+
+},{"23":23}],11:[function(require,module,exports){
+"use strict";module.exports="undefined"!=typeof Reflect&&Reflect&&Reflect.apply;
+
+},{}],10:[function(require,module,exports){
+"use strict";var bind=require(24),$TypeError=require(20),$call=require(9),$actualApply=require(7);module.exports=function callBindBasic(r){if(r.length<1||"function"!=typeof r[0])throw new $TypeError("a function is required");return $actualApply(bind,$call,r)};
+
+},{"20":20,"24":24,"7":7,"9":9}],20:[function(require,module,exports){
+"use strict";module.exports=TypeError;
+
+},{}],12:[function(require,module,exports){
+"use strict";var GetIntrinsic=require(25),callBindBasic=require(10),$indexOf=callBindBasic([GetIntrinsic("%String.prototype.indexOf%")]);module.exports=function callBoundIntrinsic(i,n){var t=GetIntrinsic(i,!!n);return"function"==typeof t&&$indexOf(i,".prototype.")>-1?callBindBasic([t]):t};
+
+},{"10":10,"25":25}],25:[function(require,module,exports){
+"use strict";var undefined,$Object=require(22),$Error=require(16),$EvalError=require(15),$RangeError=require(17),$ReferenceError=require(18),$SyntaxError=require(19),$TypeError=require(20),$URIError=require(21),abs=require(34),floor=require(35),max=require(37),min=require(38),pow=require(39),round=require(40),sign=require(41),$Function=Function,getEvalledConstructor=function(r){try{return $Function('"use strict"; return ('+r+").constructor;")()}catch(r){}},$gOPD=require(30),$defineProperty=require(14),throwTypeError=function(){throw new $TypeError},ThrowTypeError=$gOPD?function(){try{return throwTypeError}catch(r){try{return $gOPD(arguments,"callee").get}catch(r){return throwTypeError}}}():throwTypeError,hasSymbols=require(31)(),getProto=require(28),$ObjectGPO=require(26),$ReflectGPO=require(27),$apply=require(8),$call=require(9),needsEval={},TypedArray="undefined"!=typeof Uint8Array&&getProto?getProto(Uint8Array):undefined,INTRINSICS={__proto__:null,"%AggregateError%":"undefined"==typeof AggregateError?undefined:AggregateError,"%Array%":Array,"%ArrayBuffer%":"undefined"==typeof ArrayBuffer?undefined:ArrayBuffer,"%ArrayIteratorPrototype%":hasSymbols&&getProto?getProto([][Symbol.iterator]()):undefined,"%AsyncFromSyncIteratorPrototype%":undefined,"%AsyncFunction%":needsEval,"%AsyncGenerator%":needsEval,"%AsyncGeneratorFunction%":needsEval,"%AsyncIteratorPrototype%":needsEval,"%Atomics%":"undefined"==typeof Atomics?undefined:Atomics,"%BigInt%":"undefined"==typeof BigInt?undefined:BigInt,"%BigInt64Array%":"undefined"==typeof BigInt64Array?undefined:BigInt64Array,"%BigUint64Array%":"undefined"==typeof BigUint64Array?undefined:BigUint64Array,"%Boolean%":Boolean,"%DataView%":"undefined"==typeof DataView?undefined:DataView,"%Date%":Date,"%decodeURI%":decodeURI,"%decodeURIComponent%":decodeURIComponent,"%encodeURI%":encodeURI,"%encodeURIComponent%":encodeURIComponent,"%Error%":$Error,"%eval%":eval,"%EvalError%":$EvalError,"%Float16Array%":"undefined"==typeof Float16Array?undefined:Float16Array,"%Float32Array%":"undefined"==typeof Float32Array?undefined:Float32Array,"%Float64Array%":"undefined"==typeof Float64Array?undefined:Float64Array,"%FinalizationRegistry%":"undefined"==typeof FinalizationRegistry?undefined:FinalizationRegistry,"%Function%":$Function,"%GeneratorFunction%":needsEval,"%Int8Array%":"undefined"==typeof Int8Array?undefined:Int8Array,"%Int16Array%":"undefined"==typeof Int16Array?undefined:Int16Array,"%Int32Array%":"undefined"==typeof Int32Array?undefined:Int32Array,"%isFinite%":isFinite,"%isNaN%":isNaN,"%IteratorPrototype%":hasSymbols&&getProto?getProto(getProto([][Symbol.iterator]())):undefined,"%JSON%":"object"==typeof JSON?JSON:undefined,"%Map%":"undefined"==typeof Map?undefined:Map,"%MapIteratorPrototype%":"undefined"!=typeof Map&&hasSymbols&&getProto?getProto((new Map)[Symbol.iterator]()):undefined,"%Math%":Math,"%Number%":Number,"%Object%":$Object,"%Object.getOwnPropertyDescriptor%":$gOPD,"%parseFloat%":parseFloat,"%parseInt%":parseInt,"%Promise%":"undefined"==typeof Promise?undefined:Promise,"%Proxy%":"undefined"==typeof Proxy?undefined:Proxy,"%RangeError%":$RangeError,"%ReferenceError%":$ReferenceError,"%Reflect%":"undefined"==typeof Reflect?undefined:Reflect,"%RegExp%":RegExp,"%Set%":"undefined"==typeof Set?undefined:Set,"%SetIteratorPrototype%":"undefined"!=typeof Set&&hasSymbols&&getProto?getProto((new Set)[Symbol.iterator]()):undefined,"%SharedArrayBuffer%":"undefined"==typeof SharedArrayBuffer?undefined:SharedArrayBuffer,"%String%":String,"%StringIteratorPrototype%":hasSymbols&&getProto?getProto(""[Symbol.iterator]()):undefined,"%Symbol%":hasSymbols?Symbol:undefined,"%SyntaxError%":$SyntaxError,"%ThrowTypeError%":ThrowTypeError,"%TypedArray%":TypedArray,"%TypeError%":$TypeError,"%Uint8Array%":"undefined"==typeof Uint8Array?undefined:Uint8Array,"%Uint8ClampedArray%":"undefined"==typeof Uint8ClampedArray?undefined:Uint8ClampedArray,"%Uint16Array%":"undefined"==typeof Uint16Array?undefined:Uint16Array,"%Uint32Array%":"undefined"==typeof Uint32Array?undefined:Uint32Array,"%URIError%":$URIError,"%WeakMap%":"undefined"==typeof WeakMap?undefined:WeakMap,"%WeakRef%":"undefined"==typeof WeakRef?undefined:WeakRef,"%WeakSet%":"undefined"==typeof WeakSet?undefined:WeakSet,"%Function.prototype.call%":$call,"%Function.prototype.apply%":$apply,"%Object.defineProperty%":$defineProperty,"%Object.getPrototypeOf%":$ObjectGPO,"%Math.abs%":abs,"%Math.floor%":floor,"%Math.max%":max,"%Math.min%":min,"%Math.pow%":pow,"%Math.round%":round,"%Math.sign%":sign,"%Reflect.getPrototypeOf%":$ReflectGPO};if(getProto)try{null.error}catch(r){var errorProto=getProto(getProto(r));INTRINSICS["%Error.prototype%"]=errorProto}var doEval=function doEval(r){var e;if("%AsyncFunction%"===r)e=getEvalledConstructor("async function () {}");else if("%GeneratorFunction%"===r)e=getEvalledConstructor("function* () {}");else if("%AsyncGeneratorFunction%"===r)e=getEvalledConstructor("async function* () {}");else if("%AsyncGenerator%"===r){var t=doEval("%AsyncGeneratorFunction%");t&&(e=t.prototype)}else if("%AsyncIteratorPrototype%"===r){var o=doEval("%AsyncGenerator%");o&&getProto&&(e=getProto(o.prototype))}return INTRINSICS[r]=e,e},LEGACY_ALIASES={__proto__:null,"%ArrayBufferPrototype%":["ArrayBuffer","prototype"],"%ArrayPrototype%":["Array","prototype"],"%ArrayProto_entries%":["Array","prototype","entries"],"%ArrayProto_forEach%":["Array","prototype","forEach"],"%ArrayProto_keys%":["Array","prototype","keys"],"%ArrayProto_values%":["Array","prototype","values"],"%AsyncFunctionPrototype%":["AsyncFunction","prototype"],"%AsyncGenerator%":["AsyncGeneratorFunction","prototype"],"%AsyncGeneratorPrototype%":["AsyncGeneratorFunction","prototype","prototype"],"%BooleanPrototype%":["Boolean","prototype"],"%DataViewPrototype%":["DataView","prototype"],"%DatePrototype%":["Date","prototype"],"%ErrorPrototype%":["Error","prototype"],"%EvalErrorPrototype%":["EvalError","prototype"],"%Float32ArrayPrototype%":["Float32Array","prototype"],"%Float64ArrayPrototype%":["Float64Array","prototype"],"%FunctionPrototype%":["Function","prototype"],"%Generator%":["GeneratorFunction","prototype"],"%GeneratorPrototype%":["GeneratorFunction","prototype","prototype"],"%Int8ArrayPrototype%":["Int8Array","prototype"],"%Int16ArrayPrototype%":["Int16Array","prototype"],"%Int32ArrayPrototype%":["Int32Array","prototype"],"%JSONParse%":["JSON","parse"],"%JSONStringify%":["JSON","stringify"],"%MapPrototype%":["Map","prototype"],"%NumberPrototype%":["Number","prototype"],"%ObjectPrototype%":["Object","prototype"],"%ObjProto_toString%":["Object","prototype","toString"],"%ObjProto_valueOf%":["Object","prototype","valueOf"],"%PromisePrototype%":["Promise","prototype"],"%PromiseProto_then%":["Promise","prototype","then"],"%Promise_all%":["Promise","all"],"%Promise_reject%":["Promise","reject"],"%Promise_resolve%":["Promise","resolve"],"%RangeErrorPrototype%":["RangeError","prototype"],"%ReferenceErrorPrototype%":["ReferenceError","prototype"],"%RegExpPrototype%":["RegExp","prototype"],"%SetPrototype%":["Set","prototype"],"%SharedArrayBufferPrototype%":["SharedArrayBuffer","prototype"],"%StringPrototype%":["String","prototype"],"%SymbolPrototype%":["Symbol","prototype"],"%SyntaxErrorPrototype%":["SyntaxError","prototype"],"%TypedArrayPrototype%":["TypedArray","prototype"],"%TypeErrorPrototype%":["TypeError","prototype"],"%Uint8ArrayPrototype%":["Uint8Array","prototype"],"%Uint8ClampedArrayPrototype%":["Uint8ClampedArray","prototype"],"%Uint16ArrayPrototype%":["Uint16Array","prototype"],"%Uint32ArrayPrototype%":["Uint32Array","prototype"],"%URIErrorPrototype%":["URIError","prototype"],"%WeakMapPrototype%":["WeakMap","prototype"],"%WeakSetPrototype%":["WeakSet","prototype"]},bind=require(24),hasOwn=require(33),$concat=bind.call($call,Array.prototype.concat),$spliceApply=bind.call($apply,Array.prototype.splice),$replace=bind.call($call,String.prototype.replace),$strSlice=bind.call($call,String.prototype.slice),$exec=bind.call($call,RegExp.prototype.exec),rePropName=/[^%.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\\]|\\.)*?)\2)\]|(?=(?:\.|\[\])(?:\.|\[\]|%$))/g,reEscapeChar=/\\(\\)?/g,stringToPath=function stringToPath(r){var e=$strSlice(r,0,1),t=$strSlice(r,-1);if("%"===e&&"%"!==t)throw new $SyntaxError("invalid intrinsic syntax, expected closing `%`");if("%"===t&&"%"!==e)throw new $SyntaxError("invalid intrinsic syntax, expected opening `%`");var o=[];return $replace(r,rePropName,function(r,e,t,n){o[o.length]=t?$replace(n,reEscapeChar,"$1"):e||r}),o},getBaseIntrinsic=function getBaseIntrinsic(r,e){var t,o=r;if(hasOwn(LEGACY_ALIASES,o)&&(o="%"+(t=LEGACY_ALIASES[o])[0]+"%"),hasOwn(INTRINSICS,o)){var n=INTRINSICS[o];if(n===needsEval&&(n=doEval(o)),void 0===n&&!e)throw new $TypeError("intrinsic "+r+" exists, but is not available. Please file an issue!");return{alias:t,name:o,value:n}}throw new $SyntaxError("intrinsic "+r+" does not exist!")};module.exports=function GetIntrinsic(r,e){if("string"!=typeof r||0===r.length)throw new $TypeError("intrinsic name must be a non-empty string");if(arguments.length>1&&"boolean"!=typeof e)throw new $TypeError('"allowMissing" argument must be a boolean');if(null===$exec(/^%?[^%]*%?$/,r))throw new $SyntaxError("`%` may not be present anywhere but at the beginning and end of the intrinsic name");var t=stringToPath(r),o=t.length>0?t[0]:"",n=getBaseIntrinsic("%"+o+"%",e),a=n.name,i=n.value,y=!1,p=n.alias;p&&(o=p[0],$spliceApply(t,$concat([0,1],p)));for(var d=1,s=!0;d<t.length;d+=1){var f=t[d],l=$strSlice(f,0,1),u=$strSlice(f,-1);if(('"'===l||"'"===l||"`"===l||'"'===u||"'"===u||"`"===u)&&l!==u)throw new $SyntaxError("property names with quotes must have matching quotes");if("constructor"!==f&&s||(y=!0),hasOwn(INTRINSICS,a="%"+(o+="."+f)+"%"))i=INTRINSICS[a];else if(null!=i){if(!(f in i)){if(!e)throw new $TypeError("base intrinsic for "+r+" exists, but the property is not available.");return}if($gOPD&&d+1>=t.length){var c=$gOPD(i,f);i=(s=!!c)&&"get"in c&&!("originalValue"in c.get)?c.get:i[f]}else s=hasOwn(i,f),i=i[f];s&&!y&&(INTRINSICS[a]=i)}}return i};
+
+},{"14":14,"15":15,"16":16,"17":17,"18":18,"19":19,"20":20,"21":21,"22":22,"24":24,"26":26,"27":27,"28":28,"30":30,"31":31,"33":33,"34":34,"35":35,"37":37,"38":38,"39":39,"40":40,"41":41,"8":8,"9":9}],13:[function(require,module,exports){
+"use strict";var hasProtoAccessor,callBind=require(10),gOPD=require(30);try{hasProtoAccessor=[].__proto__===Array.prototype}catch(t){if(!t||"object"!=typeof t||!("code"in t)||"ERR_PROTO_ACCESS"!==t.code)throw t}var desc=!!hasProtoAccessor&&gOPD&&gOPD(Object.prototype,"__proto__"),$Object=Object,$getPrototypeOf=$Object.getPrototypeOf;module.exports=desc&&"function"==typeof desc.get?callBind([desc.get]):"function"==typeof $getPrototypeOf&&function getDunder(t){return $getPrototypeOf(null==t?t:$Object(t))};
+
+},{"10":10,"30":30}],30:[function(require,module,exports){
+"use strict";var $gOPD=require(29);if($gOPD)try{$gOPD([],"length")}catch(g){$gOPD=null}module.exports=$gOPD;
+
+},{"29":29}],15:[function(require,module,exports){
+"use strict";module.exports=EvalError;
+
+},{}],16:[function(require,module,exports){
+"use strict";module.exports=Error;
+
+},{}],17:[function(require,module,exports){
+"use strict";module.exports=RangeError;
+
+},{}],18:[function(require,module,exports){
+"use strict";module.exports=ReferenceError;
+
+},{}],19:[function(require,module,exports){
+"use strict";module.exports=SyntaxError;
+
+},{}],21:[function(require,module,exports){
+"use strict";module.exports=URIError;
+
+},{}],22:[function(require,module,exports){
+"use strict";module.exports=Object;
+
+},{}],23:[function(require,module,exports){
+"use strict";var ERROR_MESSAGE="Function.prototype.bind called on incompatible ",toStr=Object.prototype.toString,max=Math.max,funcType="[object Function]",concatty=function concatty(t,n){for(var r=[],o=0;o<t.length;o+=1)r[o]=t[o];for(var e=0;e<n.length;e+=1)r[e+t.length]=n[e];return r},slicy=function slicy(t,n){for(var r=[],o=n||0,e=0;o<t.length;o+=1,e+=1)r[e]=t[o];return r},joiny=function(t,n){for(var r="",o=0;o<t.length;o+=1)r+=t[o],o+1<t.length&&(r+=n);return r};module.exports=function bind(t){var n=this;if("function"!=typeof n||toStr.apply(n)!==funcType)throw new TypeError(ERROR_MESSAGE+n);for(var r,o=slicy(arguments,1),e=max(0,n.length-o.length),i=[],c=0;c<e;c++)i[c]="$"+c;if(r=Function("binder","return function ("+joiny(i,",")+"){ return binder.apply(this,arguments); }")(function(){if(this instanceof r){var e=n.apply(this,concatty(o,arguments));return Object(e)===e?e:this}return n.apply(t,concatty(o,arguments))}),n.prototype){var p=function Empty(){};p.prototype=n.prototype,r.prototype=new p,p.prototype=null}return r};
+
+},{}],37:[function(require,module,exports){
+"use strict";module.exports=Math.max;
+
+},{}],40:[function(require,module,exports){
+"use strict";module.exports=Math.round;
+
+},{}],34:[function(require,module,exports){
+"use strict";module.exports=Math.abs;
+
+},{}],38:[function(require,module,exports){
+"use strict";module.exports=Math.min;
+
+},{}],39:[function(require,module,exports){
+"use strict";module.exports=Math.pow;
+
+},{}],35:[function(require,module,exports){
+"use strict";module.exports=Math.floor;
+
+},{}],27:[function(require,module,exports){
+"use strict";module.exports="undefined"!=typeof Reflect&&Reflect.getPrototypeOf||null;
+
+},{}],26:[function(require,module,exports){
+"use strict";var $Object=require(22);module.exports=$Object.getPrototypeOf||null;
+
+},{"22":22}],33:[function(require,module,exports){
+"use strict";var call=Function.prototype.call,$hasOwn=Object.prototype.hasOwnProperty,bind=require(24);module.exports=bind.call(call,$hasOwn);
+
+},{"24":24}],41:[function(require,module,exports){
+"use strict";var $isNaN=require(36);module.exports=function sign(i){return $isNaN(i)||0===i?i:i<0?-1:1};
+
+},{"36":36}],31:[function(require,module,exports){
+"use strict";var origSymbol="undefined"!=typeof Symbol&&Symbol,hasSymbolSham=require(32);module.exports=function hasNativeSymbols(){return"function"==typeof origSymbol&&"function"==typeof Symbol&&"symbol"==typeof origSymbol("foo")&&"symbol"==typeof Symbol("bar")&&hasSymbolSham()};
+
+},{"32":32}],28:[function(require,module,exports){
+"use strict";var reflectGetProto=require(27),originalGetProto=require(26),getDunderProto=require(13);module.exports=reflectGetProto?function getProto(t){return reflectGetProto(t)}:originalGetProto?function getProto(t){if(!t||"object"!=typeof t&&"function"!=typeof t)throw new TypeError("getProto: not an object");return originalGetProto(t)}:getDunderProto?function getProto(t){return getDunderProto(t)}:null;
+
+},{"13":13,"26":26,"27":27}],29:[function(require,module,exports){
+"use strict";module.exports=Object.getOwnPropertyDescriptor;
+
+},{}],32:[function(require,module,exports){
+"use strict";module.exports=function hasSymbols(){if("function"!=typeof Symbol||"function"!=typeof Object.getOwnPropertySymbols)return!1;if("symbol"==typeof Symbol.iterator)return!0;var t={},e=Symbol("test"),r=Object(e);if("string"==typeof e)return!1;if("[object Symbol]"!==Object.prototype.toString.call(e))return!1;if("[object Symbol]"!==Object.prototype.toString.call(r))return!1;for(var o in t[e]=42,t)return!1;if("function"==typeof Object.keys&&0!==Object.keys(t).length)return!1;if("function"==typeof Object.getOwnPropertyNames&&0!==Object.getOwnPropertyNames(t).length)return!1;var n=Object.getOwnPropertySymbols(t);if(1!==n.length||n[0]!==e)return!1;if(!Object.prototype.propertyIsEnumerable.call(t,e))return!1;if("function"==typeof Object.getOwnPropertyDescriptor){var y=Object.getOwnPropertyDescriptor(t,e);if(42!==y.value||!0!==y.enumerable)return!1}return!0};
+
+},{}],36:[function(require,module,exports){
+"use strict";module.exports=Number.isNaN||function isNaN(e){return e!=e};
+
+},{}],42:[function(require,module,exports){
+(function (global){(function (){
+var hasMap="function"==typeof Map&&Map.prototype,mapSizeDescriptor=Object.getOwnPropertyDescriptor&&hasMap?Object.getOwnPropertyDescriptor(Map.prototype,"size"):null,mapSize=hasMap&&mapSizeDescriptor&&"function"==typeof mapSizeDescriptor.get?mapSizeDescriptor.get:null,mapForEach=hasMap&&Map.prototype.forEach,hasSet="function"==typeof Set&&Set.prototype,setSizeDescriptor=Object.getOwnPropertyDescriptor&&hasSet?Object.getOwnPropertyDescriptor(Set.prototype,"size"):null,setSize=hasSet&&setSizeDescriptor&&"function"==typeof setSizeDescriptor.get?setSizeDescriptor.get:null,setForEach=hasSet&&Set.prototype.forEach,hasWeakMap="function"==typeof WeakMap&&WeakMap.prototype,weakMapHas=hasWeakMap?WeakMap.prototype.has:null,hasWeakSet="function"==typeof WeakSet&&WeakSet.prototype,weakSetHas=hasWeakSet?WeakSet.prototype.has:null,hasWeakRef="function"==typeof WeakRef&&WeakRef.prototype,weakRefDeref=hasWeakRef?WeakRef.prototype.deref:null,booleanValueOf=Boolean.prototype.valueOf,objectToString=Object.prototype.toString,functionToString=Function.prototype.toString,$match=String.prototype.match,$slice=String.prototype.slice,$replace=String.prototype.replace,$toUpperCase=String.prototype.toUpperCase,$toLowerCase=String.prototype.toLowerCase,$test=RegExp.prototype.test,$concat=Array.prototype.concat,$join=Array.prototype.join,$arrSlice=Array.prototype.slice,$floor=Math.floor,bigIntValueOf="function"==typeof BigInt?BigInt.prototype.valueOf:null,gOPS=Object.getOwnPropertySymbols,symToString="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?Symbol.prototype.toString:null,hasShammedSymbols="function"==typeof Symbol&&"object"==typeof Symbol.iterator,toStringTag="function"==typeof Symbol&&Symbol.toStringTag&&(Symbol.toStringTag,1)?Symbol.toStringTag:null,isEnumerable=Object.prototype.propertyIsEnumerable,gPO=("function"==typeof Reflect?Reflect.getPrototypeOf:Object.getPrototypeOf)||([].__proto__===Array.prototype?function(t){return t.__proto__}:null);function addNumericSeparator(t,e){if(t===1/0||t===-1/0||t!=t||t&&t>-1e3&&t<1e3||$test.call(/e/,e))return e;var r=/[0-9](?=(?:[0-9]{3})+(?![0-9]))/g;if("number"==typeof t){var n=t<0?-$floor(-t):$floor(t);if(n!==t){var o=String(n),i=$slice.call(e,o.length+1);return $replace.call(o,r,"$&_")+"."+$replace.call($replace.call(i,/([0-9]{3})/g,"$&_"),/_$/,"")}}return $replace.call(e,r,"$&_")}var utilInspect=require(6),inspectCustom=utilInspect.custom,inspectSymbol=isSymbol(inspectCustom)?inspectCustom:null,quotes={__proto__:null,double:'"',single:"'"},quoteREs={__proto__:null,double:/(["\\])/g,single:/(['\\])/g};function wrapQuotes(t,e,r){var n=r.quoteStyle||e,o=quotes[n];return o+t+o}function quote(t){return $replace.call(String(t),/"/g,"&quot;")}function canTrustToString(t){return!toStringTag||!("object"==typeof t&&(toStringTag in t||void 0!==t[toStringTag]))}function isArray(t){return"[object Array]"===toStr(t)&&canTrustToString(t)}function isDate(t){return"[object Date]"===toStr(t)&&canTrustToString(t)}function isRegExp(t){return"[object RegExp]"===toStr(t)&&canTrustToString(t)}function isError(t){return"[object Error]"===toStr(t)&&canTrustToString(t)}function isString(t){return"[object String]"===toStr(t)&&canTrustToString(t)}function isNumber(t){return"[object Number]"===toStr(t)&&canTrustToString(t)}function isBoolean(t){return"[object Boolean]"===toStr(t)&&canTrustToString(t)}function isSymbol(t){if(hasShammedSymbols)return t&&"object"==typeof t&&t instanceof Symbol;if("symbol"==typeof t)return!0;if(!t||"object"!=typeof t||!symToString)return!1;try{return symToString.call(t),!0}catch(t){}return!1}function isBigInt(t){if(!t||"object"!=typeof t||!bigIntValueOf)return!1;try{return bigIntValueOf.call(t),!0}catch(t){}return!1}module.exports=function inspect_(t,e,r,n){var o=e||{};if(has(o,"quoteStyle")&&!has(quotes,o.quoteStyle))throw new TypeError('option "quoteStyle" must be "single" or "double"');if(has(o,"maxStringLength")&&("number"==typeof o.maxStringLength?o.maxStringLength<0&&o.maxStringLength!==1/0:null!==o.maxStringLength))throw new TypeError('option "maxStringLength", if provided, must be a positive integer, Infinity, or `null`');var i=!has(o,"customInspect")||o.customInspect;if("boolean"!=typeof i&&"symbol"!==i)throw new TypeError("option \"customInspect\", if provided, must be `true`, `false`, or `'symbol'`");if(has(o,"indent")&&null!==o.indent&&"\t"!==o.indent&&!(parseInt(o.indent,10)===o.indent&&o.indent>0))throw new TypeError('option "indent" must be "\\t", an integer > 0, or `null`');if(has(o,"numericSeparator")&&"boolean"!=typeof o.numericSeparator)throw new TypeError('option "numericSeparator", if provided, must be `true` or `false`');var a=o.numericSeparator;if(void 0===t)return"undefined";if(null===t)return"null";if("boolean"==typeof t)return t?"true":"false";if("string"==typeof t)return inspectString(t,o);if("number"==typeof t){if(0===t)return 1/0/t>0?"0":"-0";var c=String(t);return a?addNumericSeparator(t,c):c}if("bigint"==typeof t){var l=String(t)+"n";return a?addNumericSeparator(t,l):l}var u=void 0===o.depth?5:o.depth;if(void 0===r&&(r=0),r>=u&&u>0&&"object"==typeof t)return isArray(t)?"[Array]":"[Object]";var p=getIndent(o,r);if(void 0===n)n=[];else if(indexOf(n,t)>=0)return"[Circular]";function inspect(t,e,i){if(e&&(n=$arrSlice.call(n)).push(e),i){var a={depth:o.depth};return has(o,"quoteStyle")&&(a.quoteStyle=o.quoteStyle),inspect_(t,a,r+1,n)}return inspect_(t,o,r+1,n)}if("function"==typeof t&&!isRegExp(t)){var s=nameOf(t),f=arrObjKeys(t,inspect);return"[Function"+(s?": "+s:" (anonymous)")+"]"+(f.length>0?" { "+$join.call(f,", ")+" }":"")}if(isSymbol(t)){var y=hasShammedSymbols?$replace.call(String(t),/^(Symbol\(.*\))_[^)]*$/,"$1"):symToString.call(t);return"object"!=typeof t||hasShammedSymbols?y:markBoxed(y)}if(isElement(t)){for(var S="<"+$toLowerCase.call(String(t.nodeName)),g=t.attributes||[],m=0;m<g.length;m++)S+=" "+g[m].name+"="+wrapQuotes(quote(g[m].value),"double",o);return S+=">",t.childNodes&&t.childNodes.length&&(S+="..."),S+"</"+$toLowerCase.call(String(t.nodeName))+">"}if(isArray(t)){if(0===t.length)return"[]";var b=arrObjKeys(t,inspect);return p&&!singleLineValues(b)?"["+indentedJoin(b,p)+"]":"[ "+$join.call(b,", ")+" ]"}if(isError(t)){var h=arrObjKeys(t,inspect);return"cause"in Error.prototype||!("cause"in t)||isEnumerable.call(t,"cause")?0===h.length?"["+String(t)+"]":"{ ["+String(t)+"] "+$join.call(h,", ")+" }":"{ ["+String(t)+"] "+$join.call($concat.call("[cause]: "+inspect(t.cause),h),", ")+" }"}if("object"==typeof t&&i){if(inspectSymbol&&"function"==typeof t[inspectSymbol]&&utilInspect)return utilInspect(t,{depth:u-r});if("symbol"!==i&&"function"==typeof t.inspect)return t.inspect()}if(isMap(t)){var d=[];return mapForEach&&mapForEach.call(t,function(e,r){d.push(inspect(r,t,!0)+" => "+inspect(e,t))}),collectionOf("Map",mapSize.call(t),d,p)}if(isSet(t)){var O=[];return setForEach&&setForEach.call(t,function(e){O.push(inspect(e,t))}),collectionOf("Set",setSize.call(t),O,p)}if(isWeakMap(t))return weakCollectionOf("WeakMap");if(isWeakSet(t))return weakCollectionOf("WeakSet");if(isWeakRef(t))return weakCollectionOf("WeakRef");if(isNumber(t))return markBoxed(inspect(Number(t)));if(isBigInt(t))return markBoxed(inspect(bigIntValueOf.call(t)));if(isBoolean(t))return markBoxed(booleanValueOf.call(t));if(isString(t))return markBoxed(inspect(String(t)));if("undefined"!=typeof window&&t===window)return"{ [object Window] }";if("undefined"!=typeof globalThis&&t===globalThis||"undefined"!=typeof global&&t===global)return"{ [object globalThis] }";if(!isDate(t)&&!isRegExp(t)){var j=arrObjKeys(t,inspect),w=gPO?gPO(t)===Object.prototype:t instanceof Object||t.constructor===Object,$=t instanceof Object?"":"null prototype",v=!w&&toStringTag&&Object(t)===t&&toStringTag in t?$slice.call(toStr(t),8,-1):$?"Object":"",k=(w||"function"!=typeof t.constructor?"":t.constructor.name?t.constructor.name+" ":"")+(v||$?"["+$join.call($concat.call([],v||[],$||[]),": ")+"] ":"");return 0===j.length?k+"{}":p?k+"{"+indentedJoin(j,p)+"}":k+"{ "+$join.call(j,", ")+" }"}return String(t)};var hasOwn=Object.prototype.hasOwnProperty||function(t){return t in this};function has(t,e){return hasOwn.call(t,e)}function toStr(t){return objectToString.call(t)}function nameOf(t){if(t.name)return t.name;var e=$match.call(functionToString.call(t),/^function\s*([\w$]+)/);return e?e[1]:null}function indexOf(t,e){if(t.indexOf)return t.indexOf(e);for(var r=0,n=t.length;r<n;r++)if(t[r]===e)return r;return-1}function isMap(t){if(!mapSize||!t||"object"!=typeof t)return!1;try{mapSize.call(t);try{setSize.call(t)}catch(t){return!0}return t instanceof Map}catch(t){}return!1}function isWeakMap(t){if(!weakMapHas||!t||"object"!=typeof t)return!1;try{weakMapHas.call(t,weakMapHas);try{weakSetHas.call(t,weakSetHas)}catch(t){return!0}return t instanceof WeakMap}catch(t){}return!1}function isWeakRef(t){if(!weakRefDeref||!t||"object"!=typeof t)return!1;try{return weakRefDeref.call(t),!0}catch(t){}return!1}function isSet(t){if(!setSize||!t||"object"!=typeof t)return!1;try{setSize.call(t);try{mapSize.call(t)}catch(t){return!0}return t instanceof Set}catch(t){}return!1}function isWeakSet(t){if(!weakSetHas||!t||"object"!=typeof t)return!1;try{weakSetHas.call(t,weakSetHas);try{weakMapHas.call(t,weakMapHas)}catch(t){return!0}return t instanceof WeakSet}catch(t){}return!1}function isElement(t){return!(!t||"object"!=typeof t)&&("undefined"!=typeof HTMLElement&&t instanceof HTMLElement||"string"==typeof t.nodeName&&"function"==typeof t.getAttribute)}function inspectString(t,e){if(t.length>e.maxStringLength){var r=t.length-e.maxStringLength,n="... "+r+" more character"+(r>1?"s":"");return inspectString($slice.call(t,0,e.maxStringLength),e)+n}var o=quoteREs[e.quoteStyle||"single"];return o.lastIndex=0,wrapQuotes($replace.call($replace.call(t,o,"\\$1"),/[\x00-\x1f]/g,lowbyte),"single",e)}function lowbyte(t){var e=t.charCodeAt(0),r={8:"b",9:"t",10:"n",12:"f",13:"r"}[e];return r?"\\"+r:"\\x"+(e<16?"0":"")+$toUpperCase.call(e.toString(16))}function markBoxed(t){return"Object("+t+")"}function weakCollectionOf(t){return t+" { ? }"}function collectionOf(t,e,r,n){return t+" ("+e+") {"+(n?indentedJoin(r,n):$join.call(r,", "))+"}"}function singleLineValues(t){for(var e=0;e<t.length;e++)if(indexOf(t[e],"\n")>=0)return!1;return!0}function getIndent(t,e){var r;if("\t"===t.indent)r="\t";else{if(!("number"==typeof t.indent&&t.indent>0))return null;r=$join.call(Array(t.indent+1)," ")}return{base:r,prev:$join.call(Array(e+1),r)}}function indentedJoin(t,e){if(0===t.length)return"";var r="\n"+e.prev+e.base;return r+$join.call(t,","+r)+"\n"+e.prev}function arrObjKeys(t,e){var r=isArray(t),n=[];if(r){n.length=t.length;for(var o=0;o<t.length;o++)n[o]=has(t,o)?e(t[o],t):""}var i,a="function"==typeof gOPS?gOPS(t):[];if(hasShammedSymbols){i={};for(var c=0;c<a.length;c++)i["$"+a[c]]=a[c]}for(var l in t)has(t,l)&&(r&&String(Number(l))===l&&l<t.length||hasShammedSymbols&&i["$"+l]instanceof Symbol||($test.call(/[^\w$]/,l)?n.push(e(l,t)+": "+e(t[l],t)):n.push(l+": "+e(t[l],t))));if("function"==typeof gOPS)for(var u=0;u<a.length;u++)isEnumerable.call(t,a[u])&&n.push("["+e(a[u])+"]: "+e(t[a[u]],t));return n}
+
+}).call(this)}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"6":6}],43:[function(require,module,exports){
+"use strict";var inspect=require(42),$TypeError=require(20),listGetNode=function(e,t,n){for(var i,r=e;null!=(i=r.next);r=i)if(i.key===t)return r.next=i.next,n||(i.next=e.next,e.next=i),i},listGet=function(e,t){if(e){var n=listGetNode(e,t);return n&&n.value}},listSet=function(e,t,n){var i=listGetNode(e,t);i?i.value=n:e.next={key:t,next:e.next,value:n}},listHas=function(e,t){return!!e&&!!listGetNode(e,t)},listDelete=function(e,t){if(e)return listGetNode(e,t,!0)};module.exports=function getSideChannelList(){var e,t={assert:function(e){if(!t.has(e))throw new $TypeError("Side channel does not contain "+inspect(e))},delete:function(t){var n=listDelete(e,t);return n&&e&&!e.next&&(e=void 0),!!n},get:function(t){return listGet(e,t)},has:function(t){return listHas(e,t)},set:function(t,n){e||(e={next:void 0}),listSet(e,t,n)}};return t};
+
+},{"20":20,"42":42}],44:[function(require,module,exports){
+"use strict";var GetIntrinsic=require(25),callBound=require(12),inspect=require(42),$TypeError=require(20),$Map=GetIntrinsic("%Map%",!0),$mapGet=callBound("Map.prototype.get",!0),$mapSet=callBound("Map.prototype.set",!0),$mapHas=callBound("Map.prototype.has",!0),$mapDelete=callBound("Map.prototype.delete",!0),$mapSize=callBound("Map.prototype.size",!0);module.exports=!!$Map&&function getSideChannelMap(){var e,t={assert:function(e){if(!t.has(e))throw new $TypeError("Side channel does not contain "+inspect(e))},delete:function(t){if(e){var n=$mapDelete(e,t);return 0===$mapSize(e)&&(e=void 0),n}return!1},get:function(t){if(e)return $mapGet(e,t)},has:function(t){return!!e&&$mapHas(e,t)},set:function(t,n){e||(e=new $Map),$mapSet(e,t,n)}};return t};
+
+},{"12":12,"20":20,"25":25,"42":42}],45:[function(require,module,exports){
+"use strict";var GetIntrinsic=require(25),callBound=require(12),inspect=require(42),getSideChannelMap=require(44),$TypeError=require(20),$WeakMap=GetIntrinsic("%WeakMap%",!0),$weakMapGet=callBound("WeakMap.prototype.get",!0),$weakMapSet=callBound("WeakMap.prototype.set",!0),$weakMapHas=callBound("WeakMap.prototype.has",!0),$weakMapDelete=callBound("WeakMap.prototype.delete",!0);module.exports=$WeakMap?function getSideChannelWeakMap(){var e,t,a={assert:function(e){if(!a.has(e))throw new $TypeError("Side channel does not contain "+inspect(e))},delete:function(a){if($WeakMap&&a&&("object"==typeof a||"function"==typeof a)){if(e)return $weakMapDelete(e,a)}else if(getSideChannelMap&&t)return t.delete(a);return!1},get:function(a){return $WeakMap&&a&&("object"==typeof a||"function"==typeof a)&&e?$weakMapGet(e,a):t&&t.get(a)},has:function(a){return $WeakMap&&a&&("object"==typeof a||"function"==typeof a)&&e?$weakMapHas(e,a):!!t&&t.has(a)},set:function(a,n){$WeakMap&&a&&("object"==typeof a||"function"==typeof a)?(e||(e=new $WeakMap),$weakMapSet(e,a,n)):getSideChannelMap&&(t||(t=getSideChannelMap()),t.set(a,n))}};return a}:getSideChannelMap;
+
+},{"12":12,"20":20,"25":25,"42":42,"44":44}]},{},[2])(2)
+});
diff --git a/node_modules/qs/eslint.config.mjs b/node_modules/qs/eslint.config.mjs
new file mode 100644
index 0000000..5c9fc1f
--- /dev/null
+++ b/node_modules/qs/eslint.config.mjs
@@ -0,0 +1,57 @@
+import ljharbConfig from '@ljharb/eslint-config/flat';
+
+export default [
+ {
+ ignores: ['dist/'],
+ },
+
+ ...ljharbConfig,
+
+ {
+ rules: {
+ complexity: 'off',
+ 'consistent-return': 'warn',
+ eqeqeq: ['error', 'allow-null'],
+ 'func-name-matching': 'off',
+ 'id-length': [
+ 'error',
+ {
+ max: 25,
+ min: 1,
+ properties: 'never',
+ },
+ ],
+ indent: ['error', 4],
+ 'max-lines': 'off',
+ 'max-lines-per-function': [
+ 'error',
+ { max: 150 },
+ ],
+ 'max-params': ['error', 18],
+ 'max-statements': ['error', 100],
+ 'multiline-comment-style': 'off',
+ 'no-continue': 'warn',
+ 'no-magic-numbers': 'off',
+ 'no-restricted-syntax': [
+ 'error',
+ 'BreakStatement',
+ 'DebuggerStatement',
+ 'ForInStatement',
+ 'LabeledStatement',
+ 'WithStatement',
+ ],
+ },
+ },
+
+ {
+ files: ['test/**'],
+ rules: {
+ 'function-paren-newline': 'off',
+ 'max-lines-per-function': 'off',
+ 'max-statements': 'off',
+ 'no-buffer-constructor': 'off',
+ 'no-extend-native': 'off',
+ 'no-throw-literal': 'off',
+ },
+ },
+];
diff --git a/node_modules/qs/lib/formats.js b/node_modules/qs/lib/formats.js
new file mode 100644
index 0000000..f36cf20
--- /dev/null
+++ b/node_modules/qs/lib/formats.js
@@ -0,0 +1,23 @@
+'use strict';
+
+var replace = String.prototype.replace;
+var percentTwenties = /%20/g;
+
+var Format = {
+ RFC1738: 'RFC1738',
+ RFC3986: 'RFC3986'
+};
+
+module.exports = {
+ 'default': Format.RFC3986,
+ formatters: {
+ RFC1738: function (value) {
+ return replace.call(value, percentTwenties, '+');
+ },
+ RFC3986: function (value) {
+ return String(value);
+ }
+ },
+ RFC1738: Format.RFC1738,
+ RFC3986: Format.RFC3986
+};
diff --git a/node_modules/qs/lib/index.js b/node_modules/qs/lib/index.js
new file mode 100644
index 0000000..0d6a97d
--- /dev/null
+++ b/node_modules/qs/lib/index.js
@@ -0,0 +1,11 @@
+'use strict';
+
+var stringify = require('./stringify');
+var parse = require('./parse');
+var formats = require('./formats');
+
+module.exports = {
+ formats: formats,
+ parse: parse,
+ stringify: stringify
+};
diff --git a/node_modules/qs/lib/parse.js b/node_modules/qs/lib/parse.js
new file mode 100644
index 0000000..0ccb70c
--- /dev/null
+++ b/node_modules/qs/lib/parse.js
@@ -0,0 +1,414 @@
+'use strict';
+
+var utils = require('./utils');
+
+var has = Object.prototype.hasOwnProperty;
+var isArray = Array.isArray;
+
+var defaults = {
+ allowDots: false,
+ allowEmptyArrays: false,
+ allowPrototypes: false,
+ allowSparse: false,
+ arrayLimit: 20,
+ charset: 'utf-8',
+ charsetSentinel: false,
+ comma: false,
+ decodeDotInKeys: false,
+ decoder: utils.decode,
+ delimiter: '&',
+ depth: 5,
+ duplicates: 'combine',
+ ignoreQueryPrefix: false,
+ interpretNumericEntities: false,
+ parameterLimit: 1000,
+ parseArrays: true,
+ plainObjects: false,
+ strictDepth: false,
+ strictMerge: true,
+ strictNullHandling: false,
+ throwOnLimitExceeded: false
+};
+
+var interpretNumericEntities = function (str) {
+ return str.replace(/&#(\d+);/g, function ($0, numberStr) {
+ return String.fromCharCode(parseInt(numberStr, 10));
+ });
+};
+
+var parseArrayValue = function (val, options, currentArrayLength, isFlatArrayValue) {
+ if (val && typeof val === 'string' && options.comma && val.indexOf(',') > -1) {
+ if (isFlatArrayValue && options.throwOnLimitExceeded) {
+ var commaCount = 0;
+ var commaIndex = val.indexOf(',');
+ while (commaIndex > -1) {
+ commaCount += 1;
+ if (commaCount >= options.arrayLimit) {
+ throw new RangeError('Array limit exceeded. Only ' + options.arrayLimit + ' element' + (options.arrayLimit === 1 ? '' : 's') + ' allowed in an array.');
+ }
+ commaIndex = val.indexOf(',', commaIndex + 1);
+ }
+ }
+ return val.split(',');
+ }
+
+ if (options.throwOnLimitExceeded && currentArrayLength >= options.arrayLimit) {
+ throw new RangeError('Array limit exceeded. Only ' + options.arrayLimit + ' element' + (options.arrayLimit === 1 ? '' : 's') + ' allowed in an array.');
+ }
+
+ return val;
+};
+
+// This is what browsers will submit when the ✓ character occurs in an
+// application/x-www-form-urlencoded body and the encoding of the page containing
+// the form is iso-8859-1, or when the submitted form has an accept-charset
+// attribute of iso-8859-1. Presumably also with other charsets that do not contain
+// the ✓ character, such as us-ascii.
+var isoSentinel = 'utf8=%26%2310003%3B'; // encodeURIComponent('&#10003;')
+
+// These are the percent-encoded utf-8 octets representing a checkmark, indicating that the request actually is utf-8 encoded.
+var charsetSentinel = 'utf8=%E2%9C%93'; // encodeURIComponent('✓')
+
+var parseValues = function parseQueryStringValues(str, options) {
+ var obj = { __proto__: null };
+
+ var cleanStr = options.ignoreQueryPrefix ? str.replace(/^\?/, '') : str;
+ cleanStr = cleanStr.replace(/%5B/gi, '[').replace(/%5D/gi, ']');
+
+ var limit = options.parameterLimit === Infinity ? void undefined : options.parameterLimit;
+ var parts = cleanStr.split(
+ options.delimiter,
+ options.throwOnLimitExceeded && typeof limit !== 'undefined' ? limit + 1 : limit
+ );
+
+ if (options.throwOnLimitExceeded && typeof limit !== 'undefined' && parts.length > limit) {
+ throw new RangeError('Parameter limit exceeded. Only ' + limit + ' parameter' + (limit === 1 ? '' : 's') + ' allowed.');
+ }
+
+ var skipIndex = -1; // Keep track of where the utf8 sentinel was found
+ var i;
+
+ var charset = options.charset;
+ if (options.charsetSentinel) {
+ for (i = 0; i < parts.length; ++i) {
+ if (parts[i].indexOf('utf8=') === 0) {
+ if (parts[i] === charsetSentinel) {
+ charset = 'utf-8';
+ } else if (parts[i] === isoSentinel) {
+ charset = 'iso-8859-1';
+ }
+ skipIndex = i;
+ i = parts.length; // The eslint settings do not allow break;
+ }
+ }
+ }
+
+ for (i = 0; i < parts.length; ++i) {
+ if (i === skipIndex) {
+ continue;
+ }
+ var part = parts[i];
+
+ var bracketEqualsPos = part.indexOf(']=');
+ var pos = bracketEqualsPos === -1 ? part.indexOf('=') : bracketEqualsPos + 1;
+
+ var key;
+ var val;
+ if (pos === -1) {
+ key = options.decoder(part, defaults.decoder, charset, 'key');
+ val = options.strictNullHandling ? null : '';
+ } else {
+ key = options.decoder(part.slice(0, pos), defaults.decoder, charset, 'key');
+
+ if (key !== null) {
+ val = utils.maybeMap(
+ parseArrayValue(
+ part.slice(pos + 1),
+ options,
+ isArray(obj[key]) ? obj[key].length : 0,
+ part.indexOf('[]=') === -1
+ ),
+ function (encodedVal) {
+ return options.decoder(encodedVal, defaults.decoder, charset, 'value');
+ }
+ );
+ }
+ }
+
+ if (val && options.interpretNumericEntities && charset === 'iso-8859-1') {
+ val = interpretNumericEntities(String(val));
+ }
+
+ if (part.indexOf('[]=') > -1) {
+ val = isArray(val) ? [val] : val;
+ }
+
+ if (options.comma && isArray(val) && val.length > options.arrayLimit) {
+ val = utils.combine([], val, options.arrayLimit, options.plainObjects, options.throwOnLimitExceeded);
+ }
+
+ if (key !== null) {
+ var existing = has.call(obj, key);
+ if (existing && (options.duplicates === 'combine' || part.indexOf('[]=') > -1)) {
+ obj[key] = utils.combine(
+ obj[key],
+ val,
+ options.arrayLimit,
+ options.plainObjects,
+ options.throwOnLimitExceeded
+ );
+ } else if (!existing || options.duplicates === 'last') {
+ obj[key] = val;
+ }
+ }
+ }
+
+ return obj;
+};
+
+var parseObject = function (chain, val, options, valuesParsed) {
+ var currentArrayLength = 0;
+ if (chain.length > 0 && chain[chain.length - 1] === '[]') {
+ var parentKey = chain.slice(0, -1).join('');
+ currentArrayLength = Array.isArray(val) && val[parentKey] ? val[parentKey].length : 0;
+ }
+
+ var leaf = valuesParsed ? val : parseArrayValue(val, options, currentArrayLength);
+
+ for (var i = chain.length - 1; i >= 0; --i) {
+ var obj;
+ var root = chain[i];
+
+ if (root === '[]' && options.parseArrays) {
+ if (utils.isOverflow(leaf)) {
+ // leaf is already an overflow object, preserve it
+ obj = leaf;
+ } else {
+ obj = options.allowEmptyArrays && (leaf === '' || (options.strictNullHandling && leaf === null))
+ ? []
+ : utils.combine(
+ [],
+ leaf,
+ options.arrayLimit,
+ options.plainObjects,
+ options.throwOnLimitExceeded
+ );
+ }
+ } else {
+ obj = options.plainObjects ? { __proto__: null } : {};
+ var cleanRoot = root.charAt(0) === '[' && root.charAt(root.length - 1) === ']' ? root.slice(1, -1) : root;
+ var decodedRoot = options.decodeDotInKeys ? cleanRoot.replace(/%2E/g, '.') : cleanRoot;
+ var index = parseInt(decodedRoot, 10);
+ var isValidArrayIndex = !isNaN(index)
+ && root !== decodedRoot
+ && String(index) === decodedRoot
+ && index >= 0
+ && options.parseArrays;
+ if (!options.parseArrays && decodedRoot === '') {
+ obj = { 0: leaf };
+ } else if (isValidArrayIndex && index < options.arrayLimit) {
+ obj = [];
+ obj[index] = leaf;
+ } else if (isValidArrayIndex && options.throwOnLimitExceeded) {
+ throw new RangeError('Array limit exceeded. Only ' + options.arrayLimit + ' element' + (options.arrayLimit === 1 ? '' : 's') + ' allowed in an array.');
+ } else if (isValidArrayIndex) {
+ obj[index] = leaf;
+ utils.markOverflow(obj, index);
+ } else if (decodedRoot !== '__proto__') {
+ obj[decodedRoot] = leaf;
+ }
+ }
+
+ leaf = obj;
+ }
+
+ return leaf;
+};
+
+// Split a key like "a[b][c[]]" into ['a', '[b]', '[c[]]'] while preserving
+// qs parse semantics for depth/prototype guards.
+var splitKeyIntoSegments = function splitKeyIntoSegments(originalKey, options) {
+ var key = options.allowDots ? originalKey.replace(/\.([^.[]+)/g, '[$1]') : originalKey;
+
+ // depth <= 0 keeps the whole key as one segment
+ if (options.depth <= 0) {
+ if (!options.plainObjects && has.call(Object.prototype, key)) {
+ if (!options.allowPrototypes) {
+ return;
+ }
+ }
+
+ return [key];
+ }
+
+ var segments = [];
+
+ // parent before the first '[' (may be empty if key starts with '[')
+ var first = key.indexOf('[');
+ var parent = first >= 0 ? key.slice(0, first) : key;
+ if (parent) {
+ if (!options.plainObjects && has.call(Object.prototype, parent)) {
+ if (!options.allowPrototypes) {
+ return;
+ }
+ }
+
+ segments[segments.length] = parent;
+ }
+
+ var n = key.length;
+ var open = first;
+ var collected = 0;
+
+ while (open >= 0 && collected < options.depth) {
+ var level = 1;
+ var i = open + 1;
+ var close = -1;
+
+ // balance nested '[' and ']' inside this bracket group using a nesting level counter
+ while (i < n && close < 0) {
+ var cu = key.charCodeAt(i);
+ if (cu === 0x5B) { // '['
+ level += 1;
+ } else if (cu === 0x5D) { // ']'
+ level -= 1;
+ if (level === 0) {
+ close = i; // found matching close; loop will exit by condition
+ }
+ }
+ i += 1;
+ }
+
+ if (close < 0) {
+ // Unterminated group: wrap the raw remainder in one bracket pair so it stays
+ // a single literal segment (e.g. "[[]b" -> "[[]b]"); we do not infer missing ']'.
+ segments[segments.length] = '[' + key.slice(open) + ']';
+ return segments;
+ }
+
+ var seg = key.slice(open, close + 1);
+ // prototype guard for the content of this group
+ var content = seg.slice(1, -1);
+ if (!options.plainObjects && has.call(Object.prototype, content) && !options.allowPrototypes) {
+ return;
+ }
+
+ segments[segments.length] = seg;
+ collected += 1;
+
+ // find the next '[' after this balanced group
+ open = key.indexOf('[', close + 1);
+ }
+
+ if (open >= 0) {
+ if (options.strictDepth === true) {
+ throw new RangeError('Input depth exceeded depth option of ' + options.depth + ' and strictDepth is true');
+ }
+
+ segments[segments.length] = '[' + key.slice(open) + ']';
+ }
+
+ return segments;
+};
+
+var parseKeys = function parseQueryStringKeys(givenKey, val, options, valuesParsed) {
+ if (!givenKey) {
+ return;
+ }
+
+ var keys = splitKeyIntoSegments(givenKey, options);
+
+ if (!keys) {
+ return;
+ }
+
+ return parseObject(keys, val, options, valuesParsed);
+};
+
+var normalizeParseOptions = function normalizeParseOptions(opts) {
+ if (!opts) {
+ return defaults;
+ }
+
+ if (typeof opts.allowEmptyArrays !== 'undefined' && typeof opts.allowEmptyArrays !== 'boolean') {
+ throw new TypeError('`allowEmptyArrays` option can only be `true` or `false`, when provided');
+ }
+
+ if (typeof opts.decodeDotInKeys !== 'undefined' && typeof opts.decodeDotInKeys !== 'boolean') {
+ throw new TypeError('`decodeDotInKeys` option can only be `true` or `false`, when provided');
+ }
+
+ if (opts.decoder !== null && typeof opts.decoder !== 'undefined' && typeof opts.decoder !== 'function') {
+ throw new TypeError('Decoder has to be a function.');
+ }
+
+ if (typeof opts.charset !== 'undefined' && opts.charset !== 'utf-8' && opts.charset !== 'iso-8859-1') {
+ throw new TypeError('The charset option must be either utf-8, iso-8859-1, or undefined');
+ }
+
+ if (typeof opts.throwOnLimitExceeded !== 'undefined' && typeof opts.throwOnLimitExceeded !== 'boolean') {
+ throw new TypeError('`throwOnLimitExceeded` option must be a boolean');
+ }
+
+ var charset = typeof opts.charset === 'undefined' ? defaults.charset : opts.charset;
+
+ var duplicates = typeof opts.duplicates === 'undefined' ? defaults.duplicates : opts.duplicates;
+
+ if (duplicates !== 'combine' && duplicates !== 'first' && duplicates !== 'last') {
+ throw new TypeError('The duplicates option must be either combine, first, or last');
+ }
+
+ var allowDots = typeof opts.allowDots === 'undefined' ? opts.decodeDotInKeys === true ? true : defaults.allowDots : !!opts.allowDots;
+
+ return {
+ allowDots: allowDots,
+ allowEmptyArrays: typeof opts.allowEmptyArrays === 'boolean' ? !!opts.allowEmptyArrays : defaults.allowEmptyArrays,
+ allowPrototypes: typeof opts.allowPrototypes === 'boolean' ? opts.allowPrototypes : defaults.allowPrototypes,
+ allowSparse: typeof opts.allowSparse === 'boolean' ? opts.allowSparse : defaults.allowSparse,
+ arrayLimit: typeof opts.arrayLimit === 'number' ? opts.arrayLimit : defaults.arrayLimit,
+ charset: charset,
+ charsetSentinel: typeof opts.charsetSentinel === 'boolean' ? opts.charsetSentinel : defaults.charsetSentinel,
+ comma: typeof opts.comma === 'boolean' ? opts.comma : defaults.comma,
+ decodeDotInKeys: typeof opts.decodeDotInKeys === 'boolean' ? opts.decodeDotInKeys : defaults.decodeDotInKeys,
+ decoder: typeof opts.decoder === 'function' ? opts.decoder : defaults.decoder,
+ delimiter: typeof opts.delimiter === 'string' || utils.isRegExp(opts.delimiter) ? opts.delimiter : defaults.delimiter,
+ // eslint-disable-next-line no-implicit-coercion, no-extra-parens
+ depth: (typeof opts.depth === 'number' || opts.depth === false) ? +opts.depth : defaults.depth,
+ duplicates: duplicates,
+ ignoreQueryPrefix: opts.ignoreQueryPrefix === true,
+ interpretNumericEntities: typeof opts.interpretNumericEntities === 'boolean' ? opts.interpretNumericEntities : defaults.interpretNumericEntities,
+ parameterLimit: typeof opts.parameterLimit === 'number' ? opts.parameterLimit : defaults.parameterLimit,
+ parseArrays: opts.parseArrays !== false,
+ plainObjects: typeof opts.plainObjects === 'boolean' ? opts.plainObjects : defaults.plainObjects,
+ strictDepth: typeof opts.strictDepth === 'boolean' ? !!opts.strictDepth : defaults.strictDepth,
+ strictMerge: typeof opts.strictMerge === 'boolean' ? !!opts.strictMerge : defaults.strictMerge,
+ strictNullHandling: typeof opts.strictNullHandling === 'boolean' ? opts.strictNullHandling : defaults.strictNullHandling,
+ throwOnLimitExceeded: typeof opts.throwOnLimitExceeded === 'boolean' ? opts.throwOnLimitExceeded : false
+ };
+};
+
+module.exports = function (str, opts) {
+ var options = normalizeParseOptions(opts);
+
+ if (str === '' || str === null || typeof str === 'undefined') {
+ return options.plainObjects ? { __proto__: null } : {};
+ }
+
+ var tempObj = typeof str === 'string' ? parseValues(str, options) : str;
+ var obj = options.plainObjects ? { __proto__: null } : {};
+
+ // Iterate over the keys and setup the new object
+
+ var keys = Object.keys(tempObj);
+ for (var i = 0; i < keys.length; ++i) {
+ var key = keys[i];
+ var newObj = parseKeys(key, tempObj[key], options, typeof str === 'string');
+ obj = utils.merge(obj, newObj, options);
+ }
+
+ if (options.allowSparse === true) {
+ return obj;
+ }
+
+ return utils.compact(obj);
+};
diff --git a/node_modules/qs/lib/stringify.js b/node_modules/qs/lib/stringify.js
new file mode 100644
index 0000000..b661691
--- /dev/null
+++ b/node_modules/qs/lib/stringify.js
@@ -0,0 +1,363 @@
+'use strict';
+
+var getSideChannel = require('side-channel');
+var utils = require('./utils');
+var formats = require('./formats');
+var has = Object.prototype.hasOwnProperty;
+
+var arrayPrefixGenerators = {
+ brackets: function brackets(prefix) {
+ return prefix + '[]';
+ },
+ comma: 'comma',
+ indices: function indices(prefix, key) {
+ return prefix + '[' + key + ']';
+ },
+ repeat: function repeat(prefix) {
+ return prefix;
+ }
+};
+
+var isArray = Array.isArray;
+var push = Array.prototype.push;
+var pushToArray = function (arr, valueOrArray) {
+ push.apply(arr, isArray(valueOrArray) ? valueOrArray : [valueOrArray]);
+};
+
+var toISO = Date.prototype.toISOString;
+
+var defaultFormat = formats['default'];
+var defaults = {
+ addQueryPrefix: false,
+ allowDots: false,
+ allowEmptyArrays: false,
+ arrayFormat: 'indices',
+ charset: 'utf-8',
+ charsetSentinel: false,
+ commaRoundTrip: false,
+ delimiter: '&',
+ encode: true,
+ encodeDotInKeys: false,
+ encoder: utils.encode,
+ encodeValuesOnly: false,
+ filter: void undefined,
+ format: defaultFormat,
+ formatter: formats.formatters[defaultFormat],
+ // deprecated
+ indices: false,
+ serializeDate: function serializeDate(date) {
+ return toISO.call(date);
+ },
+ skipNulls: false,
+ strictNullHandling: false
+};
+
+var isNonNullishPrimitive = function isNonNullishPrimitive(v) {
+ return typeof v === 'string'
+ || typeof v === 'number'
+ || typeof v === 'boolean'
+ || typeof v === 'symbol'
+ || typeof v === 'bigint';
+};
+
+var sentinel = {};
+
+var stringify = function stringify(
+ object,
+ prefix,
+ generateArrayPrefix,
+ commaRoundTrip,
+ allowEmptyArrays,
+ strictNullHandling,
+ skipNulls,
+ encodeDotInKeys,
+ encoder,
+ filter,
+ sort,
+ allowDots,
+ serializeDate,
+ format,
+ formatter,
+ encodeValuesOnly,
+ charset,
+ sideChannel
+) {
+ var obj = object;
+
+ var tmpSc = sideChannel;
+ var step = 0;
+ var findFlag = false;
+ while ((tmpSc = tmpSc.get(sentinel)) !== void undefined && !findFlag) {
+ // Where object last appeared in the ref tree
+ var pos = tmpSc.get(object);
+ step += 1;
+ if (typeof pos !== 'undefined') {
+ if (pos === step) {
+ throw new RangeError('Cyclic object value');
+ } else {
+ findFlag = true; // Break while
+ }
+ }
+ if (typeof tmpSc.get(sentinel) === 'undefined') {
+ step = 0;
+ }
+ }
+
+ if (typeof filter === 'function') {
+ obj = filter(prefix, obj);
+ } else if (obj instanceof Date) {
+ obj = serializeDate(obj);
+ } else if (generateArrayPrefix === 'comma' && isArray(obj)) {
+ obj = utils.maybeMap(obj, function (value) {
+ if (value instanceof Date) {
+ return serializeDate(value);
+ }
+ return value;
+ });
+ }
+
+ if (obj === null) {
+ if (strictNullHandling) {
+ return formatter(encoder && !encodeValuesOnly ? encoder(prefix, defaults.encoder, charset, 'key', format) : prefix);
+ }
+
+ obj = '';
+ }
+
+ if (isNonNullishPrimitive(obj) || utils.isBuffer(obj)) {
+ if (encoder) {
+ var keyValue = encodeValuesOnly ? prefix : encoder(prefix, defaults.encoder, charset, 'key', format);
+ return [formatter(keyValue) + '=' + formatter(encoder(obj, defaults.encoder, charset, 'value', format))];
+ }
+ return [formatter(prefix) + '=' + formatter(String(obj))];
+ }
+
+ var values = [];
+
+ if (typeof obj === 'undefined') {
+ return values;
+ }
+
+ var objKeys;
+ if (generateArrayPrefix === 'comma' && isArray(obj)) {
+ // we need to join elements in
+ if (encodeValuesOnly && encoder) {
+ obj = utils.maybeMap(obj, function (v) {
+ return v == null ? v : encoder(v);
+ });
+ }
+ objKeys = [{ value: obj.length > 0 ? obj.join(',') || null : void undefined }];
+ } else if (isArray(filter)) {
+ objKeys = filter;
+ } else {
+ var keys = Object.keys(obj);
+ objKeys = sort ? keys.sort(sort) : keys;
+ }
+
+ var encodedPrefix = encodeDotInKeys ? String(prefix).replace(/\./g, '%2E') : String(prefix);
+
+ var adjustedPrefix = commaRoundTrip && isArray(obj) && obj.length === 1 ? encodedPrefix + '[]' : encodedPrefix;
+
+ if (allowEmptyArrays && isArray(obj) && obj.length === 0) {
+ return adjustedPrefix + '[]';
+ }
+
+ for (var j = 0; j < objKeys.length; ++j) {
+ var key = objKeys[j];
+ var value = typeof key === 'object' && key && typeof key.value !== 'undefined'
+ ? key.value
+ : obj[key];
+
+ if (skipNulls && value === null) {
+ continue;
+ }
+
+ var encodedKey = allowDots && encodeDotInKeys ? String(key).replace(/\./g, '%2E') : String(key);
+ var keyPrefix = isArray(obj)
+ ? typeof generateArrayPrefix === 'function' ? generateArrayPrefix(adjustedPrefix, encodedKey) : adjustedPrefix
+ : adjustedPrefix + (allowDots ? '.' + encodedKey : '[' + encodedKey + ']');
+
+ sideChannel.set(object, step);
+ var valueSideChannel = getSideChannel();
+ valueSideChannel.set(sentinel, sideChannel);
+ pushToArray(values, stringify(
+ value,
+ keyPrefix,
+ generateArrayPrefix,
+ commaRoundTrip,
+ allowEmptyArrays,
+ strictNullHandling,
+ skipNulls,
+ encodeDotInKeys,
+ generateArrayPrefix === 'comma' && encodeValuesOnly && isArray(obj) ? null : encoder,
+ filter,
+ sort,
+ allowDots,
+ serializeDate,
+ format,
+ formatter,
+ encodeValuesOnly,
+ charset,
+ valueSideChannel
+ ));
+ }
+
+ return values;
+};
+
+var normalizeStringifyOptions = function normalizeStringifyOptions(opts) {
+ if (!opts) {
+ return defaults;
+ }
+
+ if (typeof opts.allowEmptyArrays !== 'undefined' && typeof opts.allowEmptyArrays !== 'boolean') {
+ throw new TypeError('`allowEmptyArrays` option can only be `true` or `false`, when provided');
+ }
+
+ if (typeof opts.encodeDotInKeys !== 'undefined' && typeof opts.encodeDotInKeys !== 'boolean') {
+ throw new TypeError('`encodeDotInKeys` option can only be `true` or `false`, when provided');
+ }
+
+ if (opts.encoder !== null && typeof opts.encoder !== 'undefined' && typeof opts.encoder !== 'function') {
+ throw new TypeError('Encoder has to be a function.');
+ }
+
+ var charset = opts.charset || defaults.charset;
+ if (typeof opts.charset !== 'undefined' && opts.charset !== 'utf-8' && opts.charset !== 'iso-8859-1') {
+ throw new TypeError('The charset option must be either utf-8, iso-8859-1, or undefined');
+ }
+
+ var format = formats['default'];
+ if (typeof opts.format !== 'undefined') {
+ if (!has.call(formats.formatters, opts.format)) {
+ throw new TypeError('Unknown format option provided.');
+ }
+ format = opts.format;
+ }
+ var formatter = formats.formatters[format];
+
+ var filter = defaults.filter;
+ if (typeof opts.filter === 'function' || isArray(opts.filter)) {
+ filter = opts.filter;
+ }
+
+ var arrayFormat;
+ if (opts.arrayFormat in arrayPrefixGenerators) {
+ arrayFormat = opts.arrayFormat;
+ } else if ('indices' in opts) {
+ arrayFormat = opts.indices ? 'indices' : 'repeat';
+ } else {
+ arrayFormat = defaults.arrayFormat;
+ }
+
+ if ('commaRoundTrip' in opts && typeof opts.commaRoundTrip !== 'boolean') {
+ throw new TypeError('`commaRoundTrip` must be a boolean, or absent');
+ }
+
+ var allowDots = typeof opts.allowDots === 'undefined' ? opts.encodeDotInKeys === true ? true : defaults.allowDots : !!opts.allowDots;
+
+ return {
+ addQueryPrefix: typeof opts.addQueryPrefix === 'boolean' ? opts.addQueryPrefix : defaults.addQueryPrefix,
+ allowDots: allowDots,
+ allowEmptyArrays: typeof opts.allowEmptyArrays === 'boolean' ? !!opts.allowEmptyArrays : defaults.allowEmptyArrays,
+ arrayFormat: arrayFormat,
+ charset: charset,
+ charsetSentinel: typeof opts.charsetSentinel === 'boolean' ? opts.charsetSentinel : defaults.charsetSentinel,
+ commaRoundTrip: !!opts.commaRoundTrip,
+ delimiter: typeof opts.delimiter === 'undefined' ? defaults.delimiter : opts.delimiter,
+ encode: typeof opts.encode === 'boolean' ? opts.encode : defaults.encode,
+ encodeDotInKeys: typeof opts.encodeDotInKeys === 'boolean' ? opts.encodeDotInKeys : defaults.encodeDotInKeys,
+ encoder: typeof opts.encoder === 'function' ? opts.encoder : defaults.encoder,
+ encodeValuesOnly: typeof opts.encodeValuesOnly === 'boolean' ? opts.encodeValuesOnly : defaults.encodeValuesOnly,
+ filter: filter,
+ format: format,
+ formatter: formatter,
+ serializeDate: typeof opts.serializeDate === 'function' ? opts.serializeDate : defaults.serializeDate,
+ skipNulls: typeof opts.skipNulls === 'boolean' ? opts.skipNulls : defaults.skipNulls,
+ sort: typeof opts.sort === 'function' ? opts.sort : null,
+ strictNullHandling: typeof opts.strictNullHandling === 'boolean' ? opts.strictNullHandling : defaults.strictNullHandling
+ };
+};
+
+module.exports = function (object, opts) {
+ var obj = object;
+ var options = normalizeStringifyOptions(opts);
+
+ var objKeys;
+ var filter;
+
+ if (typeof options.filter === 'function') {
+ filter = options.filter;
+ obj = filter('', obj);
+ } else if (isArray(options.filter)) {
+ filter = options.filter;
+ objKeys = filter;
+ }
+
+ var keys = [];
+
+ if (typeof obj !== 'object' || obj === null) {
+ return '';
+ }
+
+ var generateArrayPrefix = arrayPrefixGenerators[options.arrayFormat];
+ var commaRoundTrip = generateArrayPrefix === 'comma' && options.commaRoundTrip;
+
+ if (!objKeys) {
+ objKeys = Object.keys(obj);
+ }
+
+ if (options.sort) {
+ objKeys.sort(options.sort);
+ }
+
+ var sideChannel = getSideChannel();
+ for (var i = 0; i < objKeys.length; ++i) {
+ var key = objKeys[i];
+
+ if (typeof key === 'undefined' || key === null) {
+ continue;
+ }
+
+ var value = obj[key];
+
+ if (options.skipNulls && value === null) {
+ continue;
+ }
+ pushToArray(keys, stringify(
+ value,
+ key,
+ generateArrayPrefix,
+ commaRoundTrip,
+ options.allowEmptyArrays,
+ options.strictNullHandling,
+ options.skipNulls,
+ options.encodeDotInKeys,
+ options.encode ? options.encoder : null,
+ options.filter,
+ options.sort,
+ options.allowDots,
+ options.serializeDate,
+ options.format,
+ options.formatter,
+ options.encodeValuesOnly,
+ options.charset,
+ sideChannel
+ ));
+ }
+
+ var joined = keys.join(options.delimiter);
+ var prefix = options.addQueryPrefix === true ? '?' : '';
+
+ if (options.charsetSentinel) {
+ if (options.charset === 'iso-8859-1') {
+ // encodeURIComponent('&#10003;'), the "numeric entity" representation of a checkmark
+ prefix += 'utf8=%26%2310003%3B' + options.delimiter;
+ } else {
+ // encodeURIComponent('✓')
+ prefix += 'utf8=%E2%9C%93' + options.delimiter;
+ }
+ }
+
+ return joined.length > 0 ? prefix + joined : '';
+};
diff --git a/node_modules/qs/lib/utils.js b/node_modules/qs/lib/utils.js
new file mode 100644
index 0000000..6fce590
--- /dev/null
+++ b/node_modules/qs/lib/utils.js
@@ -0,0 +1,381 @@
+'use strict';
+
+var formats = require('./formats');
+var getSideChannel = require('side-channel');
+var defineProperty = require('es-define-property');
+
+var has = Object.prototype.hasOwnProperty;
+var isArray = Array.isArray;
+
+// Track objects created from arrayLimit overflow using side-channel
+// Stores the current max numeric index for O(1) lookup
+var overflowChannel = getSideChannel();
+
+var markOverflow = function markOverflow(obj, maxIndex) {
+ overflowChannel.set(obj, maxIndex);
+ return obj;
+};
+
+var isOverflow = function isOverflow(obj) {
+ return overflowChannel.has(obj);
+};
+
+var getMaxIndex = function getMaxIndex(obj) {
+ return overflowChannel.get(obj);
+};
+
+var setMaxIndex = function setMaxIndex(obj, maxIndex) {
+ overflowChannel.set(obj, maxIndex);
+};
+
+var hexTable = (function () {
+ var array = [];
+ for (var i = 0; i < 256; ++i) {
+ array[array.length] = '%' + ((i < 16 ? '0' : '') + i.toString(16)).toUpperCase();
+ }
+
+ return array;
+}());
+
+var compactQueue = function compactQueue(queue) {
+ while (queue.length > 1) {
+ var item = queue.pop();
+ var obj = item.obj[item.prop];
+
+ if (isArray(obj)) {
+ var compacted = [];
+
+ for (var j = 0; j < obj.length; ++j) {
+ if (typeof obj[j] !== 'undefined') {
+ compacted[compacted.length] = obj[j];
+ }
+ }
+
+ item.obj[item.prop] = compacted;
+ }
+ }
+};
+
+var arrayToObject = function arrayToObject(source, options) {
+ var obj = options && options.plainObjects ? { __proto__: null } : {};
+ for (var i = 0; i < source.length; ++i) {
+ if (typeof source[i] !== 'undefined') {
+ obj[i] = source[i];
+ }
+ }
+
+ return obj;
+};
+
+var setProperty = function setProperty(obj, key, value) {
+ if (key === '__proto__' && defineProperty) {
+ defineProperty(obj, key, {
+ configurable: true,
+ enumerable: true,
+ value: value,
+ writable: true
+ });
+ } else {
+ obj[key] = value;
+ }
+};
+
+var merge = function merge(target, source, options) {
+ /* eslint no-param-reassign: 0 */
+ if (!source) {
+ return target;
+ }
+
+ if (typeof source !== 'object' && typeof source !== 'function') {
+ if (isArray(target)) {
+ var nextIndex = target.length;
+ if (options && typeof options.arrayLimit === 'number' && nextIndex >= options.arrayLimit) {
+ if (options.throwOnLimitExceeded) {
+ throw new RangeError('Array limit exceeded. Only ' + options.arrayLimit + ' element' + (options.arrayLimit === 1 ? '' : 's') + ' allowed in an array.');
+ }
+ return markOverflow(arrayToObject(target.concat(source), options), nextIndex);
+ }
+ target[nextIndex] = source;
+ } else if (target && typeof target === 'object') {
+ if (isOverflow(target)) {
+ // Add at next numeric index for overflow objects
+ var newIndex = getMaxIndex(target) + 1;
+ target[newIndex] = source;
+ setMaxIndex(target, newIndex);
+ } else if (options && options.strictMerge) {
+ return [target, source];
+ } else if (
+ (options && (options.plainObjects || options.allowPrototypes))
+ || !has.call(Object.prototype, source)
+ ) {
+ target[source] = true;
+ }
+ } else {
+ return [target, source];
+ }
+
+ return target;
+ }
+
+ if (!target || typeof target !== 'object') {
+ if (isOverflow(source)) {
+ // Create new object with target at 0, source values shifted by 1
+ var sourceKeys = Object.keys(source);
+ var result = options && options.plainObjects
+ ? { __proto__: null, 0: target }
+ : { 0: target };
+ for (var m = 0; m < sourceKeys.length; m++) {
+ var oldKey = parseInt(sourceKeys[m], 10);
+ result[oldKey + 1] = source[sourceKeys[m]];
+ }
+ return markOverflow(result, getMaxIndex(source) + 1);
+ }
+ var combined = [target].concat(source);
+ if (options && typeof options.arrayLimit === 'number' && combined.length > options.arrayLimit) {
+ if (options.throwOnLimitExceeded) {
+ throw new RangeError('Array limit exceeded. Only ' + options.arrayLimit + ' element' + (options.arrayLimit === 1 ? '' : 's') + ' allowed in an array.');
+ }
+ return markOverflow(arrayToObject(combined, options), combined.length - 1);
+ }
+ return combined;
+ }
+
+ var mergeTarget = target;
+ if (isArray(target) && !isArray(source)) {
+ mergeTarget = arrayToObject(target, options);
+ }
+
+ if (isArray(target) && isArray(source)) {
+ source.forEach(function (item, i) {
+ if (has.call(target, i)) {
+ var targetItem = target[i];
+ if (targetItem && typeof targetItem === 'object' && item && typeof item === 'object') {
+ target[i] = merge(targetItem, item, options);
+ } else {
+ target[target.length] = item;
+ }
+ } else {
+ target[i] = item;
+ }
+ });
+ if (options && typeof options.arrayLimit === 'number' && target.length > options.arrayLimit) {
+ if (options.throwOnLimitExceeded) {
+ throw new RangeError('Array limit exceeded. Only ' + options.arrayLimit + ' element' + (options.arrayLimit === 1 ? '' : 's') + ' allowed in an array.');
+ }
+ return markOverflow(arrayToObject(target, options), target.length - 1);
+ }
+ return target;
+ }
+
+ return Object.keys(source).reduce(function (acc, key) {
+ var value = source[key];
+
+ if (has.call(acc, key)) {
+ setProperty(acc, key, merge(acc[key], value, options));
+ } else {
+ setProperty(acc, key, value);
+ }
+
+ if (isOverflow(source) && !isOverflow(acc)) {
+ markOverflow(acc, getMaxIndex(source));
+ }
+ if (isOverflow(acc)) {
+ var keyNum = parseInt(key, 10);
+ if (String(keyNum) === key && keyNum >= 0 && keyNum > getMaxIndex(acc)) {
+ setMaxIndex(acc, keyNum);
+ }
+ }
+
+ return acc;
+ }, mergeTarget);
+};
+
+var assign = function assignSingleSource(target, source) {
+ return Object.keys(source).reduce(function (acc, key) {
+ setProperty(acc, key, source[key]);
+ return acc;
+ }, target);
+};
+
+var decode = function (str, defaultDecoder, charset) {
+ var strWithoutPlus = str.replace(/\+/g, ' ');
+ if (charset === 'iso-8859-1') {
+ // unescape never throws, no try...catch needed:
+ return strWithoutPlus.replace(/%[0-9a-f]{2}/gi, unescape);
+ }
+ // utf-8
+ try {
+ return decodeURIComponent(strWithoutPlus);
+ } catch (e) {
+ return strWithoutPlus;
+ }
+};
+
+var limit = 1024;
+
+/* eslint operator-linebreak: [2, "before"] */
+
+var encode = function encode(str, defaultEncoder, charset, kind, format) {
+ // This code was originally written by Brian White (mscdex) for the io.js core querystring library.
+ // It has been adapted here for stricter adherence to RFC 3986
+ if (str.length === 0) {
+ return str;
+ }
+
+ var string = str;
+ if (typeof str === 'symbol') {
+ string = Symbol.prototype.toString.call(str);
+ } else if (typeof str !== 'string') {
+ string = String(str);
+ }
+
+ if (charset === 'iso-8859-1') {
+ return escape(string).replace(/%u[0-9a-f]{4}/gi, function ($0) {
+ return '%26%23' + parseInt($0.slice(2), 16) + '%3B';
+ });
+ }
+
+ var out = '';
+ for (var j = 0; j < string.length; j += limit) {
+ var segment = string.length >= limit ? string.slice(j, j + limit) : string;
+ if (j + limit < string.length) {
+ var last = segment.charCodeAt(segment.length - 1);
+ if (last >= 0xD800 && last <= 0xDBFF) {
+ segment = segment.slice(0, -1);
+ j -= 1;
+ }
+ }
+ var arr = [];
+
+ for (var i = 0; i < segment.length; ++i) {
+ var c = segment.charCodeAt(i);
+ if (
+ c === 0x2D // -
+ || c === 0x2E // .
+ || c === 0x5F // _
+ || c === 0x7E // ~
+ || (c >= 0x30 && c <= 0x39) // 0-9
+ || (c >= 0x41 && c <= 0x5A) // a-z
+ || (c >= 0x61 && c <= 0x7A) // A-Z
+ || (format === formats.RFC1738 && (c === 0x28 || c === 0x29)) // ( )
+ ) {
+ arr[arr.length] = segment.charAt(i);
+ continue;
+ }
+
+ if (c < 0x80) {
+ arr[arr.length] = hexTable[c];
+ continue;
+ }
+
+ if (c < 0x800) {
+ arr[arr.length] = hexTable[0xC0 | (c >> 6)]
+ + hexTable[0x80 | (c & 0x3F)];
+ continue;
+ }
+
+ if (c < 0xD800 || c >= 0xE000) {
+ arr[arr.length] = hexTable[0xE0 | (c >> 12)]
+ + hexTable[0x80 | ((c >> 6) & 0x3F)]
+ + hexTable[0x80 | (c & 0x3F)];
+ continue;
+ }
+
+ i += 1;
+ c = 0x10000 + (((c & 0x3FF) << 10) | (segment.charCodeAt(i) & 0x3FF));
+
+ arr[arr.length] = hexTable[0xF0 | (c >> 18)]
+ + hexTable[0x80 | ((c >> 12) & 0x3F)]
+ + hexTable[0x80 | ((c >> 6) & 0x3F)]
+ + hexTable[0x80 | (c & 0x3F)];
+ }
+
+ out += arr.join('');
+ }
+
+ return out;
+};
+
+var compact = function compact(value) {
+ var queue = [{ obj: { o: value }, prop: 'o' }];
+ var refs = getSideChannel();
+
+ for (var i = 0; i < queue.length; ++i) {
+ var item = queue[i];
+ var obj = item.obj[item.prop];
+
+ var keys = Object.keys(obj);
+ for (var j = 0; j < keys.length; ++j) {
+ var key = keys[j];
+ var val = obj[key];
+ if (typeof val === 'object' && val !== null && !refs.has(val)) {
+ queue[queue.length] = { obj: obj, prop: key };
+ refs.set(val, true);
+ }
+ }
+ }
+
+ compactQueue(queue);
+
+ return value;
+};
+
+var isRegExp = function isRegExp(obj) {
+ return Object.prototype.toString.call(obj) === '[object RegExp]';
+};
+
+var isBuffer = function isBuffer(obj) {
+ if (!obj || typeof obj !== 'object') {
+ return false;
+ }
+
+ return !!(obj.constructor && obj.constructor.isBuffer && obj.constructor.isBuffer(obj));
+};
+
+var combine = function combine(a, b, arrayLimit, plainObjects, throwOnLimitExceeded) {
+ // If 'a' is already an overflow object, add to it
+ if (isOverflow(a)) {
+ if (throwOnLimitExceeded) {
+ throw new RangeError('Array limit exceeded. Only ' + arrayLimit + ' element' + (arrayLimit === 1 ? '' : 's') + ' allowed in an array.');
+ }
+ var newIndex = getMaxIndex(a) + 1;
+ a[newIndex] = b;
+ setMaxIndex(a, newIndex);
+ return a;
+ }
+
+ var result = [].concat(a, b);
+ if (result.length > arrayLimit) {
+ if (throwOnLimitExceeded) {
+ throw new RangeError('Array limit exceeded. Only ' + arrayLimit + ' element' + (arrayLimit === 1 ? '' : 's') + ' allowed in an array.');
+ }
+ return markOverflow(arrayToObject(result, { plainObjects: plainObjects }), result.length - 1);
+ }
+ return result;
+};
+
+var maybeMap = function maybeMap(val, fn) {
+ if (isArray(val)) {
+ var mapped = [];
+ for (var i = 0; i < val.length; i += 1) {
+ mapped[mapped.length] = fn(val[i]);
+ }
+ return mapped;
+ }
+ return fn(val);
+};
+
+module.exports = {
+ arrayToObject: arrayToObject,
+ assign: assign,
+ combine: combine,
+ compact: compact,
+ decode: decode,
+ encode: encode,
+ isBuffer: isBuffer,
+ isOverflow: isOverflow,
+ isRegExp: isRegExp,
+ markOverflow: markOverflow,
+ maybeMap: maybeMap,
+ merge: merge
+};
diff --git a/node_modules/qs/package.json b/node_modules/qs/package.json
new file mode 100644
index 0000000..baa6795
--- /dev/null
+++ b/node_modules/qs/package.json
@@ -0,0 +1,95 @@
+{
+ "name": "qs",
+ "description": "A querystring parser that supports nesting and arrays, with a depth limit",
+ "homepage": "https://github.com/ljharb/qs",
+ "version": "6.15.3",
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/ljharb/qs.git"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ },
+ "main": "lib/index.js",
+ "sideEffects": false,
+ "contributors": [
+ {
+ "name": "Jordan Harband",
+ "email": "ljharb@gmail.com",
+ "url": "http://ljharb.codes"
+ }
+ ],
+ "keywords": [
+ "querystring",
+ "qs",
+ "query",
+ "url",
+ "parse",
+ "stringify"
+ ],
+ "engines": {
+ "node": ">=0.6"
+ },
+ "dependencies": {
+ "es-define-property": "^1.0.1",
+ "side-channel": "^1.1.1"
+ },
+ "devDependencies": {
+ "@browserify/envify": "^6.0.0",
+ "@browserify/uglifyify": "^6.0.0",
+ "@ljharb/eslint-config": "^22.2.3",
+ "browserify": "^16.5.2",
+ "bundle-collapser": "^1.4.0",
+ "common-shakeify": "~1.0.0",
+ "eclint": "^2.8.1",
+ "es-value-fixtures": "^1.7.1",
+ "eslint": "^10.5.0",
+ "evalmd": "^0.0.19",
+ "for-each": "^0.3.5",
+ "glob": "=10.3.7",
+ "has-bigints": "^1.1.0",
+ "has-override-mistake": "^1.0.1",
+ "has-property-descriptors": "^1.0.2",
+ "has-proto": "^1.2.0",
+ "has-symbols": "^1.1.0",
+ "iconv-lite": "^0.5.2",
+ "in-publish": "^2.0.1",
+ "jackspeak": "=2.1.1",
+ "jiti": "^0.0.0",
+ "mkdirp": "^0.5.5",
+ "mock-property": "^1.1.2",
+ "module-deps": "^6.2.3",
+ "npmignore": "^0.3.5",
+ "nyc": "^10.3.2",
+ "object-inspect": "^1.13.4",
+ "qs-iconv": "^1.0.4",
+ "safe-publish-latest": "^2.0.0",
+ "safer-buffer": "^2.1.2",
+ "tape": "^5.10.2",
+ "unassertify": "^3.0.1"
+ },
+ "scripts": {
+ "prepack": "npmignore --auto --commentLines=autogenerated && npm run dist",
+ "prepublishOnly": "safe-publish-latest",
+ "prepublish": "not-in-publish || npm run prepublishOnly",
+ "pretest": "npm run --silent readme && npm run --silent lint",
+ "test": "npm run tests-only",
+ "tests-only": "nyc tape 'test/**/*.js'",
+ "posttest": "npx npm@'>=10.2' audit --production",
+ "readme": "evalmd README.md",
+ "postlint": "eclint check $(git ls-files | xargs find 2> /dev/null | grep -vE 'node_modules|\\.git' | grep -v dist/)",
+ "lint": "eslint .",
+ "dist": "mkdirp dist && browserify --standalone Qs -g unassertify -g @browserify/envify -g [@browserify/uglifyify --mangle.keep_fnames --compress.keep_fnames --format.indent_level=1 --compress.arrows=false --compress.passes=4 --compress.typeofs=false] -p common-shakeify -p bundle-collapser/plugin lib/index.js > dist/qs.js"
+ },
+ "license": "BSD-3-Clause",
+ "publishConfig": {
+ "ignore": [
+ "!dist/*",
+ "bower.json",
+ "component.json",
+ ".github/workflows",
+ "logos",
+ "tea.yaml"
+ ]
+ }
+}
diff --git a/node_modules/qs/test/empty-keys-cases.js b/node_modules/qs/test/empty-keys-cases.js
new file mode 100644
index 0000000..2b1190e
--- /dev/null
+++ b/node_modules/qs/test/empty-keys-cases.js
@@ -0,0 +1,267 @@
+'use strict';
+
+module.exports = {
+ emptyTestCases: [
+ {
+ input: '&',
+ withEmptyKeys: {},
+ stringifyOutput: {
+ brackets: '',
+ indices: '',
+ repeat: ''
+ },
+ noEmptyKeys: {}
+ },
+ {
+ input: '&&',
+ withEmptyKeys: {},
+ stringifyOutput: {
+ brackets: '',
+ indices: '',
+ repeat: ''
+ },
+ noEmptyKeys: {}
+ },
+ {
+ input: '&=',
+ withEmptyKeys: { '': '' },
+ stringifyOutput: {
+ brackets: '=',
+ indices: '=',
+ repeat: '='
+ },
+ noEmptyKeys: {}
+ },
+ {
+ input: '&=&',
+ withEmptyKeys: { '': '' },
+ stringifyOutput: {
+ brackets: '=',
+ indices: '=',
+ repeat: '='
+ },
+ noEmptyKeys: {}
+ },
+ {
+ input: '&=&=',
+ withEmptyKeys: { '': ['', ''] },
+ stringifyOutput: {
+ brackets: '[]=&[]=',
+ indices: '[0]=&[1]=',
+ repeat: '=&='
+ },
+ noEmptyKeys: {}
+ },
+ {
+ input: '&=&=&',
+ withEmptyKeys: { '': ['', ''] },
+ stringifyOutput: {
+ brackets: '[]=&[]=',
+ indices: '[0]=&[1]=',
+ repeat: '=&='
+ },
+ noEmptyKeys: {}
+ },
+ {
+ input: '=',
+ withEmptyKeys: { '': '' },
+ noEmptyKeys: {},
+ stringifyOutput: {
+ brackets: '=',
+ indices: '=',
+ repeat: '='
+ }
+ },
+ {
+ input: '=&',
+ withEmptyKeys: { '': '' },
+ stringifyOutput: {
+ brackets: '=',
+ indices: '=',
+ repeat: '='
+ },
+ noEmptyKeys: {}
+ },
+ {
+ input: '=&&&',
+ withEmptyKeys: { '': '' },
+ stringifyOutput: {
+ brackets: '=',
+ indices: '=',
+ repeat: '='
+ },
+ noEmptyKeys: {}
+ },
+ {
+ input: '=&=&=&',
+ withEmptyKeys: { '': ['', '', ''] },
+ stringifyOutput: {
+ brackets: '[]=&[]=&[]=',
+ indices: '[0]=&[1]=&[2]=',
+ repeat: '=&=&='
+ },
+ noEmptyKeys: {}
+ },
+ {
+ input: '=&a[]=b&a[1]=c',
+ withEmptyKeys: { '': '', a: ['b', 'c'] },
+ stringifyOutput: {
+ brackets: '=&a[]=b&a[]=c',
+ indices: '=&a[0]=b&a[1]=c',
+ repeat: '=&a=b&a=c'
+ },
+ noEmptyKeys: { a: ['b', 'c'] }
+ },
+ {
+ input: '=a',
+ withEmptyKeys: { '': 'a' },
+ noEmptyKeys: {},
+ stringifyOutput: {
+ brackets: '=a',
+ indices: '=a',
+ repeat: '=a'
+ }
+ },
+ {
+ input: 'a==a',
+ withEmptyKeys: { a: '=a' },
+ noEmptyKeys: { a: '=a' },
+ stringifyOutput: {
+ brackets: 'a==a',
+ indices: 'a==a',
+ repeat: 'a==a'
+ }
+ },
+ {
+ input: '=&a[]=b',
+ withEmptyKeys: { '': '', a: ['b'] },
+ stringifyOutput: {
+ brackets: '=&a[]=b',
+ indices: '=&a[0]=b',
+ repeat: '=&a=b'
+ },
+ noEmptyKeys: { a: ['b'] }
+ },
+ {
+ input: '=&a[]=b&a[]=c&a[2]=d',
+ withEmptyKeys: { '': '', a: ['b', 'c', 'd'] },
+ stringifyOutput: {
+ brackets: '=&a[]=b&a[]=c&a[]=d',
+ indices: '=&a[0]=b&a[1]=c&a[2]=d',
+ repeat: '=&a=b&a=c&a=d'
+ },
+ noEmptyKeys: { a: ['b', 'c', 'd'] }
+ },
+ {
+ input: '=a&=b',
+ withEmptyKeys: { '': ['a', 'b'] },
+ stringifyOutput: {
+ brackets: '[]=a&[]=b',
+ indices: '[0]=a&[1]=b',
+ repeat: '=a&=b'
+ },
+ noEmptyKeys: {}
+ },
+ {
+ input: '=a&foo=b',
+ withEmptyKeys: { '': 'a', foo: 'b' },
+ noEmptyKeys: { foo: 'b' },
+ stringifyOutput: {
+ brackets: '=a&foo=b',
+ indices: '=a&foo=b',
+ repeat: '=a&foo=b'
+ }
+ },
+ {
+ input: 'a[]=b&a=c&=',
+ withEmptyKeys: { '': '', a: ['b', 'c'] },
+ stringifyOutput: {
+ brackets: '=&a[]=b&a[]=c',
+ indices: '=&a[0]=b&a[1]=c',
+ repeat: '=&a=b&a=c'
+ },
+ noEmptyKeys: { a: ['b', 'c'] }
+ },
+ {
+ input: 'a[]=b&a=c&=',
+ withEmptyKeys: { '': '', a: ['b', 'c'] },
+ stringifyOutput: {
+ brackets: '=&a[]=b&a[]=c',
+ indices: '=&a[0]=b&a[1]=c',
+ repeat: '=&a=b&a=c'
+ },
+ noEmptyKeys: { a: ['b', 'c'] }
+ },
+ {
+ input: 'a[0]=b&a=c&=',
+ withEmptyKeys: { '': '', a: ['b', 'c'] },
+ stringifyOutput: {
+ brackets: '=&a[]=b&a[]=c',
+ indices: '=&a[0]=b&a[1]=c',
+ repeat: '=&a=b&a=c'
+ },
+ noEmptyKeys: { a: ['b', 'c'] }
+ },
+ {
+ input: 'a=b&a[]=c&=',
+ withEmptyKeys: { '': '', a: ['b', 'c'] },
+ stringifyOutput: {
+ brackets: '=&a[]=b&a[]=c',
+ indices: '=&a[0]=b&a[1]=c',
+ repeat: '=&a=b&a=c'
+ },
+ noEmptyKeys: { a: ['b', 'c'] }
+ },
+ {
+ input: 'a=b&a[0]=c&=',
+ withEmptyKeys: { '': '', a: ['b', 'c'] },
+ stringifyOutput: {
+ brackets: '=&a[]=b&a[]=c',
+ indices: '=&a[0]=b&a[1]=c',
+ repeat: '=&a=b&a=c'
+ },
+ noEmptyKeys: { a: ['b', 'c'] }
+ },
+ {
+ input: '[]=a&[]=b& []=1',
+ withEmptyKeys: { '': ['a', 'b'], ' ': ['1'] },
+ stringifyOutput: {
+ brackets: '[]=a&[]=b& []=1',
+ indices: '[0]=a&[1]=b& [0]=1',
+ repeat: '=a&=b& =1'
+ },
+ noEmptyKeys: { 0: 'a', 1: 'b', ' ': ['1'] }
+ },
+ {
+ input: '[0]=a&[1]=b&a[0]=1&a[1]=2',
+ withEmptyKeys: { '': ['a', 'b'], a: ['1', '2'] },
+ noEmptyKeys: { 0: 'a', 1: 'b', a: ['1', '2'] },
+ stringifyOutput: {
+ brackets: '[]=a&[]=b&a[]=1&a[]=2',
+ indices: '[0]=a&[1]=b&a[0]=1&a[1]=2',
+ repeat: '=a&=b&a=1&a=2'
+ }
+ },
+ {
+ input: '[deep]=a&[deep]=2',
+ withEmptyKeys: { '': { deep: ['a', '2'] }
+ },
+ stringifyOutput: {
+ brackets: '[deep][]=a&[deep][]=2',
+ indices: '[deep][0]=a&[deep][1]=2',
+ repeat: '[deep]=a&[deep]=2'
+ },
+ noEmptyKeys: { deep: ['a', '2'] }
+ },
+ {
+ input: '%5B0%5D=a&%5B1%5D=b',
+ withEmptyKeys: { '': ['a', 'b'] },
+ stringifyOutput: {
+ brackets: '[]=a&[]=b',
+ indices: '[0]=a&[1]=b',
+ repeat: '=a&=b'
+ },
+ noEmptyKeys: { 0: 'a', 1: 'b' }
+ }
+ ]
+};
diff --git a/node_modules/qs/test/parse.js b/node_modules/qs/test/parse.js
new file mode 100644
index 0000000..17f30b6
--- /dev/null
+++ b/node_modules/qs/test/parse.js
@@ -0,0 +1,1933 @@
+'use strict';
+
+var test = require('tape');
+var hasPropertyDescriptors = require('has-property-descriptors')();
+var iconv = require('iconv-lite');
+var mockProperty = require('mock-property');
+var hasOverrideMistake = require('has-override-mistake')();
+var SaferBuffer = require('safer-buffer').Buffer;
+var v = require('es-value-fixtures');
+var inspect = require('object-inspect');
+var emptyTestCases = require('./empty-keys-cases').emptyTestCases;
+var hasProto = require('has-proto')();
+
+var qs = require('../');
+var utils = require('../lib/utils');
+
+var characterizeParse = function characterizeParse(st, input, opts, expected, label) {
+ var result;
+ st.doesNotThrow(function () { result = qs.parse(input, opts); }, label + ': does not throw');
+ st.deepEqual(result, expected, label + ': parses to the current lenient output');
+};
+
+test('parse()', function (t) {
+ t.test('parses a simple string', function (st) {
+ st.deepEqual(qs.parse('0=foo'), { 0: 'foo' });
+ st.deepEqual(qs.parse('foo=c++'), { foo: 'c ' });
+ st.deepEqual(qs.parse('a[>=]=23'), { a: { '>=': '23' } });
+ st.deepEqual(qs.parse('a[<=>]==23'), { a: { '<=>': '=23' } });
+ st.deepEqual(qs.parse('a[==]=23'), { a: { '==': '23' } });
+ st.deepEqual(qs.parse('foo', { strictNullHandling: true }), { foo: null });
+ st.deepEqual(qs.parse('foo'), { foo: '' });
+ st.deepEqual(qs.parse('foo='), { foo: '' });
+ st.deepEqual(qs.parse('foo=bar'), { foo: 'bar' });
+ st.deepEqual(qs.parse(' foo = bar = baz '), { ' foo ': ' bar = baz ' });
+ st.deepEqual(qs.parse('foo=bar=baz'), { foo: 'bar=baz' });
+ st.deepEqual(qs.parse('foo=bar&bar=baz'), { foo: 'bar', bar: 'baz' });
+ st.deepEqual(qs.parse('foo2=bar2&baz2='), { foo2: 'bar2', baz2: '' });
+ st.deepEqual(qs.parse('foo=bar&baz', { strictNullHandling: true }), { foo: 'bar', baz: null });
+ st.deepEqual(qs.parse('foo=bar&baz'), { foo: 'bar', baz: '' });
+ st.deepEqual(qs.parse('cht=p3&chd=t:60,40&chs=250x100&chl=Hello|World'), {
+ cht: 'p3',
+ chd: 't:60,40',
+ chs: '250x100',
+ chl: 'Hello|World'
+ });
+ st.end();
+ });
+
+ t.test('comma: false', function (st) {
+ st.deepEqual(qs.parse('a[]=b&a[]=c'), { a: ['b', 'c'] });
+ st.deepEqual(qs.parse('a[0]=b&a[1]=c'), { a: ['b', 'c'] });
+ st.deepEqual(qs.parse('a=b,c'), { a: 'b,c' });
+ st.deepEqual(qs.parse('a=b&a=c'), { a: ['b', 'c'] });
+ st.end();
+ });
+
+ t.test('comma: true', function (st) {
+ st.deepEqual(qs.parse('a[]=b&a[]=c', { comma: true }), { a: ['b', 'c'] });
+ st.deepEqual(qs.parse('a[0]=b&a[1]=c', { comma: true }), { a: ['b', 'c'] });
+ st.deepEqual(qs.parse('a=b,c', { comma: true }), { a: ['b', 'c'] });
+ st.deepEqual(qs.parse('a=b&a=c', { comma: true }), { a: ['b', 'c'] });
+ st.end();
+ });
+
+ t.test('allows enabling dot notation', function (st) {
+ st.deepEqual(qs.parse('a.b=c'), { 'a.b': 'c' });
+ st.deepEqual(qs.parse('a.b=c', { allowDots: true }), { a: { b: 'c' } });
+
+ st.end();
+ });
+
+ t.test('decode dot keys correctly', function (st) {
+ st.deepEqual(
+ qs.parse('name%252Eobj.first=John&name%252Eobj.last=Doe', { allowDots: false, decodeDotInKeys: false }),
+ { 'name%2Eobj.first': 'John', 'name%2Eobj.last': 'Doe' },
+ 'with allowDots false and decodeDotInKeys false'
+ );
+ st.deepEqual(
+ qs.parse('name.obj.first=John&name.obj.last=Doe', { allowDots: true, decodeDotInKeys: false }),
+ { name: { obj: { first: 'John', last: 'Doe' } } },
+ 'with allowDots false and decodeDotInKeys false'
+ );
+ st.deepEqual(
+ qs.parse('name%252Eobj.first=John&name%252Eobj.last=Doe', { allowDots: true, decodeDotInKeys: false }),
+ { 'name%2Eobj': { first: 'John', last: 'Doe' } },
+ 'with allowDots true and decodeDotInKeys false'
+ );
+ st.deepEqual(
+ qs.parse('name%252Eobj.first=John&name%252Eobj.last=Doe', { allowDots: true, decodeDotInKeys: true }),
+ { 'name.obj': { first: 'John', last: 'Doe' } },
+ 'with allowDots true and decodeDotInKeys true'
+ );
+
+ st.deepEqual(
+ qs.parse(
+ 'name%252Eobj%252Esubobject.first%252Egodly%252Ename=John&name%252Eobj%252Esubobject.last=Doe',
+ { allowDots: false, decodeDotInKeys: false }
+ ),
+ { 'name%2Eobj%2Esubobject.first%2Egodly%2Ename': 'John', 'name%2Eobj%2Esubobject.last': 'Doe' },
+ 'with allowDots false and decodeDotInKeys false'
+ );
+ st.deepEqual(
+ qs.parse(
+ 'name.obj.subobject.first.godly.name=John&name.obj.subobject.last=Doe',
+ { allowDots: true, decodeDotInKeys: false }
+ ),
+ { name: { obj: { subobject: { first: { godly: { name: 'John' } }, last: 'Doe' } } } },
+ 'with allowDots true and decodeDotInKeys false'
+ );
+ st.deepEqual(
+ qs.parse(
+ 'name%252Eobj%252Esubobject.first%252Egodly%252Ename=John&name%252Eobj%252Esubobject.last=Doe',
+ { allowDots: true, decodeDotInKeys: true }
+ ),
+ { 'name.obj.subobject': { 'first.godly.name': 'John', last: 'Doe' } },
+ 'with allowDots true and decodeDotInKeys true'
+ );
+ st.deepEqual(
+ qs.parse('name%252Eobj.first=John&name%252Eobj.last=Doe'),
+ { 'name%2Eobj.first': 'John', 'name%2Eobj.last': 'Doe' },
+ 'with allowDots and decodeDotInKeys undefined'
+ );
+
+ st.end();
+ });
+
+ t.test('decodes dot in key of object, and allow enabling dot notation when decodeDotInKeys is set to true and allowDots is undefined', function (st) {
+ st.deepEqual(
+ qs.parse(
+ 'name%252Eobj%252Esubobject.first%252Egodly%252Ename=John&name%252Eobj%252Esubobject.last=Doe',
+ { decodeDotInKeys: true }
+ ),
+ { 'name.obj.subobject': { 'first.godly.name': 'John', last: 'Doe' } },
+ 'with allowDots undefined and decodeDotInKeys true'
+ );
+
+ st.end();
+ });
+
+ t.test('throws when decodeDotInKeys is not of type boolean', function (st) {
+ st['throws'](
+ function () { qs.parse('foo[]&bar=baz', { decodeDotInKeys: 'foobar' }); },
+ TypeError
+ );
+
+ st['throws'](
+ function () { qs.parse('foo[]&bar=baz', { decodeDotInKeys: 0 }); },
+ TypeError
+ );
+ st['throws'](
+ function () { qs.parse('foo[]&bar=baz', { decodeDotInKeys: NaN }); },
+ TypeError
+ );
+
+ st['throws'](
+ function () { qs.parse('foo[]&bar=baz', { decodeDotInKeys: null }); },
+ TypeError
+ );
+
+ st.end();
+ });
+
+ t.test('allows empty arrays in obj values', function (st) {
+ st.deepEqual(qs.parse('foo[]&bar=baz', { allowEmptyArrays: true }), { foo: [], bar: 'baz' });
+ st.deepEqual(qs.parse('foo[]&bar=baz', { allowEmptyArrays: false }), { foo: [''], bar: 'baz' });
+
+ st.end();
+ });
+
+ t.test('throws when allowEmptyArrays is not of type boolean', function (st) {
+ st['throws'](
+ function () { qs.parse('foo[]&bar=baz', { allowEmptyArrays: 'foobar' }); },
+ TypeError
+ );
+
+ st['throws'](
+ function () { qs.parse('foo[]&bar=baz', { allowEmptyArrays: 0 }); },
+ TypeError
+ );
+ st['throws'](
+ function () { qs.parse('foo[]&bar=baz', { allowEmptyArrays: NaN }); },
+ TypeError
+ );
+
+ st['throws'](
+ function () { qs.parse('foo[]&bar=baz', { allowEmptyArrays: null }); },
+ TypeError
+ );
+
+ st.end();
+ });
+
+ t.test('allowEmptyArrays + strictNullHandling', function (st) {
+ st.deepEqual(
+ qs.parse('testEmptyArray[]', { strictNullHandling: true, allowEmptyArrays: true }),
+ { testEmptyArray: [] }
+ );
+
+ st.end();
+ });
+
+ t.deepEqual(qs.parse('a[b]=c'), { a: { b: 'c' } }, 'parses a single nested string');
+ t.deepEqual(qs.parse('a[b][c]=d'), { a: { b: { c: 'd' } } }, 'parses a double nested string');
+ t.deepEqual(
+ qs.parse('a[b][c][d][e][f][g][h]=i'),
+ { a: { b: { c: { d: { e: { f: { '[g][h]': 'i' } } } } } } },
+ 'defaults to a depth of 5'
+ );
+
+ t.test('only parses one level when depth = 1', function (st) {
+ st.deepEqual(qs.parse('a[b][c]=d', { depth: 1 }), { a: { b: { '[c]': 'd' } } });
+ st.deepEqual(qs.parse('a[b][c][d]=e', { depth: 1 }), { a: { b: { '[c][d]': 'e' } } });
+ st.end();
+ });
+
+ t.test('uses original key when depth = 0', function (st) {
+ st.deepEqual(qs.parse('a[0]=b&a[1]=c', { depth: 0 }), { 'a[0]': 'b', 'a[1]': 'c' });
+ st.deepEqual(qs.parse('a[0][0]=b&a[0][1]=c&a[1]=d&e=2', { depth: 0 }), { 'a[0][0]': 'b', 'a[0][1]': 'c', 'a[1]': 'd', e: '2' });
+ st.deepEqual(qs.parse('a.b=c', { depth: 0, allowDots: true }), { 'a[b]': 'c' }, 'normalizes dots before applying depth-0 behavior');
+ st.deepEqual(qs.parse('toString=foo', { depth: 0 }), {}, 'respects prototype guard at depth 0');
+ st.deepEqual(qs.parse('toString=foo', { depth: 0, allowPrototypes: true }), { toString: 'foo' }, 'allows prototypes at depth 0 when enabled');
+ st.end();
+ });
+
+ t.test('ignores prototype keys when depth = 0 and allowPrototypes is false', function (st) {
+ st.deepEqual(qs.parse('toString=foo', { depth: 0 }), {});
+ st.deepEqual(qs.parse('hasOwnProperty=bar', { depth: 0 }), {});
+ st.deepEqual(qs.parse('toString=foo&a=b', { depth: 0 }), { a: 'b' });
+ st.end();
+ });
+
+ t.test('allows prototype keys when depth = 0 and allowPrototypes is true', function (st) {
+ st.deepEqual(qs.parse('toString=foo', { depth: 0, allowPrototypes: true }), { toString: 'foo' });
+ st.end();
+ });
+
+ t.test('uses original key when depth = false', function (st) {
+ st.deepEqual(qs.parse('a[0]=b&a[1]=c', { depth: false }), { 'a[0]': 'b', 'a[1]': 'c' });
+ st.deepEqual(qs.parse('a[0][0]=b&a[0][1]=c&a[1]=d&e=2', { depth: false }), { 'a[0][0]': 'b', 'a[0][1]': 'c', 'a[1]': 'd', e: '2' });
+ st.end();
+ });
+
+ t.deepEqual(qs.parse('a=b&a=c'), { a: ['b', 'c'] }, 'parses a simple array');
+
+ t.test('parses an explicit array', function (st) {
+ st.deepEqual(qs.parse('a[]=b'), { a: ['b'] });
+ st.deepEqual(qs.parse('a[]=b&a[]=c'), { a: ['b', 'c'] });
+ st.deepEqual(qs.parse('a[]=b&a[]=c&a[]=d'), { a: ['b', 'c', 'd'] });
+ st.end();
+ });
+
+ t.test('parses a mix of simple and explicit arrays', function (st) {
+ st.deepEqual(qs.parse('a=b&a[]=c'), { a: ['b', 'c'] });
+ st.deepEqual(qs.parse('a[]=b&a=c'), { a: ['b', 'c'] });
+ st.deepEqual(qs.parse('a[0]=b&a=c'), { a: ['b', 'c'] });
+ st.deepEqual(qs.parse('a=b&a[0]=c'), { a: ['b', 'c'] });
+
+ st.deepEqual(qs.parse('a[1]=b&a=c', { arrayLimit: 20 }), { a: ['b', 'c'] });
+ st.deepEqual(qs.parse('a[]=b&a=c', { arrayLimit: 0 }), { a: { 0: 'b', 1: 'c' } });
+ st.deepEqual(qs.parse('a[]=b&a=c'), { a: ['b', 'c'] });
+
+ st.deepEqual(qs.parse('a=b&a[1]=c', { arrayLimit: 20 }), { a: ['b', 'c'] });
+ st.deepEqual(qs.parse('a=b&a[]=c', { arrayLimit: 0 }), { a: { 0: 'b', 1: 'c' } });
+ st.deepEqual(qs.parse('a=b&a[]=c'), { a: ['b', 'c'] });
+
+ st.end();
+ });
+
+ t.test('parses a nested array', function (st) {
+ st.deepEqual(qs.parse('a[b][]=c&a[b][]=d'), { a: { b: ['c', 'd'] } });
+ st.deepEqual(qs.parse('a[>=]=25'), { a: { '>=': '25' } });
+ st.end();
+ });
+
+ t.test('parses keys with literal [] inside a bracket group (#493)', function (st) {
+ // A bracket pair inside a bracket group should be treated literally as part of the key
+ st.deepEqual(
+ qs.parse('search[withbracket[]]=foobar'),
+ { search: { 'withbracket[]': 'foobar' } },
+ 'treats inner [] literally when inside a bracket group'
+ );
+
+ // Single-level variant
+ st.deepEqual(
+ qs.parse('a[b[]]=c'),
+ { a: { 'b[]': 'c' } },
+ 'keeps "b[]" as a literal key'
+ );
+
+ // Nested with an array push on the outer level
+ st.deepEqual(
+ qs.parse('list[][x[]]=y'),
+ { list: [{ 'x[]': 'y' }] },
+ 'preserves inner [] while still treating outer [] as array push'
+ );
+
+ // Multiple nested bracket pairs: inner [] remains literal as part of the key
+ st.deepEqual(
+ qs.parse('a[b[c[]]]=d'),
+ { a: { 'b[c[]]': 'd' } },
+ 'treats "b[c[]]" as a literal key inside the bracket group'
+ );
+
+ // Depth limits with literal brackets: preserve inner [] while limiting bracket-group parsing
+ st.deepEqual(
+ qs.parse('a[b[c[]]][d]=e', { depth: 1 }),
+ { a: { 'b[c[]]': { '[d]': 'e' } } },
+ 'respects depth: 1 and preserves literal inner [] in the parsed key'
+ );
+
+ // Unterminated inner bracket group is wrapped as a literal remainder segment
+ st.deepEqual(
+ qs.parse('a[[]b=c'),
+ { a: { '[[]b': 'c' } },
+ 'handles unterminated inner bracket groups without throwing'
+ );
+
+ st.end();
+ });
+
+ t.test('currently parses unbalanced bracket keys after a parent leniently to literal segments (issue #558)', function (st) {
+ characterizeParse(st, 'a[bc=v', undefined, { a: { '[bc': 'v' } }, 'unclosed group after a parent');
+ characterizeParse(st, 'a[=v', undefined, { a: { '[': 'v' } }, 'bare unclosed bracket after a parent');
+ characterizeParse(st, 'a[b][c=v', undefined, { a: { b: { '[c': 'v' } } }, 'unclosed group after a valid one');
+ characterizeParse(st, 'a[b]c[d=v', undefined, { a: { b: { '[d': 'v' } } }, 'unclosed group after text following a valid one');
+ characterizeParse(st, 'filters[customtags:Env: Prod=v', undefined, { filters: { '[customtags:Env: Prod': 'v' } }, 'the issue #558 reproduction');
+ characterizeParse(st, '][a=v', undefined, { ']': { '[a': 'v' } }, 'stray close bracket before an unclosed group');
+ characterizeParse(st, 'a][b=v', undefined, { 'a]': { '[b': 'v' } }, 'stray close bracket inside the parent');
+ st.end();
+ });
+
+ t.test('currently parses unbalanced bracket keys containing inner brackets leniently (issue #558)', function (st) {
+ characterizeParse(st, 'a[b[c=v', undefined, { a: { '[b[c': 'v' } }, 'unclosed group containing an inner bracket');
+ characterizeParse(st, 'a[b[c]=v', undefined, { a: { '[b[c]': 'v' } }, 'unbalanced group with an inner bracket and one close');
+ characterizeParse(st, 'a[b][c[d=v', undefined, { a: { b: { '[c[d': 'v' } } }, 'unclosed inner-bracket group after a valid one');
+ st.end();
+ });
+
+ t.test('currently parses bracket-prefixed unbalanced keys leniently (issue #558)', function (st) {
+ characterizeParse(st, '[abc=v', undefined, { '[abc': 'v' }, 'key starting with an unclosed bracket');
+ characterizeParse(st, '[[]b=v', undefined, { '[[]b': 'v' }, 'key starting with an unbalanced bracket group');
+ st.end();
+ });
+
+ t.test('lenient unbalanced-bracket handling currently depends on the depth option (issue #558)', function (st) {
+ characterizeParse(st, 'a[b]c[d]e[f=v', { depth: 5 }, { a: { b: { d: { '[f': 'v' } } } }, 'consumes groups up to the depth budget then keeps the unclosed remainder literal');
+ characterizeParse(st, 'a[b]c[d]e[f=v', { depth: 1 }, { a: { b: { '[d]e[f': 'v' } } }, 'a lower depth keeps more of the unclosed remainder literal');
+ characterizeParse(st, 'a[bc=v', { depth: 0 }, { 'a[bc': 'v' }, 'depth 0 keeps the entire key literal');
+ st.end();
+ });
+
+ t.test('currently parses an allowDots key with a trailing unclosed bracket leniently (issue #558)', function (st) {
+ characterizeParse(st, 'a.b[c=v', { allowDots: true }, { a: { b: { '[c': 'v' } } }, 'allowDots expands the dot then keeps the unclosed bracket literal');
+ st.end();
+ });
+
+ t.test('valid and stray-close bracket keys are unaffected by unbalanced-bracket handling', function (st) {
+ characterizeParse(st, 'a]b=v', undefined, { 'a]b': 'v' }, 'stray close bracket with no open bracket stays a flat key');
+ characterizeParse(st, 'a[b]extra=v', undefined, { a: { b: 'v' } }, 'text after a balanced group is ignored');
+ st.end();
+ });
+
+ t.test('allows to specify array indices', function (st) {
+ st.deepEqual(qs.parse('a[1]=c&a[0]=b&a[2]=d'), { a: ['b', 'c', 'd'] });
+ st.deepEqual(qs.parse('a[1]=c&a[0]=b'), { a: ['b', 'c'] });
+ st.deepEqual(qs.parse('a[1]=c', { arrayLimit: 20 }), { a: ['c'] });
+ st.deepEqual(qs.parse('a[1]=c', { arrayLimit: 0 }), { a: { 1: 'c' } });
+ st.deepEqual(qs.parse('a[1]=c'), { a: ['c'] });
+ st.end();
+ });
+
+ t.test('limits specific array indices to arrayLimit', function (st) {
+ st.deepEqual(qs.parse('a[19]=a', { arrayLimit: 20 }), { a: ['a'] });
+ st.deepEqual(qs.parse('a[20]=a', { arrayLimit: 20 }), { a: { 20: 'a' } });
+
+ st.deepEqual(qs.parse('a[19]=a'), { a: ['a'] });
+ st.deepEqual(qs.parse('a[20]=a'), { a: { 20: 'a' } });
+ st.end();
+ });
+
+ t.deepEqual(qs.parse('a[12b]=c'), { a: { '12b': 'c' } }, 'supports keys that begin with a number');
+
+ t.test('supports encoded = signs', function (st) {
+ st.deepEqual(qs.parse('he%3Dllo=th%3Dere'), { 'he=llo': 'th=ere' });
+ st.end();
+ });
+
+ t.test('is ok with url encoded strings', function (st) {
+ st.deepEqual(qs.parse('a[b%20c]=d'), { a: { 'b c': 'd' } });
+ st.deepEqual(qs.parse('a[b]=c%20d'), { a: { b: 'c d' } });
+ st.end();
+ });
+
+ t.test('allows brackets in the value', function (st) {
+ st.deepEqual(qs.parse('pets=["tobi"]'), { pets: '["tobi"]' });
+ st.deepEqual(qs.parse('operators=[">=", "<="]'), { operators: '[">=", "<="]' });
+ st.end();
+ });
+
+ t.test('allows empty values', function (st) {
+ st.deepEqual(qs.parse(''), {});
+ st.deepEqual(qs.parse(null), {});
+ st.deepEqual(qs.parse(undefined), {});
+ st.end();
+ });
+
+ t.test('transforms arrays to objects', function (st) {
+ st.deepEqual(qs.parse('foo[0]=bar&foo[bad]=baz'), { foo: { 0: 'bar', bad: 'baz' } });
+ st.deepEqual(qs.parse('foo[bad]=baz&foo[0]=bar'), { foo: { bad: 'baz', 0: 'bar' } });
+ st.deepEqual(qs.parse('foo[bad]=baz&foo[]=bar'), { foo: { bad: 'baz', 0: 'bar' } });
+ st.deepEqual(qs.parse('foo[]=bar&foo[bad]=baz'), { foo: { 0: 'bar', bad: 'baz' } });
+ st.deepEqual(qs.parse('foo[bad]=baz&foo[]=bar&foo[]=foo'), { foo: { bad: 'baz', 0: 'bar', 1: 'foo' } });
+ st.deepEqual(qs.parse('foo[0][a]=a&foo[0][b]=b&foo[1][a]=aa&foo[1][b]=bb'), { foo: [{ a: 'a', b: 'b' }, { a: 'aa', b: 'bb' }] });
+
+ st.deepEqual(qs.parse('a[]=b&a[t]=u&a[hasOwnProperty]=c', { allowPrototypes: false }), { a: { 0: 'b', t: 'u' } });
+ st.deepEqual(qs.parse('a[]=b&a[t]=u&a[hasOwnProperty]=c', { allowPrototypes: true }), { a: { 0: 'b', t: 'u', hasOwnProperty: 'c' } });
+ st.deepEqual(qs.parse('a[]=b&a[hasOwnProperty]=c&a[x]=y', { allowPrototypes: false }), { a: { 0: 'b', x: 'y' } });
+ st.deepEqual(qs.parse('a[]=b&a[hasOwnProperty]=c&a[x]=y', { allowPrototypes: true }), { a: { 0: 'b', hasOwnProperty: 'c', x: 'y' } });
+ st.end();
+ });
+
+ t.test('transforms arrays to objects (dot notation)', function (st) {
+ st.deepEqual(qs.parse('foo[0].baz=bar&fool.bad=baz', { allowDots: true }), { foo: [{ baz: 'bar' }], fool: { bad: 'baz' } });
+ st.deepEqual(qs.parse('foo[0].baz=bar&fool.bad.boo=baz', { allowDots: true }), { foo: [{ baz: 'bar' }], fool: { bad: { boo: 'baz' } } });
+ st.deepEqual(qs.parse('foo[0][0].baz=bar&fool.bad=baz', { allowDots: true }), { foo: [[{ baz: 'bar' }]], fool: { bad: 'baz' } });
+ st.deepEqual(qs.parse('foo[0].baz[0]=15&foo[0].bar=2', { allowDots: true }), { foo: [{ baz: ['15'], bar: '2' }] });
+ st.deepEqual(qs.parse('foo[0].baz[0]=15&foo[0].baz[1]=16&foo[0].bar=2', { allowDots: true }), { foo: [{ baz: ['15', '16'], bar: '2' }] });
+ st.deepEqual(qs.parse('foo.bad=baz&foo[0]=bar', { allowDots: true }), { foo: { bad: 'baz', 0: 'bar' } });
+ st.deepEqual(qs.parse('foo.bad=baz&foo[]=bar', { allowDots: true }), { foo: { bad: 'baz', 0: 'bar' } });
+ st.deepEqual(qs.parse('foo[]=bar&foo.bad=baz', { allowDots: true }), { foo: { 0: 'bar', bad: 'baz' } });
+ st.deepEqual(qs.parse('foo.bad=baz&foo[]=bar&foo[]=foo', { allowDots: true }), { foo: { bad: 'baz', 0: 'bar', 1: 'foo' } });
+ st.deepEqual(qs.parse('foo[0].a=a&foo[0].b=b&foo[1].a=aa&foo[1].b=bb', { allowDots: true }), { foo: [{ a: 'a', b: 'b' }, { a: 'aa', b: 'bb' }] });
+ st.end();
+ });
+
+ t.test('correctly prunes undefined values when converting an array to an object', function (st) {
+ st.deepEqual(qs.parse('a[2]=b&a[99999999]=c'), { a: { 2: 'b', 99999999: 'c' } });
+ st.end();
+ });
+
+ t.test('supports malformed uri characters', function (st) {
+ st.deepEqual(qs.parse('{%:%}', { strictNullHandling: true }), { '{%:%}': null });
+ st.deepEqual(qs.parse('{%:%}='), { '{%:%}': '' });
+ st.deepEqual(qs.parse('foo=%:%}'), { foo: '%:%}' });
+ st.end();
+ });
+
+ t.test('doesn\'t produce empty keys', function (st) {
+ st.deepEqual(qs.parse('_r=1&'), { _r: '1' });
+ st.end();
+ });
+
+ t.test('cannot access Object prototype', function (st) {
+ qs.parse('constructor[prototype][bad]=bad');
+ qs.parse('bad[constructor][prototype][bad]=bad');
+ st.equal(typeof Object.prototype.bad, 'undefined');
+ st.end();
+ });
+
+ t.test('parses arrays of objects', function (st) {
+ st.deepEqual(qs.parse('a[][b]=c'), { a: [{ b: 'c' }] });
+ st.deepEqual(qs.parse('a[0][b]=c'), { a: [{ b: 'c' }] });
+ st.end();
+ });
+
+ t.test('allows for empty strings in arrays', function (st) {
+ st.deepEqual(qs.parse('a[]=b&a[]=&a[]=c'), { a: ['b', '', 'c'] });
+
+ st.deepEqual(
+ qs.parse('a[0]=b&a[1]&a[2]=c&a[19]=', { strictNullHandling: true, arrayLimit: 20 }),
+ { a: ['b', null, 'c', ''] },
+ 'with arrayLimit 20 + array indices: null then empty string works'
+ );
+ st.deepEqual(
+ qs.parse('a[]=b&a[]&a[]=c&a[]=', { strictNullHandling: true, arrayLimit: 0 }),
+ { a: { 0: 'b', 1: null, 2: 'c', 3: '' } },
+ 'with arrayLimit 0 + array brackets: null then empty string works'
+ );
+
+ st.deepEqual(
+ qs.parse('a[0]=b&a[1]=&a[2]=c&a[19]', { strictNullHandling: true, arrayLimit: 20 }),
+ { a: ['b', '', 'c', null] },
+ 'with arrayLimit 20 + array indices: empty string then null works'
+ );
+ st.deepEqual(
+ qs.parse('a[]=b&a[]=&a[]=c&a[]', { strictNullHandling: true, arrayLimit: 0 }),
+ { a: { 0: 'b', 1: '', 2: 'c', 3: null } },
+ 'with arrayLimit 0 + array brackets: empty string then null works'
+ );
+
+ st.deepEqual(
+ qs.parse('a[]=&a[]=b&a[]=c'),
+ { a: ['', 'b', 'c'] },
+ 'array brackets: empty strings work'
+ );
+ st.end();
+ });
+
+ t.test('compacts sparse arrays', function (st) {
+ st.deepEqual(qs.parse('a[10]=1&a[2]=2', { arrayLimit: 20 }), { a: ['2', '1'] });
+ st.deepEqual(qs.parse('a[1][b][2][c]=1', { arrayLimit: 20 }), { a: [{ b: [{ c: '1' }] }] });
+ st.deepEqual(qs.parse('a[1][2][3][c]=1', { arrayLimit: 20 }), { a: [[[{ c: '1' }]]] });
+ st.deepEqual(qs.parse('a[1][2][3][c][1]=1', { arrayLimit: 20 }), { a: [[[{ c: ['1'] }]]] });
+ st.end();
+ });
+
+ t.test('parses sparse arrays', function (st) {
+ /* eslint no-sparse-arrays: 0 */
+ st.deepEqual(qs.parse('a[4]=1&a[1]=2', { allowSparse: true }), { a: [, '2', , , '1'] });
+ st.deepEqual(qs.parse('a[1][b][2][c]=1', { allowSparse: true }), { a: [, { b: [, , { c: '1' }] }] });
+ st.deepEqual(qs.parse('a[1][2][3][c]=1', { allowSparse: true }), { a: [, [, , [, , , { c: '1' }]]] });
+ st.deepEqual(qs.parse('a[1][2][3][c][1]=1', { allowSparse: true }), { a: [, [, , [, , , { c: [, '1'] }]]] });
+ st.end();
+ });
+
+ t.test('parses semi-parsed strings', function (st) {
+ st.deepEqual(qs.parse({ 'a[b]': 'c' }), { a: { b: 'c' } });
+ st.deepEqual(qs.parse({ 'a[b]': 'c', 'a[d]': 'e' }), { a: { b: 'c', d: 'e' } });
+ st.end();
+ });
+
+ t.test('parses buffers correctly', function (st) {
+ var b = SaferBuffer.from('test');
+ st.deepEqual(qs.parse({ a: b }), { a: b });
+ st.end();
+ });
+
+ t.test('parses jquery-param strings', function (st) {
+ // readable = 'filter[0][]=int1&filter[0][]==&filter[0][]=77&filter[]=and&filter[2][]=int2&filter[2][]==&filter[2][]=8'
+ var encoded = 'filter%5B0%5D%5B%5D=int1&filter%5B0%5D%5B%5D=%3D&filter%5B0%5D%5B%5D=77&filter%5B%5D=and&filter%5B2%5D%5B%5D=int2&filter%5B2%5D%5B%5D=%3D&filter%5B2%5D%5B%5D=8';
+ var expected = { filter: [['int1', '=', '77'], 'and', ['int2', '=', '8']] };
+ st.deepEqual(qs.parse(encoded), expected);
+ st.end();
+ });
+
+ t.test('continues parsing when no parent is found', function (st) {
+ st.deepEqual(qs.parse('[]=&a=b'), { 0: '', a: 'b' });
+ st.deepEqual(qs.parse('[]&a=b', { strictNullHandling: true }), { 0: null, a: 'b' });
+ st.deepEqual(qs.parse('[foo]=bar'), { foo: 'bar' });
+ st.end();
+ });
+
+ t.test('does not error when parsing a very long array', function (st) {
+ var str = 'a[]=a';
+ while (Buffer.byteLength(str) < 128 * 1024) {
+ str = str + '&' + str;
+ }
+
+ st.doesNotThrow(function () {
+ qs.parse(str);
+ });
+
+ st.end();
+ });
+
+ t.test('does not throw when a native prototype has an enumerable property', function (st) {
+ st.intercept(Object.prototype, 'crash', { value: '' });
+ st.intercept(Array.prototype, 'crash', { value: '' });
+
+ st.doesNotThrow(qs.parse.bind(null, 'a=b'));
+ st.deepEqual(qs.parse('a=b'), { a: 'b' });
+ st.doesNotThrow(qs.parse.bind(null, 'a[][b]=c'));
+ st.deepEqual(qs.parse('a[][b]=c'), { a: [{ b: 'c' }] });
+
+ st.end();
+ });
+
+ t.test('parses a string with an alternative string delimiter', function (st) {
+ st.deepEqual(qs.parse('a=b;c=d', { delimiter: ';' }), { a: 'b', c: 'd' });
+ st.end();
+ });
+
+ t.test('parses a string with an alternative RegExp delimiter', function (st) {
+ st.deepEqual(qs.parse('a=b; c=d', { delimiter: /[;,] */ }), { a: 'b', c: 'd' });
+ st.end();
+ });
+
+ t.test('does not use non-splittable objects as delimiters', function (st) {
+ st.deepEqual(qs.parse('a=b&c=d', { delimiter: true }), { a: 'b', c: 'd' });
+ st.end();
+ });
+
+ t.test('allows overriding parameter limit', function (st) {
+ st.deepEqual(qs.parse('a=b&c=d', { parameterLimit: 1 }), { a: 'b' });
+ st.end();
+ });
+
+ t.test('allows setting the parameter limit to Infinity', function (st) {
+ st.deepEqual(qs.parse('a=b&c=d', { parameterLimit: Infinity }), { a: 'b', c: 'd' });
+ st.end();
+ });
+
+ t.test('allows overriding array limit', function (st) {
+ st.deepEqual(qs.parse('a[0]=b', { arrayLimit: -1 }), { a: { 0: 'b' } });
+ st.deepEqual(qs.parse('a[0]=b', { arrayLimit: 0 }), { a: { 0: 'b' } });
+
+ st.deepEqual(qs.parse('a[-1]=b', { arrayLimit: -1 }), { a: { '-1': 'b' } });
+ st.deepEqual(qs.parse('a[-1]=b', { arrayLimit: 0 }), { a: { '-1': 'b' } });
+
+ st.deepEqual(qs.parse('a[0]=b&a[1]=c', { arrayLimit: -1 }), { a: { 0: 'b', 1: 'c' } });
+ st.deepEqual(qs.parse('a[0]=b&a[1]=c', { arrayLimit: 0 }), { a: { 0: 'b', 1: 'c' } });
+
+ st.end();
+ });
+
+ t.test('allows disabling array parsing', function (st) {
+ var indices = qs.parse('a[0]=b&a[1]=c', { parseArrays: false });
+ st.deepEqual(indices, { a: { 0: 'b', 1: 'c' } });
+ st.equal(Array.isArray(indices.a), false, 'parseArrays:false, indices case is not an array');
+
+ var emptyBrackets = qs.parse('a[]=b', { parseArrays: false });
+ st.deepEqual(emptyBrackets, { a: { 0: 'b' } });
+ st.equal(Array.isArray(emptyBrackets.a), false, 'parseArrays:false, empty brackets case is not an array');
+
+ st.end();
+ });
+
+ t.test('allows for query string prefix', function (st) {
+ st.deepEqual(qs.parse('?foo=bar', { ignoreQueryPrefix: true }), { foo: 'bar' });
+ st.deepEqual(qs.parse('foo=bar', { ignoreQueryPrefix: true }), { foo: 'bar' });
+ st.deepEqual(qs.parse('?foo=bar', { ignoreQueryPrefix: false }), { '?foo': 'bar' });
+
+ st.end();
+ });
+
+ t.test('parses an object', function (st) {
+ var input = {
+ 'user[name]': { 'pop[bob]': 3 },
+ 'user[email]': null
+ };
+
+ var expected = {
+ user: {
+ name: { 'pop[bob]': 3 },
+ email: null
+ }
+ };
+
+ var result = qs.parse(input);
+
+ st.deepEqual(result, expected);
+ st.end();
+ });
+
+ t.test('parses string with comma as array divider', function (st) {
+ st.deepEqual(qs.parse('foo=bar,tee', { comma: true }), { foo: ['bar', 'tee'] });
+ st.deepEqual(qs.parse('foo[bar]=coffee,tee', { comma: true }), { foo: { bar: ['coffee', 'tee'] } });
+ st.deepEqual(qs.parse('foo=', { comma: true }), { foo: '' });
+ st.deepEqual(qs.parse('foo', { comma: true }), { foo: '' });
+ st.deepEqual(qs.parse('foo', { comma: true, strictNullHandling: true }), { foo: null });
+
+ // test cases inversed from from stringify tests
+ st.deepEqual(qs.parse('a[0]=c'), { a: ['c'] });
+ st.deepEqual(qs.parse('a[]=c'), { a: ['c'] });
+ st.deepEqual(qs.parse('a[]=c', { comma: true }), { a: ['c'] });
+
+ st.deepEqual(qs.parse('a[0]=c&a[1]=d'), { a: ['c', 'd'] });
+ st.deepEqual(qs.parse('a[]=c&a[]=d'), { a: ['c', 'd'] });
+ st.deepEqual(qs.parse('a=c,d', { comma: true }), { a: ['c', 'd'] });
+
+ st.end();
+ });
+
+ t.test('parses values with comma as array divider', function (st) {
+ st.deepEqual(qs.parse({ foo: 'bar,tee' }, { comma: false }), { foo: 'bar,tee' });
+ st.deepEqual(qs.parse({ foo: 'bar,tee' }, { comma: true }), { foo: ['bar', 'tee'] });
+ st.end();
+ });
+
+ t.test('use number decoder, parses string that has one number with comma option enabled', function (st) {
+ var decoder = function (str, defaultDecoder, charset, type) {
+ if (!isNaN(Number(str))) {
+ return parseFloat(str);
+ }
+ return defaultDecoder(str, defaultDecoder, charset, type);
+ };
+
+ st.deepEqual(qs.parse('foo=1', { comma: true, decoder: decoder }), { foo: 1 });
+ st.deepEqual(qs.parse('foo=0', { comma: true, decoder: decoder }), { foo: 0 });
+
+ st.end();
+ });
+
+ t.test('parses brackets holds array of arrays when having two parts of strings with comma as array divider', function (st) {
+ st.deepEqual(qs.parse('foo[]=1,2,3&foo[]=4,5,6', { comma: true }), { foo: [['1', '2', '3'], ['4', '5', '6']] });
+ st.deepEqual(qs.parse('foo[]=1,2,3&foo[]=', { comma: true }), { foo: [['1', '2', '3'], ''] });
+ st.deepEqual(qs.parse('foo[]=1,2,3&foo[]=,', { comma: true }), { foo: [['1', '2', '3'], ['', '']] });
+ st.deepEqual(qs.parse('foo[]=1,2,3&foo[]=a', { comma: true }), { foo: [['1', '2', '3'], 'a'] });
+
+ st.end();
+ });
+
+ t.test('parses url-encoded brackets holds array of arrays when having two parts of strings with comma as array divider', function (st) {
+ st.deepEqual(qs.parse('foo%5B%5D=1,2,3&foo%5B%5D=4,5,6', { comma: true }), { foo: [['1', '2', '3'], ['4', '5', '6']] });
+ st.deepEqual(qs.parse('foo%5B%5D=1,2,3&foo%5B%5D=', { comma: true }), { foo: [['1', '2', '3'], ''] });
+ st.deepEqual(qs.parse('foo%5B%5D=1,2,3&foo%5B%5D=,', { comma: true }), { foo: [['1', '2', '3'], ['', '']] });
+ st.deepEqual(qs.parse('foo%5B%5D=1,2,3&foo%5B%5D=a', { comma: true }), { foo: [['1', '2', '3'], 'a'] });
+
+ st.end();
+ });
+
+ t.test('parses comma delimited array while having percent-encoded comma treated as normal text', function (st) {
+ st.deepEqual(qs.parse('foo=a%2Cb', { comma: true }), { foo: 'a,b' });
+ st.deepEqual(qs.parse('foo=a%2C%20b,d', { comma: true }), { foo: ['a, b', 'd'] });
+ st.deepEqual(qs.parse('foo=a%2C%20b,c%2C%20d', { comma: true }), { foo: ['a, b', 'c, d'] });
+
+ st.end();
+ });
+
+ t.test('parses an object in dot notation', function (st) {
+ var input = {
+ 'user.name': { 'pop[bob]': 3 },
+ 'user.email.': null
+ };
+
+ var expected = {
+ user: {
+ name: { 'pop[bob]': 3 },
+ email: null
+ }
+ };
+
+ var result = qs.parse(input, { allowDots: true });
+
+ st.deepEqual(result, expected);
+ st.end();
+ });
+
+ t.test('parses an object and not child values', function (st) {
+ var input = {
+ 'user[name]': { 'pop[bob]': { test: 3 } },
+ 'user[email]': null
+ };
+
+ var expected = {
+ user: {
+ name: { 'pop[bob]': { test: 3 } },
+ email: null
+ }
+ };
+
+ var result = qs.parse(input);
+
+ st.deepEqual(result, expected);
+ st.end();
+ });
+
+ t.test('does not blow up when Buffer global is missing', function (st) {
+ var restore = mockProperty(global, 'Buffer', { 'delete': true });
+
+ var result = qs.parse('a=b&c=d');
+
+ restore();
+
+ st.deepEqual(result, { a: 'b', c: 'd' });
+ st.end();
+ });
+
+ t.test('does not crash when parsing circular references', function (st) {
+ var a = {};
+ a.b = a;
+
+ var parsed;
+
+ st.doesNotThrow(function () {
+ parsed = qs.parse({ 'foo[bar]': 'baz', 'foo[baz]': a });
+ });
+
+ st.equal('foo' in parsed, true, 'parsed has "foo" property');
+ st.equal('bar' in parsed.foo, true);
+ st.equal('baz' in parsed.foo, true);
+ st.equal(parsed.foo.bar, 'baz');
+ st.deepEqual(parsed.foo.baz, a);
+ st.end();
+ });
+
+ t.test('does not crash on multi-step circular references', function (st) {
+ var a = {};
+ a.b = { c: { d: a } };
+
+ var parsed;
+
+ st.doesNotThrow(function () {
+ parsed = qs.parse({ foo: a });
+ });
+
+ st.equal('foo' in parsed, true, 'parsed has "foo" property');
+ st.equal(parsed.foo.b.c.d, parsed.foo, 'the multi-step cycle is preserved');
+ st.end();
+ });
+
+ t.test('does not crash when parsing deep objects', function (st) {
+ var parsed;
+ var str = 'foo';
+
+ for (var i = 0; i < 5000; i++) {
+ str += '[p]';
+ }
+
+ str += '=bar';
+
+ st.doesNotThrow(function () {
+ parsed = qs.parse(str, { depth: 5000 });
+ });
+
+ st.equal('foo' in parsed, true, 'parsed has "foo" property');
+
+ var depth = 0;
+ var ref = parsed.foo;
+ while ((ref = ref.p)) {
+ depth += 1;
+ }
+
+ st.equal(depth, 5000, 'parsed is 5000 properties deep');
+
+ st.end();
+ });
+
+ t.test('parses null objects correctly', { skip: !hasProto }, function (st) {
+ var a = { __proto__: null, b: 'c' };
+
+ st.deepEqual(qs.parse(a), { b: 'c' });
+ var result = qs.parse({ a: a });
+ st.equal('a' in result, true, 'result has "a" property');
+ st.deepEqual(result.a, a);
+ st.end();
+ });
+
+ t.test('parses dates correctly', function (st) {
+ var now = new Date();
+ st.deepEqual(qs.parse({ a: now }), { a: now });
+ st.end();
+ });
+
+ t.test('parses regular expressions correctly', function (st) {
+ var re = /^test$/;
+ st.deepEqual(qs.parse({ a: re }), { a: re });
+ st.end();
+ });
+
+ t.test('does not allow overwriting prototype properties', function (st) {
+ st.deepEqual(qs.parse('a[hasOwnProperty]=b', { allowPrototypes: false }), {});
+ st.deepEqual(qs.parse('hasOwnProperty=b', { allowPrototypes: false }), {});
+
+ st.deepEqual(
+ qs.parse('toString', { allowPrototypes: false }),
+ {},
+ 'bare "toString" results in {}'
+ );
+
+ st.end();
+ });
+
+ t.test('can allow overwriting prototype properties', function (st) {
+ st.deepEqual(qs.parse('a[hasOwnProperty]=b', { allowPrototypes: true }), { a: { hasOwnProperty: 'b' } });
+ st.deepEqual(qs.parse('hasOwnProperty=b', { allowPrototypes: true }), { hasOwnProperty: 'b' });
+
+ st.deepEqual(
+ qs.parse('toString', { allowPrototypes: true }),
+ { toString: '' },
+ 'bare "toString" results in { toString: "" }'
+ );
+
+ st.end();
+ });
+
+ t.test('does not crash when the global Object prototype is frozen', { skip: !hasPropertyDescriptors || !hasOverrideMistake }, function (st) {
+ // We can't actually freeze the global Object prototype as that will interfere with other tests, and once an object is frozen, it
+ // can't be unfrozen. Instead, we add a new non-writable property to simulate this.
+ st.teardown(mockProperty(Object.prototype, 'frozenProp', { value: 'foo', nonWritable: true, nonEnumerable: true }));
+
+ st['throws'](
+ function () {
+ var obj = {};
+ obj.frozenProp = 'bar';
+ },
+ // node < 6 has a different error message
+ /^TypeError: Cannot assign to read only property 'frozenProp' of (?:object '#<Object>'|#<Object>)/,
+ 'regular assignment of an inherited non-writable property throws'
+ );
+
+ var parsed;
+ st.doesNotThrow(
+ function () {
+ parsed = qs.parse('frozenProp', { allowPrototypes: false });
+ },
+ 'parsing a nonwritable Object.prototype property does not throw'
+ );
+
+ st.deepEqual(parsed, {}, 'bare "frozenProp" results in {}');
+
+ st.end();
+ });
+
+ t.test('params starting with a closing bracket', function (st) {
+ st.deepEqual(qs.parse(']=toString'), { ']': 'toString' });
+ st.deepEqual(qs.parse(']]=toString'), { ']]': 'toString' });
+ st.deepEqual(qs.parse(']hello]=toString'), { ']hello]': 'toString' });
+ st.end();
+ });
+
+ t.test('params starting with a starting bracket', function (st) {
+ st.deepEqual(qs.parse('[=toString'), { '[': 'toString' });
+ st.deepEqual(qs.parse('[[=toString'), { '[[': 'toString' });
+ st.deepEqual(qs.parse('[hello[=toString'), { '[hello[': 'toString' });
+ st.end();
+ });
+
+ t.test('add keys to objects', function (st) {
+ st.deepEqual(
+ qs.parse('a[b]=c&a=d', { strictMerge: false }),
+ { a: { b: 'c', d: true } },
+ 'can add keys to objects'
+ );
+
+ st.deepEqual(
+ qs.parse('a[b]=c&a=toString', { strictMerge: false }),
+ { a: { b: 'c' } },
+ 'can not overwrite prototype'
+ );
+
+ st.deepEqual(
+ qs.parse('a[b]=c&a=toString', { strictMerge: false, allowPrototypes: true }),
+ { a: { b: 'c', toString: true } },
+ 'can overwrite prototype with allowPrototypes true'
+ );
+
+ st.deepEqual(
+ qs.parse('a[b]=c&a=toString', { strictMerge: false, plainObjects: true }),
+ { __proto__: null, a: { __proto__: null, b: 'c', toString: true } },
+ 'can overwrite prototype with plainObjects true'
+ );
+
+ st.end();
+ });
+
+ t.test('strictMerge wraps object and primitive into an array', function (st) {
+ st.deepEqual(
+ qs.parse('a[b]=c&a=d'),
+ { a: [{ b: 'c' }, 'd'] },
+ 'object then primitive produces array'
+ );
+
+ st.deepEqual(
+ qs.parse('a=d&a[b]=c'),
+ { a: ['d', { b: 'c' }] },
+ 'primitive then object produces array'
+ );
+
+ st.deepEqual(
+ qs.parse('a[b]=c&a=toString'),
+ { a: [{ b: 'c' }, 'toString'] },
+ 'prototype-colliding value is preserved in array'
+ );
+
+ st.deepEqual(
+ qs.parse('a[b]=c&a=toString', { plainObjects: true }),
+ { __proto__: null, a: [{ __proto__: null, b: 'c' }, 'toString'] },
+ 'plainObjects preserved in array wrapping'
+ );
+
+ st.end();
+ });
+
+ t.test('dunder proto is ignored', function (st) {
+ var payload = 'categories[__proto__]=login&categories[__proto__]&categories[length]=42';
+ var result = qs.parse(payload, { allowPrototypes: true });
+
+ st.deepEqual(
+ result,
+ {
+ categories: {
+ length: '42'
+ }
+ },
+ 'silent [[Prototype]] payload'
+ );
+
+ var plainResult = qs.parse(payload, { allowPrototypes: true, plainObjects: true });
+
+ st.deepEqual(
+ plainResult,
+ {
+ __proto__: null,
+ categories: {
+ __proto__: null,
+ length: '42'
+ }
+ },
+ 'silent [[Prototype]] payload: plain objects'
+ );
+
+ var query = qs.parse('categories[__proto__]=cats&categories[__proto__]=dogs&categories[some][json]=toInject', { allowPrototypes: true });
+
+ st.notOk(Array.isArray(query.categories), 'is not an array');
+ st.notOk(query.categories instanceof Array, 'is not instanceof an array');
+ st.deepEqual(query.categories, { some: { json: 'toInject' } });
+ st.equal(JSON.stringify(query.categories), '{"some":{"json":"toInject"}}', 'stringifies as a non-array');
+
+ st.deepEqual(
+ qs.parse('foo[__proto__][hidden]=value&foo[bar]=stuffs', { allowPrototypes: true }),
+ {
+ foo: {
+ bar: 'stuffs'
+ }
+ },
+ 'hidden values'
+ );
+
+ st.deepEqual(
+ qs.parse('foo[__proto__][hidden]=value&foo[bar]=stuffs', { allowPrototypes: true, plainObjects: true }),
+ {
+ __proto__: null,
+ foo: {
+ __proto__: null,
+ bar: 'stuffs'
+ }
+ },
+ 'hidden values: plain objects'
+ );
+
+ st.end();
+ });
+
+ t.test('object-valued input with own `__proto__` does not mutate sub-object [[Prototype]]', function (st) {
+ // JSON.parse creates own data `__proto__` properties (via CreateDataProperty),
+ // which would trigger the Object.prototype.__proto__ accessor if merged via `acc[key] = value`.
+ var out = qs.parse({
+ 'user[name]': 'alice',
+ user: JSON.parse('{"__proto__":{"isAdmin":true}}')
+ }, { allowPrototypes: false });
+
+ st.equal(out.user.name, 'alice', 'name from bracket key is preserved');
+ st.equal(out.user.isAdmin, undefined, 'attacker-controlled inherited property is not exposed');
+ st.equal(Object.getPrototypeOf(out.user), Object.prototype, 'sub-object [[Prototype]] is unchanged');
+ st.equal(Object.prototype.isAdmin, undefined, 'Object.prototype is not polluted');
+
+ st.end();
+ });
+
+ t.test('can return null objects', { skip: !hasProto }, function (st) {
+ var expected = {
+ __proto__: null,
+ a: {
+ __proto__: null,
+ b: 'c',
+ hasOwnProperty: 'd'
+ }
+ };
+ st.deepEqual(qs.parse('a[b]=c&a[hasOwnProperty]=d', { plainObjects: true }), expected);
+ st.deepEqual(qs.parse(null, { plainObjects: true }), { __proto__: null });
+ var expectedArray = {
+ __proto__: null,
+ a: {
+ __proto__: null,
+ 0: 'b',
+ c: 'd'
+ }
+ };
+ st.deepEqual(qs.parse('a[]=b&a[c]=d', { plainObjects: true }), expectedArray);
+ st.end();
+ });
+
+ t.test('can parse with custom encoding', function (st) {
+ st.deepEqual(qs.parse('%8c%a7=%91%e5%8d%e3%95%7b', {
+ decoder: function (str) {
+ var reg = /%([0-9A-F]{2})/ig;
+ var result = [];
+ var parts = reg.exec(str);
+ while (parts) {
+ result.push(parseInt(parts[1], 16));
+ parts = reg.exec(str);
+ }
+ return String(iconv.decode(SaferBuffer.from(result), 'shift_jis'));
+ }
+ }), { 県: '大阪府' });
+ st.end();
+ });
+
+ t.test('receives the default decoder as a second argument', function (st) {
+ st.plan(1);
+ qs.parse('a', {
+ decoder: function (str, defaultDecoder) {
+ st.equal(defaultDecoder, utils.decode);
+ }
+ });
+ st.end();
+ });
+
+ t.test('throws error with wrong decoder', function (st) {
+ st['throws'](function () {
+ qs.parse({}, { decoder: 'string' });
+ }, new TypeError('Decoder has to be a function.'));
+ st.end();
+ });
+
+ t.test('does not mutate the options argument', function (st) {
+ var options = {};
+ qs.parse('a[b]=true', options);
+ st.deepEqual(options, {});
+ st.end();
+ });
+
+ t.test('throws if an invalid charset is specified', function (st) {
+ st['throws'](function () {
+ qs.parse('a=b', { charset: 'foobar' });
+ }, new TypeError('The charset option must be either utf-8, iso-8859-1, or undefined'));
+ st.end();
+ });
+
+ t.test('parses an iso-8859-1 string if asked to', function (st) {
+ st.deepEqual(qs.parse('%A2=%BD', { charset: 'iso-8859-1' }), { '¢': '½' });
+ st.end();
+ });
+
+ var urlEncodedCheckmarkInUtf8 = '%E2%9C%93';
+ var urlEncodedOSlashInUtf8 = '%C3%B8';
+ var urlEncodedNumCheckmark = '%26%2310003%3B';
+ var urlEncodedNumSmiley = '%26%239786%3B';
+
+ t.test('prefers an utf-8 charset specified by the utf8 sentinel to a default charset of iso-8859-1', function (st) {
+ st.deepEqual(qs.parse('utf8=' + urlEncodedCheckmarkInUtf8 + '&' + urlEncodedOSlashInUtf8 + '=' + urlEncodedOSlashInUtf8, { charsetSentinel: true, charset: 'iso-8859-1' }), { ø: 'ø' });
+ st.end();
+ });
+
+ t.test('prefers an iso-8859-1 charset specified by the utf8 sentinel to a default charset of utf-8', function (st) {
+ st.deepEqual(qs.parse('utf8=' + urlEncodedNumCheckmark + '&' + urlEncodedOSlashInUtf8 + '=' + urlEncodedOSlashInUtf8, { charsetSentinel: true, charset: 'utf-8' }), { 'ø': 'ø' });
+ st.end();
+ });
+
+ t.test('does not require the utf8 sentinel to be defined before the parameters whose decoding it affects', function (st) {
+ st.deepEqual(qs.parse('a=' + urlEncodedOSlashInUtf8 + '&utf8=' + urlEncodedNumCheckmark, { charsetSentinel: true, charset: 'utf-8' }), { a: 'ø' });
+ st.end();
+ });
+
+ t.test('ignores an utf8 sentinel with an unknown value', function (st) {
+ st.deepEqual(qs.parse('utf8=foo&' + urlEncodedOSlashInUtf8 + '=' + urlEncodedOSlashInUtf8, { charsetSentinel: true, charset: 'utf-8' }), { ø: 'ø' });
+ st.end();
+ });
+
+ t.test('uses the utf8 sentinel to switch to utf-8 when no default charset is given', function (st) {
+ st.deepEqual(qs.parse('utf8=' + urlEncodedCheckmarkInUtf8 + '&' + urlEncodedOSlashInUtf8 + '=' + urlEncodedOSlashInUtf8, { charsetSentinel: true }), { ø: 'ø' });
+ st.end();
+ });
+
+ t.test('uses the utf8 sentinel to switch to iso-8859-1 when no default charset is given', function (st) {
+ st.deepEqual(qs.parse('utf8=' + urlEncodedNumCheckmark + '&' + urlEncodedOSlashInUtf8 + '=' + urlEncodedOSlashInUtf8, { charsetSentinel: true }), { 'ø': 'ø' });
+ st.end();
+ });
+
+ t.test('interprets numeric entities in iso-8859-1 when `interpretNumericEntities`', function (st) {
+ st.deepEqual(qs.parse('foo=' + urlEncodedNumSmiley, { charset: 'iso-8859-1', interpretNumericEntities: true }), { foo: '☺' });
+ st.end();
+ });
+
+ t.test('handles a custom decoder returning `null`, in the `iso-8859-1` charset, when `interpretNumericEntities`', function (st) {
+ st.deepEqual(qs.parse('foo=&bar=' + urlEncodedNumSmiley, {
+ charset: 'iso-8859-1',
+ decoder: function (str, defaultDecoder, charset) {
+ return str ? defaultDecoder(str, defaultDecoder, charset) : null;
+ },
+ interpretNumericEntities: true
+ }), { foo: null, bar: '☺' });
+ st.end();
+ });
+
+ t.test('handles a custom decoder returning `null`, with a string key of `null`', function (st) {
+ st.deepEqual(
+ qs.parse('null=1&ToNull=2', {
+ decoder: function (str, defaultDecoder, charset) {
+ return str === 'ToNull' ? null : defaultDecoder(str, defaultDecoder, charset);
+ }
+ }),
+ { 'null': '1' },
+ '"null" key is not overridden by `null` decoder result'
+ );
+
+ st.end();
+ });
+
+ t.test('does not interpret numeric entities in iso-8859-1 when `interpretNumericEntities` is absent', function (st) {
+ st.deepEqual(qs.parse('foo=' + urlEncodedNumSmiley, { charset: 'iso-8859-1' }), { foo: '&#9786;' });
+ st.end();
+ });
+
+ t.test('does not interpret numeric entities when the charset is utf-8, even when `interpretNumericEntities`', function (st) {
+ st.deepEqual(qs.parse('foo=' + urlEncodedNumSmiley, { charset: 'utf-8', interpretNumericEntities: true }), { foo: '&#9786;' });
+ st.end();
+ });
+
+ t.test('interpretNumericEntities with comma:true and iso charset does not crash', function (st) {
+ st.deepEqual(
+ qs.parse('b&a[]=1,' + urlEncodedNumSmiley, { comma: true, charset: 'iso-8859-1', interpretNumericEntities: true }),
+ { b: '', a: ['1,☺'] }
+ );
+
+ st.end();
+ });
+
+ t.test('does not interpret %uXXXX syntax in iso-8859-1 mode', function (st) {
+ st.deepEqual(qs.parse('%u263A=%u263A', { charset: 'iso-8859-1' }), { '%u263A': '%u263A' });
+ st.end();
+ });
+
+ t.test('allows for decoding keys and values differently', function (st) {
+ var decoder = function (str, defaultDecoder, charset, type) {
+ if (type === 'key') {
+ return defaultDecoder(str, defaultDecoder, charset, type).toLowerCase();
+ }
+ if (type === 'value') {
+ return defaultDecoder(str, defaultDecoder, charset, type).toUpperCase();
+ }
+ throw 'this should never happen! type: ' + type;
+ };
+
+ st.deepEqual(qs.parse('KeY=vAlUe', { decoder: decoder }), { key: 'VALUE' });
+
+ var noopDecoder = function () { return 'x'; };
+ noopDecoder();
+ st['throws'](
+ function () { decoder('x', noopDecoder, 'utf-8', 'unknown'); },
+ 'this should never happen! type: unknown',
+ 'decoder throws for unexpected type'
+ );
+
+ st.end();
+ });
+
+ t.test('parameter limit tests', function (st) {
+ st.test('does not throw error when within parameter limit', function (sst) {
+ var result = qs.parse('a=1&b=2&c=3', { parameterLimit: 5, throwOnLimitExceeded: true });
+ sst.deepEqual(result, { a: '1', b: '2', c: '3' }, 'parses without errors');
+ sst.end();
+ });
+
+ st.test('throws error when throwOnLimitExceeded is present but not boolean', function (sst) {
+ sst['throws'](
+ function () {
+ qs.parse('a=1&b=2&c=3&d=4&e=5&f=6', { parameterLimit: 3, throwOnLimitExceeded: 'true' });
+ },
+ new TypeError('`throwOnLimitExceeded` option must be a boolean'),
+ 'throws error when throwOnLimitExceeded is present and not boolean'
+ );
+ sst.end();
+ });
+
+ st.test('throws error when parameter limit exceeded', function (sst) {
+ sst['throws'](
+ function () {
+ qs.parse('a=1&b=2&c=3&d=4&e=5&f=6', { parameterLimit: 3, throwOnLimitExceeded: true });
+ },
+ new RangeError('Parameter limit exceeded. Only 3 parameters allowed.'),
+ 'throws error when parameter limit is exceeded'
+ );
+
+ sst['throws'](
+ function () {
+ qs.parse('a=1&b=2', { parameterLimit: 1, throwOnLimitExceeded: true });
+ },
+ new RangeError('Parameter limit exceeded. Only 1 parameter allowed.'),
+ 'throws error with singular "parameter" when parameterLimit is 1'
+ );
+ sst.end();
+ });
+
+ st.test('silently truncates when throwOnLimitExceeded is not given', function (sst) {
+ var result = qs.parse('a=1&b=2&c=3&d=4&e=5', { parameterLimit: 3 });
+ sst.deepEqual(result, { a: '1', b: '2', c: '3' }, 'parses and truncates silently');
+ sst.end();
+ });
+
+ st.test('silently truncates when parameter limit exceeded without error', function (sst) {
+ var result = qs.parse('a=1&b=2&c=3&d=4&e=5', { parameterLimit: 3, throwOnLimitExceeded: false });
+ sst.deepEqual(result, { a: '1', b: '2', c: '3' }, 'parses and truncates silently');
+ sst.end();
+ });
+
+ st.test('allows unlimited parameters when parameterLimit set to Infinity', function (sst) {
+ var result = qs.parse('a=1&b=2&c=3&d=4&e=5&f=6', { parameterLimit: Infinity });
+ sst.deepEqual(result, { a: '1', b: '2', c: '3', d: '4', e: '5', f: '6' }, 'parses all parameters without truncation');
+ sst.end();
+ });
+
+ st.test('allows unlimited parameters when parameterLimit is Infinity and throwOnLimitExceeded is true', function (sst) {
+ var result = qs.parse('a=1&b=2&c=3&d=4&e=5&f=6', { parameterLimit: Infinity, throwOnLimitExceeded: true });
+ sst.deepEqual(result, { a: '1', b: '2', c: '3', d: '4', e: '5', f: '6' }, 'parses all parameters without truncation or throwing');
+ sst.end();
+ });
+
+ st.end();
+ });
+
+ t.test('array limit tests', function (st) {
+ st.test('does not throw error when array is within limit', function (sst) {
+ var result = qs.parse('a[]=1&a[]=2&a[]=3', { arrayLimit: 5, throwOnLimitExceeded: true });
+ sst.deepEqual(result, { a: ['1', '2', '3'] }, 'parses array without errors');
+ sst.end();
+ });
+
+ st.test('throws error when throwOnLimitExceeded is present but not boolean for array limit', function (sst) {
+ sst['throws'](
+ function () {
+ qs.parse('a[]=1&a[]=2&a[]=3&a[]=4', { arrayLimit: 3, throwOnLimitExceeded: 'true' });
+ },
+ new TypeError('`throwOnLimitExceeded` option must be a boolean'),
+ 'throws error when throwOnLimitExceeded is present and not boolean for array limit'
+ );
+ sst.end();
+ });
+
+ st.test('throws error when array limit exceeded', function (sst) {
+ // 4 elements exceeds limit of 3
+ sst['throws'](
+ function () {
+ qs.parse('a[]=1&a[]=2&a[]=3&a[]=4', { arrayLimit: 3, throwOnLimitExceeded: true });
+ },
+ new RangeError('Array limit exceeded. Only 3 elements allowed in an array.'),
+ 'throws error when array limit is exceeded'
+ );
+ sst.end();
+ });
+
+ st.test('does not throw when at limit', function (sst) {
+ // 3 elements = limit of 3, should not throw
+ var result = qs.parse('a[]=1&a[]=2&a[]=3', { arrayLimit: 3, throwOnLimitExceeded: true });
+ sst.ok(Array.isArray(result.a), 'result is an array');
+ sst.deepEqual(result.a, ['1', '2', '3'], 'all values present');
+ sst.end();
+ });
+
+ st.test('converts array to object if length is greater than limit', function (sst) {
+ var result = qs.parse('a[1]=1&a[2]=2&a[3]=3&a[4]=4&a[5]=5&a[6]=6', { arrayLimit: 5 });
+
+ sst.deepEqual(result, { a: { 1: '1', 2: '2', 3: '3', 4: '4', 5: '5', 6: '6' } }, 'parses into object if array length is greater than limit');
+ sst.end();
+ });
+
+ st.test('throws error when indexed notation exceeds arrayLimit with throwOnLimitExceeded', function (sst) {
+ sst['throws'](
+ function () {
+ qs.parse('a[1001]=b', { arrayLimit: 1000, throwOnLimitExceeded: true });
+ },
+ new RangeError('Array limit exceeded. Only 1000 elements allowed in an array.'),
+ 'throws error for a single index exceeding arrayLimit'
+ );
+
+ sst['throws'](
+ function () {
+ qs.parse('a[0]=1&a[1]=2&a[2]=3&a[10]=4', { arrayLimit: 6, throwOnLimitExceeded: true, allowSparse: true });
+ },
+ new RangeError('Array limit exceeded. Only 6 elements allowed in an array.'),
+ 'throws error when a sparse index exceeds arrayLimit'
+ );
+
+ sst['throws'](
+ function () {
+ qs.parse('a[2]=b', { arrayLimit: 1, throwOnLimitExceeded: true });
+ },
+ new RangeError('Array limit exceeded. Only 1 element allowed in an array.'),
+ 'throws error with singular "element" when arrayLimit is 1'
+ );
+
+ sst.end();
+ });
+
+ st.test('does not throw for indexed notation within arrayLimit with throwOnLimitExceeded', function (sst) {
+ var result = qs.parse('a[4]=b', { arrayLimit: 5, throwOnLimitExceeded: true, allowSparse: true });
+ sst.ok(Array.isArray(result.a), 'result is an array');
+ sst.equal(result.a.length, 5, 'array has correct length');
+ sst.equal(result.a[4], 'b', 'value at index 4 is correct');
+ sst.end();
+ });
+
+ st.test('silently converts to object for indexed notation exceeding arrayLimit without throwOnLimitExceeded', function (sst) {
+ var result = qs.parse('a[1001]=b', { arrayLimit: 1000 });
+ sst.deepEqual(result, { a: { 1001: 'b' } }, 'converts to object without throwing');
+ sst.end();
+ });
+
+ st.test('throws when duplicate bracket keys exceed arrayLimit with throwOnLimitExceeded', function (sst) {
+ sst['throws'](
+ function () {
+ qs.parse('a[]=1&a[]=2&a[]=3&a[]=4&a[]=5&a[]=6', { arrayLimit: 5, throwOnLimitExceeded: true });
+ },
+ new RangeError('Array limit exceeded. Only 5 elements allowed in an array.'),
+ 'throws error when duplicate bracket notation exceeds array limit'
+ );
+ sst.end();
+ });
+
+ st.test('throws when cumulative comma + duplicate-key combine exceeds arrayLimit', function (sst) {
+ sst['throws'](
+ function () {
+ qs.parse('a=1,2,3&a=4,5,6', { comma: true, arrayLimit: 5, throwOnLimitExceeded: true });
+ },
+ new RangeError('Array limit exceeded. Only 5 elements allowed in an array.'),
+ 'throws when comma groups within the limit cumulatively exceed it across duplicate keys'
+ );
+
+ sst['throws'](
+ function () {
+ qs.parse('a=v,v,v,v,v&a=v,v,v,v,v&a=v,v,v,v,v', { comma: true, arrayLimit: 5, throwOnLimitExceeded: true });
+ },
+ new RangeError('Array limit exceeded. Only 5 elements allowed in an array.'),
+ 'throws on a subsequent part once the cumulative array is already over the limit'
+ );
+ sst.end();
+ });
+
+ st.test('throws when plain duplicate keys combine past arrayLimit at the boundary', function (sst) {
+ sst['throws'](
+ function () { qs.parse('a=x&a=y', { arrayLimit: 1, throwOnLimitExceeded: true }); },
+ new RangeError('Array limit exceeded. Only 1 element allowed in an array.'),
+ 'duplicate scalar keys'
+ );
+
+ sst['throws'](
+ function () { qs.parse('a[]=x&a[]=y', { arrayLimit: 1, throwOnLimitExceeded: true }); },
+ new RangeError('Array limit exceeded. Only 1 element allowed in an array.'),
+ 'duplicate bracket keys'
+ );
+ sst.end();
+ });
+
+ st.test('throws when mixed index and key notation merge past arrayLimit', function (sst) {
+ sst['throws'](
+ function () { qs.parse('a=x&a[0]=y', { arrayLimit: 1, throwOnLimitExceeded: true }); },
+ new RangeError('Array limit exceeded. Only 1 element allowed in an array.'),
+ 'scalar then index that overflows on merge'
+ );
+
+ sst['throws'](
+ function () { qs.parse('a[0]=1&a[1]=2&a=3', { arrayLimit: 1, throwOnLimitExceeded: true }); },
+ new RangeError('Array limit exceeded. Only 1 element allowed in an array.'),
+ 'indexed array then scalar that overflows on merge'
+ );
+ sst.end();
+ });
+
+ st.test('enforces arrayLimit on merge at the boundary, consistently with combine', function (sst) {
+ sst['throws'](
+ function () { qs.parse('a[0]=x&a=y', { arrayLimit: 1, throwOnLimitExceeded: true }); },
+ new RangeError('Array limit exceeded. Only 1 element allowed in an array.'),
+ 'a trailing scalar merged into an at-limit array throws'
+ );
+ sst.deepEqual(
+ qs.parse('a[0]=x&a=y', { arrayLimit: 1 }),
+ { a: { 0: 'x', 1: 'y' } },
+ 'and converts to an overflow object without throwOnLimitExceeded'
+ );
+
+ sst['throws'](
+ function () { qs.parse('a[0]=x&a[]=y', { arrayLimit: 1, throwOnLimitExceeded: true }); },
+ new RangeError('Array limit exceeded. Only 1 element allowed in an array.'),
+ 'mixed index and bracket notation merged past the limit throws'
+ );
+ sst.deepEqual(
+ qs.parse('a[0]=x&a[]=y', { arrayLimit: 1 }),
+ { a: { 0: 'x', 1: 'y' } },
+ 'mixed index and bracket notation converts like duplicate-bracket combine'
+ );
+
+ sst.end();
+ });
+
+ st.test('does not throw when cumulative comma combine stays within arrayLimit', function (sst) {
+ var result = qs.parse('a=1,2,3&a=4', { comma: true, arrayLimit: 5, throwOnLimitExceeded: true });
+ sst.deepEqual(result, { a: ['1', '2', '3', '4'] }, 'combined array within limit is preserved');
+ sst.end();
+ });
+
+ st.test('silently combines to an overflow object when throwOnLimitExceeded is not set', function (sst) {
+ var result = qs.parse('a=1,2,3&a=4,5,6', { comma: true, arrayLimit: 5 });
+ sst.deepEqual(result, { a: { 0: '1', 1: '2', 2: '3', 3: '4', 4: '5', 5: '6' } }, 'converts to object without throwing');
+ sst.end();
+ });
+
+ st.test('does not throw for comma groups nested under bracket notation, counting each group as one element', function (sst) {
+ var result = qs.parse('a[]=1,2,3&a[]=4,5,6', { comma: true, arrayLimit: 5, throwOnLimitExceeded: true });
+ sst.deepEqual(result, { a: [['1', '2', '3'], ['4', '5', '6']] }, 'nested comma groups count as one element each');
+ sst.end();
+ });
+
+ st.test('throws before splitting when a single comma value exceeds arrayLimit', function (sst) {
+ sst['throws'](
+ function () {
+ qs.parse('a=1,2,3,4,5,6', { comma: true, arrayLimit: 5, throwOnLimitExceeded: true });
+ },
+ new RangeError('Array limit exceeded. Only 5 elements allowed in an array.'),
+ 'a flat comma value over the limit throws'
+ );
+
+ sst['throws'](
+ function () {
+ qs.parse('a=1,2', { comma: true, arrayLimit: 1, throwOnLimitExceeded: true });
+ },
+ new RangeError('Array limit exceeded. Only 1 element allowed in an array.'),
+ 'singular message at arrayLimit 1'
+ );
+
+ sst['throws'](
+ function () {
+ qs.parse('a[b]=1,2,3,4,5,6', { comma: true, arrayLimit: 5, throwOnLimitExceeded: true });
+ },
+ new RangeError('Array limit exceeded. Only 5 elements allowed in an array.'),
+ 'a non-bracket nested key comma value over the limit throws'
+ );
+ sst.end();
+ });
+
+ st.test('does not throw for a single comma value within arrayLimit', function (sst) {
+ sst.deepEqual(
+ qs.parse('a=1,2,3', { comma: true, arrayLimit: 5, throwOnLimitExceeded: true }),
+ { a: ['1', '2', '3'] },
+ 'within the limit'
+ );
+ sst.deepEqual(
+ qs.parse('a=1,2,3,4,5', { comma: true, arrayLimit: 5, throwOnLimitExceeded: true }),
+ { a: ['1', '2', '3', '4', '5'] },
+ 'exactly at the limit'
+ );
+ sst.end();
+ });
+
+ st.test('does not throw for a bracketed comma group within arrayLimit', function (sst) {
+ var result = qs.parse('a[]=1,2,3,4,5,6', { comma: true, arrayLimit: 5, throwOnLimitExceeded: true });
+ sst.deepEqual(result, { a: [['1', '2', '3', '4', '5', '6']] }, 'a bracketed comma group is a single element');
+ sst.end();
+ });
+
+ st.test('throws for a bracketed comma group when arrayLimit is 0', function (sst) {
+ sst['throws'](
+ function () {
+ qs.parse('a[]=1,2,3', { comma: true, arrayLimit: 0, throwOnLimitExceeded: true });
+ },
+ new RangeError('Array limit exceeded. Only 0 elements allowed in an array.'),
+ 'a single bracketed element still exceeds arrayLimit 0'
+ );
+ sst.end();
+ });
+
+ st.end();
+ });
+
+ t.end();
+});
+
+test('parses empty keys', function (t) {
+ emptyTestCases.forEach(function (testCase) {
+ t.test('skips empty string key with ' + testCase.input, function (st) {
+ st.deepEqual(qs.parse(testCase.input), testCase.noEmptyKeys);
+
+ st.end();
+ });
+ });
+});
+
+test('`duplicates` option', function (t) {
+ v.nonStrings.concat('not a valid option').forEach(function (invalidOption) {
+ if (typeof invalidOption !== 'undefined') {
+ t['throws'](
+ function () { qs.parse('', { duplicates: invalidOption }); },
+ TypeError,
+ 'throws on invalid option: ' + inspect(invalidOption)
+ );
+ }
+ });
+
+ t.deepEqual(
+ qs.parse('foo=bar&foo=baz'),
+ { foo: ['bar', 'baz'] },
+ 'duplicates: default, combine'
+ );
+
+ t.deepEqual(
+ qs.parse('foo=bar&foo=baz', { duplicates: 'combine' }),
+ { foo: ['bar', 'baz'] },
+ 'duplicates: combine'
+ );
+
+ t.deepEqual(
+ qs.parse('foo=bar&foo=baz', { duplicates: 'first' }),
+ { foo: 'bar' },
+ 'duplicates: first'
+ );
+
+ t.deepEqual(
+ qs.parse('foo=bar&foo=baz', { duplicates: 'last' }),
+ { foo: 'baz' },
+ 'duplicates: last'
+ );
+
+ t.test('bracket notation always combines regardless of duplicates', function (st) {
+ st.deepEqual(
+ qs.parse('a=1&a=2&b[]=1&b[]=2', { duplicates: 'last' }),
+ { a: '2', b: ['1', '2'] },
+ 'duplicates last: unbracketed takes last, bracketed combines'
+ );
+
+ st.deepEqual(
+ qs.parse('b[]=1&b[]=2', { duplicates: 'last' }),
+ { b: ['1', '2'] },
+ 'duplicates last: bracketed always combines'
+ );
+
+ st.deepEqual(
+ qs.parse('b[]=1&b[]=2', { duplicates: 'first' }),
+ { b: ['1', '2'] },
+ 'duplicates first: bracketed always combines'
+ );
+
+ st.deepEqual(
+ qs.parse('a=1&a=2&b[]=1&b[]=2', { duplicates: 'first' }),
+ { a: '1', b: ['1', '2'] },
+ 'duplicates first: unbracketed takes first, bracketed combines'
+ );
+
+ st.end();
+ });
+
+ t.end();
+});
+
+test('qs strictDepth option - throw cases', function (t) {
+ t.test('throws an exception when depth exceeds the limit with strictDepth: true', function (st) {
+ st['throws'](
+ function () {
+ qs.parse('a[b][c][d][e][f][g][h][i]=j', { depth: 1, strictDepth: true });
+ },
+ RangeError,
+ 'throws RangeError'
+ );
+ st.end();
+ });
+
+ t.test('throws an exception for multiple nested arrays with strictDepth: true', function (st) {
+ st['throws'](
+ function () {
+ qs.parse('a[0][1][2][3][4]=b', { depth: 3, strictDepth: true });
+ },
+ RangeError,
+ 'throws RangeError'
+ );
+ st.end();
+ });
+
+ t.test('throws an exception for nested objects and arrays with strictDepth: true', function (st) {
+ st['throws'](
+ function () {
+ qs.parse('a[b][c][0][d][e]=f', { depth: 3, strictDepth: true });
+ },
+ RangeError,
+ 'throws RangeError'
+ );
+ st.end();
+ });
+
+ t.test('throws an exception for different types of values with strictDepth: true', function (st) {
+ st['throws'](
+ function () {
+ qs.parse('a[b][c][d][e]=true&a[b][c][d][f]=42', { depth: 3, strictDepth: true });
+ },
+ RangeError,
+ 'throws RangeError'
+ );
+ st.end();
+ });
+
+});
+
+test('qs strictDepth option - non-throw cases', function (t) {
+ t.test('when depth is 0 and strictDepth true, do not throw', function (st) {
+ st.doesNotThrow(
+ function () {
+ qs.parse('a[b][c][d][e]=true&a[b][c][d][f]=42', { depth: 0, strictDepth: true });
+ },
+ RangeError,
+ 'does not throw RangeError'
+ );
+ st.end();
+ });
+
+ t.test('parses successfully when depth is within the limit with strictDepth: true', function (st) {
+ st.doesNotThrow(
+ function () {
+ var result = qs.parse('a[b]=c', { depth: 1, strictDepth: true });
+ st.deepEqual(result, { a: { b: 'c' } }, 'parses correctly');
+ }
+ );
+ st.end();
+ });
+
+ t.test('does not throw an exception when depth exceeds the limit with strictDepth: false', function (st) {
+ st.doesNotThrow(
+ function () {
+ var result = qs.parse('a[b][c][d][e][f][g][h][i]=j', { depth: 1 });
+ st.deepEqual(result, { a: { b: { '[c][d][e][f][g][h][i]': 'j' } } }, 'parses with depth limit');
+ }
+ );
+ st.end();
+ });
+
+ t.test('parses successfully when depth is within the limit with strictDepth: false', function (st) {
+ st.doesNotThrow(
+ function () {
+ var result = qs.parse('a[b]=c', { depth: 1 });
+ st.deepEqual(result, { a: { b: 'c' } }, 'parses correctly');
+ }
+ );
+ st.end();
+ });
+
+ t.test('does not throw when depth is exactly at the limit with strictDepth: true', function (st) {
+ st.doesNotThrow(
+ function () {
+ var result = qs.parse('a[b][c]=d', { depth: 2, strictDepth: true });
+ st.deepEqual(result, { a: { b: { c: 'd' } } }, 'parses correctly');
+ }
+ );
+ st.end();
+ });
+});
+
+test('DOS', function (t) {
+ var arr = [];
+ for (var i = 0; i < 105; i++) {
+ arr[arr.length] = 'x';
+ }
+ var attack = 'a[]=' + arr.join('&a[]=');
+ var result = qs.parse(attack, { arrayLimit: 100 });
+
+ t.notOk(Array.isArray(result.a), 'arrayLimit is respected: result is an object, not an array');
+ t.equal(Object.keys(result.a).length, 105, 'all values are preserved');
+
+ t.end();
+});
+
+test('arrayLimit boundary conditions', function (t) {
+ // arrayLimit is the max number of elements allowed in an array
+ t.test('exactly at the limit stays as array', function (st) {
+ // 3 elements = limit of 3
+ var result = qs.parse('a[]=1&a[]=2&a[]=3', { arrayLimit: 3 });
+ st.ok(Array.isArray(result.a), 'result is an array when count equals limit');
+ st.deepEqual(result.a, ['1', '2', '3'], 'all values present');
+ st.end();
+ });
+
+ t.test('one over the limit converts to object', function (st) {
+ // 4 elements exceeds limit of 3
+ var result = qs.parse('a[]=1&a[]=2&a[]=3&a[]=4', { arrayLimit: 3 });
+ st.notOk(Array.isArray(result.a), 'result is not an array when over limit');
+ st.deepEqual(result.a, { 0: '1', 1: '2', 2: '3', 3: '4' }, 'all values preserved as object');
+ st.end();
+ });
+
+ t.test('arrayLimit 1 with one value', function (st) {
+ // 1 element = limit of 1
+ var result = qs.parse('a[]=1', { arrayLimit: 1 });
+ st.ok(Array.isArray(result.a), 'result is an array when count equals limit');
+ st.deepEqual(result.a, ['1'], 'value preserved as array');
+ st.end();
+ });
+
+ t.test('arrayLimit 1 with two values converts to object', function (st) {
+ // 2 elements exceeds limit of 1
+ var result = qs.parse('a[]=1&a[]=2', { arrayLimit: 1 });
+ st.notOk(Array.isArray(result.a), 'result is not an array');
+ st.deepEqual(result.a, { 0: '1', 1: '2' }, 'all values preserved as object');
+ st.end();
+ });
+
+ t.end();
+});
+
+test('comma + arrayLimit', function (t) {
+ t.test('comma-separated values within arrayLimit stay as array', function (st) {
+ var result = qs.parse('a=1,2,3', { comma: true, arrayLimit: 5 });
+ st.ok(Array.isArray(result.a), 'result is an array');
+ st.deepEqual(result.a, ['1', '2', '3'], 'all values present');
+ st.end();
+ });
+
+ t.test('comma-separated values exceeding arrayLimit convert to object', function (st) {
+ var result = qs.parse('a=1,2,3,4', { comma: true, arrayLimit: 3 });
+ st.notOk(Array.isArray(result.a), 'result is not an array when over limit');
+ st.deepEqual(result.a, { 0: '1', 1: '2', 2: '3', 3: '4' }, 'all values preserved as object');
+ st.end();
+ });
+
+ t.test('comma-separated values exceeding arrayLimit with throwOnLimitExceeded throws', function (st) {
+ st['throws'](
+ function () {
+ qs.parse('a=1,2,3,4', { comma: true, arrayLimit: 3, throwOnLimitExceeded: true });
+ },
+ new RangeError('Array limit exceeded. Only 3 elements allowed in an array.'),
+ 'throws error when comma-split exceeds array limit'
+ );
+
+ st['throws'](
+ function () {
+ qs.parse('a=1,2,3', { comma: true, arrayLimit: 1, throwOnLimitExceeded: true });
+ },
+ new RangeError('Array limit exceeded. Only 1 element allowed in an array.'),
+ 'throws error with singular "element" when arrayLimit is 1'
+ );
+ st.end();
+ });
+
+ t.test('comma-separated values at exactly arrayLimit stay as array', function (st) {
+ var result = qs.parse('a=1,2,3', { comma: true, arrayLimit: 3 });
+ st.ok(Array.isArray(result.a), 'result is an array when exactly at limit');
+ st.deepEqual(result.a, ['1', '2', '3'], 'all values present');
+ st.end();
+ });
+
+ t.end();
+});
+
+test('mixed array and object notation', function (t) {
+ t.test('array brackets with object key - under limit', function (st) {
+ st.deepEqual(
+ qs.parse('a[]=b&a[c]=d'),
+ { a: { 0: 'b', c: 'd' } },
+ 'mixing [] and [key] converts to object'
+ );
+ st.end();
+ });
+
+ t.test('array index with object key - under limit', function (st) {
+ st.deepEqual(
+ qs.parse('a[0]=b&a[c]=d'),
+ { a: { 0: 'b', c: 'd' } },
+ 'mixing [0] and [key] produces object'
+ );
+ st.end();
+ });
+
+ t.test('plain value with array brackets - under limit', function (st) {
+ st.deepEqual(
+ qs.parse('a=b&a[]=c', { arrayLimit: 20 }),
+ { a: ['b', 'c'] },
+ 'plain value combined with [] stays as array under limit'
+ );
+ st.end();
+ });
+
+ t.test('array brackets with plain value - under limit', function (st) {
+ st.deepEqual(
+ qs.parse('a[]=b&a=c', { arrayLimit: 20 }),
+ { a: ['b', 'c'] },
+ '[] combined with plain value stays as array under limit'
+ );
+ st.end();
+ });
+
+ t.test('plain value with array index - under limit', function (st) {
+ st.deepEqual(
+ qs.parse('a=b&a[0]=c', { arrayLimit: 20 }),
+ { a: ['b', 'c'] },
+ 'plain value combined with [0] stays as array under limit'
+ );
+ st.end();
+ });
+
+ t.test('multiple plain values with duplicates combine', function (st) {
+ st.deepEqual(
+ qs.parse('a=b&a=c&a=d', { arrayLimit: 20 }),
+ { a: ['b', 'c', 'd'] },
+ 'duplicate plain keys combine into array'
+ );
+ st.end();
+ });
+
+ t.test('multiple plain values exceeding limit', function (st) {
+ // 3 elements (indices 0-2), max index 2 > limit 1
+ st.deepEqual(
+ qs.parse('a=b&a=c&a=d', { arrayLimit: 1 }),
+ { a: { 0: 'b', 1: 'c', 2: 'd' } },
+ 'duplicate plain keys convert to object when exceeding limit'
+ );
+ st.end();
+ });
+
+ t.test('mixed notation produces consistent results when arrayLimit is exceeded', function (st) {
+ var expected = { a: { 0: 'b', 1: 'c', 2: 'd' } };
+
+ st.deepEqual(
+ qs.parse('a[]=b&a[1]=c&a=d', { arrayLimit: -1 }),
+ expected,
+ 'arrayLimit -1'
+ );
+
+ st.deepEqual(
+ qs.parse('a[]=b&a[1]=c&a=d', { arrayLimit: 0 }),
+ expected,
+ 'arrayLimit 0'
+ );
+
+ st.deepEqual(
+ qs.parse('a[]=b&a[1]=c&a=d', { arrayLimit: 1 }),
+ expected,
+ 'arrayLimit 1'
+ );
+
+ st.end();
+ });
+
+ t.test('uses existing array length for currentArrayLength when parsing object input with bracket keys', function (st) {
+ var input = {};
+ var arr = ['x', 'y'];
+ arr.a = ['z', 'w'];
+ input['a[]'] = arr;
+ st.deepEqual(qs.parse(input), { a: ['x', 'y'] }, 'parses object input with bracket keys using existing array values');
+ st.end();
+ });
+
+ t.test('throws with singular message when object input bracket key exceeds arrayLimit of 1', function (st) {
+ var input = {};
+ var arr = ['x'];
+ arr.a = ['z', 'w'];
+ input['a[]'] = arr;
+ st['throws'](
+ function () {
+ qs.parse(input, { throwOnLimitExceeded: true, arrayLimit: 1 });
+ },
+ new RangeError('Array limit exceeded. Only 1 element allowed in an array.'),
+ 'throws singular error for object input exceeding arrayLimit 1'
+ );
+ st.end();
+ });
+
+ t.end();
+});
diff --git a/node_modules/qs/test/stringify.js b/node_modules/qs/test/stringify.js
new file mode 100644
index 0000000..07324cf
--- /dev/null
+++ b/node_modules/qs/test/stringify.js
@@ -0,0 +1,1448 @@
+'use strict';
+
+var test = require('tape');
+var qs = require('../');
+var utils = require('../lib/utils');
+var iconv = require('iconv-lite');
+var SaferBuffer = require('safer-buffer').Buffer;
+var hasSymbols = require('has-symbols');
+var mockProperty = require('mock-property');
+var emptyTestCases = require('./empty-keys-cases').emptyTestCases;
+var hasProto = require('has-proto')();
+var hasBigInt = require('has-bigints')();
+
+test('stringify()', function (t) {
+ t.test('stringifies a querystring object', function (st) {
+ st.equal(qs.stringify({ a: 'b' }), 'a=b');
+ st.equal(qs.stringify({ a: 1 }), 'a=1');
+ st.equal(qs.stringify({ a: 1, b: 2 }), 'a=1&b=2');
+ st.equal(qs.stringify({ a: 'A_Z' }), 'a=A_Z');
+ st.equal(qs.stringify({ a: '€' }), 'a=%E2%82%AC');
+ st.equal(qs.stringify({ a: '' }), 'a=%EE%80%80');
+ st.equal(qs.stringify({ a: 'א' }), 'a=%D7%90');
+ st.equal(qs.stringify({ a: '𐐷' }), 'a=%F0%90%90%B7');
+ st.end();
+ });
+
+ t.test('stringifies falsy values', function (st) {
+ st.equal(qs.stringify(undefined), '');
+ st.equal(qs.stringify(null), '');
+ st.equal(qs.stringify(null, { strictNullHandling: true }), '');
+ st.equal(qs.stringify(false), '');
+ st.equal(qs.stringify(0), '');
+ st.end();
+ });
+
+ t.test('stringifies symbols', { skip: !hasSymbols() }, function (st) {
+ st.equal(qs.stringify(Symbol.iterator), '');
+ st.equal(qs.stringify([Symbol.iterator]), '0=Symbol%28Symbol.iterator%29');
+ st.equal(qs.stringify({ a: Symbol.iterator }), 'a=Symbol%28Symbol.iterator%29');
+ st.equal(
+ qs.stringify({ a: [Symbol.iterator] }, { encodeValuesOnly: true, arrayFormat: 'brackets' }),
+ 'a[]=Symbol%28Symbol.iterator%29'
+ );
+ st.end();
+ });
+
+ t.test('stringifies bigints', { skip: !hasBigInt }, function (st) {
+ var three = BigInt(3);
+ var encodeWithN = function (value, defaultEncoder, charset) {
+ var result = defaultEncoder(value, defaultEncoder, charset);
+ return typeof value === 'bigint' ? result + 'n' : result;
+ };
+ st.equal(qs.stringify(three), '');
+ st.equal(qs.stringify([three]), '0=3');
+ st.equal(qs.stringify([three], { encoder: encodeWithN }), '0=3n');
+ st.equal(qs.stringify({ a: three }), 'a=3');
+ st.equal(qs.stringify({ a: three }, { encoder: encodeWithN }), 'a=3n');
+ st.equal(
+ qs.stringify({ a: [three] }, { encodeValuesOnly: true, arrayFormat: 'brackets' }),
+ 'a[]=3'
+ );
+ st.equal(
+ qs.stringify({ a: [three] }, { encodeValuesOnly: true, encoder: encodeWithN, arrayFormat: 'brackets' }),
+ 'a[]=3n'
+ );
+ st.end();
+ });
+
+ t.test('encodes dot in key of object when encodeDotInKeys and allowDots is provided', function (st) {
+ st.equal(
+ qs.stringify(
+ { 'name.obj': { first: 'John', last: 'Doe' } },
+ { allowDots: false, encodeDotInKeys: false }
+ ),
+ 'name.obj%5Bfirst%5D=John&name.obj%5Blast%5D=Doe',
+ 'with allowDots false and encodeDotInKeys false'
+ );
+ st.equal(
+ qs.stringify(
+ { 'name.obj': { first: 'John', last: 'Doe' } },
+ { allowDots: true, encodeDotInKeys: false }
+ ),
+ 'name.obj.first=John&name.obj.last=Doe',
+ 'with allowDots true and encodeDotInKeys false'
+ );
+ st.equal(
+ qs.stringify(
+ { 'name.obj': { first: 'John', last: 'Doe' } },
+ { allowDots: false, encodeDotInKeys: true }
+ ),
+ 'name%252Eobj%5Bfirst%5D=John&name%252Eobj%5Blast%5D=Doe',
+ 'with allowDots false and encodeDotInKeys true'
+ );
+ st.equal(
+ qs.stringify(
+ { 'name.obj': { first: 'John', last: 'Doe' } },
+ { allowDots: true, encodeDotInKeys: true }
+ ),
+ 'name%252Eobj.first=John&name%252Eobj.last=Doe',
+ 'with allowDots true and encodeDotInKeys true'
+ );
+
+ st.equal(
+ qs.stringify(
+ { 'name.obj.subobject': { 'first.godly.name': 'John', last: 'Doe' } },
+ { allowDots: false, encodeDotInKeys: false }
+ ),
+ 'name.obj.subobject%5Bfirst.godly.name%5D=John&name.obj.subobject%5Blast%5D=Doe',
+ 'with allowDots false and encodeDotInKeys false'
+ );
+ st.equal(
+ qs.stringify(
+ { 'name.obj.subobject': { 'first.godly.name': 'John', last: 'Doe' } },
+ { allowDots: true, encodeDotInKeys: false }
+ ),
+ 'name.obj.subobject.first.godly.name=John&name.obj.subobject.last=Doe',
+ 'with allowDots false and encodeDotInKeys false'
+ );
+ st.equal(
+ qs.stringify(
+ { 'name.obj.subobject': { 'first.godly.name': 'John', last: 'Doe' } },
+ { allowDots: false, encodeDotInKeys: true }
+ ),
+ 'name%252Eobj%252Esubobject%5Bfirst.godly.name%5D=John&name%252Eobj%252Esubobject%5Blast%5D=Doe',
+ 'with allowDots false and encodeDotInKeys true'
+ );
+ st.equal(
+ qs.stringify(
+ { 'name.obj.subobject': { 'first.godly.name': 'John', last: 'Doe' } },
+ { allowDots: true, encodeDotInKeys: true }
+ ),
+ 'name%252Eobj%252Esubobject.first%252Egodly%252Ename=John&name%252Eobj%252Esubobject.last=Doe',
+ 'with allowDots true and encodeDotInKeys true'
+ );
+
+ st.end();
+ });
+
+ t.test('should encode dot in key of object, and automatically set allowDots to `true` when encodeDotInKeys is true and allowDots in undefined', function (st) {
+ st.equal(
+ qs.stringify(
+ { 'name.obj.subobject': { 'first.godly.name': 'John', last: 'Doe' } },
+ { encodeDotInKeys: true }
+ ),
+ 'name%252Eobj%252Esubobject.first%252Egodly%252Ename=John&name%252Eobj%252Esubobject.last=Doe',
+ 'with allowDots undefined and encodeDotInKeys true'
+ );
+ st.end();
+ });
+
+ t.test('should encode dot in key of object when encodeDotInKeys and allowDots is provided, and nothing else when encodeValuesOnly is provided', function (st) {
+ st.equal(
+ qs.stringify({ 'name.obj': { first: 'John', last: 'Doe' } }, {
+ encodeDotInKeys: true, allowDots: true, encodeValuesOnly: true
+ }),
+ 'name%2Eobj.first=John&name%2Eobj.last=Doe'
+ );
+
+ st.equal(
+ qs.stringify({ 'name.obj.subobject': { 'first.godly.name': 'John', last: 'Doe' } }, { allowDots: true, encodeDotInKeys: true, encodeValuesOnly: true }),
+ 'name%2Eobj%2Esubobject.first%2Egodly%2Ename=John&name%2Eobj%2Esubobject.last=Doe'
+ );
+
+ st.end();
+ });
+
+ t.test('throws when `commaRoundTrip` is not a boolean', function (st) {
+ st['throws'](
+ function () { qs.stringify({}, { commaRoundTrip: 'not a boolean' }); },
+ TypeError,
+ 'throws when `commaRoundTrip` is not a boolean'
+ );
+
+ st.end();
+ });
+
+ t.test('throws when `encodeDotInKeys` is not a boolean', function (st) {
+ st['throws'](
+ function () { qs.stringify({ a: [], b: 'zz' }, { encodeDotInKeys: 'foobar' }); },
+ TypeError
+ );
+
+ st['throws'](
+ function () { qs.stringify({ a: [], b: 'zz' }, { encodeDotInKeys: 0 }); },
+ TypeError
+ );
+
+ st['throws'](
+ function () { qs.stringify({ a: [], b: 'zz' }, { encodeDotInKeys: NaN }); },
+ TypeError
+ );
+
+ st['throws'](
+ function () { qs.stringify({ a: [], b: 'zz' }, { encodeDotInKeys: null }); },
+ TypeError
+ );
+
+ st.end();
+ });
+
+ t.test('adds query prefix', function (st) {
+ st.equal(qs.stringify({ a: 'b' }, { addQueryPrefix: true }), '?a=b');
+ st.end();
+ });
+
+ t.test('with query prefix, outputs blank string given an empty object', function (st) {
+ st.equal(qs.stringify({}, { addQueryPrefix: true }), '');
+ st.end();
+ });
+
+ t.test('stringifies nested falsy values', function (st) {
+ st.equal(qs.stringify({ a: { b: { c: null } } }), 'a%5Bb%5D%5Bc%5D=');
+ st.equal(qs.stringify({ a: { b: { c: null } } }, { strictNullHandling: true }), 'a%5Bb%5D%5Bc%5D');
+ st.equal(qs.stringify({ a: { b: { c: false } } }), 'a%5Bb%5D%5Bc%5D=false');
+ st.end();
+ });
+
+ t.test('stringifies a nested object', function (st) {
+ st.equal(qs.stringify({ a: { b: 'c' } }), 'a%5Bb%5D=c');
+ st.equal(qs.stringify({ a: { b: { c: { d: 'e' } } } }), 'a%5Bb%5D%5Bc%5D%5Bd%5D=e');
+ st.end();
+ });
+
+ t.test('`allowDots` option: stringifies a nested object with dots notation', function (st) {
+ st.equal(qs.stringify({ a: { b: 'c' } }, { allowDots: true }), 'a.b=c');
+ st.equal(qs.stringify({ a: { b: { c: { d: 'e' } } } }, { allowDots: true }), 'a.b.c.d=e');
+ st.end();
+ });
+
+ t.test('stringifies an array value', function (st) {
+ st.equal(
+ qs.stringify({ a: ['b', 'c', 'd'] }, { arrayFormat: 'indices' }),
+ 'a%5B0%5D=b&a%5B1%5D=c&a%5B2%5D=d',
+ 'indices => indices'
+ );
+ st.equal(
+ qs.stringify({ a: ['b', 'c', 'd'] }, { arrayFormat: 'brackets' }),
+ 'a%5B%5D=b&a%5B%5D=c&a%5B%5D=d',
+ 'brackets => brackets'
+ );
+ st.equal(
+ qs.stringify({ a: ['b', 'c', 'd'] }, { arrayFormat: 'comma' }),
+ 'a=b%2Cc%2Cd',
+ 'comma => comma'
+ );
+ st.equal(
+ qs.stringify({ a: ['b', 'c', 'd'] }, { arrayFormat: 'comma', commaRoundTrip: true }),
+ 'a=b%2Cc%2Cd',
+ 'comma round trip => comma'
+ );
+ st.equal(
+ qs.stringify({ a: ['b', 'c', 'd'] }),
+ 'a%5B0%5D=b&a%5B1%5D=c&a%5B2%5D=d',
+ 'default => indices'
+ );
+ st.end();
+ });
+
+ t.test('`skipNulls` option', function (st) {
+ st.equal(
+ qs.stringify({ a: 'b', c: null }, { skipNulls: true }),
+ 'a=b',
+ 'omits nulls when asked'
+ );
+
+ st.equal(
+ qs.stringify({ a: { b: 'c', d: null } }, { skipNulls: true }),
+ 'a%5Bb%5D=c',
+ 'omits nested nulls when asked'
+ );
+
+ st.end();
+ });
+
+ t.test('omits array indices when asked', function (st) {
+ st.equal(qs.stringify({ a: ['b', 'c', 'd'] }, { indices: false }), 'a=b&a=c&a=d');
+
+ st.end();
+ });
+
+ t.test('omits object key/value pair when value is empty array', function (st) {
+ st.equal(qs.stringify({ a: [], b: 'zz' }), 'b=zz');
+
+ st.end();
+ });
+
+ t.test('should not omit object key/value pair when value is empty array and when asked', function (st) {
+ st.equal(qs.stringify({ a: [], b: 'zz' }), 'b=zz');
+ st.equal(qs.stringify({ a: [], b: 'zz' }, { allowEmptyArrays: false }), 'b=zz');
+ st.equal(qs.stringify({ a: [], b: 'zz' }, { allowEmptyArrays: true }), 'a[]&b=zz');
+
+ st.end();
+ });
+
+ t.test('should throw when allowEmptyArrays is not of type boolean', function (st) {
+ st['throws'](
+ function () { qs.stringify({ a: [], b: 'zz' }, { allowEmptyArrays: 'foobar' }); },
+ TypeError
+ );
+
+ st['throws'](
+ function () { qs.stringify({ a: [], b: 'zz' }, { allowEmptyArrays: 0 }); },
+ TypeError
+ );
+
+ st['throws'](
+ function () { qs.stringify({ a: [], b: 'zz' }, { allowEmptyArrays: NaN }); },
+ TypeError
+ );
+
+ st['throws'](
+ function () { qs.stringify({ a: [], b: 'zz' }, { allowEmptyArrays: null }); },
+ TypeError
+ );
+
+ st.end();
+ });
+
+ t.test('allowEmptyArrays + strictNullHandling', function (st) {
+ st.equal(
+ qs.stringify(
+ { testEmptyArray: [] },
+ { strictNullHandling: true, allowEmptyArrays: true }
+ ),
+ 'testEmptyArray[]'
+ );
+
+ st.end();
+ });
+
+ t.test('stringifies an array value with one item vs multiple items', function (st) {
+ st.test('non-array item', function (s2t) {
+ s2t.equal(qs.stringify({ a: 'c' }, { encodeValuesOnly: true, arrayFormat: 'indices' }), 'a=c');
+ s2t.equal(qs.stringify({ a: 'c' }, { encodeValuesOnly: true, arrayFormat: 'brackets' }), 'a=c');
+ s2t.equal(qs.stringify({ a: 'c' }, { encodeValuesOnly: true, arrayFormat: 'comma' }), 'a=c');
+ s2t.equal(qs.stringify({ a: 'c' }, { encodeValuesOnly: true }), 'a=c');
+
+ s2t.end();
+ });
+
+ st.test('array with a single item', function (s2t) {
+ s2t.equal(qs.stringify({ a: ['c'] }, { encodeValuesOnly: true, arrayFormat: 'indices' }), 'a[0]=c');
+ s2t.equal(qs.stringify({ a: ['c'] }, { encodeValuesOnly: true, arrayFormat: 'brackets' }), 'a[]=c');
+ s2t.equal(qs.stringify({ a: ['c'] }, { encodeValuesOnly: true, arrayFormat: 'comma' }), 'a=c');
+ s2t.equal(qs.stringify({ a: ['c'] }, { encodeValuesOnly: true, arrayFormat: 'comma', commaRoundTrip: true }), 'a[]=c'); // so it parses back as an array
+ s2t.equal(qs.stringify({ a: ['c'] }, { encodeValuesOnly: true }), 'a[0]=c');
+
+ s2t.end();
+ });
+
+ st.test('array with multiple items', function (s2t) {
+ s2t.equal(qs.stringify({ a: ['c', 'd'] }, { encodeValuesOnly: true, arrayFormat: 'indices' }), 'a[0]=c&a[1]=d');
+ s2t.equal(qs.stringify({ a: ['c', 'd'] }, { encodeValuesOnly: true, arrayFormat: 'brackets' }), 'a[]=c&a[]=d');
+ s2t.equal(qs.stringify({ a: ['c', 'd'] }, { encodeValuesOnly: true, arrayFormat: 'comma' }), 'a=c,d');
+ s2t.equal(qs.stringify({ a: ['c', 'd'] }, { encodeValuesOnly: true, arrayFormat: 'comma', commaRoundTrip: true }), 'a=c,d');
+ s2t.equal(qs.stringify({ a: ['c', 'd'] }, { encodeValuesOnly: true }), 'a[0]=c&a[1]=d');
+
+ s2t.end();
+ });
+
+ st.test('array with multiple items with a comma inside', function (s2t) {
+ s2t.equal(qs.stringify({ a: ['c,d', 'e'] }, { encodeValuesOnly: true, arrayFormat: 'comma' }), 'a=c%2Cd,e');
+ s2t.equal(qs.stringify({ a: ['c,d', 'e'] }, { arrayFormat: 'comma' }), 'a=c%2Cd%2Ce');
+
+ s2t.equal(qs.stringify({ a: ['c,d', 'e'] }, { encodeValuesOnly: true, arrayFormat: 'comma', commaRoundTrip: true }), 'a=c%2Cd,e');
+ s2t.equal(qs.stringify({ a: ['c,d', 'e'] }, { arrayFormat: 'comma', commaRoundTrip: true }), 'a=c%2Cd%2Ce');
+
+ s2t.end();
+ });
+
+ st.end();
+ });
+
+ t.test('stringifies a nested array value', function (st) {
+ st.equal(qs.stringify({ a: { b: ['c', 'd'] } }, { encodeValuesOnly: true, arrayFormat: 'indices' }), 'a[b][0]=c&a[b][1]=d');
+ st.equal(qs.stringify({ a: { b: ['c', 'd'] } }, { encodeValuesOnly: true, arrayFormat: 'brackets' }), 'a[b][]=c&a[b][]=d');
+ st.equal(qs.stringify({ a: { b: ['c', 'd'] } }, { encodeValuesOnly: true, arrayFormat: 'comma' }), 'a[b]=c,d');
+ st.equal(qs.stringify({ a: { b: ['c', 'd'] } }, { encodeValuesOnly: true }), 'a[b][0]=c&a[b][1]=d');
+ st.end();
+ });
+
+ t.test('stringifies comma and empty array values', function (st) {
+ st.equal(qs.stringify({ a: [',', '', 'c,d%'] }, { encode: false, arrayFormat: 'indices' }), 'a[0]=,&a[1]=&a[2]=c,d%');
+ st.equal(qs.stringify({ a: [',', '', 'c,d%'] }, { encode: false, arrayFormat: 'brackets' }), 'a[]=,&a[]=&a[]=c,d%');
+ st.equal(qs.stringify({ a: [',', '', 'c,d%'] }, { encode: false, arrayFormat: 'comma' }), 'a=,,,c,d%');
+ st.equal(qs.stringify({ a: [',', '', 'c,d%'] }, { encode: false, arrayFormat: 'repeat' }), 'a=,&a=&a=c,d%');
+
+ st.equal(qs.stringify({ a: [',', '', 'c,d%'] }, { encode: true, encodeValuesOnly: true, arrayFormat: 'indices' }), 'a[0]=%2C&a[1]=&a[2]=c%2Cd%25');
+ st.equal(qs.stringify({ a: [',', '', 'c,d%'] }, { encode: true, encodeValuesOnly: true, arrayFormat: 'brackets' }), 'a[]=%2C&a[]=&a[]=c%2Cd%25');
+ st.equal(qs.stringify({ a: [',', '', 'c,d%'] }, { encode: true, encodeValuesOnly: true, arrayFormat: 'comma' }), 'a=%2C,,c%2Cd%25');
+ st.equal(qs.stringify({ a: [',', '', 'c,d%'] }, { encode: true, encodeValuesOnly: true, arrayFormat: 'repeat' }), 'a=%2C&a=&a=c%2Cd%25');
+
+ st.equal(qs.stringify({ a: [',', '', 'c,d%'] }, { encode: true, encodeValuesOnly: false, arrayFormat: 'indices' }), 'a%5B0%5D=%2C&a%5B1%5D=&a%5B2%5D=c%2Cd%25');
+ st.equal(qs.stringify({ a: [',', '', 'c,d%'] }, { encode: true, encodeValuesOnly: false, arrayFormat: 'brackets' }), 'a%5B%5D=%2C&a%5B%5D=&a%5B%5D=c%2Cd%25');
+ st.equal(qs.stringify({ a: [',', '', 'c,d%'] }, { encode: true, encodeValuesOnly: false, arrayFormat: 'comma' }), 'a=%2C%2C%2Cc%2Cd%25');
+ st.equal(qs.stringify({ a: [',', '', 'c,d%'] }, { encode: true, encodeValuesOnly: false, arrayFormat: 'repeat' }), 'a=%2C&a=&a=c%2Cd%25');
+
+ st.end();
+ });
+
+ t.test('stringifies comma and empty non-array values', function (st) {
+ st.equal(qs.stringify({ a: ',', b: '', c: 'c,d%' }, { encode: false, arrayFormat: 'indices' }), 'a=,&b=&c=c,d%');
+ st.equal(qs.stringify({ a: ',', b: '', c: 'c,d%' }, { encode: false, arrayFormat: 'brackets' }), 'a=,&b=&c=c,d%');
+ st.equal(qs.stringify({ a: ',', b: '', c: 'c,d%' }, { encode: false, arrayFormat: 'comma' }), 'a=,&b=&c=c,d%');
+ st.equal(qs.stringify({ a: ',', b: '', c: 'c,d%' }, { encode: false, arrayFormat: 'repeat' }), 'a=,&b=&c=c,d%');
+
+ st.equal(qs.stringify({ a: ',', b: '', c: 'c,d%' }, { encode: true, encodeValuesOnly: true, arrayFormat: 'indices' }), 'a=%2C&b=&c=c%2Cd%25');
+ st.equal(qs.stringify({ a: ',', b: '', c: 'c,d%' }, { encode: true, encodeValuesOnly: true, arrayFormat: 'brackets' }), 'a=%2C&b=&c=c%2Cd%25');
+ st.equal(qs.stringify({ a: ',', b: '', c: 'c,d%' }, { encode: true, encodeValuesOnly: true, arrayFormat: 'comma' }), 'a=%2C&b=&c=c%2Cd%25');
+ st.equal(qs.stringify({ a: ',', b: '', c: 'c,d%' }, { encode: true, encodeValuesOnly: true, arrayFormat: 'repeat' }), 'a=%2C&b=&c=c%2Cd%25');
+
+ st.equal(qs.stringify({ a: ',', b: '', c: 'c,d%' }, { encode: true, encodeValuesOnly: false, arrayFormat: 'indices' }), 'a=%2C&b=&c=c%2Cd%25');
+ st.equal(qs.stringify({ a: ',', b: '', c: 'c,d%' }, { encode: true, encodeValuesOnly: false, arrayFormat: 'brackets' }), 'a=%2C&b=&c=c%2Cd%25');
+ st.equal(qs.stringify({ a: ',', b: '', c: 'c,d%' }, { encode: true, encodeValuesOnly: false, arrayFormat: 'comma' }), 'a=%2C&b=&c=c%2Cd%25');
+ st.equal(qs.stringify({ a: ',', b: '', c: 'c,d%' }, { encode: true, encodeValuesOnly: false, arrayFormat: 'repeat' }), 'a=%2C&b=&c=c%2Cd%25');
+
+ st.end();
+ });
+
+ t.test('stringifies a nested array value with dots notation', function (st) {
+ st.equal(
+ qs.stringify(
+ { a: { b: ['c', 'd'] } },
+ { allowDots: true, encodeValuesOnly: true, arrayFormat: 'indices' }
+ ),
+ 'a.b[0]=c&a.b[1]=d',
+ 'indices: stringifies with dots + indices'
+ );
+ st.equal(
+ qs.stringify(
+ { a: { b: ['c', 'd'] } },
+ { allowDots: true, encodeValuesOnly: true, arrayFormat: 'brackets' }
+ ),
+ 'a.b[]=c&a.b[]=d',
+ 'brackets: stringifies with dots + brackets'
+ );
+ st.equal(
+ qs.stringify(
+ { a: { b: ['c', 'd'] } },
+ { allowDots: true, encodeValuesOnly: true, arrayFormat: 'comma' }
+ ),
+ 'a.b=c,d',
+ 'comma: stringifies with dots + comma'
+ );
+ st.equal(
+ qs.stringify(
+ { a: { b: ['c', 'd'] } },
+ { allowDots: true, encodeValuesOnly: true }
+ ),
+ 'a.b[0]=c&a.b[1]=d',
+ 'default: stringifies with dots + indices'
+ );
+ st.end();
+ });
+
+ t.test('stringifies an object inside an array', function (st) {
+ st.equal(
+ qs.stringify({ a: [{ b: 'c' }] }, { arrayFormat: 'indices', encodeValuesOnly: true }),
+ 'a[0][b]=c',
+ 'indices => indices'
+ );
+ st.equal(
+ qs.stringify({ a: [{ b: 'c' }] }, { arrayFormat: 'repeat', encodeValuesOnly: true }),
+ 'a[b]=c',
+ 'repeat => repeat'
+ );
+ st.equal(
+ qs.stringify({ a: [{ b: 'c' }] }, { arrayFormat: 'brackets', encodeValuesOnly: true }),
+ 'a[][b]=c',
+ 'brackets => brackets'
+ );
+ st.equal(
+ qs.stringify({ a: [{ b: 'c' }] }, { encodeValuesOnly: true }),
+ 'a[0][b]=c',
+ 'default => indices'
+ );
+
+ st.equal(
+ qs.stringify({ a: [{ b: { c: [1] } }] }, { arrayFormat: 'indices', encodeValuesOnly: true }),
+ 'a[0][b][c][0]=1',
+ 'indices => indices'
+ );
+ st.equal(
+ qs.stringify({ a: [{ b: { c: [1] } }] }, { arrayFormat: 'repeat', encodeValuesOnly: true }),
+ 'a[b][c]=1',
+ 'repeat => repeat'
+ );
+ st.equal(
+ qs.stringify({ a: [{ b: { c: [1] } }] }, { arrayFormat: 'brackets', encodeValuesOnly: true }),
+ 'a[][b][c][]=1',
+ 'brackets => brackets'
+ );
+ st.equal(
+ qs.stringify({ a: [{ b: { c: [1] } }] }, { encodeValuesOnly: true }),
+ 'a[0][b][c][0]=1',
+ 'default => indices'
+ );
+
+ st.end();
+ });
+
+ t.test('stringifies an array with mixed objects and primitives', function (st) {
+ st.equal(
+ qs.stringify({ a: [{ b: 1 }, 2, 3] }, { encodeValuesOnly: true, arrayFormat: 'indices' }),
+ 'a[0][b]=1&a[1]=2&a[2]=3',
+ 'indices => indices'
+ );
+ st.equal(
+ qs.stringify({ a: [{ b: 1 }, 2, 3] }, { encodeValuesOnly: true, arrayFormat: 'brackets' }),
+ 'a[][b]=1&a[]=2&a[]=3',
+ 'brackets => brackets'
+ );
+ st.equal(
+ qs.stringify({ a: [{ b: 1 }, 2, 3] }, { encodeValuesOnly: true, arrayFormat: 'comma' }),
+ '???',
+ 'brackets => brackets',
+ { skip: 'TODO: figure out what this should do' }
+ );
+ st.equal(
+ qs.stringify({ a: [{ b: 1 }, 2, 3] }, { encodeValuesOnly: true }),
+ 'a[0][b]=1&a[1]=2&a[2]=3',
+ 'default => indices'
+ );
+
+ st.end();
+ });
+
+ t.test('stringifies an object inside an array with dots notation', function (st) {
+ st.equal(
+ qs.stringify(
+ { a: [{ b: 'c' }] },
+ { allowDots: true, encode: false, arrayFormat: 'indices' }
+ ),
+ 'a[0].b=c',
+ 'indices => indices'
+ );
+ st.equal(
+ qs.stringify(
+ { a: [{ b: 'c' }] },
+ { allowDots: true, encode: false, arrayFormat: 'brackets' }
+ ),
+ 'a[].b=c',
+ 'brackets => brackets'
+ );
+ st.equal(
+ qs.stringify(
+ { a: [{ b: 'c' }] },
+ { allowDots: true, encode: false }
+ ),
+ 'a[0].b=c',
+ 'default => indices'
+ );
+
+ st.equal(
+ qs.stringify(
+ { a: [{ b: { c: [1] } }] },
+ { allowDots: true, encode: false, arrayFormat: 'indices' }
+ ),
+ 'a[0].b.c[0]=1',
+ 'indices => indices'
+ );
+ st.equal(
+ qs.stringify(
+ { a: [{ b: { c: [1] } }] },
+ { allowDots: true, encode: false, arrayFormat: 'brackets' }
+ ),
+ 'a[].b.c[]=1',
+ 'brackets => brackets'
+ );
+ st.equal(
+ qs.stringify(
+ { a: [{ b: { c: [1] } }] },
+ { allowDots: true, encode: false }
+ ),
+ 'a[0].b.c[0]=1',
+ 'default => indices'
+ );
+
+ st.end();
+ });
+
+ t.test('does not omit object keys when indices = false', function (st) {
+ st.equal(qs.stringify({ a: [{ b: 'c' }] }, { indices: false }), 'a%5Bb%5D=c');
+ st.end();
+ });
+
+ t.test('uses indices notation for arrays when indices=true', function (st) {
+ st.equal(qs.stringify({ a: ['b', 'c'] }, { indices: true }), 'a%5B0%5D=b&a%5B1%5D=c');
+ st.end();
+ });
+
+ t.test('uses indices notation for arrays when no arrayFormat is specified', function (st) {
+ st.equal(qs.stringify({ a: ['b', 'c'] }), 'a%5B0%5D=b&a%5B1%5D=c');
+ st.end();
+ });
+
+ t.test('uses indices notation for arrays when arrayFormat=indices', function (st) {
+ st.equal(qs.stringify({ a: ['b', 'c'] }, { arrayFormat: 'indices' }), 'a%5B0%5D=b&a%5B1%5D=c');
+ st.end();
+ });
+
+ t.test('uses repeat notation for arrays when arrayFormat=repeat', function (st) {
+ st.equal(qs.stringify({ a: ['b', 'c'] }, { arrayFormat: 'repeat' }), 'a=b&a=c');
+ st.end();
+ });
+
+ t.test('uses brackets notation for arrays when arrayFormat=brackets', function (st) {
+ st.equal(qs.stringify({ a: ['b', 'c'] }, { arrayFormat: 'brackets' }), 'a%5B%5D=b&a%5B%5D=c');
+ st.end();
+ });
+
+ t.test('stringifies a complicated object', function (st) {
+ st.equal(qs.stringify({ a: { b: 'c', d: 'e' } }), 'a%5Bb%5D=c&a%5Bd%5D=e');
+ st.end();
+ });
+
+ t.test('stringifies an empty value', function (st) {
+ st.equal(qs.stringify({ a: '' }), 'a=');
+ st.equal(qs.stringify({ a: null }, { strictNullHandling: true }), 'a');
+
+ st.equal(qs.stringify({ a: '', b: '' }), 'a=&b=');
+ st.equal(qs.stringify({ a: null, b: '' }, { strictNullHandling: true }), 'a&b=');
+
+ st.equal(qs.stringify({ a: { b: '' } }), 'a%5Bb%5D=');
+ st.equal(qs.stringify({ a: { b: null } }, { strictNullHandling: true }), 'a%5Bb%5D');
+ st.equal(qs.stringify({ a: { b: null } }, { strictNullHandling: false }), 'a%5Bb%5D=');
+
+ st.end();
+ });
+
+ t.test('stringifies an empty array in different arrayFormat', function (st) {
+ st.equal(qs.stringify({ a: [], b: [null], c: 'c' }, { encode: false }), 'b[0]=&c=c');
+ // arrayFormat default
+ st.equal(qs.stringify({ a: [], b: [null], c: 'c' }, { encode: false, arrayFormat: 'indices' }), 'b[0]=&c=c');
+ st.equal(qs.stringify({ a: [], b: [null], c: 'c' }, { encode: false, arrayFormat: 'brackets' }), 'b[]=&c=c');
+ st.equal(qs.stringify({ a: [], b: [null], c: 'c' }, { encode: false, arrayFormat: 'repeat' }), 'b=&c=c');
+ st.equal(qs.stringify({ a: [], b: [null], c: 'c' }, { encode: false, arrayFormat: 'comma' }), 'b=&c=c');
+ st.equal(qs.stringify({ a: [], b: [null], c: 'c' }, { encode: false, arrayFormat: 'comma', commaRoundTrip: true }), 'b[]=&c=c');
+ // with strictNullHandling
+ st.equal(qs.stringify({ a: [], b: [null], c: 'c' }, { encode: false, arrayFormat: 'indices', strictNullHandling: true }), 'b[0]&c=c');
+ st.equal(qs.stringify({ a: [], b: [null], c: 'c' }, { encode: false, arrayFormat: 'brackets', strictNullHandling: true }), 'b[]&c=c');
+ st.equal(qs.stringify({ a: [], b: [null], c: 'c' }, { encode: false, arrayFormat: 'repeat', strictNullHandling: true }), 'b&c=c');
+ st.equal(qs.stringify({ a: [], b: [null], c: 'c' }, { encode: false, arrayFormat: 'comma', strictNullHandling: true }), 'b&c=c');
+ st.equal(qs.stringify({ a: [], b: [null], c: 'c' }, { encode: false, arrayFormat: 'comma', strictNullHandling: true, commaRoundTrip: true }), 'b[]&c=c');
+ // with skipNulls
+ st.equal(qs.stringify({ a: [], b: [null], c: 'c' }, { encode: false, arrayFormat: 'indices', skipNulls: true }), 'c=c');
+ st.equal(qs.stringify({ a: [], b: [null], c: 'c' }, { encode: false, arrayFormat: 'brackets', skipNulls: true }), 'c=c');
+ st.equal(qs.stringify({ a: [], b: [null], c: 'c' }, { encode: false, arrayFormat: 'repeat', skipNulls: true }), 'c=c');
+ st.equal(qs.stringify({ a: [], b: [null], c: 'c' }, { encode: false, arrayFormat: 'comma', skipNulls: true }), 'c=c');
+
+ st.end();
+ });
+
+ t.test('does not crash on null/undefined entries in arrayFormat=comma with encodeValuesOnly', function (st) {
+ st.doesNotThrow(
+ function () { qs.stringify({ a: [null, 'b'] }, { arrayFormat: 'comma', encodeValuesOnly: true }); },
+ 'does not pass a raw null array entry to the encoder'
+ );
+ st.doesNotThrow(
+ function () { qs.stringify({ a: [undefined, 'b'] }, { arrayFormat: 'comma', encodeValuesOnly: true }); },
+ 'does not pass a raw undefined array entry to the encoder'
+ );
+ st.doesNotThrow(
+ function () { qs.stringify({ a: [null] }, { arrayFormat: 'comma', encodeValuesOnly: true }); },
+ 'does not crash on a single-null array'
+ );
+
+ st.equal(
+ qs.stringify({ a: [null, 'b'] }, { arrayFormat: 'comma', encodeValuesOnly: true }),
+ 'a=,b',
+ 'null entry joins as empty, comma stays unencoded under encodeValuesOnly'
+ );
+ st.equal(
+ qs.stringify({ a: [undefined, 'b'] }, { arrayFormat: 'comma', encodeValuesOnly: true }),
+ 'a=,b',
+ 'undefined entry joins as empty, comma stays unencoded under encodeValuesOnly'
+ );
+ st.equal(
+ qs.stringify({ a: [null] }, { arrayFormat: 'comma', encodeValuesOnly: true }),
+ 'a=',
+ 'single-null array stringifies as empty value'
+ );
+ st.equal(
+ qs.stringify({ a: [null] }, { arrayFormat: 'comma', encodeValuesOnly: true, strictNullHandling: true }),
+ 'a',
+ 'strictNullHandling drops the equals sign for a single-null array'
+ );
+ st.equal(
+ qs.stringify({ a: [null] }, { arrayFormat: 'comma', encodeValuesOnly: true, skipNulls: true }),
+ '',
+ 'skipNulls drops a single-null array entirely'
+ );
+
+ st.end();
+ });
+
+ t.test('stringifies a null object', { skip: !hasProto }, function (st) {
+ st.equal(qs.stringify({ __proto__: null, a: 'b' }), 'a=b');
+ st.end();
+ });
+
+ t.test('returns an empty string for invalid input', function (st) {
+ st.equal(qs.stringify(undefined), '');
+ st.equal(qs.stringify(false), '');
+ st.equal(qs.stringify(null), '');
+ st.equal(qs.stringify(''), '');
+ st.end();
+ });
+
+ t.test('stringifies an object with a null object as a child', { skip: !hasProto }, function (st) {
+ st.equal(qs.stringify({ a: { __proto__: null, b: 'c' } }), 'a%5Bb%5D=c');
+ st.end();
+ });
+
+ t.test('drops keys with a value of undefined', function (st) {
+ st.equal(qs.stringify({ a: undefined }), '');
+
+ st.equal(qs.stringify({ a: { b: undefined, c: null } }, { strictNullHandling: true }), 'a%5Bc%5D');
+ st.equal(qs.stringify({ a: { b: undefined, c: null } }, { strictNullHandling: false }), 'a%5Bc%5D=');
+ st.equal(qs.stringify({ a: { b: undefined, c: '' } }), 'a%5Bc%5D=');
+ st.end();
+ });
+
+ t.test('url encodes values', function (st) {
+ st.equal(qs.stringify({ a: 'b c' }), 'a=b%20c');
+ st.end();
+ });
+
+ t.test('stringifies a date', function (st) {
+ var now = new Date();
+ var str = 'a=' + encodeURIComponent(now.toISOString());
+ st.equal(qs.stringify({ a: now }), str);
+ st.end();
+ });
+
+ t.test('stringifies the weird object from qs', function (st) {
+ st.equal(qs.stringify({ 'my weird field': '~q1!2"\'w$5&7/z8)?' }), 'my%20weird%20field=~q1%212%22%27w%245%267%2Fz8%29%3F');
+ st.end();
+ });
+
+ t.test('skips properties that are part of the object prototype', function (st) {
+ st.intercept(Object.prototype, 'crash', { value: 'test' });
+
+ st.equal(qs.stringify({ a: 'b' }), 'a=b');
+ st.equal(qs.stringify({ a: { b: 'c' } }), 'a%5Bb%5D=c');
+
+ st.end();
+ });
+
+ t.test('stringifies boolean values', function (st) {
+ st.equal(qs.stringify({ a: true }), 'a=true');
+ st.equal(qs.stringify({ a: { b: true } }), 'a%5Bb%5D=true');
+ st.equal(qs.stringify({ b: false }), 'b=false');
+ st.equal(qs.stringify({ b: { c: false } }), 'b%5Bc%5D=false');
+ st.end();
+ });
+
+ t.test('stringifies buffer values', function (st) {
+ st.equal(qs.stringify({ a: SaferBuffer.from('test') }), 'a=test');
+ st.equal(qs.stringify({ a: { b: SaferBuffer.from('test') } }), 'a%5Bb%5D=test');
+ st.end();
+ });
+
+ t.test('stringifies an object using an alternative delimiter', function (st) {
+ st.equal(qs.stringify({ a: 'b', c: 'd' }, { delimiter: ';' }), 'a=b;c=d');
+ st.end();
+ });
+
+ t.test('does not blow up when Buffer global is missing', function (st) {
+ var restore = mockProperty(global, 'Buffer', { 'delete': true });
+
+ var result = qs.stringify({ a: 'b', c: 'd' });
+
+ restore();
+
+ st.equal(result, 'a=b&c=d');
+ st.end();
+ });
+
+ t.test('does not crash when parsing circular references', function (st) {
+ var a = {};
+ a.b = a;
+
+ st['throws'](
+ function () { qs.stringify({ 'foo[bar]': 'baz', 'foo[baz]': a }); },
+ /RangeError: Cyclic object value/,
+ 'cyclic values throw'
+ );
+
+ var circular = {
+ a: 'value'
+ };
+ circular.a = circular;
+ st['throws'](
+ function () { qs.stringify(circular); },
+ /RangeError: Cyclic object value/,
+ 'cyclic values throw'
+ );
+
+ var arr = ['a'];
+ st.doesNotThrow(
+ function () { qs.stringify({ x: arr, y: arr }); },
+ 'non-cyclic values do not throw'
+ );
+
+ st.end();
+ });
+
+ t.test('non-circular duplicated references can still work', function (st) {
+ var hourOfDay = {
+ 'function': 'hour_of_day'
+ };
+
+ var p1 = {
+ 'function': 'gte',
+ arguments: [hourOfDay, 0]
+ };
+ var p2 = {
+ 'function': 'lte',
+ arguments: [hourOfDay, 23]
+ };
+
+ st.equal(
+ qs.stringify({ filters: { $and: [p1, p2] } }, { encodeValuesOnly: true, arrayFormat: 'indices' }),
+ 'filters[$and][0][function]=gte&filters[$and][0][arguments][0][function]=hour_of_day&filters[$and][0][arguments][1]=0&filters[$and][1][function]=lte&filters[$and][1][arguments][0][function]=hour_of_day&filters[$and][1][arguments][1]=23'
+ );
+ st.equal(
+ qs.stringify({ filters: { $and: [p1, p2] } }, { encodeValuesOnly: true, arrayFormat: 'brackets' }),
+ 'filters[$and][][function]=gte&filters[$and][][arguments][][function]=hour_of_day&filters[$and][][arguments][]=0&filters[$and][][function]=lte&filters[$and][][arguments][][function]=hour_of_day&filters[$and][][arguments][]=23'
+ );
+ st.equal(
+ qs.stringify({ filters: { $and: [p1, p2] } }, { encodeValuesOnly: true, arrayFormat: 'repeat' }),
+ 'filters[$and][function]=gte&filters[$and][arguments][function]=hour_of_day&filters[$and][arguments]=0&filters[$and][function]=lte&filters[$and][arguments][function]=hour_of_day&filters[$and][arguments]=23'
+ );
+
+ st.end();
+ });
+
+ t.test('selects properties when filter=array', function (st) {
+ st.equal(qs.stringify({ a: 'b' }, { filter: ['a'] }), 'a=b');
+ st.equal(qs.stringify({ a: 1 }, { filter: [] }), '');
+
+ st.equal(
+ qs.stringify(
+ { a: { b: [1, 2, 3, 4], c: 'd' }, c: 'f' },
+ { filter: ['a', 'b', 0, 2], arrayFormat: 'indices' }
+ ),
+ 'a%5Bb%5D%5B0%5D=1&a%5Bb%5D%5B2%5D=3',
+ 'indices => indices'
+ );
+ st.equal(
+ qs.stringify(
+ { a: { b: [1, 2, 3, 4], c: 'd' }, c: 'f' },
+ { filter: ['a', 'b', 0, 2], arrayFormat: 'brackets' }
+ ),
+ 'a%5Bb%5D%5B%5D=1&a%5Bb%5D%5B%5D=3',
+ 'brackets => brackets'
+ );
+ st.equal(
+ qs.stringify(
+ { a: { b: [1, 2, 3, 4], c: 'd' }, c: 'f' },
+ { filter: ['a', 'b', 0, 2] }
+ ),
+ 'a%5Bb%5D%5B0%5D=1&a%5Bb%5D%5B2%5D=3',
+ 'default => indices'
+ );
+
+ st.end();
+ });
+
+ t.test('skips null/undefined entries in filter=array', function (st) {
+ st.doesNotThrow(
+ function () { qs.stringify({ a: 'b', undefined: 'x' }, { filter: ['a', undefined] }); },
+ 'does not pass a raw undefined filter entry to the encoder'
+ );
+ st.doesNotThrow(
+ function () { qs.stringify({ a: 'b', 'null': 'x' }, { filter: ['a', null] }); },
+ 'does not pass a raw null filter entry to the encoder'
+ );
+
+ st.equal(
+ qs.stringify({ a: 'b', undefined: 'x', c: 'd' }, { filter: ['a', undefined, 'c'] }),
+ 'a=b&c=d',
+ 'undefined filter entry is skipped, remaining keys are kept'
+ );
+ st.equal(
+ qs.stringify({ a: 'b', 'null': 'x', c: 'd' }, { filter: ['a', null, 'c'] }),
+ 'a=b&c=d',
+ 'null filter entry is skipped, remaining keys are kept'
+ );
+ st.equal(
+ qs.stringify({ a: 'b', 'null': 'x' }, { filter: [null] }),
+ '',
+ 'filter array containing only null yields empty string'
+ );
+
+ st.end();
+ });
+
+ t.test('supports custom representations when filter=function', function (st) {
+ var calls = 0;
+ var obj = { a: 'b', c: 'd', e: { f: new Date(1257894000000) } };
+ var filterFunc = function (prefix, value) {
+ calls += 1;
+ if (calls === 1) {
+ st.equal(prefix, '', 'prefix is empty');
+ st.equal(value, obj);
+ } else if (prefix === 'c') {
+ return void 0;
+ } else if (value instanceof Date) {
+ st.equal(prefix, 'e[f]');
+ return value.getTime();
+ }
+ return value;
+ };
+
+ st.equal(qs.stringify(obj, { filter: filterFunc }), 'a=b&e%5Bf%5D=1257894000000');
+ st.equal(calls, 5);
+ st.end();
+ });
+
+ t.test('can disable uri encoding', function (st) {
+ st.equal(qs.stringify({ a: 'b' }, { encode: false }), 'a=b');
+ st.equal(qs.stringify({ a: { b: 'c' } }, { encode: false }), 'a[b]=c');
+ st.equal(qs.stringify({ a: 'b', c: null }, { strictNullHandling: true, encode: false }), 'a=b&c');
+ st.end();
+ });
+
+ t.test('can sort the keys', function (st) {
+ var sort = function (a, b) {
+ return a.localeCompare(b);
+ };
+ st.equal(qs.stringify({ a: 'c', z: 'y', b: 'f' }, { sort: sort }), 'a=c&b=f&z=y');
+ st.equal(qs.stringify({ a: 'c', z: { j: 'a', i: 'b' }, b: 'f' }, { sort: sort }), 'a=c&b=f&z%5Bi%5D=b&z%5Bj%5D=a');
+ st.end();
+ });
+
+ t.test('can sort the keys at depth 3 or more too', function (st) {
+ var sort = function (a, b) {
+ return a.localeCompare(b);
+ };
+ st.equal(
+ qs.stringify(
+ { a: 'a', z: { zj: { zjb: 'zjb', zja: 'zja' }, zi: { zib: 'zib', zia: 'zia' } }, b: 'b' },
+ { sort: sort, encode: false }
+ ),
+ 'a=a&b=b&z[zi][zia]=zia&z[zi][zib]=zib&z[zj][zja]=zja&z[zj][zjb]=zjb'
+ );
+ st.equal(
+ qs.stringify(
+ { a: 'a', z: { zj: { zjb: 'zjb', zja: 'zja' }, zi: { zib: 'zib', zia: 'zia' } }, b: 'b' },
+ { sort: null, encode: false }
+ ),
+ 'a=a&z[zj][zjb]=zjb&z[zj][zja]=zja&z[zi][zib]=zib&z[zi][zia]=zia&b=b'
+ );
+ st.end();
+ });
+
+ t.test('can stringify with custom encoding', function (st) {
+ st.equal(qs.stringify({ 県: '大阪府', '': '' }, {
+ encoder: function (str) {
+ if (str.length === 0) {
+ return '';
+ }
+ var buf = iconv.encode(str, 'shiftjis');
+ var result = [];
+ for (var i = 0; i < buf.length; ++i) {
+ result.push(buf.readUInt8(i).toString(16));
+ }
+ return '%' + result.join('%');
+ }
+ }), '%8c%a7=%91%e5%8d%e3%95%7b&=');
+ st.end();
+ });
+
+ t.test('receives the default encoder as a second argument', function (st) {
+ st.plan(8);
+
+ qs.stringify({ a: 1, b: new Date(), c: true, d: [1] }, {
+ encoder: function (str) {
+ st.match(typeof str, /^(?:string|number|boolean)$/);
+ return '';
+ }
+ });
+
+ st.end();
+ });
+
+ t.test('receives the default encoder as a second argument', function (st) {
+ st.plan(2);
+
+ qs.stringify({ a: 1 }, {
+ encoder: function (str, defaultEncoder) {
+ st.equal(defaultEncoder, utils.encode);
+ }
+ });
+
+ st.end();
+ });
+
+ t.test('throws error with wrong encoder', function (st) {
+ st['throws'](function () {
+ qs.stringify({}, { encoder: 'string' });
+ }, new TypeError('Encoder has to be a function.'));
+ st.end();
+ });
+
+ t.test('can use custom encoder for a buffer object', { skip: typeof Buffer === 'undefined' }, function (st) {
+ st.equal(qs.stringify({ a: SaferBuffer.from([1]) }, {
+ encoder: function (buffer) {
+ if (typeof buffer === 'string') {
+ return buffer;
+ }
+ return String.fromCharCode(buffer.readUInt8(0) + 97);
+ }
+ }), 'a=b');
+
+ st.equal(qs.stringify({ a: SaferBuffer.from('a b') }, {
+ encoder: function (buffer) {
+ return buffer;
+ }
+ }), 'a=a b');
+ st.end();
+ });
+
+ t.test('serializeDate option', function (st) {
+ var date = new Date();
+ st.equal(
+ qs.stringify({ a: date }),
+ 'a=' + date.toISOString().replace(/:/g, '%3A'),
+ 'default is toISOString'
+ );
+
+ var mutatedDate = new Date();
+ mutatedDate.toISOString = function () {
+ throw new SyntaxError();
+ };
+ st['throws'](function () {
+ mutatedDate.toISOString();
+ }, SyntaxError);
+ st.equal(
+ qs.stringify({ a: mutatedDate }),
+ 'a=' + Date.prototype.toISOString.call(mutatedDate).replace(/:/g, '%3A'),
+ 'toISOString works even when method is not locally present'
+ );
+
+ var specificDate = new Date(6);
+ st.equal(
+ qs.stringify(
+ { a: specificDate },
+ { serializeDate: function (d) { return d.getTime() * 7; } }
+ ),
+ 'a=42',
+ 'custom serializeDate function called'
+ );
+
+ st.equal(
+ qs.stringify(
+ { a: [date] },
+ {
+ serializeDate: function (d) { return d.getTime(); },
+ arrayFormat: 'comma'
+ }
+ ),
+ 'a=' + date.getTime(),
+ 'works with arrayFormat comma'
+ );
+ st.equal(
+ qs.stringify(
+ { a: [date] },
+ {
+ serializeDate: function (d) { return d.getTime(); },
+ arrayFormat: 'comma',
+ commaRoundTrip: true
+ }
+ ),
+ 'a%5B%5D=' + date.getTime(),
+ 'works with arrayFormat comma'
+ );
+
+ st.end();
+ });
+
+ t.test('RFC 1738 serialization', function (st) {
+ st.equal(qs.stringify({ a: 'b c' }, { format: qs.formats.RFC1738 }), 'a=b+c');
+ st.equal(qs.stringify({ 'a b': 'c d' }, { format: qs.formats.RFC1738 }), 'a+b=c+d');
+ st.equal(qs.stringify({ 'a b': SaferBuffer.from('a b') }, { format: qs.formats.RFC1738 }), 'a+b=a+b');
+
+ st.equal(qs.stringify({ 'foo(ref)': 'bar' }, { format: qs.formats.RFC1738 }), 'foo(ref)=bar');
+
+ st.end();
+ });
+
+ t.test('RFC 3986 spaces serialization', function (st) {
+ st.equal(qs.stringify({ a: 'b c' }, { format: qs.formats.RFC3986 }), 'a=b%20c');
+ st.equal(qs.stringify({ 'a b': 'c d' }, { format: qs.formats.RFC3986 }), 'a%20b=c%20d');
+ st.equal(qs.stringify({ 'a b': SaferBuffer.from('a b') }, { format: qs.formats.RFC3986 }), 'a%20b=a%20b');
+
+ st.end();
+ });
+
+ t.test('Backward compatibility to RFC 3986', function (st) {
+ st.equal(qs.stringify({ a: 'b c' }), 'a=b%20c');
+ st.equal(qs.stringify({ 'a b': SaferBuffer.from('a b') }), 'a%20b=a%20b');
+
+ st.end();
+ });
+
+ t.test('Edge cases and unknown formats', function (st) {
+ ['UFO1234', false, 1234, null, {}, []].forEach(function (format) {
+ st['throws'](
+ function () {
+ qs.stringify({ a: 'b c' }, { format: format });
+ },
+ new TypeError('Unknown format option provided.')
+ );
+ });
+ st.end();
+ });
+
+ t.test('encodeValuesOnly', function (st) {
+ st.equal(
+ qs.stringify(
+ { a: 'b', c: ['d', 'e=f'], f: [['g'], ['h']] },
+ { encodeValuesOnly: true, arrayFormat: 'indices' }
+ ),
+ 'a=b&c[0]=d&c[1]=e%3Df&f[0][0]=g&f[1][0]=h',
+ 'encodeValuesOnly + indices'
+ );
+ st.equal(
+ qs.stringify(
+ { a: 'b', c: ['d', 'e=f'], f: [['g'], ['h']] },
+ { encodeValuesOnly: true, arrayFormat: 'brackets' }
+ ),
+ 'a=b&c[]=d&c[]=e%3Df&f[][]=g&f[][]=h',
+ 'encodeValuesOnly + brackets'
+ );
+ st.equal(
+ qs.stringify(
+ { a: 'b', c: ['d', 'e=f'], f: [['g'], ['h']] },
+ { encodeValuesOnly: true, arrayFormat: 'repeat' }
+ ),
+ 'a=b&c=d&c=e%3Df&f=g&f=h',
+ 'encodeValuesOnly + repeat'
+ );
+
+ st.equal(
+ qs.stringify(
+ { a: 'b', c: ['d', 'e'], f: [['g'], ['h']] },
+ { arrayFormat: 'indices' }
+ ),
+ 'a=b&c%5B0%5D=d&c%5B1%5D=e&f%5B0%5D%5B0%5D=g&f%5B1%5D%5B0%5D=h',
+ 'no encodeValuesOnly + indices'
+ );
+ st.equal(
+ qs.stringify(
+ { a: 'b', c: ['d', 'e'], f: [['g'], ['h']] },
+ { arrayFormat: 'brackets' }
+ ),
+ 'a=b&c%5B%5D=d&c%5B%5D=e&f%5B%5D%5B%5D=g&f%5B%5D%5B%5D=h',
+ 'no encodeValuesOnly + brackets'
+ );
+ st.equal(
+ qs.stringify(
+ { a: 'b', c: ['d', 'e'], f: [['g'], ['h']] },
+ { arrayFormat: 'repeat' }
+ ),
+ 'a=b&c=d&c=e&f=g&f=h',
+ 'no encodeValuesOnly + repeat'
+ );
+
+ st.end();
+ });
+
+ t.test('encodeValuesOnly - strictNullHandling', function (st) {
+ st.equal(
+ qs.stringify(
+ { a: { b: null } },
+ { encodeValuesOnly: true, strictNullHandling: true }
+ ),
+ 'a[b]'
+ );
+ st.end();
+ });
+
+ t.test('strictNullHandling: applies the formatter to the encoded key (RFC1738)', function (st) {
+ st.equal(
+ qs.stringify(
+ { 'a b': null, 'c d': 'e f' },
+ { strictNullHandling: false, format: 'RFC1738' }
+ ),
+ 'a+b=&c+d=e+f',
+ 'without: as expected'
+ );
+
+ st.equal(
+ qs.stringify(
+ { 'a b': null, 'c d': 'e f' },
+ { strictNullHandling: true, format: 'RFC1738' }
+ ),
+ 'a+b&c+d=e+f',
+ 'with: as expected'
+ );
+
+ st.end();
+ });
+
+ t.test('throws if an invalid charset is specified', function (st) {
+ st['throws'](function () {
+ qs.stringify({ a: 'b' }, { charset: 'foobar' });
+ }, new TypeError('The charset option must be either utf-8, iso-8859-1, or undefined'));
+ st.end();
+ });
+
+ t.test('respects a charset of iso-8859-1', function (st) {
+ st.equal(qs.stringify({ æ: 'æ' }, { charset: 'iso-8859-1' }), '%E6=%E6');
+ st.end();
+ });
+
+ t.test('encodes unrepresentable chars as numeric entities in iso-8859-1 mode', function (st) {
+ st.equal(qs.stringify({ a: '☺' }, { charset: 'iso-8859-1' }), 'a=%26%239786%3B');
+ st.end();
+ });
+
+ t.test('respects an explicit charset of utf-8 (the default)', function (st) {
+ st.equal(qs.stringify({ a: 'æ' }, { charset: 'utf-8' }), 'a=%C3%A6');
+ st.end();
+ });
+
+ t.test('`charsetSentinel` option', function (st) {
+ st.equal(
+ qs.stringify({ a: 'æ' }, { charsetSentinel: true, charset: 'utf-8' }),
+ 'utf8=%E2%9C%93&a=%C3%A6',
+ 'adds the right sentinel when instructed to and the charset is utf-8'
+ );
+
+ st.equal(
+ qs.stringify({ a: 'æ' }, { charsetSentinel: true, charset: 'iso-8859-1' }),
+ 'utf8=%26%2310003%3B&a=%E6',
+ 'adds the right sentinel when instructed to and the charset is iso-8859-1'
+ );
+
+ st.equal(
+ qs.stringify({ a: 1, b: 2 }, { charsetSentinel: true, delimiter: ';' }),
+ 'utf8=%E2%9C%93;a=1;b=2',
+ 'uses the configured delimiter after the sentinel'
+ );
+
+ st.end();
+ });
+
+ t.test('does not mutate the options argument', function (st) {
+ var options = {};
+ qs.stringify({}, options);
+ st.deepEqual(options, {});
+ st.end();
+ });
+
+ t.test('strictNullHandling works with custom filter', function (st) {
+ var filter = function (prefix, value) {
+ return value;
+ };
+
+ var options = { strictNullHandling: true, filter: filter };
+ st.equal(qs.stringify({ key: null }, options), 'key');
+ st.end();
+ });
+
+ t.test('strictNullHandling works with null serializeDate', function (st) {
+ var serializeDate = function () {
+ return null;
+ };
+ var options = { strictNullHandling: true, serializeDate: serializeDate };
+ var date = new Date();
+ st.equal(qs.stringify({ key: date }, options), 'key');
+ st.end();
+ });
+
+ t.test('allows for encoding keys and values differently', function (st) {
+ var encoder = function (str, defaultEncoder, charset, type) {
+ if (type === 'key') {
+ return defaultEncoder(str, defaultEncoder, charset, type).toLowerCase();
+ }
+ if (type === 'value') {
+ return defaultEncoder(str, defaultEncoder, charset, type).toUpperCase();
+ }
+ throw 'this should never happen! type: ' + type;
+ };
+
+ st.deepEqual(qs.stringify({ KeY: 'vAlUe' }, { encoder: encoder }), 'key=VALUE');
+
+ var noopEncoder = function () { return 'x'; };
+ noopEncoder();
+ st['throws'](
+ function () { encoder('x', noopEncoder, 'utf-8', 'unknown'); },
+ 'this should never happen! type: unknown',
+ 'encoder throws for unexpected type'
+ );
+
+ st.end();
+ });
+
+ t.test('objects inside arrays', function (st) {
+ var obj = { a: { b: { c: 'd', e: 'f' } } };
+ var withArray = { a: { b: [{ c: 'd', e: 'f' }] } };
+
+ st.equal(qs.stringify(obj, { encode: false }), 'a[b][c]=d&a[b][e]=f', 'no array, no arrayFormat');
+ st.equal(qs.stringify(obj, { encode: false, arrayFormat: 'brackets' }), 'a[b][c]=d&a[b][e]=f', 'no array, bracket');
+ st.equal(qs.stringify(obj, { encode: false, arrayFormat: 'indices' }), 'a[b][c]=d&a[b][e]=f', 'no array, indices');
+ st.equal(qs.stringify(obj, { encode: false, arrayFormat: 'repeat' }), 'a[b][c]=d&a[b][e]=f', 'no array, repeat');
+ st.equal(qs.stringify(obj, { encode: false, arrayFormat: 'comma' }), 'a[b][c]=d&a[b][e]=f', 'no array, comma');
+
+ st.equal(qs.stringify(withArray, { encode: false }), 'a[b][0][c]=d&a[b][0][e]=f', 'array, no arrayFormat');
+ st.equal(qs.stringify(withArray, { encode: false, arrayFormat: 'brackets' }), 'a[b][][c]=d&a[b][][e]=f', 'array, bracket');
+ st.equal(qs.stringify(withArray, { encode: false, arrayFormat: 'indices' }), 'a[b][0][c]=d&a[b][0][e]=f', 'array, indices');
+ st.equal(qs.stringify(withArray, { encode: false, arrayFormat: 'repeat' }), 'a[b][c]=d&a[b][e]=f', 'array, repeat');
+ st.equal(
+ qs.stringify(withArray, { encode: false, arrayFormat: 'comma' }),
+ '???',
+ 'array, comma',
+ { skip: 'TODO: figure out what this should do' }
+ );
+
+ st.end();
+ });
+
+ t.test('stringifies sparse arrays', function (st) {
+ /* eslint no-sparse-arrays: 0 */
+ st.equal(qs.stringify({ a: [, '2', , , '1'] }, { encodeValuesOnly: true, arrayFormat: 'indices' }), 'a[1]=2&a[4]=1');
+ st.equal(qs.stringify({ a: [, '2', , , '1'] }, { encodeValuesOnly: true, arrayFormat: 'brackets' }), 'a[]=2&a[]=1');
+ st.equal(qs.stringify({ a: [, '2', , , '1'] }, { encodeValuesOnly: true, arrayFormat: 'repeat' }), 'a=2&a=1');
+
+ st.equal(qs.stringify({ a: [, { b: [, , { c: '1' }] }] }, { encodeValuesOnly: true, arrayFormat: 'indices' }), 'a[1][b][2][c]=1');
+ st.equal(qs.stringify({ a: [, { b: [, , { c: '1' }] }] }, { encodeValuesOnly: true, arrayFormat: 'brackets' }), 'a[][b][][c]=1');
+ st.equal(qs.stringify({ a: [, { b: [, , { c: '1' }] }] }, { encodeValuesOnly: true, arrayFormat: 'repeat' }), 'a[b][c]=1');
+
+ st.equal(qs.stringify({ a: [, [, , [, , , { c: '1' }]]] }, { encodeValuesOnly: true, arrayFormat: 'indices' }), 'a[1][2][3][c]=1');
+ st.equal(qs.stringify({ a: [, [, , [, , , { c: '1' }]]] }, { encodeValuesOnly: true, arrayFormat: 'brackets' }), 'a[][][][c]=1');
+ st.equal(qs.stringify({ a: [, [, , [, , , { c: '1' }]]] }, { encodeValuesOnly: true, arrayFormat: 'repeat' }), 'a[c]=1');
+
+ st.equal(qs.stringify({ a: [, [, , [, , , { c: [, '1'] }]]] }, { encodeValuesOnly: true, arrayFormat: 'indices' }), 'a[1][2][3][c][1]=1');
+ st.equal(qs.stringify({ a: [, [, , [, , , { c: [, '1'] }]]] }, { encodeValuesOnly: true, arrayFormat: 'brackets' }), 'a[][][][c][]=1');
+ st.equal(qs.stringify({ a: [, [, , [, , , { c: [, '1'] }]]] }, { encodeValuesOnly: true, arrayFormat: 'repeat' }), 'a[c]=1');
+
+ st.end();
+ });
+
+ t.test('encodes a very long string', function (st) {
+ var chars = [];
+ var expected = [];
+ for (var i = 0; i < 5e3; i++) {
+ chars.push(' ' + i);
+
+ expected.push('%20' + i);
+ }
+
+ var obj = {
+ foo: chars.join('')
+ };
+
+ st.equal(
+ qs.stringify(obj, { arrayFormat: 'brackets', charset: 'utf-8' }),
+ 'foo=' + expected.join('')
+ );
+
+ st.end();
+ });
+
+ t.end();
+});
+
+test('stringifies empty keys', function (t) {
+ emptyTestCases.forEach(function (testCase) {
+ t.test('stringifies an object with empty string key with ' + testCase.input, function (st) {
+ st.deepEqual(
+ qs.stringify(testCase.withEmptyKeys, { encode: false, arrayFormat: 'indices' }),
+ testCase.stringifyOutput.indices,
+ 'test case: ' + testCase.input + ', indices'
+ );
+ st.deepEqual(
+ qs.stringify(testCase.withEmptyKeys, { encode: false, arrayFormat: 'brackets' }),
+ testCase.stringifyOutput.brackets,
+ 'test case: ' + testCase.input + ', brackets'
+ );
+ st.deepEqual(
+ qs.stringify(testCase.withEmptyKeys, { encode: false, arrayFormat: 'repeat' }),
+ testCase.stringifyOutput.repeat,
+ 'test case: ' + testCase.input + ', repeat'
+ );
+
+ st.end();
+ });
+ });
+
+ t.test('edge case with object/arrays', function (st) {
+ st.deepEqual(qs.stringify({ '': { '': [2, 3] } }, { encode: false }), '[][0]=2&[][1]=3');
+ st.deepEqual(qs.stringify({ '': { '': [2, 3], a: 2 } }, { encode: false }), '[][0]=2&[][1]=3&[a]=2');
+ st.deepEqual(qs.stringify({ '': { '': [2, 3] } }, { encode: false, arrayFormat: 'indices' }), '[][0]=2&[][1]=3');
+ st.deepEqual(qs.stringify({ '': { '': [2, 3], a: 2 } }, { encode: false, arrayFormat: 'indices' }), '[][0]=2&[][1]=3&[a]=2');
+
+ st.end();
+ });
+
+ t.test('stringifies non-string keys', function (st) {
+ var S = Object('abc');
+ S.toString = function () {
+ return 'd';
+ };
+ var actual = qs.stringify({ a: 'b', 'false': {}, 1e+22: 'c', d: 'e' }, {
+ filter: ['a', false, null, 10000000000000000000000, S],
+ allowDots: true,
+ encodeDotInKeys: true
+ });
+
+ st.equal(actual, 'a=b&1e%2B22=c&d=e', 'stringifies correctly');
+
+ st.end();
+ });
+
+ t.test('round-trips keys containing percent-encoded bracket text', function (st) {
+ var cases = [
+ { 'a%5Bb': 'c' },
+ { 'a%5Db': 'c' },
+ { 'a%255Bb': 'c' },
+ { 'a%255Db': 'c' },
+ { a: { 'b%5Bc': 'd' } },
+ { a: { 'b%255Bc': 'd' } },
+ { 'a%5B%255Bb': 'c' }
+ ];
+ for (var i = 0; i < cases.length; i++) {
+ st.deepEqual(
+ qs.parse(qs.stringify(cases[i])),
+ cases[i],
+ 'round-trips ' + JSON.stringify(cases[i])
+ );
+ }
+
+ st.end();
+ });
+
+ t.test('parses input containing percent-encoded bracket text without mangling', function (st) {
+ st.deepEqual(qs.parse('a%25255Bb=c'), { 'a%255Bb': 'c' }, 'a%25255Bb decodes to a%255Bb, not a%5Bb');
+ st.deepEqual(qs.parse('a%25255Db=c'), { 'a%255Db': 'c' }, 'a%25255Db decodes to a%255Db, not a%5Db');
+ st.deepEqual(qs.parse('a%5Bb%25255Bc%5D=d'), { a: { 'b%255Bc': 'd' } }, 'nested %25255B decodes to %255B inside segment, not %5B');
+
+ st.end();
+ });
+});
diff --git a/node_modules/qs/test/utils.js b/node_modules/qs/test/utils.js
new file mode 100644
index 0000000..713b89b
--- /dev/null
+++ b/node_modules/qs/test/utils.js
@@ -0,0 +1,626 @@
+'use strict';
+
+var test = require('tape');
+var inspect = require('object-inspect');
+var SaferBuffer = require('safer-buffer').Buffer;
+var forEach = require('for-each');
+var v = require('es-value-fixtures');
+
+var utils = require('../lib/utils');
+
+test('merge()', function (t) {
+ t.deepEqual(utils.merge(null, true), [null, true], 'merges true into null');
+
+ t.deepEqual(utils.merge(null, [42]), [null, 42], 'merges null into an array');
+
+ t.deepEqual(utils.merge({ a: 'b' }, { a: 'c' }), { a: ['b', 'c'] }, 'merges two objects with the same key');
+
+ var oneMerged = utils.merge({ foo: 'bar' }, { foo: { first: '123' } });
+ t.deepEqual(oneMerged, { foo: ['bar', { first: '123' }] }, 'merges a standalone and an object into an array');
+
+ var twoMerged = utils.merge({ foo: ['bar', { first: '123' }] }, { foo: { second: '456' } });
+ t.deepEqual(twoMerged, { foo: { 0: 'bar', 1: { first: '123' }, second: '456' } }, 'merges a standalone and two objects into an array');
+
+ var sandwiched = utils.merge({ foo: ['bar', { first: '123', second: '456' }] }, { foo: 'baz' });
+ t.deepEqual(sandwiched, { foo: ['bar', { first: '123', second: '456' }, 'baz'] }, 'merges an object sandwiched by two standalones into an array');
+
+ var nestedArrays = utils.merge({ foo: ['baz'] }, { foo: ['bar', 'xyzzy'] });
+ t.deepEqual(nestedArrays, { foo: ['baz', 'bar', 'xyzzy'] });
+
+ var noOptionsNonObjectSource = utils.merge({ foo: 'baz' }, 'bar');
+ t.deepEqual(noOptionsNonObjectSource, { foo: 'baz', bar: true });
+
+ var func = function f() {};
+ func();
+ t.deepEqual(
+ utils.merge(func, { foo: 'bar' }),
+ [func, { foo: 'bar' }],
+ 'functions can not be merged into'
+ );
+
+ func.bar = 'baz';
+ t.deepEqual(
+ utils.merge({ foo: 'bar' }, func),
+ { foo: 'bar', bar: 'baz' },
+ 'functions can be merge sources'
+ );
+
+ t.test(
+ 'avoids invoking array setters unnecessarily',
+ { skip: typeof Object.defineProperty !== 'function' },
+ function (st) {
+ var setCount = 0;
+ var getCount = 0;
+ var observed = [];
+ Object.defineProperty(observed, 0, {
+ get: function () {
+ getCount += 1;
+ return { bar: 'baz' };
+ },
+ set: function () { setCount += 1; }
+ });
+ utils.merge(observed, [null]);
+ st.equal(setCount, 0);
+ st.equal(getCount, 1);
+ observed[0] = observed[0]; // eslint-disable-line no-self-assign
+ st.equal(setCount, 1);
+ st.equal(getCount, 2);
+
+ st.end();
+ }
+ );
+
+ t.test('with overflow objects (from arrayLimit)', function (st) {
+ // arrayLimit is max index, so with limit 0, max index 0 is allowed (1 element)
+ // To create overflow, need 2+ elements with limit 0, or 3+ with limit 1, etc.
+ st.test('merges primitive into overflow object at next index', function (s2t) {
+ // Create an overflow object via combine: 3 elements (indices 0-2) with limit 0
+ var overflow = utils.combine(['a', 'b'], 'c', 0, false);
+ s2t.ok(utils.isOverflow(overflow), 'overflow object is marked');
+ var merged = utils.merge(overflow, 'd');
+ s2t.deepEqual(merged, { 0: 'a', 1: 'b', 2: 'c', 3: 'd' }, 'adds primitive at next numeric index');
+
+ s2t.end();
+ });
+
+ st.test('merges primitive into regular object with numeric keys normally', function (s2t) {
+ var obj = { 0: 'a', 1: 'b' };
+ s2t.notOk(utils.isOverflow(obj), 'plain object is not marked as overflow');
+ var merged = utils.merge(obj, 'c');
+ s2t.deepEqual(merged, { 0: 'a', 1: 'b', c: true }, 'adds primitive as key (not at next index)');
+ s2t.end();
+ });
+
+ st.test('merges primitive into object with non-numeric keys normally', function (s2t) {
+ var obj = { foo: 'bar' };
+ var merged = utils.merge(obj, 'baz');
+ s2t.deepEqual(merged, { foo: 'bar', baz: true }, 'adds primitive as key with value true');
+
+ s2t.end();
+ });
+
+ st.test('with strictMerge, wraps object and primitive in array', function (s2t) {
+ var obj = { foo: 'bar' };
+ var merged = utils.merge(obj, 'baz', { strictMerge: true });
+ s2t.deepEqual(merged, [{ foo: 'bar' }, 'baz'], 'wraps in array with strictMerge');
+ s2t.end();
+ });
+
+ st.test('merges overflow object into primitive', function (s2t) {
+ // Create an overflow object via combine: 2 elements (indices 0-1) with limit 0
+ var overflow = utils.combine(['a'], 'b', 0, false);
+ s2t.ok(utils.isOverflow(overflow), 'overflow object is marked');
+ var merged = utils.merge('c', overflow);
+ s2t.ok(utils.isOverflow(merged), 'result is also marked as overflow');
+ s2t.deepEqual(merged, { 0: 'c', 1: 'a', 2: 'b' }, 'creates object with primitive at 0, source values shifted');
+
+ s2t.end();
+ });
+
+ st.test('merges overflow object into primitive with plainObjects', function (s2t) {
+ var overflow = utils.combine(['a'], 'b', 0, false);
+ s2t.ok(utils.isOverflow(overflow), 'overflow object is marked');
+ var merged = utils.merge('c', overflow, { plainObjects: true });
+ s2t.ok(utils.isOverflow(merged), 'result is also marked as overflow');
+ s2t.deepEqual(merged, { __proto__: null, 0: 'c', 1: 'a', 2: 'b' }, 'creates null-proto object with primitive at 0');
+
+ s2t.end();
+ });
+
+ st.test('merges overflow object with multiple values into primitive', function (s2t) {
+ // Create an overflow object via combine: 3 elements (indices 0-2) with limit 0
+ var overflow = utils.combine(['b', 'c'], 'd', 0, false);
+ s2t.ok(utils.isOverflow(overflow), 'overflow object is marked');
+ var merged = utils.merge('a', overflow);
+ s2t.deepEqual(merged, { 0: 'a', 1: 'b', 2: 'c', 3: 'd' }, 'shifts all source indices by 1');
+
+ s2t.end();
+ });
+
+ st.test('merges regular object into primitive as array', function (s2t) {
+ var obj = { foo: 'bar' };
+ var merged = utils.merge('a', obj);
+ s2t.deepEqual(merged, ['a', { foo: 'bar' }], 'creates array with primitive and object');
+
+ s2t.end();
+ });
+
+ st.test('merges primitive into array that exceeds arrayLimit', function (s2t) {
+ var arr = ['a', 'b', 'c'];
+ var merged = utils.merge(arr, 'd', { arrayLimit: 1 });
+ s2t.ok(utils.isOverflow(merged), 'result is marked as overflow');
+ s2t.deepEqual(merged, { 0: 'a', 1: 'b', 2: 'c', 3: 'd' }, 'converts to overflow object with primitive appended');
+
+ s2t.end();
+ });
+
+ st.test('merges array into primitive that exceeds arrayLimit', function (s2t) {
+ var merged = utils.merge('a', ['b', 'c'], { arrayLimit: 1 });
+ s2t.ok(utils.isOverflow(merged), 'result is marked as overflow');
+ s2t.deepEqual(merged, { 0: 'a', 1: 'b', 2: 'c' }, 'converts to overflow object');
+
+ s2t.end();
+ });
+
+ st.test('merges primitive into array at the arrayLimit boundary, consistently with combine', function (s2t) {
+ var merged = utils.merge(['a'], 'b', { arrayLimit: 1 });
+ s2t.ok(utils.isOverflow(merged), 'result is marked as overflow at the boundary');
+ s2t.deepEqual(merged, { 0: 'a', 1: 'b' }, 'converts to overflow object instead of a length-2 array');
+
+ s2t.end();
+ });
+
+ st.test('merges two arrays that exceed arrayLimit into an overflow object', function (s2t) {
+ var merged = utils.merge(['a'], ['b'], { arrayLimit: 1 });
+ s2t.ok(utils.isOverflow(merged), 'result is marked as overflow');
+ s2t.deepEqual(merged, { 0: 'a', 1: 'b' }, 'array-into-array merge enforces arrayLimit like combine');
+
+ s2t.end();
+ });
+
+ st.test('throws at the arrayLimit boundary when merging a primitive into an array with throwOnLimitExceeded', function (s2t) {
+ s2t['throws'](
+ function () { utils.merge(['a'], 'b', { arrayLimit: 1, throwOnLimitExceeded: true }); },
+ new RangeError('Array limit exceeded. Only 1 element allowed in an array.'),
+ 'throws when the resulting length would exceed arrayLimit'
+ );
+
+ s2t.end();
+ });
+
+ st.test('throws when merging two arrays past arrayLimit with throwOnLimitExceeded', function (s2t) {
+ s2t['throws'](
+ function () { utils.merge(['a'], ['b'], { arrayLimit: 1, throwOnLimitExceeded: true }); },
+ new RangeError('Array limit exceeded. Only 1 element allowed in an array.'),
+ 'array-into-array merge throws rather than silently exceeding arrayLimit'
+ );
+ s2t['throws'](
+ function () { utils.merge(['a', 'b', 'c'], ['d', 'e', 'f'], { arrayLimit: 2, throwOnLimitExceeded: true }); },
+ new RangeError('Array limit exceeded. Only 2 elements allowed in an array.'),
+ 'uses the plural message when arrayLimit is not 1'
+ );
+
+ s2t.end();
+ });
+
+ st.test('throws instead of merging primitive into over-limit array when throwOnLimitExceeded is set', function (s2t) {
+ s2t['throws'](
+ function () { utils.merge(['a', 'b', 'c'], 'd', { arrayLimit: 1, throwOnLimitExceeded: true }); },
+ new RangeError('Array limit exceeded. Only 1 element allowed in an array.'),
+ 'throws rather than converting to an overflow object'
+ );
+ s2t['throws'](
+ function () { utils.merge(['a', 'b', 'c'], 'd', { arrayLimit: 2, throwOnLimitExceeded: true }); },
+ new RangeError('Array limit exceeded. Only 2 elements allowed in an array.'),
+ 'uses the plural message when arrayLimit is not 1'
+ );
+
+ s2t.end();
+ });
+
+ st.test('throws instead of merging array into primitive when throwOnLimitExceeded is set', function (s2t) {
+ s2t['throws'](
+ function () { utils.merge('a', ['b', 'c'], { arrayLimit: 1, throwOnLimitExceeded: true }); },
+ new RangeError('Array limit exceeded. Only 1 element allowed in an array.'),
+ 'throws rather than converting to an overflow object'
+ );
+ s2t['throws'](
+ function () { utils.merge('a', ['b', 'c', 'd'], { arrayLimit: 2, throwOnLimitExceeded: true }); },
+ new RangeError('Array limit exceeded. Only 2 elements allowed in an array.'),
+ 'uses the plural message when arrayLimit is not 1'
+ );
+
+ s2t.end();
+ });
+
+ st.end();
+ });
+
+ t.end();
+});
+
+test('assign()', function (t) {
+ var target = { a: 1, b: 2 };
+ var source = { b: 3, c: 4 };
+ var result = utils.assign(target, source);
+
+ t.equal(result, target, 'returns the target');
+ t.deepEqual(target, { a: 1, b: 3, c: 4 }, 'target and source are merged');
+ t.deepEqual(source, { b: 3, c: 4 }, 'source is untouched');
+
+ t.end();
+});
+
+test('combine()', function (t) {
+ t.test('both arrays', function (st) {
+ var a = [1];
+ var b = [2];
+ var combined = utils.combine(a, b);
+
+ st.deepEqual(a, [1], 'a is not mutated');
+ st.deepEqual(b, [2], 'b is not mutated');
+ st.notEqual(a, combined, 'a !== combined');
+ st.notEqual(b, combined, 'b !== combined');
+ st.deepEqual(combined, [1, 2], 'combined is a + b');
+
+ st.end();
+ });
+
+ t.test('one array, one non-array', function (st) {
+ var aN = 1;
+ var a = [aN];
+ var bN = 2;
+ var b = [bN];
+
+ var combinedAnB = utils.combine(aN, b);
+ st.deepEqual(b, [bN], 'b is not mutated');
+ st.notEqual(aN, combinedAnB, 'aN + b !== aN');
+ st.notEqual(a, combinedAnB, 'aN + b !== a');
+ st.notEqual(bN, combinedAnB, 'aN + b !== bN');
+ st.notEqual(b, combinedAnB, 'aN + b !== b');
+ st.deepEqual([1, 2], combinedAnB, 'first argument is array-wrapped when not an array');
+
+ var combinedABn = utils.combine(a, bN);
+ st.deepEqual(a, [aN], 'a is not mutated');
+ st.notEqual(aN, combinedABn, 'a + bN !== aN');
+ st.notEqual(a, combinedABn, 'a + bN !== a');
+ st.notEqual(bN, combinedABn, 'a + bN !== bN');
+ st.notEqual(b, combinedABn, 'a + bN !== b');
+ st.deepEqual([1, 2], combinedABn, 'second argument is array-wrapped when not an array');
+
+ st.end();
+ });
+
+ t.test('neither is an array', function (st) {
+ var combined = utils.combine(1, 2);
+ st.notEqual(1, combined, '1 + 2 !== 1');
+ st.notEqual(2, combined, '1 + 2 !== 2');
+ st.deepEqual([1, 2], combined, 'both arguments are array-wrapped when not an array');
+
+ st.end();
+ });
+
+ t.test('with arrayLimit', function (st) {
+ st.test('under the limit', function (s2t) {
+ var combined = utils.combine(['a', 'b'], 'c', 10, false);
+ s2t.deepEqual(combined, ['a', 'b', 'c'], 'returns array when under limit');
+ s2t.ok(Array.isArray(combined), 'result is an array');
+ s2t.end();
+ });
+
+ st.test('exactly at the limit stays as array', function (s2t) {
+ var combined = utils.combine(['a', 'b'], 'c', 3, false);
+ s2t.deepEqual(combined, ['a', 'b', 'c'], 'stays as array when count equals limit');
+ s2t.ok(Array.isArray(combined), 'result is an array');
+ s2t.end();
+ });
+
+ st.test('over the limit', function (s2t) {
+ var combined = utils.combine(['a', 'b', 'c'], 'd', 3, false);
+ s2t.deepEqual(combined, { 0: 'a', 1: 'b', 2: 'c', 3: 'd' }, 'converts to object when over limit');
+ s2t.notOk(Array.isArray(combined), 'result is not an array');
+ s2t.end();
+ });
+
+ st.test('with arrayLimit 1', function (s2t) {
+ var combined = utils.combine([], 'a', 1, false);
+ s2t.deepEqual(combined, ['a'], 'stays as array when count equals limit');
+ s2t.ok(Array.isArray(combined), 'result is an array');
+ s2t.end();
+ });
+
+ st.test('with arrayLimit 0 converts single element to object', function (s2t) {
+ var combined = utils.combine([], 'a', 0, false);
+ s2t.deepEqual(combined, { 0: 'a' }, 'converts to object when count exceeds limit');
+ s2t.notOk(Array.isArray(combined), 'result is not an array');
+ s2t.end();
+ });
+
+ st.test('with arrayLimit 0 and two elements converts to object', function (s2t) {
+ var combined = utils.combine(['a'], 'b', 0, false);
+ s2t.deepEqual(combined, { 0: 'a', 1: 'b' }, 'converts to object when count exceeds limit');
+ s2t.notOk(Array.isArray(combined), 'result is not an array');
+ s2t.end();
+ });
+
+ st.test('with plainObjects option', function (s2t) {
+ var combined = utils.combine(['a', 'b'], 'c', 1, true);
+ var expected = { __proto__: null, 0: 'a', 1: 'b', 2: 'c' };
+ s2t.deepEqual(combined, expected, 'converts to object with null prototype');
+ s2t.equal(Object.getPrototypeOf(combined), null, 'result has null prototype when plainObjects is true');
+ s2t.end();
+ });
+
+ st.end();
+ });
+
+ t.test('with throwOnLimitExceeded', function (st) {
+ st.test('throws when concatenation exceeds the limit', function (s2t) {
+ s2t['throws'](
+ function () { utils.combine(['a', 'b', 'c'], 'd', 3, false, true); },
+ new RangeError('Array limit exceeded. Only 3 elements allowed in an array.'),
+ 'throws instead of converting to an overflow object'
+ );
+ s2t['throws'](
+ function () { utils.combine([], 'a', 0, false, true); },
+ new RangeError('Array limit exceeded. Only 0 elements allowed in an array.'),
+ 'throws with the correct count at arrayLimit 0'
+ );
+ s2t.end();
+ });
+
+ st.test('throws when adding to an existing overflow object', function (s2t) {
+ var overflow = utils.combine(['a', 'b'], 'c', 0, false);
+ s2t.ok(utils.isOverflow(overflow), 'initial object is marked as overflow');
+
+ s2t['throws'](
+ function () { utils.combine(overflow, 'd', 0, false, true); },
+ new RangeError('Array limit exceeded. Only 0 elements allowed in an array.'),
+ 'throws rather than appending to the overflow object'
+ );
+ s2t['throws'](
+ function () { utils.combine(overflow, 'd', 1, false, true); },
+ new RangeError('Array limit exceeded. Only 1 element allowed in an array.'),
+ 'uses the singular message at arrayLimit 1'
+ );
+ s2t.end();
+ });
+
+ st.test('does not throw when within the limit', function (s2t) {
+ var combined = utils.combine(['a'], 'b', 5, false, true);
+ s2t.deepEqual(combined, ['a', 'b'], 'returns the array unchanged when under the limit');
+ s2t.end();
+ });
+
+ st.end();
+ });
+
+ t.test('with existing overflow object', function (st) {
+ st.test('adds to existing overflow object at next index', function (s2t) {
+ // Create overflow object first via combine: 3 elements (indices 0-2) with limit 0
+ var overflow = utils.combine(['a', 'b'], 'c', 0, false);
+ s2t.ok(utils.isOverflow(overflow), 'initial object is marked as overflow');
+
+ var combined = utils.combine(overflow, 'd', 10, false);
+ s2t.equal(combined, overflow, 'returns the same object (mutated)');
+ s2t.deepEqual(combined, { 0: 'a', 1: 'b', 2: 'c', 3: 'd' }, 'adds value at next numeric index');
+ s2t.end();
+ });
+
+ st.test('does not treat plain object with numeric keys as overflow', function (s2t) {
+ var plainObj = { 0: 'a', 1: 'b' };
+ s2t.notOk(utils.isOverflow(plainObj), 'plain object is not marked as overflow');
+
+ // combine treats this as a regular value, not an overflow object to append to
+ var combined = utils.combine(plainObj, 'c', 10, false);
+ s2t.deepEqual(combined, [{ 0: 'a', 1: 'b' }, 'c'], 'concatenates as regular values');
+ s2t.end();
+ });
+
+ st.end();
+ });
+
+ t.end();
+});
+
+test('decode', function (t) {
+ t.equal(
+ utils.decode('a+b'),
+ 'a b',
+ 'decodes + to space'
+ );
+
+ t.equal(
+ utils.decode('name%2Eobj'),
+ 'name.obj',
+ 'decodes a string'
+ );
+ t.equal(
+ utils.decode('name%2Eobj%2Efoo', null, 'iso-8859-1'),
+ 'name.obj.foo',
+ 'decodes a string in iso-8859-1'
+ );
+
+ t.end();
+});
+
+test('encode', function (t) {
+ forEach(v.nullPrimitives, function (nullish) {
+ t['throws'](
+ function () { utils.encode(nullish); },
+ TypeError,
+ inspect(nullish) + ' is not a string'
+ );
+ });
+
+ t.equal(utils.encode(''), '', 'empty string returns itself');
+ t.deepEqual(utils.encode([]), [], 'empty array returns itself');
+ t.deepEqual(utils.encode({ length: 0 }), { length: 0 }, 'empty arraylike returns itself');
+
+ t.test('symbols', { skip: !v.hasSymbols }, function (st) {
+ st.equal(utils.encode(Symbol('x')), 'Symbol%28x%29', 'symbol is encoded');
+
+ st.end();
+ });
+
+ t.equal(
+ utils.encode('(abc)'),
+ '%28abc%29',
+ 'encodes parentheses'
+ );
+ t.equal(
+ utils.encode({ toString: function () { return '(abc)'; } }),
+ '%28abc%29',
+ 'toStrings and encodes parentheses'
+ );
+
+ t.equal(
+ utils.encode('abc 123 💩', null, 'iso-8859-1'),
+ 'abc%20123%20%26%2355357%3B%26%2356489%3B',
+ 'encodes in iso-8859-1'
+ );
+
+ var longString = '';
+ var expectedString = '';
+ for (var i = 0; i < 1500; i++) {
+ longString += ' ';
+ expectedString += '%20';
+ }
+
+ t.equal(
+ utils.encode(longString),
+ expectedString,
+ 'encodes a long string'
+ );
+
+ var boundary = '';
+ var expected = '';
+ for (var j = 0; j < 1023; j++) {
+ boundary += 'a';
+ expected += 'a';
+ }
+ boundary += '😀';
+ expected += '%F0%9F%98%80';
+
+ t.equal(
+ utils.encode(boundary),
+ expected,
+ 'encodes a surrogate pair split across long-string chunks'
+ );
+
+ var laterBoundary = '';
+ var laterExpected = '';
+ for (var k = 0; k < 2047; k++) {
+ laterBoundary += 'a';
+ laterExpected += 'a';
+ }
+ laterBoundary += '😀';
+ laterExpected += '%F0%9F%98%80';
+
+ t.equal(
+ utils.encode(laterBoundary),
+ laterExpected,
+ 'encodes a surrogate pair split across a later chunk boundary'
+ );
+
+ var twoPairs = '';
+ for (k = 0; k < 1023; k++) {
+ twoPairs += 'a';
+ }
+ twoPairs += '😀';
+ for (k = 0; k < 1022; k++) {
+ twoPairs += 'b';
+ }
+ twoPairs += '😀';
+
+ t.equal(
+ (utils.encode(twoPairs).match(/%F0%9F%98%80/g) || []).length,
+ 2,
+ 'encodes two surrogate pairs each split across a chunk boundary'
+ );
+
+ var roundTrip = '';
+ for (k = 0; k < 1023; k++) {
+ roundTrip += 'a';
+ }
+ roundTrip += '😀';
+
+ t.equal(
+ decodeURIComponent(utils.encode(roundTrip)),
+ roundTrip,
+ 'a boundary-split surrogate pair round-trips through decodeURIComponent'
+ );
+
+ var loneBoundary = '';
+ var loneExpected = '';
+ for (k = 0; k < 1023; k++) {
+ loneBoundary += 'a';
+ loneExpected += 'a';
+ }
+ loneBoundary += '\uD83DX';
+ loneExpected += '%F0%9F%91%98';
+
+ t.equal(
+ utils.encode(loneBoundary),
+ loneExpected,
+ 'a lone high surrogate at a chunk boundary encodes the same as mid-chunk'
+ );
+
+ t.equal(
+ utils.encode('\x28\x29'),
+ '%28%29',
+ 'encodes parens normally'
+ );
+ t.equal(
+ utils.encode('\x28\x29', null, null, null, 'RFC1738'),
+ '()',
+ 'does not encode parens in RFC1738'
+ );
+
+ // todo RFC1738 format
+
+ t.equal(
+ utils.encode('Āက豈'),
+ '%C4%80%E1%80%80%EF%A4%80',
+ 'encodes multibyte chars'
+ );
+
+ t.equal(
+ utils.encode('\uD83D \uDCA9'),
+ '%F0%9F%90%A0%F0%BA%90%80',
+ 'encodes lone surrogates'
+ );
+
+ t.end();
+});
+
+test('isBuffer()', function (t) {
+ var fn = function () {};
+ fn();
+ forEach([null, undefined, true, false, '', 'abc', 42, 0, NaN, {}, [], fn, /a/g], function (x) {
+ t.equal(utils.isBuffer(x), false, inspect(x) + ' is not a buffer');
+ });
+
+ var fakeBuffer = { constructor: Buffer };
+ t.equal(utils.isBuffer(fakeBuffer), false, 'fake buffer is not a buffer');
+
+ var saferBuffer = SaferBuffer.from('abc');
+ t.equal(utils.isBuffer(saferBuffer), true, 'SaferBuffer instance is a buffer');
+
+ var buffer = SaferBuffer.from('abc');
+ t.notEqual(saferBuffer, buffer, 'different buffer instances');
+ t.equal(utils.isBuffer(buffer), true, 'another Buffer instance is a buffer');
+ t.end();
+});
+
+test('isRegExp()', function (t) {
+ t.equal(utils.isRegExp(/a/g), true, 'RegExp is a RegExp');
+ t.equal(utils.isRegExp(new RegExp('a', 'g')), true, 'new RegExp is a RegExp');
+ t.equal(utils.isRegExp(new Date()), false, 'Date is not a RegExp');
+
+ forEach(v.primitives, function (primitive) {
+ t.equal(utils.isRegExp(primitive), false, inspect(primitive) + ' is not a RegExp');
+ });
+
+ t.end();
+});