This commit is contained in:
2022-07-18 02:50:52 +00:00
parent befd344ab0
commit 06181b34d6
8569 changed files with 818704 additions and 352705 deletions
+10
View File
@@ -8,10 +8,14 @@ charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
max_line_length = 160
quote_type = single
[test/*]
max_line_length = off
[LICENSE.md]
indent_size = off
[*.md]
max_line_length = off
@@ -28,3 +32,9 @@ indent_size = 2
[LICENSE]
indent_size = 2
max_line_length = off
[coverage/**/*]
indent_size = off
indent_style = off
indent = off
max_line_length = off
Generated Vendored
+21 -4
View File
@@ -3,19 +3,36 @@
"extends": "@ljharb",
"ignorePatterns": [
"dist/",
],
"rules": {
"complexity": 0,
"consistent-return": 1,
"func-name-matching": 0,
"func-name-matching": 0,
"id-length": [2, { "min": 1, "max": 25, "properties": "never" }],
"indent": [2, 4],
"max-lines-per-function": [2, { "max": 150 }],
"max-params": [2, 14],
"max-params": [2, 15],
"max-statements": [2, 52],
"multiline-comment-style": 0,
"no-continue": 1,
"no-magic-numbers": 0,
"no-restricted-syntax": [2, "BreakStatement", "DebuggerStatement", "ForInStatement", "LabeledStatement", "WithStatement"],
"operator-linebreak": [2, "before"],
}
},
"overrides": [
{
"files": "test/**",
"rules": {
"function-paren-newline": 0,
"max-lines-per-function": 0,
"max-statements": 0,
"no-buffer-constructor": 0,
"no-extend-native": 0,
"no-throw-literal": 0,
}
}
]
}
+12
View File
@@ -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
Generated Vendored
+13
View File
@@ -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"
]
}
+132
View File
@@ -1,3 +1,135 @@
## **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.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.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 its properly detected as BSD-3-Clause
## **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 its 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)
Generated Vendored
+29
View File
@@ -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.
Generated Vendored
+71 -18
View File
@@ -1,12 +1,13 @@
# qs <sup>[![Version Badge][2]][1]</sup>
[![Build Status][3]][4]
[![dependency status][5]][6]
[![dev dependency status][7]][8]
[![github actions][actions-image]][actions-url]
[![coverage][codecov-image]][codecov-url]
[![dependency status][deps-svg]][deps-url]
[![dev dependency status][dev-deps-svg]][dev-deps-url]
[![License][license-image]][license-url]
[![Downloads][downloads-image]][downloads-url]
[![npm badge][11]][1]
[![npm badge][npm-badge-png]][package-url]
A querystring parsing and stringifying library with some added security.
@@ -227,6 +228,13 @@ 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
@@ -280,6 +288,17 @@ 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)
@@ -330,6 +349,30 @@ var decoded = qs.parse('x=z', { decoder: function (str) {
}})
```
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, by default they are given explicit indices:
@@ -553,18 +596,28 @@ assert.equal(qs.stringify({ a: 'b c' }, { format : 'RFC3986' }), 'a=b%20c');
assert.equal(qs.stringify({ a: 'b c' }, { format : 'RFC1738' }), 'a=b+c');
```
[1]: https://npmjs.org/package/qs
[2]: http://versionbadg.es/ljharb/qs.svg
[3]: https://api.travis-ci.org/ljharb/qs.svg
[4]: https://travis-ci.org/ljharb/qs
[5]: https://david-dm.org/ljharb/qs.svg
[6]: https://david-dm.org/ljharb/qs
[7]: https://david-dm.org/ljharb/qs/dev-status.svg
[8]: https://david-dm.org/ljharb/qs?type=dev
[9]: https://ci.testling.com/ljharb/qs.png
[10]: https://ci.testling.com/ljharb/qs
[11]: https://nodei.co/npm/qs.png?downloads=true&stars=true
[license-image]: http://img.shields.io/npm/l/qs.svg
## 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]: http://img.shields.io/npm/dm/qs.svg
[downloads-url]: http://npm-stat.com/charts.html?package=qs
[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/endpoint?url=https://github-actions-badge-u3jn4tfpocch.runkit.sh/ljharb/qs
[actions-url]: https://github.com/ljharb/qs/actions
Generated Vendored
+1336 -74
View File
File diff suppressed because it is too large Load Diff
+9 -4
View File
@@ -3,16 +3,21 @@
var replace = String.prototype.replace;
var percentTwenties = /%20/g;
var Format = {
RFC1738: 'RFC1738',
RFC3986: 'RFC3986'
};
module.exports = {
'default': 'RFC3986',
'default': Format.RFC3986,
formatters: {
RFC1738: function (value) {
return replace.call(value, percentTwenties, '+');
},
RFC3986: function (value) {
return value;
return String(value);
}
},
RFC1738: 'RFC1738',
RFC3986: 'RFC3986'
RFC1738: Format.RFC1738,
RFC3986: Format.RFC3986
};
+36 -15
View File
@@ -3,10 +3,12 @@
var utils = require('./utils');
var has = Object.prototype.hasOwnProperty;
var isArray = Array.isArray;
var defaults = {
allowDots: false,
allowPrototypes: false,
allowSparse: false,
arrayLimit: 20,
charset: 'utf-8',
charsetSentinel: false,
@@ -28,6 +30,14 @@ var interpretNumericEntities = function (str) {
});
};
var parseArrayValue = function (val, options) {
if (val && typeof val === 'string' && options.comma && val.indexOf(',') > -1) {
return val.split(',');
}
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
@@ -72,19 +82,24 @@ var parseValues = function parseQueryStringValues(str, options) {
var key, val;
if (pos === -1) {
key = options.decoder(part, defaults.decoder, charset);
key = options.decoder(part, defaults.decoder, charset, 'key');
val = options.strictNullHandling ? null : '';
} else {
key = options.decoder(part.slice(0, pos), defaults.decoder, charset);
val = options.decoder(part.slice(pos + 1), defaults.decoder, charset);
key = options.decoder(part.slice(0, pos), defaults.decoder, charset, 'key');
val = utils.maybeMap(
parseArrayValue(part.slice(pos + 1), options),
function (encodedVal) {
return options.decoder(encodedVal, defaults.decoder, charset, 'value');
}
);
}
if (val && options.interpretNumericEntities && charset === 'iso-8859-1') {
val = interpretNumericEntities(val);
}
if (val && options.comma && val.indexOf(',') > -1) {
val = val.split(',');
if (part.indexOf('[]=') > -1) {
val = isArray(val) ? [val] : val;
}
if (has.call(obj, key)) {
@@ -97,8 +112,8 @@ var parseValues = function parseQueryStringValues(str, options) {
return obj;
};
var parseObject = function (chain, val, options) {
var leaf = val;
var parseObject = function (chain, val, options, valuesParsed) {
var leaf = valuesParsed ? val : parseArrayValue(val, options);
for (var i = chain.length - 1; i >= 0; --i) {
var obj;
@@ -121,7 +136,7 @@ var parseObject = function (chain, val, options) {
) {
obj = [];
obj[index] = leaf;
} else {
} else if (cleanRoot !== '__proto__') {
obj[cleanRoot] = leaf;
}
}
@@ -132,7 +147,7 @@ var parseObject = function (chain, val, options) {
return leaf;
};
var parseKeys = function parseQueryStringKeys(givenKey, val, options) {
var parseKeys = function parseQueryStringKeys(givenKey, val, options, valuesParsed) {
if (!givenKey) {
return;
}
@@ -147,7 +162,7 @@ var parseKeys = function parseQueryStringKeys(givenKey, val, options) {
// Get the parent
var segment = brackets.exec(key);
var segment = options.depth > 0 && brackets.exec(key);
var parent = segment ? key.slice(0, segment.index) : key;
// Stash the parent if it exists
@@ -167,7 +182,7 @@ var parseKeys = function parseQueryStringKeys(givenKey, val, options) {
// Loop through children appending to the array until we hit depth
var i = 0;
while ((segment = child.exec(key)) !== null && i < options.depth) {
while (options.depth > 0 && (segment = child.exec(key)) !== null && i < options.depth) {
i += 1;
if (!options.plainObjects && has.call(Object.prototype, segment[1].slice(1, -1))) {
if (!options.allowPrototypes) {
@@ -183,7 +198,7 @@ var parseKeys = function parseQueryStringKeys(givenKey, val, options) {
keys.push('[' + key.slice(segment.index) + ']');
}
return parseObject(keys, val, options);
return parseObject(keys, val, options, valuesParsed);
};
var normalizeParseOptions = function normalizeParseOptions(opts) {
@@ -196,20 +211,22 @@ var normalizeParseOptions = function normalizeParseOptions(opts) {
}
if (typeof opts.charset !== 'undefined' && opts.charset !== 'utf-8' && opts.charset !== 'iso-8859-1') {
throw new Error('The charset option must be either utf-8, iso-8859-1, or undefined');
throw new TypeError('The charset option must be either utf-8, iso-8859-1, or undefined');
}
var charset = typeof opts.charset === 'undefined' ? defaults.charset : opts.charset;
return {
allowDots: typeof opts.allowDots === 'undefined' ? defaults.allowDots : !!opts.allowDots,
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,
decoder: typeof opts.decoder === 'function' ? opts.decoder : defaults.decoder,
delimiter: typeof opts.delimiter === 'string' || utils.isRegExp(opts.delimiter) ? opts.delimiter : defaults.delimiter,
depth: typeof opts.depth === 'number' ? opts.depth : defaults.depth,
// eslint-disable-next-line no-implicit-coercion, no-extra-parens
depth: (typeof opts.depth === 'number' || opts.depth === false) ? +opts.depth : defaults.depth,
ignoreQueryPrefix: opts.ignoreQueryPrefix === true,
interpretNumericEntities: typeof opts.interpretNumericEntities === 'boolean' ? opts.interpretNumericEntities : defaults.interpretNumericEntities,
parameterLimit: typeof opts.parameterLimit === 'number' ? opts.parameterLimit : defaults.parameterLimit,
@@ -234,9 +251,13 @@ module.exports = function (str, opts) {
var keys = Object.keys(tempObj);
for (var i = 0; i < keys.length; ++i) {
var key = keys[i];
var newObj = parseKeys(key, tempObj[key], options);
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);
};
+99 -51
View File
@@ -1,23 +1,25 @@
'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) { // eslint-disable-line func-name-matching
brackets: function brackets(prefix) {
return prefix + '[]';
},
comma: 'comma',
indices: function indices(prefix, key) { // eslint-disable-line func-name-matching
indices: function indices(prefix, key) {
return prefix + '[' + key + ']';
},
repeat: function repeat(prefix) { // eslint-disable-line func-name-matching
repeat: function repeat(prefix) {
return prefix;
}
};
var isArray = Array.isArray;
var split = String.prototype.split;
var push = Array.prototype.push;
var pushToArray = function (arr, valueOrArray) {
push.apply(arr, isArray(valueOrArray) ? valueOrArray : [valueOrArray]);
@@ -25,6 +27,7 @@ var pushToArray = function (arr, valueOrArray) {
var toISO = Date.prototype.toISOString;
var defaultFormat = formats['default'];
var defaults = {
addQueryPrefix: false,
allowDots: false,
@@ -34,17 +37,28 @@ var defaults = {
encode: true,
encoder: utils.encode,
encodeValuesOnly: false,
formatter: formats.formatters[formats['default']],
format: defaultFormat,
formatter: formats.formatters[defaultFormat],
// deprecated
indices: false,
serializeDate: function serializeDate(date) { // eslint-disable-line func-name-matching
serializeDate: function serializeDate(date) {
return toISO.call(date);
},
skipNulls: false,
strictNullHandling: false
};
var stringify = function stringify( // eslint-disable-line func-name-matching
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,
@@ -55,31 +69,66 @@ var stringify = function stringify( // eslint-disable-line func-name-matching
sort,
allowDots,
serializeDate,
format,
formatter,
encodeValuesOnly,
charset
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 = obj.join(',');
obj = utils.maybeMap(obj, function (value) {
if (value instanceof Date) {
return serializeDate(value);
}
return value;
});
}
if (obj === null) {
if (strictNullHandling) {
return encoder && !encodeValuesOnly ? encoder(prefix, defaults.encoder, charset) : prefix;
return encoder && !encodeValuesOnly ? encoder(prefix, defaults.encoder, charset, 'key', format) : prefix;
}
obj = '';
}
if (typeof obj === 'string' || typeof obj === 'number' || typeof obj === 'boolean' || utils.isBuffer(obj)) {
if (isNonNullishPrimitive(obj) || utils.isBuffer(obj)) {
if (encoder) {
var keyValue = encodeValuesOnly ? prefix : encoder(prefix, defaults.encoder, charset);
return [formatter(keyValue) + '=' + formatter(encoder(obj, defaults.encoder, charset))];
var keyValue = encodeValuesOnly ? prefix : encoder(prefix, defaults.encoder, charset, 'key', format);
if (generateArrayPrefix === 'comma' && encodeValuesOnly) {
var valuesArray = split.call(String(obj), ',');
var valuesJoined = '';
for (var i = 0; i < valuesArray.length; ++i) {
valuesJoined += (i === 0 ? '' : ',') + formatter(encoder(valuesArray[i], defaults.encoder, charset, 'value', format));
}
return [formatter(keyValue) + '=' + valuesJoined];
}
return [formatter(keyValue) + '=' + formatter(encoder(obj, defaults.encoder, charset, 'value', format))];
}
return [formatter(prefix) + '=' + formatter(String(obj))];
}
@@ -91,53 +140,48 @@ var stringify = function stringify( // eslint-disable-line func-name-matching
}
var objKeys;
if (isArray(filter)) {
if (generateArrayPrefix === 'comma' && isArray(obj)) {
// we need to join elements in
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;
}
for (var i = 0; i < objKeys.length; ++i) {
var key = objKeys[i];
for (var j = 0; j < objKeys.length; ++j) {
var key = objKeys[j];
var value = typeof key === 'object' && typeof key.value !== 'undefined' ? key.value : obj[key];
if (skipNulls && obj[key] === null) {
if (skipNulls && value === null) {
continue;
}
if (isArray(obj)) {
pushToArray(values, stringify(
obj[key],
typeof generateArrayPrefix === 'function' ? generateArrayPrefix(prefix, key) : prefix,
generateArrayPrefix,
strictNullHandling,
skipNulls,
encoder,
filter,
sort,
allowDots,
serializeDate,
formatter,
encodeValuesOnly,
charset
));
} else {
pushToArray(values, stringify(
obj[key],
prefix + (allowDots ? '.' + key : '[' + key + ']'),
generateArrayPrefix,
strictNullHandling,
skipNulls,
encoder,
filter,
sort,
allowDots,
serializeDate,
formatter,
encodeValuesOnly,
charset
));
}
var keyPrefix = isArray(obj)
? typeof generateArrayPrefix === 'function' ? generateArrayPrefix(prefix, key) : prefix
: prefix + (allowDots ? '.' + key : '[' + key + ']');
sideChannel.set(object, step);
var valueSideChannel = getSideChannel();
valueSideChannel.set(sentinel, sideChannel);
pushToArray(values, stringify(
value,
keyPrefix,
generateArrayPrefix,
strictNullHandling,
skipNulls,
encoder,
filter,
sort,
allowDots,
serializeDate,
format,
formatter,
encodeValuesOnly,
charset,
valueSideChannel
));
}
return values;
@@ -148,7 +192,7 @@ var normalizeStringifyOptions = function normalizeStringifyOptions(opts) {
return defaults;
}
if (opts.encoder !== null && opts.encoder !== undefined && typeof opts.encoder !== 'function') {
if (opts.encoder !== null && typeof opts.encoder !== 'undefined' && typeof opts.encoder !== 'function') {
throw new TypeError('Encoder has to be a function.');
}
@@ -181,6 +225,7 @@ var normalizeStringifyOptions = function normalizeStringifyOptions(opts) {
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,
@@ -229,6 +274,7 @@ module.exports = function (object, opts) {
objKeys.sort(options.sort);
}
var sideChannel = getSideChannel();
for (var i = 0; i < objKeys.length; ++i) {
var key = objKeys[i];
@@ -246,9 +292,11 @@ module.exports = function (object, opts) {
options.sort,
options.allowDots,
options.serializeDate,
options.format,
options.formatter,
options.encodeValuesOnly,
options.charset
options.charset,
sideChannel
));
}
+24 -2
View File
@@ -1,5 +1,7 @@
'use strict';
var formats = require('./formats');
var has = Object.prototype.hasOwnProperty;
var isArray = Array.isArray;
@@ -43,6 +45,7 @@ var arrayToObject = function arrayToObject(source, options) {
};
var merge = function merge(target, source, options) {
/* eslint no-param-reassign: 0 */
if (!source) {
return target;
}
@@ -119,14 +122,19 @@ var decode = function (str, decoder, charset) {
}
};
var encode = function encode(str, defaultEncoder, charset) {
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 = typeof str === 'string' ? str : String(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) {
@@ -146,6 +154,7 @@ var encode = function encode(str, defaultEncoder, charset) {
|| (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)) // ( )
) {
out += string.charAt(i);
continue;
@@ -168,6 +177,7 @@ var encode = function encode(str, defaultEncoder, charset) {
i += 1;
c = 0x10000 + (((c & 0x3FF) << 10) | (string.charCodeAt(i) & 0x3FF));
/* eslint operator-linebreak: [2, "before"] */
out += hexTable[0xF0 | (c >> 18)]
+ hexTable[0x80 | ((c >> 12) & 0x3F)]
+ hexTable[0x80 | ((c >> 6) & 0x3F)]
@@ -217,6 +227,17 @@ var combine = function combine(a, b) {
return [].concat(a, b);
};
var maybeMap = function maybeMap(val, fn) {
if (isArray(val)) {
var mapped = [];
for (var i = 0; i < val.length; i += 1) {
mapped.push(fn(val[i]));
}
return mapped;
}
return fn(val);
};
module.exports = {
arrayToObject: arrayToObject,
assign: assign,
@@ -226,5 +247,6 @@ module.exports = {
encode: encode,
isBuffer: isBuffer,
isRegExp: isRegExp,
maybeMap: maybeMap,
merge: merge
};
+46 -31
View File
@@ -1,33 +1,33 @@
{
"_args": [
[
"qs@6.7.0",
"/mnt/Foxconn/Digitalent/Deverloper/liff-push_2series"
"qs@6.10.3",
"/home/node/nuxt"
]
],
"_from": "qs@6.7.0",
"_id": "qs@6.7.0",
"_from": "qs@6.10.3",
"_id": "qs@6.10.3",
"_inBundle": false,
"_integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==",
"_integrity": "sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ==",
"_location": "/qs",
"_phantomChildren": {},
"_requested": {
"type": "version",
"registry": true,
"raw": "qs@6.7.0",
"raw": "qs@6.10.3",
"name": "qs",
"escapedName": "qs",
"rawSpec": "6.7.0",
"rawSpec": "6.10.3",
"saveSpec": null,
"fetchSpec": "6.7.0"
"fetchSpec": "6.10.3"
},
"_requiredBy": [
"/body-parser",
"/express"
"/parse-path"
],
"_resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz",
"_spec": "6.7.0",
"_where": "/mnt/Foxconn/Digitalent/Deverloper/liff-push_2series",
"_resolved": "https://registry.npmjs.org/qs/-/qs-6.10.3.tgz",
"_spec": "6.10.3",
"_where": "/home/node/nuxt",
"bugs": {
"url": "https://github.com/ljharb/qs/issues"
},
@@ -38,27 +38,41 @@
"url": "http://ljharb.codes"
}
],
"dependencies": {},
"dependencies": {
"side-channel": "^1.0.4"
},
"description": "A querystring parser that supports nesting and arrays, with a depth limit",
"devDependencies": {
"@ljharb/eslint-config": "^13.1.1",
"browserify": "^16.2.3",
"covert": "^1.1.1",
"editorconfig-tools": "^0.1.1",
"eslint": "^5.15.3",
"evalmd": "^0.0.17",
"@ljharb/eslint-config": "^20.1.0",
"aud": "^1.1.5",
"browserify": "^16.5.2",
"eclint": "^2.8.1",
"eslint": "^8.6.0",
"evalmd": "^0.0.19",
"for-each": "^0.3.3",
"iconv-lite": "^0.4.24",
"mkdirp": "^0.5.1",
"object-inspect": "^1.6.0",
"has-symbols": "^1.0.2",
"iconv-lite": "^0.5.1",
"in-publish": "^2.0.1",
"mkdirp": "^0.5.5",
"nyc": "^10.3.2",
"object-inspect": "^1.12.0",
"qs-iconv": "^1.0.4",
"safe-publish-latest": "^1.1.2",
"safe-publish-latest": "^2.0.0",
"safer-buffer": "^2.1.2",
"tape": "^4.10.1"
"tape": "^5.4.0"
},
"engines": {
"node": ">=0.6"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
},
"greenkeeper": {
"ignore": [
"iconv-lite",
"mkdirp"
]
},
"homepage": "https://github.com/ljharb/qs",
"keywords": [
"querystring",
@@ -76,15 +90,16 @@
"url": "git+https://github.com/ljharb/qs.git"
},
"scripts": {
"coverage": "covert test",
"dist": "mkdirp dist && browserify --standalone Qs lib/index.js > dist/qs.js",
"lint": "eslint lib/*.js test/*.js",
"postlint": "editorconfig-tools check * lib/* test/*",
"prepublish": "safe-publish-latest && npm run dist",
"lint": "eslint .",
"postlint": "eclint check * lib/* test/* !dist/*",
"posttest": "aud --production",
"prepublish": "not-in-publish || npm run prepublishOnly",
"prepublishOnly": "safe-publish-latest && npm run dist",
"pretest": "npm run --silent readme && npm run --silent lint",
"readme": "evalmd README.md",
"test": "npm run --silent coverage",
"tests-only": "node test"
"test": "npm run tests-only",
"tests-only": "nyc tape 'test/**/*.js'"
},
"version": "6.7.0"
"version": "6.10.3"
}
+166 -1
View File
@@ -32,6 +32,38 @@ test('parse()', function (t) {
st.end();
});
t.test('arrayFormat: brackets allows only explicit arrays', function (st) {
st.deepEqual(qs.parse('a[]=b&a[]=c', { arrayFormat: 'brackets' }), { a: ['b', 'c'] });
st.deepEqual(qs.parse('a[0]=b&a[1]=c', { arrayFormat: 'brackets' }), { a: ['b', 'c'] });
st.deepEqual(qs.parse('a=b,c', { arrayFormat: 'brackets' }), { a: 'b,c' });
st.deepEqual(qs.parse('a=b&a=c', { arrayFormat: 'brackets' }), { a: ['b', 'c'] });
st.end();
});
t.test('arrayFormat: indices allows only indexed arrays', function (st) {
st.deepEqual(qs.parse('a[]=b&a[]=c', { arrayFormat: 'indices' }), { a: ['b', 'c'] });
st.deepEqual(qs.parse('a[0]=b&a[1]=c', { arrayFormat: 'indices' }), { a: ['b', 'c'] });
st.deepEqual(qs.parse('a=b,c', { arrayFormat: 'indices' }), { a: 'b,c' });
st.deepEqual(qs.parse('a=b&a=c', { arrayFormat: 'indices' }), { a: ['b', 'c'] });
st.end();
});
t.test('arrayFormat: comma allows only comma-separated arrays', function (st) {
st.deepEqual(qs.parse('a[]=b&a[]=c', { arrayFormat: 'comma' }), { a: ['b', 'c'] });
st.deepEqual(qs.parse('a[0]=b&a[1]=c', { arrayFormat: 'comma' }), { a: ['b', 'c'] });
st.deepEqual(qs.parse('a=b,c', { arrayFormat: 'comma' }), { a: 'b,c' });
st.deepEqual(qs.parse('a=b&a=c', { arrayFormat: 'comma' }), { a: ['b', 'c'] });
st.end();
});
t.test('arrayFormat: repeat allows only repeated values', function (st) {
st.deepEqual(qs.parse('a[]=b&a[]=c', { arrayFormat: 'repeat' }), { a: ['b', 'c'] });
st.deepEqual(qs.parse('a[0]=b&a[1]=c', { arrayFormat: 'repeat' }), { a: ['b', 'c'] });
st.deepEqual(qs.parse('a=b,c', { arrayFormat: 'repeat' }), { a: 'b,c' });
st.deepEqual(qs.parse('a=b&a=c', { arrayFormat: 'repeat' }), { 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' } });
@@ -52,6 +84,18 @@ test('parse()', function (t) {
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.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) {
@@ -225,6 +269,15 @@ test('parse()', function (t) {
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' } });
@@ -356,6 +409,43 @@ test('parse()', function (t) {
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 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 },
@@ -532,13 +622,73 @@ test('parse()', function (t) {
st.deepEqual(
qs.parse('a[b]=c&a=toString', { plainObjects: true }),
{ a: { b: 'c', toString: true } },
{ __proto__: null, a: { __proto__: null, b: 'c', toString: true } },
'can overwrite prototype with plainObjects true'
);
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('can return null objects', { skip: !Object.create }, function (st) {
var expected = Object.create(null);
expected.a = Object.create(null);
@@ -672,5 +822,20 @@ test('parse()', function (t) {
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' });
st.end();
});
t.end();
});
+199 -13
View File
@@ -5,6 +5,8 @@ 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 hasBigInt = typeof BigInt === 'function';
test('stringify()', function (t) {
t.test('stringifies a querystring object', function (st) {
@@ -28,6 +30,39 @@ test('stringify()', function (t) {
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('adds query prefix', function (st) {
st.equal(qs.stringify({ a: 'b' }, { addQueryPrefix: true }), '?a=b');
st.end();
@@ -97,10 +132,10 @@ test('stringify()', function (t) {
});
t.test('stringifies a nested array value', function (st) {
st.equal(qs.stringify({ a: { b: ['c', 'd'] } }, { arrayFormat: 'indices' }), 'a%5Bb%5D%5B0%5D=c&a%5Bb%5D%5B1%5D=d');
st.equal(qs.stringify({ a: { b: ['c', 'd'] } }, { arrayFormat: 'brackets' }), 'a%5Bb%5D%5B%5D=c&a%5Bb%5D%5B%5D=d');
st.equal(qs.stringify({ a: { b: ['c', 'd'] } }, { arrayFormat: 'comma' }), 'a%5Bb%5D=c%2Cd'); // a[b]=c,d
st.equal(qs.stringify({ a: { b: ['c', 'd'] } }), 'a%5Bb%5D%5B0%5D=c&a%5Bb%5D%5B1%5D=d');
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();
});
@@ -108,7 +143,7 @@ test('stringify()', function (t) {
st.equal(
qs.stringify(
{ a: { b: ['c', 'd'] } },
{ allowDots: true, encode: false, arrayFormat: 'indices' }
{ allowDots: true, encodeValuesOnly: true, arrayFormat: 'indices' }
),
'a.b[0]=c&a.b[1]=d',
'indices: stringifies with dots + indices'
@@ -116,7 +151,7 @@ test('stringify()', function (t) {
st.equal(
qs.stringify(
{ a: { b: ['c', 'd'] } },
{ allowDots: true, encode: false, arrayFormat: 'brackets' }
{ allowDots: true, encodeValuesOnly: true, arrayFormat: 'brackets' }
),
'a.b[]=c&a.b[]=d',
'brackets: stringifies with dots + brackets'
@@ -124,7 +159,7 @@ test('stringify()', function (t) {
st.equal(
qs.stringify(
{ a: { b: ['c', 'd'] } },
{ allowDots: true, encode: false, arrayFormat: 'comma' }
{ allowDots: true, encodeValuesOnly: true, arrayFormat: 'comma' }
),
'a.b=c,d',
'comma: stringifies with dots + comma'
@@ -132,7 +167,7 @@ test('stringify()', function (t) {
st.equal(
qs.stringify(
{ a: { b: ['c', 'd'] } },
{ allowDots: true, encode: false }
{ allowDots: true, encodeValuesOnly: true }
),
'a.b[0]=c&a.b[1]=d',
'default: stringifies with dots + indices'
@@ -180,17 +215,23 @@ test('stringify()', function (t) {
t.test('stringifies an array with mixed objects and primitives', function (st) {
st.equal(
qs.stringify({ a: [{ b: 1 }, 2, 3] }, { encode: false, arrayFormat: 'indices' }),
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] }, { encode: false, arrayFormat: 'brackets' }),
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] }, { encode: false }),
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'
);
@@ -301,6 +342,27 @@ test('stringify()', function (t) {
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');
// 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');
// 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('stringifies a null object', { skip: !Object.create }, function (st) {
var obj = Object.create(null);
obj.a = 'b';
@@ -377,7 +439,7 @@ test('stringify()', function (t) {
st.end();
});
t.test('doesn\'t blow up when Buffer global is missing', function (st) {
t.test('does not blow up when Buffer global is missing', function (st) {
var tempBuffer = global.Buffer;
delete global.Buffer;
var result = qs.stringify({ a: 'b', c: 'd' });
@@ -386,6 +448,57 @@ test('stringify()', function (t) {
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 }),
'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.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: [] }), '');
@@ -520,6 +633,12 @@ test('stringify()', function (t) {
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();
});
@@ -554,23 +673,43 @@ test('stringify()', function (t) {
'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.end();
});
t.test('RFC 1738 spaces serialization', function (st) {
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();
});
@@ -675,5 +814,52 @@ test('stringify()', function (t) {
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');
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: 'bracket' }), '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: '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: 'bracket' }), 'a[b][0][c]=d&a[b][0][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: '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 }), 'a[1]=2&a[4]=1');
st.equal(qs.stringify({ a: [, { b: [, , { c: '1' }] }] }, { encodeValuesOnly: true }), 'a[1][b][2][c]=1');
st.equal(qs.stringify({ a: [, [, , [, , , { c: '1' }]]] }, { encodeValuesOnly: true }), 'a[1][2][3][c]=1');
st.equal(qs.stringify({ a: [, [, , [, , , { c: [, '1'] }]]] }, { encodeValuesOnly: true }), 'a[1][2][3][c][1]=1');
st.end();
});
t.end();
});
+1 -1
View File
@@ -130,7 +130,7 @@ test('isBuffer()', function (t) {
var saferBuffer = SaferBuffer.from('abc');
t.equal(utils.isBuffer(saferBuffer), true, 'SaferBuffer instance is a buffer');
var buffer = Buffer.from ? Buffer.from('abc') : new Buffer('abc');
var buffer = Buffer.from && Buffer.alloc ? Buffer.from('abc') : new Buffer('abc');
t.equal(utils.isBuffer(buffer), true, 'real Buffer instance is a buffer');
t.end();
});