From fde5bb712218e1bef9337d2d1fc7fd0862509816 Mon Sep 17 00:00:00 2001 From: Wes Roberts Date: Mon, 4 Dec 2017 22:23:12 -0500 Subject: [PATCH 001/278] [Fix] correctly parse nested arrays Fixes #212. --- lib/utils.js | 2 +- test/parse.js | 8 ++++++++ test/utils.js | 24 ++++++++++++++++++++++++ 3 files changed, 33 insertions(+), 1 deletion(-) diff --git a/lib/utils.js b/lib/utils.js index fe11c162..9a2c8313 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -72,7 +72,7 @@ var merge = function merge(target, source, options) { if (Array.isArray(target) && Array.isArray(source)) { source.forEach(function (item, i) { if (has.call(target, i)) { - if (target[i] && typeof target[i] === 'object') { + if (target[i] && typeof target[i] === 'object' && item && typeof item === 'object') { target[i] = merge(target[i], item, options); } else { target.push(item); diff --git a/test/parse.js b/test/parse.js index c888bf59..f2e4d7b3 100644 --- a/test/parse.js +++ b/test/parse.js @@ -237,6 +237,14 @@ test('parse()', function (t) { 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' }); diff --git a/test/utils.js b/test/utils.js index c11af553..e60d1029 100644 --- a/test/utils.js +++ b/test/utils.js @@ -21,6 +21,30 @@ test('merge()', function (t) { var noOptionsNonObjectSource = utils.merge({ foo: 'baz' }, 'bar'); t.deepEqual(noOptionsNonObjectSource, { foo: 'baz', bar: true }); + 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, 2); + observed[0] = observed[0]; // eslint-disable-line no-self-assign + st.equal(setCount, 1); + st.equal(getCount, 3); + st.end(); + } + ); + t.end(); }); From 1bfe04cbd6881060590ee59a7b5c28291f96ee39 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Sun, 13 May 2018 15:19:04 -0700 Subject: [PATCH 002/278] [Refactor] `parse`: only need to reassign the var once --- lib/parse.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/parse.js b/lib/parse.js index 8c9872ec..47570132 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -54,8 +54,7 @@ var parseObject = function (chain, val, options) { var root = chain[i]; if (root === '[]') { - obj = []; - obj = obj.concat(leaf); + obj = [].concat(leaf); } else { obj = options.plainObjects ? Object.create(null) : {}; var cleanRoot = root.charAt(0) === '[' && root.charAt(root.length - 1) === ']' ? root.slice(1, -1) : root; From eee72e37b078abf027ab1876d960f766d9f51261 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Sun, 13 May 2018 15:22:40 -0700 Subject: [PATCH 003/278] [Tests] up to `node` `v10.1`, `v9.11`, `v8.11`, `v6.14`, `v4.9`; pin included builds to LTS --- .travis.yml | 58 ++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 53 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index 4081ea4e..7bc8e73b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,13 +1,14 @@ language: node_js -dist: precise os: - linux node_js: - - "8.4" + - "10.1" + - "9.11" + - "8.11" - "7.10" - - "6.11" + - "6.14" - "5.12" - - "4.8" + - "4.9" - "iojs-v3.3" - "iojs-v2.5" - "iojs-v1.8" @@ -16,6 +17,7 @@ node_js: - "0.8" - "0.6" before_install: + - 'case "${TRAVIS_NODE_VERSION}" in 0.*) export NPM_CONFIG_STRICT_SSL=false ;; esac' - 'nvm install-latest-npm' install: - 'if [ "${TRAVIS_NODE_VERSION}" = "0.6" ] || [ "${TRAVIS_NODE_VERSION}" = "0.9" ]; then nvm install --latest-npm 0.8 && npm install && nvm use "${TRAVIS_NODE_VERSION}"; else npm install; fi;' @@ -30,10 +32,48 @@ env: matrix: fast_finish: true include: - - node_js: "node" + - node_js: "lts/*" env: PRETEST=true - node_js: "4" env: COVERAGE=true + - node_js: "10.0" + env: TEST=true ALLOW_FAILURE=true + - node_js: "9.10" + env: TEST=true ALLOW_FAILURE=true + - node_js: "9.9" + env: TEST=true ALLOW_FAILURE=true + - node_js: "9.8" + env: TEST=true ALLOW_FAILURE=true + - node_js: "9.7" + env: TEST=true ALLOW_FAILURE=true + - node_js: "9.6" + env: TEST=true ALLOW_FAILURE=true + - node_js: "9.5" + env: TEST=true ALLOW_FAILURE=true + - node_js: "9.4" + env: TEST=true ALLOW_FAILURE=true + - node_js: "9.3" + env: TEST=true ALLOW_FAILURE=true + - node_js: "9.2" + env: TEST=true ALLOW_FAILURE=true + - node_js: "9.1" + env: TEST=true ALLOW_FAILURE=true + - node_js: "9.0" + env: TEST=true ALLOW_FAILURE=true + - node_js: "8.10" + env: TEST=true ALLOW_FAILURE=true + - node_js: "8.9" + env: TEST=true ALLOW_FAILURE=true + - node_js: "8.8" + env: TEST=true ALLOW_FAILURE=true + - node_js: "8.7" + env: TEST=true ALLOW_FAILURE=true + - node_js: "8.6" + env: TEST=true ALLOW_FAILURE=true + - node_js: "8.5" + env: TEST=true ALLOW_FAILURE=true + - node_js: "8.4" + env: TEST=true ALLOW_FAILURE=true - node_js: "8.3" env: TEST=true ALLOW_FAILURE=true - node_js: "8.2" @@ -62,6 +102,12 @@ matrix: env: TEST=true ALLOW_FAILURE=true - node_js: "7.0" env: TEST=true ALLOW_FAILURE=true + - node_js: "6.13" + env: TEST=true ALLOW_FAILURE=true + - node_js: "6.12" + env: TEST=true ALLOW_FAILURE=true + - node_js: "6.11" + env: TEST=true ALLOW_FAILURE=true - node_js: "6.10" env: TEST=true ALLOW_FAILURE=true - node_js: "6.9" @@ -108,6 +154,8 @@ matrix: env: TEST=true ALLOW_FAILURE=true - node_js: "5.0" env: TEST=true ALLOW_FAILURE=true + - node_js: "4.8" + env: TEST=true ALLOW_FAILURE=true - node_js: "4.7" env: TEST=true ALLOW_FAILURE=true - node_js: "4.6" From f85bce66f84ba5a7002423f603c60971d76a270c Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Sun, 13 May 2018 15:20:45 -0700 Subject: [PATCH 004/278] [Fix] when `parseArrays` is false, properly handle keys ending in `[]` Fixes #260. --- lib/parse.js | 6 ++++-- package.json | 2 +- test/parse.js | 9 ++++++++- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/lib/parse.js b/lib/parse.js index 47570132..4abc1a70 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -53,13 +53,15 @@ var parseObject = function (chain, val, options) { var obj; var root = chain[i]; - if (root === '[]') { + if (root === '[]' && options.parseArrays) { obj = [].concat(leaf); } else { obj = options.plainObjects ? Object.create(null) : {}; var cleanRoot = root.charAt(0) === '[' && root.charAt(root.length - 1) === ']' ? root.slice(1, -1) : root; var index = parseInt(cleanRoot, 10); - if ( + if (!options.parseArrays && cleanRoot === '') { + obj = { 0: leaf }; + } else if ( !isNaN(index) && root !== cleanRoot && String(index) === cleanRoot diff --git a/package.json b/package.json index 2c654900..ed6ccf06 100644 --- a/package.json +++ b/package.json @@ -43,7 +43,7 @@ "test": "npm run --silent coverage", "tests-only": "node test", "readme": "evalmd README.md", - "prelint": "editorconfig-tools check * lib/* test/*", + "postlint": "editorconfig-tools check * lib/* test/*", "lint": "eslint lib/*.js test/*.js", "coverage": "covert test", "dist": "mkdirp dist && browserify --standalone Qs lib/index.js > dist/qs.js" diff --git a/test/parse.js b/test/parse.js index 0f8fe457..c6d0fbfe 100644 --- a/test/parse.js +++ b/test/parse.js @@ -302,7 +302,14 @@ test('parse()', function (t) { }); t.test('allows disabling array parsing', function (st) { - st.deepEqual(qs.parse('a[0]=b&a[1]=c', { parseArrays: false }), { a: { 0: 'b', 1: 'c' } }); + 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(); }); From b6956c90f8d7e5be7172237bfc20a94be2eb893d Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Fri, 7 Sep 2018 21:40:34 -0700 Subject: [PATCH 005/278] [Tests] remove nonexistent tape option --- test/parse.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/parse.js b/test/parse.js index c6d0fbfe..74056e37 100644 --- a/test/parse.js +++ b/test/parse.js @@ -257,7 +257,7 @@ test('parse()', function (t) { st.end(); }); - t.test('should not throw when a native prototype has an enumerable property', { parallel: false }, function (st) { + t.test('should not throw when a native prototype has an enumerable property', function (st) { Object.prototype.crash = ''; Array.prototype.crash = ''; st.doesNotThrow(qs.parse.bind(null, 'a=b')); From d1d1a97332ddc42c815f5e8a4a047b248ab229bf Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Mon, 17 Sep 2018 16:16:06 -0700 Subject: [PATCH 006/278] [Fix] `utils`: `merge`: fix crash when `source` is a truthy primitive & no options are provided --- lib/utils.js | 2 +- test/utils.js | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/utils.js b/lib/utils.js index 8775a327..6d5f440b 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -54,7 +54,7 @@ var merge = function merge(target, source, options) { if (Array.isArray(target)) { target.push(source); } else if (typeof target === 'object') { - if (options.plainObjects || options.allowPrototypes || !has.call(Object.prototype, source)) { + if ((options && (options.plainObjects || options.allowPrototypes)) || !has.call(Object.prototype, source)) { target[source] = true; } } else { diff --git a/test/utils.js b/test/utils.js index eff4011a..5c6056f2 100644 --- a/test/utils.js +++ b/test/utils.js @@ -18,6 +18,9 @@ test('merge()', function (t) { 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 }); + t.end(); }); From c1c2a9dd9d916f695189f08f008c5986f4e64580 Mon Sep 17 00:00:00 2001 From: Chris Dyson Date: Mon, 17 Sep 2018 10:45:31 +1200 Subject: [PATCH 007/278] [Fix] `stringify`: fix a crash with `strictNullHandling` and a custom `filter`/`serializeDate` (#279) --- lib/stringify.js | 4 +++- test/stringify.js | 20 ++++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/lib/stringify.js b/lib/stringify.js index ab915ac4..e8143120 100644 --- a/lib/stringify.js +++ b/lib/stringify.js @@ -48,7 +48,9 @@ var stringify = function stringify( // eslint-disable-line func-name-matching obj = filter(prefix, obj); } else if (obj instanceof Date) { obj = serializeDate(obj); - } else if (obj === null) { + } + + if (obj === null) { if (strictNullHandling) { return encoder && !encodeValuesOnly ? encoder(prefix, defaults.encoder) : prefix; } diff --git a/test/stringify.js b/test/stringify.js index 165ac621..1c2a2237 100644 --- a/test/stringify.js +++ b/test/stringify.js @@ -593,5 +593,25 @@ test('stringify()', function (t) { 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.end(); }); From 55d217b206db1247aeda91637432246011481d82 Mon Sep 17 00:00:00 2001 From: Andreas Lind Date: Fri, 27 Jul 2018 00:09:13 +0200 Subject: [PATCH 008/278] [refactor] `stringify`: Avoid arr = arr.concat(...), push to the existing instance (#269) --- lib/stringify.js | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/lib/stringify.js b/lib/stringify.js index e8143120..d34dbe15 100644 --- a/lib/stringify.js +++ b/lib/stringify.js @@ -15,6 +15,12 @@ var arrayPrefixGenerators = { } }; +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 defaults = { @@ -88,7 +94,7 @@ var stringify = function stringify( // eslint-disable-line func-name-matching } if (Array.isArray(obj)) { - values = values.concat(stringify( + pushToArray(values, stringify( obj[key], generateArrayPrefix(prefix, key), generateArrayPrefix, @@ -103,7 +109,7 @@ var stringify = function stringify( // eslint-disable-line func-name-matching encodeValuesOnly )); } else { - values = values.concat(stringify( + pushToArray(values, stringify( obj[key], prefix + (allowDots ? '.' + key : '[' + key + ']'), generateArrayPrefix, @@ -188,8 +194,7 @@ module.exports = function (object, opts) { if (skipNulls && obj[key] === null) { continue; } - - keys = keys.concat(stringify( + pushToArray(keys, stringify( obj[key], key, generateArrayPrefix, From fafc2d269b01921bc4f649ee2524c0fe4a329199 Mon Sep 17 00:00:00 2001 From: Wes Roberts Date: Mon, 4 Dec 2017 22:23:12 -0500 Subject: [PATCH 009/278] [Fix] correctly parse nested arrays Fixes #212. --- lib/utils.js | 2 +- test/parse.js | 8 ++++++++ test/utils.js | 24 ++++++++++++++++++++++++ 3 files changed, 33 insertions(+), 1 deletion(-) diff --git a/lib/utils.js b/lib/utils.js index 6d5f440b..5958edc8 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -76,7 +76,7 @@ var merge = function merge(target, source, options) { if (Array.isArray(target) && Array.isArray(source)) { source.forEach(function (item, i) { if (has.call(target, i)) { - if (target[i] && typeof target[i] === 'object') { + if (target[i] && typeof target[i] === 'object' && item && typeof item === 'object') { target[i] = merge(target[i], item, options); } else { target.push(item); diff --git a/test/parse.js b/test/parse.js index 74056e37..01a8ff47 100644 --- a/test/parse.js +++ b/test/parse.js @@ -237,6 +237,14 @@ test('parse()', function (t) { 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' }); diff --git a/test/utils.js b/test/utils.js index 5c6056f2..f255de3f 100644 --- a/test/utils.js +++ b/test/utils.js @@ -21,6 +21,30 @@ test('merge()', function (t) { var noOptionsNonObjectSource = utils.merge({ foo: 'baz' }, 'bar'); t.deepEqual(noOptionsNonObjectSource, { foo: 'baz', bar: true }); + 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, 2); + observed[0] = observed[0]; // eslint-disable-line no-self-assign + st.equal(setCount, 1); + st.equal(getCount, 3); + st.end(); + } + ); + t.end(); }); From f1a00b7482385c9f6eaaa997360ad0f80f60f660 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Wed, 26 Jun 2019 10:06:17 -0700 Subject: [PATCH 010/278] [meta] add FUNDING.yml This is an experiment; I intend to use 100% of funds to support the OSS community and my OSS projects' costs. --- .github/FUNDING.yml | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 .github/FUNDING.yml diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 00000000..0355f4f5 --- /dev/null +++ b/.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 From c438d15c5ccea3943c877d3143f3c794ae99a3ff Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Sun, 13 May 2018 15:20:45 -0700 Subject: [PATCH 011/278] [Fix] when `parseArrays` is false, properly handle keys ending in `[]` Fixes #260. --- dist/qs.js | 20 +++++++++++--------- lib/parse.js | 12 ++++++++---- test/parse.js | 9 ++++++++- 3 files changed, 27 insertions(+), 14 deletions(-) diff --git a/dist/qs.js b/dist/qs.js index 2d0d63ff..483714d8 100644 --- a/dist/qs.js +++ b/dist/qs.js @@ -1,4 +1,4 @@ -(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 e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o= 0 && - (options.parseArrays && index <= options.arrayLimit) + if (!options.parseArrays && cleanRoot === '') { + obj = { 0: val }; + } else if ( + !isNaN(index) + && root !== cleanRoot + && String(index) === cleanRoot + && index >= 0 + && (options.parseArrays && index <= options.arrayLimit) ) { obj = []; obj[index] = parseObject(chain, val, options); @@ -594,4 +596,4 @@ exports.isBuffer = function (obj) { }; },{}]},{},[2])(2) -}); \ No newline at end of file +}); diff --git a/lib/parse.js b/lib/parse.js index 1307e9d7..6eabc3c0 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -50,14 +50,16 @@ var parseObject = function parseObjectRecursive(chain, val, options) { var root = chain.shift(); var obj; - if (root === '[]') { + if (root === '[]' && options.parseArrays) { obj = []; obj = obj.concat(parseObject(chain, val, options)); } else { obj = options.plainObjects ? Object.create(null) : {}; var cleanRoot = root.charAt(0) === '[' && root.charAt(root.length - 1) === ']' ? root.slice(1, -1) : root; var index = parseInt(cleanRoot, 10); - if ( + if (!options.parseArrays && cleanRoot === '') { + obj = { 0: val }; + } else if ( !isNaN(index) && root !== cleanRoot && String(index) === cleanRoot && @@ -96,8 +98,10 @@ var parseKeys = function parseQueryStringKeys(givenKey, val, options) { var keys = []; if (parent) { - // If we aren't using plain objects, optionally prefix keys - // that would overwrite object prototype properties + /* + * If we aren't using plain objects, optionally prefix keys + * that would overwrite object prototype properties + */ if (!options.plainObjects && has.call(Object.prototype, parent)) { if (!options.allowPrototypes) { return; diff --git a/test/parse.js b/test/parse.js index e451e91f..209fa027 100644 --- a/test/parse.js +++ b/test/parse.js @@ -300,7 +300,14 @@ test('parse()', function (t) { }); t.test('allows disabling array parsing', function (st) { - st.deepEqual(qs.parse('a[0]=b&a[1]=c', { parseArrays: false }), { a: { 0: 'b', 1: 'c' } }); + 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(); }); From 839c06288bda2e48c8f1ad78e7f958d05dc0e9ea Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Fri, 7 Sep 2018 21:40:34 -0700 Subject: [PATCH 012/278] [Tests] remove nonexistent tape option --- test/parse.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/parse.js b/test/parse.js index 209fa027..a68ac92a 100644 --- a/test/parse.js +++ b/test/parse.js @@ -255,7 +255,7 @@ test('parse()', function (t) { st.end(); }); - t.test('should not throw when a native prototype has an enumerable property', { parallel: false }, function (st) { + t.test('should not throw when a native prototype has an enumerable property', function (st) { Object.prototype.crash = ''; Array.prototype.crash = ''; st.doesNotThrow(qs.parse.bind(null, 'a=b')); From 6cf5f81b6c312f90f57d34b6e5c390c5cdebd43a Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Mon, 17 Sep 2018 16:16:06 -0700 Subject: [PATCH 013/278] [Fix] `utils`: `merge`: fix crash when `source` is a truthy primitive & no options are provided --- lib/utils.js | 2 +- test/utils.js | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/utils.js b/lib/utils.js index b2143323..e0ebba2d 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -31,7 +31,7 @@ exports.merge = function (target, source, options) { if (Array.isArray(target)) { target.push(source); } else if (typeof target === 'object') { - if (options.plainObjects || options.allowPrototypes || !has.call(Object.prototype, source)) { + if ((options && (options.plainObjects || options.allowPrototypes)) || !has.call(Object.prototype, source)) { target[source] = true; } } else { diff --git a/test/utils.js b/test/utils.js index 0721dd8e..999f860d 100644 --- a/test/utils.js +++ b/test/utils.js @@ -18,5 +18,8 @@ test('merge()', function (t) { 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 }); + t.end(); }); From 0669d587c9ecb42db745be85dca3d99edff3e4d0 Mon Sep 17 00:00:00 2001 From: Chris Dyson Date: Mon, 17 Sep 2018 10:45:31 +1200 Subject: [PATCH 014/278] [Fix] `stringify`: fix a crash with `strictNullHandling` and a custom `filter`/`serializeDate` (#279) --- lib/stringify.js | 4 +++- test/stringify.js | 21 +++++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/lib/stringify.js b/lib/stringify.js index 7694988c..7ee8b7c3 100644 --- a/lib/stringify.js +++ b/lib/stringify.js @@ -48,7 +48,9 @@ var stringify = function stringify( // eslint-disable-line func-name-matching obj = filter(prefix, obj); } else if (obj instanceof Date) { obj = serializeDate(obj); - } else if (obj === null) { + } + + if (obj === null) { if (strictNullHandling) { return encoder && !encodeValuesOnly ? encoder(prefix) : prefix; } diff --git a/test/stringify.js b/test/stringify.js index 711dae50..8a4744b0 100644 --- a/test/stringify.js +++ b/test/stringify.js @@ -564,4 +564,25 @@ test('stringify()', function (t) { 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.end(); }); From 9b96ace67c5d05525f8c2e0034e1b9b1473a19e0 Mon Sep 17 00:00:00 2001 From: Andreas Lind Date: Fri, 27 Jul 2018 00:09:13 +0200 Subject: [PATCH 015/278] [refactor] `stringify`: Avoid arr = arr.concat(...), push to the existing instance (#269) --- lib/stringify.js | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/lib/stringify.js b/lib/stringify.js index 7ee8b7c3..3b603dc0 100644 --- a/lib/stringify.js +++ b/lib/stringify.js @@ -15,6 +15,12 @@ var arrayPrefixGenerators = { } }; +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 defaults = { @@ -88,7 +94,7 @@ var stringify = function stringify( // eslint-disable-line func-name-matching } if (Array.isArray(obj)) { - values = values.concat(stringify( + pushToArray(values, stringify( obj[key], generateArrayPrefix(prefix, key), generateArrayPrefix, @@ -103,7 +109,7 @@ var stringify = function stringify( // eslint-disable-line func-name-matching encodeValuesOnly )); } else { - values = values.concat(stringify( + pushToArray(values, stringify( obj[key], prefix + (allowDots ? '.' + key : '[' + key + ']'), generateArrayPrefix, @@ -188,8 +194,7 @@ module.exports = function (object, opts) { if (skipNulls && obj[key] === null) { continue; } - - keys = keys.concat(stringify( + pushToArray(keys, stringify( obj[key], key, generateArrayPrefix, From e0f1989bd0425b4652e5bd3ae2824361aa037e71 Mon Sep 17 00:00:00 2001 From: Dmitry Kirilyuk Date: Wed, 16 Jan 2019 16:21:27 +0300 Subject: [PATCH 016/278] [Docs] Clarify the need for "arrayLimit" option --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 32fc3123..a08260f7 100644 --- a/README.md +++ b/README.md @@ -169,7 +169,7 @@ assert.deepEqual(withIndexedEmptyString, { a: ['b', '', 'c'] }); ``` **qs** will also limit specifying indices in an array to a maximum index of `20`. Any array members with an index of greater than `20` will -instead be converted to an object with the index as the key: +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'); From 3fdf63467166a7fc45ea33cd42e4bcf063a8b131 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Thu, 31 Jan 2019 14:24:14 -0800 Subject: [PATCH 017/278] [Refactor] use cached `Array.isArray` --- lib/stringify.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/stringify.js b/lib/stringify.js index 3b603dc0..3c191f04 100644 --- a/lib/stringify.js +++ b/lib/stringify.js @@ -79,7 +79,7 @@ var stringify = function stringify( // eslint-disable-line func-name-matching } var objKeys; - if (Array.isArray(filter)) { + if (isArray(filter)) { objKeys = filter; } else { var keys = Object.keys(obj); @@ -93,7 +93,7 @@ var stringify = function stringify( // eslint-disable-line func-name-matching continue; } - if (Array.isArray(obj)) { + if (isArray(obj)) { pushToArray(values, stringify( obj[key], generateArrayPrefix(prefix, key), @@ -158,7 +158,7 @@ module.exports = function (object, opts) { if (typeof options.filter === 'function') { filter = options.filter; obj = filter('', obj); - } else if (Array.isArray(options.filter)) { + } else if (isArray(options.filter)) { filter = options.filter; objKeys = filter; } From d856d8007e6f19937f4356d8ebcecbdd13fb2b7e Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Fri, 1 Feb 2019 13:48:43 -0800 Subject: [PATCH 018/278] [Fix]` `utils.merge`: avoid a crash with a null target and a truthy non-array source --- lib/utils.js | 2 +- test/utils.js | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/utils.js b/lib/utils.js index e0ebba2d..32a83c75 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -30,7 +30,7 @@ exports.merge = function (target, source, options) { if (typeof source !== 'object') { if (Array.isArray(target)) { target.push(source); - } else if (typeof target === 'object') { + } else if (target && typeof target === 'object') { if ((options && (options.plainObjects || options.allowPrototypes)) || !has.call(Object.prototype, source)) { target[source] = true; } diff --git a/test/utils.js b/test/utils.js index 999f860d..67ef9369 100644 --- a/test/utils.js +++ b/test/utils.js @@ -4,6 +4,8 @@ var test = require('tape'); 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({ a: 'b' }, { a: 'c' }), { a: ['b', 'c'] }, 'merges two objects with the same key'); var oneMerged = utils.merge({ foo: 'bar' }, { foo: { first: '123' } }); From a54ca9e21fbbdc9b2b6c0bd4b69f6bef4dcb3f78 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Sun, 3 Feb 2019 00:11:31 -0800 Subject: [PATCH 019/278] [Fix] `utils.merge`: avoid a crash with a null target and an array source --- lib/utils.js | 2 +- test/utils.js | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/utils.js b/lib/utils.js index 32a83c75..9095b9d1 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -41,7 +41,7 @@ exports.merge = function (target, source, options) { return target; } - if (typeof target !== 'object') { + if (!target || typeof target !== 'object') { return [target].concat(source); } diff --git a/test/utils.js b/test/utils.js index 67ef9369..3c7ee523 100644 --- a/test/utils.js +++ b/test/utils.js @@ -6,6 +6,8 @@ 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' } }); From 0485440902d3fc03d1d973d91af5a183fa4e3059 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Tue, 20 Mar 2018 22:22:47 +0000 Subject: [PATCH 020/278] [Fix] use `safer-buffer` instead of `Buffer` constructor https://github.com/ChALkeR/safer-buffer/blob/master/Porting-Buffer.md#variant-2 / https://github.com/nodejs/node/issues/19079 --- package.json | 1 + test/parse.js | 5 +++-- test/stringify.js | 7 ++++--- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index de4f7371..8757e401 100644 --- a/package.json +++ b/package.json @@ -34,6 +34,7 @@ "parallelshell": "^2.0.0", "qs-iconv": "^1.0.4", "safe-publish-latest": "^1.1.1", + "safer-buffer": "^2.0.2", "tape": "^4.6.3" }, "scripts": { diff --git a/test/parse.js b/test/parse.js index a68ac92a..a90739b9 100644 --- a/test/parse.js +++ b/test/parse.js @@ -3,6 +3,7 @@ var test = require('tape'); var qs = require('../'); var iconv = require('iconv-lite'); +var SaferBuffer = require('safer-buffer').Buffer; test('parse()', function (t) { t.test('parses a simple string', function (st) { @@ -230,7 +231,7 @@ test('parse()', function (t) { }); t.test('parses buffers correctly', function (st) { - var b = new Buffer('test'); + var b = SaferBuffer.from('test'); st.deepEqual(qs.parse({ a: b }), { a: b }); st.end(); }); @@ -511,7 +512,7 @@ test('parse()', function (t) { result.push(parseInt(parts[1], 16)); parts = reg.exec(str); } - return iconv.decode(new Buffer(result), 'shift_jis').toString(); + return iconv.decode(SaferBuffer.from(result), 'shift_jis').toString(); } }), { 県: '大阪府' }); st.end(); diff --git a/test/stringify.js b/test/stringify.js index 8a4744b0..11504fdb 100644 --- a/test/stringify.js +++ b/test/stringify.js @@ -3,6 +3,7 @@ var test = require('tape'); var qs = require('../'); var iconv = require('iconv-lite'); +var SaferBuffer = require('safer-buffer').Buffer; test('stringify()', function (t) { t.test('stringifies a querystring object', function (st) { @@ -325,8 +326,8 @@ test('stringify()', function (t) { }); t.test('stringifies buffer values', function (st) { - st.equal(qs.stringify({ a: new Buffer('test') }), 'a=test'); - st.equal(qs.stringify({ a: { b: new Buffer('test') } }), 'a%5Bb%5D=test'); + 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(); }); @@ -460,7 +461,7 @@ test('stringify()', function (t) { }); t.test('can use custom encoder for a buffer object', { skip: typeof Buffer === 'undefined' }, function (st) { - st.equal(qs.stringify({ a: new Buffer([1]) }, { + st.equal(qs.stringify({ a: SaferBuffer.from([1]) }, { encoder: function (buffer) { if (typeof buffer === 'string') { return buffer; From 45dcd40b1fa0b19a3c75fc462908d24dd675c1ae Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Sun, 13 May 2018 15:20:45 -0700 Subject: [PATCH 021/278] [Fix] when `parseArrays` is false, properly handle keys ending in `[]` Fixes #260. --- dist/qs.js | 20 +++++++++++--------- lib/parse.js | 12 ++++++++---- test/parse.js | 9 ++++++++- 3 files changed, 27 insertions(+), 14 deletions(-) diff --git a/dist/qs.js b/dist/qs.js index f609e622..bf895a9e 100644 --- a/dist/qs.js +++ b/dist/qs.js @@ -1,4 +1,4 @@ -(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 e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o= 0 && - (options.parseArrays && index <= options.arrayLimit) + if (!options.parseArrays && cleanRoot === '') { + obj = { 0: val }; + } else if ( + !isNaN(index) + && root !== cleanRoot + && String(index) === cleanRoot + && index >= 0 + && (options.parseArrays && index <= options.arrayLimit) ) { obj = []; obj[index] = parseObject(chain, val, options); @@ -587,4 +589,4 @@ exports.isBuffer = function (obj) { }; },{}]},{},[2])(2) -}); \ No newline at end of file +}); diff --git a/lib/parse.js b/lib/parse.js index 1307e9d7..6eabc3c0 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -50,14 +50,16 @@ var parseObject = function parseObjectRecursive(chain, val, options) { var root = chain.shift(); var obj; - if (root === '[]') { + if (root === '[]' && options.parseArrays) { obj = []; obj = obj.concat(parseObject(chain, val, options)); } else { obj = options.plainObjects ? Object.create(null) : {}; var cleanRoot = root.charAt(0) === '[' && root.charAt(root.length - 1) === ']' ? root.slice(1, -1) : root; var index = parseInt(cleanRoot, 10); - if ( + if (!options.parseArrays && cleanRoot === '') { + obj = { 0: val }; + } else if ( !isNaN(index) && root !== cleanRoot && String(index) === cleanRoot && @@ -96,8 +98,10 @@ var parseKeys = function parseQueryStringKeys(givenKey, val, options) { var keys = []; if (parent) { - // If we aren't using plain objects, optionally prefix keys - // that would overwrite object prototype properties + /* + * If we aren't using plain objects, optionally prefix keys + * that would overwrite object prototype properties + */ if (!options.plainObjects && has.call(Object.prototype, parent)) { if (!options.allowPrototypes) { return; diff --git a/test/parse.js b/test/parse.js index e451e91f..209fa027 100644 --- a/test/parse.js +++ b/test/parse.js @@ -300,7 +300,14 @@ test('parse()', function (t) { }); t.test('allows disabling array parsing', function (st) { - st.deepEqual(qs.parse('a[0]=b&a[1]=c', { parseArrays: false }), { a: { 0: 'b', 1: 'c' } }); + 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(); }); From 439534a4be80d59720f55955ec94329f8a6535ce Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Wed, 26 Jun 2019 10:06:17 -0700 Subject: [PATCH 022/278] [meta] add FUNDING.yml This is an experiment; I intend to use 100% of funds to support the OSS community and my OSS projects' costs. --- .github/FUNDING.yml | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 .github/FUNDING.yml diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 00000000..0355f4f5 --- /dev/null +++ b/.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 From c38b943b2803a15d7a5c890d3662c83387b0bde7 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Fri, 7 Sep 2018 21:40:34 -0700 Subject: [PATCH 023/278] [Tests] remove nonexistent tape option --- test/parse.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/parse.js b/test/parse.js index 209fa027..a68ac92a 100644 --- a/test/parse.js +++ b/test/parse.js @@ -255,7 +255,7 @@ test('parse()', function (t) { st.end(); }); - t.test('should not throw when a native prototype has an enumerable property', { parallel: false }, function (st) { + t.test('should not throw when a native prototype has an enumerable property', function (st) { Object.prototype.crash = ''; Array.prototype.crash = ''; st.doesNotThrow(qs.parse.bind(null, 'a=b')); From 471261a4b80018b350e26eb74df9c9b6e53eb98f Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Mon, 17 Sep 2018 16:16:06 -0700 Subject: [PATCH 024/278] [Fix] `utils`: `merge`: fix crash when `source` is a truthy primitive & no options are provided --- lib/utils.js | 2 +- test/utils.js | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/utils.js b/lib/utils.js index b2143323..e0ebba2d 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -31,7 +31,7 @@ exports.merge = function (target, source, options) { if (Array.isArray(target)) { target.push(source); } else if (typeof target === 'object') { - if (options.plainObjects || options.allowPrototypes || !has.call(Object.prototype, source)) { + if ((options && (options.plainObjects || options.allowPrototypes)) || !has.call(Object.prototype, source)) { target[source] = true; } } else { diff --git a/test/utils.js b/test/utils.js index 0721dd8e..999f860d 100644 --- a/test/utils.js +++ b/test/utils.js @@ -18,5 +18,8 @@ test('merge()', function (t) { 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 }); + t.end(); }); From 47e46a6e1a6cfb30a17d839ecdf41bdea3df482f Mon Sep 17 00:00:00 2001 From: Chris Dyson Date: Mon, 17 Sep 2018 10:45:31 +1200 Subject: [PATCH 025/278] [Fix] `stringify`: fix a crash with `strictNullHandling` and a custom `filter`/`serializeDate` (#279) --- lib/stringify.js | 4 +++- test/stringify.js | 44 ++++++++++++++++++++++++++++++++------------ 2 files changed, 35 insertions(+), 13 deletions(-) diff --git a/lib/stringify.js b/lib/stringify.js index 8b5c8484..4b510688 100644 --- a/lib/stringify.js +++ b/lib/stringify.js @@ -46,7 +46,9 @@ var stringify = function stringify( // eslint-disable-line func-name-matching obj = filter(prefix, obj); } else if (obj instanceof Date) { obj = serializeDate(obj); - } else if (obj === null) { + } + + if (obj === null) { if (strictNullHandling) { return encoder ? encoder(prefix) : prefix; } diff --git a/test/stringify.js b/test/stringify.js index c3dd095c..53cda69c 100644 --- a/test/stringify.js +++ b/test/stringify.js @@ -453,7 +453,7 @@ test('stringify()', function (t) { }); t.test('throws error with wrong encoder', function (st) { - st.throws(function () { + st['throws'](function () { qs.stringify({}, { encoder: 'string' }); }, new TypeError('Encoder has to be a function.')); st.end(); @@ -483,7 +483,7 @@ test('stringify()', function (t) { mutatedDate.toISOString = function () { throw new SyntaxError(); }; - st.throws(function () { + st['throws'](function () { mutatedDate.toISOString(); }, SyntaxError); st.equal( @@ -523,16 +523,36 @@ test('stringify()', function (t) { }); 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.') - ); - } - ); + ['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('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.end(); }); From bc90696163f006365ed1f41dc7ef7a655ed47ccf Mon Sep 17 00:00:00 2001 From: Andreas Lind Date: Fri, 27 Jul 2018 00:09:13 +0200 Subject: [PATCH 026/278] [Refactor] `stringify`: Avoid arr = arr.concat(...), push to the existing instance (#269) --- lib/stringify.js | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/lib/stringify.js b/lib/stringify.js index 4b510688..616f8458 100644 --- a/lib/stringify.js +++ b/lib/stringify.js @@ -15,6 +15,12 @@ var arrayPrefixGenerators = { } }; +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 defaults = { @@ -85,7 +91,7 @@ var stringify = function stringify( // eslint-disable-line func-name-matching } if (Array.isArray(obj)) { - values = values.concat(stringify( + pushToArray(values, stringify( obj[key], generateArrayPrefix(prefix, key), generateArrayPrefix, @@ -99,7 +105,7 @@ var stringify = function stringify( // eslint-disable-line func-name-matching formatter )); } else { - values = values.concat(stringify( + pushToArray(values, stringify( obj[key], prefix + (allowDots ? '.' + key : '[' + key + ']'), generateArrayPrefix, @@ -182,8 +188,7 @@ module.exports = function (object, opts) { if (skipNulls && obj[key] === null) { continue; } - - keys = keys.concat(stringify( + pushToArray(keys, stringify( obj[key], key, generateArrayPrefix, From f326a8098ae36851d20a8d68102c714ec05c9de6 Mon Sep 17 00:00:00 2001 From: Dmitry Kirilyuk Date: Wed, 16 Jan 2019 16:21:27 +0300 Subject: [PATCH 027/278] [Docs] Clarify the need for "arrayLimit" option --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1ec74627..53afe2f3 100644 --- a/README.md +++ b/README.md @@ -169,7 +169,7 @@ assert.deepEqual(withIndexedEmptyString, { a: ['b', '', 'c'] }); ``` **qs** will also limit specifying indices in an array to a maximum index of `20`. Any array members with an index of greater than `20` will -instead be converted to an object with the index as the key: +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'); From 915517c73b1b13e915c1f2d3e044ef3ee7358d75 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Thu, 31 Jan 2019 14:24:14 -0800 Subject: [PATCH 028/278] [Refactor] use cached `Array.isArray` --- lib/stringify.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/stringify.js b/lib/stringify.js index 616f8458..49565c13 100644 --- a/lib/stringify.js +++ b/lib/stringify.js @@ -76,7 +76,7 @@ var stringify = function stringify( // eslint-disable-line func-name-matching } var objKeys; - if (Array.isArray(filter)) { + if (isArray(filter)) { objKeys = filter; } else { var keys = Object.keys(obj); @@ -90,7 +90,7 @@ var stringify = function stringify( // eslint-disable-line func-name-matching continue; } - if (Array.isArray(obj)) { + if (isArray(obj)) { pushToArray(values, stringify( obj[key], generateArrayPrefix(prefix, key), @@ -152,7 +152,7 @@ module.exports = function (object, opts) { if (typeof options.filter === 'function') { filter = options.filter; obj = filter('', obj); - } else if (Array.isArray(options.filter)) { + } else if (isArray(options.filter)) { filter = options.filter; objKeys = filter; } From 213d513b990fa871ce0d731489a60c256d743480 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Fri, 1 Feb 2019 13:48:43 -0800 Subject: [PATCH 029/278] [Fix]` `utils.merge`: avoid a crash with a null target and a truthy non-array source --- lib/utils.js | 2 +- test/utils.js | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/utils.js b/lib/utils.js index e0ebba2d..32a83c75 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -30,7 +30,7 @@ exports.merge = function (target, source, options) { if (typeof source !== 'object') { if (Array.isArray(target)) { target.push(source); - } else if (typeof target === 'object') { + } else if (target && typeof target === 'object') { if ((options && (options.plainObjects || options.allowPrototypes)) || !has.call(Object.prototype, source)) { target[source] = true; } diff --git a/test/utils.js b/test/utils.js index 999f860d..67ef9369 100644 --- a/test/utils.js +++ b/test/utils.js @@ -4,6 +4,8 @@ var test = require('tape'); 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({ a: 'b' }, { a: 'c' }), { a: ['b', 'c'] }, 'merges two objects with the same key'); var oneMerged = utils.merge({ foo: 'bar' }, { foo: { first: '123' } }); From f7139bfdb67312a6f8c55ecc7ec4657f6d60ae89 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Sun, 3 Feb 2019 00:11:31 -0800 Subject: [PATCH 030/278] [Fix] `utils.merge`: avoid a crash with a null target and an array source --- lib/utils.js | 2 +- test/utils.js | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/utils.js b/lib/utils.js index 32a83c75..9095b9d1 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -41,7 +41,7 @@ exports.merge = function (target, source, options) { return target; } - if (typeof target !== 'object') { + if (!target || typeof target !== 'object') { return [target].concat(source); } diff --git a/test/utils.js b/test/utils.js index 67ef9369..3c7ee523 100644 --- a/test/utils.js +++ b/test/utils.js @@ -6,6 +6,8 @@ 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' } }); From e0b2c4b0d4a126337bb550395e61265258c3c083 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Tue, 20 Mar 2018 22:22:47 +0000 Subject: [PATCH 031/278] [Tests] use `safer-buffer` instead of `Buffer` constructor https://github.com/ChALkeR/safer-buffer/blob/master/Porting-Buffer.md#variant-2 / https://github.com/nodejs/node/issues/19079 --- package.json | 1 + test/parse.js | 5 +++-- test/stringify.js | 7 ++++--- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 8dc70096..61997d35 100644 --- a/package.json +++ b/package.json @@ -34,6 +34,7 @@ "parallelshell": "^2.0.0", "qs-iconv": "^1.0.4", "safe-publish-latest": "^1.1.1", + "safer-buffer": "^2.0.2", "tape": "^4.6.3" }, "scripts": { diff --git a/test/parse.js b/test/parse.js index a68ac92a..a90739b9 100644 --- a/test/parse.js +++ b/test/parse.js @@ -3,6 +3,7 @@ var test = require('tape'); var qs = require('../'); var iconv = require('iconv-lite'); +var SaferBuffer = require('safer-buffer').Buffer; test('parse()', function (t) { t.test('parses a simple string', function (st) { @@ -230,7 +231,7 @@ test('parse()', function (t) { }); t.test('parses buffers correctly', function (st) { - var b = new Buffer('test'); + var b = SaferBuffer.from('test'); st.deepEqual(qs.parse({ a: b }), { a: b }); st.end(); }); @@ -511,7 +512,7 @@ test('parse()', function (t) { result.push(parseInt(parts[1], 16)); parts = reg.exec(str); } - return iconv.decode(new Buffer(result), 'shift_jis').toString(); + return iconv.decode(SaferBuffer.from(result), 'shift_jis').toString(); } }), { 県: '大阪府' }); st.end(); diff --git a/test/stringify.js b/test/stringify.js index 53cda69c..84d1a407 100644 --- a/test/stringify.js +++ b/test/stringify.js @@ -3,6 +3,7 @@ var test = require('tape'); var qs = require('../'); var iconv = require('iconv-lite'); +var SaferBuffer = require('safer-buffer').Buffer; test('stringify()', function (t) { t.test('stringifies a querystring object', function (st) { @@ -325,8 +326,8 @@ test('stringify()', function (t) { }); t.test('stringifies buffer values', function (st) { - st.equal(qs.stringify({ a: new Buffer('test') }), 'a=test'); - st.equal(qs.stringify({ a: { b: new Buffer('test') } }), 'a%5Bb%5D=test'); + 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(); }); @@ -460,7 +461,7 @@ test('stringify()', function (t) { }); t.test('can use custom encoder for a buffer object', { skip: typeof Buffer === 'undefined' }, function (st) { - st.equal(qs.stringify({ a: new Buffer([1]) }, { + st.equal(qs.stringify({ a: SaferBuffer.from([1]) }, { encoder: function (buffer) { if (typeof buffer === 'string') { return buffer; From d828941767fe56b13dca35ebda21beb22bb56398 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Sun, 13 May 2018 15:20:45 -0700 Subject: [PATCH 032/278] [Fix] when `parseArrays` is false, properly handle keys ending in `[]` Fixes #260. --- lib/parse.js | 12 ++++++++---- test/parse.js | 9 ++++++++- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/lib/parse.js b/lib/parse.js index c57f4691..2110e74a 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -50,14 +50,16 @@ var parseObject = function parseObject(chain, val, options) { var root = chain.shift(); var obj; - if (root === '[]') { + if (root === '[]' && options.parseArrays) { obj = []; obj = obj.concat(parseObject(chain, val, options)); } else { obj = options.plainObjects ? Object.create(null) : {}; var cleanRoot = root.charAt(0) === '[' && root.charAt(root.length - 1) === ']' ? root.slice(1, -1) : root; var index = parseInt(cleanRoot, 10); - if ( + if (!options.parseArrays && cleanRoot === '') { + obj = { 0: val }; + } else if ( !isNaN(index) && root !== cleanRoot && String(index) === cleanRoot && @@ -96,8 +98,10 @@ var parseKeys = function parseKeys(givenKey, val, options) { var keys = []; if (parent) { - // If we aren't using plain objects, optionally prefix keys - // that would overwrite object prototype properties + /* + * If we aren't using plain objects, optionally prefix keys + * that would overwrite object prototype properties + */ if (!options.plainObjects && has.call(Object.prototype, parent)) { if (!options.allowPrototypes) { return; diff --git a/test/parse.js b/test/parse.js index 2ce27f2f..25c929ff 100644 --- a/test/parse.js +++ b/test/parse.js @@ -289,7 +289,14 @@ test('parse()', function (t) { }); t.test('allows disabling array parsing', function (st) { - st.deepEqual(qs.parse('a[0]=b&a[1]=c', { parseArrays: false }), { a: { '0': 'b', '1': 'c' } }); + 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(); }); From 1b7c83e20bf30ebfc9c9aa615a1edc4d067e7af2 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Wed, 26 Jun 2019 10:06:17 -0700 Subject: [PATCH 033/278] [meta] add FUNDING.yml This is an experiment; I intend to use 100% of funds to support the OSS community and my OSS projects' costs. --- .github/FUNDING.yml | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 .github/FUNDING.yml diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 00000000..0355f4f5 --- /dev/null +++ b/.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 From 42de2073831f6dc9ea63a9051ecf5ad5cf23459f Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Fri, 7 Sep 2018 21:40:34 -0700 Subject: [PATCH 034/278] [Tests] remove nonexistent tape option --- test/parse.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/parse.js b/test/parse.js index 25c929ff..23e5e976 100644 --- a/test/parse.js +++ b/test/parse.js @@ -244,7 +244,7 @@ test('parse()', function (t) { st.end(); }); - t.test('should not throw when a native prototype has an enumerable property', { parallel: false }, function (st) { + t.test('should not throw when a native prototype has an enumerable property', function (st) { Object.prototype.crash = ''; Array.prototype.crash = ''; st.doesNotThrow(qs.parse.bind(null, 'a=b')); From 4c8dcaf862118b73e3bd955a6c1da18029d8f3f8 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Mon, 17 Sep 2018 16:16:06 -0700 Subject: [PATCH 035/278] [Fix] `utils`: `merge`: fix crash when `source` is a truthy primitive & no options are provided --- lib/utils.js | 50 ++++++++++++++++++++++++++++++++++++++++---------- test/utils.js | 16 ++++++++++++++++ 2 files changed, 56 insertions(+), 10 deletions(-) diff --git a/lib/utils.js b/lib/utils.js index ebe8867e..f778daae 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -12,7 +12,7 @@ var hexTable = (function () { var has = Object.prototype.hasOwnProperty; exports.arrayToObject = function (source, options) { - var obj = options.plainObjects ? Object.create(null) : {}; + var obj = options && options.plainObjects ? Object.create(null) : {}; for (var i = 0; i < source.length; ++i) { if (typeof source[i] !== 'undefined') { obj[i] = source[i]; @@ -22,16 +22,30 @@ exports.arrayToObject = function (source, options) { return obj; }; -exports.merge = function (target, source, options) { +var isArray = Array.isArray; + +var arrayToObject = function arrayToObject(source, options) { + var obj = options && options.plainObjects ? Object.create(null) : {}; + for (var i = 0; i < source.length; ++i) { + if (typeof source[i] !== 'undefined') { + obj[i] = source[i]; + } + } + + return obj; +}; + +exports.merge = function merge(target, source, options) { + /* eslint no-param-reassign: 0 */ if (!source) { return target; } if (typeof source !== 'object') { - if (Array.isArray(target)) { + if (isArray(target)) { target.push(source); - } else if (typeof target === 'object') { - if (options.plainObjects || options.allowPrototypes || !has.call(Object.prototype, source)) { + } else if (target && typeof target === 'object') { + if ((options && (options.plainObjects || options.allowPrototypes)) || !has.call(Object.prototype, source)) { target[source] = true; } } else { @@ -41,20 +55,36 @@ exports.merge = function (target, source, options) { return target; } - if (typeof target !== 'object') { + if (!target || typeof target !== 'object') { return [target].concat(source); } var mergeTarget = target; - if (Array.isArray(target) && !Array.isArray(source)) { - mergeTarget = exports.arrayToObject(target, options); + 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.push(item); + } + } else { + target[i] = item; + } + }); + return target; } return Object.keys(source).reduce(function (acc, key) { var value = source[key]; if (has.call(acc, key)) { - acc[key] = exports.merge(acc[key], value, options); + acc[key] = merge(acc[key], value, options); } else { acc[key] = value; } @@ -132,7 +162,7 @@ exports.compact = function (obj, references) { refs.push(obj); - if (Array.isArray(obj)) { + if (isArray(obj)) { var compacted = []; for (var i = 0; i < obj.length; ++i) { diff --git a/test/utils.js b/test/utils.js index 4a8d8246..999f860d 100644 --- a/test/utils.js +++ b/test/utils.js @@ -5,5 +5,21 @@ var utils = require('../lib/utils'); test('merge()', function (t) { 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 }); + t.end(); }); From 39a11bc59ab8d86d75e4a56814bca8e825fdf037 Mon Sep 17 00:00:00 2001 From: Dmitry Kirilyuk Date: Wed, 16 Jan 2019 16:21:27 +0300 Subject: [PATCH 036/278] [Docs] Clarify the need for "arrayLimit" option --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 97e39079..d00c86bc 100644 --- a/README.md +++ b/README.md @@ -169,7 +169,7 @@ assert.deepEqual(withIndexedEmptyString, { a: ['b', '', 'c'] }); ``` **qs** will also limit specifying indices in an array to a maximum index of `20`. Any array members with an index of greater than `20` will -instead be converted to an object with the index as the key: +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'); From 563588dafbfda6fa1800b9959700f6b9b4bd3038 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Thu, 31 Jan 2019 14:24:14 -0800 Subject: [PATCH 037/278] [Refactor] use cached `Array.isArray` --- .eslintrc | 34 +++++++++++++++++++--------------- lib/stringify.js | 13 ++++++++----- test/stringify.js | 11 +++++------ 3 files changed, 32 insertions(+), 26 deletions(-) diff --git a/.eslintrc b/.eslintrc index 1faac273..16344a23 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,19 +1,23 @@ { - "root": true, + "root": true, - "extends": "@ljharb", + "extends": "@ljharb", - "rules": { - "complexity": [2, 22], - "consistent-return": [1], - "id-length": [2, { "min": 1, "max": 25, "properties": "never" }], - "indent": [2, 4], - "max-params": [2, 9], - "max-statements": [2, 36], - "no-extra-parens": [1], - "no-continue": [1], - "no-magic-numbers": 0, - "no-restricted-syntax": [2, "BreakStatement", "DebuggerStatement", "ForInStatement", "LabeledStatement", "WithStatement"], - "operator-linebreak": 1 - } + "rules": { + "complexity": [2, 25], + "consistent-return": [1], + "func-name-matching": 0, + "id-length": [2, { "min": 1, "max": 25, "properties": "never" }], + "indent": [2, 4], + "max-len": 0, + "max-lines-per-function": 0, + "max-params": [2, 9], + "max-statements": [0, 36], + "no-extra-parens": [1], + "no-continue": [1], + "no-magic-numbers": 0, + "no-restricted-syntax": [2, "BreakStatement", "DebuggerStatement", "ForInStatement", "LabeledStatement", "WithStatement"], + "operator-linebreak": 1, + "sort-keys": 0, + }, } diff --git a/lib/stringify.js b/lib/stringify.js index 6e1c9a26..f05c6dd0 100644 --- a/lib/stringify.js +++ b/lib/stringify.js @@ -22,6 +22,8 @@ var defaults = { encoder: Utils.encode }; +var isArray = Array.isArray; + var stringify = function stringify(object, prefix, generateArrayPrefix, strictNullHandling, skipNulls, encoder, filter, sort, allowDots) { var obj = object; if (typeof filter === 'function') { @@ -50,7 +52,7 @@ var stringify = function stringify(object, prefix, generateArrayPrefix, strictNu } var objKeys; - if (Array.isArray(filter)) { + if (isArray(filter)) { objKeys = filter; } else { var keys = Object.keys(obj); @@ -64,7 +66,7 @@ var stringify = function stringify(object, prefix, generateArrayPrefix, strictNu continue; } - if (Array.isArray(obj)) { + if (isArray(obj)) { values = values.concat(stringify(obj[key], generateArrayPrefix(prefix, key), generateArrayPrefix, strictNullHandling, skipNulls, encoder, filter, sort, allowDots)); } else { values = values.concat(stringify(obj[key], prefix + (allowDots ? '.' + key : '[' + key + ']'), generateArrayPrefix, strictNullHandling, skipNulls, encoder, filter, sort, allowDots)); @@ -81,7 +83,7 @@ module.exports = function (object, opts) { var strictNullHandling = typeof options.strictNullHandling === 'boolean' ? options.strictNullHandling : defaults.strictNullHandling; var skipNulls = typeof options.skipNulls === 'boolean' ? options.skipNulls : defaults.skipNulls; var encode = typeof options.encode === 'boolean' ? options.encode : defaults.encode; - var encoder = encode ? (typeof options.encoder === 'function' ? options.encoder : defaults.encoder) : null; + var encoder = encode ? typeof options.encoder === 'function' ? options.encoder : defaults.encoder : null; var sort = typeof options.sort === 'function' ? options.sort : null; var allowDots = typeof options.allowDots === 'undefined' ? false : options.allowDots; var objKeys; @@ -94,8 +96,9 @@ module.exports = function (object, opts) { if (typeof options.filter === 'function') { filter = options.filter; obj = filter('', obj); - } else if (Array.isArray(options.filter)) { - objKeys = filter = options.filter; + } else if (isArray(options.filter)) { + objKeys = options.filter; + filter = options.filter; } var keys = []; diff --git a/test/stringify.js b/test/stringify.js index 699397e3..3befb520 100644 --- a/test/stringify.js +++ b/test/stringify.js @@ -39,7 +39,6 @@ test('stringify()', function (t) { st.end(); }); - t.test('omits nested nulls when asked', function (st) { st.equal(qs.stringify({ a: { b: 'c', d: null } }, { skipNulls: true }), 'a%5Bb%5D=c'); st.end(); @@ -258,20 +257,20 @@ test('stringify()', function (t) { 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.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({ 県: '大阪府', '': ''}, { + 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) { + for (var i = 0; i < buf.length; ++i) { result.push(buf.readUInt8(i).toString(16)); } return '%' + result.join('%'); @@ -281,7 +280,7 @@ test('stringify()', function (t) { }); t.test('throws error with wrong encoder', function (st) { - st.throws(function () { + st['throws'](function () { qs.stringify({}, { encoder: 'string' }); From c103b909b2b4a12be5bd6437149115f31268a63a Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Fri, 1 Feb 2019 13:48:43 -0800 Subject: [PATCH 038/278] [Fix]` `utils.merge`: avoid a crash with a null target and a truthy non-array source --- test/utils.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/utils.js b/test/utils.js index 999f860d..67ef9369 100644 --- a/test/utils.js +++ b/test/utils.js @@ -4,6 +4,8 @@ var test = require('tape'); 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({ a: 'b' }, { a: 'c' }), { a: ['b', 'c'] }, 'merges two objects with the same key'); var oneMerged = utils.merge({ foo: 'bar' }, { foo: { first: '123' } }); From 0d8291611f243ab925be9871154ab77ce93fbe3e Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Sun, 3 Feb 2019 00:11:31 -0800 Subject: [PATCH 039/278] [Fix] `utils.merge`: avoid a crash with a null target and an array source --- test/utils.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/utils.js b/test/utils.js index 67ef9369..3c7ee523 100644 --- a/test/utils.js +++ b/test/utils.js @@ -6,6 +6,8 @@ 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' } }); From 943e41177c1053d287c7cb4cdef049ed84505d51 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Wed, 26 Jun 2019 00:22:20 -0700 Subject: [PATCH 040/278] =?UTF-8?q?[meta]=20Clean=20up=20license=20text=20?= =?UTF-8?q?so=20it=E2=80=99s=20properly=20detected=20as=20BSD-3-Clause?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- LICENSE | 28 ---------------------------- LICENSE.md | 29 +++++++++++++++++++++++++++++ 2 files changed, 29 insertions(+), 28 deletions(-) delete mode 100644 LICENSE create mode 100644 LICENSE.md diff --git a/LICENSE b/LICENSE deleted file mode 100644 index d4569487..00000000 --- a/LICENSE +++ /dev/null @@ -1,28 +0,0 @@ -Copyright (c) 2014 Nathan LaFreniere and other 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: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * 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. - * The names of any contributors may not 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 HOLDERS AND 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. - - * * * - -The complete list of contributors can be found at: https://github.com/hapijs/qs/graphs/contributors diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 00000000..fecf6b69 --- /dev/null +++ b/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. From 081a3ab2ca94b8ebe35ed7a018300996cb3694fd Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Tue, 20 Mar 2018 22:22:47 +0000 Subject: [PATCH 041/278] [Tests] use `safer-buffer` instead of `Buffer` constructor https://github.com/ChALkeR/safer-buffer/blob/master/Porting-Buffer.md#variant-2 / https://github.com/nodejs/node/issues/19079 --- package.json | 14 ++++++++------ test/parse.js | 5 +++-- test/stringify.js | 11 +++++------ 3 files changed, 16 insertions(+), 14 deletions(-) diff --git a/package.json b/package.json index cec10972..4bf439f8 100644 --- a/package.json +++ b/package.json @@ -24,16 +24,18 @@ }, "dependencies": {}, "devDependencies": { + "@ljharb/eslint-config": "^6.0.0", "browserify": "^13.0.1", - "tape": "^4.6.0", "covert": "^1.1.0", - "mkdirp": "^0.5.1", "eslint": "^3.1.0", - "@ljharb/eslint-config": "^6.0.0", - "parallelshell": "^2.0.0", + "evalmd": "^0.0.17", "iconv-lite": "^0.4.13", - "qs-iconv": "^1.0.3", - "evalmd": "^0.0.17" + "mkdirp": "^0.5.1", + "parallelshell": "^2.0.0", + "qs-iconv": "^1.0.4", + "safe-publish-latest": "^1.1.1", + "safer-buffer": "^2.0.2", + "tape": "^4.6.3" }, "scripts": { "pretest": "npm run --silent readme && npm run --silent lint", diff --git a/test/parse.js b/test/parse.js index 23e5e976..f6051312 100644 --- a/test/parse.js +++ b/test/parse.js @@ -3,6 +3,7 @@ var test = require('tape'); var qs = require('../'); var iconv = require('iconv-lite'); +var SaferBuffer = require('safer-buffer').Buffer; test('parse()', function (t) { t.test('parses a simple string', function (st) { @@ -221,7 +222,7 @@ test('parse()', function (t) { }); t.test('parses buffers correctly', function (st) { - var b = new Buffer('test'); + var b = SaferBuffer.from('test'); st.deepEqual(qs.parse({ a: b }), { a: b }); st.end(); }); @@ -501,7 +502,7 @@ test('parse()', function (t) { result.push(parseInt(parts[1], 16)); last = parts.index + parts[0].length; } - return iconv.decode(new Buffer(result), 'shift_jis').toString(); + return iconv.decode(SaferBuffer.from(result), 'shift_jis').toString(); } }), { 県: '大阪府' }); st.end(); diff --git a/test/stringify.js b/test/stringify.js index 3befb520..46be2334 100644 --- a/test/stringify.js +++ b/test/stringify.js @@ -3,6 +3,7 @@ var test = require('tape'); var qs = require('../'); var iconv = require('iconv-lite'); +var SaferBuffer = require('safer-buffer').Buffer; test('stringify()', function (t) { t.test('stringifies a querystring object', function (st) { @@ -193,8 +194,8 @@ test('stringify()', function (t) { }); t.test('stringifies buffer values', function (st) { - st.equal(qs.stringify({ a: new Buffer('test') }), 'a=test'); - st.equal(qs.stringify({ a: { b: new Buffer('test') } }), 'a%5Bb%5D=test'); + 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(); }); @@ -288,10 +289,8 @@ test('stringify()', function (t) { st.end(); }); - t.test('can use custom encoder for a buffer object', { - skip: typeof Buffer === 'undefined' - }, function (st) { - st.equal(qs.stringify({ a: new Buffer([1]) }, { + 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; From 8dbad285bbd8446bdfe9fe75b3b69f365942e4d9 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Fri, 29 Mar 2019 14:50:56 -0400 Subject: [PATCH 042/278] [Fix] fix for an impossible situation: when the formatter is called with a non-string value Note that all these tests passed already. Since the only time a formatter is called is in a context where it is concatenated with another string using `+`, this is a redundant step. However, for pedantic correctness and documentation, the contract for formatters is to always return a string. --- lib/formats.js | 2 +- test/stringify.js | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/lib/formats.js b/lib/formats.js index df459975..702da12e 100644 --- a/lib/formats.js +++ b/lib/formats.js @@ -10,7 +10,7 @@ module.exports = { return replace.call(value, percentTwenties, '+'); }, RFC3986: function (value) { - return value; + return String(value); } }, RFC1738: 'RFC1738', diff --git a/test/stringify.js b/test/stringify.js index 53041c2e..7a07931b 100644 --- a/test/stringify.js +++ b/test/stringify.js @@ -520,6 +520,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(); }); @@ -560,17 +566,20 @@ test('stringify()', function (t) { t.test('RFC 1738 spaces 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.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(); }); From faeab539919d0cd456a9687cdef1a510eca2efad Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Thu, 6 Sep 2018 17:28:03 -0700 Subject: [PATCH 043/278] [Refactor] `formats`: tiny bit of cleanup. Additionally, `stringify`'s defaults get `format`, even though it isn't necessary due to the line that assigns it to the internal options. This way, however, things are a bit more internally consistent. --- lib/formats.js | 28 ++++++++++++++++++---------- lib/stringify.js | 4 +++- 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/lib/formats.js b/lib/formats.js index 702da12e..a4ecca78 100644 --- a/lib/formats.js +++ b/lib/formats.js @@ -3,16 +3,24 @@ var replace = String.prototype.replace; var percentTwenties = /%20/g; -module.exports = { - 'default': 'RFC3986', - formatters: { - RFC1738: function (value) { - return replace.call(value, percentTwenties, '+'); - }, - RFC3986: function (value) { - return String(value); - } - }, +var util = require('./utils'); + +var Format = { RFC1738: 'RFC1738', RFC3986: 'RFC3986' }; + +module.exports = util.assign( + { + 'default': Format.RFC3986, + formatters: { + RFC1738: function (value) { + return replace.call(value, percentTwenties, '+'); + }, + RFC3986: function (value) { + return String(value); + } + } + }, + Format +); diff --git a/lib/stringify.js b/lib/stringify.js index 7455049c..4eac941b 100644 --- a/lib/stringify.js +++ b/lib/stringify.js @@ -25,6 +25,7 @@ var pushToArray = function (arr, valueOrArray) { var toISO = Date.prototype.toISOString; +var defaultFormat = formats['default']; var defaults = { addQueryPrefix: false, allowDots: false, @@ -34,7 +35,8 @@ 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 From d666dcb6e26b7bfeb0caca6b7e2ed5b4db1c63a7 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Fri, 29 Mar 2019 14:50:56 -0400 Subject: [PATCH 044/278] [Fix] fix for an impossible situation: when the formatter is called with a non-string value Note that all these tests passed already. Since the only time a formatter is called is in a context where it is concatenated with another string using `+`, this is a redundant step. However, for pedantic correctness and documentation, the contract for formatters is to always return a string. --- lib/formats.js | 2 +- test/stringify.js | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/lib/formats.js b/lib/formats.js index df459975..702da12e 100644 --- a/lib/formats.js +++ b/lib/formats.js @@ -10,7 +10,7 @@ module.exports = { return replace.call(value, percentTwenties, '+'); }, RFC3986: function (value) { - return value; + return String(value); } }, RFC1738: 'RFC1738', diff --git a/test/stringify.js b/test/stringify.js index 53041c2e..7a07931b 100644 --- a/test/stringify.js +++ b/test/stringify.js @@ -520,6 +520,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(); }); @@ -560,17 +566,20 @@ test('stringify()', function (t) { t.test('RFC 1738 spaces 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.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(); }); From da781934f4bb2eafad848a8a3c40e650fc767a32 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Thu, 6 Sep 2018 17:28:03 -0700 Subject: [PATCH 045/278] [Refactor] `formats`: tiny bit of cleanup. Additionally, `stringify`'s defaults get `format`, even though it isn't necessary due to the line that assigns it to the internal options. This way, however, things are a bit more internally consistent. --- lib/formats.js | 28 ++++++++++++++++++---------- lib/stringify.js | 4 +++- 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/lib/formats.js b/lib/formats.js index 702da12e..a4ecca78 100644 --- a/lib/formats.js +++ b/lib/formats.js @@ -3,16 +3,24 @@ var replace = String.prototype.replace; var percentTwenties = /%20/g; -module.exports = { - 'default': 'RFC3986', - formatters: { - RFC1738: function (value) { - return replace.call(value, percentTwenties, '+'); - }, - RFC3986: function (value) { - return String(value); - } - }, +var util = require('./utils'); + +var Format = { RFC1738: 'RFC1738', RFC3986: 'RFC3986' }; + +module.exports = util.assign( + { + 'default': Format.RFC3986, + formatters: { + RFC1738: function (value) { + return replace.call(value, percentTwenties, '+'); + }, + RFC3986: function (value) { + return String(value); + } + } + }, + Format +); diff --git a/lib/stringify.js b/lib/stringify.js index 7455049c..4eac941b 100644 --- a/lib/stringify.js +++ b/lib/stringify.js @@ -25,6 +25,7 @@ var pushToArray = function (arr, valueOrArray) { var toISO = Date.prototype.toISOString; +var defaultFormat = formats['default']; var defaults = { addQueryPrefix: false, allowDots: false, @@ -34,7 +35,8 @@ 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 From 2b9b41bbbe4e14d70c908254156d51064dac88ed Mon Sep 17 00:00:00 2001 From: Cas Cornelissen Date: Sat, 8 Dec 2018 17:00:28 +0100 Subject: [PATCH 046/278] Fixes typo in CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 13e2c77c..e4696054 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,7 +11,7 @@ - [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` +- [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.2** From b7be5a14063409297cbd1495de10a003124c0130 Mon Sep 17 00:00:00 2001 From: Dmitry Kirilyuk Date: Wed, 16 Jan 2019 16:21:27 +0300 Subject: [PATCH 047/278] Clarify the need for "arrayLimit" option --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7fbe063a..ef6e6838 100644 --- a/README.md +++ b/README.md @@ -238,7 +238,7 @@ assert.deepEqual(withIndexedEmptyString, { a: ['b', '', 'c'] }); ``` **qs** will also limit specifying indices in an array to a maximum index of `20`. Any array members with an index of greater than `20` will -instead be converted to an object with the index as the key: +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'); From 8caafe698466a7a5a0632a0f42a5d0697ee2a7ef Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Thu, 31 Jan 2019 10:09:33 -0800 Subject: [PATCH 048/278] [Refactor] `parse`/`stringify`: make a function to normalize the options --- .editorconfig | 2 +- lib/parse.js | 47 ++++++++++++--------- lib/stringify.js | 103 +++++++++++++++++++++++++++++------------------ 3 files changed, 92 insertions(+), 60 deletions(-) diff --git a/.editorconfig b/.editorconfig index b2654e7a..a4893ddf 100644 --- a/.editorconfig +++ b/.editorconfig @@ -7,7 +7,7 @@ end_of_line = lf charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true -max_line_length = 140 +max_line_length = 160 [test/*] max_line_length = off diff --git a/lib/parse.js b/lib/parse.js index fd675089..13f57129 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -180,31 +180,40 @@ var parseKeys = function parseQueryStringKeys(givenKey, val, options) { return parseObject(keys, val, options); }; -module.exports = function (str, opts) { - var options = opts ? utils.assign({}, opts) : {}; +var normalizeParseOptions = function normalizeParseOptions(opts) { + if (!opts) { + return defaults; + } - if (options.decoder !== null && options.decoder !== undefined && typeof options.decoder !== 'function') { + if (opts.decoder !== null && opts.decoder !== undefined && typeof opts.decoder !== 'function') { throw new TypeError('Decoder has to be a function.'); } - options.ignoreQueryPrefix = options.ignoreQueryPrefix === true; - options.delimiter = typeof options.delimiter === 'string' || utils.isRegExp(options.delimiter) ? options.delimiter : defaults.delimiter; - options.depth = typeof options.depth === 'number' ? options.depth : defaults.depth; - options.arrayLimit = typeof options.arrayLimit === 'number' ? options.arrayLimit : defaults.arrayLimit; - options.parseArrays = options.parseArrays !== false; - options.decoder = typeof options.decoder === 'function' ? options.decoder : defaults.decoder; - options.allowDots = typeof options.allowDots === 'undefined' ? defaults.allowDots : !!options.allowDots; - options.plainObjects = typeof options.plainObjects === 'boolean' ? options.plainObjects : defaults.plainObjects; - options.allowPrototypes = typeof options.allowPrototypes === 'boolean' ? options.allowPrototypes : defaults.allowPrototypes; - options.parameterLimit = typeof options.parameterLimit === 'number' ? options.parameterLimit : defaults.parameterLimit; - options.strictNullHandling = typeof options.strictNullHandling === 'boolean' ? options.strictNullHandling : defaults.strictNullHandling; - - if (typeof options.charset !== 'undefined' && options.charset !== 'utf-8' && options.charset !== 'iso-8859-1') { + 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'); } - if (typeof options.charset === 'undefined') { - options.charset = defaults.charset; - } + 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, + arrayLimit: typeof opts.arrayLimit === 'number' ? opts.arrayLimit : defaults.arrayLimit, + charset: charset, + charsetSentinel: typeof opts.charsetSentinel === 'boolean' ? opts.charsetSentinel : defaults.charsetSentinel, + 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, + 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, + strictNullHandling: typeof opts.strictNullHandling === 'boolean' ? opts.strictNullHandling : defaults.strictNullHandling + }; +}; + +module.exports = function (str, opts) { + var options = normalizeParseOptions(opts); if (str === '' || str === null || typeof str === 'undefined') { return options.plainObjects ? Object.create(null) : {}; diff --git a/lib/stringify.js b/lib/stringify.js index b5e69ead..586fae18 100644 --- a/lib/stringify.js +++ b/lib/stringify.js @@ -32,6 +32,7 @@ var defaults = { encode: true, encoder: utils.encode, encodeValuesOnly: false, + formatter: formats.formatters[formats['default']], // deprecated indices: false, serializeDate: function serializeDate(date) { // eslint-disable-line func-name-matching @@ -138,34 +139,56 @@ var stringify = function stringify( // eslint-disable-line func-name-matching return values; }; -module.exports = function (object, opts) { - var obj = object; - var options = opts ? utils.assign({}, opts) : {}; +var normalizeStringifyOptions = function normalizeStringifyOptions(opts) { + if (!opts) { + return defaults; + } - if (options.encoder !== null && options.encoder !== undefined && typeof options.encoder !== 'function') { + if (opts.encoder !== null && opts.encoder !== undefined && typeof opts.encoder !== 'function') { throw new TypeError('Encoder has to be a function.'); } - var delimiter = typeof options.delimiter === 'undefined' ? defaults.delimiter : options.delimiter; - var strictNullHandling = typeof options.strictNullHandling === 'boolean' ? options.strictNullHandling : defaults.strictNullHandling; - var skipNulls = typeof options.skipNulls === 'boolean' ? options.skipNulls : defaults.skipNulls; - var encode = typeof options.encode === 'boolean' ? options.encode : defaults.encode; - var encoder = typeof options.encoder === 'function' ? options.encoder : defaults.encoder; - var sort = typeof options.sort === 'function' ? options.sort : null; - var allowDots = typeof options.allowDots === 'undefined' ? defaults.allowDots : !!options.allowDots; - var serializeDate = typeof options.serializeDate === 'function' ? options.serializeDate : defaults.serializeDate; - var encodeValuesOnly = typeof options.encodeValuesOnly === 'boolean' ? options.encodeValuesOnly : defaults.encodeValuesOnly; - var charset = options.charset || defaults.charset; - if (typeof options.charset !== 'undefined' && options.charset !== 'utf-8' && options.charset !== 'iso-8859-1') { - throw new Error('The charset option must be either utf-8, iso-8859-1, or undefined'); + 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'); } - if (typeof options.format === 'undefined') { - options.format = formats['default']; - } else if (!Object.prototype.hasOwnProperty.call(formats.formatters, options.format)) { - throw new TypeError('Unknown format option provided.'); + var format = formats['default']; + if (typeof opts.format !== 'undefined') { + if (!Object.prototype.hasOwnProperty.call(formats.formatters, opts.format)) { + throw new TypeError('Unknown format option provided.'); + } + format = opts.format; } - var formatter = formats.formatters[options.format]; + var formatter = formats.formatters[format]; + + var filter = defaults.filter; + if (typeof opts.filter === 'function' || Array.isArray(opts.filter)) { + filter = opts.filter; + } + + return { + addQueryPrefix: typeof opts.addQueryPrefix === 'boolean' ? opts.addQueryPrefix : defaults.addQueryPrefix, + allowDots: typeof opts.allowDots === 'undefined' ? defaults.allowDots : !!opts.allowDots, + charset: charset, + charsetSentinel: typeof opts.charsetSentinel === 'boolean' ? opts.charsetSentinel : defaults.charsetSentinel, + delimiter: typeof opts.delimiter === 'undefined' ? defaults.delimiter : opts.delimiter, + encode: typeof opts.encode === 'boolean' ? opts.encode : defaults.encode, + encoder: typeof opts.encoder === 'function' ? opts.encoder : defaults.encoder, + encodeValuesOnly: typeof opts.encodeValuesOnly === 'boolean' ? opts.encodeValuesOnly : defaults.encodeValuesOnly, + filter: filter, + 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; @@ -184,10 +207,10 @@ module.exports = function (object, opts) { } var arrayFormat; - if (options.arrayFormat in arrayPrefixGenerators) { - arrayFormat = options.arrayFormat; - } else if ('indices' in options) { - arrayFormat = options.indices ? 'indices' : 'repeat'; + if (opts && opts.arrayFormat in arrayPrefixGenerators) { + arrayFormat = opts.arrayFormat; + } else if (opts && 'indices' in opts) { + arrayFormat = opts.indices ? 'indices' : 'repeat'; } else { arrayFormat = 'indices'; } @@ -198,38 +221,38 @@ module.exports = function (object, opts) { objKeys = Object.keys(obj); } - if (sort) { - objKeys.sort(sort); + if (options.sort) { + objKeys.sort(options.sort); } for (var i = 0; i < objKeys.length; ++i) { var key = objKeys[i]; - if (skipNulls && obj[key] === null) { + if (options.skipNulls && obj[key] === null) { continue; } pushToArray(keys, stringify( obj[key], key, generateArrayPrefix, - strictNullHandling, - skipNulls, - encode ? encoder : null, - filter, - sort, - allowDots, - serializeDate, - formatter, - encodeValuesOnly, - charset + options.strictNullHandling, + options.skipNulls, + options.encode ? options.encoder : null, + options.filter, + options.sort, + options.allowDots, + options.serializeDate, + options.formatter, + options.encodeValuesOnly, + options.charset )); } - var joined = keys.join(delimiter); + var joined = keys.join(options.delimiter); var prefix = options.addQueryPrefix === true ? '?' : ''; if (options.charsetSentinel) { - if (charset === 'iso-8859-1') { + if (options.charset === 'iso-8859-1') { // encodeURIComponent('✓'), the "numeric entity" representation of a checkmark prefix += 'utf8=%26%2310003%3B&'; } else { From 04ec4770113f83a5d76a58a6413e7331b0d75a96 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Thu, 31 Jan 2019 14:24:14 -0800 Subject: [PATCH 049/278] [Refactor] use cached `Array.isArray` --- lib/stringify.js | 6 +++--- test/stringify.js | 16 ++++++++++++++++ 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/lib/stringify.js b/lib/stringify.js index 586fae18..b6586787 100644 --- a/lib/stringify.js +++ b/lib/stringify.js @@ -87,7 +87,7 @@ var stringify = function stringify( // eslint-disable-line func-name-matching } var objKeys; - if (Array.isArray(filter)) { + if (isArray(filter)) { objKeys = filter; } else { var keys = Object.keys(obj); @@ -101,7 +101,7 @@ var stringify = function stringify( // eslint-disable-line func-name-matching continue; } - if (Array.isArray(obj)) { + if (isArray(obj)) { pushToArray(values, stringify( obj[key], generateArrayPrefix(prefix, key), @@ -195,7 +195,7 @@ module.exports = function (object, opts) { if (typeof options.filter === 'function') { filter = options.filter; obj = filter('', obj); - } else if (Array.isArray(options.filter)) { + } else if (isArray(options.filter)) { filter = options.filter; objKeys = filter; } diff --git a/test/stringify.js b/test/stringify.js index 7901ea00..c421eac4 100644 --- a/test/stringify.js +++ b/test/stringify.js @@ -19,6 +19,15 @@ test('stringify()', function (t) { 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('adds query prefix', function (st) { st.equal(qs.stringify({ a: 'b' }, { addQueryPrefix: true }), '?a=b'); st.end(); @@ -29,6 +38,13 @@ test('stringify()', function (t) { 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'); From 340ac6ef5382e7f65d5b70ceffaf3e265a31fa02 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Fri, 1 Feb 2019 13:48:43 -0800 Subject: [PATCH 050/278] [Fix]` `utils.merge`: avoid a crash with a null target and a truthy non-array source --- lib/utils.js | 2 +- test/utils.js | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/utils.js b/lib/utils.js index 9a2c8313..b4e12997 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -49,7 +49,7 @@ var merge = function merge(target, source, options) { if (typeof source !== 'object') { if (Array.isArray(target)) { target.push(source); - } else if (typeof target === 'object') { + } else if (target && typeof target === 'object') { if ((options && (options.plainObjects || options.allowPrototypes)) || !has.call(Object.prototype, source)) { target[source] = true; } diff --git a/test/utils.js b/test/utils.js index e60d1029..be4f3e45 100644 --- a/test/utils.js +++ b/test/utils.js @@ -4,6 +4,8 @@ var test = require('tape'); 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({ a: 'b' }, { a: 'c' }), { a: ['b', 'c'] }, 'merges two objects with the same key'); var oneMerged = utils.merge({ foo: 'bar' }, { foo: { first: '123' } }); From 0762294947c63b7037772f1d1460ff3f85856fe4 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Sun, 3 Feb 2019 00:05:08 -0800 Subject: [PATCH 051/278] [Refactor] `utils`: reduce observable [[Get]]s --- lib/utils.js | 5 +++-- test/utils.js | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/utils.js b/lib/utils.js index b4e12997..5c18bf3f 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -72,8 +72,9 @@ var merge = function merge(target, source, options) { if (Array.isArray(target) && Array.isArray(source)) { source.forEach(function (item, i) { if (has.call(target, i)) { - if (target[i] && typeof target[i] === 'object' && item && typeof item === 'object') { - target[i] = merge(target[i], item, options); + var targetItem = target[i]; + if (targetItem && typeof targetItem === 'object' && item && typeof item === 'object') { + target[i] = merge(targetItem, item, options); } else { target.push(item); } diff --git a/test/utils.js b/test/utils.js index be4f3e45..6738654a 100644 --- a/test/utils.js +++ b/test/utils.js @@ -39,10 +39,10 @@ test('merge()', function (t) { }); utils.merge(observed, [null]); st.equal(setCount, 0); - st.equal(getCount, 2); + st.equal(getCount, 1); observed[0] = observed[0]; // eslint-disable-line no-self-assign st.equal(setCount, 1); - st.equal(getCount, 3); + st.equal(getCount, 2); st.end(); } ); From 4d2cc40e0e211c0d52ed77ed9b07ad86f48bdd84 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Sun, 3 Feb 2019 00:07:21 -0800 Subject: [PATCH 052/278] [Refactor]: `stringify`/`utils`: cache `Array.isArray` --- lib/stringify.js | 2 +- lib/utils.js | 9 +++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/lib/stringify.js b/lib/stringify.js index b6586787..3e45c523 100644 --- a/lib/stringify.js +++ b/lib/stringify.js @@ -163,7 +163,7 @@ var normalizeStringifyOptions = function normalizeStringifyOptions(opts) { var formatter = formats.formatters[format]; var filter = defaults.filter; - if (typeof opts.filter === 'function' || Array.isArray(opts.filter)) { + if (typeof opts.filter === 'function' || isArray(opts.filter)) { filter = opts.filter; } diff --git a/lib/utils.js b/lib/utils.js index 5c18bf3f..b0fbd074 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -1,6 +1,7 @@ 'use strict'; var has = Object.prototype.hasOwnProperty; +var isArray = Array.isArray; var hexTable = (function () { var array = []; @@ -16,7 +17,7 @@ var compactQueue = function compactQueue(queue) { var item = queue.pop(); var obj = item.obj[item.prop]; - if (Array.isArray(obj)) { + if (isArray(obj)) { var compacted = []; for (var j = 0; j < obj.length; ++j) { @@ -47,7 +48,7 @@ var merge = function merge(target, source, options) { } if (typeof source !== 'object') { - if (Array.isArray(target)) { + if (isArray(target)) { target.push(source); } else if (target && typeof target === 'object') { if ((options && (options.plainObjects || options.allowPrototypes)) || !has.call(Object.prototype, source)) { @@ -65,11 +66,11 @@ var merge = function merge(target, source, options) { } var mergeTarget = target; - if (Array.isArray(target) && !Array.isArray(source)) { + if (isArray(target) && !isArray(source)) { mergeTarget = arrayToObject(target, options); } - if (Array.isArray(target) && Array.isArray(source)) { + if (isArray(target) && isArray(source)) { source.forEach(function (item, i) { if (has.call(target, i)) { var targetItem = target[i]; From a6070be8d95f808a0542195760ad44a96eefd7d0 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Sun, 3 Feb 2019 00:11:31 -0800 Subject: [PATCH 053/278] [Fix] `utils.merge`: avoid a crash with a null target and an array source --- lib/utils.js | 2 +- test/utils.js | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/utils.js b/lib/utils.js index b0fbd074..4b56c182 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -61,7 +61,7 @@ var merge = function merge(target, source, options) { return target; } - if (typeof target !== 'object') { + if (!target || typeof target !== 'object') { return [target].concat(source); } diff --git a/test/utils.js b/test/utils.js index 6738654a..b3c5dedb 100644 --- a/test/utils.js +++ b/test/utils.js @@ -6,6 +6,8 @@ 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' } }); From 75a186c7bb34965de2abd90a58b0b590ed213eb5 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Wed, 13 Feb 2019 23:14:47 -0800 Subject: [PATCH 054/278] [Tests] always use `String(x)` over `x.toString()` --- test/parse.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/parse.js b/test/parse.js index f2e4d7b3..06a63536 100644 --- a/test/parse.js +++ b/test/parse.js @@ -555,7 +555,7 @@ test('parse()', function (t) { result.push(parseInt(parts[1], 16)); parts = reg.exec(str); } - return iconv.decode(SaferBuffer.from(result), 'shift_jis').toString(); + return String(iconv.decode(SaferBuffer.from(result), 'shift_jis')); } }), { 県: '大阪府' }); st.end(); From b88830467947027856fd90c69e14bbfe3209a09c Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Wed, 13 Feb 2019 23:18:02 -0800 Subject: [PATCH 055/278] [Robustness] `stringify`: cache `Object.prototype.hasOwnProperty` --- lib/stringify.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/stringify.js b/lib/stringify.js index 3e45c523..bf509c5c 100644 --- a/lib/stringify.js +++ b/lib/stringify.js @@ -2,6 +2,7 @@ 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 @@ -155,7 +156,7 @@ var normalizeStringifyOptions = function normalizeStringifyOptions(opts) { var format = formats['default']; if (typeof opts.format !== 'undefined') { - if (!Object.prototype.hasOwnProperty.call(formats.formatters, opts.format)) { + if (!has.call(formats.formatters, opts.format)) { throw new TypeError('Unknown format option provided.'); } format = opts.format; From 3189e58e4baa03dc5036adaf344b6cddb211d3fe Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Wed, 13 Feb 2019 23:26:51 -0800 Subject: [PATCH 056/278] [Refactor] `utils`: `isBuffer`: small tweak; add tests --- lib/utils.js | 2 +- package.json | 2 ++ test/utils.js | 19 +++++++++++++++++++ 3 files changed, 22 insertions(+), 1 deletion(-) diff --git a/lib/utils.js b/lib/utils.js index 4b56c182..1b219cdd 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -206,7 +206,7 @@ var isRegExp = function isRegExp(obj) { }; var isBuffer = function isBuffer(obj) { - if (obj === null || typeof obj === 'undefined') { + if (!obj || typeof obj !== 'object') { return false; } diff --git a/package.json b/package.json index ced1effc..bd7877dc 100644 --- a/package.json +++ b/package.json @@ -30,8 +30,10 @@ "editorconfig-tools": "^0.1.1", "eslint": "^5.9.0", "evalmd": "^0.0.17", + "for-each": "^0.3.3", "iconv-lite": "^0.4.24", "mkdirp": "^0.5.1", + "object-inspect": "^1.6.0", "qs-iconv": "^1.0.4", "safe-publish-latest": "^1.1.2", "safer-buffer": "^2.1.2", diff --git a/test/utils.js b/test/utils.js index b3c5dedb..259c5ad9 100644 --- a/test/utils.js +++ b/test/utils.js @@ -1,6 +1,9 @@ 'use strict'; var test = require('tape'); +var inspect = require('object-inspect'); +var SaferBuffer = require('safer-buffer').Buffer; +var forEach = require('for-each'); var utils = require('../lib/utils'); test('merge()', function (t) { @@ -115,3 +118,19 @@ test('combine()', function (t) { t.end(); }); + +test('isBuffer()', function (t) { + forEach([null, undefined, true, false, '', 'abc', 42, 0, NaN, {}, [], function () {}, /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 = Buffer.from('abc'); + t.equal(utils.isBuffer(buffer), true, 'real Buffer instance is a buffer'); + t.end(); +}); From 8bb6271cb50f7eee1bba259e6ff1e100759534db Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Thu, 14 Feb 2019 21:07:43 -0800 Subject: [PATCH 057/278] [Tests] fix Buffer tests to work in node < 4.5 and node < 5.10 --- test/utils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/utils.js b/test/utils.js index 259c5ad9..da31ce53 100644 --- a/test/utils.js +++ b/test/utils.js @@ -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('abc'); + var buffer = Buffer.from ? Buffer.from('abc') : new Buffer('abc'); t.equal(utils.isBuffer(buffer), true, 'real Buffer instance is a buffer'); t.end(); }); From 0a7c1310232f33f350bf7dbc402c13c0d85ddd58 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Sun, 9 Jan 2022 22:40:45 -0800 Subject: [PATCH 058/278] [Dev Deps] backport from main --- .editorconfig | 13 +++++++++++++ .eslintignore | 1 - .eslintrc | 21 +++++++++++++++++++-- .gitignore | 3 +++ .npmignore | 15 +++++++++++---- .nycrc | 13 +++++++++++++ lib/stringify.js | 10 +++++----- package.json | 32 +++++++++++++++++--------------- test/.eslintrc | 17 ----------------- 9 files changed, 81 insertions(+), 44 deletions(-) delete mode 100644 .eslintignore create mode 100644 .nycrc delete mode 100644 test/.eslintrc diff --git a/.editorconfig b/.editorconfig index a4893ddf..2f084445 100644 --- a/.editorconfig +++ b/.editorconfig @@ -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,12 @@ 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 diff --git a/.eslintignore b/.eslintignore deleted file mode 100644 index 1521c8b7..00000000 --- a/.eslintignore +++ /dev/null @@ -1 +0,0 @@ -dist diff --git a/.eslintrc b/.eslintrc index e3bde898..db168717 100644 --- a/.eslintrc +++ b/.eslintrc @@ -3,10 +3,14 @@ "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 }], @@ -15,7 +19,20 @@ "multiline-comment-style": 0, "no-continue": 1, "no-magic-numbers": 0, + "no-param-reassign": 1, "no-restricted-syntax": [2, "BreakStatement", "DebuggerStatement", "ForInStatement", "LabeledStatement", "WithStatement"], "operator-linebreak": [2, "before"], - } + }, + + "overrides": [ + { + "files": "test/**", + "rules": { + "max-lines-per-function": 0, + "max-statements": 0, + "no-extend-native": 0, + "function-paren-newline": 0, + }, + }, + ], } diff --git a/.gitignore b/.gitignore index 645ff13c..c036d926 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,6 @@ dist/* yarn.lock package-lock.json npm-shrinkwrap.json + +.nyc_output/ +coverage/ diff --git a/.npmignore b/.npmignore index ac980d91..ee474f22 100644 --- a/.npmignore +++ b/.npmignore @@ -1,4 +1,11 @@ -bower.json -component.json -.npmignore -.travis.yml +# gitignore +npm-debug.log +node_modules +.DS_Store + +# Only apps should have lockfiles +yarn.lock +package-lock.json +npm-shrinkwrap.json + +.github/workflows diff --git a/.nycrc b/.nycrc new file mode 100644 index 00000000..1d57cabe --- /dev/null +++ b/.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/lib/stringify.js b/lib/stringify.js index bf509c5c..40559b5d 100644 --- a/lib/stringify.js +++ b/lib/stringify.js @@ -5,13 +5,13 @@ 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 + '[]'; }, - 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; } }; @@ -36,14 +36,14 @@ var defaults = { formatter: formats.formatters[formats['default']], // 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 stringify = function stringify( object, prefix, generateArrayPrefix, diff --git a/package.json b/package.json index bd7877dc..e652b08b 100644 --- a/package.json +++ b/package.json @@ -22,32 +22,34 @@ "engines": { "node": ">=0.6" }, - "dependencies": {}, "devDependencies": { - "@ljharb/eslint-config": "^13.0.0", - "browserify": "^16.2.3", - "covert": "^1.1.0", - "editorconfig-tools": "^0.1.1", - "eslint": "^5.9.0", + "@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.17", "for-each": "^0.3.3", "iconv-lite": "^0.4.24", + "in-publish": "^2.0.1", "mkdirp": "^0.5.1", - "object-inspect": "^1.6.0", + "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.9.1" + "tape": "^4.14.0" }, "scripts": { - "prepublish": "safe-publish-latest && npm run dist", + "prepublishOnly": "safe-publish-latest && npm run dist", + "prepublish": "not-in-publish || npm run prepublishOnly", "pretest": "npm run --silent readme && npm run --silent lint", - "test": "npm run --silent coverage", - "tests-only": "node test", + "test": "npm run --silent tests-only", + "tests-only": "nyc tape 'test/**/*.js'", + "posttest": "aud --production", "readme": "evalmd README.md", - "postlint": "editorconfig-tools check * lib/* test/*", - "lint": "eslint lib/*.js test/*.js", - "coverage": "covert test", + "postlint": "eclint check $(git ls-files | xargs find 2> /dev/null | grep -vE 'node_modules|\\.git')", + "lint": "eslint --ext=js,mjs .", "dist": "mkdirp dist && browserify --standalone Qs lib/index.js > dist/qs.js" }, "license": "BSD-3-Clause" diff --git a/test/.eslintrc b/test/.eslintrc deleted file mode 100644 index 9ebbb921..00000000 --- a/test/.eslintrc +++ /dev/null @@ -1,17 +0,0 @@ -{ - "rules": { - "array-bracket-newline": 0, - "array-element-newline": 0, - "consistent-return": 2, - "function-paren-newline": 0, - "max-lines": 0, - "max-lines-per-function": 0, - "max-nested-callbacks": [2, 3], - "max-statements": 0, - "no-buffer-constructor": 0, - "no-extend-native": 0, - "no-magic-numbers": 0, - "object-curly-newline": 0, - "sort-keys": 0 - } -} From 0fab4ebcfec888504a12cf511ad24f2e805c4f81 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Fri, 29 Mar 2019 14:50:56 -0400 Subject: [PATCH 059/278] [Fix] fix for an impossible situation: when the formatter is called with a non-string value Note that all these tests passed already. Since the only time a formatter is called is in a context where it is concatenated with another string using `+`, this is a redundant step. However, for pedantic correctness and documentation, the contract for formatters is to always return a string. --- lib/formats.js | 2 +- test/stringify.js | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/lib/formats.js b/lib/formats.js index df459975..702da12e 100644 --- a/lib/formats.js +++ b/lib/formats.js @@ -10,7 +10,7 @@ module.exports = { return replace.call(value, percentTwenties, '+'); }, RFC3986: function (value) { - return value; + return String(value); } }, RFC1738: 'RFC1738', diff --git a/test/stringify.js b/test/stringify.js index c421eac4..f1e2d5f4 100644 --- a/test/stringify.js +++ b/test/stringify.js @@ -506,6 +506,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(); }); @@ -546,17 +552,20 @@ test('stringify()', function (t) { t.test('RFC 1738 spaces 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.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(); }); From 1e7aa19a1a1e1d125a219a51148b146bf6df885b Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Thu, 6 Sep 2018 17:28:03 -0700 Subject: [PATCH 060/278] [Refactor] `formats`: tiny bit of cleanup. Additionally, `stringify`'s defaults get `format`, even though it isn't necessary due to the line that assigns it to the internal options. This way, however, things are a bit more internally consistent. --- lib/formats.js | 28 ++++++++++++++++++---------- lib/stringify.js | 4 +++- 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/lib/formats.js b/lib/formats.js index 702da12e..a4ecca78 100644 --- a/lib/formats.js +++ b/lib/formats.js @@ -3,16 +3,24 @@ var replace = String.prototype.replace; var percentTwenties = /%20/g; -module.exports = { - 'default': 'RFC3986', - formatters: { - RFC1738: function (value) { - return replace.call(value, percentTwenties, '+'); - }, - RFC3986: function (value) { - return String(value); - } - }, +var util = require('./utils'); + +var Format = { RFC1738: 'RFC1738', RFC3986: 'RFC3986' }; + +module.exports = util.assign( + { + 'default': Format.RFC3986, + formatters: { + RFC1738: function (value) { + return replace.call(value, percentTwenties, '+'); + }, + RFC3986: function (value) { + return String(value); + } + } + }, + Format +); diff --git a/lib/stringify.js b/lib/stringify.js index 40559b5d..b74c014c 100644 --- a/lib/stringify.js +++ b/lib/stringify.js @@ -24,6 +24,7 @@ var pushToArray = function (arr, valueOrArray) { var toISO = Date.prototype.toISOString; +var defaultFormat = formats['default']; var defaults = { addQueryPrefix: false, allowDots: false, @@ -33,7 +34,8 @@ 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) { From 107c3029408c3e6fa156e52675643f03d13de379 Mon Sep 17 00:00:00 2001 From: Dmitry Kirilyuk Date: Wed, 16 Jan 2019 16:21:27 +0300 Subject: [PATCH 061/278] [Docs] Clarify the need for "arrayLimit" option --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d8119666..cb64e0f6 100644 --- a/README.md +++ b/README.md @@ -182,7 +182,7 @@ assert.deepEqual(withIndexedEmptyString, { a: ['b', '', 'c'] }); ``` **qs** will also limit specifying indices in an array to a maximum index of `20`. Any array members with an index of greater than `20` will -instead be converted to an object with the index as the key: +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'); From ef27de40ca4ce418a132f23846f551ad21c52750 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Thu, 31 Jan 2019 14:24:14 -0800 Subject: [PATCH 062/278] [Refactor] use cached `Array.isArray` --- lib/stringify.js | 6 +++--- test/stringify.js | 16 ++++++++++++++++ 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/lib/stringify.js b/lib/stringify.js index d34dbe15..fb53ca07 100644 --- a/lib/stringify.js +++ b/lib/stringify.js @@ -79,7 +79,7 @@ var stringify = function stringify( // eslint-disable-line func-name-matching } var objKeys; - if (Array.isArray(filter)) { + if (isArray(filter)) { objKeys = filter; } else { var keys = Object.keys(obj); @@ -93,7 +93,7 @@ var stringify = function stringify( // eslint-disable-line func-name-matching continue; } - if (Array.isArray(obj)) { + if (isArray(obj)) { pushToArray(values, stringify( obj[key], generateArrayPrefix(prefix, key), @@ -158,7 +158,7 @@ module.exports = function (object, opts) { if (typeof options.filter === 'function') { filter = options.filter; obj = filter('', obj); - } else if (Array.isArray(options.filter)) { + } else if (isArray(options.filter)) { filter = options.filter; objKeys = filter; } diff --git a/test/stringify.js b/test/stringify.js index 1c2a2237..5d18d230 100644 --- a/test/stringify.js +++ b/test/stringify.js @@ -19,6 +19,15 @@ test('stringify()', function (t) { 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('adds query prefix', function (st) { st.equal(qs.stringify({ a: 'b' }, { addQueryPrefix: true }), '?a=b'); st.end(); @@ -29,6 +38,13 @@ test('stringify()', function (t) { 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'); From 49ad67f263df6f2262681f136deec2c4553915c5 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Fri, 1 Feb 2019 13:48:43 -0800 Subject: [PATCH 063/278] [Fix]` `utils.merge`: avoid a crash with a null target and a truthy non-array source --- lib/utils.js | 2 +- test/utils.js | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/utils.js b/lib/utils.js index 5958edc8..cfed4ecd 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -53,7 +53,7 @@ var merge = function merge(target, source, options) { if (typeof source !== 'object') { if (Array.isArray(target)) { target.push(source); - } else if (typeof target === 'object') { + } else if (target && typeof target === 'object') { if ((options && (options.plainObjects || options.allowPrototypes)) || !has.call(Object.prototype, source)) { target[source] = true; } diff --git a/test/utils.js b/test/utils.js index f255de3f..fcd85a01 100644 --- a/test/utils.js +++ b/test/utils.js @@ -4,6 +4,8 @@ var test = require('tape'); 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({ a: 'b' }, { a: 'c' }), { a: ['b', 'c'] }, 'merges two objects with the same key'); var oneMerged = utils.merge({ foo: 'bar' }, { foo: { first: '123' } }); From 98c93d62a3bac9d73d6e9247c240bcd089affab9 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Sun, 3 Feb 2019 00:05:08 -0800 Subject: [PATCH 064/278] [Refactor] `utils`: reduce observable [[Get]]s --- lib/utils.js | 5 +++-- test/utils.js | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/utils.js b/lib/utils.js index cfed4ecd..b9b18cd3 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -76,8 +76,9 @@ var merge = function merge(target, source, options) { if (Array.isArray(target) && Array.isArray(source)) { source.forEach(function (item, i) { if (has.call(target, i)) { - if (target[i] && typeof target[i] === 'object' && item && typeof item === 'object') { - target[i] = merge(target[i], item, options); + var targetItem = target[i]; + if (targetItem && typeof targetItem === 'object' && item && typeof item === 'object') { + target[i] = merge(targetItem, item, options); } else { target.push(item); } diff --git a/test/utils.js b/test/utils.js index fcd85a01..c3faa704 100644 --- a/test/utils.js +++ b/test/utils.js @@ -39,10 +39,10 @@ test('merge()', function (t) { }); utils.merge(observed, [null]); st.equal(setCount, 0); - st.equal(getCount, 2); + st.equal(getCount, 1); observed[0] = observed[0]; // eslint-disable-line no-self-assign st.equal(setCount, 1); - st.equal(getCount, 3); + st.equal(getCount, 2); st.end(); } ); From 31bcb32e072724eb78579ab6f9176c633af39bd1 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Sun, 3 Feb 2019 00:11:31 -0800 Subject: [PATCH 065/278] [Fix] `utils.merge`: avoid a crash with a null target and an array source --- lib/utils.js | 2 +- test/utils.js | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/utils.js b/lib/utils.js index b9b18cd3..f42136ee 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -64,7 +64,7 @@ var merge = function merge(target, source, options) { return target; } - if (typeof target !== 'object') { + if (!target || typeof target !== 'object') { return [target].concat(source); } diff --git a/test/utils.js b/test/utils.js index c3faa704..2bfe03a6 100644 --- a/test/utils.js +++ b/test/utils.js @@ -6,6 +6,8 @@ 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' } }); From fd950b0f5e04cf6d7ec78b1e3879a49c45c04bc0 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Wed, 13 Feb 2019 23:14:47 -0800 Subject: [PATCH 066/278] [Tests] always use `String(x)` over `x.toString()` --- test/parse.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/parse.js b/test/parse.js index 01a8ff47..ce014d42 100644 --- a/test/parse.js +++ b/test/parse.js @@ -555,7 +555,7 @@ test('parse()', function (t) { result.push(parseInt(parts[1], 16)); parts = reg.exec(str); } - return iconv.decode(SaferBuffer.from(result), 'shift_jis').toString(); + return String(iconv.decode(SaferBuffer.from(result), 'shift_jis')); } }), { 県: '大阪府' }); st.end(); From f814a7f8f2af059f8158f7e4b2bf8b46aeb62cd3 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Sun, 9 Jan 2022 22:40:45 -0800 Subject: [PATCH 067/278] [Dev Deps] backport from main --- .editorconfig | 15 ++++++++++++++- .eslintignore | 1 - .eslintrc | 24 +++++++++++++++++++++--- .gitignore | 3 +++ .npmignore | 18 ++++++++++++++---- .nycrc | 13 +++++++++++++ lib/stringify.js | 10 +++++----- lib/utils.js | 1 + package.json | 32 +++++++++++++++++--------------- test/.eslintrc | 15 --------------- test/parse.js | 2 +- 11 files changed, 89 insertions(+), 45 deletions(-) delete mode 100644 .eslintignore create mode 100644 .nycrc delete mode 100644 test/.eslintrc diff --git a/.editorconfig b/.editorconfig index b2654e7a..2f084445 100644 --- a/.editorconfig +++ b/.editorconfig @@ -7,11 +7,15 @@ end_of_line = lf charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true -max_line_length = 140 +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,12 @@ 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 diff --git a/.eslintignore b/.eslintignore deleted file mode 100644 index 1521c8b7..00000000 --- a/.eslintignore +++ /dev/null @@ -1 +0,0 @@ -dist diff --git a/.eslintrc b/.eslintrc index b7a87b93..3f848996 100644 --- a/.eslintrc +++ b/.eslintrc @@ -3,17 +3,35 @@ "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": 0, "max-params": [2, 12], "max-statements": [2, 45], + "multiline-comment-style": 0, "no-continue": 1, "no-magic-numbers": 0, + "no-param-reassign": 1, "no-restricted-syntax": [2, "BreakStatement", "DebuggerStatement", "ForInStatement", "LabeledStatement", "WithStatement"], - "operator-linebreak": [2, "before"], - } + }, + + "overrides": [ + { + "files": "test/**", + "rules": { + "max-lines-per-function": 0, + "max-statements": 0, + "no-extend-native": 0, + "function-paren-newline": 0, + }, + }, + ], } diff --git a/.gitignore b/.gitignore index 645ff13c..c036d926 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,6 @@ dist/* yarn.lock package-lock.json npm-shrinkwrap.json + +.nyc_output/ +coverage/ diff --git a/.npmignore b/.npmignore index ac980d91..5fafe6be 100644 --- a/.npmignore +++ b/.npmignore @@ -1,4 +1,14 @@ -bower.json -component.json -.npmignore -.travis.yml +# gitignore +npm-debug.log +node_modules +.DS_Store + +# Only apps should have lockfiles +yarn.lock +package-lock.json +npm-shrinkwrap.json + +.nyc_output/ +coverage/ + +.github/workflows diff --git a/.nycrc b/.nycrc new file mode 100644 index 00000000..1d57cabe --- /dev/null +++ b/.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/lib/stringify.js b/lib/stringify.js index fb53ca07..0b21ca75 100644 --- a/lib/stringify.js +++ b/lib/stringify.js @@ -4,13 +4,13 @@ var utils = require('./utils'); var formats = require('./formats'); var arrayPrefixGenerators = { - brackets: function brackets(prefix) { // eslint-disable-line func-name-matching + brackets: function brackets(prefix) { return prefix + '[]'; }, - 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; } }; @@ -28,14 +28,14 @@ var defaults = { encode: true, encoder: utils.encode, encodeValuesOnly: 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 stringify = function stringify( object, prefix, generateArrayPrefix, diff --git a/lib/utils.js b/lib/utils.js index f42136ee..6592e206 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -159,6 +159,7 @@ var encode = function encode(str) { 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)] diff --git a/package.json b/package.json index ed6ccf06..a3e8bda4 100644 --- a/package.json +++ b/package.json @@ -22,30 +22,32 @@ "engines": { "node": ">=0.6" }, - "dependencies": {}, "devDependencies": { - "@ljharb/eslint-config": "^12.2.1", - "browserify": "^16.2.0", - "covert": "^1.1.0", - "editorconfig-tools": "^0.1.1", - "eslint": "^4.19.1", + "@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.17", - "iconv-lite": "^0.4.21", + "iconv-lite": "^0.4.24", + "in-publish": "^2.0.1", "mkdirp": "^0.5.1", + "nyc": "^10.3.2", "qs-iconv": "^1.0.4", - "safe-publish-latest": "^1.1.1", + "safe-publish-latest": "^2.0.0", "safer-buffer": "^2.1.2", - "tape": "^4.9.0" + "tape": "^5.4.0" }, "scripts": { - "prepublish": "safe-publish-latest && npm run dist", + "prepublishOnly": "safe-publish-latest && npm run dist", + "prepublish": "not-in-publish || npm run prepublishOnly", "pretest": "npm run --silent readme && npm run --silent lint", - "test": "npm run --silent coverage", - "tests-only": "node test", + "test": "npm run --silent tests-only", + "tests-only": "nyc tape 'test/**/*.js'", + "posttest": "aud --production", "readme": "evalmd README.md", - "postlint": "editorconfig-tools check * lib/* test/*", - "lint": "eslint lib/*.js test/*.js", - "coverage": "covert test", + "postlint": "eclint check $(git ls-files | xargs find 2> /dev/null | grep -vE 'node_modules|\\.git')", + "lint": "eslint --ext=js,mjs .", "dist": "mkdirp dist && browserify --standalone Qs lib/index.js > dist/qs.js" }, "license": "BSD-3-Clause" diff --git a/test/.eslintrc b/test/.eslintrc deleted file mode 100644 index 20175d64..00000000 --- a/test/.eslintrc +++ /dev/null @@ -1,15 +0,0 @@ -{ - "rules": { - "array-bracket-newline": 0, - "array-element-newline": 0, - "consistent-return": 2, - "max-lines": 0, - "max-nested-callbacks": [2, 3], - "max-statements": 0, - "no-buffer-constructor": 0, - "no-extend-native": 0, - "no-magic-numbers": 0, - "object-curly-newline": 0, - "sort-keys": 0 - } -} diff --git a/test/parse.js b/test/parse.js index ce014d42..7614a287 100644 --- a/test/parse.js +++ b/test/parse.js @@ -523,7 +523,7 @@ 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' ); From 45f675936e742d92fac8d4dae5cfc385c576a977 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Fri, 29 Mar 2019 14:50:56 -0400 Subject: [PATCH 068/278] [Fix] fix for an impossible situation: when the formatter is called with a non-string value Note that all these tests passed already. Since the only time a formatter is called is in a context where it is concatenated with another string using `+`, this is a redundant step. However, for pedantic correctness and documentation, the contract for formatters is to always return a string. --- lib/formats.js | 2 +- test/stringify.js | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/lib/formats.js b/lib/formats.js index df459975..702da12e 100644 --- a/lib/formats.js +++ b/lib/formats.js @@ -10,7 +10,7 @@ module.exports = { return replace.call(value, percentTwenties, '+'); }, RFC3986: function (value) { - return value; + return String(value); } }, RFC1738: 'RFC1738', diff --git a/test/stringify.js b/test/stringify.js index 5d18d230..08f78430 100644 --- a/test/stringify.js +++ b/test/stringify.js @@ -506,6 +506,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(); }); @@ -546,17 +552,20 @@ test('stringify()', function (t) { t.test('RFC 1738 spaces 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.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(); }); From 7d4670fca6ed46a1fc6237bccffe0ea82a641411 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Sun, 9 Jan 2022 22:40:45 -0800 Subject: [PATCH 069/278] [Dev Deps] backport from main --- .editorconfig | 44 ++++++++++++++++++++ .eslintignore | 1 - .eslintrc | 27 +++++++++++-- .gitignore | 8 ++++ .npmignore | 18 +++++++-- .nycrc | 13 ++++++ bower.json | 38 ++++++++--------- component.json | 26 ++++++------ lib/parse.js | 15 +++---- lib/stringify.js | 12 +++--- lib/utils.js | 20 +++++---- package.json | 101 ++++++++++++++++++++++++---------------------- test/.eslintrc | 11 ----- test/parse.js | 4 +- test/stringify.js | 6 +-- 15 files changed, 215 insertions(+), 129 deletions(-) create mode 100644 .editorconfig delete mode 100644 .eslintignore create mode 100644 .nycrc delete mode 100644 test/.eslintrc diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..226a9322 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,44 @@ +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 = 160 +quote_type = single + +[test/*] +max_line_length = off + +[*.md] +indent_size = off +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 + +[dist/*] +max_line_length = off + +[.nycrc] +indent_style = tab diff --git a/.eslintignore b/.eslintignore deleted file mode 100644 index 1521c8b7..00000000 --- a/.eslintignore +++ /dev/null @@ -1 +0,0 @@ -dist diff --git a/.eslintrc b/.eslintrc index e2cade5e..cd5979ed 100644 --- a/.eslintrc +++ b/.eslintrc @@ -3,16 +3,35 @@ "extends": "@ljharb", + "ignorePatterns": [ + "dist/", + ], + "rules": { - "complexity": [2, 26], + "complexity": [2, 29], "consistent-return": 1, + "func-name-matching": 0, "id-length": [2, { "min": 1, "max": 25, "properties": "never" }], "indent": [2, 4], + "max-lines-per-function": 0, "max-params": [2, 12], - "max-statements": [2, 43], + "max-statements": [2, 45], + "multiline-comment-style": 0, "no-continue": 1, "no-magic-numbers": 0, + "no-param-reassign": 1, "no-restricted-syntax": [2, "BreakStatement", "DebuggerStatement", "ForInStatement", "LabeledStatement", "WithStatement"], - "operator-linebreak": [2, "after"], - } + }, + + "overrides": [ + { + "files": "test/**", + "rules": { + "max-lines-per-function": 0, + "max-statements": 0, + "no-extend-native": 0, + "function-paren-newline": 0, + }, + }, + ], } diff --git a/.gitignore b/.gitignore index 8cace31c..267da50e 100644 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,11 @@ lib-cov complexity.md tests.tap dist/* + +# Only apps should have lockfiles +yarn.lock +package-lock.json +npm-shrinkwrap.json + +.nyc_output/ +coverage/ diff --git a/.npmignore b/.npmignore index ac980d91..5fafe6be 100644 --- a/.npmignore +++ b/.npmignore @@ -1,4 +1,14 @@ -bower.json -component.json -.npmignore -.travis.yml +# gitignore +npm-debug.log +node_modules +.DS_Store + +# Only apps should have lockfiles +yarn.lock +package-lock.json +npm-shrinkwrap.json + +.nyc_output/ +coverage/ + +.github/workflows diff --git a/.nycrc b/.nycrc new file mode 100644 index 00000000..1d57cabe --- /dev/null +++ b/.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/bower.json b/bower.json index 44f05064..7a582762 100644 --- a/bower.json +++ b/bower.json @@ -1,21 +1,21 @@ { - "name": "qs", - "main": "dist/qs.js", - "homepage": "https://github.com/hapijs/qs", - "authors": [ - "Nathan LaFreniere " - ], - "description": "A querystring parser that supports nesting and arrays, with a depth limit", - "keywords": [ - "querystring", - "qs" - ], - "license": "BSD-3-Clause", - "ignore": [ - "**/.*", - "node_modules", - "bower_components", - "test", - "tests" - ] + "name": "qs", + "main": "dist/qs.js", + "homepage": "https://github.com/hapijs/qs", + "authors": [ + "Nathan LaFreniere " + ], + "description": "A querystring parser that supports nesting and arrays, with a depth limit", + "keywords": [ + "querystring", + "qs" + ], + "license": "BSD-3-Clause", + "ignore": [ + "**/.*", + "node_modules", + "bower_components", + "test", + "tests" + ] } diff --git a/component.json b/component.json index d5ad2921..f15c2133 100644 --- a/component.json +++ b/component.json @@ -1,15 +1,15 @@ { - "name": "qs", - "repository": "hapijs/qs", - "description": "query-string parser / stringifier with nesting support", - "version": "6.4.0", - "keywords": ["querystring", "query", "parser"], - "main": "lib/index.js", - "scripts": [ - "lib/index.js", - "lib/parse.js", - "lib/stringify.js", - "lib/utils.js" - ], - "license": "BSD-3-Clause" + "name": "qs", + "repository": "hapijs/qs", + "description": "query-string parser / stringifier with nesting support", + "version": "6.4.0", + "keywords": ["querystring", "query", "parser"], + "main": "lib/index.js", + "scripts": [ + "lib/index.js", + "lib/parse.js", + "lib/stringify.js", + "lib/utils.js" + ], + "license": "BSD-3-Clause" } diff --git a/lib/parse.js b/lib/parse.js index 6eabc3c0..81e415cc 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -60,11 +60,11 @@ var parseObject = function parseObjectRecursive(chain, val, options) { if (!options.parseArrays && cleanRoot === '') { obj = { 0: val }; } else if ( - !isNaN(index) && - root !== cleanRoot && - String(index) === cleanRoot && - index >= 0 && - (options.parseArrays && index <= options.arrayLimit) + !isNaN(index) + && root !== cleanRoot + && String(index) === cleanRoot + && index >= 0 + && (options.parseArrays && index <= options.arrayLimit) ) { obj = []; obj[index] = parseObject(chain, val, options); @@ -98,10 +98,7 @@ var parseKeys = function parseQueryStringKeys(givenKey, val, options) { var keys = []; if (parent) { - /* - * If we aren't using plain objects, optionally prefix keys - * that would overwrite object prototype properties - */ + // If we aren't using plain objects, optionally prefix keys that would overwrite object prototype properties if (!options.plainObjects && has.call(Object.prototype, parent)) { if (!options.allowPrototypes) { return; diff --git a/lib/stringify.js b/lib/stringify.js index 3c191f04..bdbf115e 100644 --- a/lib/stringify.js +++ b/lib/stringify.js @@ -4,13 +4,13 @@ var utils = require('./utils'); var formats = require('./formats'); var arrayPrefixGenerators = { - brackets: function brackets(prefix) { // eslint-disable-line func-name-matching + brackets: function brackets(prefix) { return prefix + '[]'; }, - 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; } }; @@ -28,14 +28,14 @@ var defaults = { encode: true, encoder: utils.encode, encodeValuesOnly: 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 stringify = function stringify( object, prefix, generateArrayPrefix, @@ -147,7 +147,7 @@ module.exports = function (object, opts) { var serializeDate = typeof options.serializeDate === 'function' ? options.serializeDate : defaults.serializeDate; var encodeValuesOnly = typeof options.encodeValuesOnly === 'boolean' ? options.encodeValuesOnly : defaults.encodeValuesOnly; if (typeof options.format === 'undefined') { - options.format = formats.default; + options.format = formats['default']; } else if (!Object.prototype.hasOwnProperty.call(formats.formatters, options.format)) { throw new TypeError('Unknown format option provided.'); } diff --git a/lib/utils.js b/lib/utils.js index 9095b9d1..b198ae98 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -99,13 +99,13 @@ exports.encode = function (str) { var c = string.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 + 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 ) { out += string.charAt(i); continue; @@ -128,7 +128,11 @@ exports.encode = function (str) { i += 1; c = 0x10000 + (((c & 0x3FF) << 10) | (string.charCodeAt(i) & 0x3FF)); - out += hexTable[0xF0 | (c >> 18)] + hexTable[0x80 | ((c >> 12) & 0x3F)] + hexTable[0x80 | ((c >> 6) & 0x3F)] + hexTable[0x80 | (c & 0x3F)]; // eslint-disable-line max-len + /* eslint operator-linebreak: [2, "before"] */ + out += hexTable[0xF0 | (c >> 18)] + + hexTable[0x80 | ((c >> 12) & 0x3F)] + + hexTable[0x80 | ((c >> 6) & 0x3F)] + + hexTable[0x80 | (c & 0x3F)]; } return out; diff --git a/package.json b/package.json index 8757e401..1cb81e5d 100644 --- a/package.json +++ b/package.json @@ -1,51 +1,54 @@ { - "name": "qs", - "description": "A querystring parser that supports nesting and arrays, with a depth limit", - "homepage": "https://github.com/ljharb/qs", - "version": "6.4.0", - "repository": { - "type": "git", - "url": "https://github.com/ljharb/qs.git" - }, - "main": "lib/index.js", - "contributors": [ - { - "name": "Jordan Harband", - "email": "ljharb@gmail.com", - "url": "http://ljharb.codes" - } - ], - "keywords": [ - "querystring", - "qs" - ], - "engines": { - "node": ">=0.6" - }, - "dependencies": {}, - "devDependencies": { - "@ljharb/eslint-config": "^11.0.0", - "browserify": "^14.1.0", - "covert": "^1.1.0", - "eslint": "^3.17.0", - "evalmd": "^0.0.17", - "iconv-lite": "^0.4.15", - "mkdirp": "^0.5.1", - "parallelshell": "^2.0.0", - "qs-iconv": "^1.0.4", - "safe-publish-latest": "^1.1.1", - "safer-buffer": "^2.0.2", - "tape": "^4.6.3" - }, - "scripts": { - "prepublish": "safe-publish-latest && npm run dist", - "pretest": "npm run --silent readme && npm run --silent lint", - "test": "npm run --silent coverage", - "tests-only": "node test", - "readme": "evalmd README.md", - "lint": "eslint lib/*.js test/*.js", - "coverage": "covert test", - "dist": "mkdirp dist && browserify --standalone Qs lib/index.js > dist/qs.js" - }, - "license": "BSD-3-Clause" + "name": "qs", + "description": "A querystring parser that supports nesting and arrays, with a depth limit", + "homepage": "https://github.com/ljharb/qs", + "version": "6.4.0", + "repository": { + "type": "git", + "url": "https://github.com/ljharb/qs.git" + }, + "main": "lib/index.js", + "contributors": [ + { + "name": "Jordan Harband", + "email": "ljharb@gmail.com", + "url": "http://ljharb.codes" + } + ], + "keywords": [ + "querystring", + "qs" + ], + "engines": { + "node": ">=0.6" + }, + "devDependencies": { + "@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.17", + "iconv-lite": "^0.4.24", + "in-publish": "^2.0.1", + "mkdirp": "^0.5.1", + "nyc": "^10.3.2", + "qs-iconv": "^1.0.4", + "safe-publish-latest": "^2.0.0", + "safer-buffer": "^2.1.2", + "tape": "^5.4.0" + }, + "scripts": { + "prepublishOnly": "safe-publish-latest && npm run dist", + "prepublish": "not-in-publish || npm run prepublishOnly", + "pretest": "npm run --silent readme && npm run --silent lint", + "test": "npm run --silent tests-only", + "tests-only": "nyc tape 'test/**/*.js'", + "posttest": "aud --production", + "readme": "evalmd README.md", + "postlint": "eclint check $(git ls-files | xargs find 2> /dev/null | grep -vE 'node_modules|\\.git')", + "lint": "eslint --ext=js,mjs .", + "dist": "mkdirp dist && browserify --standalone Qs lib/index.js > dist/qs.js" + }, + "license": "BSD-3-Clause" } diff --git a/test/.eslintrc b/test/.eslintrc deleted file mode 100644 index c4f52d02..00000000 --- a/test/.eslintrc +++ /dev/null @@ -1,11 +0,0 @@ -{ - "rules": { - "consistent-return": 2, - "max-lines": 0, - "max-nested-callbacks": [2, 3], - "max-statements": 0, - "no-extend-native": 0, - "no-magic-numbers": 0, - "sort-keys": 0 - } -} diff --git a/test/parse.js b/test/parse.js index a90739b9..9aaf67ad 100644 --- a/test/parse.js +++ b/test/parse.js @@ -480,7 +480,7 @@ 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' ); @@ -519,7 +519,7 @@ test('parse()', function (t) { }); t.test('throws error with wrong decoder', function (st) { - st.throws(function () { + st['throws'](function () { qs.parse({}, { decoder: 'string' }); }, new TypeError('Decoder has to be a function.')); st.end(); diff --git a/test/stringify.js b/test/stringify.js index 11504fdb..7076680a 100644 --- a/test/stringify.js +++ b/test/stringify.js @@ -454,7 +454,7 @@ test('stringify()', function (t) { }); t.test('throws error with wrong encoder', function (st) { - st.throws(function () { + st['throws'](function () { qs.stringify({}, { encoder: 'string' }); }, new TypeError('Encoder has to be a function.')); st.end(); @@ -484,7 +484,7 @@ test('stringify()', function (t) { mutatedDate.toISOString = function () { throw new SyntaxError(); }; - st.throws(function () { + st['throws'](function () { mutatedDate.toISOString(); }, SyntaxError); st.equal( @@ -526,7 +526,7 @@ test('stringify()', function (t) { t.test('Edge cases and unknown formats', function (st) { ['UFO1234', false, 1234, null, {}, []].forEach( function (format) { - st.throws( + st['throws']( function () { qs.stringify({ a: 'b c' }, { format: format }); }, From 35dfb227e274367e163b3d943fc975f95448685a Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Sat, 8 Jan 2022 21:24:45 -0800 Subject: [PATCH 070/278] [actions] backport actions from main --- .github/workflows/node-aught.yml | 18 +++ .github/workflows/node-pretest.yml | 7 + .github/workflows/node-tens.yml | 18 +++ .github/workflows/rebase.yml | 15 ++ .github/workflows/require-allow-edits.yml | 12 ++ .travis.yml | 173 ---------------------- 6 files changed, 70 insertions(+), 173 deletions(-) create mode 100644 .github/workflows/node-aught.yml create mode 100644 .github/workflows/node-pretest.yml create mode 100644 .github/workflows/node-tens.yml create mode 100644 .github/workflows/rebase.yml create mode 100644 .github/workflows/require-allow-edits.yml delete mode 100644 .travis.yml diff --git a/.github/workflows/node-aught.yml b/.github/workflows/node-aught.yml new file mode 100644 index 00000000..f3cddd85 --- /dev/null +++ b/.github/workflows/node-aught.yml @@ -0,0 +1,18 @@ +name: 'Tests: node.js < 10' + +on: [pull_request, push] + +jobs: + tests: + uses: ljharb/actions/.github/workflows/node.yml@main + with: + range: '< 10' + type: minors + command: npm run tests-only + + node: + name: 'node < 10' + needs: [tests] + runs-on: ubuntu-latest + steps: + - run: 'echo tests completed' diff --git a/.github/workflows/node-pretest.yml b/.github/workflows/node-pretest.yml new file mode 100644 index 00000000..765edf79 --- /dev/null +++ b/.github/workflows/node-pretest.yml @@ -0,0 +1,7 @@ +name: 'Tests: pretest/posttest' + +on: [pull_request, push] + +jobs: + tests: + uses: ljharb/actions/.github/workflows/pretest.yml@main diff --git a/.github/workflows/node-tens.yml b/.github/workflows/node-tens.yml new file mode 100644 index 00000000..b49ceb1f --- /dev/null +++ b/.github/workflows/node-tens.yml @@ -0,0 +1,18 @@ +name: 'Tests: node.js >= 10' + +on: [pull_request, push] + +jobs: + tests: + uses: ljharb/actions/.github/workflows/node.yml@main + with: + range: '>= 10' + type: minors + command: npm run tests-only + + node: + name: 'node >= 10' + needs: [tests] + runs-on: ubuntu-latest + steps: + - run: 'echo tests completed' diff --git a/.github/workflows/rebase.yml b/.github/workflows/rebase.yml new file mode 100644 index 00000000..9596e285 --- /dev/null +++ b/.github/workflows/rebase.yml @@ -0,0 +1,15 @@ +name: Automatic Rebase + +on: [pull_request] + +jobs: + _: + name: "Automatic Rebase" + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - uses: ljharb/rebase@master + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/require-allow-edits.yml b/.github/workflows/require-allow-edits.yml new file mode 100644 index 00000000..7b842f89 --- /dev/null +++ b/.github/workflows/require-allow-edits.yml @@ -0,0 +1,12 @@ +name: Require “Allow Edits” + +on: [pull_request_target] + +jobs: + _: + name: "Require “Allow Edits”" + + runs-on: ubuntu-latest + + steps: + - uses: ljharb/require-allow-edits@main diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 0dbeaef9..00000000 --- a/.travis.yml +++ /dev/null @@ -1,173 +0,0 @@ -language: node_js -os: - - linux -node_js: - - "7.7" - - "6.10" - - "5.12" - - "4.8" - - "iojs-v3.3" - - "iojs-v2.5" - - "iojs-v1.8" - - "0.12" - - "0.10" - - "0.8" -before_install: - - 'if [ "${TRAVIS_NODE_VERSION}" = "0.6" ]; then npm install -g npm@1.3 ; elif [ "${TRAVIS_NODE_VERSION}" != "0.9" ]; then case "$(npm --version)" in 1.*) npm install -g npm@1.4.28 ;; 2.*) npm install -g npm@2 ;; esac ; fi' - - 'if [ "${TRAVIS_NODE_VERSION}" != "0.6" ] && [ "${TRAVIS_NODE_VERSION}" != "0.9" ]; then npm install -g npm; fi' -script: - - 'if [ -n "${PRETEST-}" ]; then npm run pretest ; fi' - - 'if [ -n "${POSTTEST-}" ]; then npm run posttest ; fi' - - 'if [ -n "${COVERAGE-}" ]; then npm run coverage ; fi' - - 'if [ -n "${TEST-}" ]; then npm run tests-only ; fi' -sudo: false -env: - - TEST=true -matrix: - fast_finish: true - include: - - node_js: "node" - env: PRETEST=true - - node_js: "4" - env: COVERAGE=true - - node_js: "7.6" - env: TEST=true ALLOW_FAILURE=true - - node_js: "7.5" - env: TEST=true ALLOW_FAILURE=true - - node_js: "7.4" - env: TEST=true ALLOW_FAILURE=true - - node_js: "7.3" - env: TEST=true ALLOW_FAILURE=true - - node_js: "7.2" - env: TEST=true ALLOW_FAILURE=true - - node_js: "7.1" - env: TEST=true ALLOW_FAILURE=true - - node_js: "7.0" - env: TEST=true ALLOW_FAILURE=true - - node_js: "6.9" - env: TEST=true ALLOW_FAILURE=true - - node_js: "6.8" - env: TEST=true ALLOW_FAILURE=true - - node_js: "6.7" - env: TEST=true ALLOW_FAILURE=true - - node_js: "6.6" - env: TEST=true ALLOW_FAILURE=true - - node_js: "6.5" - env: TEST=true ALLOW_FAILURE=true - - node_js: "6.4" - env: TEST=true ALLOW_FAILURE=true - - node_js: "6.3" - env: TEST=true ALLOW_FAILURE=true - - node_js: "6.2" - env: TEST=true ALLOW_FAILURE=true - - node_js: "6.1" - env: TEST=true ALLOW_FAILURE=true - - node_js: "6.0" - env: TEST=true ALLOW_FAILURE=true - - node_js: "5.11" - env: TEST=true ALLOW_FAILURE=true - - node_js: "5.10" - env: TEST=true ALLOW_FAILURE=true - - node_js: "5.9" - env: TEST=true ALLOW_FAILURE=true - - node_js: "5.8" - env: TEST=true ALLOW_FAILURE=true - - node_js: "5.7" - env: TEST=true ALLOW_FAILURE=true - - node_js: "5.6" - env: TEST=true ALLOW_FAILURE=true - - node_js: "5.5" - env: TEST=true ALLOW_FAILURE=true - - node_js: "5.4" - env: TEST=true ALLOW_FAILURE=true - - node_js: "5.3" - env: TEST=true ALLOW_FAILURE=true - - node_js: "5.2" - env: TEST=true ALLOW_FAILURE=true - - node_js: "5.1" - env: TEST=true ALLOW_FAILURE=true - - node_js: "5.0" - env: TEST=true ALLOW_FAILURE=true - - node_js: "4.7" - env: TEST=true ALLOW_FAILURE=true - - node_js: "4.6" - env: TEST=true ALLOW_FAILURE=true - - node_js: "4.5" - env: TEST=true ALLOW_FAILURE=true - - node_js: "4.4" - env: TEST=true ALLOW_FAILURE=true - - node_js: "4.3" - env: TEST=true ALLOW_FAILURE=true - - node_js: "4.2" - env: TEST=true ALLOW_FAILURE=true - - node_js: "4.1" - env: TEST=true ALLOW_FAILURE=true - - node_js: "4.0" - env: TEST=true ALLOW_FAILURE=true - - node_js: "iojs-v3.2" - env: TEST=true ALLOW_FAILURE=true - - node_js: "iojs-v3.1" - env: TEST=true ALLOW_FAILURE=true - - node_js: "iojs-v3.0" - env: TEST=true ALLOW_FAILURE=true - - node_js: "iojs-v2.4" - env: TEST=true ALLOW_FAILURE=true - - node_js: "iojs-v2.3" - env: TEST=true ALLOW_FAILURE=true - - node_js: "iojs-v2.2" - env: TEST=true ALLOW_FAILURE=true - - node_js: "iojs-v2.1" - env: TEST=true ALLOW_FAILURE=true - - node_js: "iojs-v2.0" - env: TEST=true ALLOW_FAILURE=true - - node_js: "iojs-v1.7" - env: TEST=true ALLOW_FAILURE=true - - node_js: "iojs-v1.6" - env: TEST=true ALLOW_FAILURE=true - - node_js: "iojs-v1.5" - env: TEST=true ALLOW_FAILURE=true - - node_js: "iojs-v1.4" - env: TEST=true ALLOW_FAILURE=true - - node_js: "iojs-v1.3" - env: TEST=true ALLOW_FAILURE=true - - node_js: "iojs-v1.2" - env: TEST=true ALLOW_FAILURE=true - - node_js: "iojs-v1.1" - env: TEST=true ALLOW_FAILURE=true - - node_js: "iojs-v1.0" - env: TEST=true ALLOW_FAILURE=true - - node_js: "0.11" - env: TEST=true ALLOW_FAILURE=true - - node_js: "0.9" - env: TEST=true ALLOW_FAILURE=true - - node_js: "0.6" - env: TEST=true ALLOW_FAILURE=true - - node_js: "0.4" - env: TEST=true ALLOW_FAILURE=true - ##- node_js: "7" - #env: TEST=true - #os: osx - #- node_js: "6" - #env: TEST=true - #os: osx - #- node_js: "5" - #env: TEST=true - #os: osx - #- node_js: "4" - #env: TEST=true - #os: osx - #- node_js: "iojs" - #env: TEST=true - #os: osx - #- node_js: "0.12" - #env: TEST=true - #os: osx - #- node_js: "0.10" - #env: TEST=true - #os: osx - #- node_js: "0.8" - #env: TEST=true - #os: osx - allow_failures: - - os: osx - - env: TEST=true ALLOW_FAILURE=true From 74227ef022282881f41d37d65adba5d399d2b33a Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Wed, 26 Jun 2019 00:22:20 -0700 Subject: [PATCH 071/278] =?UTF-8?q?Clean=20up=20license=20text=20so=20it?= =?UTF-8?q?=E2=80=99s=20properly=20detected=20as=20BSD-3-Clause?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- LICENSE | 28 ---------------------------- LICENSE.md | 29 +++++++++++++++++++++++++++++ 2 files changed, 29 insertions(+), 28 deletions(-) delete mode 100644 LICENSE create mode 100644 LICENSE.md diff --git a/LICENSE b/LICENSE deleted file mode 100644 index d4569487..00000000 --- a/LICENSE +++ /dev/null @@ -1,28 +0,0 @@ -Copyright (c) 2014 Nathan LaFreniere and other 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: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * 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. - * The names of any contributors may not 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 HOLDERS AND 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. - - * * * - -The complete list of contributors can be found at: https://github.com/hapijs/qs/graphs/contributors diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 00000000..fecf6b69 --- /dev/null +++ b/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. From 9566d25019caae8c4f1a9097bf344238a583d014 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Fri, 29 Mar 2019 14:50:56 -0400 Subject: [PATCH 072/278] [Fix] fix for an impossible situation: when the formatter is called with a non-string value Note that all these tests passed already. Since the only time a formatter is called is in a context where it is concatenated with another string using `+`, this is a redundant step. However, for pedantic correctness and documentation, the contract for formatters is to always return a string. --- lib/formats.js | 2 +- test/stringify.js | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/lib/formats.js b/lib/formats.js index df459975..702da12e 100644 --- a/lib/formats.js +++ b/lib/formats.js @@ -10,7 +10,7 @@ module.exports = { return replace.call(value, percentTwenties, '+'); }, RFC3986: function (value) { - return value; + return String(value); } }, RFC1738: 'RFC1738', diff --git a/test/stringify.js b/test/stringify.js index 7076680a..ec4df056 100644 --- a/test/stringify.js +++ b/test/stringify.js @@ -469,6 +469,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(); }); @@ -509,17 +515,20 @@ test('stringify()', function (t) { t.test('RFC 1738 spaces 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.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(); }); From 180bfa532e5a1be34323c97e5067fe0c7fda6a0d Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Wed, 26 Jun 2019 00:22:20 -0700 Subject: [PATCH 073/278] =?UTF-8?q?[meta]=20Clean=20up=20license=20text=20?= =?UTF-8?q?so=20it=E2=80=99s=20properly=20detected=20as=20BSD-3-Clause?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- LICENSE | 28 ---------------------------- LICENSE.md | 29 +++++++++++++++++++++++++++++ 2 files changed, 29 insertions(+), 28 deletions(-) delete mode 100644 LICENSE create mode 100644 LICENSE.md diff --git a/LICENSE b/LICENSE deleted file mode 100644 index d4569487..00000000 --- a/LICENSE +++ /dev/null @@ -1,28 +0,0 @@ -Copyright (c) 2014 Nathan LaFreniere and other 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: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * 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. - * The names of any contributors may not 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 HOLDERS AND 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. - - * * * - -The complete list of contributors can be found at: https://github.com/hapijs/qs/graphs/contributors diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 00000000..fecf6b69 --- /dev/null +++ b/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. From 4c036ced428e265037f6d4d69034f88d3da2601f Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Fri, 29 Mar 2019 14:50:56 -0400 Subject: [PATCH 074/278] [Fix] fix for an impossible situation: when the formatter is called with a non-string value Note that all these tests passed already. Since the only time a formatter is called is in a context where it is concatenated with another string using `+`, this is a redundant step. However, for pedantic correctness and documentation, the contract for formatters is to always return a string. --- lib/formats.js | 2 +- test/stringify.js | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/lib/formats.js b/lib/formats.js index df459975..702da12e 100644 --- a/lib/formats.js +++ b/lib/formats.js @@ -10,7 +10,7 @@ module.exports = { return replace.call(value, percentTwenties, '+'); }, RFC3986: function (value) { - return value; + return String(value); } }, RFC1738: 'RFC1738', diff --git a/test/stringify.js b/test/stringify.js index 84d1a407..19700e51 100644 --- a/test/stringify.js +++ b/test/stringify.js @@ -469,6 +469,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(); }); @@ -509,17 +515,20 @@ test('stringify()', function (t) { t.test('RFC 1738 spaces 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.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(); }); From 2ebaf879d5665f126ebabb18ac7874dceaef46ab Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Sat, 30 Mar 2019 13:54:02 -0700 Subject: [PATCH 075/278] [New] [Fix] stringify symbols and bigints --- lib/stringify.js | 10 +++++++++- package.json | 1 + test/stringify.js | 37 +++++++++++++++++++++++++++++++++++++ 3 files changed, 47 insertions(+), 1 deletion(-) diff --git a/lib/stringify.js b/lib/stringify.js index 4eac941b..b2e5f06e 100644 --- a/lib/stringify.js +++ b/lib/stringify.js @@ -46,6 +46,14 @@ var defaults = { strictNullHandling: false }; +var isNonNullishPrimitive = function isNonNullishPrimitive(v) { // eslint-disable-line func-name-matching + return typeof v === 'string' + || typeof v === 'number' + || typeof v === 'boolean' + || typeof v === 'symbol' + || typeof v === 'bigint'; // eslint-disable-line valid-typeof +}; + var stringify = function stringify( // eslint-disable-line func-name-matching object, prefix, @@ -78,7 +86,7 @@ var stringify = function stringify( // eslint-disable-line func-name-matching 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))]; diff --git a/package.json b/package.json index 28d98a11..94095e85 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,7 @@ "eslint": "^5.15.3", "evalmd": "^0.0.17", "for-each": "^0.3.3", + "has-symbols": "^1.0.0", "iconv-lite": "^0.4.24", "mkdirp": "^0.5.1", "object-inspect": "^1.6.0", diff --git a/test/stringify.js b/test/stringify.js index 7a07931b..a35942c3 100644 --- a/test/stringify.js +++ b/test/stringify.js @@ -1,10 +1,14 @@ 'use strict'; +/* globals Symbol, BigInt */ + 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 hasBigInt = typeof BigInt === 'function'; test('stringify()', function (t) { t.test('stringifies a querystring object', function (st) { @@ -28,6 +32,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); // eslint-disable-line new-cap + var encodeWithN = function (value, defaultEncoder, charset) { + var result = defaultEncoder(value, defaultEncoder, charset); + return typeof value === 'bigint' ? result + 'n' : result; // eslint-disable-line valid-typeof + }; + 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(); From b04febd9cb1c94b466aa2bd81b6452b44712414e Mon Sep 17 00:00:00 2001 From: Ivan Zverev Date: Mon, 27 May 2019 17:41:12 +0300 Subject: [PATCH 076/278] [New] `parse`: add `allowSparse` option for collapsing arrays with missing indices Fixes #181. --- README.md | 7 +++++++ lib/parse.js | 6 ++++++ test/parse.js | 9 +++++++++ test/stringify.js | 10 ++++++++++ 4 files changed, 32 insertions(+) diff --git a/README.md b/README.md index 84f2534e..d2676bd7 100644 --- a/README.md +++ b/README.md @@ -227,6 +227,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 diff --git a/lib/parse.js b/lib/parse.js index 553498b4..c833315c 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -8,6 +8,7 @@ var isArray = Array.isArray; var defaults = { allowDots: false, allowPrototypes: false, + allowSparse: false, arrayLimit: 20, charset: 'utf-8', charsetSentinel: false, @@ -217,6 +218,7 @@ var normalizeParseOptions = function normalizeParseOptions(opts) { 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, @@ -253,5 +255,9 @@ module.exports = function (str, opts) { obj = utils.merge(obj, newObj, options); } + if (options.allowSparse === true) { + return obj; + } + return utils.compact(obj); }; diff --git a/test/parse.js b/test/parse.js index b6ec1b72..7a3cfdef 100644 --- a/test/parse.js +++ b/test/parse.js @@ -269,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' } }); diff --git a/test/stringify.js b/test/stringify.js index 7f0ec70c..59324e0c 100644 --- a/test/stringify.js +++ b/test/stringify.js @@ -789,5 +789,15 @@ test('stringify()', function (t) { 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(); }); From e39c235760b58dfaf3a8b5b18b8ff85331e9ddd0 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Fri, 16 Aug 2019 19:43:55 -0700 Subject: [PATCH 077/278] [Tests] `Buffer.from` in node v5.0-v5.9 and v4.0-v4.4 requires a TypedArray --- test/utils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/utils.js b/test/utils.js index da31ce53..aa84dfdc 100644 --- a/test/utils.js +++ b/test/utils.js @@ -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(); }); From 760a6702ce3d7d0adb356dc89207da8c1eebad0b Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Thu, 12 Sep 2019 18:05:38 -0700 Subject: [PATCH 078/278] [Dev Deps] update `eslint`, `@ljharb/eslint-config`, `evalmd` --- package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 91ad94dd..a02ab0e0 100644 --- a/package.json +++ b/package.json @@ -28,12 +28,12 @@ }, "dependencies": {}, "devDependencies": { - "@ljharb/eslint-config": "^14.0.2", + "@ljharb/eslint-config": "^14.1.0", "browserify": "^16.5.0", "covert": "^1.1.1", "eclint": "^2.8.1", - "eslint": "^6.1.0", - "evalmd": "^0.0.17", + "eslint": "^6.3.0", + "evalmd": "^0.0.19", "for-each": "^0.3.3", "has-symbols": "^1.0.0", "iconv-lite": "^0.4.24", From 97154a653e73dcf461024d5ab77a17841caab4f5 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Thu, 12 Sep 2019 18:07:07 -0700 Subject: [PATCH 079/278] [Tests] up to `node` `v12.10`, `v11.15`, `v10.16`, `v8.16` --- .travis.yml | 37 ++++++++++++++++++++++++++++++++++--- 1 file changed, 34 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index cad8f455..daa10621 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,10 +2,11 @@ language: node_js os: - linux node_js: - - "11.12" - - "10.15" + - "12.10" + - "11.15" + - "10.16" - "9.11" - - "8.15" + - "8.16" - "7.10" - "6.17" - "5.12" @@ -37,6 +38,32 @@ matrix: env: PRETEST=true - node_js: "4" env: COVERAGE=true + - node_js: "12.9" + env: TEST=true ALLOW_FAILURE=true + - node_js: "12.8" + env: TEST=true ALLOW_FAILURE=true + - node_js: "12.7" + env: TEST=true ALLOW_FAILURE=true + - node_js: "12.6" + env: TEST=true ALLOW_FAILURE=true + - node_js: "12.5" + env: TEST=true ALLOW_FAILURE=true + - node_js: "12.4" + env: TEST=true ALLOW_FAILURE=true + - node_js: "12.3" + env: TEST=true ALLOW_FAILURE=true + - node_js: "12.2" + env: TEST=true ALLOW_FAILURE=true + - node_js: "12.1" + env: TEST=true ALLOW_FAILURE=true + - node_js: "12.0" + env: TEST=true ALLOW_FAILURE=true + - node_js: "11.14" + env: TEST=true ALLOW_FAILURE=true + - node_js: "11.13" + env: TEST=true ALLOW_FAILURE=true + - node_js: "11.12" + env: TEST=true ALLOW_FAILURE=true - node_js: "11.11" env: TEST=true ALLOW_FAILURE=true - node_js: "11.10" @@ -61,6 +88,8 @@ matrix: env: TEST=true ALLOW_FAILURE=true - node_js: "11.0" env: TEST=true ALLOW_FAILURE=true + - node_js: "10.15" + env: TEST=true ALLOW_FAILURE=true - node_js: "10.14" env: TEST=true ALLOW_FAILURE=true - node_js: "10.13" @@ -113,6 +142,8 @@ matrix: env: TEST=true ALLOW_FAILURE=true - node_js: "9.0" env: TEST=true ALLOW_FAILURE=true + - node_js: "8.15" + env: TEST=true ALLOW_FAILURE=true - node_js: "8.14" env: TEST=true ALLOW_FAILURE=true - node_js: "8.13" From 40191798d07d51403add5fb15014db3d87a05ae3 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Thu, 12 Sep 2019 18:07:53 -0700 Subject: [PATCH 080/278] [Tests] add `posttest` using `npx aud` to run `npm audit` without a lockfile --- .travis.yml | 2 ++ package.json | 1 + 2 files changed, 3 insertions(+) diff --git a/.travis.yml b/.travis.yml index daa10621..5e11195d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -36,6 +36,8 @@ matrix: include: - node_js: "lts/*" env: PRETEST=true + - node_js: "lts/*" + env: POSTTEST=true - node_js: "4" env: COVERAGE=true - node_js: "12.9" diff --git a/package.json b/package.json index a02ab0e0..29d24086 100644 --- a/package.json +++ b/package.json @@ -49,6 +49,7 @@ "pretest": "npm run --silent readme && npm run --silent lint", "test": "npm run --silent coverage", "tests-only": "node test", + "posttest": "npx aud", "readme": "evalmd README.md", "postlint": "eclint check * lib/* test/*", "lint": "eslint lib/*.js test/*.js", From 7f216eef99cb9eb0ebb3714411a044174c1448cd Mon Sep 17 00:00:00 2001 From: Chris Dyson Date: Thu, 12 Sep 2019 13:32:23 +1200 Subject: [PATCH 081/278] [New] `parse`/`stringify`: Pass extra key/value argument to `decoder` A new fourth argument is now provided when invoking the decoder inside parse, this argument represents whether the first argument (str) is a key or a value. This can then be used to decode key and values different within the decode function. A test has been added for this new behavior. --- README.md | 24 ++++++++++++++++++++++++ lib/parse.js | 6 +++--- lib/stringify.js | 6 +++--- test/.eslintrc | 3 ++- test/parse.js | 15 +++++++++++++++ test/stringify.js | 15 +++++++++++++++ 6 files changed, 62 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index cb47d60a..8592a4c2 100644 --- a/README.md +++ b/README.md @@ -330,6 +330,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, defaultEncoder, 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: diff --git a/lib/parse.js b/lib/parse.js index 057adc85..04493e1d 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -72,11 +72,11 @@ 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 = options.decoder(part.slice(pos + 1), defaults.decoder, charset, 'value'); } if (val && options.interpretNumericEntities && charset === 'iso-8859-1') { diff --git a/lib/stringify.js b/lib/stringify.js index b2e5f06e..96e34d0f 100644 --- a/lib/stringify.js +++ b/lib/stringify.js @@ -80,7 +80,7 @@ var stringify = function stringify( // eslint-disable-line func-name-matching if (obj === null) { if (strictNullHandling) { - return encoder && !encodeValuesOnly ? encoder(prefix, defaults.encoder, charset) : prefix; + return encoder && !encodeValuesOnly ? encoder(prefix, defaults.encoder, charset, 'key') : prefix; } obj = ''; @@ -88,8 +88,8 @@ var stringify = function stringify( // eslint-disable-line func-name-matching 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'); + return [formatter(keyValue) + '=' + formatter(encoder(obj, defaults.encoder, charset, 'value'))]; } return [formatter(prefix) + '=' + formatter(String(obj))]; } diff --git a/test/.eslintrc b/test/.eslintrc index f76d573c..8ab8536a 100644 --- a/test/.eslintrc +++ b/test/.eslintrc @@ -11,7 +11,8 @@ "no-buffer-constructor": 0, "no-extend-native": 0, "no-magic-numbers": 0, + "no-throw-literal": 0, "object-curly-newline": 0, - "sort-keys": 0 + "sort-keys": 0, } } diff --git a/test/parse.js b/test/parse.js index 397b867d..138e6625 100644 --- a/test/parse.js +++ b/test/parse.js @@ -686,5 +686,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(); }); diff --git a/test/stringify.js b/test/stringify.js index 053fe1f2..e36272cd 100644 --- a/test/stringify.js +++ b/test/stringify.js @@ -719,5 +719,20 @@ 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.end(); }); From df0cb440773e5540d926065f9d1a9e3d066173cd Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Sat, 21 Sep 2019 10:08:42 -0700 Subject: [PATCH 082/278] [Dev Deps] update `eslint` --- dist/qs.js | 12 ++++++------ lib/stringify.js | 14 +++++++------- package.json | 2 +- test/parse.js | 2 -- test/stringify.js | 4 ++-- 5 files changed, 16 insertions(+), 18 deletions(-) diff --git a/dist/qs.js b/dist/qs.js index 08335520..6d31bda2 100644 --- a/dist/qs.js +++ b/dist/qs.js @@ -114,11 +114,11 @@ 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 = options.decoder(part.slice(pos + 1), defaults.decoder, charset, 'value'); } if (val && options.interpretNumericEntities && charset === 'iso-8859-1') { @@ -367,7 +367,7 @@ var stringify = function stringify( // eslint-disable-line func-name-matching if (obj === null) { if (strictNullHandling) { - return encoder && !encodeValuesOnly ? encoder(prefix, defaults.encoder, charset) : prefix; + return encoder && !encodeValuesOnly ? encoder(prefix, defaults.encoder, charset, 'key') : prefix; } obj = ''; @@ -375,8 +375,8 @@ var stringify = function stringify( // eslint-disable-line func-name-matching 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'); + return [formatter(keyValue) + '=' + formatter(encoder(obj, defaults.encoder, charset, 'value'))]; } return [formatter(prefix) + '=' + formatter(String(obj))]; } diff --git a/lib/stringify.js b/lib/stringify.js index 96e34d0f..72ded14a 100644 --- a/lib/stringify.js +++ b/lib/stringify.js @@ -5,14 +5,14 @@ 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; } }; @@ -39,22 +39,22 @@ var defaults = { 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 isNonNullishPrimitive = function isNonNullishPrimitive(v) { // 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'; // eslint-disable-line valid-typeof + || typeof v === 'bigint'; }; -var stringify = function stringify( // eslint-disable-line func-name-matching +var stringify = function stringify( object, prefix, generateArrayPrefix, diff --git a/package.json b/package.json index 29d24086..9fd351ed 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,7 @@ "browserify": "^16.5.0", "covert": "^1.1.1", "eclint": "^2.8.1", - "eslint": "^6.3.0", + "eslint": "^6.4.0", "evalmd": "^0.0.19", "for-each": "^0.3.3", "has-symbols": "^1.0.0", diff --git a/test/parse.js b/test/parse.js index 138e6625..a473b634 100644 --- a/test/parse.js +++ b/test/parse.js @@ -629,7 +629,6 @@ test('parse()', function (t) { }); t.test('prefers an iso-8859-1 charset specified by the utf8 sentinel to a default charset of utf-8', function (st) { - // eslint-disable-next-line quote-props st.deepEqual(qs.parse('utf8=' + urlEncodedNumCheckmark + '&' + urlEncodedOSlashInUtf8 + '=' + urlEncodedOSlashInUtf8, { charsetSentinel: true, charset: 'utf-8' }), { 'ø': 'ø' }); st.end(); }); @@ -650,7 +649,6 @@ test('parse()', function (t) { }); t.test('uses the utf8 sentinel to switch to iso-8859-1 when no default charset is given', function (st) { - // eslint-disable-next-line quote-props st.deepEqual(qs.parse('utf8=' + urlEncodedNumCheckmark + '&' + urlEncodedOSlashInUtf8 + '=' + urlEncodedOSlashInUtf8, { charsetSentinel: true }), { 'ø': 'ø' }); st.end(); }); diff --git a/test/stringify.js b/test/stringify.js index e36272cd..760d08cb 100644 --- a/test/stringify.js +++ b/test/stringify.js @@ -42,10 +42,10 @@ test('stringify()', function (t) { }); t.test('stringifies bigints', { skip: !hasBigInt }, function (st) { - var three = BigInt(3); // eslint-disable-line new-cap + var three = BigInt(3); var encodeWithN = function (value, defaultEncoder, charset) { var result = defaultEncoder(value, defaultEncoder, charset); - return typeof value === 'bigint' ? result + 'n' : result; // eslint-disable-line valid-typeof + return typeof value === 'bigint' ? result + 'n' : result; }; st.equal(qs.stringify(three), ''); st.equal(qs.stringify([three]), '0=3'); From dadf9dbd97434ebaa20636d528a108287ca40bbc Mon Sep 17 00:00:00 2001 From: Roman Usherenko Date: Sun, 8 Sep 2019 10:27:42 +0300 Subject: [PATCH 083/278] [Tests] `parse`: add passing `arrayFormat` tests --- test/parse.js | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/test/parse.js b/test/parse.js index a473b634..9ea38dd3 100644 --- a/test/parse.js +++ b/test/parse.js @@ -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' } }); From 670254b63fc7770894eed9a0f020bc0b72698ce3 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Sat, 21 Sep 2019 14:03:59 -0700 Subject: [PATCH 084/278] v6.9.0 --- CHANGELOG.md | 8 ++++++++ dist/qs.js | 14 +++++++------- package.json | 2 +- 3 files changed, 16 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f25205ba..99452542 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ +## **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.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 diff --git a/dist/qs.js b/dist/qs.js index 6d31bda2..4474559e 100644 --- a/dist/qs.js +++ b/dist/qs.js @@ -292,14 +292,14 @@ 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; } }; @@ -326,22 +326,22 @@ var defaults = { 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 isNonNullishPrimitive = function isNonNullishPrimitive(v) { // 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'; // eslint-disable-line valid-typeof + || typeof v === 'bigint'; }; -var stringify = function stringify( // eslint-disable-line func-name-matching +var stringify = function stringify( object, prefix, generateArrayPrefix, diff --git a/package.json b/package.json index 9fd351ed..57896e8a 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "qs", "description": "A querystring parser that supports nesting and arrays, with a depth limit", "homepage": "https://github.com/ljharb/qs", - "version": "6.8.0", + "version": "6.9.0", "repository": { "type": "git", "url": "https://github.com/ljharb/qs.git" From 698b683d7382721c1c32c1cdcb97ca0b16917cf9 Mon Sep 17 00:00:00 2001 From: Mohamed Omar Date: Fri, 4 Oct 2019 02:57:00 +0200 Subject: [PATCH 085/278] [fix] `parse`: with comma true, do not split non-string values --- lib/parse.js | 2 +- test/parse.js | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/lib/parse.js b/lib/parse.js index 04493e1d..53c30dbc 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -83,7 +83,7 @@ var parseValues = function parseQueryStringValues(str, options) { val = interpretNumericEntities(val); } - if (val && options.comma && val.indexOf(',') > -1) { + if (val && typeof val === 'string' && options.comma && val.indexOf(',') > -1) { val = val.split(','); } diff --git a/test/parse.js b/test/parse.js index 9ea38dd3..349f14b2 100644 --- a/test/parse.js +++ b/test/parse.js @@ -400,6 +400,20 @@ test('parse()', function (t) { 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 an object in dot notation', function (st) { var input = { 'user.name': { 'pop[bob]': 3 }, From f884e2d6274c51ad8455e1339a0ad9b12bd63e06 Mon Sep 17 00:00:00 2001 From: Mohamed Omar Date: Fri, 4 Oct 2019 05:50:34 +0200 Subject: [PATCH 086/278] [Fix] `parse`: with comma true, handle field that holds an array of arrays Fixes #327. --- lib/parse.js | 5 +++++ test/parse.js | 9 +++++++++ 2 files changed, 14 insertions(+) diff --git a/lib/parse.js b/lib/parse.js index 53c30dbc..7cca8842 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -3,6 +3,7 @@ var utils = require('./utils'); var has = Object.prototype.hasOwnProperty; +var isArray = Array.isArray; var defaults = { allowDots: false, @@ -87,6 +88,10 @@ var parseValues = function parseQueryStringValues(str, options) { val = val.split(','); } + if (part.indexOf('[]=') > -1) { + val = isArray(val) ? [val] : val; + } + if (has.call(obj, key)) { obj[key] = utils.combine(obj[key], val); } else { diff --git a/test/parse.js b/test/parse.js index 349f14b2..be10400b 100644 --- a/test/parse.js +++ b/test/parse.js @@ -414,6 +414,15 @@ test('parse()', function (t) { 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 an object in dot notation', function (st) { var input = { 'user.name': { 'pop[bob]': 3 }, From 1f358315d91a895578d1a4832441fbcade3a895f Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Thu, 7 Nov 2019 15:03:10 -0800 Subject: [PATCH 087/278] [Dev Deps] update `eslint`, `@ljharb/eslint-config` --- lib/utils.js | 1 + package.json | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/utils.js b/lib/utils.js index f3260633..bb972417 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -43,6 +43,7 @@ var arrayToObject = function arrayToObject(source, options) { }; var merge = function merge(target, source, options) { + /* eslint no-param-reassign: 0 */ if (!source) { return target; } diff --git a/package.json b/package.json index 57896e8a..e4fc3504 100644 --- a/package.json +++ b/package.json @@ -28,11 +28,11 @@ }, "dependencies": {}, "devDependencies": { - "@ljharb/eslint-config": "^14.1.0", + "@ljharb/eslint-config": "^15.0.0", "browserify": "^16.5.0", "covert": "^1.1.1", "eclint": "^2.8.1", - "eslint": "^6.4.0", + "eslint": "^6.6.0", "evalmd": "^0.0.19", "for-each": "^0.3.3", "has-symbols": "^1.0.0", From b9a032fbe4baf3fe9fd3e7a86fe95b50ce41ee77 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Thu, 7 Nov 2019 15:05:08 -0800 Subject: [PATCH 088/278] [meta] add `funding` field --- package.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/package.json b/package.json index e4fc3504..21b28392 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,9 @@ "type": "git", "url": "https://github.com/ljharb/qs.git" }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + }, "main": "lib/index.js", "contributors": [ { From 6151be3bc24d61d63500d904d5e3484524400d88 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Thu, 7 Nov 2019 15:06:19 -0800 Subject: [PATCH 089/278] [Tests] use shared travis-ci config --- .travis.yml | 317 +--------------------------------------------------- 1 file changed, 6 insertions(+), 311 deletions(-) diff --git a/.travis.yml b/.travis.yml index 5e11195d..7709ba6d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,317 +1,12 @@ +version: ~> 1.0 language: node_js os: - linux -node_js: - - "12.10" - - "11.15" - - "10.16" - - "9.11" - - "8.16" - - "7.10" - - "6.17" - - "5.12" - - "4.9" - - "iojs-v3.3" - - "iojs-v2.5" - - "iojs-v1.8" - - "0.12" - - "0.10" - - "0.8" - - "0.6" -before_install: - - 'case "${TRAVIS_NODE_VERSION}" in 0.*) export NPM_CONFIG_STRICT_SSL=false ;; esac' - - 'nvm install-latest-npm' -install: - - 'if [ "${TRAVIS_NODE_VERSION}" = "0.6" ] || [ "${TRAVIS_NODE_VERSION}" = "0.9" ]; then nvm install --latest-npm 0.8 && npm install && nvm use "${TRAVIS_NODE_VERSION}"; else npm install; fi;' -script: - - 'if [ -n "${PRETEST-}" ]; then npm run pretest ; fi' - - 'if [ -n "${POSTTEST-}" ]; then npm run posttest ; fi' - - 'if [ -n "${COVERAGE-}" ]; then npm run coverage ; fi' - - 'if [ -n "${TEST-}" ]; then npm run tests-only ; fi' -sudo: false -env: - - TEST=true +import: + - ljharb/travis-ci:node/all.yml + - ljharb/travis-ci:node/pretest.yml + - ljharb/travis-ci:node/posttest.yml + - ljharb/travis-ci:node/coverage.yml matrix: - fast_finish: true - include: - - node_js: "lts/*" - env: PRETEST=true - - node_js: "lts/*" - env: POSTTEST=true - - node_js: "4" - env: COVERAGE=true - - node_js: "12.9" - env: TEST=true ALLOW_FAILURE=true - - node_js: "12.8" - env: TEST=true ALLOW_FAILURE=true - - node_js: "12.7" - env: TEST=true ALLOW_FAILURE=true - - node_js: "12.6" - env: TEST=true ALLOW_FAILURE=true - - node_js: "12.5" - env: TEST=true ALLOW_FAILURE=true - - node_js: "12.4" - env: TEST=true ALLOW_FAILURE=true - - node_js: "12.3" - env: TEST=true ALLOW_FAILURE=true - - node_js: "12.2" - env: TEST=true ALLOW_FAILURE=true - - node_js: "12.1" - env: TEST=true ALLOW_FAILURE=true - - node_js: "12.0" - env: TEST=true ALLOW_FAILURE=true - - node_js: "11.14" - env: TEST=true ALLOW_FAILURE=true - - node_js: "11.13" - env: TEST=true ALLOW_FAILURE=true - - node_js: "11.12" - env: TEST=true ALLOW_FAILURE=true - - node_js: "11.11" - env: TEST=true ALLOW_FAILURE=true - - node_js: "11.10" - env: TEST=true ALLOW_FAILURE=true - - node_js: "11.9" - env: TEST=true ALLOW_FAILURE=true - - node_js: "11.8" - env: TEST=true ALLOW_FAILURE=true - - node_js: "11.7" - env: TEST=true ALLOW_FAILURE=true - - node_js: "11.6" - env: TEST=true ALLOW_FAILURE=true - - node_js: "11.5" - env: TEST=true ALLOW_FAILURE=true - - node_js: "11.4" - env: TEST=true ALLOW_FAILURE=true - - node_js: "11.3" - env: TEST=true ALLOW_FAILURE=true - - node_js: "11.2" - env: TEST=true ALLOW_FAILURE=true - - node_js: "11.1" - env: TEST=true ALLOW_FAILURE=true - - node_js: "11.0" - env: TEST=true ALLOW_FAILURE=true - - node_js: "10.15" - env: TEST=true ALLOW_FAILURE=true - - node_js: "10.14" - env: TEST=true ALLOW_FAILURE=true - - node_js: "10.13" - env: TEST=true ALLOW_FAILURE=true - - node_js: "10.12" - env: TEST=true ALLOW_FAILURE=true - - node_js: "10.11" - env: TEST=true ALLOW_FAILURE=true - - node_js: "10.10" - env: TEST=true ALLOW_FAILURE=true - - node_js: "10.9" - env: TEST=true ALLOW_FAILURE=true - - node_js: "10.8" - env: TEST=true ALLOW_FAILURE=true - - node_js: "10.7" - env: TEST=true ALLOW_FAILURE=true - - node_js: "10.6" - env: TEST=true ALLOW_FAILURE=true - - node_js: "10.5" - env: TEST=true ALLOW_FAILURE=true - - node_js: "10.4" - env: TEST=true ALLOW_FAILURE=true - - node_js: "10.3" - env: TEST=true ALLOW_FAILURE=true - - node_js: "10.2" - env: TEST=true ALLOW_FAILURE=true - - node_js: "10.1" - env: TEST=true ALLOW_FAILURE=true - - node_js: "10.0" - env: TEST=true ALLOW_FAILURE=true - - node_js: "9.10" - env: TEST=true ALLOW_FAILURE=true - - node_js: "9.9" - env: TEST=true ALLOW_FAILURE=true - - node_js: "9.8" - env: TEST=true ALLOW_FAILURE=true - - node_js: "9.7" - env: TEST=true ALLOW_FAILURE=true - - node_js: "9.6" - env: TEST=true ALLOW_FAILURE=true - - node_js: "9.5" - env: TEST=true ALLOW_FAILURE=true - - node_js: "9.4" - env: TEST=true ALLOW_FAILURE=true - - node_js: "9.3" - env: TEST=true ALLOW_FAILURE=true - - node_js: "9.2" - env: TEST=true ALLOW_FAILURE=true - - node_js: "9.1" - env: TEST=true ALLOW_FAILURE=true - - node_js: "9.0" - env: TEST=true ALLOW_FAILURE=true - - node_js: "8.15" - env: TEST=true ALLOW_FAILURE=true - - node_js: "8.14" - env: TEST=true ALLOW_FAILURE=true - - node_js: "8.13" - env: TEST=true ALLOW_FAILURE=true - - node_js: "8.12" - env: TEST=true ALLOW_FAILURE=true - - node_js: "8.11" - env: TEST=true ALLOW_FAILURE=true - - node_js: "8.10" - env: TEST=true ALLOW_FAILURE=true - - node_js: "8.9" - env: TEST=true ALLOW_FAILURE=true - - node_js: "8.8" - env: TEST=true ALLOW_FAILURE=true - - node_js: "8.7" - env: TEST=true ALLOW_FAILURE=true - - node_js: "8.6" - env: TEST=true ALLOW_FAILURE=true - - node_js: "8.5" - env: TEST=true ALLOW_FAILURE=true - - node_js: "8.4" - env: TEST=true ALLOW_FAILURE=true - - node_js: "8.3" - env: TEST=true ALLOW_FAILURE=true - - node_js: "8.2" - env: TEST=true ALLOW_FAILURE=true - - node_js: "8.1" - env: TEST=true ALLOW_FAILURE=true - - node_js: "8.0" - env: TEST=true ALLOW_FAILURE=true - - node_js: "7.9" - env: TEST=true ALLOW_FAILURE=true - - node_js: "7.8" - env: TEST=true ALLOW_FAILURE=true - - node_js: "7.7" - env: TEST=true ALLOW_FAILURE=true - - node_js: "7.6" - env: TEST=true ALLOW_FAILURE=true - - node_js: "7.5" - env: TEST=true ALLOW_FAILURE=true - - node_js: "7.4" - env: TEST=true ALLOW_FAILURE=true - - node_js: "7.3" - env: TEST=true ALLOW_FAILURE=true - - node_js: "7.2" - env: TEST=true ALLOW_FAILURE=true - - node_js: "7.1" - env: TEST=true ALLOW_FAILURE=true - - node_js: "7.0" - env: TEST=true ALLOW_FAILURE=true - - node_js: "6.16" - env: TEST=true ALLOW_FAILURE=true - - node_js: "6.15" - env: TEST=true ALLOW_FAILURE=true - - node_js: "6.14" - env: TEST=true ALLOW_FAILURE=true - - node_js: "6.13" - env: TEST=true ALLOW_FAILURE=true - - node_js: "6.12" - env: TEST=true ALLOW_FAILURE=true - - node_js: "6.11" - env: TEST=true ALLOW_FAILURE=true - - node_js: "6.10" - env: TEST=true ALLOW_FAILURE=true - - node_js: "6.9" - env: TEST=true ALLOW_FAILURE=true - - node_js: "6.8" - env: TEST=true ALLOW_FAILURE=true - - node_js: "6.7" - env: TEST=true ALLOW_FAILURE=true - - node_js: "6.6" - env: TEST=true ALLOW_FAILURE=true - - node_js: "6.5" - env: TEST=true ALLOW_FAILURE=true - - node_js: "6.4" - env: TEST=true ALLOW_FAILURE=true - - node_js: "6.3" - env: TEST=true ALLOW_FAILURE=true - - node_js: "6.2" - env: TEST=true ALLOW_FAILURE=true - - node_js: "6.1" - env: TEST=true ALLOW_FAILURE=true - - node_js: "6.0" - env: TEST=true ALLOW_FAILURE=true - - node_js: "5.11" - env: TEST=true ALLOW_FAILURE=true - - node_js: "5.10" - env: TEST=true ALLOW_FAILURE=true - - node_js: "5.9" - env: TEST=true ALLOW_FAILURE=true - - node_js: "5.8" - env: TEST=true ALLOW_FAILURE=true - - node_js: "5.7" - env: TEST=true ALLOW_FAILURE=true - - node_js: "5.6" - env: TEST=true ALLOW_FAILURE=true - - node_js: "5.5" - env: TEST=true ALLOW_FAILURE=true - - node_js: "5.4" - env: TEST=true ALLOW_FAILURE=true - - node_js: "5.3" - env: TEST=true ALLOW_FAILURE=true - - node_js: "5.2" - env: TEST=true ALLOW_FAILURE=true - - node_js: "5.1" - env: TEST=true ALLOW_FAILURE=true - - node_js: "5.0" - env: TEST=true ALLOW_FAILURE=true - - node_js: "4.8" - env: TEST=true ALLOW_FAILURE=true - - node_js: "4.7" - env: TEST=true ALLOW_FAILURE=true - - node_js: "4.6" - env: TEST=true ALLOW_FAILURE=true - - node_js: "4.5" - env: TEST=true ALLOW_FAILURE=true - - node_js: "4.4" - env: TEST=true ALLOW_FAILURE=true - - node_js: "4.3" - env: TEST=true ALLOW_FAILURE=true - - node_js: "4.2" - env: TEST=true ALLOW_FAILURE=true - - node_js: "4.1" - env: TEST=true ALLOW_FAILURE=true - - node_js: "4.0" - env: TEST=true ALLOW_FAILURE=true - - node_js: "iojs-v3.2" - env: TEST=true ALLOW_FAILURE=true - - node_js: "iojs-v3.1" - env: TEST=true ALLOW_FAILURE=true - - node_js: "iojs-v3.0" - env: TEST=true ALLOW_FAILURE=true - - node_js: "iojs-v2.4" - env: TEST=true ALLOW_FAILURE=true - - node_js: "iojs-v2.3" - env: TEST=true ALLOW_FAILURE=true - - node_js: "iojs-v2.2" - env: TEST=true ALLOW_FAILURE=true - - node_js: "iojs-v2.1" - env: TEST=true ALLOW_FAILURE=true - - node_js: "iojs-v2.0" - env: TEST=true ALLOW_FAILURE=true - - node_js: "iojs-v1.7" - env: TEST=true ALLOW_FAILURE=true - - node_js: "iojs-v1.6" - env: TEST=true ALLOW_FAILURE=true - - node_js: "iojs-v1.5" - env: TEST=true ALLOW_FAILURE=true - - node_js: "iojs-v1.4" - env: TEST=true ALLOW_FAILURE=true - - node_js: "iojs-v1.3" - env: TEST=true ALLOW_FAILURE=true - - node_js: "iojs-v1.2" - env: TEST=true ALLOW_FAILURE=true - - node_js: "iojs-v1.1" - env: TEST=true ALLOW_FAILURE=true - - node_js: "iojs-v1.0" - env: TEST=true ALLOW_FAILURE=true - - node_js: "0.11" - env: TEST=true ALLOW_FAILURE=true - - node_js: "0.9" - env: TEST=true ALLOW_FAILURE=true - - node_js: "0.4" - env: TEST=true ALLOW_FAILURE=true allow_failures: - - os: osx - - env: TEST=true ALLOW_FAILURE=true - - node_js: "0.6" # temporarily allow this to fail - env: COVERAGE=true # temporarily allow this to fail From 7b368004723b8d11d4d237ff0479b9edcfb41449 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Thu, 7 Nov 2019 22:32:26 -0800 Subject: [PATCH 090/278] v6.9.1 --- CHANGELOG.md | 7 +++++++ dist/qs.js | 8 +++++++- package.json | 2 +- 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 99452542..5591d202 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## **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` diff --git a/dist/qs.js b/dist/qs.js index 4474559e..602c3a68 100644 --- a/dist/qs.js +++ b/dist/qs.js @@ -45,6 +45,7 @@ module.exports = { var utils = require('./utils'); var has = Object.prototype.hasOwnProperty; +var isArray = Array.isArray; var defaults = { allowDots: false, @@ -125,10 +126,14 @@ var parseValues = function parseQueryStringValues(str, options) { val = interpretNumericEntities(val); } - if (val && options.comma && val.indexOf(',') > -1) { + if (val && typeof val === 'string' && options.comma && val.indexOf(',') > -1) { val = val.split(','); } + if (part.indexOf('[]=') > -1) { + val = isArray(val) ? [val] : val; + } + if (has.call(obj, key)) { obj[key] = utils.combine(obj[key], val); } else { @@ -611,6 +616,7 @@ var arrayToObject = function arrayToObject(source, options) { }; var merge = function merge(target, source, options) { + /* eslint no-param-reassign: 0 */ if (!source) { return target; } diff --git a/package.json b/package.json index 21b28392..682462b9 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "qs", "description": "A querystring parser that supports nesting and arrays, with a depth limit", "homepage": "https://github.com/ljharb/qs", - "version": "6.9.0", + "version": "6.9.1", "repository": { "type": "git", "url": "https://github.com/ljharb/qs.git" From 152b26c7fd74cbcd3ed56bb1edc2fa6abb7db8e8 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Fri, 6 Dec 2019 14:37:31 -0800 Subject: [PATCH 091/278] [meta] add tidelift marketing copy --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index 8592a4c2..84f2534e 100644 --- a/README.md +++ b/README.md @@ -581,6 +581,12 @@ assert.equal(qs.stringify({ a: 'b c' }, { format : 'RFC1738' }), 'a=b+c'); 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) + [1]: https://npmjs.org/package/qs [2]: http://versionbadg.es/ljharb/qs.svg [3]: https://api.travis-ci.org/ljharb/qs.svg From 76e45701df6d2a768dca22ed3777e6693a83ed88 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Mon, 13 Jan 2020 21:57:54 -0800 Subject: [PATCH 092/278] [actions] add automatic rebasing / merge commit blocking See https://github.com/ljharb/es-abstract/pull/74 --- .github/workflows/rebase.yml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 .github/workflows/rebase.yml diff --git a/.github/workflows/rebase.yml b/.github/workflows/rebase.yml new file mode 100644 index 00000000..436cb79d --- /dev/null +++ b/.github/workflows/rebase.yml @@ -0,0 +1,15 @@ +name: Automatic Rebase + +on: [pull_request] + +jobs: + _: + name: "Automatic Rebase" + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v1 + - uses: ljharb/rebase@master + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From 72dc89f3ce4bc9e89ef4cfc57b62951c90e1d7ff Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Mon, 13 Jan 2020 21:37:05 -0800 Subject: [PATCH 093/278] [meta] fix indentation in package.json --- package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 682462b9..67d3cc3d 100644 --- a/package.json +++ b/package.json @@ -7,9 +7,9 @@ "type": "git", "url": "https://github.com/ljharb/qs.git" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + }, "main": "lib/index.js", "contributors": [ { From 5af2bf8553e90217a51cbae9ae69053fa214e600 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Mon, 13 Jan 2020 22:00:36 -0800 Subject: [PATCH 094/278] [meta] ignore eclint transitive audit warning --- .npmrc | 1 + 1 file changed, 1 insertion(+) diff --git a/.npmrc b/.npmrc index 43c97e71..fc227f38 100644 --- a/.npmrc +++ b/.npmrc @@ -1 +1,2 @@ package-lock=false +audit-level=moderate From eac5616d3086b263740bd3af08964438f529b5e5 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Mon, 13 Jan 2020 21:40:08 -0800 Subject: [PATCH 095/278] [Dev Deps] update `eslint`, `@ljharb/eslint-config`, `object-inspect`, `has-symbols`, `tape` --- package.json | 12 ++++++------ test/parse.js | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index 67d3cc3d..81c989c4 100644 --- a/package.json +++ b/package.json @@ -31,21 +31,21 @@ }, "dependencies": {}, "devDependencies": { - "@ljharb/eslint-config": "^15.0.0", + "@ljharb/eslint-config": "^15.1.0", "browserify": "^16.5.0", "covert": "^1.1.1", "eclint": "^2.8.1", - "eslint": "^6.6.0", + "eslint": "^6.8.0", "evalmd": "^0.0.19", "for-each": "^0.3.3", - "has-symbols": "^1.0.0", + "has-symbols": "^1.0.1", "iconv-lite": "^0.4.24", "mkdirp": "^0.5.1", - "object-inspect": "^1.6.0", + "object-inspect": "^1.7.0", "qs-iconv": "^1.0.4", - "safe-publish-latest": "^1.1.3", + "safe-publish-latest": "^1.1.4", "safer-buffer": "^2.1.2", - "tape": "^4.11.0" + "tape": "^5.0.0-next.3" }, "scripts": { "prepublish": "safe-publish-latest && npm run dist", diff --git a/test/parse.js b/test/parse.js index be10400b..fcfd86ac 100644 --- a/test/parse.js +++ b/test/parse.js @@ -599,7 +599,7 @@ 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' ); From fe6384c848a336640219815c06bcd9a2f0f3e20f Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Mon, 13 Jan 2020 21:50:38 -0800 Subject: [PATCH 096/278] [Fix] `parse`: throw a TypeError instead of an Error for bad charset --- lib/parse.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/parse.js b/lib/parse.js index 7cca8842..8db3a5f8 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -201,7 +201,7 @@ 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; From 0625c496f242771b549cd0bc052e5b2716217af1 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Sun, 26 Jan 2020 09:50:47 -0800 Subject: [PATCH 097/278] [Dev Deps] update `@ljharb/eslint-config`, `tape` --- package.json | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 81c989c4..ae65f480 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,7 @@ }, "dependencies": {}, "devDependencies": { - "@ljharb/eslint-config": "^15.1.0", + "@ljharb/eslint-config": "^16.0.0", "browserify": "^16.5.0", "covert": "^1.1.1", "eclint": "^2.8.1", @@ -45,7 +45,7 @@ "qs-iconv": "^1.0.4", "safe-publish-latest": "^1.1.4", "safer-buffer": "^2.1.2", - "tape": "^5.0.0-next.3" + "tape": "^5.0.0-next.4" }, "scripts": { "prepublish": "safe-publish-latest && npm run dist", @@ -59,5 +59,11 @@ "coverage": "covert test", "dist": "mkdirp dist && browserify --standalone Qs lib/index.js > dist/qs.js" }, - "license": "BSD-3-Clause" + "license": "BSD-3-Clause", + "greenkeeper": { + "ignore": [ + "iconv-lite", + "mkdirp" + ] + } } From eecd28d292aa4c89d112ac769f2807c062deebcb Mon Sep 17 00:00:00 2001 From: Alexander Yefanov Date: Sun, 15 Mar 2020 10:45:46 +0100 Subject: [PATCH 098/278] [Fix] `parse`: Fix parsing array from object with `comma` true Fixes #357 --- lib/parse.js | 14 ++++++++++---- test/parse.js | 6 ++++++ 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/lib/parse.js b/lib/parse.js index 8db3a5f8..3be8e1bc 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -29,6 +29,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 @@ -84,9 +92,7 @@ var parseValues = function parseQueryStringValues(str, options) { val = interpretNumericEntities(val); } - if (val && typeof val === 'string' && options.comma && val.indexOf(',') > -1) { - val = val.split(','); - } + val = parseArrayValue(val, options); if (part.indexOf('[]=') > -1) { val = isArray(val) ? [val] : val; @@ -103,7 +109,7 @@ var parseValues = function parseQueryStringValues(str, options) { }; var parseObject = function (chain, val, options) { - var leaf = val; + var leaf = parseArrayValue(val, options); for (var i = chain.length - 1; i >= 0; --i) { var obj; diff --git a/test/parse.js b/test/parse.js index fcfd86ac..903b2af3 100644 --- a/test/parse.js +++ b/test/parse.js @@ -400,6 +400,12 @@ 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))) { From 911efabec48ea86da210e1edd5dd15b1e0e11ec9 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Sun, 22 Mar 2020 00:09:49 -0700 Subject: [PATCH 099/278] [Dev Deps] update `tape`, `mkdirp`, `iconv-lite` --- package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index ae65f480..9befdbe6 100644 --- a/package.json +++ b/package.json @@ -39,13 +39,13 @@ "evalmd": "^0.0.19", "for-each": "^0.3.3", "has-symbols": "^1.0.1", - "iconv-lite": "^0.4.24", - "mkdirp": "^0.5.1", + "iconv-lite": "^0.5.1", + "mkdirp": "^0.5.3", "object-inspect": "^1.7.0", "qs-iconv": "^1.0.4", "safe-publish-latest": "^1.1.4", "safer-buffer": "^2.1.2", - "tape": "^5.0.0-next.4" + "tape": "^5.0.0-next.5" }, "scripts": { "prepublish": "safe-publish-latest && npm run dist", From ddc1ff9ca16a4b8963d7cf72d0a881732e54b8c9 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Sun, 22 Mar 2020 00:14:49 -0700 Subject: [PATCH 100/278] v6.9.2 --- CHANGELOG.md | 9 +++++++++ dist/qs.js | 16 +++++++++++----- package.json | 2 +- 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5591d202..cf0643af 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,12 @@ +## **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) diff --git a/dist/qs.js b/dist/qs.js index 602c3a68..2d78d97a 100644 --- a/dist/qs.js +++ b/dist/qs.js @@ -71,6 +71,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 @@ -126,9 +134,7 @@ var parseValues = function parseQueryStringValues(str, options) { val = interpretNumericEntities(val); } - if (val && typeof val === 'string' && options.comma && val.indexOf(',') > -1) { - val = val.split(','); - } + val = parseArrayValue(val, options); if (part.indexOf('[]=') > -1) { val = isArray(val) ? [val] : val; @@ -145,7 +151,7 @@ var parseValues = function parseQueryStringValues(str, options) { }; var parseObject = function (chain, val, options) { - var leaf = val; + var leaf = parseArrayValue(val, options); for (var i = chain.length - 1; i >= 0; --i) { var obj; @@ -243,7 +249,7 @@ 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; diff --git a/package.json b/package.json index 9befdbe6..c1611cba 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "qs", "description": "A querystring parser that supports nesting and arrays, with a depth limit", "homepage": "https://github.com/ljharb/qs", - "version": "6.9.1", + "version": "6.9.2", "repository": { "type": "git", "url": "https://github.com/ljharb/qs.git" From cd9a3cd692df33421115741e1ec0e4526dd9da22 Mon Sep 17 00:00:00 2001 From: Mohamed Omar Date: Fri, 18 Oct 2019 19:41:30 +0200 Subject: [PATCH 101/278] [Fix] parses comma delimited array while having percent-encoded comma treated as normal text Co-authored-by: Mohamed Omar Co-authored-by: Quentin de Longraye --- lib/parse.js | 10 +++++++++- test/parse.js | 8 ++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/lib/parse.js b/lib/parse.js index 3be8e1bc..67fb938f 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -85,7 +85,15 @@ var parseValues = function parseQueryStringValues(str, options) { val = options.strictNullHandling ? null : ''; } else { key = options.decoder(part.slice(0, pos), defaults.decoder, charset, 'key'); - val = options.decoder(part.slice(pos + 1), defaults.decoder, charset, 'value'); + var encodedVal = part.slice(pos + 1); + if (options.comma && encodedVal.indexOf(',') !== -1) { + val = encodedVal.split(',') + .map(function (encodedFragment) { + return options.decoder(encodedFragment, defaults.decoder, charset, 'value'); + }); + } else { + val = options.decoder(encodedVal, defaults.decoder, charset, 'value'); + } } if (val && options.interpretNumericEntities && charset === 'iso-8859-1') { diff --git a/test/parse.js b/test/parse.js index 903b2af3..10b649a7 100644 --- a/test/parse.js +++ b/test/parse.js @@ -429,6 +429,14 @@ test('parse()', function (t) { 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 }, From bf0ea91f37e1fd4a4e75c0057e216de00b4489e3 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Tue, 24 Mar 2020 20:53:21 -0700 Subject: [PATCH 102/278] [Fix] proper comma parsing of URL-encoded commas Fixes #311. Followup to #336. --- lib/parse.js | 40 +++++++++++++++++++++++----------------- test/parse.js | 2 +- 2 files changed, 24 insertions(+), 18 deletions(-) diff --git a/lib/parse.js b/lib/parse.js index 67fb938f..49d5c042 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -37,6 +37,17 @@ var parseArrayValue = function (val, options) { return val; }; +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); +}; + // 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 @@ -85,23 +96,18 @@ var parseValues = function parseQueryStringValues(str, options) { val = options.strictNullHandling ? null : ''; } else { key = options.decoder(part.slice(0, pos), defaults.decoder, charset, 'key'); - var encodedVal = part.slice(pos + 1); - if (options.comma && encodedVal.indexOf(',') !== -1) { - val = encodedVal.split(',') - .map(function (encodedFragment) { - return options.decoder(encodedFragment, defaults.decoder, charset, 'value'); - }); - } else { - val = options.decoder(encodedVal, defaults.decoder, charset, 'value'); - } + val = 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); } - val = parseArrayValue(val, options); - if (part.indexOf('[]=') > -1) { val = isArray(val) ? [val] : val; } @@ -116,8 +122,8 @@ var parseValues = function parseQueryStringValues(str, options) { return obj; }; -var parseObject = function (chain, val, options) { - var leaf = parseArrayValue(val, options); +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; @@ -145,13 +151,13 @@ var parseObject = function (chain, val, options) { } } - leaf = obj; + leaf = obj; // eslint-disable-line no-param-reassign } return leaf; }; -var parseKeys = function parseQueryStringKeys(givenKey, val, options) { +var parseKeys = function parseQueryStringKeys(givenKey, val, options, valuesParsed) { if (!givenKey) { return; } @@ -202,7 +208,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) { @@ -254,7 +260,7 @@ 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); } diff --git a/test/parse.js b/test/parse.js index 10b649a7..b6ec1b72 100644 --- a/test/parse.js +++ b/test/parse.js @@ -430,7 +430,7 @@ test('parse()', function (t) { }); 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%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'] }); From 511e1c9e527679ca182b055895ce301ea82213ee Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Wed, 25 Mar 2020 12:50:51 -0700 Subject: [PATCH 103/278] v6.9.3 --- CHANGELOG.md | 4 ++++ dist/qs.js | 32 +++++++++++++++++++++++--------- package.json | 2 +- 3 files changed, 28 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 64214b87..f09f6942 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## **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) diff --git a/dist/qs.js b/dist/qs.js index 2d78d97a..752629db 100644 --- a/dist/qs.js +++ b/dist/qs.js @@ -79,6 +79,17 @@ var parseArrayValue = function (val, options) { return val; }; +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); +}; + // 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 @@ -127,15 +138,18 @@ var parseValues = function parseQueryStringValues(str, options) { val = options.strictNullHandling ? null : ''; } else { key = options.decoder(part.slice(0, pos), defaults.decoder, charset, 'key'); - val = options.decoder(part.slice(pos + 1), defaults.decoder, charset, 'value'); + val = 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); } - val = parseArrayValue(val, options); - if (part.indexOf('[]=') > -1) { val = isArray(val) ? [val] : val; } @@ -150,8 +164,8 @@ var parseValues = function parseQueryStringValues(str, options) { return obj; }; -var parseObject = function (chain, val, options) { - var leaf = parseArrayValue(val, options); +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; @@ -179,13 +193,13 @@ var parseObject = function (chain, val, options) { } } - leaf = obj; + leaf = obj; // eslint-disable-line no-param-reassign } return leaf; }; -var parseKeys = function parseQueryStringKeys(givenKey, val, options) { +var parseKeys = function parseQueryStringKeys(givenKey, val, options, valuesParsed) { if (!givenKey) { return; } @@ -236,7 +250,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) { @@ -288,7 +302,7 @@ 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); } diff --git a/package.json b/package.json index cfb1bd75..443cba7c 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "qs", "description": "A querystring parser that supports nesting and arrays, with a depth limit", "homepage": "https://github.com/ljharb/qs", - "version": "6.9.2", + "version": "6.9.3", "repository": { "type": "git", "url": "https://github.com/ljharb/qs.git" From 5a629d3642bc80a5e42c6874d50f6696a6d19017 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Sat, 2 May 2020 13:54:20 -0700 Subject: [PATCH 104/278] [Dev Deps] update `browserify`, `tape` --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 443cba7c..9e8f2480 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,7 @@ "dependencies": {}, "devDependencies": { "@ljharb/eslint-config": "^16.0.0", - "browserify": "^16.5.0", + "browserify": "^16.5.1", "covert": "^1.1.1", "eclint": "^2.8.1", "eslint": "^6.8.0", @@ -45,7 +45,7 @@ "qs-iconv": "^1.0.4", "safe-publish-latest": "^1.1.4", "safer-buffer": "^2.1.2", - "tape": "^5.0.0-next.5" + "tape": "^5.0.0" }, "scripts": { "prepublish": "safe-publish-latest && npm run dist", From 47e5f8b391d5925cd007ad2265a5134a82f9e74c Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Sat, 2 May 2020 14:07:14 -0700 Subject: [PATCH 105/278] [Refactor] move `maybeMap` to `utils` --- lib/parse.js | 13 +------------ lib/utils.js | 12 ++++++++++++ 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/lib/parse.js b/lib/parse.js index 49d5c042..1751c7be 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -37,17 +37,6 @@ var parseArrayValue = function (val, options) { return val; }; -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); -}; - // 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 @@ -96,7 +85,7 @@ var parseValues = function parseQueryStringValues(str, options) { val = options.strictNullHandling ? null : ''; } else { key = options.decoder(part.slice(0, pos), defaults.decoder, charset, 'key'); - val = maybeMap( + val = utils.maybeMap( parseArrayValue(part.slice(pos + 1), options), function (encodedVal) { return options.decoder(encodedVal, defaults.decoder, charset, 'value'); diff --git a/lib/utils.js b/lib/utils.js index bb972417..98ab1937 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -223,6 +223,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, @@ -232,5 +243,6 @@ module.exports = { encode: encode, isBuffer: isBuffer, isRegExp: isRegExp, + maybeMap: maybeMap, merge: merge }; From daf3e6aeb5ba8ba7d9bef7b690a3e8dbfe70a23d Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Sat, 2 May 2020 14:13:11 -0700 Subject: [PATCH 106/278] [Fix] `stringify`: when `arrayFormat` is `comma`, respect `serializeDate` Fixes #364. --- lib/stringify.js | 7 ++++++- test/stringify.js | 12 ++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/lib/stringify.js b/lib/stringify.js index 72ded14a..fcf5cc8f 100644 --- a/lib/stringify.js +++ b/lib/stringify.js @@ -75,7 +75,12 @@ var stringify = function stringify( } 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; + }).join(','); } if (obj === null) { diff --git a/test/stringify.js b/test/stringify.js index 760d08cb..49692184 100644 --- a/test/stringify.js +++ b/test/stringify.js @@ -595,6 +595,18 @@ 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(); }); From 83d1e9a421a119da355be6817a4895a58c64ba26 Mon Sep 17 00:00:00 2001 From: Yipeng Zhao Date: Sun, 9 Jun 2019 22:47:25 +0800 Subject: [PATCH 107/278] [Refactor] `stringify`: reduce branching --- lib/stringify.js | 55 ++++++++++++++++++------------------------------ 1 file changed, 21 insertions(+), 34 deletions(-) diff --git a/lib/stringify.js b/lib/stringify.js index fcf5cc8f..5d22260c 100644 --- a/lib/stringify.js +++ b/lib/stringify.js @@ -115,44 +115,31 @@ var stringify = function stringify( for (var i = 0; i < objKeys.length; ++i) { var key = objKeys[i]; + var 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 + ']'); + + pushToArray(values, stringify( + value, + keyPrefix, + generateArrayPrefix, + strictNullHandling, + skipNulls, + encoder, + filter, + sort, + allowDots, + serializeDate, + formatter, + encodeValuesOnly, + charset + )); } return values; From 14c69e6b29661a01c33bf3c59dab6a47e8d62a10 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Wed, 26 Jun 2019 00:22:20 -0700 Subject: [PATCH 108/278] =?UTF-8?q?Clean=20up=20license=20text=20so=20it?= =?UTF-8?q?=E2=80=99s=20properly=20detected=20as=20BSD-3-Clause?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- LICENSE | 28 ---------------------------- LICENSE.md | 29 +++++++++++++++++++++++++++++ 2 files changed, 29 insertions(+), 28 deletions(-) delete mode 100644 LICENSE create mode 100644 LICENSE.md diff --git a/LICENSE b/LICENSE deleted file mode 100644 index d4569487..00000000 --- a/LICENSE +++ /dev/null @@ -1,28 +0,0 @@ -Copyright (c) 2014 Nathan LaFreniere and other 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: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * 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. - * The names of any contributors may not 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 HOLDERS AND 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. - - * * * - -The complete list of contributors can be found at: https://github.com/hapijs/qs/graphs/contributors diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 00000000..fecf6b69 --- /dev/null +++ b/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. From 3b40167e2f5333e62e7fb03944f5f65894bc6cac Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Wed, 26 Jun 2019 00:42:04 -0700 Subject: [PATCH 109/278] [Fix] ensure node 0.12 can stringify Symbols --- lib/utils.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/utils.js b/lib/utils.js index 1b219cdd..f3260633 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -126,7 +126,12 @@ var encode = function encode(str, defaultEncoder, charset) { 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) { From a07882f1042a0ebe0e9f17437b9f9f50b69ae0bb Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Wed, 26 Jun 2019 10:06:17 -0700 Subject: [PATCH 110/278] add FUNDING.yml This is an experiment; I intend to use 100% of funds to support the OSS community and my OSS projects' costs. --- .github/FUNDING.yml | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 .github/FUNDING.yml diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 00000000..adf86430 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,12 @@ +# These are supported funding model platforms + +github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] +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 From 4a1cf056f25fa6e9acee500980767eb584d52936 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Wed, 26 Jun 2019 11:21:41 -0700 Subject: [PATCH 111/278] readme: add security note --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 8590cfd3..cb47d60a 100644 --- a/README.md +++ b/README.md @@ -553,6 +553,10 @@ 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. + [1]: https://npmjs.org/package/qs [2]: http://versionbadg.es/ljharb/qs.svg [3]: https://api.travis-ci.org/ljharb/qs.svg From 82c4a5f4fe7ea026ead2f217ce3d661980d246a1 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Wed, 10 Jul 2019 22:55:29 -0700 Subject: [PATCH 112/278] add github sponsorship --- .github/FUNDING.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index adf86430..0355f4f5 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1,6 +1,6 @@ # These are supported funding model platforms -github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] +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 From b14b63845c1b8884bfd6d374e3da020c2455f0d0 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Wed, 10 Jul 2019 23:21:36 -0700 Subject: [PATCH 113/278] [Dev Deps] update `eslint`, `iconv-lite`, `browserify`, `tape` --- dist/qs.js | 240 ++++++++++++++++++++++++++++++++------------------ package.json | 6 +- test/parse.js | 2 + 3 files changed, 159 insertions(+), 89 deletions(-) diff --git a/dist/qs.js b/dist/qs.js index b9482991..70e75ef9 100644 --- a/dist/qs.js +++ b/dist/qs.js @@ -4,21 +4,29 @@ var replace = String.prototype.replace; var percentTwenties = /%20/g; -module.exports = { - 'default': 'RFC3986', - formatters: { - RFC1738: function (value) { - return replace.call(value, percentTwenties, '+'); - }, - RFC3986: function (value) { - return value; - } - }, +var util = require('./utils'); + +var Format = { RFC1738: 'RFC1738', RFC3986: 'RFC3986' }; -},{}],2:[function(require,module,exports){ +module.exports = util.assign( + { + 'default': Format.RFC3986, + formatters: { + RFC1738: function (value) { + return replace.call(value, percentTwenties, '+'); + }, + RFC3986: function (value) { + return String(value); + } + } + }, + Format +); + +},{"./utils":5}],2:[function(require,module,exports){ 'use strict'; var stringify = require('./stringify'); @@ -44,6 +52,7 @@ var defaults = { arrayLimit: 20, charset: 'utf-8', charsetSentinel: false, + comma: false, decoder: utils.decode, delimiter: '&', depth: 5, @@ -115,6 +124,11 @@ var parseValues = function parseQueryStringValues(str, options) { if (val && options.interpretNumericEntities && charset === 'iso-8859-1') { val = interpretNumericEntities(val); } + + if (val && options.comma && val.indexOf(',') > -1) { + val = val.split(','); + } + if (has.call(obj, key)) { obj[key] = utils.combine(obj[key], val); } else { @@ -214,31 +228,41 @@ var parseKeys = function parseQueryStringKeys(givenKey, val, options) { return parseObject(keys, val, options); }; -module.exports = function (str, opts) { - var options = opts ? utils.assign({}, opts) : {}; +var normalizeParseOptions = function normalizeParseOptions(opts) { + if (!opts) { + return defaults; + } - if (options.decoder !== null && options.decoder !== undefined && typeof options.decoder !== 'function') { + if (opts.decoder !== null && opts.decoder !== undefined && typeof opts.decoder !== 'function') { throw new TypeError('Decoder has to be a function.'); } - options.ignoreQueryPrefix = options.ignoreQueryPrefix === true; - options.delimiter = typeof options.delimiter === 'string' || utils.isRegExp(options.delimiter) ? options.delimiter : defaults.delimiter; - options.depth = typeof options.depth === 'number' ? options.depth : defaults.depth; - options.arrayLimit = typeof options.arrayLimit === 'number' ? options.arrayLimit : defaults.arrayLimit; - options.parseArrays = options.parseArrays !== false; - options.decoder = typeof options.decoder === 'function' ? options.decoder : defaults.decoder; - options.allowDots = typeof options.allowDots === 'undefined' ? defaults.allowDots : !!options.allowDots; - options.plainObjects = typeof options.plainObjects === 'boolean' ? options.plainObjects : defaults.plainObjects; - options.allowPrototypes = typeof options.allowPrototypes === 'boolean' ? options.allowPrototypes : defaults.allowPrototypes; - options.parameterLimit = typeof options.parameterLimit === 'number' ? options.parameterLimit : defaults.parameterLimit; - options.strictNullHandling = typeof options.strictNullHandling === 'boolean' ? options.strictNullHandling : defaults.strictNullHandling; - - if (typeof options.charset !== 'undefined' && options.charset !== 'utf-8' && options.charset !== 'iso-8859-1') { + 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'); } - if (typeof options.charset === 'undefined') { - options.charset = defaults.charset; - } + 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, + 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, + 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, + strictNullHandling: typeof opts.strictNullHandling === 'boolean' ? opts.strictNullHandling : defaults.strictNullHandling + }; +}; + +module.exports = function (str, opts) { + var options = normalizeParseOptions(opts); if (str === '' || str === null || typeof str === 'undefined') { return options.plainObjects ? Object.create(null) : {}; @@ -264,11 +288,13 @@ module.exports = function (str, opts) { 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 return prefix + '[]'; }, + comma: 'comma', indices: function indices(prefix, key) { // eslint-disable-line func-name-matching return prefix + '[' + key + ']'; }, @@ -285,6 +311,7 @@ var pushToArray = function (arr, valueOrArray) { var toISO = Date.prototype.toISOString; +var defaultFormat = formats['default']; var defaults = { addQueryPrefix: false, allowDots: false, @@ -294,6 +321,8 @@ var defaults = { encode: true, encoder: utils.encode, encodeValuesOnly: false, + format: defaultFormat, + formatter: formats.formatters[defaultFormat], // deprecated indices: false, serializeDate: function serializeDate(date) { // eslint-disable-line func-name-matching @@ -303,6 +332,14 @@ var defaults = { strictNullHandling: false }; +var isNonNullishPrimitive = function isNonNullishPrimitive(v) { // eslint-disable-line func-name-matching + return typeof v === 'string' + || typeof v === 'number' + || typeof v === 'boolean' + || typeof v === 'symbol' + || typeof v === 'bigint'; // eslint-disable-line valid-typeof +}; + var stringify = function stringify( // eslint-disable-line func-name-matching object, prefix, @@ -323,6 +360,8 @@ var stringify = function stringify( // eslint-disable-line func-name-matching obj = filter(prefix, obj); } else if (obj instanceof Date) { obj = serializeDate(obj); + } else if (generateArrayPrefix === 'comma' && isArray(obj)) { + obj = obj.join(','); } if (obj === null) { @@ -333,7 +372,7 @@ var stringify = function stringify( // eslint-disable-line func-name-matching 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))]; @@ -348,7 +387,7 @@ var stringify = function stringify( // eslint-disable-line func-name-matching } var objKeys; - if (Array.isArray(filter)) { + if (isArray(filter)) { objKeys = filter; } else { var keys = Object.keys(obj); @@ -362,10 +401,10 @@ var stringify = function stringify( // eslint-disable-line func-name-matching continue; } - if (Array.isArray(obj)) { + if (isArray(obj)) { pushToArray(values, stringify( obj[key], - generateArrayPrefix(prefix, key), + typeof generateArrayPrefix === 'function' ? generateArrayPrefix(prefix, key) : prefix, generateArrayPrefix, strictNullHandling, skipNulls, @@ -400,41 +439,63 @@ var stringify = function stringify( // eslint-disable-line func-name-matching return values; }; -module.exports = function (object, opts) { - var obj = object; - var options = opts ? utils.assign({}, opts) : {}; +var normalizeStringifyOptions = function normalizeStringifyOptions(opts) { + if (!opts) { + return defaults; + } - if (options.encoder !== null && options.encoder !== undefined && typeof options.encoder !== 'function') { + if (opts.encoder !== null && opts.encoder !== undefined && typeof opts.encoder !== 'function') { throw new TypeError('Encoder has to be a function.'); } - var delimiter = typeof options.delimiter === 'undefined' ? defaults.delimiter : options.delimiter; - var strictNullHandling = typeof options.strictNullHandling === 'boolean' ? options.strictNullHandling : defaults.strictNullHandling; - var skipNulls = typeof options.skipNulls === 'boolean' ? options.skipNulls : defaults.skipNulls; - var encode = typeof options.encode === 'boolean' ? options.encode : defaults.encode; - var encoder = typeof options.encoder === 'function' ? options.encoder : defaults.encoder; - var sort = typeof options.sort === 'function' ? options.sort : null; - var allowDots = typeof options.allowDots === 'undefined' ? defaults.allowDots : !!options.allowDots; - var serializeDate = typeof options.serializeDate === 'function' ? options.serializeDate : defaults.serializeDate; - var encodeValuesOnly = typeof options.encodeValuesOnly === 'boolean' ? options.encodeValuesOnly : defaults.encodeValuesOnly; - var charset = options.charset || defaults.charset; - if (typeof options.charset !== 'undefined' && options.charset !== 'utf-8' && options.charset !== 'iso-8859-1') { - throw new Error('The charset option must be either utf-8, iso-8859-1, or undefined'); + 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'); } - if (typeof options.format === 'undefined') { - options.format = formats['default']; - } else if (!Object.prototype.hasOwnProperty.call(formats.formatters, options.format)) { - throw new TypeError('Unknown format option provided.'); - } - var formatter = formats.formatters[options.format]; + 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; + } + + return { + addQueryPrefix: typeof opts.addQueryPrefix === 'boolean' ? opts.addQueryPrefix : defaults.addQueryPrefix, + allowDots: typeof opts.allowDots === 'undefined' ? defaults.allowDots : !!opts.allowDots, + charset: charset, + charsetSentinel: typeof opts.charsetSentinel === 'boolean' ? opts.charsetSentinel : defaults.charsetSentinel, + delimiter: typeof opts.delimiter === 'undefined' ? defaults.delimiter : opts.delimiter, + encode: typeof opts.encode === 'boolean' ? opts.encode : defaults.encode, + encoder: typeof opts.encoder === 'function' ? opts.encoder : defaults.encoder, + encodeValuesOnly: typeof opts.encodeValuesOnly === 'boolean' ? opts.encodeValuesOnly : defaults.encodeValuesOnly, + filter: filter, + 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 (Array.isArray(options.filter)) { + } else if (isArray(options.filter)) { filter = options.filter; objKeys = filter; } @@ -446,10 +507,10 @@ module.exports = function (object, opts) { } var arrayFormat; - if (options.arrayFormat in arrayPrefixGenerators) { - arrayFormat = options.arrayFormat; - } else if ('indices' in options) { - arrayFormat = options.indices ? 'indices' : 'repeat'; + if (opts && opts.arrayFormat in arrayPrefixGenerators) { + arrayFormat = opts.arrayFormat; + } else if (opts && 'indices' in opts) { + arrayFormat = opts.indices ? 'indices' : 'repeat'; } else { arrayFormat = 'indices'; } @@ -460,38 +521,38 @@ module.exports = function (object, opts) { objKeys = Object.keys(obj); } - if (sort) { - objKeys.sort(sort); + if (options.sort) { + objKeys.sort(options.sort); } for (var i = 0; i < objKeys.length; ++i) { var key = objKeys[i]; - if (skipNulls && obj[key] === null) { + if (options.skipNulls && obj[key] === null) { continue; } pushToArray(keys, stringify( obj[key], key, generateArrayPrefix, - strictNullHandling, - skipNulls, - encode ? encoder : null, - filter, - sort, - allowDots, - serializeDate, - formatter, - encodeValuesOnly, - charset + options.strictNullHandling, + options.skipNulls, + options.encode ? options.encoder : null, + options.filter, + options.sort, + options.allowDots, + options.serializeDate, + options.formatter, + options.encodeValuesOnly, + options.charset )); } - var joined = keys.join(delimiter); + var joined = keys.join(options.delimiter); var prefix = options.addQueryPrefix === true ? '?' : ''; if (options.charsetSentinel) { - if (charset === 'iso-8859-1') { + if (options.charset === 'iso-8859-1') { // encodeURIComponent('✓'), the "numeric entity" representation of a checkmark prefix += 'utf8=%26%2310003%3B&'; } else { @@ -507,6 +568,7 @@ module.exports = function (object, opts) { 'use strict'; var has = Object.prototype.hasOwnProperty; +var isArray = Array.isArray; var hexTable = (function () { var array = []; @@ -522,7 +584,7 @@ var compactQueue = function compactQueue(queue) { var item = queue.pop(); var obj = item.obj[item.prop]; - if (Array.isArray(obj)) { + if (isArray(obj)) { var compacted = []; for (var j = 0; j < obj.length; ++j) { @@ -553,9 +615,9 @@ var merge = function merge(target, source, options) { } if (typeof source !== 'object') { - if (Array.isArray(target)) { + if (isArray(target)) { target.push(source); - } else if (typeof target === 'object') { + } else if (target && typeof target === 'object') { if ((options && (options.plainObjects || options.allowPrototypes)) || !has.call(Object.prototype, source)) { target[source] = true; } @@ -566,20 +628,21 @@ var merge = function merge(target, source, options) { return target; } - if (typeof target !== 'object') { + if (!target || typeof target !== 'object') { return [target].concat(source); } var mergeTarget = target; - if (Array.isArray(target) && !Array.isArray(source)) { + if (isArray(target) && !isArray(source)) { mergeTarget = arrayToObject(target, options); } - if (Array.isArray(target) && Array.isArray(source)) { + if (isArray(target) && isArray(source)) { source.forEach(function (item, i) { if (has.call(target, i)) { - if (target[i] && typeof target[i] === 'object') { - target[i] = merge(target[i], item, options); + var targetItem = target[i]; + if (targetItem && typeof targetItem === 'object' && item && typeof item === 'object') { + target[i] = merge(targetItem, item, options); } else { target.push(item); } @@ -630,7 +693,12 @@ var encode = function encode(str, defaultEncoder, charset) { 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) { @@ -710,7 +778,7 @@ var isRegExp = function isRegExp(obj) { }; var isBuffer = function isBuffer(obj) { - if (obj === null || typeof obj === 'undefined') { + if (!obj || typeof obj !== 'object') { return false; } diff --git a/package.json b/package.json index 94095e85..9b5ff14d 100644 --- a/package.json +++ b/package.json @@ -29,10 +29,10 @@ "dependencies": {}, "devDependencies": { "@ljharb/eslint-config": "^13.1.1", - "browserify": "^16.2.3", + "browserify": "^16.3.0", "covert": "^1.1.1", "editorconfig-tools": "^0.1.1", - "eslint": "^5.15.3", + "eslint": "^5.16.0", "evalmd": "^0.0.17", "for-each": "^0.3.3", "has-symbols": "^1.0.0", @@ -42,7 +42,7 @@ "qs-iconv": "^1.0.4", "safe-publish-latest": "^1.1.2", "safer-buffer": "^2.1.2", - "tape": "^4.10.1" + "tape": "^4.11.0" }, "scripts": { "prepublish": "safe-publish-latest && npm run dist", diff --git a/test/parse.js b/test/parse.js index 89677899..e6c998eb 100644 --- a/test/parse.js +++ b/test/parse.js @@ -617,6 +617,7 @@ test('parse()', function (t) { }); t.test('prefers an iso-8859-1 charset specified by the utf8 sentinel to a default charset of utf-8', function (st) { + // eslint-disable-next-line quote-props st.deepEqual(qs.parse('utf8=' + urlEncodedNumCheckmark + '&' + urlEncodedOSlashInUtf8 + '=' + urlEncodedOSlashInUtf8, { charsetSentinel: true, charset: 'utf-8' }), { 'ø': 'ø' }); st.end(); }); @@ -637,6 +638,7 @@ test('parse()', function (t) { }); t.test('uses the utf8 sentinel to switch to iso-8859-1 when no default charset is given', function (st) { + // eslint-disable-next-line quote-props st.deepEqual(qs.parse('utf8=' + urlEncodedNumCheckmark + '&' + urlEncodedOSlashInUtf8 + '=' + urlEncodedOSlashInUtf8, { charsetSentinel: true }), { 'ø': 'ø' }); st.end(); }); From 360ec16dd5a36462096a8c5d583636e85c03992d Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Wed, 10 Jul 2019 23:50:19 -0700 Subject: [PATCH 114/278] [Tests] use `eclint` instead of `editorconfig-tools` --- .editorconfig | 3 +++ package.json | 4 ++-- test/.eslintrc | 10 +++++----- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/.editorconfig b/.editorconfig index a4893ddf..b442a9f9 100644 --- a/.editorconfig +++ b/.editorconfig @@ -12,6 +12,9 @@ max_line_length = 160 [test/*] max_line_length = off +[LICENSE.md] +indent_size = off + [*.md] max_line_length = off diff --git a/package.json b/package.json index 9b5ff14d..1190acf1 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,7 @@ "@ljharb/eslint-config": "^13.1.1", "browserify": "^16.3.0", "covert": "^1.1.1", - "editorconfig-tools": "^0.1.1", + "eclint": "^2.8.1", "eslint": "^5.16.0", "evalmd": "^0.0.17", "for-each": "^0.3.3", @@ -50,7 +50,7 @@ "test": "npm run --silent coverage", "tests-only": "node test", "readme": "evalmd README.md", - "postlint": "editorconfig-tools check * lib/* test/*", + "postlint": "eclint check * lib/* test/*", "lint": "eslint lib/*.js test/*.js", "coverage": "covert test", "dist": "mkdirp dist && browserify --standalone Qs lib/index.js > dist/qs.js" diff --git a/test/.eslintrc b/test/.eslintrc index 9ebbb921..f76d573c 100644 --- a/test/.eslintrc +++ b/test/.eslintrc @@ -1,17 +1,17 @@ { "rules": { - "array-bracket-newline": 0, - "array-element-newline": 0, - "consistent-return": 2, + "array-bracket-newline": 0, + "array-element-newline": 0, + "consistent-return": 2, "function-paren-newline": 0, "max-lines": 0, "max-lines-per-function": 0, "max-nested-callbacks": [2, 3], "max-statements": 0, - "no-buffer-constructor": 0, + "no-buffer-constructor": 0, "no-extend-native": 0, "no-magic-numbers": 0, - "object-curly-newline": 0, + "object-curly-newline": 0, "sort-keys": 0 } } From a30e4b1cdf8f77bf4f84b5f8c6ee4bbde9d95792 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=BD=98=E4=B8=BD=E6=95=8F?= Date: Thu, 8 Aug 2019 11:40:59 +0800 Subject: [PATCH 115/278] [Tests] add tests for `depth=0` and `depth=false` behavior, both current and intuitive/intended --- test/parse.js | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/test/parse.js b/test/parse.js index e6c998eb..0d30de23 100644 --- a/test/parse.js +++ b/test/parse.js @@ -52,6 +52,30 @@ test('parse()', function (t) { st.end(); }); + t.test('current behavior with depth = 0', function (st) { + st.deepEqual(qs.parse('a[0]=b&a[1]=c', { depth: 0 }), { a: { '[0]': 'b', '[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', '[0][1]': 'c', '[1]': 'd' }, e: '2' }); + st.end(); + }); + + t.test('uses original key when depth = 0', { skip: true, todo: true }, 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('current behavior with depth = false', function (st) { + st.deepEqual(qs.parse('a[0]=b&a[1]=c', { depth: false }), { a: ['b', 'c'] }); + st.deepEqual(qs.parse('a[0][0]=b&a[0][1]=c&a[1]=d&e=2', { depth: false }), { a: [['b', 'c'], 'd'], e: '2' }); + st.end(); + }); + + t.test('uses original key when depth = false', { skip: true, todo: true }, 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) { From 649f05f0a9d3c8c51dc1255298b2ff8408b8ddd4 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Fri, 16 Aug 2019 14:17:40 -0700 Subject: [PATCH 116/278] [New] add `depth=false` to preserve the original key; [Fix] `depth=0` should preserve the original key --- lib/parse.js | 7 ++++--- test/parse.js | 16 ++-------------- 2 files changed, 6 insertions(+), 17 deletions(-) diff --git a/lib/parse.js b/lib/parse.js index d81628b5..057adc85 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -147,7 +147,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 +167,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) { @@ -209,7 +209,8 @@ var normalizeParseOptions = function normalizeParseOptions(opts) { 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, diff --git a/test/parse.js b/test/parse.js index 0d30de23..397b867d 100644 --- a/test/parse.js +++ b/test/parse.js @@ -52,25 +52,13 @@ test('parse()', function (t) { st.end(); }); - t.test('current behavior with depth = 0', function (st) { - st.deepEqual(qs.parse('a[0]=b&a[1]=c', { depth: 0 }), { a: { '[0]': 'b', '[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', '[0][1]': 'c', '[1]': 'd' }, e: '2' }); - st.end(); - }); - - t.test('uses original key when depth = 0', { skip: true, todo: true }, function (st) { + 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('current behavior with depth = false', function (st) { - st.deepEqual(qs.parse('a[0]=b&a[1]=c', { depth: false }), { a: ['b', 'c'] }); - st.deepEqual(qs.parse('a[0][0]=b&a[0][1]=c&a[1]=d&e=2', { depth: false }), { a: [['b', 'c'], 'd'], e: '2' }); - st.end(); - }); - - t.test('uses original key when depth = false', { skip: true, todo: true }, function (st) { + 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(); From d1d06a606b11111c8c301be7039bc46d5e541ddb Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Fri, 16 Aug 2019 16:25:30 -0700 Subject: [PATCH 117/278] [Dev Deps] update `eslint`, `@ljharb/eslint-config`, `browserify`, `safe-publish-latest` --- package.json | 8 ++++---- test/stringify.js | 2 -- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 1190acf1..2ec9d9ec 100644 --- a/package.json +++ b/package.json @@ -28,11 +28,11 @@ }, "dependencies": {}, "devDependencies": { - "@ljharb/eslint-config": "^13.1.1", - "browserify": "^16.3.0", + "@ljharb/eslint-config": "^14.0.2", + "browserify": "^16.5.0", "covert": "^1.1.1", "eclint": "^2.8.1", - "eslint": "^5.16.0", + "eslint": "^6.1.0", "evalmd": "^0.0.17", "for-each": "^0.3.3", "has-symbols": "^1.0.0", @@ -40,7 +40,7 @@ "mkdirp": "^0.5.1", "object-inspect": "^1.6.0", "qs-iconv": "^1.0.4", - "safe-publish-latest": "^1.1.2", + "safe-publish-latest": "^1.1.3", "safer-buffer": "^2.1.2", "tape": "^4.11.0" }, diff --git a/test/stringify.js b/test/stringify.js index a35942c3..053fe1f2 100644 --- a/test/stringify.js +++ b/test/stringify.js @@ -1,7 +1,5 @@ 'use strict'; -/* globals Symbol, BigInt */ - var test = require('tape'); var qs = require('../'); var utils = require('../lib/utils'); From 7ebe4ad78f6abc9fcc15bdfd0e5a9a771b855cf5 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Fri, 16 Aug 2019 16:57:49 -0700 Subject: [PATCH 118/278] v6.8.0 --- CHANGELOG.md | 14 ++++++++++++++ dist/qs.js | 7 ++++--- package.json | 2 +- 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 50505c46..f25205ba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,17 @@ +## **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.0** - [New] `stringify`/`parse`: add `comma` as an `arrayFormat` option (#276, #219) - [Fix] correctly parse nested arrays (#212) diff --git a/dist/qs.js b/dist/qs.js index 70e75ef9..08335520 100644 --- a/dist/qs.js +++ b/dist/qs.js @@ -189,7 +189,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 @@ -209,7 +209,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) { @@ -251,7 +251,8 @@ var normalizeParseOptions = function normalizeParseOptions(opts) { 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, diff --git a/package.json b/package.json index 2ec9d9ec..91ad94dd 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "qs", "description": "A querystring parser that supports nesting and arrays, with a depth limit", "homepage": "https://github.com/ljharb/qs", - "version": "6.7.0", + "version": "6.8.0", "repository": { "type": "git", "url": "https://github.com/ljharb/qs.git" From 26cfd53456598960487b9c4a96d66b56bd40f6bc Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Mon, 13 Jan 2020 21:50:38 -0800 Subject: [PATCH 119/278] [Fix] `parse`: throw a TypeError instead of an Error for bad charset (#349) --- lib/parse.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/parse.js b/lib/parse.js index 057adc85..606b5829 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -196,7 +196,7 @@ 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; From b635b60b432bc05e7d20874d133cd8f23caa43fd Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Fri, 16 Aug 2019 19:43:55 -0700 Subject: [PATCH 120/278] [Tests] `Buffer.from` in node v5.0-v5.9 and v4.0-v4.4 requires a TypedArray --- test/utils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/utils.js b/test/utils.js index da31ce53..aa84dfdc 100644 --- a/test/utils.js +++ b/test/utils.js @@ -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(); }); From 8851d84fb37372cbb292ca9044730cfaf48ad400 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Mon, 23 Mar 2020 21:18:47 -0700 Subject: [PATCH 121/278] [Dev Deps] update `eslint`, `@ljharb/eslint-config`, `tape`, `safe-publish-latest`, `evalmd`, `has-symbols`, `iconv-lite`, `mkdirp`, `object-inspect` --- lib/stringify.js | 12 ++++++------ lib/utils.js | 12 ++++++------ package.json | 18 +++++++++--------- test/parse.js | 4 +--- test/stringify.js | 4 ++-- 5 files changed, 24 insertions(+), 26 deletions(-) diff --git a/lib/stringify.js b/lib/stringify.js index b2e5f06e..c80c0236 100644 --- a/lib/stringify.js +++ b/lib/stringify.js @@ -5,14 +5,14 @@ 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; } }; @@ -39,14 +39,14 @@ var defaults = { 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 isNonNullishPrimitive = function isNonNullishPrimitive(v) { // eslint-disable-line func-name-matching +var isNonNullishPrimitive = function isNonNullishPrimitive(v) { return typeof v === 'string' || typeof v === 'number' || typeof v === 'boolean' @@ -54,7 +54,7 @@ var isNonNullishPrimitive = function isNonNullishPrimitive(v) { // eslint-disabl || typeof v === 'bigint'; // eslint-disable-line valid-typeof }; -var stringify = function stringify( // eslint-disable-line func-name-matching +var stringify = function stringify( object, prefix, generateArrayPrefix, diff --git a/lib/utils.js b/lib/utils.js index f3260633..32eedac7 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -52,7 +52,7 @@ var merge = function merge(target, source, options) { target.push(source); } else if (target && typeof target === 'object') { if ((options && (options.plainObjects || options.allowPrototypes)) || !has.call(Object.prototype, source)) { - target[source] = true; + target[source] = true; // eslint-disable-line no-param-reassign } } else { return [target, source]; @@ -75,12 +75,12 @@ var merge = function merge(target, source, options) { if (has.call(target, i)) { var targetItem = target[i]; if (targetItem && typeof targetItem === 'object' && item && typeof item === 'object') { - target[i] = merge(targetItem, item, options); + target[i] = merge(targetItem, item, options); // eslint-disable-line no-param-reassign } else { target.push(item); } } else { - target[i] = item; + target[i] = item; // eslint-disable-line no-param-reassign } }); return target; @@ -90,9 +90,9 @@ var merge = function merge(target, source, options) { var value = source[key]; if (has.call(acc, key)) { - acc[key] = merge(acc[key], value, options); + acc[key] = merge(acc[key], value, options); // eslint-disable-line no-param-reassign } else { - acc[key] = value; + acc[key] = value; // eslint-disable-line no-param-reassign } return acc; }, mergeTarget); @@ -100,7 +100,7 @@ var merge = function merge(target, source, options) { var assign = function assignSingleSource(target, source) { return Object.keys(source).reduce(function (acc, key) { - acc[key] = source[key]; + acc[key] = source[key]; // eslint-disable-line no-param-reassign return acc; }, target); }; diff --git a/package.json b/package.json index 91ad94dd..f2c093f1 100644 --- a/package.json +++ b/package.json @@ -28,21 +28,21 @@ }, "dependencies": {}, "devDependencies": { - "@ljharb/eslint-config": "^14.0.2", + "@ljharb/eslint-config": "^16.0.0", "browserify": "^16.5.0", "covert": "^1.1.1", "eclint": "^2.8.1", - "eslint": "^6.1.0", - "evalmd": "^0.0.17", + "eslint": "^6.8.0", + "evalmd": "^0.0.19", "for-each": "^0.3.3", - "has-symbols": "^1.0.0", - "iconv-lite": "^0.4.24", - "mkdirp": "^0.5.1", - "object-inspect": "^1.6.0", + "has-symbols": "^1.0.1", + "iconv-lite": "^0.5.1", + "mkdirp": "^0.5.4", + "object-inspect": "^1.7.0", "qs-iconv": "^1.0.4", - "safe-publish-latest": "^1.1.3", + "safe-publish-latest": "^1.1.4", "safer-buffer": "^2.1.2", - "tape": "^4.11.0" + "tape": "^5.0.0-next.5" }, "scripts": { "prepublish": "safe-publish-latest && npm run dist", diff --git a/test/parse.js b/test/parse.js index 397b867d..cafa7b2a 100644 --- a/test/parse.js +++ b/test/parse.js @@ -544,7 +544,7 @@ 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' ); @@ -629,7 +629,6 @@ test('parse()', function (t) { }); t.test('prefers an iso-8859-1 charset specified by the utf8 sentinel to a default charset of utf-8', function (st) { - // eslint-disable-next-line quote-props st.deepEqual(qs.parse('utf8=' + urlEncodedNumCheckmark + '&' + urlEncodedOSlashInUtf8 + '=' + urlEncodedOSlashInUtf8, { charsetSentinel: true, charset: 'utf-8' }), { 'ø': 'ø' }); st.end(); }); @@ -650,7 +649,6 @@ test('parse()', function (t) { }); t.test('uses the utf8 sentinel to switch to iso-8859-1 when no default charset is given', function (st) { - // eslint-disable-next-line quote-props st.deepEqual(qs.parse('utf8=' + urlEncodedNumCheckmark + '&' + urlEncodedOSlashInUtf8 + '=' + urlEncodedOSlashInUtf8, { charsetSentinel: true }), { 'ø': 'ø' }); st.end(); }); diff --git a/test/stringify.js b/test/stringify.js index 053fe1f2..0bdc26eb 100644 --- a/test/stringify.js +++ b/test/stringify.js @@ -42,10 +42,10 @@ test('stringify()', function (t) { }); t.test('stringifies bigints', { skip: !hasBigInt }, function (st) { - var three = BigInt(3); // eslint-disable-line new-cap + var three = BigInt(3); var encodeWithN = function (value, defaultEncoder, charset) { var result = defaultEncoder(value, defaultEncoder, charset); - return typeof value === 'bigint' ? result + 'n' : result; // eslint-disable-line valid-typeof + return typeof value === 'bigint' ? result + 'n' : result; }; st.equal(qs.stringify(three), ''); st.equal(qs.stringify([three]), '0=3'); From 5811c1bfcb73d6909868e13a2e89ec5f793dde88 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Mon, 23 Mar 2020 21:27:38 -0700 Subject: [PATCH 122/278] [Tests] use shared travis-ci configs --- .travis.yml | 284 ++------------------------------------------------- package.json | 1 + 2 files changed, 7 insertions(+), 278 deletions(-) diff --git a/.travis.yml b/.travis.yml index cad8f455..7709ba6d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,284 +1,12 @@ +version: ~> 1.0 language: node_js os: - linux -node_js: - - "11.12" - - "10.15" - - "9.11" - - "8.15" - - "7.10" - - "6.17" - - "5.12" - - "4.9" - - "iojs-v3.3" - - "iojs-v2.5" - - "iojs-v1.8" - - "0.12" - - "0.10" - - "0.8" - - "0.6" -before_install: - - 'case "${TRAVIS_NODE_VERSION}" in 0.*) export NPM_CONFIG_STRICT_SSL=false ;; esac' - - 'nvm install-latest-npm' -install: - - 'if [ "${TRAVIS_NODE_VERSION}" = "0.6" ] || [ "${TRAVIS_NODE_VERSION}" = "0.9" ]; then nvm install --latest-npm 0.8 && npm install && nvm use "${TRAVIS_NODE_VERSION}"; else npm install; fi;' -script: - - 'if [ -n "${PRETEST-}" ]; then npm run pretest ; fi' - - 'if [ -n "${POSTTEST-}" ]; then npm run posttest ; fi' - - 'if [ -n "${COVERAGE-}" ]; then npm run coverage ; fi' - - 'if [ -n "${TEST-}" ]; then npm run tests-only ; fi' -sudo: false -env: - - TEST=true +import: + - ljharb/travis-ci:node/all.yml + - ljharb/travis-ci:node/pretest.yml + - ljharb/travis-ci:node/posttest.yml + - ljharb/travis-ci:node/coverage.yml matrix: - fast_finish: true - include: - - node_js: "lts/*" - env: PRETEST=true - - node_js: "4" - env: COVERAGE=true - - node_js: "11.11" - env: TEST=true ALLOW_FAILURE=true - - node_js: "11.10" - env: TEST=true ALLOW_FAILURE=true - - node_js: "11.9" - env: TEST=true ALLOW_FAILURE=true - - node_js: "11.8" - env: TEST=true ALLOW_FAILURE=true - - node_js: "11.7" - env: TEST=true ALLOW_FAILURE=true - - node_js: "11.6" - env: TEST=true ALLOW_FAILURE=true - - node_js: "11.5" - env: TEST=true ALLOW_FAILURE=true - - node_js: "11.4" - env: TEST=true ALLOW_FAILURE=true - - node_js: "11.3" - env: TEST=true ALLOW_FAILURE=true - - node_js: "11.2" - env: TEST=true ALLOW_FAILURE=true - - node_js: "11.1" - env: TEST=true ALLOW_FAILURE=true - - node_js: "11.0" - env: TEST=true ALLOW_FAILURE=true - - node_js: "10.14" - env: TEST=true ALLOW_FAILURE=true - - node_js: "10.13" - env: TEST=true ALLOW_FAILURE=true - - node_js: "10.12" - env: TEST=true ALLOW_FAILURE=true - - node_js: "10.11" - env: TEST=true ALLOW_FAILURE=true - - node_js: "10.10" - env: TEST=true ALLOW_FAILURE=true - - node_js: "10.9" - env: TEST=true ALLOW_FAILURE=true - - node_js: "10.8" - env: TEST=true ALLOW_FAILURE=true - - node_js: "10.7" - env: TEST=true ALLOW_FAILURE=true - - node_js: "10.6" - env: TEST=true ALLOW_FAILURE=true - - node_js: "10.5" - env: TEST=true ALLOW_FAILURE=true - - node_js: "10.4" - env: TEST=true ALLOW_FAILURE=true - - node_js: "10.3" - env: TEST=true ALLOW_FAILURE=true - - node_js: "10.2" - env: TEST=true ALLOW_FAILURE=true - - node_js: "10.1" - env: TEST=true ALLOW_FAILURE=true - - node_js: "10.0" - env: TEST=true ALLOW_FAILURE=true - - node_js: "9.10" - env: TEST=true ALLOW_FAILURE=true - - node_js: "9.9" - env: TEST=true ALLOW_FAILURE=true - - node_js: "9.8" - env: TEST=true ALLOW_FAILURE=true - - node_js: "9.7" - env: TEST=true ALLOW_FAILURE=true - - node_js: "9.6" - env: TEST=true ALLOW_FAILURE=true - - node_js: "9.5" - env: TEST=true ALLOW_FAILURE=true - - node_js: "9.4" - env: TEST=true ALLOW_FAILURE=true - - node_js: "9.3" - env: TEST=true ALLOW_FAILURE=true - - node_js: "9.2" - env: TEST=true ALLOW_FAILURE=true - - node_js: "9.1" - env: TEST=true ALLOW_FAILURE=true - - node_js: "9.0" - env: TEST=true ALLOW_FAILURE=true - - node_js: "8.14" - env: TEST=true ALLOW_FAILURE=true - - node_js: "8.13" - env: TEST=true ALLOW_FAILURE=true - - node_js: "8.12" - env: TEST=true ALLOW_FAILURE=true - - node_js: "8.11" - env: TEST=true ALLOW_FAILURE=true - - node_js: "8.10" - env: TEST=true ALLOW_FAILURE=true - - node_js: "8.9" - env: TEST=true ALLOW_FAILURE=true - - node_js: "8.8" - env: TEST=true ALLOW_FAILURE=true - - node_js: "8.7" - env: TEST=true ALLOW_FAILURE=true - - node_js: "8.6" - env: TEST=true ALLOW_FAILURE=true - - node_js: "8.5" - env: TEST=true ALLOW_FAILURE=true - - node_js: "8.4" - env: TEST=true ALLOW_FAILURE=true - - node_js: "8.3" - env: TEST=true ALLOW_FAILURE=true - - node_js: "8.2" - env: TEST=true ALLOW_FAILURE=true - - node_js: "8.1" - env: TEST=true ALLOW_FAILURE=true - - node_js: "8.0" - env: TEST=true ALLOW_FAILURE=true - - node_js: "7.9" - env: TEST=true ALLOW_FAILURE=true - - node_js: "7.8" - env: TEST=true ALLOW_FAILURE=true - - node_js: "7.7" - env: TEST=true ALLOW_FAILURE=true - - node_js: "7.6" - env: TEST=true ALLOW_FAILURE=true - - node_js: "7.5" - env: TEST=true ALLOW_FAILURE=true - - node_js: "7.4" - env: TEST=true ALLOW_FAILURE=true - - node_js: "7.3" - env: TEST=true ALLOW_FAILURE=true - - node_js: "7.2" - env: TEST=true ALLOW_FAILURE=true - - node_js: "7.1" - env: TEST=true ALLOW_FAILURE=true - - node_js: "7.0" - env: TEST=true ALLOW_FAILURE=true - - node_js: "6.16" - env: TEST=true ALLOW_FAILURE=true - - node_js: "6.15" - env: TEST=true ALLOW_FAILURE=true - - node_js: "6.14" - env: TEST=true ALLOW_FAILURE=true - - node_js: "6.13" - env: TEST=true ALLOW_FAILURE=true - - node_js: "6.12" - env: TEST=true ALLOW_FAILURE=true - - node_js: "6.11" - env: TEST=true ALLOW_FAILURE=true - - node_js: "6.10" - env: TEST=true ALLOW_FAILURE=true - - node_js: "6.9" - env: TEST=true ALLOW_FAILURE=true - - node_js: "6.8" - env: TEST=true ALLOW_FAILURE=true - - node_js: "6.7" - env: TEST=true ALLOW_FAILURE=true - - node_js: "6.6" - env: TEST=true ALLOW_FAILURE=true - - node_js: "6.5" - env: TEST=true ALLOW_FAILURE=true - - node_js: "6.4" - env: TEST=true ALLOW_FAILURE=true - - node_js: "6.3" - env: TEST=true ALLOW_FAILURE=true - - node_js: "6.2" - env: TEST=true ALLOW_FAILURE=true - - node_js: "6.1" - env: TEST=true ALLOW_FAILURE=true - - node_js: "6.0" - env: TEST=true ALLOW_FAILURE=true - - node_js: "5.11" - env: TEST=true ALLOW_FAILURE=true - - node_js: "5.10" - env: TEST=true ALLOW_FAILURE=true - - node_js: "5.9" - env: TEST=true ALLOW_FAILURE=true - - node_js: "5.8" - env: TEST=true ALLOW_FAILURE=true - - node_js: "5.7" - env: TEST=true ALLOW_FAILURE=true - - node_js: "5.6" - env: TEST=true ALLOW_FAILURE=true - - node_js: "5.5" - env: TEST=true ALLOW_FAILURE=true - - node_js: "5.4" - env: TEST=true ALLOW_FAILURE=true - - node_js: "5.3" - env: TEST=true ALLOW_FAILURE=true - - node_js: "5.2" - env: TEST=true ALLOW_FAILURE=true - - node_js: "5.1" - env: TEST=true ALLOW_FAILURE=true - - node_js: "5.0" - env: TEST=true ALLOW_FAILURE=true - - node_js: "4.8" - env: TEST=true ALLOW_FAILURE=true - - node_js: "4.7" - env: TEST=true ALLOW_FAILURE=true - - node_js: "4.6" - env: TEST=true ALLOW_FAILURE=true - - node_js: "4.5" - env: TEST=true ALLOW_FAILURE=true - - node_js: "4.4" - env: TEST=true ALLOW_FAILURE=true - - node_js: "4.3" - env: TEST=true ALLOW_FAILURE=true - - node_js: "4.2" - env: TEST=true ALLOW_FAILURE=true - - node_js: "4.1" - env: TEST=true ALLOW_FAILURE=true - - node_js: "4.0" - env: TEST=true ALLOW_FAILURE=true - - node_js: "iojs-v3.2" - env: TEST=true ALLOW_FAILURE=true - - node_js: "iojs-v3.1" - env: TEST=true ALLOW_FAILURE=true - - node_js: "iojs-v3.0" - env: TEST=true ALLOW_FAILURE=true - - node_js: "iojs-v2.4" - env: TEST=true ALLOW_FAILURE=true - - node_js: "iojs-v2.3" - env: TEST=true ALLOW_FAILURE=true - - node_js: "iojs-v2.2" - env: TEST=true ALLOW_FAILURE=true - - node_js: "iojs-v2.1" - env: TEST=true ALLOW_FAILURE=true - - node_js: "iojs-v2.0" - env: TEST=true ALLOW_FAILURE=true - - node_js: "iojs-v1.7" - env: TEST=true ALLOW_FAILURE=true - - node_js: "iojs-v1.6" - env: TEST=true ALLOW_FAILURE=true - - node_js: "iojs-v1.5" - env: TEST=true ALLOW_FAILURE=true - - node_js: "iojs-v1.4" - env: TEST=true ALLOW_FAILURE=true - - node_js: "iojs-v1.3" - env: TEST=true ALLOW_FAILURE=true - - node_js: "iojs-v1.2" - env: TEST=true ALLOW_FAILURE=true - - node_js: "iojs-v1.1" - env: TEST=true ALLOW_FAILURE=true - - node_js: "iojs-v1.0" - env: TEST=true ALLOW_FAILURE=true - - node_js: "0.11" - env: TEST=true ALLOW_FAILURE=true - - node_js: "0.9" - env: TEST=true ALLOW_FAILURE=true - - node_js: "0.4" - env: TEST=true ALLOW_FAILURE=true allow_failures: - - os: osx - - env: TEST=true ALLOW_FAILURE=true - - node_js: "0.6" # temporarily allow this to fail - env: COVERAGE=true # temporarily allow this to fail diff --git a/package.json b/package.json index f2c093f1..3b6e3861 100644 --- a/package.json +++ b/package.json @@ -49,6 +49,7 @@ "pretest": "npm run --silent readme && npm run --silent lint", "test": "npm run --silent coverage", "tests-only": "node test", + "posttest": "npx aud --production", "readme": "evalmd README.md", "postlint": "eclint check * lib/* test/*", "lint": "eslint lib/*.js test/*.js", From 0a94735433238c2bb4c3464b1b5435eb77e95281 Mon Sep 17 00:00:00 2001 From: Roman Usherenko Date: Sun, 8 Sep 2019 10:27:42 +0300 Subject: [PATCH 123/278] [Tests] `parse`: add passing `arrayFormat` tests --- test/parse.js | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/test/parse.js b/test/parse.js index cafa7b2a..78d091b2 100644 --- a/test/parse.js +++ b/test/parse.js @@ -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' } }); From 99a81819607342abfc0d8bc34d1d370676be3dc6 Mon Sep 17 00:00:00 2001 From: Mohamed Omar Date: Fri, 4 Oct 2019 02:57:00 +0200 Subject: [PATCH 124/278] [fix] `parse`: with comma true, do not split non-string values (#334) --- lib/parse.js | 2 +- test/parse.js | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/lib/parse.js b/lib/parse.js index 606b5829..f0c2a080 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -83,7 +83,7 @@ var parseValues = function parseQueryStringValues(str, options) { val = interpretNumericEntities(val); } - if (val && options.comma && val.indexOf(',') > -1) { + if (val && typeof val === 'string' && options.comma && val.indexOf(',') > -1) { val = val.split(','); } diff --git a/test/parse.js b/test/parse.js index 78d091b2..1563b5f6 100644 --- a/test/parse.js +++ b/test/parse.js @@ -400,6 +400,20 @@ test('parse()', function (t) { 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 an object in dot notation', function (st) { var input = { 'user.name': { 'pop[bob]': 3 }, From 77c28462557443b5c2dfb54e29eb96d0c7810068 Mon Sep 17 00:00:00 2001 From: Mohamed Omar Date: Fri, 4 Oct 2019 05:50:34 +0200 Subject: [PATCH 125/278] [Fix] `parse`: with comma true, handle field that holds an array of arrays (#335) Fixes #327. --- lib/parse.js | 5 +++++ test/parse.js | 9 +++++++++ 2 files changed, 14 insertions(+) diff --git a/lib/parse.js b/lib/parse.js index f0c2a080..3283fb11 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -3,6 +3,7 @@ var utils = require('./utils'); var has = Object.prototype.hasOwnProperty; +var isArray = Array.isArray; var defaults = { allowDots: false, @@ -87,6 +88,10 @@ var parseValues = function parseQueryStringValues(str, options) { val = val.split(','); } + if (part.indexOf('[]=') > -1) { + val = isArray(val) ? [val] : val; + } + if (has.call(obj, key)) { obj[key] = utils.combine(obj[key], val); } else { diff --git a/test/parse.js b/test/parse.js index 1563b5f6..101e5160 100644 --- a/test/parse.js +++ b/test/parse.js @@ -414,6 +414,15 @@ test('parse()', function (t) { 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 an object in dot notation', function (st) { var input = { 'user.name': { 'pop[bob]': 3 }, From d9fcf9895a56eba6186d2c9e07c402a12f7fddb7 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Thu, 7 Nov 2019 15:05:08 -0800 Subject: [PATCH 126/278] [meta] add `funding` field --- package.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/package.json b/package.json index 3b6e3861..57b312ca 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,9 @@ "type": "git", "url": "https://github.com/ljharb/qs.git" }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + }, "main": "lib/index.js", "contributors": [ { From 705b3382bb4ed2a89d7c2c8cc980f38a34054bc7 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Fri, 6 Dec 2019 14:37:31 -0800 Subject: [PATCH 127/278] [meta] add tidelift marketing copy --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index cb47d60a..cae83b48 100644 --- a/README.md +++ b/README.md @@ -557,6 +557,12 @@ assert.equal(qs.stringify({ a: 'b c' }, { format : 'RFC1738' }), 'a=b+c'); 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) + [1]: https://npmjs.org/package/qs [2]: http://versionbadg.es/ljharb/qs.svg [3]: https://api.travis-ci.org/ljharb/qs.svg From d5c46dfab5302c1bf6d62a0473a7a6d68ecd8331 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Mon, 13 Jan 2020 21:57:54 -0800 Subject: [PATCH 128/278] [actions] add automatic rebasing / merge commit blocking See https://github.com/ljharb/es-abstract/pull/74 --- .github/workflows/rebase.yml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 .github/workflows/rebase.yml diff --git a/.github/workflows/rebase.yml b/.github/workflows/rebase.yml new file mode 100644 index 00000000..436cb79d --- /dev/null +++ b/.github/workflows/rebase.yml @@ -0,0 +1,15 @@ +name: Automatic Rebase + +on: [pull_request] + +jobs: + _: + name: "Automatic Rebase" + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v1 + - uses: ljharb/rebase@master + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From 85a3d325c7b170bc46d70dfc10c3066f22c71411 Mon Sep 17 00:00:00 2001 From: Alexander Yefanov Date: Sun, 15 Mar 2020 10:45:46 +0100 Subject: [PATCH 129/278] [Fix] `parse`: Fix parsing array from object with `comma` true (#359) Fixes #357 --- lib/parse.js | 14 ++++++++++---- test/parse.js | 6 ++++++ 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/lib/parse.js b/lib/parse.js index 3283fb11..7c531de4 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -29,6 +29,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 @@ -84,9 +92,7 @@ var parseValues = function parseQueryStringValues(str, options) { val = interpretNumericEntities(val); } - if (val && typeof val === 'string' && options.comma && val.indexOf(',') > -1) { - val = val.split(','); - } + val = parseArrayValue(val, options); if (part.indexOf('[]=') > -1) { val = isArray(val) ? [val] : val; @@ -103,7 +109,7 @@ var parseValues = function parseQueryStringValues(str, options) { }; var parseObject = function (chain, val, options) { - var leaf = val; + var leaf = parseArrayValue(val, options); for (var i = chain.length - 1; i >= 0; --i) { var obj; diff --git a/test/parse.js b/test/parse.js index 101e5160..b6d6d364 100644 --- a/test/parse.js +++ b/test/parse.js @@ -400,6 +400,12 @@ 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))) { From 47247f4ec3b527e5f71551fb6af5434672ecf756 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Mon, 23 Mar 2020 21:33:01 -0700 Subject: [PATCH 130/278] v6.8.1 --- CHANGELOG.md | 13 +++++++++++++ dist/qs.js | 43 +++++++++++++++++++++++++++---------------- package.json | 2 +- 3 files changed, 41 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f25205ba..106c0c16 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,16 @@ +## **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 diff --git a/dist/qs.js b/dist/qs.js index 08335520..70256206 100644 --- a/dist/qs.js +++ b/dist/qs.js @@ -45,6 +45,7 @@ module.exports = { var utils = require('./utils'); var has = Object.prototype.hasOwnProperty; +var isArray = Array.isArray; var defaults = { allowDots: false, @@ -70,6 +71,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 @@ -125,8 +134,10 @@ var parseValues = function parseQueryStringValues(str, options) { val = interpretNumericEntities(val); } - if (val && options.comma && val.indexOf(',') > -1) { - val = val.split(','); + val = parseArrayValue(val, options); + + if (part.indexOf('[]=') > -1) { + val = isArray(val) ? [val] : val; } if (has.call(obj, key)) { @@ -140,7 +151,7 @@ var parseValues = function parseQueryStringValues(str, options) { }; var parseObject = function (chain, val, options) { - var leaf = val; + var leaf = parseArrayValue(val, options); for (var i = chain.length - 1; i >= 0; --i) { var obj; @@ -238,7 +249,7 @@ 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; @@ -292,14 +303,14 @@ 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; } }; @@ -326,14 +337,14 @@ var defaults = { 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 isNonNullishPrimitive = function isNonNullishPrimitive(v) { // eslint-disable-line func-name-matching +var isNonNullishPrimitive = function isNonNullishPrimitive(v) { return typeof v === 'string' || typeof v === 'number' || typeof v === 'boolean' @@ -341,7 +352,7 @@ var isNonNullishPrimitive = function isNonNullishPrimitive(v) { // eslint-disabl || typeof v === 'bigint'; // eslint-disable-line valid-typeof }; -var stringify = function stringify( // eslint-disable-line func-name-matching +var stringify = function stringify( object, prefix, generateArrayPrefix, @@ -620,7 +631,7 @@ var merge = function merge(target, source, options) { target.push(source); } else if (target && typeof target === 'object') { if ((options && (options.plainObjects || options.allowPrototypes)) || !has.call(Object.prototype, source)) { - target[source] = true; + target[source] = true; // eslint-disable-line no-param-reassign } } else { return [target, source]; @@ -643,12 +654,12 @@ var merge = function merge(target, source, options) { if (has.call(target, i)) { var targetItem = target[i]; if (targetItem && typeof targetItem === 'object' && item && typeof item === 'object') { - target[i] = merge(targetItem, item, options); + target[i] = merge(targetItem, item, options); // eslint-disable-line no-param-reassign } else { target.push(item); } } else { - target[i] = item; + target[i] = item; // eslint-disable-line no-param-reassign } }); return target; @@ -658,9 +669,9 @@ var merge = function merge(target, source, options) { var value = source[key]; if (has.call(acc, key)) { - acc[key] = merge(acc[key], value, options); + acc[key] = merge(acc[key], value, options); // eslint-disable-line no-param-reassign } else { - acc[key] = value; + acc[key] = value; // eslint-disable-line no-param-reassign } return acc; }, mergeTarget); @@ -668,7 +679,7 @@ var merge = function merge(target, source, options) { var assign = function assignSingleSource(target, source) { return Object.keys(source).reduce(function (acc, key) { - acc[key] = source[key]; + acc[key] = source[key]; // eslint-disable-line no-param-reassign return acc; }, target); }; diff --git a/package.json b/package.json index 57b312ca..82e40b62 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "qs", "description": "A querystring parser that supports nesting and arrays, with a depth limit", "homepage": "https://github.com/ljharb/qs", - "version": "6.8.0", + "version": "6.8.1", "repository": { "type": "git", "url": "https://github.com/ljharb/qs.git" From 0c358d3600166771103c9d0b87fbfaaee2c9cd25 Mon Sep 17 00:00:00 2001 From: Mohamed Omar Date: Fri, 18 Oct 2019 19:41:30 +0200 Subject: [PATCH 131/278] [Fix] parses comma delimited array while having percent-encoded comma treated as normal text (#336) Co-authored-by: Mohamed Omar Co-authored-by: Quentin de Longraye --- lib/parse.js | 10 +++++++++- test/parse.js | 8 ++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/lib/parse.js b/lib/parse.js index 7c531de4..98dbf716 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -85,7 +85,15 @@ var parseValues = function parseQueryStringValues(str, options) { 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); + var encodedVal = part.slice(pos + 1); + if (options.comma && encodedVal.indexOf(',') !== -1) { + val = encodedVal.split(',') + .map(function (encodedFragment) { + return options.decoder(encodedFragment, defaults.decoder, charset); + }); + } else { + val = options.decoder(encodedVal, defaults.decoder, charset); + } } if (val && options.interpretNumericEntities && charset === 'iso-8859-1') { diff --git a/test/parse.js b/test/parse.js index b6d6d364..ab8aadbc 100644 --- a/test/parse.js +++ b/test/parse.js @@ -429,6 +429,14 @@ test('parse()', function (t) { 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 }, From 0ece6d886a6e59589f0b5e753e1d0a3c98bc1b3c Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Tue, 24 Mar 2020 20:53:21 -0700 Subject: [PATCH 132/278] [Fix] proper comma parsing of URL-encoded commas (#361) Fixes #311. Followup to #336. --- lib/parse.js | 40 +++++++++++++++++++++++----------------- test/parse.js | 2 +- 2 files changed, 24 insertions(+), 18 deletions(-) diff --git a/lib/parse.js b/lib/parse.js index 98dbf716..533e884b 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -37,6 +37,17 @@ var parseArrayValue = function (val, options) { return val; }; +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); +}; + // 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 @@ -85,23 +96,18 @@ var parseValues = function parseQueryStringValues(str, options) { val = options.strictNullHandling ? null : ''; } else { key = options.decoder(part.slice(0, pos), defaults.decoder, charset); - var encodedVal = part.slice(pos + 1); - if (options.comma && encodedVal.indexOf(',') !== -1) { - val = encodedVal.split(',') - .map(function (encodedFragment) { - return options.decoder(encodedFragment, defaults.decoder, charset); - }); - } else { - val = options.decoder(encodedVal, defaults.decoder, charset); - } + val = maybeMap( + parseArrayValue(part.slice(pos + 1), options), + function (encodedVal) { + return options.decoder(encodedVal, defaults.decoder, charset); + } + ); } if (val && options.interpretNumericEntities && charset === 'iso-8859-1') { val = interpretNumericEntities(val); } - val = parseArrayValue(val, options); - if (part.indexOf('[]=') > -1) { val = isArray(val) ? [val] : val; } @@ -116,8 +122,8 @@ var parseValues = function parseQueryStringValues(str, options) { return obj; }; -var parseObject = function (chain, val, options) { - var leaf = parseArrayValue(val, options); +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; @@ -145,13 +151,13 @@ var parseObject = function (chain, val, options) { } } - leaf = obj; + leaf = obj; // eslint-disable-line no-param-reassign } return leaf; }; -var parseKeys = function parseQueryStringKeys(givenKey, val, options) { +var parseKeys = function parseQueryStringKeys(givenKey, val, options, valuesParsed) { if (!givenKey) { return; } @@ -202,7 +208,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) { @@ -254,7 +260,7 @@ 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); } diff --git a/test/parse.js b/test/parse.js index ab8aadbc..0e440f42 100644 --- a/test/parse.js +++ b/test/parse.js @@ -430,7 +430,7 @@ test('parse()', function (t) { }); 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%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'] }); From 808b0b233e9408e0b5a7d7d19eb01093944240c5 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Wed, 25 Mar 2020 12:20:19 -0700 Subject: [PATCH 133/278] v6.8.2 --- CHANGELOG.md | 4 ++++ dist/qs.js | 32 +++++++++++++++++++++++--------- package.json | 2 +- 3 files changed, 28 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 106c0c16..26e48e96 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## **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) diff --git a/dist/qs.js b/dist/qs.js index 70256206..869cdae7 100644 --- a/dist/qs.js +++ b/dist/qs.js @@ -79,6 +79,17 @@ var parseArrayValue = function (val, options) { return val; }; +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); +}; + // 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 @@ -127,15 +138,18 @@ var parseValues = function parseQueryStringValues(str, options) { 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); + val = maybeMap( + parseArrayValue(part.slice(pos + 1), options), + function (encodedVal) { + return options.decoder(encodedVal, defaults.decoder, charset); + } + ); } if (val && options.interpretNumericEntities && charset === 'iso-8859-1') { val = interpretNumericEntities(val); } - val = parseArrayValue(val, options); - if (part.indexOf('[]=') > -1) { val = isArray(val) ? [val] : val; } @@ -150,8 +164,8 @@ var parseValues = function parseQueryStringValues(str, options) { return obj; }; -var parseObject = function (chain, val, options) { - var leaf = parseArrayValue(val, options); +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; @@ -179,13 +193,13 @@ var parseObject = function (chain, val, options) { } } - leaf = obj; + leaf = obj; // eslint-disable-line no-param-reassign } return leaf; }; -var parseKeys = function parseQueryStringKeys(givenKey, val, options) { +var parseKeys = function parseQueryStringKeys(givenKey, val, options, valuesParsed) { if (!givenKey) { return; } @@ -236,7 +250,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) { @@ -288,7 +302,7 @@ 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); } diff --git a/package.json b/package.json index 82e40b62..8b04708d 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "qs", "description": "A querystring parser that supports nesting and arrays, with a depth limit", "homepage": "https://github.com/ljharb/qs", - "version": "6.8.1", + "version": "6.8.2", "repository": { "type": "git", "url": "https://github.com/ljharb/qs.git" From 7db8e63e76e743230c0433c775f2a20ebfbfafc6 Mon Sep 17 00:00:00 2001 From: Yipeng Zhao Date: Sun, 9 Jun 2019 22:47:25 +0800 Subject: [PATCH 134/278] [Refactor] `stringify`: reduce branching --- lib/stringify.js | 55 ++++++++++++++++++------------------------------ 1 file changed, 21 insertions(+), 34 deletions(-) diff --git a/lib/stringify.js b/lib/stringify.js index c80c0236..9380c99a 100644 --- a/lib/stringify.js +++ b/lib/stringify.js @@ -110,44 +110,31 @@ var stringify = function stringify( for (var i = 0; i < objKeys.length; ++i) { var key = objKeys[i]; + var 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 + ']'); + + pushToArray(values, stringify( + value, + keyPrefix, + generateArrayPrefix, + strictNullHandling, + skipNulls, + encoder, + filter, + sort, + allowDots, + serializeDate, + formatter, + encodeValuesOnly, + charset + )); } return values; From 8e014a7b1749ccec52104121950e7b0d251caa78 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Sun, 3 May 2020 14:38:57 -0700 Subject: [PATCH 135/278] v6.9.4 --- CHANGELOG.md | 6 ++++ dist/qs.js | 87 ++++++++++++++++++++++++---------------------------- package.json | 2 +- 3 files changed, 47 insertions(+), 48 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f09f6942..d18060b2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +## **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) diff --git a/dist/qs.js b/dist/qs.js index 752629db..a6204f25 100644 --- a/dist/qs.js +++ b/dist/qs.js @@ -79,17 +79,6 @@ var parseArrayValue = function (val, options) { return val; }; -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); -}; - // 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 @@ -138,7 +127,7 @@ var parseValues = function parseQueryStringValues(str, options) { val = options.strictNullHandling ? null : ''; } else { key = options.decoder(part.slice(0, pos), defaults.decoder, charset, 'key'); - val = maybeMap( + val = utils.maybeMap( parseArrayValue(part.slice(pos + 1), options), function (encodedVal) { return options.decoder(encodedVal, defaults.decoder, charset, 'value'); @@ -387,7 +376,12 @@ var stringify = function stringify( } 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; + }).join(','); } if (obj === null) { @@ -422,44 +416,31 @@ var stringify = function stringify( for (var i = 0; i < objKeys.length; ++i) { var key = objKeys[i]; + var 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 + ']'); + + pushToArray(values, stringify( + value, + keyPrefix, + generateArrayPrefix, + strictNullHandling, + skipNulls, + encoder, + filter, + sort, + allowDots, + serializeDate, + formatter, + encodeValuesOnly, + charset + )); } return values; @@ -816,6 +797,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, @@ -825,6 +817,7 @@ module.exports = { encode: encode, isBuffer: isBuffer, isRegExp: isRegExp, + maybeMap: maybeMap, merge: merge }; diff --git a/package.json b/package.json index 9e8f2480..475ed630 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "qs", "description": "A querystring parser that supports nesting and arrays, with a depth limit", "homepage": "https://github.com/ljharb/qs", - "version": "6.9.3", + "version": "6.9.4", "repository": { "type": "git", "url": "https://github.com/ljharb/qs.git" From af9bf4812b9ff0c943975d6db1977f4879b976ae Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Fri, 19 Jun 2020 15:45:34 -0700 Subject: [PATCH 136/278] [Dev Deps] update `eslint`, `@ljharb/eslint-config`, `mkdirp`, `object-inspect`, `tape`; add `aud` --- package.json | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 475ed630..6f702069 100644 --- a/package.json +++ b/package.json @@ -31,21 +31,22 @@ }, "dependencies": {}, "devDependencies": { - "@ljharb/eslint-config": "^16.0.0", + "@ljharb/eslint-config": "^17.1.0", + "aud": "^1.1.2", "browserify": "^16.5.1", "covert": "^1.1.1", "eclint": "^2.8.1", - "eslint": "^6.8.0", + "eslint": "^7.3.0", "evalmd": "^0.0.19", "for-each": "^0.3.3", "has-symbols": "^1.0.1", "iconv-lite": "^0.5.1", - "mkdirp": "^0.5.4", - "object-inspect": "^1.7.0", + "mkdirp": "^0.5.5", + "object-inspect": "^1.8.0", "qs-iconv": "^1.0.4", "safe-publish-latest": "^1.1.4", "safer-buffer": "^2.1.2", - "tape": "^5.0.0" + "tape": "^5.0.1" }, "scripts": { "prepublish": "safe-publish-latest && npm run dist", From deada9450273f397a011f402b91a3a4216a62f0b Mon Sep 17 00:00:00 2001 From: Yipeng Zhao Date: Sun, 9 Jun 2019 22:52:01 +0800 Subject: [PATCH 137/278] [Fix] `stringify`: fix arrayFormat comma with empty array/objects --- lib/stringify.js | 9 ++++++--- test/stringify.js | 21 +++++++++++++++++++++ 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/lib/stringify.js b/lib/stringify.js index 5d22260c..da5380a8 100644 --- a/lib/stringify.js +++ b/lib/stringify.js @@ -80,7 +80,7 @@ var stringify = function stringify( return serializeDate(value); } return value; - }).join(','); + }); } if (obj === null) { @@ -106,7 +106,10 @@ var stringify = function stringify( } var objKeys; - if (isArray(filter)) { + if (generateArrayPrefix === 'comma' && isArray(obj)) { + // we need to join elements in + objKeys = [{ value: obj.length > 0 ? obj.join(',') || null : undefined }]; + } else if (isArray(filter)) { objKeys = filter; } else { var keys = Object.keys(obj); @@ -115,7 +118,7 @@ var stringify = function stringify( for (var i = 0; i < objKeys.length; ++i) { var key = objKeys[i]; - var value = obj[key]; + var value = typeof key === 'object' && key.value !== undefined ? key.value : obj[key]; if (skipNulls && value === null) { continue; diff --git a/test/stringify.js b/test/stringify.js index 49692184..cbd90b2c 100644 --- a/test/stringify.js +++ b/test/stringify.js @@ -336,6 +336,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'; From 75f7af8f31ca95e0197dba456bb6f1f006a6283b Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Wed, 26 Jun 2019 00:22:20 -0700 Subject: [PATCH 138/278] =?UTF-8?q?Clean=20up=20license=20text=20so=20it?= =?UTF-8?q?=E2=80=99s=20properly=20detected=20as=20BSD-3-Clause?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- LICENSE | 28 ---------------------------- LICENSE.md | 29 +++++++++++++++++++++++++++++ 2 files changed, 29 insertions(+), 28 deletions(-) delete mode 100644 LICENSE create mode 100644 LICENSE.md diff --git a/LICENSE b/LICENSE deleted file mode 100644 index d4569487..00000000 --- a/LICENSE +++ /dev/null @@ -1,28 +0,0 @@ -Copyright (c) 2014 Nathan LaFreniere and other 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: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * 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. - * The names of any contributors may not 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 HOLDERS AND 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. - - * * * - -The complete list of contributors can be found at: https://github.com/hapijs/qs/graphs/contributors diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 00000000..fecf6b69 --- /dev/null +++ b/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. From 16148bd636339d514d54654e976ccfb72b4abb45 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Wed, 26 Jun 2019 10:06:17 -0700 Subject: [PATCH 139/278] add FUNDING.yml This is an experiment; I intend to use 100% of funds to support the OSS community and my OSS projects' costs. --- .github/FUNDING.yml | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 .github/FUNDING.yml diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 00000000..0355f4f5 --- /dev/null +++ b/.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 From 8e8e1ef58a348deff687f05ba725122150314f72 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Wed, 26 Jun 2019 00:22:20 -0700 Subject: [PATCH 140/278] =?UTF-8?q?Clean=20up=20license=20text=20so=20it?= =?UTF-8?q?=E2=80=99s=20properly=20detected=20as=20BSD-3-Clause?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- LICENSE | 28 ---------------------------- LICENSE.md | 29 +++++++++++++++++++++++++++++ 2 files changed, 29 insertions(+), 28 deletions(-) delete mode 100644 LICENSE create mode 100644 LICENSE.md diff --git a/LICENSE b/LICENSE deleted file mode 100644 index d4569487..00000000 --- a/LICENSE +++ /dev/null @@ -1,28 +0,0 @@ -Copyright (c) 2014 Nathan LaFreniere and other 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: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * 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. - * The names of any contributors may not 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 HOLDERS AND 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. - - * * * - -The complete list of contributors can be found at: https://github.com/hapijs/qs/graphs/contributors diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 00000000..fecf6b69 --- /dev/null +++ b/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. From 51b8a0b1b213596dd1702b837f5e7dec2229793d Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Wed, 26 Jun 2019 10:06:17 -0700 Subject: [PATCH 141/278] add FUNDING.yml This is an experiment; I intend to use 100% of funds to support the OSS community and my OSS projects' costs. --- .github/FUNDING.yml | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 .github/FUNDING.yml diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 00000000..0355f4f5 --- /dev/null +++ b/.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 From 5639c20ce0a7c1332200a3181339331483e5a3a1 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Wed, 26 Jun 2019 00:22:20 -0700 Subject: [PATCH 142/278] =?UTF-8?q?Clean=20up=20license=20text=20so=20it?= =?UTF-8?q?=E2=80=99s=20properly=20detected=20as=20BSD-3-Clause?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- LICENSE | 28 ---------------------------- LICENSE.md | 29 +++++++++++++++++++++++++++++ 2 files changed, 29 insertions(+), 28 deletions(-) delete mode 100644 LICENSE create mode 100644 LICENSE.md diff --git a/LICENSE b/LICENSE deleted file mode 100644 index d4569487..00000000 --- a/LICENSE +++ /dev/null @@ -1,28 +0,0 @@ -Copyright (c) 2014 Nathan LaFreniere and other 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: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * 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. - * The names of any contributors may not 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 HOLDERS AND 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. - - * * * - -The complete list of contributors can be found at: https://github.com/hapijs/qs/graphs/contributors diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 00000000..fecf6b69 --- /dev/null +++ b/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. From 2ad0ccbb2e20ed56085cbbdafb725aa055b34938 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Wed, 26 Jun 2019 10:06:17 -0700 Subject: [PATCH 143/278] add FUNDING.yml This is an experiment; I intend to use 100% of funds to support the OSS community and my OSS projects' costs. --- .github/FUNDING.yml | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 .github/FUNDING.yml diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 00000000..0355f4f5 --- /dev/null +++ b/.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 From 01f586762bde84751d097fc49f63454cd818231b Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Wed, 26 Jun 2019 11:21:41 -0700 Subject: [PATCH 144/278] readme: add security note --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 8590cfd3..cb47d60a 100644 --- a/README.md +++ b/README.md @@ -553,6 +553,10 @@ 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. + [1]: https://npmjs.org/package/qs [2]: http://versionbadg.es/ljharb/qs.svg [3]: https://api.travis-ci.org/ljharb/qs.svg From 1b039693b54d83c6fd437fedcab235b359c5410a Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Wed, 10 Jul 2019 23:21:36 -0700 Subject: [PATCH 145/278] [Dev Deps] update `eslint`, `iconv-lite`, `browserify`, `tape` --- dist/qs.js | 240 ++++++++++++++++++++++++++++++++------------------ package.json | 6 +- test/parse.js | 2 + 3 files changed, 159 insertions(+), 89 deletions(-) diff --git a/dist/qs.js b/dist/qs.js index b9482991..70e75ef9 100644 --- a/dist/qs.js +++ b/dist/qs.js @@ -4,21 +4,29 @@ var replace = String.prototype.replace; var percentTwenties = /%20/g; -module.exports = { - 'default': 'RFC3986', - formatters: { - RFC1738: function (value) { - return replace.call(value, percentTwenties, '+'); - }, - RFC3986: function (value) { - return value; - } - }, +var util = require('./utils'); + +var Format = { RFC1738: 'RFC1738', RFC3986: 'RFC3986' }; -},{}],2:[function(require,module,exports){ +module.exports = util.assign( + { + 'default': Format.RFC3986, + formatters: { + RFC1738: function (value) { + return replace.call(value, percentTwenties, '+'); + }, + RFC3986: function (value) { + return String(value); + } + } + }, + Format +); + +},{"./utils":5}],2:[function(require,module,exports){ 'use strict'; var stringify = require('./stringify'); @@ -44,6 +52,7 @@ var defaults = { arrayLimit: 20, charset: 'utf-8', charsetSentinel: false, + comma: false, decoder: utils.decode, delimiter: '&', depth: 5, @@ -115,6 +124,11 @@ var parseValues = function parseQueryStringValues(str, options) { if (val && options.interpretNumericEntities && charset === 'iso-8859-1') { val = interpretNumericEntities(val); } + + if (val && options.comma && val.indexOf(',') > -1) { + val = val.split(','); + } + if (has.call(obj, key)) { obj[key] = utils.combine(obj[key], val); } else { @@ -214,31 +228,41 @@ var parseKeys = function parseQueryStringKeys(givenKey, val, options) { return parseObject(keys, val, options); }; -module.exports = function (str, opts) { - var options = opts ? utils.assign({}, opts) : {}; +var normalizeParseOptions = function normalizeParseOptions(opts) { + if (!opts) { + return defaults; + } - if (options.decoder !== null && options.decoder !== undefined && typeof options.decoder !== 'function') { + if (opts.decoder !== null && opts.decoder !== undefined && typeof opts.decoder !== 'function') { throw new TypeError('Decoder has to be a function.'); } - options.ignoreQueryPrefix = options.ignoreQueryPrefix === true; - options.delimiter = typeof options.delimiter === 'string' || utils.isRegExp(options.delimiter) ? options.delimiter : defaults.delimiter; - options.depth = typeof options.depth === 'number' ? options.depth : defaults.depth; - options.arrayLimit = typeof options.arrayLimit === 'number' ? options.arrayLimit : defaults.arrayLimit; - options.parseArrays = options.parseArrays !== false; - options.decoder = typeof options.decoder === 'function' ? options.decoder : defaults.decoder; - options.allowDots = typeof options.allowDots === 'undefined' ? defaults.allowDots : !!options.allowDots; - options.plainObjects = typeof options.plainObjects === 'boolean' ? options.plainObjects : defaults.plainObjects; - options.allowPrototypes = typeof options.allowPrototypes === 'boolean' ? options.allowPrototypes : defaults.allowPrototypes; - options.parameterLimit = typeof options.parameterLimit === 'number' ? options.parameterLimit : defaults.parameterLimit; - options.strictNullHandling = typeof options.strictNullHandling === 'boolean' ? options.strictNullHandling : defaults.strictNullHandling; - - if (typeof options.charset !== 'undefined' && options.charset !== 'utf-8' && options.charset !== 'iso-8859-1') { + 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'); } - if (typeof options.charset === 'undefined') { - options.charset = defaults.charset; - } + 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, + 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, + 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, + strictNullHandling: typeof opts.strictNullHandling === 'boolean' ? opts.strictNullHandling : defaults.strictNullHandling + }; +}; + +module.exports = function (str, opts) { + var options = normalizeParseOptions(opts); if (str === '' || str === null || typeof str === 'undefined') { return options.plainObjects ? Object.create(null) : {}; @@ -264,11 +288,13 @@ module.exports = function (str, opts) { 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 return prefix + '[]'; }, + comma: 'comma', indices: function indices(prefix, key) { // eslint-disable-line func-name-matching return prefix + '[' + key + ']'; }, @@ -285,6 +311,7 @@ var pushToArray = function (arr, valueOrArray) { var toISO = Date.prototype.toISOString; +var defaultFormat = formats['default']; var defaults = { addQueryPrefix: false, allowDots: false, @@ -294,6 +321,8 @@ var defaults = { encode: true, encoder: utils.encode, encodeValuesOnly: false, + format: defaultFormat, + formatter: formats.formatters[defaultFormat], // deprecated indices: false, serializeDate: function serializeDate(date) { // eslint-disable-line func-name-matching @@ -303,6 +332,14 @@ var defaults = { strictNullHandling: false }; +var isNonNullishPrimitive = function isNonNullishPrimitive(v) { // eslint-disable-line func-name-matching + return typeof v === 'string' + || typeof v === 'number' + || typeof v === 'boolean' + || typeof v === 'symbol' + || typeof v === 'bigint'; // eslint-disable-line valid-typeof +}; + var stringify = function stringify( // eslint-disable-line func-name-matching object, prefix, @@ -323,6 +360,8 @@ var stringify = function stringify( // eslint-disable-line func-name-matching obj = filter(prefix, obj); } else if (obj instanceof Date) { obj = serializeDate(obj); + } else if (generateArrayPrefix === 'comma' && isArray(obj)) { + obj = obj.join(','); } if (obj === null) { @@ -333,7 +372,7 @@ var stringify = function stringify( // eslint-disable-line func-name-matching 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))]; @@ -348,7 +387,7 @@ var stringify = function stringify( // eslint-disable-line func-name-matching } var objKeys; - if (Array.isArray(filter)) { + if (isArray(filter)) { objKeys = filter; } else { var keys = Object.keys(obj); @@ -362,10 +401,10 @@ var stringify = function stringify( // eslint-disable-line func-name-matching continue; } - if (Array.isArray(obj)) { + if (isArray(obj)) { pushToArray(values, stringify( obj[key], - generateArrayPrefix(prefix, key), + typeof generateArrayPrefix === 'function' ? generateArrayPrefix(prefix, key) : prefix, generateArrayPrefix, strictNullHandling, skipNulls, @@ -400,41 +439,63 @@ var stringify = function stringify( // eslint-disable-line func-name-matching return values; }; -module.exports = function (object, opts) { - var obj = object; - var options = opts ? utils.assign({}, opts) : {}; +var normalizeStringifyOptions = function normalizeStringifyOptions(opts) { + if (!opts) { + return defaults; + } - if (options.encoder !== null && options.encoder !== undefined && typeof options.encoder !== 'function') { + if (opts.encoder !== null && opts.encoder !== undefined && typeof opts.encoder !== 'function') { throw new TypeError('Encoder has to be a function.'); } - var delimiter = typeof options.delimiter === 'undefined' ? defaults.delimiter : options.delimiter; - var strictNullHandling = typeof options.strictNullHandling === 'boolean' ? options.strictNullHandling : defaults.strictNullHandling; - var skipNulls = typeof options.skipNulls === 'boolean' ? options.skipNulls : defaults.skipNulls; - var encode = typeof options.encode === 'boolean' ? options.encode : defaults.encode; - var encoder = typeof options.encoder === 'function' ? options.encoder : defaults.encoder; - var sort = typeof options.sort === 'function' ? options.sort : null; - var allowDots = typeof options.allowDots === 'undefined' ? defaults.allowDots : !!options.allowDots; - var serializeDate = typeof options.serializeDate === 'function' ? options.serializeDate : defaults.serializeDate; - var encodeValuesOnly = typeof options.encodeValuesOnly === 'boolean' ? options.encodeValuesOnly : defaults.encodeValuesOnly; - var charset = options.charset || defaults.charset; - if (typeof options.charset !== 'undefined' && options.charset !== 'utf-8' && options.charset !== 'iso-8859-1') { - throw new Error('The charset option must be either utf-8, iso-8859-1, or undefined'); + 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'); } - if (typeof options.format === 'undefined') { - options.format = formats['default']; - } else if (!Object.prototype.hasOwnProperty.call(formats.formatters, options.format)) { - throw new TypeError('Unknown format option provided.'); - } - var formatter = formats.formatters[options.format]; + 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; + } + + return { + addQueryPrefix: typeof opts.addQueryPrefix === 'boolean' ? opts.addQueryPrefix : defaults.addQueryPrefix, + allowDots: typeof opts.allowDots === 'undefined' ? defaults.allowDots : !!opts.allowDots, + charset: charset, + charsetSentinel: typeof opts.charsetSentinel === 'boolean' ? opts.charsetSentinel : defaults.charsetSentinel, + delimiter: typeof opts.delimiter === 'undefined' ? defaults.delimiter : opts.delimiter, + encode: typeof opts.encode === 'boolean' ? opts.encode : defaults.encode, + encoder: typeof opts.encoder === 'function' ? opts.encoder : defaults.encoder, + encodeValuesOnly: typeof opts.encodeValuesOnly === 'boolean' ? opts.encodeValuesOnly : defaults.encodeValuesOnly, + filter: filter, + 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 (Array.isArray(options.filter)) { + } else if (isArray(options.filter)) { filter = options.filter; objKeys = filter; } @@ -446,10 +507,10 @@ module.exports = function (object, opts) { } var arrayFormat; - if (options.arrayFormat in arrayPrefixGenerators) { - arrayFormat = options.arrayFormat; - } else if ('indices' in options) { - arrayFormat = options.indices ? 'indices' : 'repeat'; + if (opts && opts.arrayFormat in arrayPrefixGenerators) { + arrayFormat = opts.arrayFormat; + } else if (opts && 'indices' in opts) { + arrayFormat = opts.indices ? 'indices' : 'repeat'; } else { arrayFormat = 'indices'; } @@ -460,38 +521,38 @@ module.exports = function (object, opts) { objKeys = Object.keys(obj); } - if (sort) { - objKeys.sort(sort); + if (options.sort) { + objKeys.sort(options.sort); } for (var i = 0; i < objKeys.length; ++i) { var key = objKeys[i]; - if (skipNulls && obj[key] === null) { + if (options.skipNulls && obj[key] === null) { continue; } pushToArray(keys, stringify( obj[key], key, generateArrayPrefix, - strictNullHandling, - skipNulls, - encode ? encoder : null, - filter, - sort, - allowDots, - serializeDate, - formatter, - encodeValuesOnly, - charset + options.strictNullHandling, + options.skipNulls, + options.encode ? options.encoder : null, + options.filter, + options.sort, + options.allowDots, + options.serializeDate, + options.formatter, + options.encodeValuesOnly, + options.charset )); } - var joined = keys.join(delimiter); + var joined = keys.join(options.delimiter); var prefix = options.addQueryPrefix === true ? '?' : ''; if (options.charsetSentinel) { - if (charset === 'iso-8859-1') { + if (options.charset === 'iso-8859-1') { // encodeURIComponent('✓'), the "numeric entity" representation of a checkmark prefix += 'utf8=%26%2310003%3B&'; } else { @@ -507,6 +568,7 @@ module.exports = function (object, opts) { 'use strict'; var has = Object.prototype.hasOwnProperty; +var isArray = Array.isArray; var hexTable = (function () { var array = []; @@ -522,7 +584,7 @@ var compactQueue = function compactQueue(queue) { var item = queue.pop(); var obj = item.obj[item.prop]; - if (Array.isArray(obj)) { + if (isArray(obj)) { var compacted = []; for (var j = 0; j < obj.length; ++j) { @@ -553,9 +615,9 @@ var merge = function merge(target, source, options) { } if (typeof source !== 'object') { - if (Array.isArray(target)) { + if (isArray(target)) { target.push(source); - } else if (typeof target === 'object') { + } else if (target && typeof target === 'object') { if ((options && (options.plainObjects || options.allowPrototypes)) || !has.call(Object.prototype, source)) { target[source] = true; } @@ -566,20 +628,21 @@ var merge = function merge(target, source, options) { return target; } - if (typeof target !== 'object') { + if (!target || typeof target !== 'object') { return [target].concat(source); } var mergeTarget = target; - if (Array.isArray(target) && !Array.isArray(source)) { + if (isArray(target) && !isArray(source)) { mergeTarget = arrayToObject(target, options); } - if (Array.isArray(target) && Array.isArray(source)) { + if (isArray(target) && isArray(source)) { source.forEach(function (item, i) { if (has.call(target, i)) { - if (target[i] && typeof target[i] === 'object') { - target[i] = merge(target[i], item, options); + var targetItem = target[i]; + if (targetItem && typeof targetItem === 'object' && item && typeof item === 'object') { + target[i] = merge(targetItem, item, options); } else { target.push(item); } @@ -630,7 +693,12 @@ var encode = function encode(str, defaultEncoder, charset) { 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) { @@ -710,7 +778,7 @@ var isRegExp = function isRegExp(obj) { }; var isBuffer = function isBuffer(obj) { - if (obj === null || typeof obj === 'undefined') { + if (!obj || typeof obj !== 'object') { return false; } diff --git a/package.json b/package.json index 28d98a11..f51c2829 100644 --- a/package.json +++ b/package.json @@ -29,10 +29,10 @@ "dependencies": {}, "devDependencies": { "@ljharb/eslint-config": "^13.1.1", - "browserify": "^16.2.3", + "browserify": "^16.3.0", "covert": "^1.1.1", "editorconfig-tools": "^0.1.1", - "eslint": "^5.15.3", + "eslint": "^5.16.0", "evalmd": "^0.0.17", "for-each": "^0.3.3", "iconv-lite": "^0.4.24", @@ -41,7 +41,7 @@ "qs-iconv": "^1.0.4", "safe-publish-latest": "^1.1.2", "safer-buffer": "^2.1.2", - "tape": "^4.10.1" + "tape": "^4.11.0" }, "scripts": { "prepublish": "safe-publish-latest && npm run dist", diff --git a/test/parse.js b/test/parse.js index 89677899..e6c998eb 100644 --- a/test/parse.js +++ b/test/parse.js @@ -617,6 +617,7 @@ test('parse()', function (t) { }); t.test('prefers an iso-8859-1 charset specified by the utf8 sentinel to a default charset of utf-8', function (st) { + // eslint-disable-next-line quote-props st.deepEqual(qs.parse('utf8=' + urlEncodedNumCheckmark + '&' + urlEncodedOSlashInUtf8 + '=' + urlEncodedOSlashInUtf8, { charsetSentinel: true, charset: 'utf-8' }), { 'ø': 'ø' }); st.end(); }); @@ -637,6 +638,7 @@ test('parse()', function (t) { }); t.test('uses the utf8 sentinel to switch to iso-8859-1 when no default charset is given', function (st) { + // eslint-disable-next-line quote-props st.deepEqual(qs.parse('utf8=' + urlEncodedNumCheckmark + '&' + urlEncodedOSlashInUtf8 + '=' + urlEncodedOSlashInUtf8, { charsetSentinel: true }), { 'ø': 'ø' }); st.end(); }); From 66df5e9bc8e9a045805ccc4e36b299d4c59c3227 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Wed, 10 Jul 2019 23:50:19 -0700 Subject: [PATCH 146/278] [Tests] use `eclint` instead of `editorconfig-tools` --- .editorconfig | 3 +++ package.json | 4 ++-- test/.eslintrc | 10 +++++----- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/.editorconfig b/.editorconfig index a4893ddf..b442a9f9 100644 --- a/.editorconfig +++ b/.editorconfig @@ -12,6 +12,9 @@ max_line_length = 160 [test/*] max_line_length = off +[LICENSE.md] +indent_size = off + [*.md] max_line_length = off diff --git a/package.json b/package.json index f51c2829..a5058074 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,7 @@ "@ljharb/eslint-config": "^13.1.1", "browserify": "^16.3.0", "covert": "^1.1.1", - "editorconfig-tools": "^0.1.1", + "eclint": "^2.8.1", "eslint": "^5.16.0", "evalmd": "^0.0.17", "for-each": "^0.3.3", @@ -49,7 +49,7 @@ "test": "npm run --silent coverage", "tests-only": "node test", "readme": "evalmd README.md", - "postlint": "editorconfig-tools check * lib/* test/*", + "postlint": "eclint check * lib/* test/*", "lint": "eslint lib/*.js test/*.js", "coverage": "covert test", "dist": "mkdirp dist && browserify --standalone Qs lib/index.js > dist/qs.js" diff --git a/test/.eslintrc b/test/.eslintrc index 9ebbb921..f76d573c 100644 --- a/test/.eslintrc +++ b/test/.eslintrc @@ -1,17 +1,17 @@ { "rules": { - "array-bracket-newline": 0, - "array-element-newline": 0, - "consistent-return": 2, + "array-bracket-newline": 0, + "array-element-newline": 0, + "consistent-return": 2, "function-paren-newline": 0, "max-lines": 0, "max-lines-per-function": 0, "max-nested-callbacks": [2, 3], "max-statements": 0, - "no-buffer-constructor": 0, + "no-buffer-constructor": 0, "no-extend-native": 0, "no-magic-numbers": 0, - "object-curly-newline": 0, + "object-curly-newline": 0, "sort-keys": 0 } } From b558f532dcd5db2573b34aece13daa530c8c4c54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=BD=98=E4=B8=BD=E6=95=8F?= Date: Thu, 8 Aug 2019 11:40:59 +0800 Subject: [PATCH 147/278] [Tests] add tests for `depth=0` and `depth=false` behavior, both current and intuitive/intended --- test/parse.js | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/test/parse.js b/test/parse.js index e6c998eb..0d30de23 100644 --- a/test/parse.js +++ b/test/parse.js @@ -52,6 +52,30 @@ test('parse()', function (t) { st.end(); }); + t.test('current behavior with depth = 0', function (st) { + st.deepEqual(qs.parse('a[0]=b&a[1]=c', { depth: 0 }), { a: { '[0]': 'b', '[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', '[0][1]': 'c', '[1]': 'd' }, e: '2' }); + st.end(); + }); + + t.test('uses original key when depth = 0', { skip: true, todo: true }, 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('current behavior with depth = false', function (st) { + st.deepEqual(qs.parse('a[0]=b&a[1]=c', { depth: false }), { a: ['b', 'c'] }); + st.deepEqual(qs.parse('a[0][0]=b&a[0][1]=c&a[1]=d&e=2', { depth: false }), { a: [['b', 'c'], 'd'], e: '2' }); + st.end(); + }); + + t.test('uses original key when depth = false', { skip: true, todo: true }, 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) { From fcb96440e680faedb2c9f6c4fc37402a06cd93d8 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Fri, 16 Aug 2019 16:25:30 -0700 Subject: [PATCH 148/278] [Dev Deps] update `eslint`, `@ljharb/eslint-config`, `browserify`, `safe-publish-latest` --- package.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index a5058074..b81fc7bb 100644 --- a/package.json +++ b/package.json @@ -28,18 +28,18 @@ }, "dependencies": {}, "devDependencies": { - "@ljharb/eslint-config": "^13.1.1", - "browserify": "^16.3.0", + "@ljharb/eslint-config": "^14.0.2", + "browserify": "^16.5.0", "covert": "^1.1.1", "eclint": "^2.8.1", - "eslint": "^5.16.0", + "eslint": "^6.1.0", "evalmd": "^0.0.17", "for-each": "^0.3.3", "iconv-lite": "^0.4.24", "mkdirp": "^0.5.1", "object-inspect": "^1.6.0", "qs-iconv": "^1.0.4", - "safe-publish-latest": "^1.1.2", + "safe-publish-latest": "^1.1.3", "safer-buffer": "^2.1.2", "tape": "^4.11.0" }, From 67166f7a9902c5d38f4f1f52f78591eeb2560932 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Mon, 13 Jan 2020 21:50:38 -0800 Subject: [PATCH 149/278] [Fix] `parse`: throw a TypeError instead of an Error for bad charset (#349) --- lib/parse.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/parse.js b/lib/parse.js index d81628b5..22242351 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -196,7 +196,7 @@ 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; From dccab8b62a33ae617c8da94e51a6d2bb704cd238 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Fri, 16 Aug 2019 19:43:55 -0700 Subject: [PATCH 150/278] [Tests] `Buffer.from` in node v5.0-v5.9 and v4.0-v4.4 requires a TypedArray --- test/utils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/utils.js b/test/utils.js index da31ce53..aa84dfdc 100644 --- a/test/utils.js +++ b/test/utils.js @@ -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(); }); From 0b46d834cfcd548c6925b03e7d679a44d78f38ef Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Mon, 23 Mar 2020 21:18:47 -0700 Subject: [PATCH 151/278] [Dev Deps] update `eslint`, `@ljharb/eslint-config`, `tape`, `safe-publish-latest`, `evalmd`, `iconv-lite`, `mkdirp`, `object-inspect` --- lib/stringify.js | 10 +++++----- lib/utils.js | 12 ++++++------ package.json | 16 ++++++++-------- test/parse.js | 4 +--- 4 files changed, 20 insertions(+), 22 deletions(-) diff --git a/lib/stringify.js b/lib/stringify.js index 4eac941b..415a2411 100644 --- a/lib/stringify.js +++ b/lib/stringify.js @@ -5,14 +5,14 @@ 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; } }; @@ -39,14 +39,14 @@ var defaults = { 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 stringify = function stringify( object, prefix, generateArrayPrefix, diff --git a/lib/utils.js b/lib/utils.js index 1b219cdd..a5d0401e 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -52,7 +52,7 @@ var merge = function merge(target, source, options) { target.push(source); } else if (target && typeof target === 'object') { if ((options && (options.plainObjects || options.allowPrototypes)) || !has.call(Object.prototype, source)) { - target[source] = true; + target[source] = true; // eslint-disable-line no-param-reassign } } else { return [target, source]; @@ -75,12 +75,12 @@ var merge = function merge(target, source, options) { if (has.call(target, i)) { var targetItem = target[i]; if (targetItem && typeof targetItem === 'object' && item && typeof item === 'object') { - target[i] = merge(targetItem, item, options); + target[i] = merge(targetItem, item, options); // eslint-disable-line no-param-reassign } else { target.push(item); } } else { - target[i] = item; + target[i] = item; // eslint-disable-line no-param-reassign } }); return target; @@ -90,9 +90,9 @@ var merge = function merge(target, source, options) { var value = source[key]; if (has.call(acc, key)) { - acc[key] = merge(acc[key], value, options); + acc[key] = merge(acc[key], value, options); // eslint-disable-line no-param-reassign } else { - acc[key] = value; + acc[key] = value; // eslint-disable-line no-param-reassign } return acc; }, mergeTarget); @@ -100,7 +100,7 @@ var merge = function merge(target, source, options) { var assign = function assignSingleSource(target, source) { return Object.keys(source).reduce(function (acc, key) { - acc[key] = source[key]; + acc[key] = source[key]; // eslint-disable-line no-param-reassign return acc; }, target); }; diff --git a/package.json b/package.json index b81fc7bb..56444826 100644 --- a/package.json +++ b/package.json @@ -28,20 +28,20 @@ }, "dependencies": {}, "devDependencies": { - "@ljharb/eslint-config": "^14.0.2", + "@ljharb/eslint-config": "^16.0.0", "browserify": "^16.5.0", "covert": "^1.1.1", "eclint": "^2.8.1", - "eslint": "^6.1.0", - "evalmd": "^0.0.17", + "eslint": "^6.8.0", + "evalmd": "^0.0.19", "for-each": "^0.3.3", - "iconv-lite": "^0.4.24", - "mkdirp": "^0.5.1", - "object-inspect": "^1.6.0", + "iconv-lite": "^0.5.1", + "mkdirp": "^0.5.4", + "object-inspect": "^1.7.0", "qs-iconv": "^1.0.4", - "safe-publish-latest": "^1.1.3", + "safe-publish-latest": "^1.1.4", "safer-buffer": "^2.1.2", - "tape": "^4.11.0" + "tape": "^5.0.0-next.5" }, "scripts": { "prepublish": "safe-publish-latest && npm run dist", diff --git a/test/parse.js b/test/parse.js index 0d30de23..2e33e8f0 100644 --- a/test/parse.js +++ b/test/parse.js @@ -556,7 +556,7 @@ 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' ); @@ -641,7 +641,6 @@ test('parse()', function (t) { }); t.test('prefers an iso-8859-1 charset specified by the utf8 sentinel to a default charset of utf-8', function (st) { - // eslint-disable-next-line quote-props st.deepEqual(qs.parse('utf8=' + urlEncodedNumCheckmark + '&' + urlEncodedOSlashInUtf8 + '=' + urlEncodedOSlashInUtf8, { charsetSentinel: true, charset: 'utf-8' }), { 'ø': 'ø' }); st.end(); }); @@ -662,7 +661,6 @@ test('parse()', function (t) { }); t.test('uses the utf8 sentinel to switch to iso-8859-1 when no default charset is given', function (st) { - // eslint-disable-next-line quote-props st.deepEqual(qs.parse('utf8=' + urlEncodedNumCheckmark + '&' + urlEncodedOSlashInUtf8 + '=' + urlEncodedOSlashInUtf8, { charsetSentinel: true }), { 'ø': 'ø' }); st.end(); }); From dca16435ea41e0e8aebe30a5d5bf44ca85f468c3 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Mon, 23 Mar 2020 21:27:38 -0700 Subject: [PATCH 152/278] [Tests] use shared travis-ci configs --- .travis.yml | 284 ++------------------------------------------------- package.json | 1 + 2 files changed, 7 insertions(+), 278 deletions(-) diff --git a/.travis.yml b/.travis.yml index cad8f455..7709ba6d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,284 +1,12 @@ +version: ~> 1.0 language: node_js os: - linux -node_js: - - "11.12" - - "10.15" - - "9.11" - - "8.15" - - "7.10" - - "6.17" - - "5.12" - - "4.9" - - "iojs-v3.3" - - "iojs-v2.5" - - "iojs-v1.8" - - "0.12" - - "0.10" - - "0.8" - - "0.6" -before_install: - - 'case "${TRAVIS_NODE_VERSION}" in 0.*) export NPM_CONFIG_STRICT_SSL=false ;; esac' - - 'nvm install-latest-npm' -install: - - 'if [ "${TRAVIS_NODE_VERSION}" = "0.6" ] || [ "${TRAVIS_NODE_VERSION}" = "0.9" ]; then nvm install --latest-npm 0.8 && npm install && nvm use "${TRAVIS_NODE_VERSION}"; else npm install; fi;' -script: - - 'if [ -n "${PRETEST-}" ]; then npm run pretest ; fi' - - 'if [ -n "${POSTTEST-}" ]; then npm run posttest ; fi' - - 'if [ -n "${COVERAGE-}" ]; then npm run coverage ; fi' - - 'if [ -n "${TEST-}" ]; then npm run tests-only ; fi' -sudo: false -env: - - TEST=true +import: + - ljharb/travis-ci:node/all.yml + - ljharb/travis-ci:node/pretest.yml + - ljharb/travis-ci:node/posttest.yml + - ljharb/travis-ci:node/coverage.yml matrix: - fast_finish: true - include: - - node_js: "lts/*" - env: PRETEST=true - - node_js: "4" - env: COVERAGE=true - - node_js: "11.11" - env: TEST=true ALLOW_FAILURE=true - - node_js: "11.10" - env: TEST=true ALLOW_FAILURE=true - - node_js: "11.9" - env: TEST=true ALLOW_FAILURE=true - - node_js: "11.8" - env: TEST=true ALLOW_FAILURE=true - - node_js: "11.7" - env: TEST=true ALLOW_FAILURE=true - - node_js: "11.6" - env: TEST=true ALLOW_FAILURE=true - - node_js: "11.5" - env: TEST=true ALLOW_FAILURE=true - - node_js: "11.4" - env: TEST=true ALLOW_FAILURE=true - - node_js: "11.3" - env: TEST=true ALLOW_FAILURE=true - - node_js: "11.2" - env: TEST=true ALLOW_FAILURE=true - - node_js: "11.1" - env: TEST=true ALLOW_FAILURE=true - - node_js: "11.0" - env: TEST=true ALLOW_FAILURE=true - - node_js: "10.14" - env: TEST=true ALLOW_FAILURE=true - - node_js: "10.13" - env: TEST=true ALLOW_FAILURE=true - - node_js: "10.12" - env: TEST=true ALLOW_FAILURE=true - - node_js: "10.11" - env: TEST=true ALLOW_FAILURE=true - - node_js: "10.10" - env: TEST=true ALLOW_FAILURE=true - - node_js: "10.9" - env: TEST=true ALLOW_FAILURE=true - - node_js: "10.8" - env: TEST=true ALLOW_FAILURE=true - - node_js: "10.7" - env: TEST=true ALLOW_FAILURE=true - - node_js: "10.6" - env: TEST=true ALLOW_FAILURE=true - - node_js: "10.5" - env: TEST=true ALLOW_FAILURE=true - - node_js: "10.4" - env: TEST=true ALLOW_FAILURE=true - - node_js: "10.3" - env: TEST=true ALLOW_FAILURE=true - - node_js: "10.2" - env: TEST=true ALLOW_FAILURE=true - - node_js: "10.1" - env: TEST=true ALLOW_FAILURE=true - - node_js: "10.0" - env: TEST=true ALLOW_FAILURE=true - - node_js: "9.10" - env: TEST=true ALLOW_FAILURE=true - - node_js: "9.9" - env: TEST=true ALLOW_FAILURE=true - - node_js: "9.8" - env: TEST=true ALLOW_FAILURE=true - - node_js: "9.7" - env: TEST=true ALLOW_FAILURE=true - - node_js: "9.6" - env: TEST=true ALLOW_FAILURE=true - - node_js: "9.5" - env: TEST=true ALLOW_FAILURE=true - - node_js: "9.4" - env: TEST=true ALLOW_FAILURE=true - - node_js: "9.3" - env: TEST=true ALLOW_FAILURE=true - - node_js: "9.2" - env: TEST=true ALLOW_FAILURE=true - - node_js: "9.1" - env: TEST=true ALLOW_FAILURE=true - - node_js: "9.0" - env: TEST=true ALLOW_FAILURE=true - - node_js: "8.14" - env: TEST=true ALLOW_FAILURE=true - - node_js: "8.13" - env: TEST=true ALLOW_FAILURE=true - - node_js: "8.12" - env: TEST=true ALLOW_FAILURE=true - - node_js: "8.11" - env: TEST=true ALLOW_FAILURE=true - - node_js: "8.10" - env: TEST=true ALLOW_FAILURE=true - - node_js: "8.9" - env: TEST=true ALLOW_FAILURE=true - - node_js: "8.8" - env: TEST=true ALLOW_FAILURE=true - - node_js: "8.7" - env: TEST=true ALLOW_FAILURE=true - - node_js: "8.6" - env: TEST=true ALLOW_FAILURE=true - - node_js: "8.5" - env: TEST=true ALLOW_FAILURE=true - - node_js: "8.4" - env: TEST=true ALLOW_FAILURE=true - - node_js: "8.3" - env: TEST=true ALLOW_FAILURE=true - - node_js: "8.2" - env: TEST=true ALLOW_FAILURE=true - - node_js: "8.1" - env: TEST=true ALLOW_FAILURE=true - - node_js: "8.0" - env: TEST=true ALLOW_FAILURE=true - - node_js: "7.9" - env: TEST=true ALLOW_FAILURE=true - - node_js: "7.8" - env: TEST=true ALLOW_FAILURE=true - - node_js: "7.7" - env: TEST=true ALLOW_FAILURE=true - - node_js: "7.6" - env: TEST=true ALLOW_FAILURE=true - - node_js: "7.5" - env: TEST=true ALLOW_FAILURE=true - - node_js: "7.4" - env: TEST=true ALLOW_FAILURE=true - - node_js: "7.3" - env: TEST=true ALLOW_FAILURE=true - - node_js: "7.2" - env: TEST=true ALLOW_FAILURE=true - - node_js: "7.1" - env: TEST=true ALLOW_FAILURE=true - - node_js: "7.0" - env: TEST=true ALLOW_FAILURE=true - - node_js: "6.16" - env: TEST=true ALLOW_FAILURE=true - - node_js: "6.15" - env: TEST=true ALLOW_FAILURE=true - - node_js: "6.14" - env: TEST=true ALLOW_FAILURE=true - - node_js: "6.13" - env: TEST=true ALLOW_FAILURE=true - - node_js: "6.12" - env: TEST=true ALLOW_FAILURE=true - - node_js: "6.11" - env: TEST=true ALLOW_FAILURE=true - - node_js: "6.10" - env: TEST=true ALLOW_FAILURE=true - - node_js: "6.9" - env: TEST=true ALLOW_FAILURE=true - - node_js: "6.8" - env: TEST=true ALLOW_FAILURE=true - - node_js: "6.7" - env: TEST=true ALLOW_FAILURE=true - - node_js: "6.6" - env: TEST=true ALLOW_FAILURE=true - - node_js: "6.5" - env: TEST=true ALLOW_FAILURE=true - - node_js: "6.4" - env: TEST=true ALLOW_FAILURE=true - - node_js: "6.3" - env: TEST=true ALLOW_FAILURE=true - - node_js: "6.2" - env: TEST=true ALLOW_FAILURE=true - - node_js: "6.1" - env: TEST=true ALLOW_FAILURE=true - - node_js: "6.0" - env: TEST=true ALLOW_FAILURE=true - - node_js: "5.11" - env: TEST=true ALLOW_FAILURE=true - - node_js: "5.10" - env: TEST=true ALLOW_FAILURE=true - - node_js: "5.9" - env: TEST=true ALLOW_FAILURE=true - - node_js: "5.8" - env: TEST=true ALLOW_FAILURE=true - - node_js: "5.7" - env: TEST=true ALLOW_FAILURE=true - - node_js: "5.6" - env: TEST=true ALLOW_FAILURE=true - - node_js: "5.5" - env: TEST=true ALLOW_FAILURE=true - - node_js: "5.4" - env: TEST=true ALLOW_FAILURE=true - - node_js: "5.3" - env: TEST=true ALLOW_FAILURE=true - - node_js: "5.2" - env: TEST=true ALLOW_FAILURE=true - - node_js: "5.1" - env: TEST=true ALLOW_FAILURE=true - - node_js: "5.0" - env: TEST=true ALLOW_FAILURE=true - - node_js: "4.8" - env: TEST=true ALLOW_FAILURE=true - - node_js: "4.7" - env: TEST=true ALLOW_FAILURE=true - - node_js: "4.6" - env: TEST=true ALLOW_FAILURE=true - - node_js: "4.5" - env: TEST=true ALLOW_FAILURE=true - - node_js: "4.4" - env: TEST=true ALLOW_FAILURE=true - - node_js: "4.3" - env: TEST=true ALLOW_FAILURE=true - - node_js: "4.2" - env: TEST=true ALLOW_FAILURE=true - - node_js: "4.1" - env: TEST=true ALLOW_FAILURE=true - - node_js: "4.0" - env: TEST=true ALLOW_FAILURE=true - - node_js: "iojs-v3.2" - env: TEST=true ALLOW_FAILURE=true - - node_js: "iojs-v3.1" - env: TEST=true ALLOW_FAILURE=true - - node_js: "iojs-v3.0" - env: TEST=true ALLOW_FAILURE=true - - node_js: "iojs-v2.4" - env: TEST=true ALLOW_FAILURE=true - - node_js: "iojs-v2.3" - env: TEST=true ALLOW_FAILURE=true - - node_js: "iojs-v2.2" - env: TEST=true ALLOW_FAILURE=true - - node_js: "iojs-v2.1" - env: TEST=true ALLOW_FAILURE=true - - node_js: "iojs-v2.0" - env: TEST=true ALLOW_FAILURE=true - - node_js: "iojs-v1.7" - env: TEST=true ALLOW_FAILURE=true - - node_js: "iojs-v1.6" - env: TEST=true ALLOW_FAILURE=true - - node_js: "iojs-v1.5" - env: TEST=true ALLOW_FAILURE=true - - node_js: "iojs-v1.4" - env: TEST=true ALLOW_FAILURE=true - - node_js: "iojs-v1.3" - env: TEST=true ALLOW_FAILURE=true - - node_js: "iojs-v1.2" - env: TEST=true ALLOW_FAILURE=true - - node_js: "iojs-v1.1" - env: TEST=true ALLOW_FAILURE=true - - node_js: "iojs-v1.0" - env: TEST=true ALLOW_FAILURE=true - - node_js: "0.11" - env: TEST=true ALLOW_FAILURE=true - - node_js: "0.9" - env: TEST=true ALLOW_FAILURE=true - - node_js: "0.4" - env: TEST=true ALLOW_FAILURE=true allow_failures: - - os: osx - - env: TEST=true ALLOW_FAILURE=true - - node_js: "0.6" # temporarily allow this to fail - env: COVERAGE=true # temporarily allow this to fail diff --git a/package.json b/package.json index 56444826..2b9f0de1 100644 --- a/package.json +++ b/package.json @@ -48,6 +48,7 @@ "pretest": "npm run --silent readme && npm run --silent lint", "test": "npm run --silent coverage", "tests-only": "node test", + "posttest": "npx aud --production", "readme": "evalmd README.md", "postlint": "eclint check * lib/* test/*", "lint": "eslint lib/*.js test/*.js", From 427da67eace5e4557fa76b2e62fe77c800172972 Mon Sep 17 00:00:00 2001 From: Roman Usherenko Date: Sun, 8 Sep 2019 10:27:42 +0300 Subject: [PATCH 153/278] [Tests] `parse`: add passing `arrayFormat` tests --- test/parse.js | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/test/parse.js b/test/parse.js index 2e33e8f0..fbfb7b39 100644 --- a/test/parse.js +++ b/test/parse.js @@ -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' } }); From 174493b14f08b1a6e9e45d19d84ce445f7c7bff0 Mon Sep 17 00:00:00 2001 From: Mohamed Omar Date: Fri, 4 Oct 2019 02:57:00 +0200 Subject: [PATCH 154/278] [fix] `parse`: with comma true, do not split non-string values (#334) --- lib/parse.js | 2 +- test/parse.js | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/lib/parse.js b/lib/parse.js index 22242351..d7ac6e21 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -83,7 +83,7 @@ var parseValues = function parseQueryStringValues(str, options) { val = interpretNumericEntities(val); } - if (val && options.comma && val.indexOf(',') > -1) { + if (val && typeof val === 'string' && options.comma && val.indexOf(',') > -1) { val = val.split(','); } diff --git a/test/parse.js b/test/parse.js index fbfb7b39..5513ad63 100644 --- a/test/parse.js +++ b/test/parse.js @@ -412,6 +412,20 @@ test('parse()', function (t) { 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 an object in dot notation', function (st) { var input = { 'user.name': { 'pop[bob]': 3 }, From ea21ba805ce56c39690790de7f9cad9653d2d29c Mon Sep 17 00:00:00 2001 From: Mohamed Omar Date: Fri, 4 Oct 2019 05:50:34 +0200 Subject: [PATCH 155/278] [Fix] `parse`: with comma true, handle field that holds an array of arrays (#335) Fixes #327. --- lib/parse.js | 5 +++++ test/parse.js | 9 +++++++++ 2 files changed, 14 insertions(+) diff --git a/lib/parse.js b/lib/parse.js index d7ac6e21..4308ca92 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -3,6 +3,7 @@ var utils = require('./utils'); var has = Object.prototype.hasOwnProperty; +var isArray = Array.isArray; var defaults = { allowDots: false, @@ -87,6 +88,10 @@ var parseValues = function parseQueryStringValues(str, options) { val = val.split(','); } + if (part.indexOf('[]=') > -1) { + val = isArray(val) ? [val] : val; + } + if (has.call(obj, key)) { obj[key] = utils.combine(obj[key], val); } else { diff --git a/test/parse.js b/test/parse.js index 5513ad63..60d322bb 100644 --- a/test/parse.js +++ b/test/parse.js @@ -426,6 +426,15 @@ test('parse()', function (t) { 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 an object in dot notation', function (st) { var input = { 'user.name': { 'pop[bob]': 3 }, From 7977bc96c2abeb3b4686746ebf1eafe5f16d5e7f Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Thu, 7 Nov 2019 15:05:08 -0800 Subject: [PATCH 156/278] [meta] add `funding` field --- package.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/package.json b/package.json index 2b9f0de1..7b183295 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,9 @@ "type": "git", "url": "https://github.com/ljharb/qs.git" }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + }, "main": "lib/index.js", "contributors": [ { From 0fc71c9b527d4b92c1b69f7cb4da8bdef9d1efd2 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Fri, 6 Dec 2019 14:37:31 -0800 Subject: [PATCH 157/278] [meta] add tidelift marketing copy --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index cb47d60a..cae83b48 100644 --- a/README.md +++ b/README.md @@ -557,6 +557,12 @@ assert.equal(qs.stringify({ a: 'b c' }, { format : 'RFC1738' }), 'a=b+c'); 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) + [1]: https://npmjs.org/package/qs [2]: http://versionbadg.es/ljharb/qs.svg [3]: https://api.travis-ci.org/ljharb/qs.svg From cbd246947c235b51d706811cc9f6f8c55b6522dd Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Mon, 13 Jan 2020 21:57:54 -0800 Subject: [PATCH 158/278] [actions] add automatic rebasing / merge commit blocking See https://github.com/ljharb/es-abstract/pull/74 --- .github/workflows/rebase.yml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 .github/workflows/rebase.yml diff --git a/.github/workflows/rebase.yml b/.github/workflows/rebase.yml new file mode 100644 index 00000000..436cb79d --- /dev/null +++ b/.github/workflows/rebase.yml @@ -0,0 +1,15 @@ +name: Automatic Rebase + +on: [pull_request] + +jobs: + _: + name: "Automatic Rebase" + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v1 + - uses: ljharb/rebase@master + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From a80b84a87d3e41425ac5f5777c615fe744988f7c Mon Sep 17 00:00:00 2001 From: Alexander Yefanov Date: Sun, 15 Mar 2020 10:45:46 +0100 Subject: [PATCH 159/278] [Fix] `parse`: Fix parsing array from object with `comma` true (#359) Fixes #357 --- lib/parse.js | 14 ++++++++++---- test/parse.js | 6 ++++++ 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/lib/parse.js b/lib/parse.js index 4308ca92..a25f6ece 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -29,6 +29,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 @@ -84,9 +92,7 @@ var parseValues = function parseQueryStringValues(str, options) { val = interpretNumericEntities(val); } - if (val && typeof val === 'string' && options.comma && val.indexOf(',') > -1) { - val = val.split(','); - } + val = parseArrayValue(val, options); if (part.indexOf('[]=') > -1) { val = isArray(val) ? [val] : val; @@ -103,7 +109,7 @@ var parseValues = function parseQueryStringValues(str, options) { }; var parseObject = function (chain, val, options) { - var leaf = val; + var leaf = parseArrayValue(val, options); for (var i = chain.length - 1; i >= 0; --i) { var obj; diff --git a/test/parse.js b/test/parse.js index 60d322bb..a6cb0d0d 100644 --- a/test/parse.js +++ b/test/parse.js @@ -412,6 +412,12 @@ 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))) { From 92f97f25e40bcc7cb3396ddc2ea813bcac0b4dac Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Mon, 23 Mar 2020 21:56:02 -0700 Subject: [PATCH 160/278] v6.7.1 --- CHANGELOG.md | 20 ++++++++++++++++++ dist/qs.js | 58 +++++++++++++++++++++++++--------------------------- package.json | 2 +- 3 files changed, 49 insertions(+), 31 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 50505c46..ee2f773a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,23 @@ +## **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) diff --git a/dist/qs.js b/dist/qs.js index 70e75ef9..4986a7b8 100644 --- a/dist/qs.js +++ b/dist/qs.js @@ -45,6 +45,7 @@ module.exports = { var utils = require('./utils'); var has = Object.prototype.hasOwnProperty; +var isArray = Array.isArray; var defaults = { allowDots: false, @@ -70,6 +71,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 @@ -125,8 +134,10 @@ var parseValues = function parseQueryStringValues(str, options) { val = interpretNumericEntities(val); } - if (val && options.comma && val.indexOf(',') > -1) { - val = val.split(','); + val = parseArrayValue(val, options); + + if (part.indexOf('[]=') > -1) { + val = isArray(val) ? [val] : val; } if (has.call(obj, key)) { @@ -140,7 +151,7 @@ var parseValues = function parseQueryStringValues(str, options) { }; var parseObject = function (chain, val, options) { - var leaf = val; + var leaf = parseArrayValue(val, options); for (var i = chain.length - 1; i >= 0; --i) { var obj; @@ -238,7 +249,7 @@ 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; @@ -291,14 +302,14 @@ 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; } }; @@ -325,22 +336,14 @@ var defaults = { 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 isNonNullishPrimitive = function isNonNullishPrimitive(v) { // eslint-disable-line func-name-matching - return typeof v === 'string' - || typeof v === 'number' - || typeof v === 'boolean' - || typeof v === 'symbol' - || typeof v === 'bigint'; // eslint-disable-line valid-typeof -}; - -var stringify = function stringify( // eslint-disable-line func-name-matching +var stringify = function stringify( object, prefix, generateArrayPrefix, @@ -372,7 +375,7 @@ var stringify = function stringify( // eslint-disable-line func-name-matching obj = ''; } - if (isNonNullishPrimitive(obj) || utils.isBuffer(obj)) { + if (typeof obj === 'string' || typeof obj === 'number' || typeof obj === 'boolean' || utils.isBuffer(obj)) { if (encoder) { var keyValue = encodeValuesOnly ? prefix : encoder(prefix, defaults.encoder, charset); return [formatter(keyValue) + '=' + formatter(encoder(obj, defaults.encoder, charset))]; @@ -619,7 +622,7 @@ var merge = function merge(target, source, options) { target.push(source); } else if (target && typeof target === 'object') { if ((options && (options.plainObjects || options.allowPrototypes)) || !has.call(Object.prototype, source)) { - target[source] = true; + target[source] = true; // eslint-disable-line no-param-reassign } } else { return [target, source]; @@ -642,12 +645,12 @@ var merge = function merge(target, source, options) { if (has.call(target, i)) { var targetItem = target[i]; if (targetItem && typeof targetItem === 'object' && item && typeof item === 'object') { - target[i] = merge(targetItem, item, options); + target[i] = merge(targetItem, item, options); // eslint-disable-line no-param-reassign } else { target.push(item); } } else { - target[i] = item; + target[i] = item; // eslint-disable-line no-param-reassign } }); return target; @@ -657,9 +660,9 @@ var merge = function merge(target, source, options) { var value = source[key]; if (has.call(acc, key)) { - acc[key] = merge(acc[key], value, options); + acc[key] = merge(acc[key], value, options); // eslint-disable-line no-param-reassign } else { - acc[key] = value; + acc[key] = value; // eslint-disable-line no-param-reassign } return acc; }, mergeTarget); @@ -667,7 +670,7 @@ var merge = function merge(target, source, options) { var assign = function assignSingleSource(target, source) { return Object.keys(source).reduce(function (acc, key) { - acc[key] = source[key]; + acc[key] = source[key]; // eslint-disable-line no-param-reassign return acc; }, target); }; @@ -693,12 +696,7 @@ var encode = function encode(str, defaultEncoder, charset) { return str; } - var string = str; - if (typeof str === 'symbol') { - string = Symbol.prototype.toString.call(str); - } else if (typeof str !== 'string') { - string = String(str); - } + var string = typeof str === 'string' ? str : String(str); if (charset === 'iso-8859-1') { return escape(string).replace(/%u[0-9a-f]{4}/gi, function ($0) { diff --git a/package.json b/package.json index 7b183295..3a4b587d 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "qs", "description": "A querystring parser that supports nesting and arrays, with a depth limit", "homepage": "https://github.com/ljharb/qs", - "version": "6.7.0", + "version": "6.7.1", "repository": { "type": "git", "url": "https://github.com/ljharb/qs.git" From 3756d407132dd757683c47a0e54b43c7259c0399 Mon Sep 17 00:00:00 2001 From: Mohamed Omar Date: Fri, 18 Oct 2019 19:41:30 +0200 Subject: [PATCH 161/278] [Fix] parses comma delimited array while having percent-encoded comma treated as normal text (#336) Co-authored-by: Mohamed Omar Co-authored-by: Quentin de Longraye --- lib/parse.js | 10 +++++++++- test/parse.js | 8 ++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/lib/parse.js b/lib/parse.js index a25f6ece..5203aa84 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -85,7 +85,15 @@ var parseValues = function parseQueryStringValues(str, options) { 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); + var encodedVal = part.slice(pos + 1); + if (options.comma && encodedVal.indexOf(',') !== -1) { + val = encodedVal.split(',') + .map(function (encodedFragment) { + return options.decoder(encodedFragment, defaults.decoder, charset); + }); + } else { + val = options.decoder(encodedVal, defaults.decoder, charset); + } } if (val && options.interpretNumericEntities && charset === 'iso-8859-1') { diff --git a/test/parse.js b/test/parse.js index a6cb0d0d..003d3eb1 100644 --- a/test/parse.js +++ b/test/parse.js @@ -441,6 +441,14 @@ test('parse()', function (t) { 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 }, From 74fcd83d5fb2674b7576b41f6b9c3d0628e6c51c Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Tue, 24 Mar 2020 20:53:21 -0700 Subject: [PATCH 162/278] [Fix] proper comma parsing of URL-encoded commas (#361) Fixes #311. Followup to #336. --- lib/parse.js | 40 +++++++++++++++++++++++----------------- test/parse.js | 2 +- 2 files changed, 24 insertions(+), 18 deletions(-) diff --git a/lib/parse.js b/lib/parse.js index 5203aa84..fe1cf328 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -37,6 +37,17 @@ var parseArrayValue = function (val, options) { return val; }; +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); +}; + // 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 @@ -85,23 +96,18 @@ var parseValues = function parseQueryStringValues(str, options) { val = options.strictNullHandling ? null : ''; } else { key = options.decoder(part.slice(0, pos), defaults.decoder, charset); - var encodedVal = part.slice(pos + 1); - if (options.comma && encodedVal.indexOf(',') !== -1) { - val = encodedVal.split(',') - .map(function (encodedFragment) { - return options.decoder(encodedFragment, defaults.decoder, charset); - }); - } else { - val = options.decoder(encodedVal, defaults.decoder, charset); - } + val = maybeMap( + parseArrayValue(part.slice(pos + 1), options), + function (encodedVal) { + return options.decoder(encodedVal, defaults.decoder, charset); + } + ); } if (val && options.interpretNumericEntities && charset === 'iso-8859-1') { val = interpretNumericEntities(val); } - val = parseArrayValue(val, options); - if (part.indexOf('[]=') > -1) { val = isArray(val) ? [val] : val; } @@ -116,8 +122,8 @@ var parseValues = function parseQueryStringValues(str, options) { return obj; }; -var parseObject = function (chain, val, options) { - var leaf = parseArrayValue(val, options); +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; @@ -145,13 +151,13 @@ var parseObject = function (chain, val, options) { } } - leaf = obj; + leaf = obj; // eslint-disable-line no-param-reassign } return leaf; }; -var parseKeys = function parseQueryStringKeys(givenKey, val, options) { +var parseKeys = function parseQueryStringKeys(givenKey, val, options, valuesParsed) { if (!givenKey) { return; } @@ -202,7 +208,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) { @@ -253,7 +259,7 @@ 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); } diff --git a/test/parse.js b/test/parse.js index 003d3eb1..a8e76a07 100644 --- a/test/parse.js +++ b/test/parse.js @@ -442,7 +442,7 @@ test('parse()', function (t) { }); 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%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'] }); From bf93c5719c88e4c7ace17d747c62844988a65018 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Wed, 25 Mar 2020 12:10:46 -0700 Subject: [PATCH 163/278] v6.7.2 --- CHANGELOG.md | 4 ++++ dist/qs.js | 32 +++++++++++++++++++++++--------- package.json | 2 +- 3 files changed, 28 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ee2f773a..47e0e93a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## **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) diff --git a/dist/qs.js b/dist/qs.js index 4986a7b8..48bf862f 100644 --- a/dist/qs.js +++ b/dist/qs.js @@ -79,6 +79,17 @@ var parseArrayValue = function (val, options) { return val; }; +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); +}; + // 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 @@ -127,15 +138,18 @@ var parseValues = function parseQueryStringValues(str, options) { 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); + val = maybeMap( + parseArrayValue(part.slice(pos + 1), options), + function (encodedVal) { + return options.decoder(encodedVal, defaults.decoder, charset); + } + ); } if (val && options.interpretNumericEntities && charset === 'iso-8859-1') { val = interpretNumericEntities(val); } - val = parseArrayValue(val, options); - if (part.indexOf('[]=') > -1) { val = isArray(val) ? [val] : val; } @@ -150,8 +164,8 @@ var parseValues = function parseQueryStringValues(str, options) { return obj; }; -var parseObject = function (chain, val, options) { - var leaf = parseArrayValue(val, options); +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; @@ -179,13 +193,13 @@ var parseObject = function (chain, val, options) { } } - leaf = obj; + leaf = obj; // eslint-disable-line no-param-reassign } return leaf; }; -var parseKeys = function parseQueryStringKeys(givenKey, val, options) { +var parseKeys = function parseQueryStringKeys(givenKey, val, options, valuesParsed) { if (!givenKey) { return; } @@ -236,7 +250,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) { @@ -287,7 +301,7 @@ 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); } diff --git a/package.json b/package.json index 3a4b587d..777ffa40 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "qs", "description": "A querystring parser that supports nesting and arrays, with a depth limit", "homepage": "https://github.com/ljharb/qs", - "version": "6.7.1", + "version": "6.7.2", "repository": { "type": "git", "url": "https://github.com/ljharb/qs.git" From fddf182ed4642a8ddc247db6b8da9e837801f6de Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Fri, 28 Aug 2020 22:16:06 -0700 Subject: [PATCH 164/278] [actions] switch Automatic Rebase workflow to `pull_request_target` event --- .github/workflows/rebase.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/rebase.yml b/.github/workflows/rebase.yml index 436cb79d..0c2ad39b 100644 --- a/.github/workflows/rebase.yml +++ b/.github/workflows/rebase.yml @@ -1,6 +1,6 @@ name: Automatic Rebase -on: [pull_request] +on: [pull_request_target] jobs: _: From 049c9bb6bab81c522ad3a30dd7d9d466f57ddfe1 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Tue, 12 Jan 2021 19:58:06 -0800 Subject: [PATCH 165/278] [meta] add "Allow Edits" workflow; update rebase workflow --- .github/workflows/rebase.yml | 2 +- .github/workflows/require-allow-edits.yml | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/require-allow-edits.yml diff --git a/.github/workflows/rebase.yml b/.github/workflows/rebase.yml index 0c2ad39b..027aed07 100644 --- a/.github/workflows/rebase.yml +++ b/.github/workflows/rebase.yml @@ -9,7 +9,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v2 - uses: ljharb/rebase@master env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/require-allow-edits.yml b/.github/workflows/require-allow-edits.yml new file mode 100644 index 00000000..549d7b48 --- /dev/null +++ b/.github/workflows/require-allow-edits.yml @@ -0,0 +1,12 @@ +name: Require “Allow Edits” + +on: [pull_request_target] + +jobs: + _: + name: "Require “Allow Edits”" + + runs-on: ubuntu-latest + + steps: + - uses: ljharb/require-allow-edits@main From fcb1ef1819ad79ab684ac793f3772dcf2ba340fa Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Tue, 12 Jan 2021 20:08:11 -0800 Subject: [PATCH 166/278] [Tests] run `nyc` on all tests; use `tape` runner --- .eslintignore | 3 ++- .eslintrc | 16 +++++++++++++++- .gitignore | 4 ++++ .nycrc | 13 +++++++++++++ lib/parse.js | 2 +- package.json | 9 ++++----- test/.eslintrc | 18 ------------------ test/index.js | 7 ------- 8 files changed, 39 insertions(+), 33 deletions(-) create mode 100644 .nycrc delete mode 100644 test/.eslintrc delete mode 100644 test/index.js diff --git a/.eslintignore b/.eslintignore index 1521c8b7..a60030e3 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1 +1,2 @@ -dist +dist/ +coverage/ diff --git a/.eslintrc b/.eslintrc index e3bde898..e448a2f5 100644 --- a/.eslintrc +++ b/.eslintrc @@ -17,5 +17,19 @@ "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, + } + } + ] } diff --git a/.gitignore b/.gitignore index 645ff13c..82dda142 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,7 @@ dist/* yarn.lock package-lock.json npm-shrinkwrap.json + +# coverage output +coverage/ +.nyc_output/ diff --git a/.nycrc b/.nycrc new file mode 100644 index 00000000..1d57cabe --- /dev/null +++ b/.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/lib/parse.js b/lib/parse.js index 1751c7be..553498b4 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -140,7 +140,7 @@ var parseObject = function (chain, val, options, valuesParsed) { } } - leaf = obj; // eslint-disable-line no-param-reassign + leaf = obj; } return leaf; diff --git a/package.json b/package.json index 6f702069..2b247a95 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,6 @@ "@ljharb/eslint-config": "^17.1.0", "aud": "^1.1.2", "browserify": "^16.5.1", - "covert": "^1.1.1", "eclint": "^2.8.1", "eslint": "^7.3.0", "evalmd": "^0.0.19", @@ -42,6 +41,7 @@ "has-symbols": "^1.0.1", "iconv-lite": "^0.5.1", "mkdirp": "^0.5.5", + "nyc": "^10.3.2", "object-inspect": "^1.8.0", "qs-iconv": "^1.0.4", "safe-publish-latest": "^1.1.4", @@ -51,13 +51,12 @@ "scripts": { "prepublish": "safe-publish-latest && npm run dist", "pretest": "npm run --silent readme && npm run --silent lint", - "test": "npm run --silent coverage", - "tests-only": "node test", - "posttest": "npx aud --production", + "test": "npm run tests-only", + "tests-only": "nyc tape 'test/**/*.js'", + "posttest": "aud --production", "readme": "evalmd README.md", "postlint": "eclint check * lib/* test/*", "lint": "eslint lib/*.js test/*.js", - "coverage": "covert test", "dist": "mkdirp dist && browserify --standalone Qs lib/index.js > dist/qs.js" }, "license": "BSD-3-Clause", diff --git a/test/.eslintrc b/test/.eslintrc deleted file mode 100644 index 8ab8536a..00000000 --- a/test/.eslintrc +++ /dev/null @@ -1,18 +0,0 @@ -{ - "rules": { - "array-bracket-newline": 0, - "array-element-newline": 0, - "consistent-return": 2, - "function-paren-newline": 0, - "max-lines": 0, - "max-lines-per-function": 0, - "max-nested-callbacks": [2, 3], - "max-statements": 0, - "no-buffer-constructor": 0, - "no-extend-native": 0, - "no-magic-numbers": 0, - "no-throw-literal": 0, - "object-curly-newline": 0, - "sort-keys": 0, - } -} diff --git a/test/index.js b/test/index.js deleted file mode 100644 index 5e6bc8fb..00000000 --- a/test/index.js +++ /dev/null @@ -1,7 +0,0 @@ -'use strict'; - -require('./parse'); - -require('./stringify'); - -require('./utils'); From 01aaffdfc5f39c47ee7b4296b3e3b5f2c085d3c3 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Tue, 12 Jan 2021 20:08:35 -0800 Subject: [PATCH 167/278] [Tests] migrate tests to Github Actions --- .github/workflows/node-4+.yml | 54 ++++++++++++++++++++++++++++ .github/workflows/node-iojs.yml | 58 ++++++++++++++++++++++++++++++ .github/workflows/node-pretest.yml | 26 ++++++++++++++ .github/workflows/node-zero.yml | 58 ++++++++++++++++++++++++++++++ .travis.yml | 12 ------- 5 files changed, 196 insertions(+), 12 deletions(-) create mode 100644 .github/workflows/node-4+.yml create mode 100644 .github/workflows/node-iojs.yml create mode 100644 .github/workflows/node-pretest.yml create mode 100644 .github/workflows/node-zero.yml delete mode 100644 .travis.yml diff --git a/.github/workflows/node-4+.yml b/.github/workflows/node-4+.yml new file mode 100644 index 00000000..ba174e1d --- /dev/null +++ b/.github/workflows/node-4+.yml @@ -0,0 +1,54 @@ +name: 'Tests: node.js' + +on: [pull_request, push] + +jobs: + matrix: + runs-on: ubuntu-latest + outputs: + latest: ${{ steps.set-matrix.outputs.requireds }} + minors: ${{ steps.set-matrix.outputs.optionals }} + steps: + - uses: ljharb/actions/node/matrix@main + id: set-matrix + with: + preset: '>=4' + + latest: + needs: [matrix] + name: 'latest minors' + runs-on: ubuntu-latest + + strategy: + matrix: ${{ fromJson(needs.matrix.outputs.latest) }} + + steps: + - uses: actions/checkout@v2 + - uses: ljharb/actions/node/run@main + name: 'npm install && npm run tests-only' + with: + node-version: ${{ matrix.node-version }} + command: 'tests-only' + minors: + needs: [matrix, latest] + name: 'non-latest minors' + continue-on-error: true + if: ${{ !github.head_ref || !startsWith(github.head_ref, 'renovate') }} + runs-on: ubuntu-latest + + strategy: + matrix: ${{ fromJson(needs.matrix.outputs.minors) }} + + steps: + - uses: actions/checkout@v2 + - uses: ljharb/actions/node/run@main + with: + node-version: ${{ matrix.node-version }} + command: 'tests-only' + + node: + name: 'node 4+' + needs: [latest, minors] + runs-on: ubuntu-latest + steps: + - run: 'echo tests completed' diff --git a/.github/workflows/node-iojs.yml b/.github/workflows/node-iojs.yml new file mode 100644 index 00000000..f707c3cf --- /dev/null +++ b/.github/workflows/node-iojs.yml @@ -0,0 +1,58 @@ +name: 'Tests: node.js (io.js)' + +on: [pull_request, push] + +jobs: + matrix: + runs-on: ubuntu-latest + outputs: + latest: ${{ steps.set-matrix.outputs.requireds }} + minors: ${{ steps.set-matrix.outputs.optionals }} + steps: + - uses: ljharb/actions/node/matrix@main + id: set-matrix + with: + preset: 'iojs' + + latest: + needs: [matrix] + name: 'latest minors' + runs-on: ubuntu-latest + + strategy: + matrix: ${{ fromJson(needs.matrix.outputs.latest) }} + + steps: + - uses: actions/checkout@v2 + - uses: ljharb/actions/node/run@main + name: 'npm install && npm run tests-only' + with: + node-version: ${{ matrix.node-version }} + command: 'tests-only' + skip-ls-check: true + + minors: + needs: [matrix, latest] + name: 'non-latest minors' + continue-on-error: true + if: ${{ !github.head_ref || !startsWith(github.head_ref, 'renovate') }} + runs-on: ubuntu-latest + + strategy: + matrix: ${{ fromJson(needs.matrix.outputs.minors) }} + + steps: + - uses: actions/checkout@v2 + - uses: ljharb/actions/node/run@main + name: 'npm install && npm run tests-only' + with: + node-version: ${{ matrix.node-version }} + command: 'tests-only' + skip-ls-check: true + + node: + name: 'io.js' + needs: [latest, minors] + runs-on: ubuntu-latest + steps: + - run: 'echo tests completed' diff --git a/.github/workflows/node-pretest.yml b/.github/workflows/node-pretest.yml new file mode 100644 index 00000000..3921e0ae --- /dev/null +++ b/.github/workflows/node-pretest.yml @@ -0,0 +1,26 @@ +name: 'Tests: pretest/posttest' + +on: [pull_request, push] + +jobs: + pretest: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - uses: ljharb/actions/node/run@main + name: 'npm install && npm run pretest' + with: + node-version: 'lts/*' + command: 'pretest' + + posttest: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - uses: ljharb/actions/node/run@main + name: 'npm install && npm run posttest' + with: + node-version: 'lts/*' + command: 'posttest' diff --git a/.github/workflows/node-zero.yml b/.github/workflows/node-zero.yml new file mode 100644 index 00000000..d044c603 --- /dev/null +++ b/.github/workflows/node-zero.yml @@ -0,0 +1,58 @@ +name: 'Tests: node.js (0.x)' + +on: [pull_request, push] + +jobs: + matrix: + runs-on: ubuntu-latest + outputs: + stable: ${{ steps.set-matrix.outputs.requireds }} + unstable: ${{ steps.set-matrix.outputs.optionals }} + steps: + - uses: ljharb/actions/node/matrix@main + id: set-matrix + with: + preset: '0.x' + + stable: + needs: [matrix] + name: 'stable minors' + runs-on: ubuntu-latest + + strategy: + matrix: ${{ fromJson(needs.matrix.outputs.stable) }} + + steps: + - uses: actions/checkout@v2 + - uses: ljharb/actions/node/run@main + with: + node-version: ${{ matrix.node-version }} + command: 'tests-only' + cache-node-modules-key: node_modules-${{ github.workflow }}-${{ github.action }}-${{ github.run_id }} + skip-ls-check: true + + unstable: + needs: [matrix, stable] + name: 'unstable minors' + continue-on-error: true + if: ${{ !github.head_ref || !startsWith(github.head_ref, 'renovate') }} + runs-on: ubuntu-latest + + strategy: + matrix: ${{ fromJson(needs.matrix.outputs.unstable) }} + + steps: + - uses: actions/checkout@v2 + - uses: ljharb/actions/node/run@main + with: + node-version: ${{ matrix.node-version }} + command: 'tests-only' + cache-node-modules-key: node_modules-${{ github.workflow }}-${{ github.action }}-${{ github.run_id }} + skip-ls-check: true + + node: + name: 'node 0.x' + needs: [stable, unstable] + runs-on: ubuntu-latest + steps: + - run: 'echo tests completed' diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 7709ba6d..00000000 --- a/.travis.yml +++ /dev/null @@ -1,12 +0,0 @@ -version: ~> 1.0 -language: node_js -os: - - linux -import: - - ljharb/travis-ci:node/all.yml - - ljharb/travis-ci:node/pretest.yml - - ljharb/travis-ci:node/posttest.yml - - ljharb/travis-ci:node/coverage.yml -matrix: - allow_failures: - - env: COVERAGE=true # temporarily allow this to fail From 9c60d5318865648ed10074b09bef04b5f04b8fda Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Tue, 12 Jan 2021 19:33:22 -0800 Subject: [PATCH 168/278] [Refactor] `format`: remove `util.assign` call --- lib/formats.js | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/lib/formats.js b/lib/formats.js index a4ecca78..f36cf206 100644 --- a/lib/formats.js +++ b/lib/formats.js @@ -3,24 +3,21 @@ var replace = String.prototype.replace; var percentTwenties = /%20/g; -var util = require('./utils'); - var Format = { RFC1738: 'RFC1738', RFC3986: 'RFC3986' }; -module.exports = util.assign( - { - 'default': Format.RFC3986, - formatters: { - RFC1738: function (value) { - return replace.call(value, percentTwenties, '+'); - }, - RFC3986: function (value) { - return String(value); - } +module.exports = { + 'default': Format.RFC3986, + formatters: { + RFC1738: function (value) { + return replace.call(value, percentTwenties, '+'); + }, + RFC3986: function (value) { + return String(value); } }, - Format -); + RFC1738: Format.RFC1738, + RFC3986: Format.RFC3986 +}; From 4e7a5a38bb0332662250cee1b05fa97fc70a62d1 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Tue, 12 Jan 2021 19:57:15 -0800 Subject: [PATCH 169/278] [Fix] `stringify`: do not encode parens for RFC1738 Fixes #390 --- lib/stringify.js | 10 +++++++--- lib/utils.js | 5 ++++- test/stringify.js | 7 ++++++- 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/lib/stringify.js b/lib/stringify.js index da5380a8..f46bb0e1 100644 --- a/lib/stringify.js +++ b/lib/stringify.js @@ -65,6 +65,7 @@ var stringify = function stringify( sort, allowDots, serializeDate, + format, formatter, encodeValuesOnly, charset @@ -85,7 +86,7 @@ var stringify = function stringify( if (obj === null) { if (strictNullHandling) { - return encoder && !encodeValuesOnly ? encoder(prefix, defaults.encoder, charset, 'key') : prefix; + return encoder && !encodeValuesOnly ? encoder(prefix, defaults.encoder, charset, 'key', format) : prefix; } obj = ''; @@ -93,8 +94,8 @@ var stringify = function stringify( if (isNonNullishPrimitive(obj) || utils.isBuffer(obj)) { if (encoder) { - var keyValue = encodeValuesOnly ? prefix : encoder(prefix, defaults.encoder, charset, 'key'); - return [formatter(keyValue) + '=' + formatter(encoder(obj, defaults.encoder, charset, 'value'))]; + 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))]; } @@ -139,6 +140,7 @@ var stringify = function stringify( sort, allowDots, serializeDate, + format, formatter, encodeValuesOnly, charset @@ -186,6 +188,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, @@ -251,6 +254,7 @@ module.exports = function (object, opts) { options.sort, options.allowDots, options.serializeDate, + options.format, options.formatter, options.encodeValuesOnly, options.charset diff --git a/lib/utils.js b/lib/utils.js index 98ab1937..4ad6ea27 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -1,5 +1,7 @@ 'use strict'; +var formats = require('./formats'); + var has = Object.prototype.hasOwnProperty; var isArray = Array.isArray; @@ -120,7 +122,7 @@ 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) { @@ -152,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; diff --git a/test/stringify.js b/test/stringify.js index cbd90b2c..f4a1daa7 100644 --- a/test/stringify.js +++ b/test/stringify.js @@ -631,10 +631,13 @@ test('stringify()', function (t) { 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(); }); @@ -642,12 +645,14 @@ test('stringify()', function (t) { 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(); }); From da6d2497e66f06fc5ef56a99172ea77556880ef5 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Tue, 12 Jan 2021 20:45:55 -0800 Subject: [PATCH 170/278] [Dev Deps] update `eslint`, `@ljharb/eslint-config`, `aud`, `browserify`, `object-inspect`, `tape` --- .editorconfig | 6 ++++++ package.json | 12 ++++++------ 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/.editorconfig b/.editorconfig index b442a9f9..91040dde 100644 --- a/.editorconfig +++ b/.editorconfig @@ -31,3 +31,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 diff --git a/package.json b/package.json index 2b247a95..80efdb91 100644 --- a/package.json +++ b/package.json @@ -31,22 +31,22 @@ }, "dependencies": {}, "devDependencies": { - "@ljharb/eslint-config": "^17.1.0", - "aud": "^1.1.2", - "browserify": "^16.5.1", + "@ljharb/eslint-config": "^17.3.0", + "aud": "^1.1.3", + "browserify": "^16.5.2", "eclint": "^2.8.1", - "eslint": "^7.3.0", + "eslint": "^7.17.0", "evalmd": "^0.0.19", "for-each": "^0.3.3", "has-symbols": "^1.0.1", "iconv-lite": "^0.5.1", "mkdirp": "^0.5.5", "nyc": "^10.3.2", - "object-inspect": "^1.8.0", + "object-inspect": "^1.9.0", "qs-iconv": "^1.0.4", "safe-publish-latest": "^1.1.4", "safer-buffer": "^2.1.2", - "tape": "^5.0.1" + "tape": "^5.1.1" }, "scripts": { "prepublish": "safe-publish-latest && npm run dist", From 66202e74e326f2d7bf10fd7bdc47c45bba16c3b3 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Wed, 13 Jan 2021 08:00:23 -0800 Subject: [PATCH 171/278] [Tests] `stringify`: add tests for #378 --- test/stringify.js | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/test/stringify.js b/test/stringify.js index f4a1daa7..7f0ec70c 100644 --- a/test/stringify.js +++ b/test/stringify.js @@ -772,5 +772,22 @@ test('stringify()', function (t) { 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(obj, { encode: false, arrayFormat: 'comma' }), '???', 'array, comma (pending issue #378)', { skip: true }); + + st.end(); + }); + t.end(); }); From d4f6c3219907c221f8ca45406092c9504f20a46b Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Wed, 13 Jan 2021 08:29:10 -0800 Subject: [PATCH 172/278] [meta] do not publish github action workflow files --- .npmignore | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/.npmignore b/.npmignore index ac980d91..d775cdce 100644 --- a/.npmignore +++ b/.npmignore @@ -1,4 +1,21 @@ +# gitignore +npm-debug.log +node_modules +.DS_Store + +# build output +dist/* + +# Only apps should have lockfiles +yarn.lock +package-lock.json +npm-shrinkwrap.json + +# coverage output +coverage/ +.nyc_output/ + bower.json component.json .npmignore -.travis.yml +.github/workflows From 179fafc920123e60466a1729f9f2b43b2fd67212 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Wed, 13 Jan 2021 08:27:37 -0800 Subject: [PATCH 173/278] v6.9.5 --- CHANGELOG.md | 11 +++++++++++ dist/qs.js | 55 +++++++++++++++++++++++++++++----------------------- package.json | 2 +- 3 files changed, 43 insertions(+), 25 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d18060b2..0d4aecff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,14 @@ +## **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) diff --git a/dist/qs.js b/dist/qs.js index a6204f25..861a6f13 100644 --- a/dist/qs.js +++ b/dist/qs.js @@ -4,29 +4,26 @@ var replace = String.prototype.replace; var percentTwenties = /%20/g; -var util = require('./utils'); - var Format = { RFC1738: 'RFC1738', RFC3986: 'RFC3986' }; -module.exports = util.assign( - { - 'default': Format.RFC3986, - formatters: { - RFC1738: function (value) { - return replace.call(value, percentTwenties, '+'); - }, - RFC3986: function (value) { - return String(value); - } +module.exports = { + 'default': Format.RFC3986, + formatters: { + RFC1738: function (value) { + return replace.call(value, percentTwenties, '+'); + }, + RFC3986: function (value) { + return String(value); } }, - Format -); + RFC1738: Format.RFC1738, + RFC3986: Format.RFC3986 +}; -},{"./utils":5}],2:[function(require,module,exports){ +},{}],2:[function(require,module,exports){ 'use strict'; var stringify = require('./stringify'); @@ -182,7 +179,7 @@ var parseObject = function (chain, val, options, valuesParsed) { } } - leaf = obj; // eslint-disable-line no-param-reassign + leaf = obj; } return leaf; @@ -366,6 +363,7 @@ var stringify = function stringify( sort, allowDots, serializeDate, + format, formatter, encodeValuesOnly, charset @@ -381,12 +379,12 @@ var stringify = function stringify( return serializeDate(value); } return value; - }).join(','); + }); } if (obj === null) { if (strictNullHandling) { - return encoder && !encodeValuesOnly ? encoder(prefix, defaults.encoder, charset, 'key') : prefix; + return encoder && !encodeValuesOnly ? encoder(prefix, defaults.encoder, charset, 'key', format) : prefix; } obj = ''; @@ -394,8 +392,8 @@ var stringify = function stringify( if (isNonNullishPrimitive(obj) || utils.isBuffer(obj)) { if (encoder) { - var keyValue = encodeValuesOnly ? prefix : encoder(prefix, defaults.encoder, charset, 'key'); - return [formatter(keyValue) + '=' + formatter(encoder(obj, defaults.encoder, charset, 'value'))]; + 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))]; } @@ -407,7 +405,10 @@ var stringify = function stringify( } var objKeys; - if (isArray(filter)) { + if (generateArrayPrefix === 'comma' && isArray(obj)) { + // we need to join elements in + objKeys = [{ value: obj.length > 0 ? obj.join(',') || null : undefined }]; + } else if (isArray(filter)) { objKeys = filter; } else { var keys = Object.keys(obj); @@ -416,7 +417,7 @@ var stringify = function stringify( for (var i = 0; i < objKeys.length; ++i) { var key = objKeys[i]; - var value = obj[key]; + var value = typeof key === 'object' && key.value !== undefined ? key.value : obj[key]; if (skipNulls && value === null) { continue; @@ -437,6 +438,7 @@ var stringify = function stringify( sort, allowDots, serializeDate, + format, formatter, encodeValuesOnly, charset @@ -484,6 +486,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, @@ -549,6 +552,7 @@ module.exports = function (object, opts) { options.sort, options.allowDots, options.serializeDate, + options.format, options.formatter, options.encodeValuesOnly, options.charset @@ -574,6 +578,8 @@ module.exports = function (object, opts) { },{"./formats":1,"./utils":5}],5:[function(require,module,exports){ 'use strict'; +var formats = require('./formats'); + var has = Object.prototype.hasOwnProperty; var isArray = Array.isArray; @@ -694,7 +700,7 @@ 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) { @@ -726,6 +732,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; @@ -821,5 +828,5 @@ module.exports = { merge: merge }; -},{}]},{},[2])(2) +},{"./formats":1}]},{},[2])(2) }); diff --git a/package.json b/package.json index 80efdb91..cf92e49e 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "qs", "description": "A querystring parser that supports nesting and arrays, with a depth limit", "homepage": "https://github.com/ljharb/qs", - "version": "6.9.4", + "version": "6.9.5", "repository": { "type": "git", "url": "https://github.com/ljharb/qs.git" From 47d0b8348b4703b4d6321ffcfb98a458437954a9 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Wed, 13 Jan 2021 20:52:34 -0800 Subject: [PATCH 174/278] [Fix] restore `dist` dir; mistakenly removed in d4f6c32 See https://github.com/ljharb/qs/issues/148#issuecomment-759924223 --- .npmignore | 3 --- 1 file changed, 3 deletions(-) diff --git a/.npmignore b/.npmignore index d775cdce..c53769e2 100644 --- a/.npmignore +++ b/.npmignore @@ -3,9 +3,6 @@ npm-debug.log node_modules .DS_Store -# build output -dist/* - # Only apps should have lockfiles yarn.lock package-lock.json From b522d2e9993a47afd810ed9a19d35aadb6323988 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Wed, 13 Jan 2021 20:52:58 -0800 Subject: [PATCH 175/278] v6.9.6 --- CHANGELOG.md | 3 +++ package.json | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0d4aecff..d43209c3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +## **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) diff --git a/package.json b/package.json index cf92e49e..5e6b8efc 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "qs", "description": "A querystring parser that supports nesting and arrays, with a depth limit", "homepage": "https://github.com/ljharb/qs", - "version": "6.9.5", + "version": "6.9.6", "repository": { "type": "git", "url": "https://github.com/ljharb/qs.git" From 2a1d3317ad935f9acf77e57f85c8dfa69f773405 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Fri, 12 Feb 2021 21:29:59 -0800 Subject: [PATCH 176/278] [Dev Deps] update `eslint`, `@ljharb/eslint-config`, `aud` --- package.json | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 5e6b8efc..331909a8 100644 --- a/package.json +++ b/package.json @@ -29,13 +29,12 @@ "engines": { "node": ">=0.6" }, - "dependencies": {}, "devDependencies": { - "@ljharb/eslint-config": "^17.3.0", - "aud": "^1.1.3", + "@ljharb/eslint-config": "^17.5.1", + "aud": "^1.1.4", "browserify": "^16.5.2", "eclint": "^2.8.1", - "eslint": "^7.17.0", + "eslint": "^7.20.0", "evalmd": "^0.0.19", "for-each": "^0.3.3", "has-symbols": "^1.0.1", From facbd0e8d0f6d9a84c22058e40c9f3679aa0dee0 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Tue, 16 Feb 2021 16:51:32 -0800 Subject: [PATCH 177/278] Revert "[meta] ignore eclint transitive audit warning" This reverts commit 5af2bf8553e90217a51cbae9ae69053fa214e600. --- .npmrc | 1 - 1 file changed, 1 deletion(-) diff --git a/.npmrc b/.npmrc index fc227f38..43c97e71 100644 --- a/.npmrc +++ b/.npmrc @@ -1,2 +1 @@ package-lock=false -audit-level=moderate From f399189db39b382b729c0df14afd10f3adbf8239 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Sat, 8 Jan 2022 21:24:12 -0800 Subject: [PATCH 178/278] [Dev Deps] backport updates from main --- .eslintignore | 2 -- .eslintrc | 9 ++++++--- lib/utils.js | 1 + package.json | 23 ++++++++++++----------- 4 files changed, 19 insertions(+), 16 deletions(-) delete mode 100644 .eslintignore diff --git a/.eslintignore b/.eslintignore deleted file mode 100644 index a60030e3..00000000 --- a/.eslintignore +++ /dev/null @@ -1,2 +0,0 @@ -dist/ -coverage/ diff --git a/.eslintrc b/.eslintrc index e448a2f5..6884760e 100644 --- a/.eslintrc +++ b/.eslintrc @@ -3,20 +3,23 @@ "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": [ diff --git a/lib/utils.js b/lib/utils.js index 4ad6ea27..1e545381 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -177,6 +177,7 @@ var encode = function encode(str, defaultEncoder, charset, kind, format) { 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)] diff --git a/package.json b/package.json index 5e6b8efc..4bf240cd 100644 --- a/package.json +++ b/package.json @@ -29,34 +29,35 @@ "engines": { "node": ">=0.6" }, - "dependencies": {}, "devDependencies": { - "@ljharb/eslint-config": "^17.3.0", - "aud": "^1.1.3", + "@ljharb/eslint-config": "^20.1.0", + "aud": "^1.1.5", "browserify": "^16.5.2", "eclint": "^2.8.1", - "eslint": "^7.17.0", + "eslint": "^8.6.0", "evalmd": "^0.0.19", "for-each": "^0.3.3", - "has-symbols": "^1.0.1", + "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.9.0", + "object-inspect": "^1.12.0", "qs-iconv": "^1.0.4", - "safe-publish-latest": "^1.1.4", + "safe-publish-latest": "^2.0.0", "safer-buffer": "^2.1.2", - "tape": "^5.1.1" + "tape": "^5.4.0" }, "scripts": { - "prepublish": "safe-publish-latest && npm run dist", + "prepublishOnly": "safe-publish-latest && npm run dist", + "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": "aud --production", "readme": "evalmd README.md", - "postlint": "eclint check * lib/* test/*", - "lint": "eslint lib/*.js test/*.js", + "postlint": "eclint check * lib/* test/* !dist/*", + "lint": "eslint .", "dist": "mkdirp dist && browserify --standalone Qs lib/index.js > dist/qs.js" }, "license": "BSD-3-Clause", From e6cfd8bda02143678f57a7eb441cca2183620dfc Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Sat, 8 Jan 2022 21:24:45 -0800 Subject: [PATCH 179/278] [actions] backport actions from main --- .github/workflows/node-4+.yml | 54 --------------------- .github/workflows/node-aught.yml | 18 +++++++ .github/workflows/node-iojs.yml | 58 ----------------------- .github/workflows/node-pretest.yml | 23 +-------- .github/workflows/node-tens.yml | 18 +++++++ .github/workflows/node-zero.yml | 58 ----------------------- .github/workflows/rebase.yml | 8 ++-- .github/workflows/require-allow-edits.yml | 2 +- 8 files changed, 43 insertions(+), 196 deletions(-) delete mode 100644 .github/workflows/node-4+.yml create mode 100644 .github/workflows/node-aught.yml delete mode 100644 .github/workflows/node-iojs.yml create mode 100644 .github/workflows/node-tens.yml delete mode 100644 .github/workflows/node-zero.yml diff --git a/.github/workflows/node-4+.yml b/.github/workflows/node-4+.yml deleted file mode 100644 index ba174e1d..00000000 --- a/.github/workflows/node-4+.yml +++ /dev/null @@ -1,54 +0,0 @@ -name: 'Tests: node.js' - -on: [pull_request, push] - -jobs: - matrix: - runs-on: ubuntu-latest - outputs: - latest: ${{ steps.set-matrix.outputs.requireds }} - minors: ${{ steps.set-matrix.outputs.optionals }} - steps: - - uses: ljharb/actions/node/matrix@main - id: set-matrix - with: - preset: '>=4' - - latest: - needs: [matrix] - name: 'latest minors' - runs-on: ubuntu-latest - - strategy: - matrix: ${{ fromJson(needs.matrix.outputs.latest) }} - - steps: - - uses: actions/checkout@v2 - - uses: ljharb/actions/node/run@main - name: 'npm install && npm run tests-only' - with: - node-version: ${{ matrix.node-version }} - command: 'tests-only' - minors: - needs: [matrix, latest] - name: 'non-latest minors' - continue-on-error: true - if: ${{ !github.head_ref || !startsWith(github.head_ref, 'renovate') }} - runs-on: ubuntu-latest - - strategy: - matrix: ${{ fromJson(needs.matrix.outputs.minors) }} - - steps: - - uses: actions/checkout@v2 - - uses: ljharb/actions/node/run@main - with: - node-version: ${{ matrix.node-version }} - command: 'tests-only' - - node: - name: 'node 4+' - needs: [latest, minors] - runs-on: ubuntu-latest - steps: - - run: 'echo tests completed' diff --git a/.github/workflows/node-aught.yml b/.github/workflows/node-aught.yml new file mode 100644 index 00000000..f3cddd85 --- /dev/null +++ b/.github/workflows/node-aught.yml @@ -0,0 +1,18 @@ +name: 'Tests: node.js < 10' + +on: [pull_request, push] + +jobs: + tests: + uses: ljharb/actions/.github/workflows/node.yml@main + with: + range: '< 10' + type: minors + command: npm run tests-only + + node: + name: 'node < 10' + needs: [tests] + runs-on: ubuntu-latest + steps: + - run: 'echo tests completed' diff --git a/.github/workflows/node-iojs.yml b/.github/workflows/node-iojs.yml deleted file mode 100644 index f707c3cf..00000000 --- a/.github/workflows/node-iojs.yml +++ /dev/null @@ -1,58 +0,0 @@ -name: 'Tests: node.js (io.js)' - -on: [pull_request, push] - -jobs: - matrix: - runs-on: ubuntu-latest - outputs: - latest: ${{ steps.set-matrix.outputs.requireds }} - minors: ${{ steps.set-matrix.outputs.optionals }} - steps: - - uses: ljharb/actions/node/matrix@main - id: set-matrix - with: - preset: 'iojs' - - latest: - needs: [matrix] - name: 'latest minors' - runs-on: ubuntu-latest - - strategy: - matrix: ${{ fromJson(needs.matrix.outputs.latest) }} - - steps: - - uses: actions/checkout@v2 - - uses: ljharb/actions/node/run@main - name: 'npm install && npm run tests-only' - with: - node-version: ${{ matrix.node-version }} - command: 'tests-only' - skip-ls-check: true - - minors: - needs: [matrix, latest] - name: 'non-latest minors' - continue-on-error: true - if: ${{ !github.head_ref || !startsWith(github.head_ref, 'renovate') }} - runs-on: ubuntu-latest - - strategy: - matrix: ${{ fromJson(needs.matrix.outputs.minors) }} - - steps: - - uses: actions/checkout@v2 - - uses: ljharb/actions/node/run@main - name: 'npm install && npm run tests-only' - with: - node-version: ${{ matrix.node-version }} - command: 'tests-only' - skip-ls-check: true - - node: - name: 'io.js' - needs: [latest, minors] - runs-on: ubuntu-latest - steps: - - run: 'echo tests completed' diff --git a/.github/workflows/node-pretest.yml b/.github/workflows/node-pretest.yml index 3921e0ae..765edf79 100644 --- a/.github/workflows/node-pretest.yml +++ b/.github/workflows/node-pretest.yml @@ -3,24 +3,5 @@ name: 'Tests: pretest/posttest' on: [pull_request, push] jobs: - pretest: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v2 - - uses: ljharb/actions/node/run@main - name: 'npm install && npm run pretest' - with: - node-version: 'lts/*' - command: 'pretest' - - posttest: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v2 - - uses: ljharb/actions/node/run@main - name: 'npm install && npm run posttest' - with: - node-version: 'lts/*' - command: 'posttest' + tests: + uses: ljharb/actions/.github/workflows/pretest.yml@main diff --git a/.github/workflows/node-tens.yml b/.github/workflows/node-tens.yml new file mode 100644 index 00000000..b49ceb1f --- /dev/null +++ b/.github/workflows/node-tens.yml @@ -0,0 +1,18 @@ +name: 'Tests: node.js >= 10' + +on: [pull_request, push] + +jobs: + tests: + uses: ljharb/actions/.github/workflows/node.yml@main + with: + range: '>= 10' + type: minors + command: npm run tests-only + + node: + name: 'node >= 10' + needs: [tests] + runs-on: ubuntu-latest + steps: + - run: 'echo tests completed' diff --git a/.github/workflows/node-zero.yml b/.github/workflows/node-zero.yml deleted file mode 100644 index d044c603..00000000 --- a/.github/workflows/node-zero.yml +++ /dev/null @@ -1,58 +0,0 @@ -name: 'Tests: node.js (0.x)' - -on: [pull_request, push] - -jobs: - matrix: - runs-on: ubuntu-latest - outputs: - stable: ${{ steps.set-matrix.outputs.requireds }} - unstable: ${{ steps.set-matrix.outputs.optionals }} - steps: - - uses: ljharb/actions/node/matrix@main - id: set-matrix - with: - preset: '0.x' - - stable: - needs: [matrix] - name: 'stable minors' - runs-on: ubuntu-latest - - strategy: - matrix: ${{ fromJson(needs.matrix.outputs.stable) }} - - steps: - - uses: actions/checkout@v2 - - uses: ljharb/actions/node/run@main - with: - node-version: ${{ matrix.node-version }} - command: 'tests-only' - cache-node-modules-key: node_modules-${{ github.workflow }}-${{ github.action }}-${{ github.run_id }} - skip-ls-check: true - - unstable: - needs: [matrix, stable] - name: 'unstable minors' - continue-on-error: true - if: ${{ !github.head_ref || !startsWith(github.head_ref, 'renovate') }} - runs-on: ubuntu-latest - - strategy: - matrix: ${{ fromJson(needs.matrix.outputs.unstable) }} - - steps: - - uses: actions/checkout@v2 - - uses: ljharb/actions/node/run@main - with: - node-version: ${{ matrix.node-version }} - command: 'tests-only' - cache-node-modules-key: node_modules-${{ github.workflow }}-${{ github.action }}-${{ github.run_id }} - skip-ls-check: true - - node: - name: 'node 0.x' - needs: [stable, unstable] - runs-on: ubuntu-latest - steps: - - run: 'echo tests completed' diff --git a/.github/workflows/rebase.yml b/.github/workflows/rebase.yml index 027aed07..5b6d04b8 100644 --- a/.github/workflows/rebase.yml +++ b/.github/workflows/rebase.yml @@ -9,7 +9,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - uses: ljharb/rebase@master - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - uses: actions/checkout@v2 + - uses: ljharb/rebase@master + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/require-allow-edits.yml b/.github/workflows/require-allow-edits.yml index 549d7b48..7b842f89 100644 --- a/.github/workflows/require-allow-edits.yml +++ b/.github/workflows/require-allow-edits.yml @@ -9,4 +9,4 @@ jobs: runs-on: ubuntu-latest steps: - - uses: ljharb/require-allow-edits@main + - uses: ljharb/require-allow-edits@main From c44f0c59bb508ef22563ca07d9d3000c742fbee2 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Tue, 16 Feb 2021 16:51:32 -0800 Subject: [PATCH 180/278] Revert "[meta] ignore eclint transitive audit warning" This reverts commit 5af2bf8553e90217a51cbae9ae69053fa214e600. --- .npmrc | 1 - 1 file changed, 1 deletion(-) diff --git a/.npmrc b/.npmrc index fc227f38..43c97e71 100644 --- a/.npmrc +++ b/.npmrc @@ -1,2 +1 @@ package-lock=false -audit-level=moderate From e40c0e007cf3edcca354a678888c0c7f0f9af8b8 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Wed, 17 Feb 2021 14:14:36 -0800 Subject: [PATCH 181/278] [meta] only run `npm run dist` in publish, not install --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 331909a8..cb9c122e 100644 --- a/package.json +++ b/package.json @@ -39,6 +39,7 @@ "for-each": "^0.3.3", "has-symbols": "^1.0.1", "iconv-lite": "^0.5.1", + "in-publish": "^2.0.1", "mkdirp": "^0.5.5", "nyc": "^10.3.2", "object-inspect": "^1.9.0", @@ -48,7 +49,7 @@ "tape": "^5.1.1" }, "scripts": { - "prepublish": "safe-publish-latest && npm run dist", + "prepublish": "safe-publish-latest && (not-in-publish || npm run dist)", "pretest": "npm run --silent readme && npm run --silent lint", "test": "npm run tests-only", "tests-only": "nyc tape 'test/**/*.js'", From 4e2911fd384966f141c3deea449bd0c0e9e5c0c4 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Wed, 17 Mar 2021 13:38:34 -0700 Subject: [PATCH 182/278] [Tests] use `ljharb/actions/node/install` instead of `ljharb/actions/node/run` --- .github/workflows/node-4+.yml | 14 +++++++++----- .github/workflows/node-iojs.yml | 14 ++++++++------ .github/workflows/node-pretest.yml | 16 ++++++---------- .github/workflows/node-zero.yml | 12 ++++++++---- 4 files changed, 31 insertions(+), 25 deletions(-) diff --git a/.github/workflows/node-4+.yml b/.github/workflows/node-4+.yml index ba174e1d..0483dda9 100644 --- a/.github/workflows/node-4+.yml +++ b/.github/workflows/node-4+.yml @@ -24,11 +24,13 @@ jobs: steps: - uses: actions/checkout@v2 - - uses: ljharb/actions/node/run@main - name: 'npm install && npm run tests-only' + - uses: ljharb/actions/node/install@main + name: 'nvm install ${{ matrix.node-version }} && npm install' with: node-version: ${{ matrix.node-version }} - command: 'tests-only' + - run: npm run tests-only + - run: bash <(curl -s https://codecov.io/bash) -f coverage/*.json; + minors: needs: [matrix, latest] name: 'non-latest minors' @@ -41,10 +43,12 @@ jobs: steps: - uses: actions/checkout@v2 - - uses: ljharb/actions/node/run@main + - uses: ljharb/actions/node/install@main + name: 'nvm install ${{ matrix.node-version }} && npm install' with: node-version: ${{ matrix.node-version }} - command: 'tests-only' + - run: npm run tests-only + - run: bash <(curl -s https://codecov.io/bash) -f coverage/*.json; node: name: 'node 4+' diff --git a/.github/workflows/node-iojs.yml b/.github/workflows/node-iojs.yml index f707c3cf..ee47695f 100644 --- a/.github/workflows/node-iojs.yml +++ b/.github/workflows/node-iojs.yml @@ -24,12 +24,13 @@ jobs: steps: - uses: actions/checkout@v2 - - uses: ljharb/actions/node/run@main - name: 'npm install && npm run tests-only' + - uses: ljharb/actions/node/install@main + name: 'nvm install ${{ matrix.node-version }} && npm install' with: node-version: ${{ matrix.node-version }} - command: 'tests-only' skip-ls-check: true + - run: npm run tests-only + - run: bash <(curl -s https://codecov.io/bash) -f coverage/*.json; minors: needs: [matrix, latest] @@ -43,12 +44,13 @@ jobs: steps: - uses: actions/checkout@v2 - - uses: ljharb/actions/node/run@main - name: 'npm install && npm run tests-only' + - uses: ljharb/actions/node/install@main + name: 'nvm install ${{ matrix.node-version }} && npm install' with: node-version: ${{ matrix.node-version }} - command: 'tests-only' skip-ls-check: true + - run: npm run tests-only + - run: bash <(curl -s https://codecov.io/bash) -f coverage/*.json; node: name: 'io.js' diff --git a/.github/workflows/node-pretest.yml b/.github/workflows/node-pretest.yml index 3921e0ae..16461736 100644 --- a/.github/workflows/node-pretest.yml +++ b/.github/workflows/node-pretest.yml @@ -8,19 +8,15 @@ jobs: steps: - uses: actions/checkout@v2 - - uses: ljharb/actions/node/run@main - name: 'npm install && npm run pretest' - with: - node-version: 'lts/*' - command: 'pretest' + - uses: ljharb/actions/node/install@main + name: 'nvm install ${{ matrix.node-version }} && npm install' + - run: npm run pretest posttest: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - - uses: ljharb/actions/node/run@main - name: 'npm install && npm run posttest' - with: - node-version: 'lts/*' - command: 'posttest' + - uses: ljharb/actions/node/install@main + name: 'nvm install ${{ matrix.node-version }} && npm install' + - run: npm run posttest diff --git a/.github/workflows/node-zero.yml b/.github/workflows/node-zero.yml index d044c603..4e72a688 100644 --- a/.github/workflows/node-zero.yml +++ b/.github/workflows/node-zero.yml @@ -24,12 +24,14 @@ jobs: steps: - uses: actions/checkout@v2 - - uses: ljharb/actions/node/run@main + - uses: ljharb/actions/node/install@main + name: 'nvm install ${{ matrix.node-version }} && npm install' with: node-version: ${{ matrix.node-version }} - command: 'tests-only' cache-node-modules-key: node_modules-${{ github.workflow }}-${{ github.action }}-${{ github.run_id }} skip-ls-check: true + - run: npm run tests-only + - run: bash <(curl -s https://codecov.io/bash) -f coverage/*.json; unstable: needs: [matrix, stable] @@ -43,12 +45,14 @@ jobs: steps: - uses: actions/checkout@v2 - - uses: ljharb/actions/node/run@main + - uses: ljharb/actions/node/install@main + name: 'nvm install ${{ matrix.node-version }} && npm install' with: node-version: ${{ matrix.node-version }} - command: 'tests-only' cache-node-modules-key: node_modules-${{ github.workflow }}-${{ github.action }}-${{ github.run_id }} skip-ls-check: true + - run: npm run tests-only + - run: bash <(curl -s https://codecov.io/bash) -f coverage/*.json; node: name: 'node 0.x' From 834f690b3c6f9c4db50db9f99980c1ce9c766a3d Mon Sep 17 00:00:00 2001 From: Sou Mizobuchi <27652080+mizozobu@users.noreply.github.com> Date: Thu, 11 Mar 2021 17:19:51 +0900 Subject: [PATCH 183/278] [meta] fix README.md - `defaultEncoder`=> `defaultDecoder` --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d2676bd7..09e2cc9e 100644 --- a/README.md +++ b/README.md @@ -352,7 +352,7 @@ var encoded = qs.stringify({ a: { b: 'c' } }, { encoder: function (str, defaultE The type argument is also provided to the decoder: ```javascript -var decoded = qs.parse('x=z', { decoder: function (str, defaultEncoder, charset, type) { +var decoded = qs.parse('x=z', { decoder: function (str, defaultDecoder, charset, type) { if (type === 'key') { return // Decoded key } else if (type === 'value') { From cce2082f095b29903549ef43bddb509c5ba893c2 Mon Sep 17 00:00:00 2001 From: Sou Mizobuchi <27652080+mizozobu@users.noreply.github.com> Date: Thu, 11 Mar 2021 17:19:51 +0900 Subject: [PATCH 184/278] [meta] fix README.md (#399) - `defaultEncoder`=> `defaultDecoder` --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 84f2534e..2b6cab13 100644 --- a/README.md +++ b/README.md @@ -345,7 +345,7 @@ var encoded = qs.stringify({ a: { b: 'c' } }, { encoder: function (str, defaultE The type argument is also provided to the decoder: ```javascript -var decoded = qs.parse('x=z', { decoder: function (str, defaultEncoder, charset, type) { +var decoded = qs.parse('x=z', { decoder: function (str, defaultDecoder, charset, type) { if (type === 'key') { return // Decoded key } else if (type === 'value') { From 4243fe4e97b5c64bbff553837483df87bd1bf3c2 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Sun, 9 Jan 2022 14:41:39 -0800 Subject: [PATCH 185/278] [Dev Deps] backport updates from main --- .eslintignore | 1 - .eslintrc | 21 ++++++++++++++++++--- .gitignore | 3 +++ .npmignore | 15 +++++++++++++-- lib/parse.js | 2 +- lib/stringify.js | 2 +- lib/utils.js | 1 + package.json | 23 ++++++++++++----------- test/.eslintrc | 17 ----------------- 9 files changed, 49 insertions(+), 36 deletions(-) delete mode 100644 .eslintignore delete mode 100644 test/.eslintrc diff --git a/.eslintignore b/.eslintignore deleted file mode 100644 index 1521c8b7..00000000 --- a/.eslintignore +++ /dev/null @@ -1 +0,0 @@ -dist diff --git a/.eslintrc b/.eslintrc index e3bde898..92838740 100644 --- a/.eslintrc +++ b/.eslintrc @@ -3,10 +3,14 @@ "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 }], @@ -16,6 +20,17 @@ "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-extend-native": 0, + }, + }, + ], } diff --git a/.gitignore b/.gitignore index 645ff13c..c036d926 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,6 @@ dist/* yarn.lock package-lock.json npm-shrinkwrap.json + +.nyc_output/ +coverage/ diff --git a/.npmignore b/.npmignore index ac980d91..4a03e5d2 100644 --- a/.npmignore +++ b/.npmignore @@ -1,4 +1,15 @@ +# gitignore +npm-debug.log +node_modules +.DS_Store + +# Only apps should have lockfiles +yarn.lock +package-lock.json +npm-shrinkwrap.json + +.nyc_output/ +coverage/ + bower.json component.json -.npmignore -.travis.yml diff --git a/lib/parse.js b/lib/parse.js index 533e884b..177a569e 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -151,7 +151,7 @@ var parseObject = function (chain, val, options, valuesParsed) { } } - leaf = obj; // eslint-disable-line no-param-reassign + leaf = obj; } return leaf; diff --git a/lib/stringify.js b/lib/stringify.js index 9380c99a..f65dc84a 100644 --- a/lib/stringify.js +++ b/lib/stringify.js @@ -51,7 +51,7 @@ var isNonNullishPrimitive = function isNonNullishPrimitive(v) { || typeof v === 'number' || typeof v === 'boolean' || typeof v === 'symbol' - || typeof v === 'bigint'; // eslint-disable-line valid-typeof + || typeof v === 'bigint'; }; var stringify = function stringify( diff --git a/lib/utils.js b/lib/utils.js index 32eedac7..fccb406a 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -173,6 +173,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)] diff --git a/package.json b/package.json index 8b04708d..f1e575d1 100644 --- a/package.json +++ b/package.json @@ -29,33 +29,34 @@ "engines": { "node": ">=0.6" }, - "dependencies": {}, "devDependencies": { - "@ljharb/eslint-config": "^16.0.0", - "browserify": "^16.5.0", + "@ljharb/eslint-config": "^20.1.0", + "browserify": "^16.5.2", "covert": "^1.1.1", "eclint": "^2.8.1", - "eslint": "^6.8.0", + "eslint": "^8.6.0", "evalmd": "^0.0.19", "for-each": "^0.3.3", - "has-symbols": "^1.0.1", + "has-symbols": "^1.0.2", "iconv-lite": "^0.5.1", + "in-publish": "^2.0.1", "mkdirp": "^0.5.4", - "object-inspect": "^1.7.0", + "object-inspect": "^1.12.0", "qs-iconv": "^1.0.4", - "safe-publish-latest": "^1.1.4", + "safe-publish-latest": "^2.0.0", "safer-buffer": "^2.1.2", - "tape": "^5.0.0-next.5" + "tape": "^5.4.0" }, "scripts": { - "prepublish": "safe-publish-latest && npm run dist", + "prepublishOnly": "safe-publish-latest && npm run dist", + "prepublish": "not-in-publish || npm run prepublishOnly", "pretest": "npm run --silent readme && npm run --silent lint", "test": "npm run --silent coverage", "tests-only": "node test", "posttest": "npx aud --production", "readme": "evalmd README.md", - "postlint": "eclint check * lib/* test/*", - "lint": "eslint lib/*.js test/*.js", + "postlint": "eclint check * lib/* test/* !dist/* '!coverage/**' '.nyc_output/**'", + "lint": "eslint .", "coverage": "covert test", "dist": "mkdirp dist && browserify --standalone Qs lib/index.js > dist/qs.js" }, diff --git a/test/.eslintrc b/test/.eslintrc deleted file mode 100644 index f76d573c..00000000 --- a/test/.eslintrc +++ /dev/null @@ -1,17 +0,0 @@ -{ - "rules": { - "array-bracket-newline": 0, - "array-element-newline": 0, - "consistent-return": 2, - "function-paren-newline": 0, - "max-lines": 0, - "max-lines-per-function": 0, - "max-nested-callbacks": [2, 3], - "max-statements": 0, - "no-buffer-constructor": 0, - "no-extend-native": 0, - "no-magic-numbers": 0, - "object-curly-newline": 0, - "sort-keys": 0 - } -} From 49bed6934b27c3f0ef50e1fcee8d76298d108d52 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Sat, 8 Jan 2022 21:24:45 -0800 Subject: [PATCH 186/278] [actions] backport actions from main --- .github/workflows/node-aught.yml | 18 ++++++++++++++++++ .github/workflows/node-pretest.yml | 7 +++++++ .github/workflows/node-tens.yml | 18 ++++++++++++++++++ .github/workflows/rebase.yml | 8 ++++---- .github/workflows/require-allow-edits.yml | 12 ++++++++++++ 5 files changed, 59 insertions(+), 4 deletions(-) create mode 100644 .github/workflows/node-aught.yml create mode 100644 .github/workflows/node-pretest.yml create mode 100644 .github/workflows/node-tens.yml create mode 100644 .github/workflows/require-allow-edits.yml diff --git a/.github/workflows/node-aught.yml b/.github/workflows/node-aught.yml new file mode 100644 index 00000000..f3cddd85 --- /dev/null +++ b/.github/workflows/node-aught.yml @@ -0,0 +1,18 @@ +name: 'Tests: node.js < 10' + +on: [pull_request, push] + +jobs: + tests: + uses: ljharb/actions/.github/workflows/node.yml@main + with: + range: '< 10' + type: minors + command: npm run tests-only + + node: + name: 'node < 10' + needs: [tests] + runs-on: ubuntu-latest + steps: + - run: 'echo tests completed' diff --git a/.github/workflows/node-pretest.yml b/.github/workflows/node-pretest.yml new file mode 100644 index 00000000..765edf79 --- /dev/null +++ b/.github/workflows/node-pretest.yml @@ -0,0 +1,7 @@ +name: 'Tests: pretest/posttest' + +on: [pull_request, push] + +jobs: + tests: + uses: ljharb/actions/.github/workflows/pretest.yml@main diff --git a/.github/workflows/node-tens.yml b/.github/workflows/node-tens.yml new file mode 100644 index 00000000..b49ceb1f --- /dev/null +++ b/.github/workflows/node-tens.yml @@ -0,0 +1,18 @@ +name: 'Tests: node.js >= 10' + +on: [pull_request, push] + +jobs: + tests: + uses: ljharb/actions/.github/workflows/node.yml@main + with: + range: '>= 10' + type: minors + command: npm run tests-only + + node: + name: 'node >= 10' + needs: [tests] + runs-on: ubuntu-latest + steps: + - run: 'echo tests completed' diff --git a/.github/workflows/rebase.yml b/.github/workflows/rebase.yml index 436cb79d..9596e285 100644 --- a/.github/workflows/rebase.yml +++ b/.github/workflows/rebase.yml @@ -9,7 +9,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v1 - - uses: ljharb/rebase@master - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - uses: actions/checkout@v2 + - uses: ljharb/rebase@master + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/require-allow-edits.yml b/.github/workflows/require-allow-edits.yml new file mode 100644 index 00000000..7b842f89 --- /dev/null +++ b/.github/workflows/require-allow-edits.yml @@ -0,0 +1,12 @@ +name: Require “Allow Edits” + +on: [pull_request_target] + +jobs: + _: + name: "Require “Allow Edits”" + + runs-on: ubuntu-latest + + steps: + - uses: ljharb/require-allow-edits@main From 6868128ca2bd247ba935fbb63d359d0417f6b283 Mon Sep 17 00:00:00 2001 From: Sou Mizobuchi <27652080+mizozobu@users.noreply.github.com> Date: Thu, 11 Mar 2021 17:19:51 +0900 Subject: [PATCH 187/278] [meta] fix README.md (#399) - `defaultEncoder`=> `defaultDecoder` --- README.md | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/README.md b/README.md index cae83b48..2b6cab13 100644 --- a/README.md +++ b/README.md @@ -330,6 +330,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: From 1c0dc75ab9ab7a89f46983ca8a9e8717c1c0fc24 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Sun, 9 Jan 2022 22:25:28 -0800 Subject: [PATCH 188/278] [Dev Deps] backport updates from main --- .eslintignore | 1 - .eslintrc | 21 ++++++++++++++++++--- .gitignore | 3 +++ .npmignore | 16 ++++++++++++---- lib/utils.js | 1 + package.json | 20 +++++++++++--------- test/.eslintrc | 17 ----------------- 7 files changed, 45 insertions(+), 34 deletions(-) delete mode 100644 .eslintignore delete mode 100644 test/.eslintrc diff --git a/.eslintignore b/.eslintignore deleted file mode 100644 index 1521c8b7..00000000 --- a/.eslintignore +++ /dev/null @@ -1 +0,0 @@ -dist diff --git a/.eslintrc b/.eslintrc index e3bde898..43989998 100644 --- a/.eslintrc +++ b/.eslintrc @@ -3,10 +3,14 @@ "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 }], @@ -16,6 +20,17 @@ "no-continue": 1, "no-magic-numbers": 0, "no-restricted-syntax": [2, "BreakStatement", "DebuggerStatement", "ForInStatement", "LabeledStatement", "WithStatement"], - "operator-linebreak": [2, "before"], - } + }, + + "overrides": [ + { + "files": "test/**", + "rules": { + "max-lines-per-function": 0, + "max-statements": 0, + "no-extend-native": 0, + "function-paren-newline": 0, + }, + }, + ], } diff --git a/.gitignore b/.gitignore index 645ff13c..c036d926 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,6 @@ dist/* yarn.lock package-lock.json npm-shrinkwrap.json + +.nyc_output/ +coverage/ diff --git a/.npmignore b/.npmignore index ac980d91..21aa9da5 100644 --- a/.npmignore +++ b/.npmignore @@ -1,4 +1,12 @@ -bower.json -component.json -.npmignore -.travis.yml +# gitignore +npm-debug.log +node_modules +.DS_Store + +# Only apps should have lockfiles +yarn.lock +package-lock.json +npm-shrinkwrap.json + +.nyc_output/ +coverage/ diff --git a/lib/utils.js b/lib/utils.js index a5d0401e..de8639d7 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -168,6 +168,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)] diff --git a/package.json b/package.json index 777ffa40..3d04c737 100644 --- a/package.json +++ b/package.json @@ -29,31 +29,33 @@ "engines": { "node": ">=0.6" }, - "dependencies": {}, "devDependencies": { - "@ljharb/eslint-config": "^16.0.0", - "browserify": "^16.5.0", + "@ljharb/eslint-config": "^20.1.0", + "browserify": "^16.5.2", "covert": "^1.1.1", "eclint": "^2.8.1", - "eslint": "^6.8.0", + "eslint": "^8.6.0", "evalmd": "^0.0.19", "for-each": "^0.3.3", + "has-symbols": "^1.0.2", "iconv-lite": "^0.5.1", + "in-publish": "^2.0.1", "mkdirp": "^0.5.4", - "object-inspect": "^1.7.0", + "object-inspect": "^1.12.0", "qs-iconv": "^1.0.4", - "safe-publish-latest": "^1.1.4", + "safe-publish-latest": "^2.0.0", "safer-buffer": "^2.1.2", - "tape": "^5.0.0-next.5" + "tape": "^5.4.0" }, "scripts": { - "prepublish": "safe-publish-latest && npm run dist", + "prepublishOnly": "safe-publish-latest && npm run dist", + "prepublish": "not-in-publish || npm run prepublishOnly", "pretest": "npm run --silent readme && npm run --silent lint", "test": "npm run --silent coverage", "tests-only": "node test", "posttest": "npx aud --production", "readme": "evalmd README.md", - "postlint": "eclint check * lib/* test/*", + "postlint": "eclint check $(git ls-files | xargs find 2> /dev/null | grep -vE 'node_modules|\\.git')", "lint": "eslint lib/*.js test/*.js", "coverage": "covert test", "dist": "mkdirp dist && browserify --standalone Qs lib/index.js > dist/qs.js" diff --git a/test/.eslintrc b/test/.eslintrc deleted file mode 100644 index f76d573c..00000000 --- a/test/.eslintrc +++ /dev/null @@ -1,17 +0,0 @@ -{ - "rules": { - "array-bracket-newline": 0, - "array-element-newline": 0, - "consistent-return": 2, - "function-paren-newline": 0, - "max-lines": 0, - "max-lines-per-function": 0, - "max-nested-callbacks": [2, 3], - "max-statements": 0, - "no-buffer-constructor": 0, - "no-extend-native": 0, - "no-magic-numbers": 0, - "object-curly-newline": 0, - "sort-keys": 0 - } -} From 9836e5c9512ea0a0124987344b0bbfddcc15c90c Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Sat, 8 Jan 2022 21:24:45 -0800 Subject: [PATCH 189/278] [actions] backport actions from main --- .github/workflows/node-aught.yml | 18 ++++++++++++++++++ .github/workflows/node-pretest.yml | 7 +++++++ .github/workflows/node-tens.yml | 18 ++++++++++++++++++ .github/workflows/rebase.yml | 8 ++++---- .github/workflows/require-allow-edits.yml | 12 ++++++++++++ .travis.yml | 12 ------------ 6 files changed, 59 insertions(+), 16 deletions(-) create mode 100644 .github/workflows/node-aught.yml create mode 100644 .github/workflows/node-pretest.yml create mode 100644 .github/workflows/node-tens.yml create mode 100644 .github/workflows/require-allow-edits.yml delete mode 100644 .travis.yml diff --git a/.github/workflows/node-aught.yml b/.github/workflows/node-aught.yml new file mode 100644 index 00000000..f3cddd85 --- /dev/null +++ b/.github/workflows/node-aught.yml @@ -0,0 +1,18 @@ +name: 'Tests: node.js < 10' + +on: [pull_request, push] + +jobs: + tests: + uses: ljharb/actions/.github/workflows/node.yml@main + with: + range: '< 10' + type: minors + command: npm run tests-only + + node: + name: 'node < 10' + needs: [tests] + runs-on: ubuntu-latest + steps: + - run: 'echo tests completed' diff --git a/.github/workflows/node-pretest.yml b/.github/workflows/node-pretest.yml new file mode 100644 index 00000000..765edf79 --- /dev/null +++ b/.github/workflows/node-pretest.yml @@ -0,0 +1,7 @@ +name: 'Tests: pretest/posttest' + +on: [pull_request, push] + +jobs: + tests: + uses: ljharb/actions/.github/workflows/pretest.yml@main diff --git a/.github/workflows/node-tens.yml b/.github/workflows/node-tens.yml new file mode 100644 index 00000000..b49ceb1f --- /dev/null +++ b/.github/workflows/node-tens.yml @@ -0,0 +1,18 @@ +name: 'Tests: node.js >= 10' + +on: [pull_request, push] + +jobs: + tests: + uses: ljharb/actions/.github/workflows/node.yml@main + with: + range: '>= 10' + type: minors + command: npm run tests-only + + node: + name: 'node >= 10' + needs: [tests] + runs-on: ubuntu-latest + steps: + - run: 'echo tests completed' diff --git a/.github/workflows/rebase.yml b/.github/workflows/rebase.yml index 436cb79d..9596e285 100644 --- a/.github/workflows/rebase.yml +++ b/.github/workflows/rebase.yml @@ -9,7 +9,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v1 - - uses: ljharb/rebase@master - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - uses: actions/checkout@v2 + - uses: ljharb/rebase@master + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/require-allow-edits.yml b/.github/workflows/require-allow-edits.yml new file mode 100644 index 00000000..7b842f89 --- /dev/null +++ b/.github/workflows/require-allow-edits.yml @@ -0,0 +1,12 @@ +name: Require “Allow Edits” + +on: [pull_request_target] + +jobs: + _: + name: "Require “Allow Edits”" + + runs-on: ubuntu-latest + + steps: + - uses: ljharb/require-allow-edits@main diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 7709ba6d..00000000 --- a/.travis.yml +++ /dev/null @@ -1,12 +0,0 @@ -version: ~> 1.0 -language: node_js -os: - - linux -import: - - ljharb/travis-ci:node/all.yml - - ljharb/travis-ci:node/pretest.yml - - ljharb/travis-ci:node/posttest.yml - - ljharb/travis-ci:node/coverage.yml -matrix: - allow_failures: - - env: COVERAGE=true # temporarily allow this to fail From c87c8c92dd04a58258d1b6256d5fc2966f7fbf93 Mon Sep 17 00:00:00 2001 From: Sou Mizobuchi <27652080+mizozobu@users.noreply.github.com> Date: Thu, 11 Mar 2021 17:19:51 +0900 Subject: [PATCH 190/278] [meta] fix README.md (#399) - `defaultEncoder`=> `defaultDecoder` --- README.md | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/README.md b/README.md index cae83b48..2b6cab13 100644 --- a/README.md +++ b/README.md @@ -330,6 +330,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: From 54530e2594641b179b600be659c9c7e680849be9 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Sat, 8 Jan 2022 21:24:45 -0800 Subject: [PATCH 191/278] [actions] backport actions from main --- .github/workflows/node-aught.yml | 18 ++ .github/workflows/node-pretest.yml | 7 + .github/workflows/node-tens.yml | 18 ++ .github/workflows/rebase.yml | 15 ++ .github/workflows/require-allow-edits.yml | 12 ++ .travis.yml | 249 ---------------------- 6 files changed, 70 insertions(+), 249 deletions(-) create mode 100644 .github/workflows/node-aught.yml create mode 100644 .github/workflows/node-pretest.yml create mode 100644 .github/workflows/node-tens.yml create mode 100644 .github/workflows/rebase.yml create mode 100644 .github/workflows/require-allow-edits.yml delete mode 100644 .travis.yml diff --git a/.github/workflows/node-aught.yml b/.github/workflows/node-aught.yml new file mode 100644 index 00000000..f3cddd85 --- /dev/null +++ b/.github/workflows/node-aught.yml @@ -0,0 +1,18 @@ +name: 'Tests: node.js < 10' + +on: [pull_request, push] + +jobs: + tests: + uses: ljharb/actions/.github/workflows/node.yml@main + with: + range: '< 10' + type: minors + command: npm run tests-only + + node: + name: 'node < 10' + needs: [tests] + runs-on: ubuntu-latest + steps: + - run: 'echo tests completed' diff --git a/.github/workflows/node-pretest.yml b/.github/workflows/node-pretest.yml new file mode 100644 index 00000000..765edf79 --- /dev/null +++ b/.github/workflows/node-pretest.yml @@ -0,0 +1,7 @@ +name: 'Tests: pretest/posttest' + +on: [pull_request, push] + +jobs: + tests: + uses: ljharb/actions/.github/workflows/pretest.yml@main diff --git a/.github/workflows/node-tens.yml b/.github/workflows/node-tens.yml new file mode 100644 index 00000000..b49ceb1f --- /dev/null +++ b/.github/workflows/node-tens.yml @@ -0,0 +1,18 @@ +name: 'Tests: node.js >= 10' + +on: [pull_request, push] + +jobs: + tests: + uses: ljharb/actions/.github/workflows/node.yml@main + with: + range: '>= 10' + type: minors + command: npm run tests-only + + node: + name: 'node >= 10' + needs: [tests] + runs-on: ubuntu-latest + steps: + - run: 'echo tests completed' diff --git a/.github/workflows/rebase.yml b/.github/workflows/rebase.yml new file mode 100644 index 00000000..9596e285 --- /dev/null +++ b/.github/workflows/rebase.yml @@ -0,0 +1,15 @@ +name: Automatic Rebase + +on: [pull_request] + +jobs: + _: + name: "Automatic Rebase" + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - uses: ljharb/rebase@master + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/require-allow-edits.yml b/.github/workflows/require-allow-edits.yml new file mode 100644 index 00000000..7b842f89 --- /dev/null +++ b/.github/workflows/require-allow-edits.yml @@ -0,0 +1,12 @@ +name: Require “Allow Edits” + +on: [pull_request_target] + +jobs: + _: + name: "Require “Allow Edits”" + + runs-on: ubuntu-latest + + steps: + - uses: ljharb/require-allow-edits@main diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 4f9e1df7..00000000 --- a/.travis.yml +++ /dev/null @@ -1,249 +0,0 @@ -language: node_js -os: - - linux -node_js: - - "11.2" - - "10.13" - - "9.11" - - "8.13" - - "7.10" - - "6.14" - - "5.12" - - "4.9" - - "iojs-v3.3" - - "iojs-v2.5" - - "iojs-v1.8" - - "0.12" - - "0.10" - - "0.8" - - "0.6" -before_install: - - 'case "${TRAVIS_NODE_VERSION}" in 0.*) export NPM_CONFIG_STRICT_SSL=false ;; esac' - - 'nvm install-latest-npm' -install: - - 'if [ "${TRAVIS_NODE_VERSION}" = "0.6" ] || [ "${TRAVIS_NODE_VERSION}" = "0.9" ]; then nvm install --latest-npm 0.8 && npm install && nvm use "${TRAVIS_NODE_VERSION}"; else npm install; fi;' -script: - - 'if [ -n "${PRETEST-}" ]; then npm run pretest ; fi' - - 'if [ -n "${POSTTEST-}" ]; then npm run posttest ; fi' - - 'if [ -n "${COVERAGE-}" ]; then npm run coverage ; fi' - - 'if [ -n "${TEST-}" ]; then npm run tests-only ; fi' -sudo: false -env: - - TEST=true -matrix: - fast_finish: true - include: - - node_js: "lts/*" - env: PRETEST=true - - node_js: "4" - env: COVERAGE=true - - node_js: "11.1" - env: TEST=true ALLOW_FAILURE=true - - node_js: "11.0" - env: TEST=true ALLOW_FAILURE=true - - node_js: "10.12" - env: TEST=true ALLOW_FAILURE=true - - node_js: "10.11" - env: TEST=true ALLOW_FAILURE=true - - node_js: "10.10" - env: TEST=true ALLOW_FAILURE=true - - node_js: "10.9" - env: TEST=true ALLOW_FAILURE=true - - node_js: "10.8" - env: TEST=true ALLOW_FAILURE=true - - node_js: "10.7" - env: TEST=true ALLOW_FAILURE=true - - node_js: "10.6" - env: TEST=true ALLOW_FAILURE=true - - node_js: "10.5" - env: TEST=true ALLOW_FAILURE=true - - node_js: "10.4" - env: TEST=true ALLOW_FAILURE=true - - node_js: "10.3" - env: TEST=true ALLOW_FAILURE=true - - node_js: "10.2" - env: TEST=true ALLOW_FAILURE=true - - node_js: "10.1" - env: TEST=true ALLOW_FAILURE=true - - node_js: "10.0" - env: TEST=true ALLOW_FAILURE=true - - node_js: "9.10" - env: TEST=true ALLOW_FAILURE=true - - node_js: "9.9" - env: TEST=true ALLOW_FAILURE=true - - node_js: "9.8" - env: TEST=true ALLOW_FAILURE=true - - node_js: "9.7" - env: TEST=true ALLOW_FAILURE=true - - node_js: "9.6" - env: TEST=true ALLOW_FAILURE=true - - node_js: "9.5" - env: TEST=true ALLOW_FAILURE=true - - node_js: "9.4" - env: TEST=true ALLOW_FAILURE=true - - node_js: "9.3" - env: TEST=true ALLOW_FAILURE=true - - node_js: "9.2" - env: TEST=true ALLOW_FAILURE=true - - node_js: "9.1" - env: TEST=true ALLOW_FAILURE=true - - node_js: "9.0" - env: TEST=true ALLOW_FAILURE=true - - node_js: "8.12" - env: TEST=true ALLOW_FAILURE=true - - node_js: "8.11" - env: TEST=true ALLOW_FAILURE=true - - node_js: "8.10" - env: TEST=true ALLOW_FAILURE=true - - node_js: "8.9" - env: TEST=true ALLOW_FAILURE=true - - node_js: "8.8" - env: TEST=true ALLOW_FAILURE=true - - node_js: "8.7" - env: TEST=true ALLOW_FAILURE=true - - node_js: "8.6" - env: TEST=true ALLOW_FAILURE=true - - node_js: "8.5" - env: TEST=true ALLOW_FAILURE=true - - node_js: "8.4" - env: TEST=true ALLOW_FAILURE=true - - node_js: "8.3" - env: TEST=true ALLOW_FAILURE=true - - node_js: "8.2" - env: TEST=true ALLOW_FAILURE=true - - node_js: "8.1" - env: TEST=true ALLOW_FAILURE=true - - node_js: "8.0" - env: TEST=true ALLOW_FAILURE=true - - node_js: "7.9" - env: TEST=true ALLOW_FAILURE=true - - node_js: "7.8" - env: TEST=true ALLOW_FAILURE=true - - node_js: "7.7" - env: TEST=true ALLOW_FAILURE=true - - node_js: "7.6" - env: TEST=true ALLOW_FAILURE=true - - node_js: "7.5" - env: TEST=true ALLOW_FAILURE=true - - node_js: "7.4" - env: TEST=true ALLOW_FAILURE=true - - node_js: "7.3" - env: TEST=true ALLOW_FAILURE=true - - node_js: "7.2" - env: TEST=true ALLOW_FAILURE=true - - node_js: "7.1" - env: TEST=true ALLOW_FAILURE=true - - node_js: "7.0" - env: TEST=true ALLOW_FAILURE=true - - node_js: "6.13" - env: TEST=true ALLOW_FAILURE=true - - node_js: "6.12" - env: TEST=true ALLOW_FAILURE=true - - node_js: "6.11" - env: TEST=true ALLOW_FAILURE=true - - node_js: "6.10" - env: TEST=true ALLOW_FAILURE=true - - node_js: "6.9" - env: TEST=true ALLOW_FAILURE=true - - node_js: "6.8" - env: TEST=true ALLOW_FAILURE=true - - node_js: "6.7" - env: TEST=true ALLOW_FAILURE=true - - node_js: "6.6" - env: TEST=true ALLOW_FAILURE=true - - node_js: "6.5" - env: TEST=true ALLOW_FAILURE=true - - node_js: "6.4" - env: TEST=true ALLOW_FAILURE=true - - node_js: "6.3" - env: TEST=true ALLOW_FAILURE=true - - node_js: "6.2" - env: TEST=true ALLOW_FAILURE=true - - node_js: "6.1" - env: TEST=true ALLOW_FAILURE=true - - node_js: "6.0" - env: TEST=true ALLOW_FAILURE=true - - node_js: "5.11" - env: TEST=true ALLOW_FAILURE=true - - node_js: "5.10" - env: TEST=true ALLOW_FAILURE=true - - node_js: "5.9" - env: TEST=true ALLOW_FAILURE=true - - node_js: "5.8" - env: TEST=true ALLOW_FAILURE=true - - node_js: "5.7" - env: TEST=true ALLOW_FAILURE=true - - node_js: "5.6" - env: TEST=true ALLOW_FAILURE=true - - node_js: "5.5" - env: TEST=true ALLOW_FAILURE=true - - node_js: "5.4" - env: TEST=true ALLOW_FAILURE=true - - node_js: "5.3" - env: TEST=true ALLOW_FAILURE=true - - node_js: "5.2" - env: TEST=true ALLOW_FAILURE=true - - node_js: "5.1" - env: TEST=true ALLOW_FAILURE=true - - node_js: "5.0" - env: TEST=true ALLOW_FAILURE=true - - node_js: "4.8" - env: TEST=true ALLOW_FAILURE=true - - node_js: "4.7" - env: TEST=true ALLOW_FAILURE=true - - node_js: "4.6" - env: TEST=true ALLOW_FAILURE=true - - node_js: "4.5" - env: TEST=true ALLOW_FAILURE=true - - node_js: "4.4" - env: TEST=true ALLOW_FAILURE=true - - node_js: "4.3" - env: TEST=true ALLOW_FAILURE=true - - node_js: "4.2" - env: TEST=true ALLOW_FAILURE=true - - node_js: "4.1" - env: TEST=true ALLOW_FAILURE=true - - node_js: "4.0" - env: TEST=true ALLOW_FAILURE=true - - node_js: "iojs-v3.2" - env: TEST=true ALLOW_FAILURE=true - - node_js: "iojs-v3.1" - env: TEST=true ALLOW_FAILURE=true - - node_js: "iojs-v3.0" - env: TEST=true ALLOW_FAILURE=true - - node_js: "iojs-v2.4" - env: TEST=true ALLOW_FAILURE=true - - node_js: "iojs-v2.3" - env: TEST=true ALLOW_FAILURE=true - - node_js: "iojs-v2.2" - env: TEST=true ALLOW_FAILURE=true - - node_js: "iojs-v2.1" - env: TEST=true ALLOW_FAILURE=true - - node_js: "iojs-v2.0" - env: TEST=true ALLOW_FAILURE=true - - node_js: "iojs-v1.7" - env: TEST=true ALLOW_FAILURE=true - - node_js: "iojs-v1.6" - env: TEST=true ALLOW_FAILURE=true - - node_js: "iojs-v1.5" - env: TEST=true ALLOW_FAILURE=true - - node_js: "iojs-v1.4" - env: TEST=true ALLOW_FAILURE=true - - node_js: "iojs-v1.3" - env: TEST=true ALLOW_FAILURE=true - - node_js: "iojs-v1.2" - env: TEST=true ALLOW_FAILURE=true - - node_js: "iojs-v1.1" - env: TEST=true ALLOW_FAILURE=true - - node_js: "iojs-v1.0" - env: TEST=true ALLOW_FAILURE=true - - node_js: "0.11" - env: TEST=true ALLOW_FAILURE=true - - node_js: "0.9" - env: TEST=true ALLOW_FAILURE=true - - node_js: "0.4" - env: TEST=true ALLOW_FAILURE=true - allow_failures: - - os: osx - - env: TEST=true ALLOW_FAILURE=true - - node_js: "0.6" # temporarily allow this to fail From 33b27f41f187da30c7d953e79e4563596e79b91c Mon Sep 17 00:00:00 2001 From: Sou Mizobuchi <27652080+mizozobu@users.noreply.github.com> Date: Thu, 11 Mar 2021 17:19:51 +0900 Subject: [PATCH 192/278] [meta] fix README.md (#399) - `defaultEncoder`=> `defaultDecoder` --- README.md | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/README.md b/README.md index ef6e6838..89a08d25 100644 --- a/README.md +++ b/README.md @@ -323,6 +323,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: From 0338716b09fdbd4711823eeb0a14e556a2498e7a Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Sat, 8 Jan 2022 21:24:45 -0800 Subject: [PATCH 193/278] [actions] backport actions from main --- .github/workflows/node-aught.yml | 18 ++ .github/workflows/node-pretest.yml | 7 + .github/workflows/node-tens.yml | 18 ++ .github/workflows/rebase.yml | 15 ++ .github/workflows/require-allow-edits.yml | 12 ++ .travis.yml | 215 ---------------------- 6 files changed, 70 insertions(+), 215 deletions(-) create mode 100644 .github/workflows/node-aught.yml create mode 100644 .github/workflows/node-pretest.yml create mode 100644 .github/workflows/node-tens.yml create mode 100644 .github/workflows/rebase.yml create mode 100644 .github/workflows/require-allow-edits.yml delete mode 100644 .travis.yml diff --git a/.github/workflows/node-aught.yml b/.github/workflows/node-aught.yml new file mode 100644 index 00000000..f3cddd85 --- /dev/null +++ b/.github/workflows/node-aught.yml @@ -0,0 +1,18 @@ +name: 'Tests: node.js < 10' + +on: [pull_request, push] + +jobs: + tests: + uses: ljharb/actions/.github/workflows/node.yml@main + with: + range: '< 10' + type: minors + command: npm run tests-only + + node: + name: 'node < 10' + needs: [tests] + runs-on: ubuntu-latest + steps: + - run: 'echo tests completed' diff --git a/.github/workflows/node-pretest.yml b/.github/workflows/node-pretest.yml new file mode 100644 index 00000000..765edf79 --- /dev/null +++ b/.github/workflows/node-pretest.yml @@ -0,0 +1,7 @@ +name: 'Tests: pretest/posttest' + +on: [pull_request, push] + +jobs: + tests: + uses: ljharb/actions/.github/workflows/pretest.yml@main diff --git a/.github/workflows/node-tens.yml b/.github/workflows/node-tens.yml new file mode 100644 index 00000000..b49ceb1f --- /dev/null +++ b/.github/workflows/node-tens.yml @@ -0,0 +1,18 @@ +name: 'Tests: node.js >= 10' + +on: [pull_request, push] + +jobs: + tests: + uses: ljharb/actions/.github/workflows/node.yml@main + with: + range: '>= 10' + type: minors + command: npm run tests-only + + node: + name: 'node >= 10' + needs: [tests] + runs-on: ubuntu-latest + steps: + - run: 'echo tests completed' diff --git a/.github/workflows/rebase.yml b/.github/workflows/rebase.yml new file mode 100644 index 00000000..9596e285 --- /dev/null +++ b/.github/workflows/rebase.yml @@ -0,0 +1,15 @@ +name: Automatic Rebase + +on: [pull_request] + +jobs: + _: + name: "Automatic Rebase" + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - uses: ljharb/rebase@master + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/require-allow-edits.yml b/.github/workflows/require-allow-edits.yml new file mode 100644 index 00000000..7b842f89 --- /dev/null +++ b/.github/workflows/require-allow-edits.yml @@ -0,0 +1,12 @@ +name: Require “Allow Edits” + +on: [pull_request_target] + +jobs: + _: + name: "Require “Allow Edits”" + + runs-on: ubuntu-latest + + steps: + - uses: ljharb/require-allow-edits@main diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 7bc8e73b..00000000 --- a/.travis.yml +++ /dev/null @@ -1,215 +0,0 @@ -language: node_js -os: - - linux -node_js: - - "10.1" - - "9.11" - - "8.11" - - "7.10" - - "6.14" - - "5.12" - - "4.9" - - "iojs-v3.3" - - "iojs-v2.5" - - "iojs-v1.8" - - "0.12" - - "0.10" - - "0.8" - - "0.6" -before_install: - - 'case "${TRAVIS_NODE_VERSION}" in 0.*) export NPM_CONFIG_STRICT_SSL=false ;; esac' - - 'nvm install-latest-npm' -install: - - 'if [ "${TRAVIS_NODE_VERSION}" = "0.6" ] || [ "${TRAVIS_NODE_VERSION}" = "0.9" ]; then nvm install --latest-npm 0.8 && npm install && nvm use "${TRAVIS_NODE_VERSION}"; else npm install; fi;' -script: - - 'if [ -n "${PRETEST-}" ]; then npm run pretest ; fi' - - 'if [ -n "${POSTTEST-}" ]; then npm run posttest ; fi' - - 'if [ -n "${COVERAGE-}" ]; then npm run coverage ; fi' - - 'if [ -n "${TEST-}" ]; then npm run tests-only ; fi' -sudo: false -env: - - TEST=true -matrix: - fast_finish: true - include: - - node_js: "lts/*" - env: PRETEST=true - - node_js: "4" - env: COVERAGE=true - - node_js: "10.0" - env: TEST=true ALLOW_FAILURE=true - - node_js: "9.10" - env: TEST=true ALLOW_FAILURE=true - - node_js: "9.9" - env: TEST=true ALLOW_FAILURE=true - - node_js: "9.8" - env: TEST=true ALLOW_FAILURE=true - - node_js: "9.7" - env: TEST=true ALLOW_FAILURE=true - - node_js: "9.6" - env: TEST=true ALLOW_FAILURE=true - - node_js: "9.5" - env: TEST=true ALLOW_FAILURE=true - - node_js: "9.4" - env: TEST=true ALLOW_FAILURE=true - - node_js: "9.3" - env: TEST=true ALLOW_FAILURE=true - - node_js: "9.2" - env: TEST=true ALLOW_FAILURE=true - - node_js: "9.1" - env: TEST=true ALLOW_FAILURE=true - - node_js: "9.0" - env: TEST=true ALLOW_FAILURE=true - - node_js: "8.10" - env: TEST=true ALLOW_FAILURE=true - - node_js: "8.9" - env: TEST=true ALLOW_FAILURE=true - - node_js: "8.8" - env: TEST=true ALLOW_FAILURE=true - - node_js: "8.7" - env: TEST=true ALLOW_FAILURE=true - - node_js: "8.6" - env: TEST=true ALLOW_FAILURE=true - - node_js: "8.5" - env: TEST=true ALLOW_FAILURE=true - - node_js: "8.4" - env: TEST=true ALLOW_FAILURE=true - - node_js: "8.3" - env: TEST=true ALLOW_FAILURE=true - - node_js: "8.2" - env: TEST=true ALLOW_FAILURE=true - - node_js: "8.1" - env: TEST=true ALLOW_FAILURE=true - - node_js: "8.0" - env: TEST=true ALLOW_FAILURE=true - - node_js: "7.9" - env: TEST=true ALLOW_FAILURE=true - - node_js: "7.8" - env: TEST=true ALLOW_FAILURE=true - - node_js: "7.7" - env: TEST=true ALLOW_FAILURE=true - - node_js: "7.6" - env: TEST=true ALLOW_FAILURE=true - - node_js: "7.5" - env: TEST=true ALLOW_FAILURE=true - - node_js: "7.4" - env: TEST=true ALLOW_FAILURE=true - - node_js: "7.3" - env: TEST=true ALLOW_FAILURE=true - - node_js: "7.2" - env: TEST=true ALLOW_FAILURE=true - - node_js: "7.1" - env: TEST=true ALLOW_FAILURE=true - - node_js: "7.0" - env: TEST=true ALLOW_FAILURE=true - - node_js: "6.13" - env: TEST=true ALLOW_FAILURE=true - - node_js: "6.12" - env: TEST=true ALLOW_FAILURE=true - - node_js: "6.11" - env: TEST=true ALLOW_FAILURE=true - - node_js: "6.10" - env: TEST=true ALLOW_FAILURE=true - - node_js: "6.9" - env: TEST=true ALLOW_FAILURE=true - - node_js: "6.8" - env: TEST=true ALLOW_FAILURE=true - - node_js: "6.7" - env: TEST=true ALLOW_FAILURE=true - - node_js: "6.6" - env: TEST=true ALLOW_FAILURE=true - - node_js: "6.5" - env: TEST=true ALLOW_FAILURE=true - - node_js: "6.4" - env: TEST=true ALLOW_FAILURE=true - - node_js: "6.3" - env: TEST=true ALLOW_FAILURE=true - - node_js: "6.2" - env: TEST=true ALLOW_FAILURE=true - - node_js: "6.1" - env: TEST=true ALLOW_FAILURE=true - - node_js: "6.0" - env: TEST=true ALLOW_FAILURE=true - - node_js: "5.11" - env: TEST=true ALLOW_FAILURE=true - - node_js: "5.10" - env: TEST=true ALLOW_FAILURE=true - - node_js: "5.9" - env: TEST=true ALLOW_FAILURE=true - - node_js: "5.8" - env: TEST=true ALLOW_FAILURE=true - - node_js: "5.7" - env: TEST=true ALLOW_FAILURE=true - - node_js: "5.6" - env: TEST=true ALLOW_FAILURE=true - - node_js: "5.5" - env: TEST=true ALLOW_FAILURE=true - - node_js: "5.4" - env: TEST=true ALLOW_FAILURE=true - - node_js: "5.3" - env: TEST=true ALLOW_FAILURE=true - - node_js: "5.2" - env: TEST=true ALLOW_FAILURE=true - - node_js: "5.1" - env: TEST=true ALLOW_FAILURE=true - - node_js: "5.0" - env: TEST=true ALLOW_FAILURE=true - - node_js: "4.8" - env: TEST=true ALLOW_FAILURE=true - - node_js: "4.7" - env: TEST=true ALLOW_FAILURE=true - - node_js: "4.6" - env: TEST=true ALLOW_FAILURE=true - - node_js: "4.5" - env: TEST=true ALLOW_FAILURE=true - - node_js: "4.4" - env: TEST=true ALLOW_FAILURE=true - - node_js: "4.3" - env: TEST=true ALLOW_FAILURE=true - - node_js: "4.2" - env: TEST=true ALLOW_FAILURE=true - - node_js: "4.1" - env: TEST=true ALLOW_FAILURE=true - - node_js: "4.0" - env: TEST=true ALLOW_FAILURE=true - - node_js: "iojs-v3.2" - env: TEST=true ALLOW_FAILURE=true - - node_js: "iojs-v3.1" - env: TEST=true ALLOW_FAILURE=true - - node_js: "iojs-v3.0" - env: TEST=true ALLOW_FAILURE=true - - node_js: "iojs-v2.4" - env: TEST=true ALLOW_FAILURE=true - - node_js: "iojs-v2.3" - env: TEST=true ALLOW_FAILURE=true - - node_js: "iojs-v2.2" - env: TEST=true ALLOW_FAILURE=true - - node_js: "iojs-v2.1" - env: TEST=true ALLOW_FAILURE=true - - node_js: "iojs-v2.0" - env: TEST=true ALLOW_FAILURE=true - - node_js: "iojs-v1.7" - env: TEST=true ALLOW_FAILURE=true - - node_js: "iojs-v1.6" - env: TEST=true ALLOW_FAILURE=true - - node_js: "iojs-v1.5" - env: TEST=true ALLOW_FAILURE=true - - node_js: "iojs-v1.4" - env: TEST=true ALLOW_FAILURE=true - - node_js: "iojs-v1.3" - env: TEST=true ALLOW_FAILURE=true - - node_js: "iojs-v1.2" - env: TEST=true ALLOW_FAILURE=true - - node_js: "iojs-v1.1" - env: TEST=true ALLOW_FAILURE=true - - node_js: "iojs-v1.0" - env: TEST=true ALLOW_FAILURE=true - - node_js: "0.11" - env: TEST=true ALLOW_FAILURE=true - - node_js: "0.9" - env: TEST=true ALLOW_FAILURE=true - - node_js: "0.4" - env: TEST=true ALLOW_FAILURE=true - allow_failures: - - os: osx - - env: TEST=true ALLOW_FAILURE=true From 12ac1c403aaa04d1a34844f514ed9f9abfb76e64 Mon Sep 17 00:00:00 2001 From: Sou Mizobuchi <27652080+mizozobu@users.noreply.github.com> Date: Thu, 11 Mar 2021 17:19:51 +0900 Subject: [PATCH 194/278] [meta] fix README.md (#399) - `defaultEncoder`=> `defaultDecoder` --- README.md | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/README.md b/README.md index cb64e0f6..b882be70 100644 --- a/README.md +++ b/README.md @@ -267,6 +267,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: From 90a3bced518c6ff4a97919d10de9498fea961acf Mon Sep 17 00:00:00 2001 From: Sou Mizobuchi <27652080+mizozobu@users.noreply.github.com> Date: Thu, 11 Mar 2021 17:19:51 +0900 Subject: [PATCH 195/278] [meta] fix README.md (#399) - `defaultEncoder`=> `defaultDecoder` --- README.md | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/README.md b/README.md index a08260f7..46d691ae 100644 --- a/README.md +++ b/README.md @@ -254,6 +254,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: From f8510a125b6963af3964b5b885adf68e04ffee83 Mon Sep 17 00:00:00 2001 From: Sou Mizobuchi <27652080+mizozobu@users.noreply.github.com> Date: Thu, 11 Mar 2021 17:19:51 +0900 Subject: [PATCH 196/278] [meta] fix README.md (#399) - `defaultEncoder`=> `defaultDecoder` --- README.md | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/README.md b/README.md index 53afe2f3..e6e32130 100644 --- a/README.md +++ b/README.md @@ -245,6 +245,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: From 37e176d06e6c035d1270b023ed82bc72d70aad88 Mon Sep 17 00:00:00 2001 From: Sou Mizobuchi <27652080+mizozobu@users.noreply.github.com> Date: Thu, 11 Mar 2021 17:19:51 +0900 Subject: [PATCH 197/278] [meta] fix README.md (#399) - `defaultEncoder`=> `defaultDecoder` --- README.md | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/README.md b/README.md index d00c86bc..e393c936 100644 --- a/README.md +++ b/README.md @@ -245,6 +245,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: From 586f029eddd02df8796203a29dcd75b5093e375e Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Thu, 18 Feb 2021 20:05:08 -0800 Subject: [PATCH 198/278] [Tests] fix tests on node v0.6 --- .github/workflows/node-zero.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/node-zero.yml b/.github/workflows/node-zero.yml index 4e72a688..6f18484c 100644 --- a/.github/workflows/node-zero.yml +++ b/.github/workflows/node-zero.yml @@ -52,7 +52,11 @@ jobs: cache-node-modules-key: node_modules-${{ github.workflow }}-${{ github.action }}-${{ github.run_id }} skip-ls-check: true - run: npm run tests-only + if: ${{ matrix.node-version }} != 0.6 + - run: ./node_modules/.bin/tape 'test/**/*.js' + if: ${{ matrix.node-version }} = 0.6 - run: bash <(curl -s https://codecov.io/bash) -f coverage/*.json; + if: ${{ matrix.node-version }} != 0.6 node: name: 'node 0.x' From 63766c2c22282b50e701e75f961071380ecd6f8c Mon Sep 17 00:00:00 2001 From: Johan Date: Thu, 28 Jan 2021 16:08:39 +0100 Subject: [PATCH 199/278] [New] `stringify`: throw on cycles, instead of an infinite loop Fixes #367. Co-authored-by: Jordan Harband Co-authored-by: Johan --- .eslintrc | 2 +- lib/stringify.js | 17 ++++++++++++++--- package.json | 5 ++++- test/stringify.js | 25 ++++++++++++++++++++++++- 4 files changed, 43 insertions(+), 6 deletions(-) diff --git a/.eslintrc b/.eslintrc index e448a2f5..2c680d75 100644 --- a/.eslintrc +++ b/.eslintrc @@ -10,7 +10,7 @@ "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, diff --git a/lib/stringify.js b/lib/stringify.js index f46bb0e1..ab7a37e2 100644 --- a/lib/stringify.js +++ b/lib/stringify.js @@ -1,5 +1,6 @@ 'use strict'; +var getSideChannel = require('side-channel'); var utils = require('./utils'); var formats = require('./formats'); var has = Object.prototype.hasOwnProperty; @@ -68,9 +69,15 @@ var stringify = function stringify( format, formatter, encodeValuesOnly, - charset + charset, + sideChannel ) { var obj = object; + + if (sideChannel.has(object)) { + throw new RangeError('Cyclic object value'); + } + if (typeof filter === 'function') { obj = filter(prefix, obj); } else if (obj instanceof Date) { @@ -129,6 +136,7 @@ var stringify = function stringify( ? typeof generateArrayPrefix === 'function' ? generateArrayPrefix(prefix, key) : prefix : prefix + (allowDots ? '.' + key : '[' + key + ']'); + sideChannel.set(object, true); pushToArray(values, stringify( value, keyPrefix, @@ -143,7 +151,8 @@ var stringify = function stringify( format, formatter, encodeValuesOnly, - charset + charset, + sideChannel )); } @@ -237,6 +246,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]; @@ -257,7 +267,8 @@ module.exports = function (object, opts) { options.format, options.formatter, options.encodeValuesOnly, - options.charset + options.charset, + sideChannel )); } diff --git a/package.json b/package.json index cb9c122e..d6fc93b8 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,9 @@ "engines": { "node": ">=0.6" }, + "dependencies": { + "side-channel": "^1.0.4" + }, "devDependencies": { "@ljharb/eslint-config": "^17.5.1", "aud": "^1.1.4", @@ -55,7 +58,7 @@ "tests-only": "nyc tape 'test/**/*.js'", "posttest": "aud --production", "readme": "evalmd README.md", - "postlint": "eclint check * lib/* test/*", + "postlint": "eclint check * lib/* test/* !dist/*", "lint": "eslint lib/*.js test/*.js", "dist": "mkdirp dist && browserify --standalone Qs lib/index.js > dist/qs.js" }, diff --git a/test/stringify.js b/test/stringify.js index 59324e0c..b2b3f4b5 100644 --- a/test/stringify.js +++ b/test/stringify.js @@ -433,7 +433,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' }); @@ -442,6 +442,29 @@ 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 values throw' + ); + + var circular = { + a: 'value' + }; + circular.a = circular; + st['throws']( + function () { qs.stringify(circular); }, + RangeError, + 'cyclic values throw' + ); + + 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: [] }), ''); From a20cfbd853b5570f416f76e35c413faa4210f53a Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Thu, 18 Mar 2021 11:09:21 -0700 Subject: [PATCH 200/278] [Dev Deps] update `eslint`, `has-symbols`, `tape` --- package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index d6fc93b8..0ffd0f9d 100644 --- a/package.json +++ b/package.json @@ -37,10 +37,10 @@ "aud": "^1.1.4", "browserify": "^16.5.2", "eclint": "^2.8.1", - "eslint": "^7.20.0", + "eslint": "^7.22.0", "evalmd": "^0.0.19", "for-each": "^0.3.3", - "has-symbols": "^1.0.1", + "has-symbols": "^1.0.2", "iconv-lite": "^0.5.1", "in-publish": "^2.0.1", "mkdirp": "^0.5.5", @@ -49,7 +49,7 @@ "qs-iconv": "^1.0.4", "safe-publish-latest": "^1.1.4", "safer-buffer": "^2.1.2", - "tape": "^5.1.1" + "tape": "^5.2.2" }, "scripts": { "prepublish": "safe-publish-latest && (not-in-publish || npm run dist)", From 7c1fcc53047ed2d7555910fbce9f72eed1e450b1 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Thu, 18 Mar 2021 12:38:38 -0700 Subject: [PATCH 201/278] v6.10.0 --- CHANGELOG.md | 10 + dist/qs.js | 1081 +++++++++++++++++++++++++++++++++++++++++++++++++- package.json | 2 +- 3 files changed, 1087 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d43209c3..daf7a3e7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,13 @@ +## **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 diff --git a/dist/qs.js b/dist/qs.js index 861a6f13..b9c1bf40 100644 --- a/dist/qs.js +++ b/dist/qs.js @@ -47,6 +47,7 @@ var isArray = Array.isArray; var defaults = { allowDots: false, allowPrototypes: false, + allowSparse: false, arrayLimit: 20, charset: 'utf-8', charsetSentinel: false, @@ -256,6 +257,7 @@ var normalizeParseOptions = function normalizeParseOptions(opts) { 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, @@ -292,12 +294,17 @@ module.exports = function (str, opts) { obj = utils.merge(obj, newObj, options); } + if (options.allowSparse === true) { + return obj; + } + return utils.compact(obj); }; },{"./utils":5}],4:[function(require,module,exports){ 'use strict'; +var getSideChannel = require('side-channel'); var utils = require('./utils'); var formats = require('./formats'); var has = Object.prototype.hasOwnProperty; @@ -366,9 +373,15 @@ var stringify = function stringify( format, formatter, encodeValuesOnly, - charset + charset, + sideChannel ) { var obj = object; + + if (sideChannel.has(object)) { + throw new RangeError('Cyclic object value'); + } + if (typeof filter === 'function') { obj = filter(prefix, obj); } else if (obj instanceof Date) { @@ -427,6 +440,7 @@ var stringify = function stringify( ? typeof generateArrayPrefix === 'function' ? generateArrayPrefix(prefix, key) : prefix : prefix + (allowDots ? '.' + key : '[' + key + ']'); + sideChannel.set(object, true); pushToArray(values, stringify( value, keyPrefix, @@ -441,7 +455,8 @@ var stringify = function stringify( format, formatter, encodeValuesOnly, - charset + charset, + sideChannel )); } @@ -535,6 +550,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]; @@ -555,7 +571,8 @@ module.exports = function (object, opts) { options.format, options.formatter, options.encodeValuesOnly, - options.charset + options.charset, + sideChannel )); } @@ -575,7 +592,7 @@ module.exports = function (object, opts) { return joined.length > 0 ? prefix + joined : ''; }; -},{"./formats":1,"./utils":5}],5:[function(require,module,exports){ +},{"./formats":1,"./utils":5,"side-channel":16}],5:[function(require,module,exports){ 'use strict'; var formats = require('./formats'); @@ -828,5 +845,1059 @@ module.exports = { merge: merge }; -},{"./formats":1}]},{},[2])(2) +},{"./formats":1}],6:[function(require,module,exports){ + +},{}],7:[function(require,module,exports){ +'use strict'; + +var GetIntrinsic = require('get-intrinsic'); + +var callBind = require('./'); + +var $indexOf = callBind(GetIntrinsic('String.prototype.indexOf')); + +module.exports = function callBoundIntrinsic(name, allowMissing) { + var intrinsic = GetIntrinsic(name, !!allowMissing); + if (typeof intrinsic === 'function' && $indexOf(name, '.prototype.') > -1) { + return callBind(intrinsic); + } + return intrinsic; +}; + +},{"./":8,"get-intrinsic":11}],8:[function(require,module,exports){ +'use strict'; + +var bind = require('function-bind'); +var GetIntrinsic = require('get-intrinsic'); + +var $apply = GetIntrinsic('%Function.prototype.apply%'); +var $call = GetIntrinsic('%Function.prototype.call%'); +var $reflectApply = GetIntrinsic('%Reflect.apply%', true) || bind.call($call, $apply); + +var $gOPD = GetIntrinsic('%Object.getOwnPropertyDescriptor%', true); +var $defineProperty = GetIntrinsic('%Object.defineProperty%', true); +var $max = GetIntrinsic('%Math.max%'); + +if ($defineProperty) { + try { + $defineProperty({}, 'a', { value: 1 }); + } catch (e) { + // IE 8 has a broken defineProperty + $defineProperty = null; + } +} + +module.exports = function callBind(originalFunction) { + var func = $reflectApply(bind, $call, arguments); + if ($gOPD && $defineProperty) { + var desc = $gOPD(func, 'length'); + if (desc.configurable) { + // original length, plus the receiver, minus any additional arguments (after the receiver) + $defineProperty( + func, + 'length', + { value: 1 + $max(0, originalFunction.length - (arguments.length - 1)) } + ); + } + } + return func; +}; + +var applyBind = function applyBind() { + return $reflectApply(bind, $apply, arguments); +}; + +if ($defineProperty) { + $defineProperty(module.exports, 'apply', { value: applyBind }); +} else { + module.exports.apply = applyBind; +} + +},{"function-bind":10,"get-intrinsic":11}],9:[function(require,module,exports){ +'use strict'; + +/* eslint no-invalid-this: 1 */ + +var ERROR_MESSAGE = 'Function.prototype.bind called on incompatible '; +var slice = Array.prototype.slice; +var toStr = Object.prototype.toString; +var funcType = '[object Function]'; + +module.exports = function bind(that) { + var target = this; + if (typeof target !== 'function' || toStr.call(target) !== funcType) { + throw new TypeError(ERROR_MESSAGE + target); + } + var args = slice.call(arguments, 1); + + var bound; + var binder = function () { + if (this instanceof bound) { + var result = target.apply( + this, + args.concat(slice.call(arguments)) + ); + if (Object(result) === result) { + return result; + } + return this; + } else { + return target.apply( + that, + args.concat(slice.call(arguments)) + ); + } + }; + + var boundLength = Math.max(0, target.length - args.length); + var boundArgs = []; + for (var i = 0; i < boundLength; i++) { + boundArgs.push('$' + i); + } + + bound = Function('binder', 'return function (' + boundArgs.join(',') + '){ return binder.apply(this,arguments); }')(binder); + + if (target.prototype) { + var Empty = function Empty() {}; + Empty.prototype = target.prototype; + bound.prototype = new Empty(); + Empty.prototype = null; + } + + return bound; +}; + +},{}],10:[function(require,module,exports){ +'use strict'; + +var implementation = require('./implementation'); + +module.exports = Function.prototype.bind || implementation; + +},{"./implementation":9}],11:[function(require,module,exports){ +'use strict'; + +var undefined; + +var $SyntaxError = SyntaxError; +var $Function = Function; +var $TypeError = TypeError; + +// eslint-disable-next-line consistent-return +var getEvalledConstructor = function (expressionSyntax) { + try { + return $Function('"use strict"; return (' + expressionSyntax + ').constructor;')(); + } catch (e) {} +}; + +var $gOPD = Object.getOwnPropertyDescriptor; +if ($gOPD) { + try { + $gOPD({}, ''); + } catch (e) { + $gOPD = null; // this is IE 8, which has a broken gOPD + } +} + +var throwTypeError = function () { + throw new $TypeError(); +}; +var ThrowTypeError = $gOPD + ? (function () { + try { + // eslint-disable-next-line no-unused-expressions, no-caller, no-restricted-properties + arguments.callee; // IE 8 does not throw here + return throwTypeError; + } catch (calleeThrows) { + try { + // IE 8 throws on Object.getOwnPropertyDescriptor(arguments, '') + return $gOPD(arguments, 'callee').get; + } catch (gOPDthrows) { + return throwTypeError; + } + } + }()) + : throwTypeError; + +var hasSymbols = require('has-symbols')(); + +var getProto = Object.getPrototypeOf || function (x) { return x.__proto__; }; // eslint-disable-line no-proto + +var needsEval = {}; + +var TypedArray = typeof Uint8Array === 'undefined' ? undefined : getProto(Uint8Array); + +var INTRINSICS = { + '%AggregateError%': typeof AggregateError === 'undefined' ? undefined : AggregateError, + '%Array%': Array, + '%ArrayBuffer%': typeof ArrayBuffer === 'undefined' ? undefined : ArrayBuffer, + '%ArrayIteratorPrototype%': hasSymbols ? getProto([][Symbol.iterator]()) : undefined, + '%AsyncFromSyncIteratorPrototype%': undefined, + '%AsyncFunction%': needsEval, + '%AsyncGenerator%': needsEval, + '%AsyncGeneratorFunction%': needsEval, + '%AsyncIteratorPrototype%': needsEval, + '%Atomics%': typeof Atomics === 'undefined' ? undefined : Atomics, + '%BigInt%': typeof BigInt === 'undefined' ? undefined : BigInt, + '%Boolean%': Boolean, + '%DataView%': typeof DataView === 'undefined' ? undefined : DataView, + '%Date%': Date, + '%decodeURI%': decodeURI, + '%decodeURIComponent%': decodeURIComponent, + '%encodeURI%': encodeURI, + '%encodeURIComponent%': encodeURIComponent, + '%Error%': Error, + '%eval%': eval, // eslint-disable-line no-eval + '%EvalError%': EvalError, + '%Float32Array%': typeof Float32Array === 'undefined' ? undefined : Float32Array, + '%Float64Array%': typeof Float64Array === 'undefined' ? undefined : Float64Array, + '%FinalizationRegistry%': typeof FinalizationRegistry === 'undefined' ? undefined : FinalizationRegistry, + '%Function%': $Function, + '%GeneratorFunction%': needsEval, + '%Int8Array%': typeof Int8Array === 'undefined' ? undefined : Int8Array, + '%Int16Array%': typeof Int16Array === 'undefined' ? undefined : Int16Array, + '%Int32Array%': typeof Int32Array === 'undefined' ? undefined : Int32Array, + '%isFinite%': isFinite, + '%isNaN%': isNaN, + '%IteratorPrototype%': hasSymbols ? getProto(getProto([][Symbol.iterator]())) : undefined, + '%JSON%': typeof JSON === 'object' ? JSON : undefined, + '%Map%': typeof Map === 'undefined' ? undefined : Map, + '%MapIteratorPrototype%': typeof Map === 'undefined' || !hasSymbols ? undefined : getProto(new Map()[Symbol.iterator]()), + '%Math%': Math, + '%Number%': Number, + '%Object%': Object, + '%parseFloat%': parseFloat, + '%parseInt%': parseInt, + '%Promise%': typeof Promise === 'undefined' ? undefined : Promise, + '%Proxy%': typeof Proxy === 'undefined' ? undefined : Proxy, + '%RangeError%': RangeError, + '%ReferenceError%': ReferenceError, + '%Reflect%': typeof Reflect === 'undefined' ? undefined : Reflect, + '%RegExp%': RegExp, + '%Set%': typeof Set === 'undefined' ? undefined : Set, + '%SetIteratorPrototype%': typeof Set === 'undefined' || !hasSymbols ? undefined : getProto(new Set()[Symbol.iterator]()), + '%SharedArrayBuffer%': typeof SharedArrayBuffer === 'undefined' ? undefined : SharedArrayBuffer, + '%String%': String, + '%StringIteratorPrototype%': hasSymbols ? getProto(''[Symbol.iterator]()) : undefined, + '%Symbol%': hasSymbols ? Symbol : undefined, + '%SyntaxError%': $SyntaxError, + '%ThrowTypeError%': ThrowTypeError, + '%TypedArray%': TypedArray, + '%TypeError%': $TypeError, + '%Uint8Array%': typeof Uint8Array === 'undefined' ? undefined : Uint8Array, + '%Uint8ClampedArray%': typeof Uint8ClampedArray === 'undefined' ? undefined : Uint8ClampedArray, + '%Uint16Array%': typeof Uint16Array === 'undefined' ? undefined : Uint16Array, + '%Uint32Array%': typeof Uint32Array === 'undefined' ? undefined : Uint32Array, + '%URIError%': URIError, + '%WeakMap%': typeof WeakMap === 'undefined' ? undefined : WeakMap, + '%WeakRef%': typeof WeakRef === 'undefined' ? undefined : WeakRef, + '%WeakSet%': typeof WeakSet === 'undefined' ? undefined : WeakSet +}; + +var doEval = function doEval(name) { + var value; + if (name === '%AsyncFunction%') { + value = getEvalledConstructor('async function () {}'); + } else if (name === '%GeneratorFunction%') { + value = getEvalledConstructor('function* () {}'); + } else if (name === '%AsyncGeneratorFunction%') { + value = getEvalledConstructor('async function* () {}'); + } else if (name === '%AsyncGenerator%') { + var fn = doEval('%AsyncGeneratorFunction%'); + if (fn) { + value = fn.prototype; + } + } else if (name === '%AsyncIteratorPrototype%') { + var gen = doEval('%AsyncGenerator%'); + if (gen) { + value = getProto(gen.prototype); + } + } + + INTRINSICS[name] = value; + + return value; +}; + +var LEGACY_ALIASES = { + '%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'] +}; + +var bind = require('function-bind'); +var hasOwn = require('has'); +var $concat = bind.call(Function.call, Array.prototype.concat); +var $spliceApply = bind.call(Function.apply, Array.prototype.splice); +var $replace = bind.call(Function.call, String.prototype.replace); +var $strSlice = bind.call(Function.call, String.prototype.slice); + +/* adapted from https://github.com/lodash/lodash/blob/4.17.15/dist/lodash.js#L6735-L6744 */ +var rePropName = /[^%.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\\]|\\.)*?)\2)\]|(?=(?:\.|\[\])(?:\.|\[\]|%$))/g; +var reEscapeChar = /\\(\\)?/g; /** Used to match backslashes in property paths. */ +var stringToPath = function stringToPath(string) { + var first = $strSlice(string, 0, 1); + var last = $strSlice(string, -1); + if (first === '%' && last !== '%') { + throw new $SyntaxError('invalid intrinsic syntax, expected closing `%`'); + } else if (last === '%' && first !== '%') { + throw new $SyntaxError('invalid intrinsic syntax, expected opening `%`'); + } + var result = []; + $replace(string, rePropName, function (match, number, quote, subString) { + result[result.length] = quote ? $replace(subString, reEscapeChar, '$1') : number || match; + }); + return result; +}; +/* end adaptation */ + +var getBaseIntrinsic = function getBaseIntrinsic(name, allowMissing) { + var intrinsicName = name; + var alias; + if (hasOwn(LEGACY_ALIASES, intrinsicName)) { + alias = LEGACY_ALIASES[intrinsicName]; + intrinsicName = '%' + alias[0] + '%'; + } + + if (hasOwn(INTRINSICS, intrinsicName)) { + var value = INTRINSICS[intrinsicName]; + if (value === needsEval) { + value = doEval(intrinsicName); + } + if (typeof value === 'undefined' && !allowMissing) { + throw new $TypeError('intrinsic ' + name + ' exists, but is not available. Please file an issue!'); + } + + return { + alias: alias, + name: intrinsicName, + value: value + }; + } + + throw new $SyntaxError('intrinsic ' + name + ' does not exist!'); +}; + +module.exports = function GetIntrinsic(name, allowMissing) { + if (typeof name !== 'string' || name.length === 0) { + throw new $TypeError('intrinsic name must be a non-empty string'); + } + if (arguments.length > 1 && typeof allowMissing !== 'boolean') { + throw new $TypeError('"allowMissing" argument must be a boolean'); + } + + var parts = stringToPath(name); + var intrinsicBaseName = parts.length > 0 ? parts[0] : ''; + + var intrinsic = getBaseIntrinsic('%' + intrinsicBaseName + '%', allowMissing); + var intrinsicRealName = intrinsic.name; + var value = intrinsic.value; + var skipFurtherCaching = false; + + var alias = intrinsic.alias; + if (alias) { + intrinsicBaseName = alias[0]; + $spliceApply(parts, $concat([0, 1], alias)); + } + + for (var i = 1, isOwn = true; i < parts.length; i += 1) { + var part = parts[i]; + var first = $strSlice(part, 0, 1); + var last = $strSlice(part, -1); + if ( + ( + (first === '"' || first === "'" || first === '`') + || (last === '"' || last === "'" || last === '`') + ) + && first !== last + ) { + throw new $SyntaxError('property names with quotes must have matching quotes'); + } + if (part === 'constructor' || !isOwn) { + skipFurtherCaching = true; + } + + intrinsicBaseName += '.' + part; + intrinsicRealName = '%' + intrinsicBaseName + '%'; + + if (hasOwn(INTRINSICS, intrinsicRealName)) { + value = INTRINSICS[intrinsicRealName]; + } else if (value != null) { + if (!(part in value)) { + if (!allowMissing) { + throw new $TypeError('base intrinsic for ' + name + ' exists, but the property is not available.'); + } + return void undefined; + } + if ($gOPD && (i + 1) >= parts.length) { + var desc = $gOPD(value, part); + isOwn = !!desc; + + // By convention, when a data property is converted to an accessor + // property to emulate a data property that does not suffer from + // the override mistake, that accessor's getter is marked with + // an `originalValue` property. Here, when we detect this, we + // uphold the illusion by pretending to see that original data + // property, i.e., returning the value rather than the getter + // itself. + if (isOwn && 'get' in desc && !('originalValue' in desc.get)) { + value = desc.get; + } else { + value = value[part]; + } + } else { + isOwn = hasOwn(value, part); + value = value[part]; + } + + if (isOwn && !skipFurtherCaching) { + INTRINSICS[intrinsicRealName] = value; + } + } + } + return value; +}; + +},{"function-bind":10,"has":14,"has-symbols":12}],12:[function(require,module,exports){ +'use strict'; + +var origSymbol = typeof Symbol !== 'undefined' && Symbol; +var hasSymbolSham = require('./shams'); + +module.exports = function hasNativeSymbols() { + if (typeof origSymbol !== 'function') { return false; } + if (typeof Symbol !== 'function') { return false; } + if (typeof origSymbol('foo') !== 'symbol') { return false; } + if (typeof Symbol('bar') !== 'symbol') { return false; } + + return hasSymbolSham(); +}; + +},{"./shams":13}],13:[function(require,module,exports){ +'use strict'; + +/* eslint complexity: [2, 18], max-statements: [2, 33] */ +module.exports = function hasSymbols() { + if (typeof Symbol !== 'function' || typeof Object.getOwnPropertySymbols !== 'function') { return false; } + if (typeof Symbol.iterator === 'symbol') { return true; } + + var obj = {}; + var sym = Symbol('test'); + var symObj = Object(sym); + if (typeof sym === 'string') { return false; } + + if (Object.prototype.toString.call(sym) !== '[object Symbol]') { return false; } + if (Object.prototype.toString.call(symObj) !== '[object Symbol]') { return false; } + + // temp disabled per https://github.com/ljharb/object.assign/issues/17 + // if (sym instanceof Symbol) { return false; } + // temp disabled per https://github.com/WebReflection/get-own-property-symbols/issues/4 + // if (!(symObj instanceof Symbol)) { return false; } + + // if (typeof Symbol.prototype.toString !== 'function') { return false; } + // if (String(sym) !== Symbol.prototype.toString.call(sym)) { return false; } + + var symVal = 42; + obj[sym] = symVal; + for (sym in obj) { return false; } // eslint-disable-line no-restricted-syntax, no-unreachable-loop + if (typeof Object.keys === 'function' && Object.keys(obj).length !== 0) { return false; } + + if (typeof Object.getOwnPropertyNames === 'function' && Object.getOwnPropertyNames(obj).length !== 0) { return false; } + + var syms = Object.getOwnPropertySymbols(obj); + if (syms.length !== 1 || syms[0] !== sym) { return false; } + + if (!Object.prototype.propertyIsEnumerable.call(obj, sym)) { return false; } + + if (typeof Object.getOwnPropertyDescriptor === 'function') { + var descriptor = Object.getOwnPropertyDescriptor(obj, sym); + if (descriptor.value !== symVal || descriptor.enumerable !== true) { return false; } + } + + return true; +}; + +},{}],14:[function(require,module,exports){ +'use strict'; + +var bind = require('function-bind'); + +module.exports = bind.call(Function.call, Object.prototype.hasOwnProperty); + +},{"function-bind":10}],15:[function(require,module,exports){ +var hasMap = typeof Map === 'function' && Map.prototype; +var mapSizeDescriptor = Object.getOwnPropertyDescriptor && hasMap ? Object.getOwnPropertyDescriptor(Map.prototype, 'size') : null; +var mapSize = hasMap && mapSizeDescriptor && typeof mapSizeDescriptor.get === 'function' ? mapSizeDescriptor.get : null; +var mapForEach = hasMap && Map.prototype.forEach; +var hasSet = typeof Set === 'function' && Set.prototype; +var setSizeDescriptor = Object.getOwnPropertyDescriptor && hasSet ? Object.getOwnPropertyDescriptor(Set.prototype, 'size') : null; +var setSize = hasSet && setSizeDescriptor && typeof setSizeDescriptor.get === 'function' ? setSizeDescriptor.get : null; +var setForEach = hasSet && Set.prototype.forEach; +var hasWeakMap = typeof WeakMap === 'function' && WeakMap.prototype; +var weakMapHas = hasWeakMap ? WeakMap.prototype.has : null; +var hasWeakSet = typeof WeakSet === 'function' && WeakSet.prototype; +var weakSetHas = hasWeakSet ? WeakSet.prototype.has : null; +var booleanValueOf = Boolean.prototype.valueOf; +var objectToString = Object.prototype.toString; +var functionToString = Function.prototype.toString; +var match = String.prototype.match; +var bigIntValueOf = typeof BigInt === 'function' ? BigInt.prototype.valueOf : null; +var gOPS = Object.getOwnPropertySymbols; +var symToString = typeof Symbol === 'function' ? Symbol.prototype.toString : null; +var isEnumerable = Object.prototype.propertyIsEnumerable; + +var inspectCustom = require('./util.inspect').custom; +var inspectSymbol = inspectCustom && isSymbol(inspectCustom) ? inspectCustom : null; + +module.exports = function inspect_(obj, options, depth, seen) { + var opts = options || {}; + + if (has(opts, 'quoteStyle') && (opts.quoteStyle !== 'single' && opts.quoteStyle !== 'double')) { + throw new TypeError('option "quoteStyle" must be "single" or "double"'); + } + if ( + has(opts, 'maxStringLength') && (typeof opts.maxStringLength === 'number' + ? opts.maxStringLength < 0 && opts.maxStringLength !== Infinity + : opts.maxStringLength !== null + ) + ) { + throw new TypeError('option "maxStringLength", if provided, must be a positive integer, Infinity, or `null`'); + } + var customInspect = has(opts, 'customInspect') ? opts.customInspect : true; + if (typeof customInspect !== 'boolean') { + throw new TypeError('option "customInspect", if provided, must be `true` or `false`'); + } + + if ( + has(opts, 'indent') + && opts.indent !== null + && opts.indent !== '\t' + && !(parseInt(opts.indent, 10) === opts.indent && opts.indent > 0) + ) { + throw new TypeError('options "indent" must be "\\t", an integer > 0, or `null`'); + } + + if (typeof obj === 'undefined') { + return 'undefined'; + } + if (obj === null) { + return 'null'; + } + if (typeof obj === 'boolean') { + return obj ? 'true' : 'false'; + } + + if (typeof obj === 'string') { + return inspectString(obj, opts); + } + if (typeof obj === 'number') { + if (obj === 0) { + return Infinity / obj > 0 ? '0' : '-0'; + } + return String(obj); + } + if (typeof obj === 'bigint') { + return String(obj) + 'n'; + } + + var maxDepth = typeof opts.depth === 'undefined' ? 5 : opts.depth; + if (typeof depth === 'undefined') { depth = 0; } + if (depth >= maxDepth && maxDepth > 0 && typeof obj === 'object') { + return isArray(obj) ? '[Array]' : '[Object]'; + } + + var indent = getIndent(opts, depth); + + if (typeof seen === 'undefined') { + seen = []; + } else if (indexOf(seen, obj) >= 0) { + return '[Circular]'; + } + + function inspect(value, from, noIndent) { + if (from) { + seen = seen.slice(); + seen.push(from); + } + if (noIndent) { + var newOpts = { + depth: opts.depth + }; + if (has(opts, 'quoteStyle')) { + newOpts.quoteStyle = opts.quoteStyle; + } + return inspect_(value, newOpts, depth + 1, seen); + } + return inspect_(value, opts, depth + 1, seen); + } + + if (typeof obj === 'function') { + var name = nameOf(obj); + var keys = arrObjKeys(obj, inspect); + return '[Function' + (name ? ': ' + name : ' (anonymous)') + ']' + (keys.length > 0 ? ' { ' + keys.join(', ') + ' }' : ''); + } + if (isSymbol(obj)) { + var symString = symToString.call(obj); + return typeof obj === 'object' ? markBoxed(symString) : symString; + } + if (isElement(obj)) { + var s = '<' + String(obj.nodeName).toLowerCase(); + var attrs = obj.attributes || []; + for (var i = 0; i < attrs.length; i++) { + s += ' ' + attrs[i].name + '=' + wrapQuotes(quote(attrs[i].value), 'double', opts); + } + s += '>'; + if (obj.childNodes && obj.childNodes.length) { s += '...'; } + s += ''; + return s; + } + if (isArray(obj)) { + if (obj.length === 0) { return '[]'; } + var xs = arrObjKeys(obj, inspect); + if (indent && !singleLineValues(xs)) { + return '[' + indentedJoin(xs, indent) + ']'; + } + return '[ ' + xs.join(', ') + ' ]'; + } + if (isError(obj)) { + var parts = arrObjKeys(obj, inspect); + if (parts.length === 0) { return '[' + String(obj) + ']'; } + return '{ [' + String(obj) + '] ' + parts.join(', ') + ' }'; + } + if (typeof obj === 'object' && customInspect) { + if (inspectSymbol && typeof obj[inspectSymbol] === 'function') { + return obj[inspectSymbol](); + } else if (typeof obj.inspect === 'function') { + return obj.inspect(); + } + } + if (isMap(obj)) { + var mapParts = []; + mapForEach.call(obj, function (value, key) { + mapParts.push(inspect(key, obj, true) + ' => ' + inspect(value, obj)); + }); + return collectionOf('Map', mapSize.call(obj), mapParts, indent); + } + if (isSet(obj)) { + var setParts = []; + setForEach.call(obj, function (value) { + setParts.push(inspect(value, obj)); + }); + return collectionOf('Set', setSize.call(obj), setParts, indent); + } + if (isWeakMap(obj)) { + return weakCollectionOf('WeakMap'); + } + if (isWeakSet(obj)) { + return weakCollectionOf('WeakSet'); + } + if (isNumber(obj)) { + return markBoxed(inspect(Number(obj))); + } + if (isBigInt(obj)) { + return markBoxed(inspect(bigIntValueOf.call(obj))); + } + if (isBoolean(obj)) { + return markBoxed(booleanValueOf.call(obj)); + } + if (isString(obj)) { + return markBoxed(inspect(String(obj))); + } + if (!isDate(obj) && !isRegExp(obj)) { + var ys = arrObjKeys(obj, inspect); + if (ys.length === 0) { return '{}'; } + if (indent) { + return '{' + indentedJoin(ys, indent) + '}'; + } + return '{ ' + ys.join(', ') + ' }'; + } + return String(obj); +}; + +function wrapQuotes(s, defaultStyle, opts) { + var quoteChar = (opts.quoteStyle || defaultStyle) === 'double' ? '"' : "'"; + return quoteChar + s + quoteChar; +} + +function quote(s) { + return String(s).replace(/"/g, '"'); +} + +function isArray(obj) { return toStr(obj) === '[object Array]'; } +function isDate(obj) { return toStr(obj) === '[object Date]'; } +function isRegExp(obj) { return toStr(obj) === '[object RegExp]'; } +function isError(obj) { return toStr(obj) === '[object Error]'; } +function isSymbol(obj) { return toStr(obj) === '[object Symbol]'; } +function isString(obj) { return toStr(obj) === '[object String]'; } +function isNumber(obj) { return toStr(obj) === '[object Number]'; } +function isBigInt(obj) { return toStr(obj) === '[object BigInt]'; } +function isBoolean(obj) { return toStr(obj) === '[object Boolean]'; } + +var hasOwn = Object.prototype.hasOwnProperty || function (key) { return key in this; }; +function has(obj, key) { + return hasOwn.call(obj, key); +} + +function toStr(obj) { + return objectToString.call(obj); +} + +function nameOf(f) { + if (f.name) { return f.name; } + var m = match.call(functionToString.call(f), /^function\s*([\w$]+)/); + if (m) { return m[1]; } + return null; +} + +function indexOf(xs, x) { + if (xs.indexOf) { return xs.indexOf(x); } + for (var i = 0, l = xs.length; i < l; i++) { + if (xs[i] === x) { return i; } + } + return -1; +} + +function isMap(x) { + if (!mapSize || !x || typeof x !== 'object') { + return false; + } + try { + mapSize.call(x); + try { + setSize.call(x); + } catch (s) { + return true; + } + return x instanceof Map; // core-js workaround, pre-v2.5.0 + } catch (e) {} + return false; +} + +function isWeakMap(x) { + if (!weakMapHas || !x || typeof x !== 'object') { + return false; + } + try { + weakMapHas.call(x, weakMapHas); + try { + weakSetHas.call(x, weakSetHas); + } catch (s) { + return true; + } + return x instanceof WeakMap; // core-js workaround, pre-v2.5.0 + } catch (e) {} + return false; +} + +function isSet(x) { + if (!setSize || !x || typeof x !== 'object') { + return false; + } + try { + setSize.call(x); + try { + mapSize.call(x); + } catch (m) { + return true; + } + return x instanceof Set; // core-js workaround, pre-v2.5.0 + } catch (e) {} + return false; +} + +function isWeakSet(x) { + if (!weakSetHas || !x || typeof x !== 'object') { + return false; + } + try { + weakSetHas.call(x, weakSetHas); + try { + weakMapHas.call(x, weakMapHas); + } catch (s) { + return true; + } + return x instanceof WeakSet; // core-js workaround, pre-v2.5.0 + } catch (e) {} + return false; +} + +function isElement(x) { + if (!x || typeof x !== 'object') { return false; } + if (typeof HTMLElement !== 'undefined' && x instanceof HTMLElement) { + return true; + } + return typeof x.nodeName === 'string' && typeof x.getAttribute === 'function'; +} + +function inspectString(str, opts) { + if (str.length > opts.maxStringLength) { + var remaining = str.length - opts.maxStringLength; + var trailer = '... ' + remaining + ' more character' + (remaining > 1 ? 's' : ''); + return inspectString(str.slice(0, opts.maxStringLength), opts) + trailer; + } + // eslint-disable-next-line no-control-regex + var s = str.replace(/(['\\])/g, '\\$1').replace(/[\x00-\x1f]/g, lowbyte); + return wrapQuotes(s, 'single', opts); +} + +function lowbyte(c) { + var n = c.charCodeAt(0); + var x = { + 8: 'b', + 9: 't', + 10: 'n', + 12: 'f', + 13: 'r' + }[n]; + if (x) { return '\\' + x; } + return '\\x' + (n < 0x10 ? '0' : '') + n.toString(16).toUpperCase(); +} + +function markBoxed(str) { + return 'Object(' + str + ')'; +} + +function weakCollectionOf(type) { + return type + ' { ? }'; +} + +function collectionOf(type, size, entries, indent) { + var joinedEntries = indent ? indentedJoin(entries, indent) : entries.join(', '); + return type + ' (' + size + ') {' + joinedEntries + '}'; +} + +function singleLineValues(xs) { + for (var i = 0; i < xs.length; i++) { + if (indexOf(xs[i], '\n') >= 0) { + return false; + } + } + return true; +} + +function getIndent(opts, depth) { + var baseIndent; + if (opts.indent === '\t') { + baseIndent = '\t'; + } else if (typeof opts.indent === 'number' && opts.indent > 0) { + baseIndent = Array(opts.indent + 1).join(' '); + } else { + return null; + } + return { + base: baseIndent, + prev: Array(depth + 1).join(baseIndent) + }; +} + +function indentedJoin(xs, indent) { + if (xs.length === 0) { return ''; } + var lineJoiner = '\n' + indent.prev + indent.base; + return lineJoiner + xs.join(',' + lineJoiner) + '\n' + indent.prev; +} + +function arrObjKeys(obj, inspect) { + var isArr = isArray(obj); + var xs = []; + if (isArr) { + xs.length = obj.length; + for (var i = 0; i < obj.length; i++) { + xs[i] = has(obj, i) ? inspect(obj[i], obj) : ''; + } + } + for (var key in obj) { // eslint-disable-line no-restricted-syntax + if (!has(obj, key)) { continue; } // eslint-disable-line no-restricted-syntax, no-continue + if (isArr && String(Number(key)) === key && key < obj.length) { continue; } // eslint-disable-line no-restricted-syntax, no-continue + if ((/[^\w$]/).test(key)) { + xs.push(inspect(key, obj) + ': ' + inspect(obj[key], obj)); + } else { + xs.push(key + ': ' + inspect(obj[key], obj)); + } + } + if (typeof gOPS === 'function') { + var syms = gOPS(obj); + for (var j = 0; j < syms.length; j++) { + if (isEnumerable.call(obj, syms[j])) { + xs.push('[' + inspect(syms[j]) + ']: ' + inspect(obj[syms[j]], obj)); + } + } + } + return xs; +} + +},{"./util.inspect":6}],16:[function(require,module,exports){ +'use strict'; + +var GetIntrinsic = require('get-intrinsic'); +var callBound = require('call-bind/callBound'); +var inspect = require('object-inspect'); + +var $TypeError = GetIntrinsic('%TypeError%'); +var $WeakMap = GetIntrinsic('%WeakMap%', true); +var $Map = GetIntrinsic('%Map%', true); + +var $weakMapGet = callBound('WeakMap.prototype.get', true); +var $weakMapSet = callBound('WeakMap.prototype.set', true); +var $weakMapHas = callBound('WeakMap.prototype.has', true); +var $mapGet = callBound('Map.prototype.get', true); +var $mapSet = callBound('Map.prototype.set', true); +var $mapHas = callBound('Map.prototype.has', true); + +/* + * This function traverses the list returning the node corresponding to the + * given key. + * + * That node is also moved to the head of the list, so that if it's accessed + * again we don't need to traverse the whole list. By doing so, all the recently + * used nodes can be accessed relatively quickly. + */ +var listGetNode = function (list, key) { // eslint-disable-line consistent-return + for (var prev = list, curr; (curr = prev.next) !== null; prev = curr) { + if (curr.key === key) { + prev.next = curr.next; + curr.next = list.next; + list.next = curr; // eslint-disable-line no-param-reassign + return curr; + } + } +}; + +var listGet = function (objects, key) { + var node = listGetNode(objects, key); + return node && node.value; +}; +var listSet = function (objects, key, value) { + var node = listGetNode(objects, key); + if (node) { + node.value = value; + } else { + // Prepend the new node to the beginning of the list + objects.next = { // eslint-disable-line no-param-reassign + key: key, + next: objects.next, + value: value + }; + } +}; +var listHas = function (objects, key) { + return !!listGetNode(objects, key); +}; + +module.exports = function getSideChannel() { + var $wm; + var $m; + var $o; + var channel = { + assert: function (key) { + if (!channel.has(key)) { + throw new $TypeError('Side channel does not contain ' + inspect(key)); + } + }, + get: function (key) { // eslint-disable-line consistent-return + if ($WeakMap && key && (typeof key === 'object' || typeof key === 'function')) { + if ($wm) { + return $weakMapGet($wm, key); + } + } else if ($Map) { + if ($m) { + return $mapGet($m, key); + } + } else { + if ($o) { // eslint-disable-line no-lonely-if + return listGet($o, key); + } + } + }, + has: function (key) { + if ($WeakMap && key && (typeof key === 'object' || typeof key === 'function')) { + if ($wm) { + return $weakMapHas($wm, key); + } + } else if ($Map) { + if ($m) { + return $mapHas($m, key); + } + } else { + if ($o) { // eslint-disable-line no-lonely-if + return listHas($o, key); + } + } + return false; + }, + set: function (key, value) { + if ($WeakMap && key && (typeof key === 'object' || typeof key === 'function')) { + if (!$wm) { + $wm = new $WeakMap(); + } + $weakMapSet($wm, key, value); + } else if ($Map) { + if (!$m) { + $m = new $Map(); + } + $mapSet($m, key, value); + } else { + if (!$o) { + /* + * Initialize the linked list as an empty node, so that we don't have + * to special-case handling of the first node: we can always refer to + * it as (previous node).next, instead of something like (list).head + */ + $o = { key: {}, next: null }; + } + listSet($o, key, value); + } + } + }; + return channel; +}; + +},{"call-bind/callBound":7,"get-intrinsic":11,"object-inspect":15}]},{},[2])(2) }); diff --git a/package.json b/package.json index 0ffd0f9d..b502cb43 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "qs", "description": "A querystring parser that supports nesting and arrays, with a depth limit", "homepage": "https://github.com/ljharb/qs", - "version": "6.9.6", + "version": "6.10.0", "repository": { "type": "git", "url": "https://github.com/ljharb/qs.git" From e77ca2c471f3d581e2e029d22343fc67ccce7a14 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Sun, 21 Mar 2021 20:00:46 -0700 Subject: [PATCH 202/278] [Fix] `stringify`: avoid exception on repeated object values Fixes #402 --- lib/stringify.js | 3 ++- test/stringify.js | 22 ++++++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/lib/stringify.js b/lib/stringify.js index ab7a37e2..b8cee4bc 100644 --- a/lib/stringify.js +++ b/lib/stringify.js @@ -137,6 +137,7 @@ var stringify = function stringify( : prefix + (allowDots ? '.' + key : '[' + key + ']'); sideChannel.set(object, true); + var valueSideChannel = getSideChannel(); pushToArray(values, stringify( value, keyPrefix, @@ -152,7 +153,7 @@ var stringify = function stringify( formatter, encodeValuesOnly, charset, - sideChannel + valueSideChannel )); } diff --git a/test/stringify.js b/test/stringify.js index b2b3f4b5..931ac0dd 100644 --- a/test/stringify.js +++ b/test/stringify.js @@ -465,6 +465,28 @@ test('stringify()', function (t) { 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: [] }), ''); From dd0f954e4c00b02915f4cdc3ee5174ebc351f1c8 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Sun, 21 Mar 2021 20:02:14 -0700 Subject: [PATCH 203/278] v6.10.1 --- CHANGELOG.md | 3 +++ package.json | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index daf7a3e7..74dda430 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +## **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) diff --git a/package.json b/package.json index b502cb43..2f1326e0 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "qs", "description": "A querystring parser that supports nesting and arrays, with a depth limit", "homepage": "https://github.com/ljharb/qs", - "version": "6.10.0", + "version": "6.10.1", "repository": { "type": "git", "url": "https://github.com/ljharb/qs.git" From 749a58467c1a28744de66d5193a1a19c079927e0 Mon Sep 17 00:00:00 2001 From: Ryan Wheale Date: Fri, 9 Apr 2021 14:22:19 -0600 Subject: [PATCH 204/278] [Docs] add note and links for coercing primitive values (#408) See #91 --- .editorconfig | 1 + README.md | 11 +++++++++++ 2 files changed, 12 insertions(+) diff --git a/.editorconfig b/.editorconfig index 91040dde..0ea91d99 100644 --- a/.editorconfig +++ b/.editorconfig @@ -8,6 +8,7 @@ charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true max_line_length = 160 +quote_type = single [test/*] max_line_length = off diff --git a/README.md b/README.md index 2b6cab13..cff8ac1d 100644 --- a/README.md +++ b/README.md @@ -280,6 +280,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) From dbb54a8f14573e3c7512ea01d99f75f6ce0571f8 Mon Sep 17 00:00:00 2001 From: Ryan Wheale Date: Fri, 9 Apr 2021 14:22:19 -0600 Subject: [PATCH 205/278] [Docs] add note and links for coercing primitive values (#408) See #91 --- .editorconfig | 1 + README.md | 11 +++++++++++ 2 files changed, 12 insertions(+) diff --git a/.editorconfig b/.editorconfig index b442a9f9..ed16d717 100644 --- a/.editorconfig +++ b/.editorconfig @@ -8,6 +8,7 @@ charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true max_line_length = 160 +quote_type = single [test/*] max_line_length = off diff --git a/README.md b/README.md index 2b6cab13..cff8ac1d 100644 --- a/README.md +++ b/README.md @@ -280,6 +280,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) From 29c8f3c7de6541dbf2c8a2829f99e69da3b261a0 Mon Sep 17 00:00:00 2001 From: Ryan Wheale Date: Fri, 9 Apr 2021 14:22:19 -0600 Subject: [PATCH 206/278] [Docs] add note and links for coercing primitive values (#408) See #91 --- .editorconfig | 1 + README.md | 11 +++++++++++ 2 files changed, 12 insertions(+) diff --git a/.editorconfig b/.editorconfig index b442a9f9..ed16d717 100644 --- a/.editorconfig +++ b/.editorconfig @@ -8,6 +8,7 @@ charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true max_line_length = 160 +quote_type = single [test/*] max_line_length = off diff --git a/README.md b/README.md index 2b6cab13..cff8ac1d 100644 --- a/README.md +++ b/README.md @@ -280,6 +280,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) From c2ae487958515d96ebd62a4b79c080a4bb9d8921 Mon Sep 17 00:00:00 2001 From: Ryan Wheale Date: Fri, 9 Apr 2021 14:22:19 -0600 Subject: [PATCH 207/278] [Docs] add note and links for coercing primitive values See #91 --- .editorconfig | 1 + README.md | 11 +++++++++++ 2 files changed, 12 insertions(+) diff --git a/.editorconfig b/.editorconfig index 91040dde..0ea91d99 100644 --- a/.editorconfig +++ b/.editorconfig @@ -8,6 +8,7 @@ charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true max_line_length = 160 +quote_type = single [test/*] max_line_length = off diff --git a/README.md b/README.md index 09e2cc9e..f665b190 100644 --- a/README.md +++ b/README.md @@ -287,6 +287,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) From 4113a5f245987800ef2a8166f809a941661f1542 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Mon, 19 Apr 2021 08:31:51 -0700 Subject: [PATCH 208/278] [Tests] clean up stringify tests slightly --- test/stringify.js | 42 +++++++++++++++++++++++++++++------------- 1 file changed, 29 insertions(+), 13 deletions(-) diff --git a/test/stringify.js b/test/stringify.js index 7f0ec70c..8c113bab 100644 --- a/test/stringify.js +++ b/test/stringify.js @@ -132,10 +132,11 @@ 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%2Cd'); + st.equal(qs.stringify({ a: { b: ['c', 'd'] } }, { encodeValuesOnly: true, arrayFormat: 'comma' }), 'a[b]=c,d', '(pending issue #378)', { skip: true }); + st.equal(qs.stringify({ a: { b: ['c', 'd'] } }, { encodeValuesOnly: true }), 'a[b][0]=c&a[b][1]=d'); st.end(); }); @@ -143,7 +144,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' @@ -151,7 +152,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' @@ -159,15 +160,24 @@ 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', + 'a.b=c%2Cd', 'comma: stringifies with dots + comma' ); st.equal( qs.stringify( { a: { b: ['c', 'd'] } }, - { allowDots: true, encode: false } + { allowDots: true, encodeValuesOnly: true, arrayFormat: 'comma' } + ), + 'a.b=c,d', + 'comma: stringifies with dots + comma (pending issue #378)', + { skip: true } + ); + 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' @@ -215,17 +225,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 (pending issue #378)', + { skip: true } + ); + st.equal( + qs.stringify({ a: [{ b: 1 }, 2, 3] }, { encodeValuesOnly: true }), 'a[0][b]=1&a[1]=2&a[2]=3', 'default => indices' ); @@ -784,7 +800,7 @@ test('stringify()', function (t) { 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(obj, { encode: false, arrayFormat: 'comma' }), '???', 'array, comma (pending issue #378)', { skip: true }); + st.equal(qs.stringify(withArray, { encode: false, arrayFormat: 'comma' }), '???', 'array, comma (pending issue #378)', { skip: true }); st.end(); }); From 554ba810f1a49a25dd27c09a466490cedbee5c65 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Mon, 19 Apr 2021 08:31:51 -0700 Subject: [PATCH 209/278] [Tests] clean up stringify tests slightly --- test/stringify.js | 40 ++++++++++++++++++++++++++++------------ 1 file changed, 28 insertions(+), 12 deletions(-) diff --git a/test/stringify.js b/test/stringify.js index 0bdc26eb..cc0ba7dc 100644 --- a/test/stringify.js +++ b/test/stringify.js @@ -132,10 +132,11 @@ 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%2Cd'); + st.equal(qs.stringify({ a: { b: ['c', 'd'] } }, { encodeValuesOnly: true, arrayFormat: 'comma' }), 'a[b]=c,d', '(pending issue #378)', { skip: true }); + st.equal(qs.stringify({ a: { b: ['c', 'd'] } }, { encodeValuesOnly: true }), 'a[b][0]=c&a[b][1]=d'); st.end(); }); @@ -143,7 +144,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' @@ -151,7 +152,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' @@ -159,15 +160,24 @@ 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', + 'a.b=c%2Cd', 'comma: stringifies with dots + comma' ); st.equal( qs.stringify( { a: { b: ['c', 'd'] } }, - { allowDots: true, encode: false } + { allowDots: true, encodeValuesOnly: true, arrayFormat: 'comma' } + ), + 'a.b=c,d', + 'comma: stringifies with dots + comma (pending issue #378)', + { skip: true } + ); + 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' @@ -215,17 +225,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 (pending issue #378)', + { skip: true } + ); + st.equal( + qs.stringify({ a: [{ b: 1 }, 2, 3] }, { encodeValuesOnly: true }), 'a[0][b]=1&a[1]=2&a[2]=3', 'default => indices' ); From b9a039de6dd17c60702e8bd28330f86e8b3ce553 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Mon, 19 Apr 2021 08:31:51 -0700 Subject: [PATCH 210/278] [Tests] clean up stringify tests slightly --- test/stringify.js | 40 ++++++++++++++++++++++++++++------------ 1 file changed, 28 insertions(+), 12 deletions(-) diff --git a/test/stringify.js b/test/stringify.js index 7a07931b..47e7addb 100644 --- a/test/stringify.js +++ b/test/stringify.js @@ -97,10 +97,11 @@ 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%2Cd'); + st.equal(qs.stringify({ a: { b: ['c', 'd'] } }, { encodeValuesOnly: true, arrayFormat: 'comma' }), 'a[b]=c,d', '(pending issue #378)', { skip: true }); + st.equal(qs.stringify({ a: { b: ['c', 'd'] } }, { encodeValuesOnly: true }), 'a[b][0]=c&a[b][1]=d'); st.end(); }); @@ -108,7 +109,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 +117,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,15 +125,24 @@ 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', + 'a.b=c%2Cd', 'comma: stringifies with dots + comma' ); st.equal( qs.stringify( { a: { b: ['c', 'd'] } }, - { allowDots: true, encode: false } + { allowDots: true, encodeValuesOnly: true, arrayFormat: 'comma' } + ), + 'a.b=c,d', + 'comma: stringifies with dots + comma (pending issue #378)', + { skip: true } + ); + 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' @@ -180,17 +190,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 (pending issue #378)', + { skip: true } + ); + st.equal( + qs.stringify({ a: [{ b: 1 }, 2, 3] }, { encodeValuesOnly: true }), 'a[0][b]=1&a[1]=2&a[2]=3', 'default => indices' ); From bd9e3754d2871592baf42ca9fa988c2148a469a5 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Mon, 19 Apr 2021 08:31:51 -0700 Subject: [PATCH 211/278] [Tests] clean up stringify tests slightly --- test/stringify.js | 42 +++++++++++++++++++++++++++++------------- 1 file changed, 29 insertions(+), 13 deletions(-) diff --git a/test/stringify.js b/test/stringify.js index 931ac0dd..fcbec285 100644 --- a/test/stringify.js +++ b/test/stringify.js @@ -132,10 +132,11 @@ 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%2Cd'); + st.equal(qs.stringify({ a: { b: ['c', 'd'] } }, { encodeValuesOnly: true, arrayFormat: 'comma' }), 'a[b]=c,d', '(pending issue #378)', { skip: true }); + st.equal(qs.stringify({ a: { b: ['c', 'd'] } }, { encodeValuesOnly: true }), 'a[b][0]=c&a[b][1]=d'); st.end(); }); @@ -143,7 +144,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' @@ -151,7 +152,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' @@ -159,15 +160,24 @@ 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', + 'a.b=c%2Cd', 'comma: stringifies with dots + comma' ); st.equal( qs.stringify( { a: { b: ['c', 'd'] } }, - { allowDots: true, encode: false } + { allowDots: true, encodeValuesOnly: true, arrayFormat: 'comma' } + ), + 'a.b=c,d', + 'comma: stringifies with dots + comma (pending issue #378)', + { skip: true } + ); + 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' @@ -215,17 +225,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 (pending issue #378)', + { skip: true } + ); + st.equal( + qs.stringify({ a: [{ b: 1 }, 2, 3] }, { encodeValuesOnly: true }), 'a[0][b]=1&a[1]=2&a[2]=3', 'default => indices' ); @@ -829,7 +845,7 @@ test('stringify()', function (t) { 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(obj, { encode: false, arrayFormat: 'comma' }), '???', 'array, comma (pending issue #378)', { skip: true }); + st.equal(qs.stringify(withArray, { encode: false, arrayFormat: 'comma' }), '???', 'array, comma (pending issue #378)', { skip: true }); st.end(); }); From c0e13e9fc80aab01ef777cc06d7411c0df1676a7 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Wed, 1 Sep 2021 14:11:20 -0700 Subject: [PATCH 212/278] [readme] remove travis badge; add github actions/codecov badges; update URLs --- README.md | 37 +++++++++++++++++++------------------ 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index cff8ac1d..f64de3de 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,13 @@ # qs [![Version Badge][2]][1] -[![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. @@ -598,18 +599,18 @@ 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) -[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 +[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 From 4a17709e71ae510a7195ff57b969a2bf9cde139f Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Mon, 19 Apr 2021 08:35:28 -0700 Subject: [PATCH 213/278] [Fix] `stringify`: avoid encoding arrayformat comma when `encodeValuesOnly = true` (#424) Fixes #410 --- lib/stringify.js | 13 +++++++++++-- test/stringify.js | 25 ++++++++++--------------- 2 files changed, 21 insertions(+), 17 deletions(-) diff --git a/lib/stringify.js b/lib/stringify.js index f46bb0e1..f70820f9 100644 --- a/lib/stringify.js +++ b/lib/stringify.js @@ -18,6 +18,7 @@ var arrayPrefixGenerators = { }; 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]); @@ -95,6 +96,14 @@ var stringify = function stringify( if (isNonNullishPrimitive(obj) || utils.isBuffer(obj)) { if (encoder) { 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))]; @@ -117,8 +126,8 @@ var stringify = function stringify( 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' && key.value !== undefined ? key.value : obj[key]; if (skipNulls && value === null) { diff --git a/test/stringify.js b/test/stringify.js index 8c113bab..f761fb35 100644 --- a/test/stringify.js +++ b/test/stringify.js @@ -134,8 +134,7 @@ test('stringify()', function (t) { 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%2Cd'); - st.equal(qs.stringify({ a: { b: ['c', 'd'] } }, { encodeValuesOnly: true, arrayFormat: 'comma' }), 'a[b]=c,d', '(pending issue #378)', { skip: true }); + 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(); }); @@ -157,22 +156,13 @@ test('stringify()', function (t) { '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%2Cd', - 'comma: stringifies with dots + comma' - ); st.equal( qs.stringify( { a: { b: ['c', 'd'] } }, { allowDots: true, encodeValuesOnly: true, arrayFormat: 'comma' } ), 'a.b=c,d', - 'comma: stringifies with dots + comma (pending issue #378)', - { skip: true } + 'comma: stringifies with dots + comma' ); st.equal( qs.stringify( @@ -237,8 +227,8 @@ test('stringify()', function (t) { st.equal( qs.stringify({ a: [{ b: 1 }, 2, 3] }, { encodeValuesOnly: true, arrayFormat: 'comma' }), '???', - 'brackets => brackets (pending issue #378)', - { skip: true } + 'brackets => brackets', + { skip: 'TODO: figure out what this should do' } ); st.equal( qs.stringify({ a: [{ b: 1 }, 2, 3] }, { encodeValuesOnly: true }), @@ -800,7 +790,12 @@ test('stringify()', function (t) { 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 (pending issue #378)', { skip: true }); + st.equal( + qs.stringify(withArray, { encode: false, arrayFormat: 'comma' }), + '???', + 'array, comma', + { skip: 'TODO: figure out what this should do' } + ); st.end(); }); From 48673cae0226de23f6f33cc0e17af893b42f5e37 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Wed, 1 Sep 2021 14:11:20 -0700 Subject: [PATCH 214/278] [readme] remove travis badge; add github actions/codecov badges; update URLs --- README.md | 37 +++++++++++++++++++------------------ 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index cff8ac1d..f64de3de 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,13 @@ # qs [![Version Badge][2]][1] -[![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. @@ -598,18 +599,18 @@ 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) -[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 +[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 From 57918dae411c17b232377759baaa52a642762950 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Mon, 19 Apr 2021 08:35:28 -0700 Subject: [PATCH 215/278] [Fix] `stringify`: avoid encoding arrayformat comma when `encodeValuesOnly = true` (#424) Fixes #410 --- lib/stringify.js | 15 ++++++++++++--- test/stringify.js | 18 ++++-------------- 2 files changed, 16 insertions(+), 17 deletions(-) diff --git a/lib/stringify.js b/lib/stringify.js index f65dc84a..005b92a8 100644 --- a/lib/stringify.js +++ b/lib/stringify.js @@ -18,6 +18,7 @@ var arrayPrefixGenerators = { }; 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]); @@ -89,6 +90,14 @@ var stringify = function stringify( if (isNonNullishPrimitive(obj) || utils.isBuffer(obj)) { if (encoder) { var keyValue = encodeValuesOnly ? prefix : encoder(prefix, defaults.encoder, charset); + 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)); + } + return [formatter(keyValue) + '=' + valuesJoined]; + } return [formatter(keyValue) + '=' + formatter(encoder(obj, defaults.encoder, charset))]; } return [formatter(prefix) + '=' + formatter(String(obj))]; @@ -108,9 +117,9 @@ var stringify = function stringify( objKeys = sort ? keys.sort(sort) : keys; } - for (var i = 0; i < objKeys.length; ++i) { - var key = objKeys[i]; - var value = obj[key]; + for (var j = 0; j < objKeys.length; ++j) { + var key = objKeys[j]; + var value = typeof key === 'object' && key.value !== undefined ? key.value : obj[key]; if (skipNulls && value === null) { continue; diff --git a/test/stringify.js b/test/stringify.js index cc0ba7dc..e0a05de0 100644 --- a/test/stringify.js +++ b/test/stringify.js @@ -134,8 +134,7 @@ test('stringify()', function (t) { 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%2Cd'); - st.equal(qs.stringify({ a: { b: ['c', 'd'] } }, { encodeValuesOnly: true, arrayFormat: 'comma' }), 'a[b]=c,d', '(pending issue #378)', { skip: true }); + 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(); }); @@ -157,22 +156,13 @@ test('stringify()', function (t) { '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%2Cd', - 'comma: stringifies with dots + comma' - ); st.equal( qs.stringify( { a: { b: ['c', 'd'] } }, { allowDots: true, encodeValuesOnly: true, arrayFormat: 'comma' } ), 'a.b=c,d', - 'comma: stringifies with dots + comma (pending issue #378)', - { skip: true } + 'comma: stringifies with dots + comma' ); st.equal( qs.stringify( @@ -237,8 +227,8 @@ test('stringify()', function (t) { st.equal( qs.stringify({ a: [{ b: 1 }, 2, 3] }, { encodeValuesOnly: true, arrayFormat: 'comma' }), '???', - 'brackets => brackets (pending issue #378)', - { skip: true } + 'brackets => brackets', + { skip: 'TODO: figure out what this should do' } ); st.equal( qs.stringify({ a: [{ b: 1 }, 2, 3] }, { encodeValuesOnly: true }), From 9dab77e955b40d45191932ed1bd24a3dd104f179 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Wed, 1 Sep 2021 14:11:20 -0700 Subject: [PATCH 216/278] [readme] remove travis badge; add github actions/codecov badges; update URLs --- README.md | 37 +++++++++++++++++++------------------ 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index cff8ac1d..f64de3de 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,13 @@ # qs [![Version Badge][2]][1] -[![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. @@ -598,18 +599,18 @@ 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) -[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 +[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 From 04eac8db77b8b9b11a48c7cd32e21d3587add624 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Mon, 19 Apr 2021 08:35:28 -0700 Subject: [PATCH 217/278] [Fix] `stringify`: avoid encoding arrayformat comma when `encodeValuesOnly = true` (#424) Fixes #410 --- lib/stringify.js | 18 ++++++++++++++---- test/stringify.js | 18 ++++-------------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/lib/stringify.js b/lib/stringify.js index 415a2411..3c76e5fb 100644 --- a/lib/stringify.js +++ b/lib/stringify.js @@ -18,6 +18,7 @@ var arrayPrefixGenerators = { }; 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]); @@ -81,6 +82,14 @@ var stringify = function stringify( if (typeof obj === 'string' || typeof obj === 'number' || typeof obj === 'boolean' || utils.isBuffer(obj)) { if (encoder) { var keyValue = encodeValuesOnly ? prefix : encoder(prefix, defaults.encoder, charset); + 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)); + } + return [formatter(keyValue) + '=' + valuesJoined]; + } return [formatter(keyValue) + '=' + formatter(encoder(obj, defaults.encoder, charset))]; } return [formatter(prefix) + '=' + formatter(String(obj))]; @@ -100,8 +109,9 @@ var stringify = function stringify( 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' && key.value !== undefined ? key.value : obj[key]; if (skipNulls && obj[key] === null) { continue; @@ -109,7 +119,7 @@ var stringify = function stringify( if (isArray(obj)) { pushToArray(values, stringify( - obj[key], + value, typeof generateArrayPrefix === 'function' ? generateArrayPrefix(prefix, key) : prefix, generateArrayPrefix, strictNullHandling, @@ -125,7 +135,7 @@ var stringify = function stringify( )); } else { pushToArray(values, stringify( - obj[key], + value, prefix + (allowDots ? '.' + key : '[' + key + ']'), generateArrayPrefix, strictNullHandling, diff --git a/test/stringify.js b/test/stringify.js index 47e7addb..b69bc8f1 100644 --- a/test/stringify.js +++ b/test/stringify.js @@ -99,8 +99,7 @@ test('stringify()', function (t) { 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%2Cd'); - st.equal(qs.stringify({ a: { b: ['c', 'd'] } }, { encodeValuesOnly: true, arrayFormat: 'comma' }), 'a[b]=c,d', '(pending issue #378)', { skip: true }); + 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(); }); @@ -122,22 +121,13 @@ test('stringify()', function (t) { '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%2Cd', - 'comma: stringifies with dots + comma' - ); st.equal( qs.stringify( { a: { b: ['c', 'd'] } }, { allowDots: true, encodeValuesOnly: true, arrayFormat: 'comma' } ), 'a.b=c,d', - 'comma: stringifies with dots + comma (pending issue #378)', - { skip: true } + 'comma: stringifies with dots + comma' ); st.equal( qs.stringify( @@ -202,8 +192,8 @@ test('stringify()', function (t) { st.equal( qs.stringify({ a: [{ b: 1 }, 2, 3] }, { encodeValuesOnly: true, arrayFormat: 'comma' }), '???', - 'brackets => brackets (pending issue #378)', - { skip: true } + 'brackets => brackets', + { skip: 'TODO: figure out what this should do' } ); st.equal( qs.stringify({ a: [{ b: 1 }, 2, 3] }, { encodeValuesOnly: true }), From e9877e3b6e23907086403dfb7e39dab0fefb86d7 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Wed, 1 Sep 2021 14:09:38 -0700 Subject: [PATCH 218/278] [actions] update workflows --- .github/workflows/node-4+.yml | 6 ++++-- .github/workflows/node-iojs.yml | 6 ++++-- .github/workflows/node-pretest.yml | 8 ++++++-- .github/workflows/node-zero.yml | 6 ++++-- 4 files changed, 18 insertions(+), 8 deletions(-) diff --git a/.github/workflows/node-4+.yml b/.github/workflows/node-4+.yml index 0483dda9..007f7736 100644 --- a/.github/workflows/node-4+.yml +++ b/.github/workflows/node-4+.yml @@ -20,6 +20,7 @@ jobs: runs-on: ubuntu-latest strategy: + fail-fast: false matrix: ${{ fromJson(needs.matrix.outputs.latest) }} steps: @@ -29,7 +30,7 @@ jobs: with: node-version: ${{ matrix.node-version }} - run: npm run tests-only - - run: bash <(curl -s https://codecov.io/bash) -f coverage/*.json; + - uses: codecov/codecov-action@v1 minors: needs: [matrix, latest] @@ -39,6 +40,7 @@ jobs: runs-on: ubuntu-latest strategy: + fail-fast: false matrix: ${{ fromJson(needs.matrix.outputs.minors) }} steps: @@ -48,7 +50,7 @@ jobs: with: node-version: ${{ matrix.node-version }} - run: npm run tests-only - - run: bash <(curl -s https://codecov.io/bash) -f coverage/*.json; + - uses: codecov/codecov-action@v1 node: name: 'node 4+' diff --git a/.github/workflows/node-iojs.yml b/.github/workflows/node-iojs.yml index ee47695f..891b73d0 100644 --- a/.github/workflows/node-iojs.yml +++ b/.github/workflows/node-iojs.yml @@ -20,6 +20,7 @@ jobs: runs-on: ubuntu-latest strategy: + fail-fast: false matrix: ${{ fromJson(needs.matrix.outputs.latest) }} steps: @@ -30,7 +31,7 @@ jobs: node-version: ${{ matrix.node-version }} skip-ls-check: true - run: npm run tests-only - - run: bash <(curl -s https://codecov.io/bash) -f coverage/*.json; + - uses: codecov/codecov-action@v1 minors: needs: [matrix, latest] @@ -40,6 +41,7 @@ jobs: runs-on: ubuntu-latest strategy: + fail-fast: false matrix: ${{ fromJson(needs.matrix.outputs.minors) }} steps: @@ -50,7 +52,7 @@ jobs: node-version: ${{ matrix.node-version }} skip-ls-check: true - run: npm run tests-only - - run: bash <(curl -s https://codecov.io/bash) -f coverage/*.json; + - uses: codecov/codecov-action@v1 node: name: 'io.js' diff --git a/.github/workflows/node-pretest.yml b/.github/workflows/node-pretest.yml index 16461736..969d1386 100644 --- a/.github/workflows/node-pretest.yml +++ b/.github/workflows/node-pretest.yml @@ -9,7 +9,9 @@ jobs: steps: - uses: actions/checkout@v2 - uses: ljharb/actions/node/install@main - name: 'nvm install ${{ matrix.node-version }} && npm install' + name: 'nvm install lts/* && npm install' + with: + node-version: 'lts/*' - run: npm run pretest posttest: @@ -18,5 +20,7 @@ jobs: steps: - uses: actions/checkout@v2 - uses: ljharb/actions/node/install@main - name: 'nvm install ${{ matrix.node-version }} && npm install' + name: 'nvm install lts/* && npm install' + with: + node-version: 'lts/*' - run: npm run posttest diff --git a/.github/workflows/node-zero.yml b/.github/workflows/node-zero.yml index 6f18484c..4426920a 100644 --- a/.github/workflows/node-zero.yml +++ b/.github/workflows/node-zero.yml @@ -20,6 +20,7 @@ jobs: runs-on: ubuntu-latest strategy: + fail-fast: false matrix: ${{ fromJson(needs.matrix.outputs.stable) }} steps: @@ -31,7 +32,7 @@ jobs: cache-node-modules-key: node_modules-${{ github.workflow }}-${{ github.action }}-${{ github.run_id }} skip-ls-check: true - run: npm run tests-only - - run: bash <(curl -s https://codecov.io/bash) -f coverage/*.json; + - uses: codecov/codecov-action@v1 unstable: needs: [matrix, stable] @@ -41,6 +42,7 @@ jobs: runs-on: ubuntu-latest strategy: + fail-fast: false matrix: ${{ fromJson(needs.matrix.outputs.unstable) }} steps: @@ -55,7 +57,7 @@ jobs: if: ${{ matrix.node-version }} != 0.6 - run: ./node_modules/.bin/tape 'test/**/*.js' if: ${{ matrix.node-version }} = 0.6 - - run: bash <(curl -s https://codecov.io/bash) -f coverage/*.json; + - uses: codecov/codecov-action@v1 if: ${{ matrix.node-version }} != 0.6 node: From e4a81e0825a763904d3d2e8e9123ba00e7238400 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Wed, 1 Sep 2021 14:11:20 -0700 Subject: [PATCH 219/278] [readme] remove travis badge; add github actions/codecov badges; update URLs --- README.md | 37 +++++++++++++++++++------------------ 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index f665b190..01263804 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,13 @@ # qs [![Version Badge][2]][1] -[![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. @@ -605,18 +606,18 @@ 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) -[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 +[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 From 6024134d365168ca110a7fa16d32f31310b42e2b Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Wed, 1 Sep 2021 14:11:20 -0700 Subject: [PATCH 220/278] [readme] remove travis badge; add github actions/codecov badges; update URLs --- README.md | 47 +++++++++++++++++++++++++++++------------------ 1 file changed, 29 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 89a08d25..c2d1f8fc 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,13 @@ # qs [![Version Badge][2]][1] -[![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. @@ -568,18 +569,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 From 1072d57d38a690e1ad7616dced44390bffedcbb2 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Wed, 1 Sep 2021 14:11:20 -0700 Subject: [PATCH 221/278] [readme] remove travis badge; add github actions/codecov badges; update URLs --- README.md | 47 +++++++++++++++++++++++++++++------------------ 1 file changed, 29 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index b882be70..20fb9cad 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,13 @@ # qs [![Version Badge][2]][1] -[![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. @@ -482,18 +483,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 From 45e987c6038db47199a560294c20a67da9ab49e3 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Wed, 1 Sep 2021 14:11:20 -0700 Subject: [PATCH 222/278] [readme] remove travis badge; add github actions/codecov badges; update URLs --- README.md | 37 ++++++++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 46d691ae..0ad1947d 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,13 @@ -# qs +# qs [![Version Badge][2]][1] + +[![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][npm-badge-png]][package-url] A querystring parsing and stringifying library with some added security. @@ -462,3 +471,29 @@ 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/endpoint?url=https://github-actions-badge-u3jn4tfpocch.runkit.sh/ljharb/qs +[actions-url]: https://github.com/ljharb/qs/actions From 651b884c89c77eb14562a2681415b4599e7504b0 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Wed, 1 Sep 2021 14:12:18 -0700 Subject: [PATCH 223/278] [Dev Deps] update `eslint`, `@ljharb/eslint-config`, `aud`, `object-inspect`, `tape` --- package.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 2f1326e0..98eb81be 100644 --- a/package.json +++ b/package.json @@ -33,11 +33,11 @@ "side-channel": "^1.0.4" }, "devDependencies": { - "@ljharb/eslint-config": "^17.5.1", - "aud": "^1.1.4", + "@ljharb/eslint-config": "^18.0.0", + "aud": "^1.1.5", "browserify": "^16.5.2", "eclint": "^2.8.1", - "eslint": "^7.22.0", + "eslint": "^7.32.0", "evalmd": "^0.0.19", "for-each": "^0.3.3", "has-symbols": "^1.0.2", @@ -45,11 +45,11 @@ "in-publish": "^2.0.1", "mkdirp": "^0.5.5", "nyc": "^10.3.2", - "object-inspect": "^1.9.0", + "object-inspect": "^1.11.0", "qs-iconv": "^1.0.4", "safe-publish-latest": "^1.1.4", "safer-buffer": "^2.1.2", - "tape": "^5.2.2" + "tape": "^5.3.1" }, "scripts": { "prepublish": "safe-publish-latest && (not-in-publish || npm run dist)", From e2fd364d47f3e32b622d59005f53b090423e9967 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Wed, 13 Oct 2021 10:31:49 -0700 Subject: [PATCH 224/278] [actions] update codecov uploader --- .github/workflows/node-4+.yml | 4 ++-- .github/workflows/node-iojs.yml | 4 ++-- .github/workflows/node-zero.yml | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/node-4+.yml b/.github/workflows/node-4+.yml index 007f7736..dc07b85b 100644 --- a/.github/workflows/node-4+.yml +++ b/.github/workflows/node-4+.yml @@ -30,7 +30,7 @@ jobs: with: node-version: ${{ matrix.node-version }} - run: npm run tests-only - - uses: codecov/codecov-action@v1 + - uses: codecov/codecov-action@v2 minors: needs: [matrix, latest] @@ -50,7 +50,7 @@ jobs: with: node-version: ${{ matrix.node-version }} - run: npm run tests-only - - uses: codecov/codecov-action@v1 + - uses: codecov/codecov-action@v2 node: name: 'node 4+' diff --git a/.github/workflows/node-iojs.yml b/.github/workflows/node-iojs.yml index 891b73d0..ac64c55b 100644 --- a/.github/workflows/node-iojs.yml +++ b/.github/workflows/node-iojs.yml @@ -31,7 +31,7 @@ jobs: node-version: ${{ matrix.node-version }} skip-ls-check: true - run: npm run tests-only - - uses: codecov/codecov-action@v1 + - uses: codecov/codecov-action@v2 minors: needs: [matrix, latest] @@ -52,7 +52,7 @@ jobs: node-version: ${{ matrix.node-version }} skip-ls-check: true - run: npm run tests-only - - uses: codecov/codecov-action@v1 + - uses: codecov/codecov-action@v2 node: name: 'io.js' diff --git a/.github/workflows/node-zero.yml b/.github/workflows/node-zero.yml index 4426920a..02f0e68e 100644 --- a/.github/workflows/node-zero.yml +++ b/.github/workflows/node-zero.yml @@ -32,7 +32,7 @@ jobs: cache-node-modules-key: node_modules-${{ github.workflow }}-${{ github.action }}-${{ github.run_id }} skip-ls-check: true - run: npm run tests-only - - uses: codecov/codecov-action@v1 + - uses: codecov/codecov-action@v2 unstable: needs: [matrix, stable] @@ -57,7 +57,7 @@ jobs: if: ${{ matrix.node-version }} != 0.6 - run: ./node_modules/.bin/tape 'test/**/*.js' if: ${{ matrix.node-version }} = 0.6 - - uses: codecov/codecov-action@v1 + - uses: codecov/codecov-action@v2 if: ${{ matrix.node-version }} != 0.6 node: From 5dbeeb4ec03ae745073efbb6e0101e7f2f584875 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Mon, 19 Apr 2021 08:35:28 -0700 Subject: [PATCH 225/278] [Fix] `stringify`: avoid encoding arrayformat comma when `encodeValuesOnly = true` Fixes #410 --- lib/stringify.js | 13 +++++++++++-- test/stringify.js | 25 ++++++++++--------------- 2 files changed, 21 insertions(+), 17 deletions(-) diff --git a/lib/stringify.js b/lib/stringify.js index b8cee4bc..fa329304 100644 --- a/lib/stringify.js +++ b/lib/stringify.js @@ -19,6 +19,7 @@ var arrayPrefixGenerators = { }; 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]); @@ -102,6 +103,14 @@ var stringify = function stringify( if (isNonNullishPrimitive(obj) || utils.isBuffer(obj)) { if (encoder) { 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))]; @@ -124,8 +133,8 @@ var stringify = function stringify( 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' && key.value !== undefined ? key.value : obj[key]; if (skipNulls && value === null) { diff --git a/test/stringify.js b/test/stringify.js index fcbec285..9750594e 100644 --- a/test/stringify.js +++ b/test/stringify.js @@ -134,8 +134,7 @@ test('stringify()', function (t) { 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%2Cd'); - st.equal(qs.stringify({ a: { b: ['c', 'd'] } }, { encodeValuesOnly: true, arrayFormat: 'comma' }), 'a[b]=c,d', '(pending issue #378)', { skip: true }); + 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(); }); @@ -157,22 +156,13 @@ test('stringify()', function (t) { '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%2Cd', - 'comma: stringifies with dots + comma' - ); st.equal( qs.stringify( { a: { b: ['c', 'd'] } }, { allowDots: true, encodeValuesOnly: true, arrayFormat: 'comma' } ), 'a.b=c,d', - 'comma: stringifies with dots + comma (pending issue #378)', - { skip: true } + 'comma: stringifies with dots + comma' ); st.equal( qs.stringify( @@ -237,8 +227,8 @@ test('stringify()', function (t) { st.equal( qs.stringify({ a: [{ b: 1 }, 2, 3] }, { encodeValuesOnly: true, arrayFormat: 'comma' }), '???', - 'brackets => brackets (pending issue #378)', - { skip: true } + 'brackets => brackets', + { skip: 'TODO: figure out what this should do' } ); st.equal( qs.stringify({ a: [{ b: 1 }, 2, 3] }, { encodeValuesOnly: true }), @@ -845,7 +835,12 @@ test('stringify()', function (t) { 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 (pending issue #378)', { skip: true }); + st.equal( + qs.stringify(withArray, { encode: false, arrayFormat: 'comma' }), + '???', + 'array, comma', + { skip: 'TODO: figure out what this should do' } + ); st.end(); }); From 24c19cc7164b4a18b1c0190fa8466cd93f18ae92 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Tue, 9 Nov 2021 11:21:33 -0800 Subject: [PATCH 226/278] [Dev Deps] update `eslint`, `@ljharb/eslint-config`, `safe-publish-latest` --- package.json | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 98eb81be..eb9f8dff 100644 --- a/package.json +++ b/package.json @@ -33,11 +33,11 @@ "side-channel": "^1.0.4" }, "devDependencies": { - "@ljharb/eslint-config": "^18.0.0", + "@ljharb/eslint-config": "^19.0.1", "aud": "^1.1.5", "browserify": "^16.5.2", "eclint": "^2.8.1", - "eslint": "^7.32.0", + "eslint": "^8.2.0", "evalmd": "^0.0.19", "for-each": "^0.3.3", "has-symbols": "^1.0.2", @@ -47,19 +47,20 @@ "nyc": "^10.3.2", "object-inspect": "^1.11.0", "qs-iconv": "^1.0.4", - "safe-publish-latest": "^1.1.4", + "safe-publish-latest": "^2.0.0", "safer-buffer": "^2.1.2", "tape": "^5.3.1" }, "scripts": { - "prepublish": "safe-publish-latest && (not-in-publish || npm run dist)", + "prepublishOnly": "safe-publish-latest && npm run dist", + "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": "aud --production", "readme": "evalmd README.md", "postlint": "eclint check * lib/* test/* !dist/*", - "lint": "eslint lib/*.js test/*.js", + "lint": "eslint .", "dist": "mkdirp dist && browserify --standalone Qs lib/index.js > dist/qs.js" }, "license": "BSD-3-Clause", From 9aee773432b80bd50441f7ac1b64a86a7e00ccca Mon Sep 17 00:00:00 2001 From: liaokunhua Date: Sat, 4 Dec 2021 18:02:23 +0800 Subject: [PATCH 227/278] [Fix] `stringify`: actually fix cyclic references Fixes #403. Co-authored-by: liaokunhua Co-authored-by: Jordan Harband --- lib/stringify.js | 24 +++++++++++++++++++++--- test/stringify.js | 10 ++++++++-- 2 files changed, 29 insertions(+), 5 deletions(-) diff --git a/lib/stringify.js b/lib/stringify.js index fa329304..c5990499 100644 --- a/lib/stringify.js +++ b/lib/stringify.js @@ -56,6 +56,8 @@ var isNonNullishPrimitive = function isNonNullishPrimitive(v) { || typeof v === 'bigint'; }; +var sentinel = {}; + var stringify = function stringify( object, prefix, @@ -75,8 +77,23 @@ var stringify = function stringify( ) { var obj = object; - if (sideChannel.has(object)) { - throw new RangeError('Cyclic object value'); + var tmpSc = sideChannel; + var step = 0; + var findFlag = false; + while ((tmpSc = tmpSc.get(sentinel)) !== 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') { @@ -145,8 +162,9 @@ var stringify = function stringify( ? typeof generateArrayPrefix === 'function' ? generateArrayPrefix(prefix, key) : prefix : prefix + (allowDots ? '.' + key : '[' + key + ']'); - sideChannel.set(object, true); + sideChannel.set(object, step); var valueSideChannel = getSideChannel(); + valueSideChannel.set(sentinel, sideChannel); pushToArray(values, stringify( value, keyPrefix, diff --git a/test/stringify.js b/test/stringify.js index 9750594e..a3800aac 100644 --- a/test/stringify.js +++ b/test/stringify.js @@ -454,7 +454,7 @@ test('stringify()', function (t) { st['throws']( function () { qs.stringify({ 'foo[bar]': 'baz', 'foo[baz]': a }); }, - RangeError, + /RangeError: Cyclic object value/, 'cyclic values throw' ); @@ -464,10 +464,16 @@ test('stringify()', function (t) { circular.a = circular; st['throws']( function () { qs.stringify(circular); }, - RangeError, + /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(); }); From 28fba8fd928ee14c758c7f55cbce9d8730443dd4 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Sun, 5 Dec 2021 21:08:03 -0800 Subject: [PATCH 228/278] [Dev Deps] update `eslint`, `@ljharb/eslint-config`, `tape` --- .eslintrc | 1 - lib/utils.js | 1 + package.json | 6 +++--- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.eslintrc b/.eslintrc index 2c680d75..779dc032 100644 --- a/.eslintrc +++ b/.eslintrc @@ -16,7 +16,6 @@ "no-continue": 1, "no-magic-numbers": 0, "no-restricted-syntax": [2, "BreakStatement", "DebuggerStatement", "ForInStatement", "LabeledStatement", "WithStatement"], - "operator-linebreak": [2, "before"], }, "overrides": [ diff --git a/lib/utils.js b/lib/utils.js index 4ad6ea27..1e545381 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -177,6 +177,7 @@ var encode = function encode(str, defaultEncoder, charset, kind, format) { 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)] diff --git a/package.json b/package.json index eb9f8dff..7cc0df93 100644 --- a/package.json +++ b/package.json @@ -33,11 +33,11 @@ "side-channel": "^1.0.4" }, "devDependencies": { - "@ljharb/eslint-config": "^19.0.1", + "@ljharb/eslint-config": "^19.1.0", "aud": "^1.1.5", "browserify": "^16.5.2", "eclint": "^2.8.1", - "eslint": "^8.2.0", + "eslint": "^8.4.0", "evalmd": "^0.0.19", "for-each": "^0.3.3", "has-symbols": "^1.0.2", @@ -49,7 +49,7 @@ "qs-iconv": "^1.0.4", "safe-publish-latest": "^2.0.0", "safer-buffer": "^2.1.2", - "tape": "^5.3.1" + "tape": "^5.3.2" }, "scripts": { "prepublishOnly": "safe-publish-latest && npm run dist", From 3cea04d889db3953e408554012f0ff44571eeb99 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Sun, 5 Dec 2021 21:42:24 -0800 Subject: [PATCH 229/278] [Dev Deps] update `@ljharb/eslint-config` --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 7cc0df93..ffca2b78 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ "side-channel": "^1.0.4" }, "devDependencies": { - "@ljharb/eslint-config": "^19.1.0", + "@ljharb/eslint-config": "^20.0.0", "aud": "^1.1.5", "browserify": "^16.5.2", "eclint": "^2.8.1", From 408ff95f1ab94ea73027bc8a7443afb62d41a72d Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Sun, 5 Dec 2021 21:47:06 -0800 Subject: [PATCH 230/278] v6.10.2 --- CHANGELOG.md | 10 ++++ dist/qs.js | 150 ++++++++++++++++++++++++++++++++++++++++++--------- package.json | 2 +- 3 files changed, 135 insertions(+), 27 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 74dda430..82e38a0d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,13 @@ +## **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) diff --git a/dist/qs.js b/dist/qs.js index b9c1bf40..ea946c25 100644 --- a/dist/qs.js +++ b/dist/qs.js @@ -323,6 +323,7 @@ var arrayPrefixGenerators = { }; 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]); @@ -359,6 +360,8 @@ var isNonNullishPrimitive = function isNonNullishPrimitive(v) { || typeof v === 'bigint'; }; +var sentinel = {}; + var stringify = function stringify( object, prefix, @@ -378,8 +381,23 @@ var stringify = function stringify( ) { var obj = object; - if (sideChannel.has(object)) { - throw new RangeError('Cyclic object value'); + var tmpSc = sideChannel; + var step = 0; + var findFlag = false; + while ((tmpSc = tmpSc.get(sentinel)) !== 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') { @@ -406,6 +424,14 @@ var stringify = function stringify( if (isNonNullishPrimitive(obj) || utils.isBuffer(obj)) { if (encoder) { 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))]; @@ -428,8 +454,8 @@ var stringify = function stringify( 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' && key.value !== undefined ? key.value : obj[key]; if (skipNulls && value === null) { @@ -440,7 +466,9 @@ var stringify = function stringify( ? typeof generateArrayPrefix === 'function' ? generateArrayPrefix(prefix, key) : prefix : prefix + (allowDots ? '.' + key : '[' + key + ']'); - sideChannel.set(object, true); + sideChannel.set(object, step); + var valueSideChannel = getSideChannel(); + valueSideChannel.set(sentinel, sideChannel); pushToArray(values, stringify( value, keyPrefix, @@ -456,7 +484,7 @@ var stringify = function stringify( formatter, encodeValuesOnly, charset, - sideChannel + valueSideChannel )); } @@ -772,6 +800,7 @@ var encode = function encode(str, defaultEncoder, charset, kind, format) { 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)] @@ -1385,17 +1414,29 @@ var hasWeakMap = typeof WeakMap === 'function' && WeakMap.prototype; var weakMapHas = hasWeakMap ? WeakMap.prototype.has : null; var hasWeakSet = typeof WeakSet === 'function' && WeakSet.prototype; var weakSetHas = hasWeakSet ? WeakSet.prototype.has : null; +var hasWeakRef = typeof WeakRef === 'function' && WeakRef.prototype; +var weakRefDeref = hasWeakRef ? WeakRef.prototype.deref : null; var booleanValueOf = Boolean.prototype.valueOf; var objectToString = Object.prototype.toString; var functionToString = Function.prototype.toString; var match = String.prototype.match; var bigIntValueOf = typeof BigInt === 'function' ? BigInt.prototype.valueOf : null; var gOPS = Object.getOwnPropertySymbols; -var symToString = typeof Symbol === 'function' ? Symbol.prototype.toString : null; +var symToString = typeof Symbol === 'function' && typeof Symbol.iterator === 'symbol' ? Symbol.prototype.toString : null; +var hasShammedSymbols = typeof Symbol === 'function' && typeof Symbol.iterator === 'object'; var isEnumerable = Object.prototype.propertyIsEnumerable; +var gPO = (typeof Reflect === 'function' ? Reflect.getPrototypeOf : Object.getPrototypeOf) || ( + [].__proto__ === Array.prototype // eslint-disable-line no-proto + ? function (O) { + return O.__proto__; // eslint-disable-line no-proto + } + : null +); + var inspectCustom = require('./util.inspect').custom; var inspectSymbol = inspectCustom && isSymbol(inspectCustom) ? inspectCustom : null; +var toStringTag = typeof Symbol === 'function' && typeof Symbol.toStringTag !== 'undefined' ? Symbol.toStringTag : null; module.exports = function inspect_(obj, options, depth, seen) { var opts = options || {}; @@ -1412,8 +1453,8 @@ module.exports = function inspect_(obj, options, depth, seen) { throw new TypeError('option "maxStringLength", if provided, must be a positive integer, Infinity, or `null`'); } var customInspect = has(opts, 'customInspect') ? opts.customInspect : true; - if (typeof customInspect !== 'boolean') { - throw new TypeError('option "customInspect", if provided, must be `true` or `false`'); + if (typeof customInspect !== 'boolean' && customInspect !== 'symbol') { + throw new TypeError('option "customInspect", if provided, must be `true`, `false`, or `\'symbol\'`'); } if ( @@ -1485,8 +1526,8 @@ module.exports = function inspect_(obj, options, depth, seen) { return '[Function' + (name ? ': ' + name : ' (anonymous)') + ']' + (keys.length > 0 ? ' { ' + keys.join(', ') + ' }' : ''); } if (isSymbol(obj)) { - var symString = symToString.call(obj); - return typeof obj === 'object' ? markBoxed(symString) : symString; + var symString = hasShammedSymbols ? String(obj).replace(/^(Symbol\(.*\))_[^)]*$/, '$1') : symToString.call(obj); + return typeof obj === 'object' && !hasShammedSymbols ? markBoxed(symString) : symString; } if (isElement(obj)) { var s = '<' + String(obj.nodeName).toLowerCase(); @@ -1515,7 +1556,7 @@ module.exports = function inspect_(obj, options, depth, seen) { if (typeof obj === 'object' && customInspect) { if (inspectSymbol && typeof obj[inspectSymbol] === 'function') { return obj[inspectSymbol](); - } else if (typeof obj.inspect === 'function') { + } else if (customInspect !== 'symbol' && typeof obj.inspect === 'function') { return obj.inspect(); } } @@ -1539,6 +1580,9 @@ module.exports = function inspect_(obj, options, depth, seen) { if (isWeakSet(obj)) { return weakCollectionOf('WeakSet'); } + if (isWeakRef(obj)) { + return weakCollectionOf('WeakRef'); + } if (isNumber(obj)) { return markBoxed(inspect(Number(obj))); } @@ -1553,11 +1597,16 @@ module.exports = function inspect_(obj, options, depth, seen) { } if (!isDate(obj) && !isRegExp(obj)) { var ys = arrObjKeys(obj, inspect); - if (ys.length === 0) { return '{}'; } + var isPlainObject = gPO ? gPO(obj) === Object.prototype : obj instanceof Object || obj.constructor === Object; + var protoTag = obj instanceof Object ? '' : 'null prototype'; + var stringTag = !isPlainObject && toStringTag && Object(obj) === obj && toStringTag in obj ? toStr(obj).slice(8, -1) : protoTag ? 'Object' : ''; + var constructorTag = isPlainObject || typeof obj.constructor !== 'function' ? '' : obj.constructor.name ? obj.constructor.name + ' ' : ''; + var tag = constructorTag + (stringTag || protoTag ? '[' + [].concat(stringTag || [], protoTag || []).join(': ') + '] ' : ''); + if (ys.length === 0) { return tag + '{}'; } if (indent) { - return '{' + indentedJoin(ys, indent) + '}'; + return tag + '{' + indentedJoin(ys, indent) + '}'; } - return '{ ' + ys.join(', ') + ' }'; + return tag + '{ ' + ys.join(', ') + ' }'; } return String(obj); }; @@ -1571,15 +1620,42 @@ function quote(s) { return String(s).replace(/"/g, '"'); } -function isArray(obj) { return toStr(obj) === '[object Array]'; } -function isDate(obj) { return toStr(obj) === '[object Date]'; } -function isRegExp(obj) { return toStr(obj) === '[object RegExp]'; } -function isError(obj) { return toStr(obj) === '[object Error]'; } -function isSymbol(obj) { return toStr(obj) === '[object Symbol]'; } -function isString(obj) { return toStr(obj) === '[object String]'; } -function isNumber(obj) { return toStr(obj) === '[object Number]'; } -function isBigInt(obj) { return toStr(obj) === '[object BigInt]'; } -function isBoolean(obj) { return toStr(obj) === '[object Boolean]'; } +function isArray(obj) { return toStr(obj) === '[object Array]' && (!toStringTag || !(typeof obj === 'object' && toStringTag in obj)); } +function isDate(obj) { return toStr(obj) === '[object Date]' && (!toStringTag || !(typeof obj === 'object' && toStringTag in obj)); } +function isRegExp(obj) { return toStr(obj) === '[object RegExp]' && (!toStringTag || !(typeof obj === 'object' && toStringTag in obj)); } +function isError(obj) { return toStr(obj) === '[object Error]' && (!toStringTag || !(typeof obj === 'object' && toStringTag in obj)); } +function isString(obj) { return toStr(obj) === '[object String]' && (!toStringTag || !(typeof obj === 'object' && toStringTag in obj)); } +function isNumber(obj) { return toStr(obj) === '[object Number]' && (!toStringTag || !(typeof obj === 'object' && toStringTag in obj)); } +function isBoolean(obj) { return toStr(obj) === '[object Boolean]' && (!toStringTag || !(typeof obj === 'object' && toStringTag in obj)); } + +// Symbol and BigInt do have Symbol.toStringTag by spec, so that can't be used to eliminate false positives +function isSymbol(obj) { + if (hasShammedSymbols) { + return obj && typeof obj === 'object' && obj instanceof Symbol; + } + if (typeof obj === 'symbol') { + return true; + } + if (!obj || typeof obj !== 'object' || !symToString) { + return false; + } + try { + symToString.call(obj); + return true; + } catch (e) {} + return false; +} + +function isBigInt(obj) { + if (!obj || typeof obj !== 'object' || !bigIntValueOf) { + return false; + } + try { + bigIntValueOf.call(obj); + return true; + } catch (e) {} + return false; +} var hasOwn = Object.prototype.hasOwnProperty || function (key) { return key in this; }; function has(obj, key) { @@ -1637,6 +1713,17 @@ function isWeakMap(x) { return false; } +function isWeakRef(x) { + if (!weakRefDeref || !x || typeof x !== 'object') { + return false; + } + try { + weakRefDeref.call(x); + return true; + } catch (e) {} + return false; +} + function isSet(x) { if (!setSize || !x || typeof x !== 'object') { return false; @@ -1753,17 +1840,28 @@ function arrObjKeys(obj, inspect) { xs[i] = has(obj, i) ? inspect(obj[i], obj) : ''; } } + var syms = typeof gOPS === 'function' ? gOPS(obj) : []; + var symMap; + if (hasShammedSymbols) { + symMap = {}; + for (var k = 0; k < syms.length; k++) { + symMap['$' + syms[k]] = syms[k]; + } + } + for (var key in obj) { // eslint-disable-line no-restricted-syntax if (!has(obj, key)) { continue; } // eslint-disable-line no-restricted-syntax, no-continue if (isArr && String(Number(key)) === key && key < obj.length) { continue; } // eslint-disable-line no-restricted-syntax, no-continue - if ((/[^\w$]/).test(key)) { + if (hasShammedSymbols && symMap['$' + key] instanceof Symbol) { + // this is to prevent shammed Symbols, which are stored as strings, from being included in the string key section + continue; // eslint-disable-line no-restricted-syntax, no-continue + } else if ((/[^\w$]/).test(key)) { xs.push(inspect(key, obj) + ': ' + inspect(obj[key], obj)); } else { xs.push(key + ': ' + inspect(obj[key], obj)); } } if (typeof gOPS === 'function') { - var syms = gOPS(obj); for (var j = 0; j < syms.length; j++) { if (isEnumerable.call(obj, syms[j])) { xs.push('[' + inspect(syms[j]) + ']: ' + inspect(obj[syms[j]], obj)); diff --git a/package.json b/package.json index ffca2b78..480e6ea2 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "qs", "description": "A querystring parser that supports nesting and arrays, with a depth limit", "homepage": "https://github.com/ljharb/qs", - "version": "6.10.1", + "version": "6.10.2", "repository": { "type": "git", "url": "https://github.com/ljharb/qs.git" From 0a1d3e806e6c7d9d640d46df7b1d27e6e44125f8 Mon Sep 17 00:00:00 2001 From: Mikhail Bodrov Date: Mon, 27 Dec 2021 22:09:36 +0200 Subject: [PATCH 231/278] [Robustness] `stringify`: avoid relying on a global `undefined` --- lib/stringify.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/stringify.js b/lib/stringify.js index c5990499..47ea4b15 100644 --- a/lib/stringify.js +++ b/lib/stringify.js @@ -80,7 +80,7 @@ var stringify = function stringify( var tmpSc = sideChannel; var step = 0; var findFlag = false; - while ((tmpSc = tmpSc.get(sentinel)) !== undefined && !findFlag) { + while ((tmpSc = tmpSc.get(sentinel)) !== void undefined && !findFlag) { // Where object last appeared in the ref tree var pos = tmpSc.get(object); step += 1; @@ -142,7 +142,7 @@ var stringify = function stringify( var objKeys; if (generateArrayPrefix === 'comma' && isArray(obj)) { // we need to join elements in - objKeys = [{ value: obj.length > 0 ? obj.join(',') || null : undefined }]; + objKeys = [{ value: obj.length > 0 ? obj.join(',') || null : void undefined }]; } else if (isArray(filter)) { objKeys = filter; } else { @@ -152,7 +152,7 @@ var stringify = function stringify( for (var j = 0; j < objKeys.length; ++j) { var key = objKeys[j]; - var value = typeof key === 'object' && key.value !== undefined ? key.value : obj[key]; + var value = typeof key === 'object' && typeof key.value !== 'undefined' ? key.value : obj[key]; if (skipNulls && value === null) { continue; @@ -192,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.'); } From c028385f6543e3148f243e2768e487c63b7e60b7 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Mon, 27 Dec 2021 15:31:32 -0800 Subject: [PATCH 232/278] [Dev Deps] update `eslint`, `@ljharb/eslint-config`, `object-inspect`, `tape` --- package.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 480e6ea2..81463e09 100644 --- a/package.json +++ b/package.json @@ -33,11 +33,11 @@ "side-channel": "^1.0.4" }, "devDependencies": { - "@ljharb/eslint-config": "^20.0.0", + "@ljharb/eslint-config": "^20.1.0", "aud": "^1.1.5", "browserify": "^16.5.2", "eclint": "^2.8.1", - "eslint": "^8.4.0", + "eslint": "^8.5.0", "evalmd": "^0.0.19", "for-each": "^0.3.3", "has-symbols": "^1.0.2", @@ -45,11 +45,11 @@ "in-publish": "^2.0.1", "mkdirp": "^0.5.5", "nyc": "^10.3.2", - "object-inspect": "^1.11.0", + "object-inspect": "^1.12.0", "qs-iconv": "^1.0.4", "safe-publish-latest": "^2.0.0", "safer-buffer": "^2.1.2", - "tape": "^5.3.2" + "tape": "^5.4.0" }, "scripts": { "prepublishOnly": "safe-publish-latest && npm run dist", From ad63d36ce18cd2c315899ac1e8193de22be08cd8 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Sun, 5 Dec 2021 21:48:13 -0800 Subject: [PATCH 233/278] [actions] reuse common workflows See https://github.com/inspect-js/is-arguments/pull/38 --- .github/workflows/node-4+.yml | 60 -------------------- .github/workflows/node-aught.yml | 18 ++++++ .github/workflows/node-iojs.yml | 62 --------------------- .github/workflows/node-pretest.yml | 23 +------- .github/workflows/node-tens.yml | 18 ++++++ .github/workflows/node-zero.yml | 68 ----------------------- .github/workflows/rebase.yml | 8 +-- .github/workflows/require-allow-edits.yml | 2 +- 8 files changed, 43 insertions(+), 216 deletions(-) delete mode 100644 .github/workflows/node-4+.yml create mode 100644 .github/workflows/node-aught.yml delete mode 100644 .github/workflows/node-iojs.yml create mode 100644 .github/workflows/node-tens.yml delete mode 100644 .github/workflows/node-zero.yml diff --git a/.github/workflows/node-4+.yml b/.github/workflows/node-4+.yml deleted file mode 100644 index dc07b85b..00000000 --- a/.github/workflows/node-4+.yml +++ /dev/null @@ -1,60 +0,0 @@ -name: 'Tests: node.js' - -on: [pull_request, push] - -jobs: - matrix: - runs-on: ubuntu-latest - outputs: - latest: ${{ steps.set-matrix.outputs.requireds }} - minors: ${{ steps.set-matrix.outputs.optionals }} - steps: - - uses: ljharb/actions/node/matrix@main - id: set-matrix - with: - preset: '>=4' - - latest: - needs: [matrix] - name: 'latest minors' - runs-on: ubuntu-latest - - strategy: - fail-fast: false - matrix: ${{ fromJson(needs.matrix.outputs.latest) }} - - steps: - - uses: actions/checkout@v2 - - uses: ljharb/actions/node/install@main - name: 'nvm install ${{ matrix.node-version }} && npm install' - with: - node-version: ${{ matrix.node-version }} - - run: npm run tests-only - - uses: codecov/codecov-action@v2 - - minors: - needs: [matrix, latest] - name: 'non-latest minors' - continue-on-error: true - if: ${{ !github.head_ref || !startsWith(github.head_ref, 'renovate') }} - runs-on: ubuntu-latest - - strategy: - fail-fast: false - matrix: ${{ fromJson(needs.matrix.outputs.minors) }} - - steps: - - uses: actions/checkout@v2 - - uses: ljharb/actions/node/install@main - name: 'nvm install ${{ matrix.node-version }} && npm install' - with: - node-version: ${{ matrix.node-version }} - - run: npm run tests-only - - uses: codecov/codecov-action@v2 - - node: - name: 'node 4+' - needs: [latest, minors] - runs-on: ubuntu-latest - steps: - - run: 'echo tests completed' diff --git a/.github/workflows/node-aught.yml b/.github/workflows/node-aught.yml new file mode 100644 index 00000000..f3cddd85 --- /dev/null +++ b/.github/workflows/node-aught.yml @@ -0,0 +1,18 @@ +name: 'Tests: node.js < 10' + +on: [pull_request, push] + +jobs: + tests: + uses: ljharb/actions/.github/workflows/node.yml@main + with: + range: '< 10' + type: minors + command: npm run tests-only + + node: + name: 'node < 10' + needs: [tests] + runs-on: ubuntu-latest + steps: + - run: 'echo tests completed' diff --git a/.github/workflows/node-iojs.yml b/.github/workflows/node-iojs.yml deleted file mode 100644 index ac64c55b..00000000 --- a/.github/workflows/node-iojs.yml +++ /dev/null @@ -1,62 +0,0 @@ -name: 'Tests: node.js (io.js)' - -on: [pull_request, push] - -jobs: - matrix: - runs-on: ubuntu-latest - outputs: - latest: ${{ steps.set-matrix.outputs.requireds }} - minors: ${{ steps.set-matrix.outputs.optionals }} - steps: - - uses: ljharb/actions/node/matrix@main - id: set-matrix - with: - preset: 'iojs' - - latest: - needs: [matrix] - name: 'latest minors' - runs-on: ubuntu-latest - - strategy: - fail-fast: false - matrix: ${{ fromJson(needs.matrix.outputs.latest) }} - - steps: - - uses: actions/checkout@v2 - - uses: ljharb/actions/node/install@main - name: 'nvm install ${{ matrix.node-version }} && npm install' - with: - node-version: ${{ matrix.node-version }} - skip-ls-check: true - - run: npm run tests-only - - uses: codecov/codecov-action@v2 - - minors: - needs: [matrix, latest] - name: 'non-latest minors' - continue-on-error: true - if: ${{ !github.head_ref || !startsWith(github.head_ref, 'renovate') }} - runs-on: ubuntu-latest - - strategy: - fail-fast: false - matrix: ${{ fromJson(needs.matrix.outputs.minors) }} - - steps: - - uses: actions/checkout@v2 - - uses: ljharb/actions/node/install@main - name: 'nvm install ${{ matrix.node-version }} && npm install' - with: - node-version: ${{ matrix.node-version }} - skip-ls-check: true - - run: npm run tests-only - - uses: codecov/codecov-action@v2 - - node: - name: 'io.js' - needs: [latest, minors] - runs-on: ubuntu-latest - steps: - - run: 'echo tests completed' diff --git a/.github/workflows/node-pretest.yml b/.github/workflows/node-pretest.yml index 969d1386..765edf79 100644 --- a/.github/workflows/node-pretest.yml +++ b/.github/workflows/node-pretest.yml @@ -3,24 +3,5 @@ name: 'Tests: pretest/posttest' on: [pull_request, push] jobs: - pretest: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v2 - - uses: ljharb/actions/node/install@main - name: 'nvm install lts/* && npm install' - with: - node-version: 'lts/*' - - run: npm run pretest - - posttest: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v2 - - uses: ljharb/actions/node/install@main - name: 'nvm install lts/* && npm install' - with: - node-version: 'lts/*' - - run: npm run posttest + tests: + uses: ljharb/actions/.github/workflows/pretest.yml@main diff --git a/.github/workflows/node-tens.yml b/.github/workflows/node-tens.yml new file mode 100644 index 00000000..b49ceb1f --- /dev/null +++ b/.github/workflows/node-tens.yml @@ -0,0 +1,18 @@ +name: 'Tests: node.js >= 10' + +on: [pull_request, push] + +jobs: + tests: + uses: ljharb/actions/.github/workflows/node.yml@main + with: + range: '>= 10' + type: minors + command: npm run tests-only + + node: + name: 'node >= 10' + needs: [tests] + runs-on: ubuntu-latest + steps: + - run: 'echo tests completed' diff --git a/.github/workflows/node-zero.yml b/.github/workflows/node-zero.yml deleted file mode 100644 index 02f0e68e..00000000 --- a/.github/workflows/node-zero.yml +++ /dev/null @@ -1,68 +0,0 @@ -name: 'Tests: node.js (0.x)' - -on: [pull_request, push] - -jobs: - matrix: - runs-on: ubuntu-latest - outputs: - stable: ${{ steps.set-matrix.outputs.requireds }} - unstable: ${{ steps.set-matrix.outputs.optionals }} - steps: - - uses: ljharb/actions/node/matrix@main - id: set-matrix - with: - preset: '0.x' - - stable: - needs: [matrix] - name: 'stable minors' - runs-on: ubuntu-latest - - strategy: - fail-fast: false - matrix: ${{ fromJson(needs.matrix.outputs.stable) }} - - steps: - - uses: actions/checkout@v2 - - uses: ljharb/actions/node/install@main - name: 'nvm install ${{ matrix.node-version }} && npm install' - with: - node-version: ${{ matrix.node-version }} - cache-node-modules-key: node_modules-${{ github.workflow }}-${{ github.action }}-${{ github.run_id }} - skip-ls-check: true - - run: npm run tests-only - - uses: codecov/codecov-action@v2 - - unstable: - needs: [matrix, stable] - name: 'unstable minors' - continue-on-error: true - if: ${{ !github.head_ref || !startsWith(github.head_ref, 'renovate') }} - runs-on: ubuntu-latest - - strategy: - fail-fast: false - matrix: ${{ fromJson(needs.matrix.outputs.unstable) }} - - steps: - - uses: actions/checkout@v2 - - uses: ljharb/actions/node/install@main - name: 'nvm install ${{ matrix.node-version }} && npm install' - with: - node-version: ${{ matrix.node-version }} - cache-node-modules-key: node_modules-${{ github.workflow }}-${{ github.action }}-${{ github.run_id }} - skip-ls-check: true - - run: npm run tests-only - if: ${{ matrix.node-version }} != 0.6 - - run: ./node_modules/.bin/tape 'test/**/*.js' - if: ${{ matrix.node-version }} = 0.6 - - uses: codecov/codecov-action@v2 - if: ${{ matrix.node-version }} != 0.6 - - node: - name: 'node 0.x' - needs: [stable, unstable] - runs-on: ubuntu-latest - steps: - - run: 'echo tests completed' diff --git a/.github/workflows/rebase.yml b/.github/workflows/rebase.yml index 027aed07..5b6d04b8 100644 --- a/.github/workflows/rebase.yml +++ b/.github/workflows/rebase.yml @@ -9,7 +9,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - uses: ljharb/rebase@master - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - uses: actions/checkout@v2 + - uses: ljharb/rebase@master + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/require-allow-edits.yml b/.github/workflows/require-allow-edits.yml index 549d7b48..7b842f89 100644 --- a/.github/workflows/require-allow-edits.yml +++ b/.github/workflows/require-allow-edits.yml @@ -9,4 +9,4 @@ jobs: runs-on: ubuntu-latest steps: - - uses: ljharb/require-allow-edits@main + - uses: ljharb/require-allow-edits@main From 02ca358155297dc68fcc4c2ac312c26e10524e47 Mon Sep 17 00:00:00 2001 From: Mikhail Bodrov Date: Mon, 27 Dec 2021 22:09:36 +0200 Subject: [PATCH 234/278] [Robustness] `stringify`: avoid relying on a global `undefined` (#427) --- lib/stringify.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/stringify.js b/lib/stringify.js index f70820f9..bdc19c65 100644 --- a/lib/stringify.js +++ b/lib/stringify.js @@ -118,7 +118,7 @@ var stringify = function stringify( var objKeys; if (generateArrayPrefix === 'comma' && isArray(obj)) { // we need to join elements in - objKeys = [{ value: obj.length > 0 ? obj.join(',') || null : undefined }]; + objKeys = [{ value: obj.length > 0 ? obj.join(',') || null : void undefined }]; } else if (isArray(filter)) { objKeys = filter; } else { @@ -128,7 +128,7 @@ var stringify = function stringify( for (var j = 0; j < objKeys.length; ++j) { var key = objKeys[j]; - var value = typeof key === 'object' && key.value !== undefined ? key.value : obj[key]; + var value = typeof key === 'object' && typeof key.value !== 'undefined' ? key.value : obj[key]; if (skipNulls && value === null) { continue; @@ -164,7 +164,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.'); } From 4e312c487def80b879d5359e0d1991ce17685191 Mon Sep 17 00:00:00 2001 From: Mikhail Bodrov Date: Mon, 27 Dec 2021 22:09:36 +0200 Subject: [PATCH 235/278] [Robustness] `stringify`: avoid relying on a global `undefined` (#427) --- lib/stringify.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/stringify.js b/lib/stringify.js index 005b92a8..52cdd865 100644 --- a/lib/stringify.js +++ b/lib/stringify.js @@ -119,7 +119,7 @@ var stringify = function stringify( for (var j = 0; j < objKeys.length; ++j) { var key = objKeys[j]; - var value = typeof key === 'object' && key.value !== undefined ? key.value : obj[key]; + var value = typeof key === 'object' && typeof key.value !== 'undefined' ? key.value : obj[key]; if (skipNulls && value === null) { continue; @@ -154,7 +154,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.'); } From a8d52864e61bf5eee69788492e03ed5041a0fcd4 Mon Sep 17 00:00:00 2001 From: Mikhail Bodrov Date: Mon, 27 Dec 2021 22:09:36 +0200 Subject: [PATCH 236/278] [Robustness] `stringify`: avoid relying on a global `undefined` (#427) --- lib/stringify.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/stringify.js b/lib/stringify.js index 3c76e5fb..316cf2b7 100644 --- a/lib/stringify.js +++ b/lib/stringify.js @@ -111,7 +111,7 @@ var stringify = function stringify( for (var j = 0; j < objKeys.length; ++j) { var key = objKeys[j]; - var value = typeof key === 'object' && key.value !== undefined ? key.value : obj[key]; + var value = typeof key === 'object' && typeof key.value !== 'undefined' ? key.value : obj[key]; if (skipNulls && obj[key] === null) { continue; @@ -160,7 +160,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.'); } From 65c669e99f2b43b7b004f73e109ed4259d165610 Mon Sep 17 00:00:00 2001 From: Mikhail Bodrov Date: Mon, 27 Dec 2021 22:09:36 +0200 Subject: [PATCH 237/278] [Robustness] `stringify`: avoid relying on a global `undefined` (#427) --- lib/stringify.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/stringify.js b/lib/stringify.js index b74c014c..87498203 100644 --- a/lib/stringify.js +++ b/lib/stringify.js @@ -147,7 +147,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.'); } From 691e739cfa40cd42604dc05a54e6154371a429ab Mon Sep 17 00:00:00 2001 From: Mikhail Bodrov Date: Mon, 27 Dec 2021 22:09:36 +0200 Subject: [PATCH 238/278] [Robustness] `stringify`: avoid relying on a global `undefined` (#427) --- lib/stringify.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/stringify.js b/lib/stringify.js index 0b21ca75..12a96e65 100644 --- a/lib/stringify.js +++ b/lib/stringify.js @@ -133,7 +133,7 @@ module.exports = function (object, opts) { var obj = object; var options = opts ? utils.assign({}, opts) : {}; - if (options.encoder !== null && options.encoder !== undefined && typeof options.encoder !== 'function') { + if (options.encoder !== null && typeof options.encoder !== 'undefined' && typeof options.encoder !== 'function') { throw new TypeError('Encoder has to be a function.'); } From cd1874eb179950de3f5b32e708b4a3a2d0619501 Mon Sep 17 00:00:00 2001 From: Mikhail Bodrov Date: Mon, 27 Dec 2021 22:09:36 +0200 Subject: [PATCH 239/278] [Robustness] `stringify`: avoid relying on a global `undefined` (#427) --- lib/stringify.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/stringify.js b/lib/stringify.js index bdbf115e..88b570fd 100644 --- a/lib/stringify.js +++ b/lib/stringify.js @@ -133,7 +133,7 @@ module.exports = function (object, opts) { var obj = object; var options = opts || {}; - if (options.encoder !== null && options.encoder !== undefined && typeof options.encoder !== 'function') { + if (options.encoder !== null && typeof options.encoder !== 'undefined' && typeof options.encoder !== 'function') { throw new TypeError('Encoder has to be a function.'); } From aa4580e6911e1cf2e25d9d38250db6e960f0ef33 Mon Sep 17 00:00:00 2001 From: Mikhail Bodrov Date: Mon, 27 Dec 2021 22:09:36 +0200 Subject: [PATCH 240/278] [Robustness] `stringify`: avoid relying on a global `undefined` (#427) --- lib/stringify.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/stringify.js b/lib/stringify.js index 49565c13..c89285f3 100644 --- a/lib/stringify.js +++ b/lib/stringify.js @@ -128,7 +128,7 @@ module.exports = function (object, opts) { var obj = object; var options = opts || {}; - if (options.encoder !== null && options.encoder !== undefined && typeof options.encoder !== 'function') { + if (options.encoder !== null && typeof options.encoder !== 'undefined' && typeof options.encoder !== 'function') { throw new TypeError('Encoder has to be a function.'); } From 2c38654f781751e7401d1066ddbb596b1f58a394 Mon Sep 17 00:00:00 2001 From: Mikhail Bodrov Date: Mon, 27 Dec 2021 22:09:36 +0200 Subject: [PATCH 241/278] [Robustness] `stringify`: avoid relying on a global `undefined` (#427) --- lib/stringify.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/stringify.js b/lib/stringify.js index f05c6dd0..34b977b2 100644 --- a/lib/stringify.js +++ b/lib/stringify.js @@ -89,7 +89,7 @@ module.exports = function (object, opts) { var objKeys; var filter; - if (options.encoder !== null && options.encoder !== undefined && typeof options.encoder !== 'function') { + if (options.encoder !== null && typeof options.encoder !== 'undefined' && typeof options.encoder !== 'function') { throw new TypeError('Encoder has to be a function.'); } From 8b4cc14cda94a5c89341b77e5fe435ec6c41be2d Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Mon, 27 Dec 2021 19:15:57 -0800 Subject: [PATCH 242/278] [Fix] `parse`: ignore `__proto__` keys --- lib/parse.js | 2 +- test/parse.js | 60 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+), 1 deletion(-) diff --git a/lib/parse.js b/lib/parse.js index c833315c..a4ac4fa0 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -136,7 +136,7 @@ var parseObject = function (chain, val, options, valuesParsed) { ) { obj = []; obj[index] = leaf; - } else { + } else if (cleanRoot !== '__proto__') { obj[cleanRoot] = leaf; } } diff --git a/test/parse.js b/test/parse.js index 7a3cfdef..7d61023e 100644 --- a/test/parse.js +++ b/test/parse.js @@ -629,6 +629,66 @@ test('parse()', function (t) { 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); From e799ba57e573a30c14b67c1889c7c04d508b9105 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Mon, 27 Dec 2021 19:15:57 -0800 Subject: [PATCH 243/278] [Fix] `parse`: ignore `__proto__` keys (#428) --- lib/parse.js | 2 +- test/parse.js | 60 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+), 1 deletion(-) diff --git a/lib/parse.js b/lib/parse.js index 553498b4..698a310d 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -135,7 +135,7 @@ var parseObject = function (chain, val, options, valuesParsed) { ) { obj = []; obj[index] = leaf; - } else { + } else if (cleanRoot !== '__proto__') { obj[cleanRoot] = leaf; } } diff --git a/test/parse.js b/test/parse.js index b6ec1b72..7721aaa9 100644 --- a/test/parse.js +++ b/test/parse.js @@ -620,6 +620,66 @@ test('parse()', function (t) { 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); From fc3682776670524a42e19709ec4a8138d0d7afda Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Mon, 27 Dec 2021 19:15:57 -0800 Subject: [PATCH 244/278] [Fix] `parse`: ignore `__proto__` keys (#428) --- lib/parse.js | 2 +- test/parse.js | 60 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+), 1 deletion(-) diff --git a/lib/parse.js b/lib/parse.js index 177a569e..48cdad8a 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -146,7 +146,7 @@ var parseObject = function (chain, val, options, valuesParsed) { ) { obj = []; obj[index] = leaf; - } else { + } else if (cleanRoot !== '__proto__') { obj[cleanRoot] = leaf; } } diff --git a/test/parse.js b/test/parse.js index 0e440f42..213adb26 100644 --- a/test/parse.js +++ b/test/parse.js @@ -620,6 +620,66 @@ test('parse()', function (t) { 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); From f945393cfe442fe8c6e62b4156fd35452c0686ee Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Mon, 27 Dec 2021 19:15:57 -0800 Subject: [PATCH 245/278] [Fix] `parse`: ignore `__proto__` keys (#428) --- lib/parse.js | 2 +- test/parse.js | 60 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+), 1 deletion(-) diff --git a/lib/parse.js b/lib/parse.js index fe1cf328..db330b60 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -146,7 +146,7 @@ var parseObject = function (chain, val, options, valuesParsed) { ) { obj = []; obj[index] = leaf; - } else { + } else if (cleanRoot !== '__proto__') { obj[cleanRoot] = leaf; } } diff --git a/test/parse.js b/test/parse.js index a8e76a07..3bd58cbf 100644 --- a/test/parse.js +++ b/test/parse.js @@ -632,6 +632,66 @@ test('parse()', function (t) { 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); From 73205259936317b40f447c5cdb71c5b341848e1b Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Mon, 27 Dec 2021 19:15:57 -0800 Subject: [PATCH 246/278] [Fix] `parse`: ignore `__proto__` keys (#428) --- lib/parse.js | 2 +- test/parse.js | 60 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+), 1 deletion(-) diff --git a/lib/parse.js b/lib/parse.js index 13f57129..9b4c4cd3 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -115,7 +115,7 @@ var parseObject = function (chain, val, options) { ) { obj = []; obj[index] = leaf; - } else { + } else if (cleanRoot !== '__proto__') { obj[cleanRoot] = leaf; } } diff --git a/test/parse.js b/test/parse.js index 06a63536..6887f45e 100644 --- a/test/parse.js +++ b/test/parse.js @@ -530,6 +530,66 @@ test('parse()', function (t) { 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); From ed0f5dcbef4b168a8ae299d78b1e4a2e9b1baf1f Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Mon, 27 Dec 2021 19:15:57 -0800 Subject: [PATCH 247/278] [Fix] `parse`: ignore `__proto__` keys (#428) --- lib/parse.js | 2 +- test/parse.js | 60 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+), 1 deletion(-) diff --git a/lib/parse.js b/lib/parse.js index 4abc1a70..cb7127d9 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -70,7 +70,7 @@ var parseObject = function (chain, val, options) { ) { obj = []; obj[index] = leaf; - } else { + } else if (cleanRoot !== '__proto__') { obj[cleanRoot] = leaf; } } diff --git a/test/parse.js b/test/parse.js index 7614a287..7c198380 100644 --- a/test/parse.js +++ b/test/parse.js @@ -530,6 +530,66 @@ test('parse()', function (t) { 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); From 727ef5d34605108acb3513f72d5435972ed15b68 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Mon, 27 Dec 2021 19:15:57 -0800 Subject: [PATCH 248/278] [Fix] `parse`: ignore `__proto__` keys (#428) --- lib/parse.js | 2 +- test/parse.js | 60 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+), 1 deletion(-) diff --git a/lib/parse.js b/lib/parse.js index 81e415cc..f4cde7d7 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -68,7 +68,7 @@ var parseObject = function parseObjectRecursive(chain, val, options) { ) { obj = []; obj[index] = parseObject(chain, val, options); - } else { + } else if (cleanRoot !== '__proto__') { obj[cleanRoot] = parseObject(chain, val, options); } } diff --git a/test/parse.js b/test/parse.js index 9aaf67ad..ad27616a 100644 --- a/test/parse.js +++ b/test/parse.js @@ -487,6 +487,66 @@ test('parse()', function (t) { 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); From 2c103b6fd7fefc22004b8889f3f0de34d8cf9b38 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Sat, 8 Jan 2022 21:24:45 -0800 Subject: [PATCH 249/278] [actions] backport actions from main --- .github/workflows/node-aught.yml | 18 +++ .github/workflows/node-pretest.yml | 7 + .github/workflows/node-tens.yml | 18 +++ .github/workflows/rebase.yml | 15 ++ .github/workflows/require-allow-edits.yml | 12 ++ .travis.yml | 173 ---------------------- 6 files changed, 70 insertions(+), 173 deletions(-) create mode 100644 .github/workflows/node-aught.yml create mode 100644 .github/workflows/node-pretest.yml create mode 100644 .github/workflows/node-tens.yml create mode 100644 .github/workflows/rebase.yml create mode 100644 .github/workflows/require-allow-edits.yml delete mode 100644 .travis.yml diff --git a/.github/workflows/node-aught.yml b/.github/workflows/node-aught.yml new file mode 100644 index 00000000..f3cddd85 --- /dev/null +++ b/.github/workflows/node-aught.yml @@ -0,0 +1,18 @@ +name: 'Tests: node.js < 10' + +on: [pull_request, push] + +jobs: + tests: + uses: ljharb/actions/.github/workflows/node.yml@main + with: + range: '< 10' + type: minors + command: npm run tests-only + + node: + name: 'node < 10' + needs: [tests] + runs-on: ubuntu-latest + steps: + - run: 'echo tests completed' diff --git a/.github/workflows/node-pretest.yml b/.github/workflows/node-pretest.yml new file mode 100644 index 00000000..765edf79 --- /dev/null +++ b/.github/workflows/node-pretest.yml @@ -0,0 +1,7 @@ +name: 'Tests: pretest/posttest' + +on: [pull_request, push] + +jobs: + tests: + uses: ljharb/actions/.github/workflows/pretest.yml@main diff --git a/.github/workflows/node-tens.yml b/.github/workflows/node-tens.yml new file mode 100644 index 00000000..b49ceb1f --- /dev/null +++ b/.github/workflows/node-tens.yml @@ -0,0 +1,18 @@ +name: 'Tests: node.js >= 10' + +on: [pull_request, push] + +jobs: + tests: + uses: ljharb/actions/.github/workflows/node.yml@main + with: + range: '>= 10' + type: minors + command: npm run tests-only + + node: + name: 'node >= 10' + needs: [tests] + runs-on: ubuntu-latest + steps: + - run: 'echo tests completed' diff --git a/.github/workflows/rebase.yml b/.github/workflows/rebase.yml new file mode 100644 index 00000000..9596e285 --- /dev/null +++ b/.github/workflows/rebase.yml @@ -0,0 +1,15 @@ +name: Automatic Rebase + +on: [pull_request] + +jobs: + _: + name: "Automatic Rebase" + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - uses: ljharb/rebase@master + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/require-allow-edits.yml b/.github/workflows/require-allow-edits.yml new file mode 100644 index 00000000..7b842f89 --- /dev/null +++ b/.github/workflows/require-allow-edits.yml @@ -0,0 +1,12 @@ +name: Require “Allow Edits” + +on: [pull_request_target] + +jobs: + _: + name: "Require “Allow Edits”" + + runs-on: ubuntu-latest + + steps: + - uses: ljharb/require-allow-edits@main diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 0dbeaef9..00000000 --- a/.travis.yml +++ /dev/null @@ -1,173 +0,0 @@ -language: node_js -os: - - linux -node_js: - - "7.7" - - "6.10" - - "5.12" - - "4.8" - - "iojs-v3.3" - - "iojs-v2.5" - - "iojs-v1.8" - - "0.12" - - "0.10" - - "0.8" -before_install: - - 'if [ "${TRAVIS_NODE_VERSION}" = "0.6" ]; then npm install -g npm@1.3 ; elif [ "${TRAVIS_NODE_VERSION}" != "0.9" ]; then case "$(npm --version)" in 1.*) npm install -g npm@1.4.28 ;; 2.*) npm install -g npm@2 ;; esac ; fi' - - 'if [ "${TRAVIS_NODE_VERSION}" != "0.6" ] && [ "${TRAVIS_NODE_VERSION}" != "0.9" ]; then npm install -g npm; fi' -script: - - 'if [ -n "${PRETEST-}" ]; then npm run pretest ; fi' - - 'if [ -n "${POSTTEST-}" ]; then npm run posttest ; fi' - - 'if [ -n "${COVERAGE-}" ]; then npm run coverage ; fi' - - 'if [ -n "${TEST-}" ]; then npm run tests-only ; fi' -sudo: false -env: - - TEST=true -matrix: - fast_finish: true - include: - - node_js: "node" - env: PRETEST=true - - node_js: "4" - env: COVERAGE=true - - node_js: "7.6" - env: TEST=true ALLOW_FAILURE=true - - node_js: "7.5" - env: TEST=true ALLOW_FAILURE=true - - node_js: "7.4" - env: TEST=true ALLOW_FAILURE=true - - node_js: "7.3" - env: TEST=true ALLOW_FAILURE=true - - node_js: "7.2" - env: TEST=true ALLOW_FAILURE=true - - node_js: "7.1" - env: TEST=true ALLOW_FAILURE=true - - node_js: "7.0" - env: TEST=true ALLOW_FAILURE=true - - node_js: "6.9" - env: TEST=true ALLOW_FAILURE=true - - node_js: "6.8" - env: TEST=true ALLOW_FAILURE=true - - node_js: "6.7" - env: TEST=true ALLOW_FAILURE=true - - node_js: "6.6" - env: TEST=true ALLOW_FAILURE=true - - node_js: "6.5" - env: TEST=true ALLOW_FAILURE=true - - node_js: "6.4" - env: TEST=true ALLOW_FAILURE=true - - node_js: "6.3" - env: TEST=true ALLOW_FAILURE=true - - node_js: "6.2" - env: TEST=true ALLOW_FAILURE=true - - node_js: "6.1" - env: TEST=true ALLOW_FAILURE=true - - node_js: "6.0" - env: TEST=true ALLOW_FAILURE=true - - node_js: "5.11" - env: TEST=true ALLOW_FAILURE=true - - node_js: "5.10" - env: TEST=true ALLOW_FAILURE=true - - node_js: "5.9" - env: TEST=true ALLOW_FAILURE=true - - node_js: "5.8" - env: TEST=true ALLOW_FAILURE=true - - node_js: "5.7" - env: TEST=true ALLOW_FAILURE=true - - node_js: "5.6" - env: TEST=true ALLOW_FAILURE=true - - node_js: "5.5" - env: TEST=true ALLOW_FAILURE=true - - node_js: "5.4" - env: TEST=true ALLOW_FAILURE=true - - node_js: "5.3" - env: TEST=true ALLOW_FAILURE=true - - node_js: "5.2" - env: TEST=true ALLOW_FAILURE=true - - node_js: "5.1" - env: TEST=true ALLOW_FAILURE=true - - node_js: "5.0" - env: TEST=true ALLOW_FAILURE=true - - node_js: "4.7" - env: TEST=true ALLOW_FAILURE=true - - node_js: "4.6" - env: TEST=true ALLOW_FAILURE=true - - node_js: "4.5" - env: TEST=true ALLOW_FAILURE=true - - node_js: "4.4" - env: TEST=true ALLOW_FAILURE=true - - node_js: "4.3" - env: TEST=true ALLOW_FAILURE=true - - node_js: "4.2" - env: TEST=true ALLOW_FAILURE=true - - node_js: "4.1" - env: TEST=true ALLOW_FAILURE=true - - node_js: "4.0" - env: TEST=true ALLOW_FAILURE=true - - node_js: "iojs-v3.2" - env: TEST=true ALLOW_FAILURE=true - - node_js: "iojs-v3.1" - env: TEST=true ALLOW_FAILURE=true - - node_js: "iojs-v3.0" - env: TEST=true ALLOW_FAILURE=true - - node_js: "iojs-v2.4" - env: TEST=true ALLOW_FAILURE=true - - node_js: "iojs-v2.3" - env: TEST=true ALLOW_FAILURE=true - - node_js: "iojs-v2.2" - env: TEST=true ALLOW_FAILURE=true - - node_js: "iojs-v2.1" - env: TEST=true ALLOW_FAILURE=true - - node_js: "iojs-v2.0" - env: TEST=true ALLOW_FAILURE=true - - node_js: "iojs-v1.7" - env: TEST=true ALLOW_FAILURE=true - - node_js: "iojs-v1.6" - env: TEST=true ALLOW_FAILURE=true - - node_js: "iojs-v1.5" - env: TEST=true ALLOW_FAILURE=true - - node_js: "iojs-v1.4" - env: TEST=true ALLOW_FAILURE=true - - node_js: "iojs-v1.3" - env: TEST=true ALLOW_FAILURE=true - - node_js: "iojs-v1.2" - env: TEST=true ALLOW_FAILURE=true - - node_js: "iojs-v1.1" - env: TEST=true ALLOW_FAILURE=true - - node_js: "iojs-v1.0" - env: TEST=true ALLOW_FAILURE=true - - node_js: "0.11" - env: TEST=true ALLOW_FAILURE=true - - node_js: "0.9" - env: TEST=true ALLOW_FAILURE=true - - node_js: "0.6" - env: TEST=true ALLOW_FAILURE=true - - node_js: "0.4" - env: TEST=true ALLOW_FAILURE=true - ##- node_js: "7" - #env: TEST=true - #os: osx - #- node_js: "6" - #env: TEST=true - #os: osx - #- node_js: "5" - #env: TEST=true - #os: osx - #- node_js: "4" - #env: TEST=true - #os: osx - #- node_js: "iojs" - #env: TEST=true - #os: osx - #- node_js: "0.12" - #env: TEST=true - #os: osx - #- node_js: "0.10" - #env: TEST=true - #os: osx - #- node_js: "0.8" - #env: TEST=true - #os: osx - allow_failures: - - os: osx - - env: TEST=true ALLOW_FAILURE=true From da1eee03f599f3cdd802557874257091b3c4dac1 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Sun, 9 Jan 2022 22:40:45 -0800 Subject: [PATCH 250/278] [Dev Deps] backport from main --- .editorconfig | 44 +++++++++++++++++++++ .eslintignore | 1 - .eslintrc | 30 +++++++++++--- .gitignore | 8 ++++ .npmignore | 18 +++++++-- .nycrc | 13 ++++++ README.md | 2 +- bower.json | 38 +++++++++--------- component.json | 26 ++++++------ lib/parse.js | 15 +++---- lib/stringify.js | 14 +++---- lib/utils.js | 20 ++++++---- package.json | 101 ++++++++++++++++++++++++----------------------- test/.eslintrc | 10 ----- test/parse.js | 4 +- 15 files changed, 215 insertions(+), 129 deletions(-) create mode 100644 .editorconfig delete mode 100644 .eslintignore create mode 100644 .nycrc delete mode 100644 test/.eslintrc diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..226a9322 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,44 @@ +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 = 160 +quote_type = single + +[test/*] +max_line_length = off + +[*.md] +indent_size = off +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 + +[dist/*] +max_line_length = off + +[.nycrc] +indent_style = tab diff --git a/.eslintignore b/.eslintignore deleted file mode 100644 index 1521c8b7..00000000 --- a/.eslintignore +++ /dev/null @@ -1 +0,0 @@ -dist diff --git a/.eslintrc b/.eslintrc index 98560175..cd5979ed 100644 --- a/.eslintrc +++ b/.eslintrc @@ -3,17 +3,35 @@ "extends": "@ljharb", + "ignorePatterns": [ + "dist/", + ], + "rules": { - "complexity": [2, 25], + "complexity": [2, 29], "consistent-return": 1, + "func-name-matching": 0, "id-length": [2, { "min": 1, "max": 25, "properties": "never" }], "indent": [2, 4], - "max-params": [2, 11], - "max-statements": [2, 42], - "no-extra-parens": 1, + "max-lines-per-function": 0, + "max-params": [2, 12], + "max-statements": [2, 45], + "multiline-comment-style": 0, "no-continue": 1, "no-magic-numbers": 0, + "no-param-reassign": 1, "no-restricted-syntax": [2, "BreakStatement", "DebuggerStatement", "ForInStatement", "LabeledStatement", "WithStatement"], - "operator-linebreak": 1 - } + }, + + "overrides": [ + { + "files": "test/**", + "rules": { + "max-lines-per-function": 0, + "max-statements": 0, + "no-extend-native": 0, + "function-paren-newline": 0, + }, + }, + ], } diff --git a/.gitignore b/.gitignore index 8cace31c..267da50e 100644 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,11 @@ lib-cov complexity.md tests.tap dist/* + +# Only apps should have lockfiles +yarn.lock +package-lock.json +npm-shrinkwrap.json + +.nyc_output/ +coverage/ diff --git a/.npmignore b/.npmignore index ac980d91..5fafe6be 100644 --- a/.npmignore +++ b/.npmignore @@ -1,4 +1,14 @@ -bower.json -component.json -.npmignore -.travis.yml +# gitignore +npm-debug.log +node_modules +.DS_Store + +# Only apps should have lockfiles +yarn.lock +package-lock.json +npm-shrinkwrap.json + +.nyc_output/ +coverage/ + +.github/workflows diff --git a/.nycrc b/.nycrc new file mode 100644 index 00000000..1d57cabe --- /dev/null +++ b/.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/README.md b/README.md index e6e32130..7d0b750a 100644 --- a/README.md +++ b/README.md @@ -424,7 +424,7 @@ assert.equal(nullsSkipped, 'a=b'); ### Dealing with special character sets -By default the encoding and decoding of characters is done in `utf-8`. If you +By default the encoding and decoding of characters is done in `utf-8`. 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: diff --git a/bower.json b/bower.json index 44f05064..7a582762 100644 --- a/bower.json +++ b/bower.json @@ -1,21 +1,21 @@ { - "name": "qs", - "main": "dist/qs.js", - "homepage": "https://github.com/hapijs/qs", - "authors": [ - "Nathan LaFreniere " - ], - "description": "A querystring parser that supports nesting and arrays, with a depth limit", - "keywords": [ - "querystring", - "qs" - ], - "license": "BSD-3-Clause", - "ignore": [ - "**/.*", - "node_modules", - "bower_components", - "test", - "tests" - ] + "name": "qs", + "main": "dist/qs.js", + "homepage": "https://github.com/hapijs/qs", + "authors": [ + "Nathan LaFreniere " + ], + "description": "A querystring parser that supports nesting and arrays, with a depth limit", + "keywords": [ + "querystring", + "qs" + ], + "license": "BSD-3-Clause", + "ignore": [ + "**/.*", + "node_modules", + "bower_components", + "test", + "tests" + ] } diff --git a/component.json b/component.json index bbaf1344..a05aabd1 100644 --- a/component.json +++ b/component.json @@ -1,15 +1,15 @@ { - "name": "qs", - "repository": "hapijs/qs", - "description": "query-string parser / stringifier with nesting support", - "version": "6.3.2", - "keywords": ["querystring", "query", "parser"], - "main": "lib/index.js", - "scripts": [ - "lib/index.js", - "lib/parse.js", - "lib/stringify.js", - "lib/utils.js" - ], - "license": "BSD-3-Clause" + "name": "qs", + "repository": "hapijs/qs", + "description": "query-string parser / stringifier with nesting support", + "version": "6.3.2", + "keywords": ["querystring", "query", "parser"], + "main": "lib/index.js", + "scripts": [ + "lib/index.js", + "lib/parse.js", + "lib/stringify.js", + "lib/utils.js" + ], + "license": "BSD-3-Clause" } diff --git a/lib/parse.js b/lib/parse.js index 6eabc3c0..81e415cc 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -60,11 +60,11 @@ var parseObject = function parseObjectRecursive(chain, val, options) { if (!options.parseArrays && cleanRoot === '') { obj = { 0: val }; } else if ( - !isNaN(index) && - root !== cleanRoot && - String(index) === cleanRoot && - index >= 0 && - (options.parseArrays && index <= options.arrayLimit) + !isNaN(index) + && root !== cleanRoot + && String(index) === cleanRoot + && index >= 0 + && (options.parseArrays && index <= options.arrayLimit) ) { obj = []; obj[index] = parseObject(chain, val, options); @@ -98,10 +98,7 @@ var parseKeys = function parseQueryStringKeys(givenKey, val, options) { var keys = []; if (parent) { - /* - * If we aren't using plain objects, optionally prefix keys - * that would overwrite object prototype properties - */ + // If we aren't using plain objects, optionally prefix keys that would overwrite object prototype properties if (!options.plainObjects && has.call(Object.prototype, parent)) { if (!options.allowPrototypes) { return; diff --git a/lib/stringify.js b/lib/stringify.js index c89285f3..94429b84 100644 --- a/lib/stringify.js +++ b/lib/stringify.js @@ -4,13 +4,13 @@ var utils = require('./utils'); var formats = require('./formats'); var arrayPrefixGenerators = { - brackets: function brackets(prefix) { // eslint-disable-line func-name-matching + brackets: function brackets(prefix) { return prefix + '[]'; }, - 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; } }; @@ -27,14 +27,14 @@ var defaults = { delimiter: '&', encode: true, encoder: utils.encode, - 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 stringify = function stringify( object, prefix, generateArrayPrefix, @@ -136,12 +136,12 @@ module.exports = function (object, opts) { var strictNullHandling = typeof options.strictNullHandling === 'boolean' ? options.strictNullHandling : defaults.strictNullHandling; var skipNulls = typeof options.skipNulls === 'boolean' ? options.skipNulls : defaults.skipNulls; var encode = typeof options.encode === 'boolean' ? options.encode : defaults.encode; - var encoder = encode ? (typeof options.encoder === 'function' ? options.encoder : defaults.encoder) : null; + var encoder = encode ? typeof options.encoder === 'function' ? options.encoder : defaults.encoder : null; var sort = typeof options.sort === 'function' ? options.sort : null; var allowDots = typeof options.allowDots === 'undefined' ? false : options.allowDots; var serializeDate = typeof options.serializeDate === 'function' ? options.serializeDate : defaults.serializeDate; if (typeof options.format === 'undefined') { - options.format = formats.default; + options.format = formats['default']; } else if (!Object.prototype.hasOwnProperty.call(formats.formatters, options.format)) { throw new TypeError('Unknown format option provided.'); } diff --git a/lib/utils.js b/lib/utils.js index 9095b9d1..b198ae98 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -99,13 +99,13 @@ exports.encode = function (str) { var c = string.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 + 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 ) { out += string.charAt(i); continue; @@ -128,7 +128,11 @@ exports.encode = function (str) { i += 1; c = 0x10000 + (((c & 0x3FF) << 10) | (string.charCodeAt(i) & 0x3FF)); - out += hexTable[0xF0 | (c >> 18)] + hexTable[0x80 | ((c >> 12) & 0x3F)] + hexTable[0x80 | ((c >> 6) & 0x3F)] + hexTable[0x80 | (c & 0x3F)]; // eslint-disable-line max-len + /* eslint operator-linebreak: [2, "before"] */ + out += hexTable[0xF0 | (c >> 18)] + + hexTable[0x80 | ((c >> 12) & 0x3F)] + + hexTable[0x80 | ((c >> 6) & 0x3F)] + + hexTable[0x80 | (c & 0x3F)]; } return out; diff --git a/package.json b/package.json index 61997d35..00a42b4d 100644 --- a/package.json +++ b/package.json @@ -1,51 +1,54 @@ { - "name": "qs", - "description": "A querystring parser that supports nesting and arrays, with a depth limit", - "homepage": "https://github.com/ljharb/qs", - "version": "6.3.2", - "repository": { - "type": "git", - "url": "https://github.com/ljharb/qs.git" - }, - "main": "lib/index.js", - "contributors": [ - { - "name": "Jordan Harband", - "email": "ljharb@gmail.com", - "url": "http://ljharb.codes" - } - ], - "keywords": [ - "querystring", - "qs" - ], - "engines": { - "node": ">=0.6" - }, - "dependencies": {}, - "devDependencies": { - "@ljharb/eslint-config": "^11.0.0", - "browserify": "^14.1.0", - "covert": "^1.1.0", - "eslint": "^3.17.0", - "evalmd": "^0.0.17", - "iconv-lite": "^0.4.15", - "mkdirp": "^0.5.1", - "parallelshell": "^2.0.0", - "qs-iconv": "^1.0.4", - "safe-publish-latest": "^1.1.1", - "safer-buffer": "^2.0.2", - "tape": "^4.6.3" - }, - "scripts": { - "prepublish": "safe-publish-latest && npm run dist", - "pretest": "npm run --silent readme && npm run --silent lint", - "test": "npm run --silent coverage", - "tests-only": "node test", - "readme": "evalmd README.md", - "lint": "eslint lib/*.js test/*.js", - "coverage": "covert test", - "dist": "mkdirp dist && browserify --standalone Qs lib/index.js > dist/qs.js" - }, - "license": "BSD-3-Clause" + "name": "qs", + "description": "A querystring parser that supports nesting and arrays, with a depth limit", + "homepage": "https://github.com/ljharb/qs", + "version": "6.3.2", + "repository": { + "type": "git", + "url": "https://github.com/ljharb/qs.git" + }, + "main": "lib/index.js", + "contributors": [ + { + "name": "Jordan Harband", + "email": "ljharb@gmail.com", + "url": "http://ljharb.codes" + } + ], + "keywords": [ + "querystring", + "qs" + ], + "engines": { + "node": ">=0.6" + }, + "devDependencies": { + "@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.17", + "iconv-lite": "^0.4.24", + "in-publish": "^2.0.1", + "mkdirp": "^0.5.1", + "nyc": "^10.3.2", + "qs-iconv": "^1.0.4", + "safe-publish-latest": "^2.0.0", + "safer-buffer": "^2.1.2", + "tape": "^5.4.0" + }, + "scripts": { + "prepublishOnly": "safe-publish-latest && npm run dist", + "prepublish": "not-in-publish || npm run prepublishOnly", + "pretest": "npm run --silent readme && npm run --silent lint", + "test": "npm run --silent tests-only", + "tests-only": "nyc tape 'test/**/*.js'", + "posttest": "aud --production", + "readme": "evalmd README.md", + "postlint": "eclint check $(git ls-files | xargs find 2> /dev/null | grep -vE 'node_modules|\\.git')", + "lint": "eslint --ext=js,mjs .", + "dist": "mkdirp dist && browserify --standalone Qs lib/index.js > dist/qs.js" + }, + "license": "BSD-3-Clause" } diff --git a/test/.eslintrc b/test/.eslintrc deleted file mode 100644 index 1b6df617..00000000 --- a/test/.eslintrc +++ /dev/null @@ -1,10 +0,0 @@ -{ - "rules": { - "consistent-return": 2, - "max-lines": 0, - "max-nested-callbacks": [2, 3], - "max-statements": 0, - "no-extend-native": 0, - "sort-keys": 1 - } -} diff --git a/test/parse.js b/test/parse.js index a90739b9..9aaf67ad 100644 --- a/test/parse.js +++ b/test/parse.js @@ -480,7 +480,7 @@ 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' ); @@ -519,7 +519,7 @@ test('parse()', function (t) { }); t.test('throws error with wrong decoder', function (st) { - st.throws(function () { + st['throws'](function () { qs.parse({}, { decoder: 'string' }); }, new TypeError('Decoder has to be a function.')); st.end(); From 4310742efbd8c03f6495f07906b45213da0a32ec Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Mon, 27 Dec 2021 19:15:57 -0800 Subject: [PATCH 251/278] [Fix] `parse`: ignore `__proto__` keys (#428) --- lib/parse.js | 2 +- test/parse.js | 60 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+), 1 deletion(-) diff --git a/lib/parse.js b/lib/parse.js index 81e415cc..f4cde7d7 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -68,7 +68,7 @@ var parseObject = function parseObjectRecursive(chain, val, options) { ) { obj = []; obj[index] = parseObject(chain, val, options); - } else { + } else if (cleanRoot !== '__proto__') { obj[cleanRoot] = parseObject(chain, val, options); } } diff --git a/test/parse.js b/test/parse.js index 9aaf67ad..ad27616a 100644 --- a/test/parse.js +++ b/test/parse.js @@ -487,6 +487,66 @@ test('parse()', function (t) { 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); From 5f8e28bd80d8431929b85eeca35e3180147a6462 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Sat, 8 Jan 2022 21:24:45 -0800 Subject: [PATCH 252/278] [actions] backport actions from main --- .github/workflows/node-aught.yml | 18 +++ .github/workflows/node-pretest.yml | 7 + .github/workflows/node-tens.yml | 18 +++ .github/workflows/rebase.yml | 15 ++ .github/workflows/require-allow-edits.yml | 12 ++ .travis.yml | 173 ---------------------- 6 files changed, 70 insertions(+), 173 deletions(-) create mode 100644 .github/workflows/node-aught.yml create mode 100644 .github/workflows/node-pretest.yml create mode 100644 .github/workflows/node-tens.yml create mode 100644 .github/workflows/rebase.yml create mode 100644 .github/workflows/require-allow-edits.yml delete mode 100644 .travis.yml diff --git a/.github/workflows/node-aught.yml b/.github/workflows/node-aught.yml new file mode 100644 index 00000000..f3cddd85 --- /dev/null +++ b/.github/workflows/node-aught.yml @@ -0,0 +1,18 @@ +name: 'Tests: node.js < 10' + +on: [pull_request, push] + +jobs: + tests: + uses: ljharb/actions/.github/workflows/node.yml@main + with: + range: '< 10' + type: minors + command: npm run tests-only + + node: + name: 'node < 10' + needs: [tests] + runs-on: ubuntu-latest + steps: + - run: 'echo tests completed' diff --git a/.github/workflows/node-pretest.yml b/.github/workflows/node-pretest.yml new file mode 100644 index 00000000..765edf79 --- /dev/null +++ b/.github/workflows/node-pretest.yml @@ -0,0 +1,7 @@ +name: 'Tests: pretest/posttest' + +on: [pull_request, push] + +jobs: + tests: + uses: ljharb/actions/.github/workflows/pretest.yml@main diff --git a/.github/workflows/node-tens.yml b/.github/workflows/node-tens.yml new file mode 100644 index 00000000..b49ceb1f --- /dev/null +++ b/.github/workflows/node-tens.yml @@ -0,0 +1,18 @@ +name: 'Tests: node.js >= 10' + +on: [pull_request, push] + +jobs: + tests: + uses: ljharb/actions/.github/workflows/node.yml@main + with: + range: '>= 10' + type: minors + command: npm run tests-only + + node: + name: 'node >= 10' + needs: [tests] + runs-on: ubuntu-latest + steps: + - run: 'echo tests completed' diff --git a/.github/workflows/rebase.yml b/.github/workflows/rebase.yml new file mode 100644 index 00000000..9596e285 --- /dev/null +++ b/.github/workflows/rebase.yml @@ -0,0 +1,15 @@ +name: Automatic Rebase + +on: [pull_request] + +jobs: + _: + name: "Automatic Rebase" + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - uses: ljharb/rebase@master + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/require-allow-edits.yml b/.github/workflows/require-allow-edits.yml new file mode 100644 index 00000000..7b842f89 --- /dev/null +++ b/.github/workflows/require-allow-edits.yml @@ -0,0 +1,12 @@ +name: Require “Allow Edits” + +on: [pull_request_target] + +jobs: + _: + name: "Require “Allow Edits”" + + runs-on: ubuntu-latest + + steps: + - uses: ljharb/require-allow-edits@main diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 0dbeaef9..00000000 --- a/.travis.yml +++ /dev/null @@ -1,173 +0,0 @@ -language: node_js -os: - - linux -node_js: - - "7.7" - - "6.10" - - "5.12" - - "4.8" - - "iojs-v3.3" - - "iojs-v2.5" - - "iojs-v1.8" - - "0.12" - - "0.10" - - "0.8" -before_install: - - 'if [ "${TRAVIS_NODE_VERSION}" = "0.6" ]; then npm install -g npm@1.3 ; elif [ "${TRAVIS_NODE_VERSION}" != "0.9" ]; then case "$(npm --version)" in 1.*) npm install -g npm@1.4.28 ;; 2.*) npm install -g npm@2 ;; esac ; fi' - - 'if [ "${TRAVIS_NODE_VERSION}" != "0.6" ] && [ "${TRAVIS_NODE_VERSION}" != "0.9" ]; then npm install -g npm; fi' -script: - - 'if [ -n "${PRETEST-}" ]; then npm run pretest ; fi' - - 'if [ -n "${POSTTEST-}" ]; then npm run posttest ; fi' - - 'if [ -n "${COVERAGE-}" ]; then npm run coverage ; fi' - - 'if [ -n "${TEST-}" ]; then npm run tests-only ; fi' -sudo: false -env: - - TEST=true -matrix: - fast_finish: true - include: - - node_js: "node" - env: PRETEST=true - - node_js: "4" - env: COVERAGE=true - - node_js: "7.6" - env: TEST=true ALLOW_FAILURE=true - - node_js: "7.5" - env: TEST=true ALLOW_FAILURE=true - - node_js: "7.4" - env: TEST=true ALLOW_FAILURE=true - - node_js: "7.3" - env: TEST=true ALLOW_FAILURE=true - - node_js: "7.2" - env: TEST=true ALLOW_FAILURE=true - - node_js: "7.1" - env: TEST=true ALLOW_FAILURE=true - - node_js: "7.0" - env: TEST=true ALLOW_FAILURE=true - - node_js: "6.9" - env: TEST=true ALLOW_FAILURE=true - - node_js: "6.8" - env: TEST=true ALLOW_FAILURE=true - - node_js: "6.7" - env: TEST=true ALLOW_FAILURE=true - - node_js: "6.6" - env: TEST=true ALLOW_FAILURE=true - - node_js: "6.5" - env: TEST=true ALLOW_FAILURE=true - - node_js: "6.4" - env: TEST=true ALLOW_FAILURE=true - - node_js: "6.3" - env: TEST=true ALLOW_FAILURE=true - - node_js: "6.2" - env: TEST=true ALLOW_FAILURE=true - - node_js: "6.1" - env: TEST=true ALLOW_FAILURE=true - - node_js: "6.0" - env: TEST=true ALLOW_FAILURE=true - - node_js: "5.11" - env: TEST=true ALLOW_FAILURE=true - - node_js: "5.10" - env: TEST=true ALLOW_FAILURE=true - - node_js: "5.9" - env: TEST=true ALLOW_FAILURE=true - - node_js: "5.8" - env: TEST=true ALLOW_FAILURE=true - - node_js: "5.7" - env: TEST=true ALLOW_FAILURE=true - - node_js: "5.6" - env: TEST=true ALLOW_FAILURE=true - - node_js: "5.5" - env: TEST=true ALLOW_FAILURE=true - - node_js: "5.4" - env: TEST=true ALLOW_FAILURE=true - - node_js: "5.3" - env: TEST=true ALLOW_FAILURE=true - - node_js: "5.2" - env: TEST=true ALLOW_FAILURE=true - - node_js: "5.1" - env: TEST=true ALLOW_FAILURE=true - - node_js: "5.0" - env: TEST=true ALLOW_FAILURE=true - - node_js: "4.7" - env: TEST=true ALLOW_FAILURE=true - - node_js: "4.6" - env: TEST=true ALLOW_FAILURE=true - - node_js: "4.5" - env: TEST=true ALLOW_FAILURE=true - - node_js: "4.4" - env: TEST=true ALLOW_FAILURE=true - - node_js: "4.3" - env: TEST=true ALLOW_FAILURE=true - - node_js: "4.2" - env: TEST=true ALLOW_FAILURE=true - - node_js: "4.1" - env: TEST=true ALLOW_FAILURE=true - - node_js: "4.0" - env: TEST=true ALLOW_FAILURE=true - - node_js: "iojs-v3.2" - env: TEST=true ALLOW_FAILURE=true - - node_js: "iojs-v3.1" - env: TEST=true ALLOW_FAILURE=true - - node_js: "iojs-v3.0" - env: TEST=true ALLOW_FAILURE=true - - node_js: "iojs-v2.4" - env: TEST=true ALLOW_FAILURE=true - - node_js: "iojs-v2.3" - env: TEST=true ALLOW_FAILURE=true - - node_js: "iojs-v2.2" - env: TEST=true ALLOW_FAILURE=true - - node_js: "iojs-v2.1" - env: TEST=true ALLOW_FAILURE=true - - node_js: "iojs-v2.0" - env: TEST=true ALLOW_FAILURE=true - - node_js: "iojs-v1.7" - env: TEST=true ALLOW_FAILURE=true - - node_js: "iojs-v1.6" - env: TEST=true ALLOW_FAILURE=true - - node_js: "iojs-v1.5" - env: TEST=true ALLOW_FAILURE=true - - node_js: "iojs-v1.4" - env: TEST=true ALLOW_FAILURE=true - - node_js: "iojs-v1.3" - env: TEST=true ALLOW_FAILURE=true - - node_js: "iojs-v1.2" - env: TEST=true ALLOW_FAILURE=true - - node_js: "iojs-v1.1" - env: TEST=true ALLOW_FAILURE=true - - node_js: "iojs-v1.0" - env: TEST=true ALLOW_FAILURE=true - - node_js: "0.11" - env: TEST=true ALLOW_FAILURE=true - - node_js: "0.9" - env: TEST=true ALLOW_FAILURE=true - - node_js: "0.6" - env: TEST=true ALLOW_FAILURE=true - - node_js: "0.4" - env: TEST=true ALLOW_FAILURE=true - ##- node_js: "7" - #env: TEST=true - #os: osx - #- node_js: "6" - #env: TEST=true - #os: osx - #- node_js: "5" - #env: TEST=true - #os: osx - #- node_js: "4" - #env: TEST=true - #os: osx - #- node_js: "iojs" - #env: TEST=true - #os: osx - #- node_js: "0.12" - #env: TEST=true - #os: osx - #- node_js: "0.10" - #env: TEST=true - #os: osx - #- node_js: "0.8" - #env: TEST=true - #os: osx - allow_failures: - - os: osx - - env: TEST=true ALLOW_FAILURE=true From f047c9d527c329017d3d94ccbb146e6de4cff75c Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Sun, 9 Jan 2022 22:40:45 -0800 Subject: [PATCH 253/278] [Dev Deps] backport from main --- .editorconfig | 45 +++++++++++++++++++++ .eslintignore | 1 - .eslintrc | 32 +++++++++++---- .gitignore | 8 ++++ .npmignore | 18 +++++++-- .nycrc | 13 ++++++ README.md | 2 +- bower.json | 38 ++++++++--------- component.json | 26 ++++++------ lib/parse.js | 15 +++---- lib/stringify.js | 49 +++++++++++++++++++--- lib/utils.js | 20 +++++---- package.json | 101 ++++++++++++++++++++++++---------------------- test/index.js | 2 + test/parse.js | 54 ++++++++++++------------- test/stringify.js | 2 +- 16 files changed, 280 insertions(+), 146 deletions(-) create mode 100644 .editorconfig delete mode 100644 .eslintignore create mode 100644 .nycrc diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..cd3439f5 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,45 @@ +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 = 160 +quote_type = single + +[test/*] +max_line_length = off + +[*.md] +indent_size = off +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 + +[dist/*] +max_line_length = off +insert_final_newline = off + +[.nycrc] +indent_style = off diff --git a/.eslintignore b/.eslintignore deleted file mode 100644 index 1521c8b7..00000000 --- a/.eslintignore +++ /dev/null @@ -1 +0,0 @@ -dist diff --git a/.eslintrc b/.eslintrc index 16344a23..20ed377f 100644 --- a/.eslintrc +++ b/.eslintrc @@ -3,21 +3,37 @@ "extends": "@ljharb", + "ignorePatterns": [ + "dist/", + ], + "rules": { - "complexity": [2, 25], - "consistent-return": [1], + "complexity": [2, 29], + "consistent-return": 1, "func-name-matching": 0, "id-length": [2, { "min": 1, "max": 25, "properties": "never" }], "indent": [2, 4], - "max-len": 0, "max-lines-per-function": 0, - "max-params": [2, 9], - "max-statements": [0, 36], - "no-extra-parens": [1], - "no-continue": [1], + "max-lines": 0, + "max-params": [2, 12], + "max-statements": [2, 45], + "multiline-comment-style": 0, + "no-continue": 1, "no-magic-numbers": 0, + "no-param-reassign": 1, "no-restricted-syntax": [2, "BreakStatement", "DebuggerStatement", "ForInStatement", "LabeledStatement", "WithStatement"], - "operator-linebreak": 1, "sort-keys": 0, }, + + "overrides": [ + { + "files": "test/**", + "rules": { + "max-lines-per-function": 0, + "max-statements": 0, + "no-extend-native": 0, + "function-paren-newline": 0, + }, + }, + ], } diff --git a/.gitignore b/.gitignore index 8cace31c..267da50e 100644 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,11 @@ lib-cov complexity.md tests.tap dist/* + +# Only apps should have lockfiles +yarn.lock +package-lock.json +npm-shrinkwrap.json + +.nyc_output/ +coverage/ diff --git a/.npmignore b/.npmignore index ac980d91..5fafe6be 100644 --- a/.npmignore +++ b/.npmignore @@ -1,4 +1,14 @@ -bower.json -component.json -.npmignore -.travis.yml +# gitignore +npm-debug.log +node_modules +.DS_Store + +# Only apps should have lockfiles +yarn.lock +package-lock.json +npm-shrinkwrap.json + +.nyc_output/ +coverage/ + +.github/workflows diff --git a/.nycrc b/.nycrc new file mode 100644 index 00000000..1d57cabe --- /dev/null +++ b/.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/README.md b/README.md index e393c936..9a1c16af 100644 --- a/README.md +++ b/README.md @@ -380,7 +380,7 @@ assert.equal(nullsSkipped, 'a=b'); ### Dealing with special character sets -By default the encoding and decoding of characters is done in `utf-8`. If you +By default the encoding and decoding of characters is done in `utf-8`. 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: diff --git a/bower.json b/bower.json index 44f05064..7a582762 100644 --- a/bower.json +++ b/bower.json @@ -1,21 +1,21 @@ { - "name": "qs", - "main": "dist/qs.js", - "homepage": "https://github.com/hapijs/qs", - "authors": [ - "Nathan LaFreniere " - ], - "description": "A querystring parser that supports nesting and arrays, with a depth limit", - "keywords": [ - "querystring", - "qs" - ], - "license": "BSD-3-Clause", - "ignore": [ - "**/.*", - "node_modules", - "bower_components", - "test", - "tests" - ] + "name": "qs", + "main": "dist/qs.js", + "homepage": "https://github.com/hapijs/qs", + "authors": [ + "Nathan LaFreniere " + ], + "description": "A querystring parser that supports nesting and arrays, with a depth limit", + "keywords": [ + "querystring", + "qs" + ], + "license": "BSD-3-Clause", + "ignore": [ + "**/.*", + "node_modules", + "bower_components", + "test", + "tests" + ] } diff --git a/component.json b/component.json index 971bbb27..c1715a89 100644 --- a/component.json +++ b/component.json @@ -1,15 +1,15 @@ { - "name": "qs", - "repository": "hapijs/qs", - "description": "query-string parser / stringifier with nesting support", - "version": "6.2.3", - "keywords": ["querystring", "query", "parser"], - "main": "lib/index.js", - "scripts": [ - "lib/index.js", - "lib/parse.js", - "lib/stringify.js", - "lib/utils.js" - ], - "license": "BSD-3-Clause" + "name": "qs", + "repository": "hapijs/qs", + "description": "query-string parser / stringifier with nesting support", + "version": "6.2.3", + "keywords": ["querystring", "query", "parser"], + "main": "lib/index.js", + "scripts": [ + "lib/index.js", + "lib/parse.js", + "lib/stringify.js", + "lib/utils.js" + ], + "license": "BSD-3-Clause" } diff --git a/lib/parse.js b/lib/parse.js index 2110e74a..7ef09a85 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -60,11 +60,11 @@ var parseObject = function parseObject(chain, val, options) { if (!options.parseArrays && cleanRoot === '') { obj = { 0: val }; } else if ( - !isNaN(index) && - root !== cleanRoot && - String(index) === cleanRoot && - index >= 0 && - (options.parseArrays && index <= options.arrayLimit) + !isNaN(index) + && root !== cleanRoot + && String(index) === cleanRoot + && index >= 0 + && (options.parseArrays && index <= options.arrayLimit) ) { obj = []; obj[index] = parseObject(chain, val, options); @@ -98,10 +98,7 @@ var parseKeys = function parseKeys(givenKey, val, options) { var keys = []; if (parent) { - /* - * If we aren't using plain objects, optionally prefix keys - * that would overwrite object prototype properties - */ + // If we aren't using plain objects, optionally prefix keys that would overwrite object prototype properties if (!options.plainObjects && has.call(Object.prototype, parent)) { if (!options.allowPrototypes) { return; diff --git a/lib/stringify.js b/lib/stringify.js index 34b977b2..b5009706 100644 --- a/lib/stringify.js +++ b/lib/stringify.js @@ -23,8 +23,17 @@ var defaults = { }; var isArray = Array.isArray; - -var stringify = function stringify(object, prefix, generateArrayPrefix, strictNullHandling, skipNulls, encoder, filter, sort, allowDots) { +var stringify = function stringify( + object, + prefix, + generateArrayPrefix, + strictNullHandling, + skipNulls, + encoder, + filter, + sort, + allowDots +) { var obj = object; if (typeof filter === 'function') { obj = filter(prefix, obj); @@ -67,9 +76,29 @@ var stringify = function stringify(object, prefix, generateArrayPrefix, strictNu } if (isArray(obj)) { - values = values.concat(stringify(obj[key], generateArrayPrefix(prefix, key), generateArrayPrefix, strictNullHandling, skipNulls, encoder, filter, sort, allowDots)); + values = values.concat(stringify( + obj[key], + generateArrayPrefix(prefix, key), + generateArrayPrefix, + strictNullHandling, + skipNulls, + encoder, + filter, + sort, + allowDots + )); } else { - values = values.concat(stringify(obj[key], prefix + (allowDots ? '.' + key : '[' + key + ']'), generateArrayPrefix, strictNullHandling, skipNulls, encoder, filter, sort, allowDots)); + values = values.concat(stringify( + obj[key], + prefix + (allowDots ? '.' + key : '[' + key + ']'), + generateArrayPrefix, + strictNullHandling, + skipNulls, + encoder, + filter, + sort, + allowDots + )); } } @@ -133,7 +162,17 @@ module.exports = function (object, opts) { continue; } - keys = keys.concat(stringify(obj[key], key, generateArrayPrefix, strictNullHandling, skipNulls, encoder, filter, sort, allowDots)); + keys = keys.concat(stringify( + obj[key], + key, + generateArrayPrefix, + strictNullHandling, + skipNulls, + encoder, + filter, + sort, + allowDots + )); } return keys.join(delimiter); diff --git a/lib/utils.js b/lib/utils.js index f778daae..d445a74f 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -114,13 +114,13 @@ exports.encode = function (str) { var c = string.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 + 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 ) { out += string.charAt(i); continue; @@ -143,7 +143,11 @@ exports.encode = function (str) { i += 1; c = 0x10000 + (((c & 0x3FF) << 10) | (string.charCodeAt(i) & 0x3FF)); - out += hexTable[0xF0 | (c >> 18)] + hexTable[0x80 | ((c >> 12) & 0x3F)] + hexTable[0x80 | ((c >> 6) & 0x3F)] + hexTable[0x80 | (c & 0x3F)]; + /* eslint operator-linebreak: [2, "before"] */ + out += hexTable[0xF0 | (c >> 18)] + + hexTable[0x80 | ((c >> 12) & 0x3F)] + + hexTable[0x80 | ((c >> 6) & 0x3F)] + + hexTable[0x80 | (c & 0x3F)]; } return out; diff --git a/package.json b/package.json index 4bf439f8..b248fb9d 100644 --- a/package.json +++ b/package.json @@ -1,51 +1,54 @@ { - "name": "qs", - "description": "A querystring parser that supports nesting and arrays, with a depth limit", - "homepage": "https://github.com/ljharb/qs", - "version": "6.2.3", - "repository": { - "type": "git", - "url": "https://github.com/ljharb/qs.git" - }, - "main": "lib/index.js", - "contributors": [ - { - "name": "Jordan Harband", - "email": "ljharb@gmail.com", - "url": "http://ljharb.codes" - } - ], - "keywords": [ - "querystring", - "qs" - ], - "engines": { - "node": ">=0.6" - }, - "dependencies": {}, - "devDependencies": { - "@ljharb/eslint-config": "^6.0.0", - "browserify": "^13.0.1", - "covert": "^1.1.0", - "eslint": "^3.1.0", - "evalmd": "^0.0.17", - "iconv-lite": "^0.4.13", - "mkdirp": "^0.5.1", - "parallelshell": "^2.0.0", - "qs-iconv": "^1.0.4", - "safe-publish-latest": "^1.1.1", - "safer-buffer": "^2.0.2", - "tape": "^4.6.3" - }, - "scripts": { - "pretest": "npm run --silent readme && npm run --silent lint", - "test": "npm run --silent coverage", - "tests-only": "node test", - "readme": "evalmd README.md", - "lint": "eslint lib/*.js text/*.js", - "coverage": "covert test", - "dist": "mkdirp dist && browserify --standalone Qs lib/index.js > dist/qs.js", - "prepublish": "npm run dist" - }, - "license": "BSD-3-Clause" + "name": "qs", + "description": "A querystring parser that supports nesting and arrays, with a depth limit", + "homepage": "https://github.com/ljharb/qs", + "version": "6.2.3", + "repository": { + "type": "git", + "url": "https://github.com/ljharb/qs.git" + }, + "main": "lib/index.js", + "contributors": [ + { + "name": "Jordan Harband", + "email": "ljharb@gmail.com", + "url": "http://ljharb.codes" + } + ], + "keywords": [ + "querystring", + "qs" + ], + "engines": { + "node": ">=0.6" + }, + "devDependencies": { + "@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.17", + "iconv-lite": "^0.4.24", + "in-publish": "^2.0.1", + "mkdirp": "^0.5.1", + "nyc": "^10.3.2", + "qs-iconv": "^1.0.4", + "safe-publish-latest": "^2.0.0", + "safer-buffer": "^2.1.2", + "tape": "^5.4.0" + }, + "scripts": { + "prepublishOnly": "safe-publish-latest && npm run dist", + "prepublish": "not-in-publish || npm run prepublishOnly", + "pretest": "npm run --silent readme && npm run --silent lint", + "test": "npm run --silent tests-only", + "tests-only": "nyc tape 'test/**/*.js'", + "posttest": "aud --production", + "readme": "evalmd README.md", + "postlint": "eclint check $(git ls-files | xargs find 2> /dev/null | grep -vE 'node_modules|\\.git')", + "lint": "eslint --ext=js,mjs .", + "dist": "mkdirp dist && browserify --standalone Qs lib/index.js > dist/qs.js" + }, + "license": "BSD-3-Clause" } diff --git a/test/index.js b/test/index.js index b6a7d952..5e6bc8fb 100644 --- a/test/index.js +++ b/test/index.js @@ -1,3 +1,5 @@ +'use strict'; + require('./parse'); require('./stringify'); diff --git a/test/parse.js b/test/parse.js index f6051312..87e09707 100644 --- a/test/parse.js +++ b/test/parse.js @@ -7,7 +7,7 @@ var SaferBuffer = require('safer-buffer').Buffer; test('parse()', function (t) { t.test('parses a simple string', function (st) { - st.deepEqual(qs.parse('0=foo'), { '0': 'foo' }); + 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' } }); @@ -85,7 +85,7 @@ test('parse()', function (t) { t.test('limits specific array indices to 20', function (st) { st.deepEqual(qs.parse('a[20]=a'), { a: ['a'] }); - st.deepEqual(qs.parse('a[21]=a'), { a: { '21': 'a' } }); + st.deepEqual(qs.parse('a[21]=a'), { a: { 21: 'a' } }); st.end(); }); @@ -116,11 +116,11 @@ test('parse()', function (t) { }); 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]=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' } }); @@ -136,16 +136,16 @@ test('parse()', function (t) { 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.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.deepEqual(qs.parse('a[2]=b&a[99999999]=c'), { a: { 2: 'b', 99999999: 'c' } }); st.end(); }); @@ -157,7 +157,7 @@ test('parse()', function (t) { }); t.test('doesn\'t produce empty keys', function (st) { - st.deepEqual(qs.parse('_r=1&'), { '_r': '1' }); + st.deepEqual(qs.parse('_r=1&'), { _r: '1' }); st.end(); }); @@ -228,8 +228,8 @@ test('parse()', function (t) { }); 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('[]=&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(); }); @@ -283,9 +283,9 @@ test('parse()', function (t) { }); 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: -1 }), { a: { 0: 'b' } }); st.deepEqual(qs.parse('a[-1]=b', { arrayLimit: -1 }), { a: { '-1': 'b' } }); - st.deepEqual(qs.parse('a[0]=b&a[1]=c', { arrayLimit: 0 }), { a: { '0': 'b', '1': 'c' } }); + st.deepEqual(qs.parse('a[0]=b&a[1]=c', { arrayLimit: 0 }), { a: { 0: 'b', 1: 'c' } }); st.end(); }); @@ -341,13 +341,13 @@ test('parse()', function (t) { t.test('parses an object and not child values', function (st) { var input = { - 'user[name]': { 'pop[bob]': { 'test': 3 } }, + 'user[name]': { 'pop[bob]': { test: 3 } }, 'user[email]': null }; var expected = { user: { - name: { 'pop[bob]': { 'test': 3 } }, + name: { 'pop[bob]': { test: 3 } }, email: null } }; @@ -469,7 +469,7 @@ 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' ); @@ -494,13 +494,13 @@ test('parse()', function (t) { 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 reg = /%([0-9A-F]{2})/ig; var result = []; var parts; - var last = 0; - while (parts = reg.exec(str)) { + // var last = 0; + while ((parts = reg.exec(str))) { result.push(parseInt(parts[1], 16)); - last = parts.index + parts[0].length; + // last = parts.index + parts[0].length; } return iconv.decode(SaferBuffer.from(result), 'shift_jis').toString(); } @@ -509,10 +509,8 @@ test('parse()', function (t) { }); t.test('throws error with wrong decoder', function (st) { - st.throws(function () { - qs.parse({}, { - decoder: 'string' - }); + st['throws'](function () { + qs.parse({}, { decoder: 'string' }); }, new TypeError('Decoder has to be a function.')); st.end(); }); diff --git a/test/stringify.js b/test/stringify.js index 46be2334..7d77f32d 100644 --- a/test/stringify.js +++ b/test/stringify.js @@ -224,7 +224,7 @@ test('stringify()', function (t) { var calls = 0; var obj = { a: 'b', c: 'd', e: { f: new Date(1257894000000) } }; var filterFunc = function (prefix, value) { - calls++; + calls += 1; if (calls === 1) { st.equal(prefix, '', 'prefix is empty'); st.equal(value, obj); From ba24e74dd17931f825adb52f5633e48293b584e1 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Mon, 27 Dec 2021 19:15:57 -0800 Subject: [PATCH 254/278] [Fix] `parse`: ignore `__proto__` keys (#428) --- lib/parse.js | 2 +- test/parse.js | 60 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+), 1 deletion(-) diff --git a/lib/parse.js b/lib/parse.js index 7ef09a85..4bc5e214 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -68,7 +68,7 @@ var parseObject = function parseObject(chain, val, options) { ) { obj = []; obj[index] = parseObject(chain, val, options); - } else { + } else if (cleanRoot !== '__proto__') { obj[cleanRoot] = parseObject(chain, val, options); } } diff --git a/test/parse.js b/test/parse.js index 87e09707..9eeb9e2d 100644 --- a/test/parse.js +++ b/test/parse.js @@ -476,6 +476,66 @@ test('parse()', function (t) { 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); From d9e95298c88ef52d1ca3b3b5d227f02420e02a01 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Sat, 8 Jan 2022 14:34:02 -0800 Subject: [PATCH 255/278] [Dev Deps] update `eslint` --- .eslintignore | 2 -- .eslintrc | 6 +++++- package.json | 2 +- 3 files changed, 6 insertions(+), 4 deletions(-) delete mode 100644 .eslintignore diff --git a/.eslintignore b/.eslintignore deleted file mode 100644 index a60030e3..00000000 --- a/.eslintignore +++ /dev/null @@ -1,2 +0,0 @@ -dist/ -coverage/ diff --git a/.eslintrc b/.eslintrc index 779dc032..6884760e 100644 --- a/.eslintrc +++ b/.eslintrc @@ -3,10 +3,14 @@ "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 }], diff --git a/package.json b/package.json index 81463e09..11f3be61 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,7 @@ "aud": "^1.1.5", "browserify": "^16.5.2", "eclint": "^2.8.1", - "eslint": "^8.5.0", + "eslint": "^8.6.0", "evalmd": "^0.0.19", "for-each": "^0.3.3", "has-symbols": "^1.0.2", From 4cd003291fe3b347884f797e548b58a12150a0e3 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Sun, 9 Jan 2022 09:25:38 -0800 Subject: [PATCH 256/278] v6.9.7 --- CHANGELOG.md | 12 ++++++++++++ dist/qs.js | 22 ++++++++++++++++------ package.json | 2 +- 3 files changed, 29 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d43209c3..12834657 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,15 @@ +## **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 diff --git a/dist/qs.js b/dist/qs.js index 861a6f13..ca3d394f 100644 --- a/dist/qs.js +++ b/dist/qs.js @@ -174,7 +174,7 @@ var parseObject = function (chain, val, options, valuesParsed) { ) { obj = []; obj[index] = leaf; - } else { + } else if (cleanRoot !== '__proto__') { obj[cleanRoot] = leaf; } } @@ -316,6 +316,7 @@ var arrayPrefixGenerators = { }; 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]); @@ -393,6 +394,14 @@ var stringify = function stringify( if (isNonNullishPrimitive(obj) || utils.isBuffer(obj)) { if (encoder) { 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))]; @@ -407,7 +416,7 @@ var stringify = function stringify( var objKeys; if (generateArrayPrefix === 'comma' && isArray(obj)) { // we need to join elements in - objKeys = [{ value: obj.length > 0 ? obj.join(',') || null : undefined }]; + objKeys = [{ value: obj.length > 0 ? obj.join(',') || null : void undefined }]; } else if (isArray(filter)) { objKeys = filter; } else { @@ -415,9 +424,9 @@ var stringify = function stringify( objKeys = sort ? keys.sort(sort) : keys; } - for (var i = 0; i < objKeys.length; ++i) { - var key = objKeys[i]; - var value = typeof key === 'object' && key.value !== undefined ? key.value : obj[key]; + 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 && value === null) { continue; @@ -453,7 +462,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.'); } @@ -755,6 +764,7 @@ var encode = function encode(str, defaultEncoder, charset, kind, format) { 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)] diff --git a/package.json b/package.json index 4bf240cd..3ec7c111 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "qs", "description": "A querystring parser that supports nesting and arrays, with a depth limit", "homepage": "https://github.com/ljharb/qs", - "version": "6.9.6", + "version": "6.9.7", "repository": { "type": "git", "url": "https://github.com/ljharb/qs.git" From 639a381a66845925dba32531dcb9d21c446e9f1f Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Sun, 9 Jan 2022 14:48:18 -0800 Subject: [PATCH 257/278] [meta] do not publish workflow files --- .npmignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.npmignore b/.npmignore index 4a03e5d2..b516beb3 100644 --- a/.npmignore +++ b/.npmignore @@ -13,3 +13,5 @@ coverage/ bower.json component.json + +.github/workflows From 0db55386013a5d92503944ad42022fd8c112c983 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Sun, 9 Jan 2022 14:47:19 -0800 Subject: [PATCH 258/278] v6.8.3 --- CHANGELOG.md | 13 +++++++++ dist/qs.js | 77 +++++++++++++++++++++++++--------------------------- package.json | 2 +- 3 files changed, 51 insertions(+), 41 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 26e48e96..74be6276 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,16 @@ +## **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) diff --git a/dist/qs.js b/dist/qs.js index 869cdae7..3e235e30 100644 --- a/dist/qs.js +++ b/dist/qs.js @@ -188,12 +188,12 @@ var parseObject = function (chain, val, options, valuesParsed) { ) { obj = []; obj[index] = leaf; - } else { + } else if (cleanRoot !== '__proto__') { obj[cleanRoot] = leaf; } } - leaf = obj; // eslint-disable-line no-param-reassign + leaf = obj; } return leaf; @@ -330,6 +330,7 @@ var arrayPrefixGenerators = { }; 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]); @@ -363,7 +364,7 @@ var isNonNullishPrimitive = function isNonNullishPrimitive(v) { || typeof v === 'number' || typeof v === 'boolean' || typeof v === 'symbol' - || typeof v === 'bigint'; // eslint-disable-line valid-typeof + || typeof v === 'bigint'; }; var stringify = function stringify( @@ -401,6 +402,14 @@ var stringify = function stringify( if (isNonNullishPrimitive(obj) || utils.isBuffer(obj)) { if (encoder) { var keyValue = encodeValuesOnly ? prefix : encoder(prefix, defaults.encoder, charset); + 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)); + } + return [formatter(keyValue) + '=' + valuesJoined]; + } return [formatter(keyValue) + '=' + formatter(encoder(obj, defaults.encoder, charset))]; } return [formatter(prefix) + '=' + formatter(String(obj))]; @@ -420,46 +429,33 @@ var stringify = function stringify( 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 + ']'); + + pushToArray(values, stringify( + value, + keyPrefix, + generateArrayPrefix, + strictNullHandling, + skipNulls, + encoder, + filter, + sort, + allowDots, + serializeDate, + formatter, + encodeValuesOnly, + charset + )); } return values; @@ -470,7 +466,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.'); } @@ -766,6 +762,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)] diff --git a/package.json b/package.json index f1e575d1..d7d2867a 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "qs", "description": "A querystring parser that supports nesting and arrays, with a depth limit", "homepage": "https://github.com/ljharb/qs", - "version": "6.8.2", + "version": "6.8.3", "repository": { "type": "git", "url": "https://github.com/ljharb/qs.git" From 5d55ddc09cc0a37590fc467db263c8beedc6ba25 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Sun, 9 Jan 2022 14:48:18 -0800 Subject: [PATCH 259/278] [meta] do not publish workflow files --- .npmignore | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.npmignore b/.npmignore index 21aa9da5..b516beb3 100644 --- a/.npmignore +++ b/.npmignore @@ -10,3 +10,8 @@ npm-shrinkwrap.json .nyc_output/ coverage/ + +bower.json +component.json + +.github/workflows From 5a8c870a844572bba3fa0861fbeaf76ecf2e88de Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Sun, 9 Jan 2022 14:48:18 -0800 Subject: [PATCH 260/278] [meta] do not publish workflow files --- .npmignore | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.npmignore b/.npmignore index ee474f22..b516beb3 100644 --- a/.npmignore +++ b/.npmignore @@ -8,4 +8,10 @@ yarn.lock package-lock.json npm-shrinkwrap.json +.nyc_output/ +coverage/ + +bower.json +component.json + .github/workflows From 45143b6e0d32c2ef7b78d560cf14d6d5578fc70f Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Sun, 9 Jan 2022 22:29:20 -0800 Subject: [PATCH 261/278] [Tests] use `nyc` for coverage --- .editorconfig | 3 +++ .nycrc | 13 +++++++++++++ package.json | 7 +++---- 3 files changed, 19 insertions(+), 4 deletions(-) create mode 100644 .nycrc diff --git a/.editorconfig b/.editorconfig index ed16d717..7eaad66e 100644 --- a/.editorconfig +++ b/.editorconfig @@ -32,3 +32,6 @@ indent_size = 2 [LICENSE] indent_size = 2 max_line_length = off + +[.nycrc] +indent_style = tab diff --git a/.nycrc b/.nycrc new file mode 100644 index 00000000..1d57cabe --- /dev/null +++ b/.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/package.json b/package.json index 3d04c737..e892df2d 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,6 @@ "devDependencies": { "@ljharb/eslint-config": "^20.1.0", "browserify": "^16.5.2", - "covert": "^1.1.1", "eclint": "^2.8.1", "eslint": "^8.6.0", "evalmd": "^0.0.19", @@ -41,6 +40,7 @@ "iconv-lite": "^0.5.1", "in-publish": "^2.0.1", "mkdirp": "^0.5.4", + "nyc": "^10.3.2", "object-inspect": "^1.12.0", "qs-iconv": "^1.0.4", "safe-publish-latest": "^2.0.0", @@ -51,13 +51,12 @@ "prepublishOnly": "safe-publish-latest && npm run dist", "prepublish": "not-in-publish || npm run prepublishOnly", "pretest": "npm run --silent readme && npm run --silent lint", - "test": "npm run --silent coverage", - "tests-only": "node test", + "test": "npm run --silent tests-only", + "tests-only": "nyc tape 'test/**/*.js'", "posttest": "npx aud --production", "readme": "evalmd README.md", "postlint": "eclint check $(git ls-files | xargs find 2> /dev/null | grep -vE 'node_modules|\\.git')", "lint": "eslint lib/*.js test/*.js", - "coverage": "covert test", "dist": "mkdirp dist && browserify --standalone Qs lib/index.js > dist/qs.js" }, "license": "BSD-3-Clause" From 834389afb51ac8cc03a22a0c76604c65776dc468 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Sun, 9 Jan 2022 22:31:44 -0800 Subject: [PATCH 262/278] v6.7.3 --- CHANGELOG.md | 13 +++++++++++++ component.json | 4 ++-- dist/qs.js | 23 +++++++++++++++++------ package.json | 2 +- 4 files changed, 33 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 47e0e93a..083e220a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,16 @@ +## **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) diff --git a/component.json b/component.json index e686cd24..626dc74c 100644 --- a/component.json +++ b/component.json @@ -1,8 +1,8 @@ { "name": "qs", - "repository": "hapijs/qs", + "repository": "ljharb/qs", "description": "query-string parser / stringifier with nesting support", - "version": "6.5.0", + "version": "6.7.3", "keywords": ["querystring", "query", "parser"], "main": "lib/index.js", "scripts": [ diff --git a/dist/qs.js b/dist/qs.js index 48bf862f..9120f431 100644 --- a/dist/qs.js +++ b/dist/qs.js @@ -188,7 +188,7 @@ var parseObject = function (chain, val, options, valuesParsed) { ) { obj = []; obj[index] = leaf; - } else { + } else if (cleanRoot !== '__proto__') { obj[cleanRoot] = leaf; } } @@ -329,6 +329,7 @@ var arrayPrefixGenerators = { }; 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]); @@ -392,6 +393,14 @@ var stringify = function stringify( if (typeof obj === 'string' || typeof obj === 'number' || typeof obj === 'boolean' || utils.isBuffer(obj)) { if (encoder) { var keyValue = encodeValuesOnly ? prefix : encoder(prefix, defaults.encoder, charset); + 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)); + } + return [formatter(keyValue) + '=' + valuesJoined]; + } return [formatter(keyValue) + '=' + formatter(encoder(obj, defaults.encoder, charset))]; } return [formatter(prefix) + '=' + formatter(String(obj))]; @@ -411,8 +420,9 @@ var stringify = function stringify( 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) { continue; @@ -420,7 +430,7 @@ var stringify = function stringify( if (isArray(obj)) { pushToArray(values, stringify( - obj[key], + value, typeof generateArrayPrefix === 'function' ? generateArrayPrefix(prefix, key) : prefix, generateArrayPrefix, strictNullHandling, @@ -436,7 +446,7 @@ var stringify = function stringify( )); } else { pushToArray(values, stringify( - obj[key], + value, prefix + (allowDots ? '.' + key : '[' + key + ']'), generateArrayPrefix, strictNullHandling, @@ -461,7 +471,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.'); } @@ -752,6 +762,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)] diff --git a/package.json b/package.json index e892df2d..58e44965 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "qs", "description": "A querystring parser that supports nesting and arrays, with a depth limit", "homepage": "https://github.com/ljharb/qs", - "version": "6.7.2", + "version": "6.7.3", "repository": { "type": "git", "url": "https://github.com/ljharb/qs.git" From 4cc653c08c583c0b39e2eea0bf1cd2226ac5ec51 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Sun, 9 Jan 2022 22:46:18 -0800 Subject: [PATCH 263/278] v6.6.1 --- CHANGELOG.md | 26 ++++++ dist/qs.js | 223 +++++++++++++++++++++++++++++++-------------------- package.json | 2 +- 3 files changed, 161 insertions(+), 90 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e4696054..aaadece7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,29 @@ +## **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) diff --git a/dist/qs.js b/dist/qs.js index b9482991..262c7fa6 100644 --- a/dist/qs.js +++ b/dist/qs.js @@ -4,21 +4,29 @@ var replace = String.prototype.replace; var percentTwenties = /%20/g; -module.exports = { - 'default': 'RFC3986', - formatters: { - RFC1738: function (value) { - return replace.call(value, percentTwenties, '+'); - }, - RFC3986: function (value) { - return value; - } - }, +var util = require('./utils'); + +var Format = { RFC1738: 'RFC1738', RFC3986: 'RFC3986' }; -},{}],2:[function(require,module,exports){ +module.exports = util.assign( + { + 'default': Format.RFC3986, + formatters: { + RFC1738: function (value) { + return replace.call(value, percentTwenties, '+'); + }, + RFC3986: function (value) { + return String(value); + } + } + }, + Format +); + +},{"./utils":5}],2:[function(require,module,exports){ 'use strict'; var stringify = require('./stringify'); @@ -149,7 +157,7 @@ var parseObject = function (chain, val, options) { ) { obj = []; obj[index] = leaf; - } else { + } else if (cleanRoot !== '__proto__') { obj[cleanRoot] = leaf; } } @@ -214,31 +222,40 @@ var parseKeys = function parseQueryStringKeys(givenKey, val, options) { return parseObject(keys, val, options); }; -module.exports = function (str, opts) { - var options = opts ? utils.assign({}, opts) : {}; +var normalizeParseOptions = function normalizeParseOptions(opts) { + if (!opts) { + return defaults; + } - if (options.decoder !== null && options.decoder !== undefined && typeof options.decoder !== 'function') { + if (opts.decoder !== null && opts.decoder !== undefined && typeof opts.decoder !== 'function') { throw new TypeError('Decoder has to be a function.'); } - options.ignoreQueryPrefix = options.ignoreQueryPrefix === true; - options.delimiter = typeof options.delimiter === 'string' || utils.isRegExp(options.delimiter) ? options.delimiter : defaults.delimiter; - options.depth = typeof options.depth === 'number' ? options.depth : defaults.depth; - options.arrayLimit = typeof options.arrayLimit === 'number' ? options.arrayLimit : defaults.arrayLimit; - options.parseArrays = options.parseArrays !== false; - options.decoder = typeof options.decoder === 'function' ? options.decoder : defaults.decoder; - options.allowDots = typeof options.allowDots === 'undefined' ? defaults.allowDots : !!options.allowDots; - options.plainObjects = typeof options.plainObjects === 'boolean' ? options.plainObjects : defaults.plainObjects; - options.allowPrototypes = typeof options.allowPrototypes === 'boolean' ? options.allowPrototypes : defaults.allowPrototypes; - options.parameterLimit = typeof options.parameterLimit === 'number' ? options.parameterLimit : defaults.parameterLimit; - options.strictNullHandling = typeof options.strictNullHandling === 'boolean' ? options.strictNullHandling : defaults.strictNullHandling; - - if (typeof options.charset !== 'undefined' && options.charset !== 'utf-8' && options.charset !== 'iso-8859-1') { + 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'); } - if (typeof options.charset === 'undefined') { - options.charset = defaults.charset; - } + 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, + arrayLimit: typeof opts.arrayLimit === 'number' ? opts.arrayLimit : defaults.arrayLimit, + charset: charset, + charsetSentinel: typeof opts.charsetSentinel === 'boolean' ? opts.charsetSentinel : defaults.charsetSentinel, + 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, + 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, + strictNullHandling: typeof opts.strictNullHandling === 'boolean' ? opts.strictNullHandling : defaults.strictNullHandling + }; +}; + +module.exports = function (str, opts) { + var options = normalizeParseOptions(opts); if (str === '' || str === null || typeof str === 'undefined') { return options.plainObjects ? Object.create(null) : {}; @@ -264,15 +281,16 @@ module.exports = function (str, opts) { 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 + '[]'; }, - 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; } }; @@ -285,6 +303,7 @@ var pushToArray = function (arr, valueOrArray) { var toISO = Date.prototype.toISOString; +var defaultFormat = formats['default']; var defaults = { addQueryPrefix: false, allowDots: false, @@ -294,16 +313,18 @@ var defaults = { encode: true, encoder: utils.encode, encodeValuesOnly: false, + 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 stringify = function stringify( object, prefix, generateArrayPrefix, @@ -348,7 +369,7 @@ var stringify = function stringify( // eslint-disable-line func-name-matching } var objKeys; - if (Array.isArray(filter)) { + if (isArray(filter)) { objKeys = filter; } else { var keys = Object.keys(obj); @@ -362,7 +383,7 @@ var stringify = function stringify( // eslint-disable-line func-name-matching continue; } - if (Array.isArray(obj)) { + if (isArray(obj)) { pushToArray(values, stringify( obj[key], generateArrayPrefix(prefix, key), @@ -400,41 +421,63 @@ var stringify = function stringify( // eslint-disable-line func-name-matching return values; }; -module.exports = function (object, opts) { - var obj = object; - var options = opts ? utils.assign({}, opts) : {}; +var normalizeStringifyOptions = function normalizeStringifyOptions(opts) { + if (!opts) { + return defaults; + } - if (options.encoder !== null && options.encoder !== undefined && typeof options.encoder !== 'function') { + if (opts.encoder !== null && typeof opts.encoder !== 'undefined' && typeof opts.encoder !== 'function') { throw new TypeError('Encoder has to be a function.'); } - var delimiter = typeof options.delimiter === 'undefined' ? defaults.delimiter : options.delimiter; - var strictNullHandling = typeof options.strictNullHandling === 'boolean' ? options.strictNullHandling : defaults.strictNullHandling; - var skipNulls = typeof options.skipNulls === 'boolean' ? options.skipNulls : defaults.skipNulls; - var encode = typeof options.encode === 'boolean' ? options.encode : defaults.encode; - var encoder = typeof options.encoder === 'function' ? options.encoder : defaults.encoder; - var sort = typeof options.sort === 'function' ? options.sort : null; - var allowDots = typeof options.allowDots === 'undefined' ? defaults.allowDots : !!options.allowDots; - var serializeDate = typeof options.serializeDate === 'function' ? options.serializeDate : defaults.serializeDate; - var encodeValuesOnly = typeof options.encodeValuesOnly === 'boolean' ? options.encodeValuesOnly : defaults.encodeValuesOnly; - var charset = options.charset || defaults.charset; - if (typeof options.charset !== 'undefined' && options.charset !== 'utf-8' && options.charset !== 'iso-8859-1') { - throw new Error('The charset option must be either utf-8, iso-8859-1, or undefined'); + 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'); } - if (typeof options.format === 'undefined') { - options.format = formats['default']; - } else if (!Object.prototype.hasOwnProperty.call(formats.formatters, options.format)) { - throw new TypeError('Unknown format option provided.'); - } - var formatter = formats.formatters[options.format]; + 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; + } + + return { + addQueryPrefix: typeof opts.addQueryPrefix === 'boolean' ? opts.addQueryPrefix : defaults.addQueryPrefix, + allowDots: typeof opts.allowDots === 'undefined' ? defaults.allowDots : !!opts.allowDots, + charset: charset, + charsetSentinel: typeof opts.charsetSentinel === 'boolean' ? opts.charsetSentinel : defaults.charsetSentinel, + delimiter: typeof opts.delimiter === 'undefined' ? defaults.delimiter : opts.delimiter, + encode: typeof opts.encode === 'boolean' ? opts.encode : defaults.encode, + encoder: typeof opts.encoder === 'function' ? opts.encoder : defaults.encoder, + encodeValuesOnly: typeof opts.encodeValuesOnly === 'boolean' ? opts.encodeValuesOnly : defaults.encodeValuesOnly, + filter: filter, + 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 (Array.isArray(options.filter)) { + } else if (isArray(options.filter)) { filter = options.filter; objKeys = filter; } @@ -446,10 +489,10 @@ module.exports = function (object, opts) { } var arrayFormat; - if (options.arrayFormat in arrayPrefixGenerators) { - arrayFormat = options.arrayFormat; - } else if ('indices' in options) { - arrayFormat = options.indices ? 'indices' : 'repeat'; + if (opts && opts.arrayFormat in arrayPrefixGenerators) { + arrayFormat = opts.arrayFormat; + } else if (opts && 'indices' in opts) { + arrayFormat = opts.indices ? 'indices' : 'repeat'; } else { arrayFormat = 'indices'; } @@ -460,38 +503,38 @@ module.exports = function (object, opts) { objKeys = Object.keys(obj); } - if (sort) { - objKeys.sort(sort); + if (options.sort) { + objKeys.sort(options.sort); } for (var i = 0; i < objKeys.length; ++i) { var key = objKeys[i]; - if (skipNulls && obj[key] === null) { + if (options.skipNulls && obj[key] === null) { continue; } pushToArray(keys, stringify( obj[key], key, generateArrayPrefix, - strictNullHandling, - skipNulls, - encode ? encoder : null, - filter, - sort, - allowDots, - serializeDate, - formatter, - encodeValuesOnly, - charset + options.strictNullHandling, + options.skipNulls, + options.encode ? options.encoder : null, + options.filter, + options.sort, + options.allowDots, + options.serializeDate, + options.formatter, + options.encodeValuesOnly, + options.charset )); } - var joined = keys.join(delimiter); + var joined = keys.join(options.delimiter); var prefix = options.addQueryPrefix === true ? '?' : ''; if (options.charsetSentinel) { - if (charset === 'iso-8859-1') { + if (options.charset === 'iso-8859-1') { // encodeURIComponent('✓'), the "numeric entity" representation of a checkmark prefix += 'utf8=%26%2310003%3B&'; } else { @@ -507,6 +550,7 @@ module.exports = function (object, opts) { 'use strict'; var has = Object.prototype.hasOwnProperty; +var isArray = Array.isArray; var hexTable = (function () { var array = []; @@ -522,7 +566,7 @@ var compactQueue = function compactQueue(queue) { var item = queue.pop(); var obj = item.obj[item.prop]; - if (Array.isArray(obj)) { + if (isArray(obj)) { var compacted = []; for (var j = 0; j < obj.length; ++j) { @@ -553,9 +597,9 @@ var merge = function merge(target, source, options) { } if (typeof source !== 'object') { - if (Array.isArray(target)) { + if (isArray(target)) { target.push(source); - } else if (typeof target === 'object') { + } else if (target && typeof target === 'object') { if ((options && (options.plainObjects || options.allowPrototypes)) || !has.call(Object.prototype, source)) { target[source] = true; } @@ -566,20 +610,21 @@ var merge = function merge(target, source, options) { return target; } - if (typeof target !== 'object') { + if (!target || typeof target !== 'object') { return [target].concat(source); } var mergeTarget = target; - if (Array.isArray(target) && !Array.isArray(source)) { + if (isArray(target) && !isArray(source)) { mergeTarget = arrayToObject(target, options); } - if (Array.isArray(target) && Array.isArray(source)) { + if (isArray(target) && isArray(source)) { source.forEach(function (item, i) { if (has.call(target, i)) { - if (target[i] && typeof target[i] === 'object') { - target[i] = merge(target[i], item, options); + var targetItem = target[i]; + if (targetItem && typeof targetItem === 'object' && item && typeof item === 'object') { + target[i] = merge(targetItem, item, options); } else { target.push(item); } @@ -710,7 +755,7 @@ var isRegExp = function isRegExp(obj) { }; var isBuffer = function isBuffer(obj) { - if (obj === null || typeof obj === 'undefined') { + if (!obj || typeof obj !== 'object') { return false; } diff --git a/package.json b/package.json index e652b08b..7c76067f 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "qs", "description": "A querystring parser that supports nesting and arrays, with a depth limit", "homepage": "https://github.com/ljharb/qs", - "version": "6.6.0", + "version": "6.6.1", "repository": { "type": "git", "url": "https://github.com/ljharb/qs.git" From 298bfa55d6db00ddea78dd0333509aadf9bb3077 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Sun, 9 Jan 2022 23:10:19 -0800 Subject: [PATCH 264/278] v6.5.3 --- CHANGELOG.md | 24 ++++++++++++++++++++ component.json | 4 ++-- dist/qs.js | 60 +++++++++++++++++++++++++++++--------------------- package.json | 2 +- 4 files changed, 62 insertions(+), 28 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fe523209..849c92ea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,27 @@ +## **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) diff --git a/component.json b/component.json index e686cd24..dd13558f 100644 --- a/component.json +++ b/component.json @@ -1,8 +1,8 @@ { "name": "qs", - "repository": "hapijs/qs", + "repository": "ljharb/qs", "description": "query-string parser / stringifier with nesting support", - "version": "6.5.0", + "version": "6.5.3", "keywords": ["querystring", "query", "parser"], "main": "lib/index.js", "scripts": [ diff --git a/dist/qs.js b/dist/qs.js index ecf7ba44..9f54e3fb 100644 --- a/dist/qs.js +++ b/dist/qs.js @@ -11,7 +11,7 @@ module.exports = { return replace.call(value, percentTwenties, '+'); }, RFC3986: function (value) { - return value; + return String(value); } }, RFC1738: 'RFC1738', @@ -87,14 +87,15 @@ var parseObject = function (chain, val, options) { var obj; var root = chain[i]; - if (root === '[]') { - obj = []; - obj = obj.concat(leaf); + if (root === '[]' && options.parseArrays) { + obj = [].concat(leaf); } else { obj = options.plainObjects ? Object.create(null) : {}; var cleanRoot = root.charAt(0) === '[' && root.charAt(root.length - 1) === ']' ? root.slice(1, -1) : root; var index = parseInt(cleanRoot, 10); - if ( + if (!options.parseArrays && cleanRoot === '') { + obj = { 0: leaf }; + } else if ( !isNaN(index) && root !== cleanRoot && String(index) === cleanRoot @@ -103,7 +104,7 @@ var parseObject = function (chain, val, options) { ) { obj = []; obj[index] = leaf; - } else { + } else if (cleanRoot !== '__proto__') { obj[cleanRoot] = leaf; } } @@ -214,17 +215,23 @@ var utils = require('./utils'); var formats = require('./formats'); var arrayPrefixGenerators = { - brackets: function brackets(prefix) { // eslint-disable-line func-name-matching + brackets: function brackets(prefix) { return prefix + '[]'; }, - 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 push = Array.prototype.push; +var pushToArray = function (arr, valueOrArray) { + push.apply(arr, isArray(valueOrArray) ? valueOrArray : [valueOrArray]); +}; + var toISO = Date.prototype.toISOString; var defaults = { @@ -232,14 +239,14 @@ var defaults = { encode: true, encoder: utils.encode, encodeValuesOnly: 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 stringify = function stringify( object, prefix, generateArrayPrefix, @@ -258,7 +265,9 @@ var stringify = function stringify( // eslint-disable-line func-name-matching obj = filter(prefix, obj); } else if (obj instanceof Date) { obj = serializeDate(obj); - } else if (obj === null) { + } + + if (obj === null) { if (strictNullHandling) { return encoder && !encodeValuesOnly ? encoder(prefix, defaults.encoder) : prefix; } @@ -281,7 +290,7 @@ var stringify = function stringify( // eslint-disable-line func-name-matching } var objKeys; - if (Array.isArray(filter)) { + if (isArray(filter)) { objKeys = filter; } else { var keys = Object.keys(obj); @@ -295,8 +304,8 @@ var stringify = function stringify( // eslint-disable-line func-name-matching continue; } - if (Array.isArray(obj)) { - values = values.concat(stringify( + if (isArray(obj)) { + pushToArray(values, stringify( obj[key], generateArrayPrefix(prefix, key), generateArrayPrefix, @@ -311,7 +320,7 @@ var stringify = function stringify( // eslint-disable-line func-name-matching encodeValuesOnly )); } else { - values = values.concat(stringify( + pushToArray(values, stringify( obj[key], prefix + (allowDots ? '.' + key : '[' + key + ']'), generateArrayPrefix, @@ -335,7 +344,7 @@ module.exports = function (object, opts) { var obj = object; var options = opts ? utils.assign({}, opts) : {}; - if (options.encoder !== null && options.encoder !== undefined && typeof options.encoder !== 'function') { + if (options.encoder !== null && typeof options.encoder !== 'undefined' && typeof options.encoder !== 'function') { throw new TypeError('Encoder has to be a function.'); } @@ -360,7 +369,7 @@ module.exports = function (object, opts) { if (typeof options.filter === 'function') { filter = options.filter; obj = filter('', obj); - } else if (Array.isArray(options.filter)) { + } else if (isArray(options.filter)) { filter = options.filter; objKeys = filter; } @@ -396,8 +405,7 @@ module.exports = function (object, opts) { if (skipNulls && obj[key] === null) { continue; } - - keys = keys.concat(stringify( + pushToArray(keys, stringify( obj[key], key, generateArrayPrefix, @@ -475,8 +483,8 @@ var merge = function merge(target, source, options) { if (typeof source !== 'object') { if (Array.isArray(target)) { target.push(source); - } else if (typeof target === 'object') { - if (options.plainObjects || options.allowPrototypes || !has.call(Object.prototype, source)) { + } else if (target && typeof target === 'object') { + if ((options && (options.plainObjects || options.allowPrototypes)) || !has.call(Object.prototype, source)) { target[source] = true; } } else { @@ -486,7 +494,7 @@ var merge = function merge(target, source, options) { return target; } - if (typeof target !== 'object') { + if (!target || typeof target !== 'object') { return [target].concat(source); } @@ -498,8 +506,9 @@ var merge = function merge(target, source, options) { if (Array.isArray(target) && Array.isArray(source)) { source.forEach(function (item, i) { if (has.call(target, i)) { - if (target[i] && typeof target[i] === 'object') { - target[i] = merge(target[i], item, options); + var targetItem = target[i]; + if (targetItem && typeof targetItem === 'object' && item && typeof item === 'object') { + target[i] = merge(targetItem, item, options); } else { target.push(item); } @@ -580,6 +589,7 @@ var encode = function encode(str) { 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)] diff --git a/package.json b/package.json index a3e8bda4..cc651b82 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "qs", "description": "A querystring parser that supports nesting and arrays, with a depth limit", "homepage": "https://github.com/ljharb/qs", - "version": "6.5.2", + "version": "6.5.3", "repository": { "type": "git", "url": "https://github.com/ljharb/qs.git" From 486aa46547b4e878d6e87183de95dd26d46fb020 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Mon, 10 Jan 2022 14:33:08 -0800 Subject: [PATCH 265/278] v6.4.1 --- CHANGELOG.md | 21 +++++++++++++++ component.json | 2 +- dist/qs.js | 70 ++++++++++++++++++++++++++++---------------------- package.json | 2 +- 4 files changed, 63 insertions(+), 32 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 85e69b0a..30f10c74 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,24 @@ +## **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) diff --git a/component.json b/component.json index f15c2133..7867ed1c 100644 --- a/component.json +++ b/component.json @@ -2,7 +2,7 @@ "name": "qs", "repository": "hapijs/qs", "description": "query-string parser / stringifier with nesting support", - "version": "6.4.0", + "version": "6.4.1", "keywords": ["querystring", "query", "parser"], "main": "lib/index.js", "scripts": [ diff --git a/dist/qs.js b/dist/qs.js index 483714d8..b2393194 100644 --- a/dist/qs.js +++ b/dist/qs.js @@ -11,7 +11,7 @@ module.exports = { return replace.call(value, percentTwenties, '+'); }, RFC3986: function (value) { - return value; + return String(value); } }, RFC1738: 'RFC1738', @@ -102,7 +102,7 @@ var parseObject = function parseObjectRecursive(chain, val, options) { ) { obj = []; obj[index] = parseObject(chain, val, options); - } else { + } else if (cleanRoot !== '__proto__') { obj[cleanRoot] = parseObject(chain, val, options); } } @@ -132,8 +132,7 @@ var parseKeys = function parseQueryStringKeys(givenKey, val, options) { var keys = []; if (parent) { - // If we aren't using plain objects, optionally prefix keys - // that would overwrite object prototype properties + // If we aren't using plain objects, optionally prefix keys that would overwrite object prototype properties if (!options.plainObjects && has.call(Object.prototype, parent)) { if (!options.allowPrototypes) { return; @@ -209,17 +208,23 @@ var utils = require('./utils'); var formats = require('./formats'); var arrayPrefixGenerators = { - brackets: function brackets(prefix) { // eslint-disable-line func-name-matching + brackets: function brackets(prefix) { return prefix + '[]'; }, - 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 push = Array.prototype.push; +var pushToArray = function (arr, valueOrArray) { + push.apply(arr, isArray(valueOrArray) ? valueOrArray : [valueOrArray]); +}; + var toISO = Date.prototype.toISOString; var defaults = { @@ -227,14 +232,14 @@ var defaults = { encode: true, encoder: utils.encode, encodeValuesOnly: 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 stringify = function stringify( object, prefix, generateArrayPrefix, @@ -253,7 +258,9 @@ var stringify = function stringify( // eslint-disable-line func-name-matching obj = filter(prefix, obj); } else if (obj instanceof Date) { obj = serializeDate(obj); - } else if (obj === null) { + } + + if (obj === null) { if (strictNullHandling) { return encoder && !encodeValuesOnly ? encoder(prefix) : prefix; } @@ -276,7 +283,7 @@ var stringify = function stringify( // eslint-disable-line func-name-matching } var objKeys; - if (Array.isArray(filter)) { + if (isArray(filter)) { objKeys = filter; } else { var keys = Object.keys(obj); @@ -290,8 +297,8 @@ var stringify = function stringify( // eslint-disable-line func-name-matching continue; } - if (Array.isArray(obj)) { - values = values.concat(stringify( + if (isArray(obj)) { + pushToArray(values, stringify( obj[key], generateArrayPrefix(prefix, key), generateArrayPrefix, @@ -306,7 +313,7 @@ var stringify = function stringify( // eslint-disable-line func-name-matching encodeValuesOnly )); } else { - values = values.concat(stringify( + pushToArray(values, stringify( obj[key], prefix + (allowDots ? '.' + key : '[' + key + ']'), generateArrayPrefix, @@ -330,7 +337,7 @@ module.exports = function (object, opts) { var obj = object; var options = opts || {}; - if (options.encoder !== null && options.encoder !== undefined && typeof options.encoder !== 'function') { + if (options.encoder !== null && typeof options.encoder !== 'undefined' && typeof options.encoder !== 'function') { throw new TypeError('Encoder has to be a function.'); } @@ -344,7 +351,7 @@ module.exports = function (object, opts) { var serializeDate = typeof options.serializeDate === 'function' ? options.serializeDate : defaults.serializeDate; var encodeValuesOnly = typeof options.encodeValuesOnly === 'boolean' ? options.encodeValuesOnly : defaults.encodeValuesOnly; if (typeof options.format === 'undefined') { - options.format = formats.default; + options.format = formats['default']; } else if (!Object.prototype.hasOwnProperty.call(formats.formatters, options.format)) { throw new TypeError('Unknown format option provided.'); } @@ -355,7 +362,7 @@ module.exports = function (object, opts) { if (typeof options.filter === 'function') { filter = options.filter; obj = filter('', obj); - } else if (Array.isArray(options.filter)) { + } else if (isArray(options.filter)) { filter = options.filter; objKeys = filter; } @@ -391,8 +398,7 @@ module.exports = function (object, opts) { if (skipNulls && obj[key] === null) { continue; } - - keys = keys.concat(stringify( + pushToArray(keys, stringify( obj[key], key, generateArrayPrefix, @@ -444,8 +450,8 @@ exports.merge = function (target, source, options) { if (typeof source !== 'object') { if (Array.isArray(target)) { target.push(source); - } else if (typeof target === 'object') { - if (options.plainObjects || options.allowPrototypes || !has.call(Object.prototype, source)) { + } else if (target && typeof target === 'object') { + if ((options && (options.plainObjects || options.allowPrototypes)) || !has.call(Object.prototype, source)) { target[source] = true; } } else { @@ -455,7 +461,7 @@ exports.merge = function (target, source, options) { return target; } - if (typeof target !== 'object') { + if (!target || typeof target !== 'object') { return [target].concat(source); } @@ -513,13 +519,13 @@ exports.encode = function (str) { var c = string.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 + 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 ) { out += string.charAt(i); continue; @@ -542,7 +548,11 @@ exports.encode = function (str) { i += 1; c = 0x10000 + (((c & 0x3FF) << 10) | (string.charCodeAt(i) & 0x3FF)); - out += hexTable[0xF0 | (c >> 18)] + hexTable[0x80 | ((c >> 12) & 0x3F)] + hexTable[0x80 | ((c >> 6) & 0x3F)] + hexTable[0x80 | (c & 0x3F)]; // eslint-disable-line max-len + /* eslint operator-linebreak: [2, "before"] */ + out += hexTable[0xF0 | (c >> 18)] + + hexTable[0x80 | ((c >> 12) & 0x3F)] + + hexTable[0x80 | ((c >> 6) & 0x3F)] + + hexTable[0x80 | (c & 0x3F)]; } return out; diff --git a/package.json b/package.json index 1cb81e5d..422955cd 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "qs", "description": "A querystring parser that supports nesting and arrays, with a depth limit", "homepage": "https://github.com/ljharb/qs", - "version": "6.4.0", + "version": "6.4.1", "repository": { "type": "git", "url": "https://github.com/ljharb/qs.git" From ff235b4ca81f82728b745b71fbd4bad173535305 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Mon, 10 Jan 2022 14:55:08 -0800 Subject: [PATCH 266/278] v6.3.3 --- CHANGELOG.md | 20 ++++++++++++++ component.json | 2 +- dist/qs.js | 72 ++++++++++++++++++++++++++++---------------------- package.json | 2 +- 4 files changed, 63 insertions(+), 33 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 76e629f4..185aaef7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,23 @@ +## **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` diff --git a/component.json b/component.json index a05aabd1..f0c03dca 100644 --- a/component.json +++ b/component.json @@ -2,7 +2,7 @@ "name": "qs", "repository": "hapijs/qs", "description": "query-string parser / stringifier with nesting support", - "version": "6.3.2", + "version": "6.3.3", "keywords": ["querystring", "query", "parser"], "main": "lib/index.js", "scripts": [ diff --git a/dist/qs.js b/dist/qs.js index bf895a9e..75178028 100644 --- a/dist/qs.js +++ b/dist/qs.js @@ -11,7 +11,7 @@ module.exports = { return replace.call(value, percentTwenties, '+'); }, RFC3986: function (value) { - return value; + return String(value); } }, RFC1738: 'RFC1738', @@ -102,7 +102,7 @@ var parseObject = function parseObjectRecursive(chain, val, options) { ) { obj = []; obj[index] = parseObject(chain, val, options); - } else { + } else if (cleanRoot !== '__proto__') { obj[cleanRoot] = parseObject(chain, val, options); } } @@ -132,8 +132,7 @@ var parseKeys = function parseQueryStringKeys(givenKey, val, options) { var keys = []; if (parent) { - // If we aren't using plain objects, optionally prefix keys - // that would overwrite object prototype properties + // If we aren't using plain objects, optionally prefix keys that would overwrite object prototype properties if (!options.plainObjects && has.call(Object.prototype, parent)) { if (!options.allowPrototypes) { return; @@ -209,31 +208,37 @@ var utils = require('./utils'); var formats = require('./formats'); var arrayPrefixGenerators = { - brackets: function brackets(prefix) { // eslint-disable-line func-name-matching + brackets: function brackets(prefix) { return prefix + '[]'; }, - 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 push = Array.prototype.push; +var pushToArray = function (arr, valueOrArray) { + push.apply(arr, isArray(valueOrArray) ? valueOrArray : [valueOrArray]); +}; + var toISO = Date.prototype.toISOString; var defaults = { delimiter: '&', encode: true, encoder: utils.encode, - 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 stringify = function stringify( object, prefix, generateArrayPrefix, @@ -251,7 +256,9 @@ var stringify = function stringify( // eslint-disable-line func-name-matching obj = filter(prefix, obj); } else if (obj instanceof Date) { obj = serializeDate(obj); - } else if (obj === null) { + } + + if (obj === null) { if (strictNullHandling) { return encoder ? encoder(prefix) : prefix; } @@ -273,7 +280,7 @@ var stringify = function stringify( // eslint-disable-line func-name-matching } var objKeys; - if (Array.isArray(filter)) { + if (isArray(filter)) { objKeys = filter; } else { var keys = Object.keys(obj); @@ -287,8 +294,8 @@ var stringify = function stringify( // eslint-disable-line func-name-matching continue; } - if (Array.isArray(obj)) { - values = values.concat(stringify( + if (isArray(obj)) { + pushToArray(values, stringify( obj[key], generateArrayPrefix(prefix, key), generateArrayPrefix, @@ -302,7 +309,7 @@ var stringify = function stringify( // eslint-disable-line func-name-matching formatter )); } else { - values = values.concat(stringify( + pushToArray(values, stringify( obj[key], prefix + (allowDots ? '.' + key : '[' + key + ']'), generateArrayPrefix, @@ -325,7 +332,7 @@ module.exports = function (object, opts) { var obj = object; var options = opts || {}; - if (options.encoder !== null && options.encoder !== undefined && typeof options.encoder !== 'function') { + if (options.encoder !== null && typeof options.encoder !== 'undefined' && typeof options.encoder !== 'function') { throw new TypeError('Encoder has to be a function.'); } @@ -333,12 +340,12 @@ module.exports = function (object, opts) { var strictNullHandling = typeof options.strictNullHandling === 'boolean' ? options.strictNullHandling : defaults.strictNullHandling; var skipNulls = typeof options.skipNulls === 'boolean' ? options.skipNulls : defaults.skipNulls; var encode = typeof options.encode === 'boolean' ? options.encode : defaults.encode; - var encoder = encode ? (typeof options.encoder === 'function' ? options.encoder : defaults.encoder) : null; + var encoder = encode ? typeof options.encoder === 'function' ? options.encoder : defaults.encoder : null; var sort = typeof options.sort === 'function' ? options.sort : null; var allowDots = typeof options.allowDots === 'undefined' ? false : options.allowDots; var serializeDate = typeof options.serializeDate === 'function' ? options.serializeDate : defaults.serializeDate; if (typeof options.format === 'undefined') { - options.format = formats.default; + options.format = formats['default']; } else if (!Object.prototype.hasOwnProperty.call(formats.formatters, options.format)) { throw new TypeError('Unknown format option provided.'); } @@ -349,7 +356,7 @@ module.exports = function (object, opts) { if (typeof options.filter === 'function') { filter = options.filter; obj = filter('', obj); - } else if (Array.isArray(options.filter)) { + } else if (isArray(options.filter)) { filter = options.filter; objKeys = filter; } @@ -385,8 +392,7 @@ module.exports = function (object, opts) { if (skipNulls && obj[key] === null) { continue; } - - keys = keys.concat(stringify( + pushToArray(keys, stringify( obj[key], key, generateArrayPrefix, @@ -437,8 +443,8 @@ exports.merge = function (target, source, options) { if (typeof source !== 'object') { if (Array.isArray(target)) { target.push(source); - } else if (typeof target === 'object') { - if (options.plainObjects || options.allowPrototypes || !has.call(Object.prototype, source)) { + } else if (target && typeof target === 'object') { + if ((options && (options.plainObjects || options.allowPrototypes)) || !has.call(Object.prototype, source)) { target[source] = true; } } else { @@ -448,7 +454,7 @@ exports.merge = function (target, source, options) { return target; } - if (typeof target !== 'object') { + if (!target || typeof target !== 'object') { return [target].concat(source); } @@ -506,13 +512,13 @@ exports.encode = function (str) { var c = string.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 + 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 ) { out += string.charAt(i); continue; @@ -535,7 +541,11 @@ exports.encode = function (str) { i += 1; c = 0x10000 + (((c & 0x3FF) << 10) | (string.charCodeAt(i) & 0x3FF)); - out += hexTable[0xF0 | (c >> 18)] + hexTable[0x80 | ((c >> 12) & 0x3F)] + hexTable[0x80 | ((c >> 6) & 0x3F)] + hexTable[0x80 | (c & 0x3F)]; // eslint-disable-line max-len + /* eslint operator-linebreak: [2, "before"] */ + out += hexTable[0xF0 | (c >> 18)] + + hexTable[0x80 | ((c >> 12) & 0x3F)] + + hexTable[0x80 | ((c >> 6) & 0x3F)] + + hexTable[0x80 | (c & 0x3F)]; } return out; diff --git a/package.json b/package.json index 00a42b4d..93aa4ae8 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "qs", "description": "A querystring parser that supports nesting and arrays, with a depth limit", "homepage": "https://github.com/ljharb/qs", - "version": "6.3.2", + "version": "6.3.3", "repository": { "type": "git", "url": "https://github.com/ljharb/qs.git" From 90d9f2b45715b7b03da92113a7b8af236c01088d Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Mon, 10 Jan 2022 19:38:19 -0800 Subject: [PATCH 267/278] v6.2.4 --- CHANGELOG.md | 17 ++++++ component.json | 2 +- dist/qs.js | 157 ++++++++++++++++++++++++++++++++++++------------- package.json | 2 +- 4 files changed, 136 insertions(+), 42 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 59e02653..32fca586 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,20 @@ +## **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 diff --git a/component.json b/component.json index c1715a89..5fed22a4 100644 --- a/component.json +++ b/component.json @@ -2,7 +2,7 @@ "name": "qs", "repository": "hapijs/qs", "description": "query-string parser / stringifier with nesting support", - "version": "6.2.3", + "version": "6.2.4", "keywords": ["querystring", "query", "parser"], "main": "lib/index.js", "scripts": [ diff --git a/dist/qs.js b/dist/qs.js index bcac0f0c..c9a48916 100644 --- a/dist/qs.js +++ b/dist/qs.js @@ -1,4 +1,4 @@ -(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 e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o= 0 && - (options.parseArrays && index <= options.arrayLimit) + if (!options.parseArrays && cleanRoot === '') { + obj = { 0: val }; + } else if ( + !isNaN(index) + && root !== cleanRoot + && String(index) === cleanRoot + && index >= 0 + && (options.parseArrays && index <= options.arrayLimit) ) { obj = []; obj[index] = parseObject(chain, val, options); - } else { + } else if (cleanRoot !== '__proto__') { obj[cleanRoot] = parseObject(chain, val, options); } } @@ -108,8 +110,7 @@ var parseKeys = function parseKeys(givenKey, val, options) { var keys = []; if (parent) { - // If we aren't using plain objects, optionally prefix keys - // that would overwrite object prototype properties + // If we aren't using plain objects, optionally prefix keys that would overwrite object prototype properties if (!options.plainObjects && has.call(Object.prototype, parent)) { if (!options.allowPrototypes) { return; @@ -203,7 +204,18 @@ var defaults = { encoder: Utils.encode }; -var stringify = function stringify(object, prefix, generateArrayPrefix, strictNullHandling, skipNulls, encoder, filter, sort, allowDots) { +var isArray = Array.isArray; +var stringify = function stringify( + object, + prefix, + generateArrayPrefix, + strictNullHandling, + skipNulls, + encoder, + filter, + sort, + allowDots +) { var obj = object; if (typeof filter === 'function') { obj = filter(prefix, obj); @@ -231,7 +243,7 @@ var stringify = function stringify(object, prefix, generateArrayPrefix, strictNu } var objKeys; - if (Array.isArray(filter)) { + if (isArray(filter)) { objKeys = filter; } else { var keys = Object.keys(obj); @@ -245,10 +257,30 @@ var stringify = function stringify(object, prefix, generateArrayPrefix, strictNu continue; } - if (Array.isArray(obj)) { - values = values.concat(stringify(obj[key], generateArrayPrefix(prefix, key), generateArrayPrefix, strictNullHandling, skipNulls, encoder, filter, sort, allowDots)); + if (isArray(obj)) { + values = values.concat(stringify( + obj[key], + generateArrayPrefix(prefix, key), + generateArrayPrefix, + strictNullHandling, + skipNulls, + encoder, + filter, + sort, + allowDots + )); } else { - values = values.concat(stringify(obj[key], prefix + (allowDots ? '.' + key : '[' + key + ']'), generateArrayPrefix, strictNullHandling, skipNulls, encoder, filter, sort, allowDots)); + values = values.concat(stringify( + obj[key], + prefix + (allowDots ? '.' + key : '[' + key + ']'), + generateArrayPrefix, + strictNullHandling, + skipNulls, + encoder, + filter, + sort, + allowDots + )); } } @@ -262,21 +294,22 @@ module.exports = function (object, opts) { var strictNullHandling = typeof options.strictNullHandling === 'boolean' ? options.strictNullHandling : defaults.strictNullHandling; var skipNulls = typeof options.skipNulls === 'boolean' ? options.skipNulls : defaults.skipNulls; var encode = typeof options.encode === 'boolean' ? options.encode : defaults.encode; - var encoder = encode ? (typeof options.encoder === 'function' ? options.encoder : defaults.encoder) : null; + var encoder = encode ? typeof options.encoder === 'function' ? options.encoder : defaults.encoder : null; var sort = typeof options.sort === 'function' ? options.sort : null; var allowDots = typeof options.allowDots === 'undefined' ? false : options.allowDots; var objKeys; var filter; - if (options.encoder !== null && options.encoder !== undefined && typeof options.encoder !== 'function') { + if (options.encoder !== null && typeof options.encoder !== 'undefined' && typeof options.encoder !== 'function') { throw new TypeError('Encoder has to be a function.'); } if (typeof options.filter === 'function') { filter = options.filter; obj = filter('', obj); - } else if (Array.isArray(options.filter)) { - objKeys = filter = options.filter; + } else if (isArray(options.filter)) { + objKeys = options.filter; + filter = options.filter; } var keys = []; @@ -311,7 +344,17 @@ module.exports = function (object, opts) { continue; } - keys = keys.concat(stringify(obj[key], key, generateArrayPrefix, strictNullHandling, skipNulls, encoder, filter, sort, allowDots)); + keys = keys.concat(stringify( + obj[key], + key, + generateArrayPrefix, + strictNullHandling, + skipNulls, + encoder, + filter, + sort, + allowDots + )); } return keys.join(delimiter); @@ -332,7 +375,20 @@ var hexTable = (function () { var has = Object.prototype.hasOwnProperty; exports.arrayToObject = function (source, options) { - var obj = options.plainObjects ? Object.create(null) : {}; + var obj = options && options.plainObjects ? Object.create(null) : {}; + for (var i = 0; i < source.length; ++i) { + if (typeof source[i] !== 'undefined') { + obj[i] = source[i]; + } + } + + return obj; +}; + +var isArray = Array.isArray; + +var arrayToObject = function arrayToObject(source, options) { + var obj = options && options.plainObjects ? Object.create(null) : {}; for (var i = 0; i < source.length; ++i) { if (typeof source[i] !== 'undefined') { obj[i] = source[i]; @@ -342,16 +398,17 @@ exports.arrayToObject = function (source, options) { return obj; }; -exports.merge = function (target, source, options) { +exports.merge = function merge(target, source, options) { + /* eslint no-param-reassign: 0 */ if (!source) { return target; } if (typeof source !== 'object') { - if (Array.isArray(target)) { + if (isArray(target)) { target.push(source); - } else if (typeof target === 'object') { - if (options.plainObjects || options.allowPrototypes || !has.call(Object.prototype, source)) { + } else if (target && typeof target === 'object') { + if ((options && (options.plainObjects || options.allowPrototypes)) || !has.call(Object.prototype, source)) { target[source] = true; } } else { @@ -361,20 +418,36 @@ exports.merge = function (target, source, options) { return target; } - if (typeof target !== 'object') { + if (!target || typeof target !== 'object') { return [target].concat(source); } var mergeTarget = target; - if (Array.isArray(target) && !Array.isArray(source)) { - mergeTarget = exports.arrayToObject(target, options); + 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.push(item); + } + } else { + target[i] = item; + } + }); + return target; } return Object.keys(source).reduce(function (acc, key) { var value = source[key]; if (has.call(acc, key)) { - acc[key] = exports.merge(acc[key], value, options); + acc[key] = merge(acc[key], value, options); } else { acc[key] = value; } @@ -404,13 +477,13 @@ exports.encode = function (str) { var c = string.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 + 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 ) { out += string.charAt(i); continue; @@ -433,7 +506,11 @@ exports.encode = function (str) { i += 1; c = 0x10000 + (((c & 0x3FF) << 10) | (string.charCodeAt(i) & 0x3FF)); - out += hexTable[0xF0 | (c >> 18)] + hexTable[0x80 | ((c >> 12) & 0x3F)] + hexTable[0x80 | ((c >> 6) & 0x3F)] + hexTable[0x80 | (c & 0x3F)]; + /* eslint operator-linebreak: [2, "before"] */ + out += hexTable[0xF0 | (c >> 18)] + + hexTable[0x80 | ((c >> 12) & 0x3F)] + + hexTable[0x80 | ((c >> 6) & 0x3F)] + + hexTable[0x80 | (c & 0x3F)]; } return out; @@ -452,7 +529,7 @@ exports.compact = function (obj, references) { refs.push(obj); - if (Array.isArray(obj)) { + if (isArray(obj)) { var compacted = []; for (var i = 0; i < obj.length; ++i) { @@ -488,4 +565,4 @@ exports.isBuffer = function (obj) { }; },{}]},{},[1])(1) -}); \ No newline at end of file +}); diff --git a/package.json b/package.json index b248fb9d..b1dcf19e 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "qs", "description": "A querystring parser that supports nesting and arrays, with a depth limit", "homepage": "https://github.com/ljharb/qs", - "version": "6.2.3", + "version": "6.2.4", "repository": { "type": "git", "url": "https://github.com/ljharb/qs.git" From f92ddb56089ae2c74f5ca7b0447fef3a97e8c9bc Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Mon, 10 Jan 2022 20:05:09 -0800 Subject: [PATCH 268/278] v6.10.3 --- CHANGELOG.md | 6 +++ component.json | 2 +- dist/qs.js | 103 +++++++++++++++++++++++++++++++++++-------------- package.json | 2 +- 4 files changed, 81 insertions(+), 32 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 82e38a0d..c6b29155 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +## **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) diff --git a/component.json b/component.json index e686cd24..193fb738 100644 --- a/component.json +++ b/component.json @@ -2,7 +2,7 @@ "name": "qs", "repository": "hapijs/qs", "description": "query-string parser / stringifier with nesting support", - "version": "6.5.0", + "version": "6.10.3", "keywords": ["querystring", "query", "parser"], "main": "lib/index.js", "scripts": [ diff --git a/dist/qs.js b/dist/qs.js index ea946c25..94baf8ff 100644 --- a/dist/qs.js +++ b/dist/qs.js @@ -175,7 +175,7 @@ var parseObject = function (chain, val, options, valuesParsed) { ) { obj = []; obj[index] = leaf; - } else { + } else if (cleanRoot !== '__proto__') { obj[cleanRoot] = leaf; } } @@ -384,7 +384,7 @@ var stringify = function stringify( var tmpSc = sideChannel; var step = 0; var findFlag = false; - while ((tmpSc = tmpSc.get(sentinel)) !== undefined && !findFlag) { + while ((tmpSc = tmpSc.get(sentinel)) !== void undefined && !findFlag) { // Where object last appeared in the ref tree var pos = tmpSc.get(object); step += 1; @@ -446,7 +446,7 @@ var stringify = function stringify( var objKeys; if (generateArrayPrefix === 'comma' && isArray(obj)) { // we need to join elements in - objKeys = [{ value: obj.length > 0 ? obj.join(',') || null : undefined }]; + objKeys = [{ value: obj.length > 0 ? obj.join(',') || null : void undefined }]; } else if (isArray(filter)) { objKeys = filter; } else { @@ -456,7 +456,7 @@ var stringify = function stringify( for (var j = 0; j < objKeys.length; ++j) { var key = objKeys[j]; - var value = typeof key === 'object' && key.value !== undefined ? key.value : obj[key]; + var value = typeof key === 'object' && typeof key.value !== 'undefined' ? key.value : obj[key]; if (skipNulls && value === null) { continue; @@ -496,7 +496,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.'); } @@ -1419,11 +1419,24 @@ var weakRefDeref = hasWeakRef ? WeakRef.prototype.deref : null; var booleanValueOf = Boolean.prototype.valueOf; var objectToString = Object.prototype.toString; var functionToString = Function.prototype.toString; -var match = String.prototype.match; +var $match = String.prototype.match; +var $slice = String.prototype.slice; +var $replace = String.prototype.replace; +var $toUpperCase = String.prototype.toUpperCase; +var $toLowerCase = String.prototype.toLowerCase; +var $test = RegExp.prototype.test; +var $concat = Array.prototype.concat; +var $join = Array.prototype.join; +var $arrSlice = Array.prototype.slice; +var $floor = Math.floor; var bigIntValueOf = typeof BigInt === 'function' ? BigInt.prototype.valueOf : null; var gOPS = Object.getOwnPropertySymbols; var symToString = typeof Symbol === 'function' && typeof Symbol.iterator === 'symbol' ? Symbol.prototype.toString : null; var hasShammedSymbols = typeof Symbol === 'function' && typeof Symbol.iterator === 'object'; +// ie, `has-tostringtag/shams +var toStringTag = typeof Symbol === 'function' && Symbol.toStringTag && (typeof Symbol.toStringTag === hasShammedSymbols ? 'object' : 'symbol') + ? Symbol.toStringTag + : null; var isEnumerable = Object.prototype.propertyIsEnumerable; var gPO = (typeof Reflect === 'function' ? Reflect.getPrototypeOf : Object.getPrototypeOf) || ( @@ -1434,9 +1447,30 @@ var gPO = (typeof Reflect === 'function' ? Reflect.getPrototypeOf : Object.getPr : null ); +function addNumericSeparator(num, str) { + if ( + num === Infinity + || num === -Infinity + || num !== num + || (num && num > -1000 && num < 1000) + || $test.call(/e/, str) + ) { + return str; + } + var sepRegex = /[0-9](?=(?:[0-9]{3})+(?![0-9]))/g; + if (typeof num === 'number') { + var int = num < 0 ? -$floor(-num) : $floor(num); // trunc(num) + if (int !== num) { + var intStr = String(int); + var dec = $slice.call(str, intStr.length + 1); + return $replace.call(intStr, sepRegex, '$&_') + '.' + $replace.call($replace.call(dec, /([0-9]{3})/g, '$&_'), /_$/, ''); + } + } + return $replace.call(str, sepRegex, '$&_'); +} + var inspectCustom = require('./util.inspect').custom; var inspectSymbol = inspectCustom && isSymbol(inspectCustom) ? inspectCustom : null; -var toStringTag = typeof Symbol === 'function' && typeof Symbol.toStringTag !== 'undefined' ? Symbol.toStringTag : null; module.exports = function inspect_(obj, options, depth, seen) { var opts = options || {}; @@ -1463,8 +1497,12 @@ module.exports = function inspect_(obj, options, depth, seen) { && opts.indent !== '\t' && !(parseInt(opts.indent, 10) === opts.indent && opts.indent > 0) ) { - throw new TypeError('options "indent" must be "\\t", an integer > 0, or `null`'); + throw new TypeError('option "indent" must be "\\t", an integer > 0, or `null`'); } + if (has(opts, 'numericSeparator') && typeof opts.numericSeparator !== 'boolean') { + throw new TypeError('option "numericSeparator", if provided, must be `true` or `false`'); + } + var numericSeparator = opts.numericSeparator; if (typeof obj === 'undefined') { return 'undefined'; @@ -1483,10 +1521,12 @@ module.exports = function inspect_(obj, options, depth, seen) { if (obj === 0) { return Infinity / obj > 0 ? '0' : '-0'; } - return String(obj); + var str = String(obj); + return numericSeparator ? addNumericSeparator(obj, str) : str; } if (typeof obj === 'bigint') { - return String(obj) + 'n'; + var bigIntStr = String(obj) + 'n'; + return numericSeparator ? addNumericSeparator(obj, bigIntStr) : bigIntStr; } var maxDepth = typeof opts.depth === 'undefined' ? 5 : opts.depth; @@ -1505,7 +1545,7 @@ module.exports = function inspect_(obj, options, depth, seen) { function inspect(value, from, noIndent) { if (from) { - seen = seen.slice(); + seen = $arrSlice.call(seen); seen.push(from); } if (noIndent) { @@ -1523,21 +1563,21 @@ module.exports = function inspect_(obj, options, depth, seen) { if (typeof obj === 'function') { var name = nameOf(obj); var keys = arrObjKeys(obj, inspect); - return '[Function' + (name ? ': ' + name : ' (anonymous)') + ']' + (keys.length > 0 ? ' { ' + keys.join(', ') + ' }' : ''); + return '[Function' + (name ? ': ' + name : ' (anonymous)') + ']' + (keys.length > 0 ? ' { ' + $join.call(keys, ', ') + ' }' : ''); } if (isSymbol(obj)) { - var symString = hasShammedSymbols ? String(obj).replace(/^(Symbol\(.*\))_[^)]*$/, '$1') : symToString.call(obj); + var symString = hasShammedSymbols ? $replace.call(String(obj), /^(Symbol\(.*\))_[^)]*$/, '$1') : symToString.call(obj); return typeof obj === 'object' && !hasShammedSymbols ? markBoxed(symString) : symString; } if (isElement(obj)) { - var s = '<' + String(obj.nodeName).toLowerCase(); + var s = '<' + $toLowerCase.call(String(obj.nodeName)); var attrs = obj.attributes || []; for (var i = 0; i < attrs.length; i++) { s += ' ' + attrs[i].name + '=' + wrapQuotes(quote(attrs[i].value), 'double', opts); } s += '>'; if (obj.childNodes && obj.childNodes.length) { s += '...'; } - s += ''; + s += ''; return s; } if (isArray(obj)) { @@ -1546,12 +1586,15 @@ module.exports = function inspect_(obj, options, depth, seen) { if (indent && !singleLineValues(xs)) { return '[' + indentedJoin(xs, indent) + ']'; } - return '[ ' + xs.join(', ') + ' ]'; + return '[ ' + $join.call(xs, ', ') + ' ]'; } if (isError(obj)) { var parts = arrObjKeys(obj, inspect); + if ('cause' in obj && !isEnumerable.call(obj, 'cause')) { + return '{ [' + String(obj) + '] ' + $join.call($concat.call('[cause]: ' + inspect(obj.cause), parts), ', ') + ' }'; + } if (parts.length === 0) { return '[' + String(obj) + ']'; } - return '{ [' + String(obj) + '] ' + parts.join(', ') + ' }'; + return '{ [' + String(obj) + '] ' + $join.call(parts, ', ') + ' }'; } if (typeof obj === 'object' && customInspect) { if (inspectSymbol && typeof obj[inspectSymbol] === 'function') { @@ -1599,14 +1642,14 @@ module.exports = function inspect_(obj, options, depth, seen) { var ys = arrObjKeys(obj, inspect); var isPlainObject = gPO ? gPO(obj) === Object.prototype : obj instanceof Object || obj.constructor === Object; var protoTag = obj instanceof Object ? '' : 'null prototype'; - var stringTag = !isPlainObject && toStringTag && Object(obj) === obj && toStringTag in obj ? toStr(obj).slice(8, -1) : protoTag ? 'Object' : ''; + var stringTag = !isPlainObject && toStringTag && Object(obj) === obj && toStringTag in obj ? $slice.call(toStr(obj), 8, -1) : protoTag ? 'Object' : ''; var constructorTag = isPlainObject || typeof obj.constructor !== 'function' ? '' : obj.constructor.name ? obj.constructor.name + ' ' : ''; - var tag = constructorTag + (stringTag || protoTag ? '[' + [].concat(stringTag || [], protoTag || []).join(': ') + '] ' : ''); + var tag = constructorTag + (stringTag || protoTag ? '[' + $join.call($concat.call([], stringTag || [], protoTag || []), ': ') + '] ' : ''); if (ys.length === 0) { return tag + '{}'; } if (indent) { return tag + '{' + indentedJoin(ys, indent) + '}'; } - return tag + '{ ' + ys.join(', ') + ' }'; + return tag + '{ ' + $join.call(ys, ', ') + ' }'; } return String(obj); }; @@ -1617,7 +1660,7 @@ function wrapQuotes(s, defaultStyle, opts) { } function quote(s) { - return String(s).replace(/"/g, '"'); + return $replace.call(String(s), /"/g, '"'); } function isArray(obj) { return toStr(obj) === '[object Array]' && (!toStringTag || !(typeof obj === 'object' && toStringTag in obj)); } @@ -1668,7 +1711,7 @@ function toStr(obj) { function nameOf(f) { if (f.name) { return f.name; } - var m = match.call(functionToString.call(f), /^function\s*([\w$]+)/); + var m = $match.call(functionToString.call(f), /^function\s*([\w$]+)/); if (m) { return m[1]; } return null; } @@ -1768,10 +1811,10 @@ function inspectString(str, opts) { if (str.length > opts.maxStringLength) { var remaining = str.length - opts.maxStringLength; var trailer = '... ' + remaining + ' more character' + (remaining > 1 ? 's' : ''); - return inspectString(str.slice(0, opts.maxStringLength), opts) + trailer; + return inspectString($slice.call(str, 0, opts.maxStringLength), opts) + trailer; } // eslint-disable-next-line no-control-regex - var s = str.replace(/(['\\])/g, '\\$1').replace(/[\x00-\x1f]/g, lowbyte); + var s = $replace.call($replace.call(str, /(['\\])/g, '\\$1'), /[\x00-\x1f]/g, lowbyte); return wrapQuotes(s, 'single', opts); } @@ -1785,7 +1828,7 @@ function lowbyte(c) { 13: 'r' }[n]; if (x) { return '\\' + x; } - return '\\x' + (n < 0x10 ? '0' : '') + n.toString(16).toUpperCase(); + return '\\x' + (n < 0x10 ? '0' : '') + $toUpperCase.call(n.toString(16)); } function markBoxed(str) { @@ -1797,7 +1840,7 @@ function weakCollectionOf(type) { } function collectionOf(type, size, entries, indent) { - var joinedEntries = indent ? indentedJoin(entries, indent) : entries.join(', '); + var joinedEntries = indent ? indentedJoin(entries, indent) : $join.call(entries, ', '); return type + ' (' + size + ') {' + joinedEntries + '}'; } @@ -1815,20 +1858,20 @@ function getIndent(opts, depth) { if (opts.indent === '\t') { baseIndent = '\t'; } else if (typeof opts.indent === 'number' && opts.indent > 0) { - baseIndent = Array(opts.indent + 1).join(' '); + baseIndent = $join.call(Array(opts.indent + 1), ' '); } else { return null; } return { base: baseIndent, - prev: Array(depth + 1).join(baseIndent) + prev: $join.call(Array(depth + 1), baseIndent) }; } function indentedJoin(xs, indent) { if (xs.length === 0) { return ''; } var lineJoiner = '\n' + indent.prev + indent.base; - return lineJoiner + xs.join(',' + lineJoiner) + '\n' + indent.prev; + return lineJoiner + $join.call(xs, ',' + lineJoiner) + '\n' + indent.prev; } function arrObjKeys(obj, inspect) { @@ -1855,7 +1898,7 @@ function arrObjKeys(obj, inspect) { if (hasShammedSymbols && symMap['$' + key] instanceof Symbol) { // this is to prevent shammed Symbols, which are stored as strings, from being included in the string key section continue; // eslint-disable-line no-restricted-syntax, no-continue - } else if ((/[^\w$]/).test(key)) { + } else if ($test.call(/[^\w$]/, key)) { xs.push(inspect(key, obj) + ': ' + inspect(obj[key], obj)); } else { xs.push(key + ': ' + inspect(obj[key], obj)); diff --git a/package.json b/package.json index 11f3be61..845e20a2 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "qs", "description": "A querystring parser that supports nesting and arrays, with a depth limit", "homepage": "https://github.com/ljharb/qs", - "version": "6.10.2", + "version": "6.10.3", "repository": { "type": "git", "url": "https://github.com/ljharb/qs.git" From 2cf45b2dcd31a6d5c7fc16f33c7148fade0eef1e Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Sun, 15 May 2022 09:29:08 -0700 Subject: [PATCH 269/278] [meta] use `npmignore` to autogenerate an npmignore file --- .gitignore | 2 ++ .npmignore | 19 ------------------- package.json | 12 +++++++++++- 3 files changed, 13 insertions(+), 20 deletions(-) delete mode 100644 .npmignore diff --git a/.gitignore b/.gitignore index 82dda142..c073c601 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,5 @@ npm-shrinkwrap.json # coverage output coverage/ .nyc_output/ + +.npmignore diff --git a/.npmignore b/.npmignore deleted file mode 100644 index f3eaf982..00000000 --- a/.npmignore +++ /dev/null @@ -1,19 +0,0 @@ -# gitignore -npm-debug.log -node_modules -.DS_Store - -# Only apps should have lockfiles -yarn.lock -package-lock.json -npm-shrinkwrap.json - -# coverage output -coverage/ -.nyc_output/ - -bower.json -component.json -.npmignore - -.github/workflows diff --git a/package.json b/package.json index 3d6b97ba..ad7dd967 100644 --- a/package.json +++ b/package.json @@ -44,6 +44,7 @@ "iconv-lite": "^0.5.1", "in-publish": "^2.0.1", "mkdirp": "^0.5.5", + "npmignore": "^0.3.0", "nyc": "^10.3.2", "object-inspect": "^1.12.0", "qs-iconv": "^1.0.4", @@ -52,6 +53,7 @@ "tape": "^5.4.0" }, "scripts": { + "prepack": "npmignore --auto --commentLines=autogenerated", "prepublishOnly": "safe-publish-latest && npm run dist", "prepublish": "not-in-publish || npm run prepublishOnly", "pretest": "npm run --silent readme && npm run --silent lint", @@ -63,5 +65,13 @@ "lint": "eslint --ext=js,mjs .", "dist": "mkdirp dist && browserify --standalone Qs lib/index.js > dist/qs.js" }, - "license": "BSD-3-Clause" + "license": "BSD-3-Clause", + "publishConfig": { + "ignore": [ + "!dist/*", + "bower.json", + "component.json", + ".github/workflows" + ] + } } From c77f38f7174b9f10e8937e0f601fa1e6f0373b33 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Sun, 15 May 2022 09:30:26 -0700 Subject: [PATCH 270/278] [Dev Deps] update `eslint`, `@ljharb/eslint-config`, `aud`, `has-symbol`, `tape` --- package.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index ad7dd967..e6ef9d86 100644 --- a/package.json +++ b/package.json @@ -33,14 +33,14 @@ "side-channel": "^1.0.4" }, "devDependencies": { - "@ljharb/eslint-config": "^20.1.0", - "aud": "^1.1.5", + "@ljharb/eslint-config": "^21.0.0", + "aud": "^2.0.0", "browserify": "^16.5.2", "eclint": "^2.8.1", - "eslint": "^8.6.0", + "eslint": "=8.8.0", "evalmd": "^0.0.19", "for-each": "^0.3.3", - "has-symbols": "^1.0.2", + "has-symbols": "^1.0.3", "iconv-lite": "^0.5.1", "in-publish": "^2.0.1", "mkdirp": "^0.5.5", @@ -50,7 +50,7 @@ "qs-iconv": "^1.0.4", "safe-publish-latest": "^2.0.0", "safer-buffer": "^2.1.2", - "tape": "^5.4.0" + "tape": "^5.5.3" }, "scripts": { "prepack": "npmignore --auto --commentLines=autogenerated", From 113b990ed23ae8d6f670eb879e16ed105cd9081b Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Sun, 5 Jun 2022 16:48:51 -0500 Subject: [PATCH 271/278] [Dev Deps] update `object-inspect` --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e6ef9d86..65b3c20c 100644 --- a/package.json +++ b/package.json @@ -46,7 +46,7 @@ "mkdirp": "^0.5.5", "npmignore": "^0.3.0", "nyc": "^10.3.2", - "object-inspect": "^1.12.0", + "object-inspect": "^1.12.2", "qs-iconv": "^1.0.4", "safe-publish-latest": "^2.0.0", "safer-buffer": "^2.1.2", From 4e440195c7647f21c20bb76340774cb3a0cb6eac Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Sun, 5 Jun 2022 16:49:41 -0500 Subject: [PATCH 272/278] [Fix] `stringify`: with `arrayFormat: comma`, include an explicit `[]` on a single-item array Fixes #434 --- lib/stringify.js | 2 +- test/parse.js | 10 ++++++++++ test/stringify.js | 14 ++++++++++++++ 3 files changed, 25 insertions(+), 1 deletion(-) diff --git a/lib/stringify.js b/lib/stringify.js index 47ea4b15..5ace512e 100644 --- a/lib/stringify.js +++ b/lib/stringify.js @@ -126,7 +126,7 @@ var stringify = function stringify( 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) + (i === 1 ? '[]' : '') + '=' + valuesJoined]; } return [formatter(keyValue) + '=' + formatter(encoder(obj, defaults.encoder, charset, 'value', format))]; } diff --git a/test/parse.js b/test/parse.js index 9734f428..7d7b4dd8 100644 --- a/test/parse.js +++ b/test/parse.js @@ -410,6 +410,16 @@ test('parse()', function (t) { 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(); }); diff --git a/test/stringify.js b/test/stringify.js index 4c3ee0e8..f1ff112e 100644 --- a/test/stringify.js +++ b/test/stringify.js @@ -131,6 +131,20 @@ test('stringify()', function (t) { st.end(); }); + t.test('stringifies an array value with one item vs multiple items', function (st) { + st.equal(qs.stringify({ a: ['c'] }, { encodeValuesOnly: true, arrayFormat: 'indices' }), 'a[0]=c'); + st.equal(qs.stringify({ a: ['c'] }, { encodeValuesOnly: true, arrayFormat: 'brackets' }), 'a[]=c'); + st.equal(qs.stringify({ a: ['c'] }, { encodeValuesOnly: true, arrayFormat: 'comma' }), 'a[]=c'); // so it parses back as an array + st.equal(qs.stringify({ a: ['c'] }, { encodeValuesOnly: true }), 'a[0]=c'); + + st.equal(qs.stringify({ a: ['c', 'd'] }, { encodeValuesOnly: true, arrayFormat: 'indices' }), 'a[0]=c&a[1]=d'); + st.equal(qs.stringify({ a: ['c', 'd'] }, { encodeValuesOnly: true, arrayFormat: 'brackets' }), 'a[]=c&a[]=d'); + st.equal(qs.stringify({ a: ['c', 'd'] }, { encodeValuesOnly: true, arrayFormat: 'comma' }), 'a=c,d'); + st.equal(qs.stringify({ a: ['c', 'd'] }, { encodeValuesOnly: true }), 'a[0]=c&a[1]=d'); + + 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'); From ba9703c0340dfdeb73cb4387d6ab32c37768aa5b Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Mon, 6 Jun 2022 14:06:46 -0500 Subject: [PATCH 273/278] v6.10.4 --- CHANGELOG.md | 5 +++++ package.json | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6da4f366..25fd472a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +## **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) diff --git a/package.json b/package.json index 65b3c20c..65f7d493 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "qs", "description": "A querystring parser that supports nesting and arrays, with a depth limit", "homepage": "https://github.com/ljharb/qs", - "version": "6.10.3", + "version": "6.10.4", "repository": { "type": "git", "url": "https://github.com/ljharb/qs.git" From 0e903c0a9092618756b0962f1b80655ac0da436a Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Mon, 6 Jun 2022 16:55:26 -0500 Subject: [PATCH 274/278] [Fix] `stringify`: with `arrayFormat: comma`, properly include an explicit `[]` on a single-item array Properly fixes #434 --- .eslintrc | 2 +- lib/stringify.js | 8 +++++--- test/stringify.js | 39 ++++++++++++++++++++++++++++----------- 3 files changed, 34 insertions(+), 15 deletions(-) diff --git a/.eslintrc b/.eslintrc index 9e7a211a..a9343fa6 100644 --- a/.eslintrc +++ b/.eslintrc @@ -15,7 +15,7 @@ "indent": [2, 4], "max-lines-per-function": [2, { "max": 150 }], "max-params": [2, 15], - "max-statements": [2, 52], + "max-statements": [2, 53], "multiline-comment-style": 0, "no-continue": 1, "no-magic-numbers": 0, diff --git a/lib/stringify.js b/lib/stringify.js index 5ace512e..55c37404 100644 --- a/lib/stringify.js +++ b/lib/stringify.js @@ -126,7 +126,7 @@ var stringify = function stringify( for (var i = 0; i < valuesArray.length; ++i) { valuesJoined += (i === 0 ? '' : ',') + formatter(encoder(valuesArray[i], defaults.encoder, charset, 'value', format)); } - return [formatter(keyValue) + (i === 1 ? '[]' : '') + '=' + valuesJoined]; + return [formatter(keyValue) + (isArray(obj) && valuesArray.length === 1 ? '[]' : '') + '=' + valuesJoined]; } return [formatter(keyValue) + '=' + formatter(encoder(obj, defaults.encoder, charset, 'value', format))]; } @@ -150,6 +150,8 @@ var stringify = function stringify( objKeys = sort ? keys.sort(sort) : keys; } + var adjustedPrefix = generateArrayPrefix === 'comma' && isArray(obj) && obj.length === 1 ? prefix + '[]' : prefix; + 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]; @@ -159,8 +161,8 @@ var stringify = function stringify( } var keyPrefix = isArray(obj) - ? typeof generateArrayPrefix === 'function' ? generateArrayPrefix(prefix, key) : prefix - : prefix + (allowDots ? '.' + key : '[' + key + ']'); + ? typeof generateArrayPrefix === 'function' ? generateArrayPrefix(adjustedPrefix, key) : adjustedPrefix + : adjustedPrefix + (allowDots ? '.' + key : '[' + key + ']'); sideChannel.set(object, step); var valueSideChannel = getSideChannel(); diff --git a/test/stringify.js b/test/stringify.js index f1ff112e..fda52413 100644 --- a/test/stringify.js +++ b/test/stringify.js @@ -132,15 +132,32 @@ test('stringify()', function (t) { }); t.test('stringifies an array value with one item vs multiple items', function (st) { - st.equal(qs.stringify({ a: ['c'] }, { encodeValuesOnly: true, arrayFormat: 'indices' }), 'a[0]=c'); - st.equal(qs.stringify({ a: ['c'] }, { encodeValuesOnly: true, arrayFormat: 'brackets' }), 'a[]=c'); - st.equal(qs.stringify({ a: ['c'] }, { encodeValuesOnly: true, arrayFormat: 'comma' }), 'a[]=c'); // so it parses back as an array - st.equal(qs.stringify({ a: ['c'] }, { encodeValuesOnly: true }), 'a[0]=c'); + 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'); - st.equal(qs.stringify({ a: ['c', 'd'] }, { encodeValuesOnly: true, arrayFormat: 'indices' }), 'a[0]=c&a[1]=d'); - st.equal(qs.stringify({ a: ['c', 'd'] }, { encodeValuesOnly: true, arrayFormat: 'brackets' }), 'a[]=c&a[]=d'); - st.equal(qs.stringify({ a: ['c', 'd'] }, { encodeValuesOnly: true, arrayFormat: 'comma' }), 'a=c,d'); - st.equal(qs.stringify({ a: ['c', 'd'] }, { encodeValuesOnly: true }), 'a[0]=c&a[1]=d'); + 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'); // 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 }), 'a[0]=c&a[1]=d'); + + s2t.end(); + }); st.end(); }); @@ -362,12 +379,12 @@ test('stringify()', function (t) { 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' }), '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 }), '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'); @@ -695,7 +712,7 @@ test('stringify()', function (t) { arrayFormat: 'comma' } ), - 'a=' + date.getTime(), + 'a%5B%5D=' + date.getTime(), 'works with arrayFormat comma' ); From 95bc0185e157d400da4f43f1fcf1c7f008fd847e Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Mon, 6 Jun 2022 16:57:16 -0500 Subject: [PATCH 275/278] v6.10.5 --- CHANGELOG.md | 3 +++ package.json | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 25fd472a..017c2b3b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +## **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 diff --git a/package.json b/package.json index 65f7d493..d07ebd9b 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "qs", "description": "A querystring parser that supports nesting and arrays, with a depth limit", "homepage": "https://github.com/ljharb/qs", - "version": "6.10.4", + "version": "6.10.5", "repository": { "type": "git", "url": "https://github.com/ljharb/qs.git" From c31347299f34afca90e8b5ff793eb4d0f77cfa56 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Sun, 26 Jun 2022 21:26:04 -0700 Subject: [PATCH 276/278] [New] [Fix] `stringify`: revert 0e903c0; add `commaRoundTrip` option Fixes #442. See #441, #434. --- .eslintrc | 2 +- README.md | 2 ++ lib/stringify.js | 11 +++++++++-- test/stringify.js | 21 ++++++++++++++++++--- 4 files changed, 30 insertions(+), 6 deletions(-) diff --git a/.eslintrc b/.eslintrc index a9343fa6..35220cd9 100644 --- a/.eslintrc +++ b/.eslintrc @@ -14,7 +14,7 @@ "id-length": [2, { "min": 1, "max": 25, "properties": "never" }], "indent": [2, 4], "max-lines-per-function": [2, { "max": 150 }], - "max-params": [2, 15], + "max-params": [2, 16], "max-statements": [2, 53], "multiline-comment-style": 0, "no-continue": 1, diff --git a/README.md b/README.md index 01263804..28a58c62 100644 --- a/README.md +++ b/README.md @@ -402,6 +402,8 @@ 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 diff --git a/lib/stringify.js b/lib/stringify.js index 55c37404..48ec0306 100644 --- a/lib/stringify.js +++ b/lib/stringify.js @@ -62,6 +62,7 @@ var stringify = function stringify( object, prefix, generateArrayPrefix, + commaRoundTrip, strictNullHandling, skipNulls, encoder, @@ -126,7 +127,7 @@ var stringify = function stringify( for (var i = 0; i < valuesArray.length; ++i) { valuesJoined += (i === 0 ? '' : ',') + formatter(encoder(valuesArray[i], defaults.encoder, charset, 'value', format)); } - return [formatter(keyValue) + (isArray(obj) && valuesArray.length === 1 ? '[]' : '') + '=' + valuesJoined]; + return [formatter(keyValue) + (commaRoundTrip && isArray(obj) && valuesArray.length === 1 ? '[]' : '') + '=' + valuesJoined]; } return [formatter(keyValue) + '=' + formatter(encoder(obj, defaults.encoder, charset, 'value', format))]; } @@ -150,7 +151,7 @@ var stringify = function stringify( objKeys = sort ? keys.sort(sort) : keys; } - var adjustedPrefix = generateArrayPrefix === 'comma' && isArray(obj) && obj.length === 1 ? prefix + '[]' : prefix; + var adjustedPrefix = commaRoundTrip && isArray(obj) && obj.length === 1 ? prefix + '[]' : prefix; for (var j = 0; j < objKeys.length; ++j) { var key = objKeys[j]; @@ -171,6 +172,7 @@ var stringify = function stringify( value, keyPrefix, generateArrayPrefix, + commaRoundTrip, strictNullHandling, skipNulls, encoder, @@ -267,6 +269,10 @@ module.exports = function (object, opts) { } var generateArrayPrefix = arrayPrefixGenerators[arrayFormat]; + if (opts && 'commaRoundTrip' in opts && typeof opts.commaRoundTrip !== 'boolean') { + throw new TypeError('`commaRoundTrip` must be a boolean, or absent'); + } + var commaRoundTrip = generateArrayPrefix === 'comma' && opts && opts.commaRoundTrip; if (!objKeys) { objKeys = Object.keys(obj); @@ -287,6 +293,7 @@ module.exports = function (object, opts) { obj[key], key, generateArrayPrefix, + commaRoundTrip, options.strictNullHandling, options.skipNulls, options.encode ? options.encoder : null, diff --git a/test/stringify.js b/test/stringify.js index fda52413..f0cdfefa 100644 --- a/test/stringify.js +++ b/test/stringify.js @@ -144,7 +144,8 @@ test('stringify()', function (t) { 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'); // so it parses back as an array + 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(); @@ -379,12 +380,14 @@ test('stringify()', function (t) { 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' }), '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 }), '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'); @@ -712,6 +715,18 @@ test('stringify()', function (t) { 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' ); From ddd3e293b801df7a06cb7f2746462a6ca1dd3fb2 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Sun, 26 Jun 2022 21:27:48 -0700 Subject: [PATCH 277/278] [readme] fix version badge --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 28a58c62..11be8531 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# qs [![Version Badge][2]][1] +# qs [![Version Badge][npm-version-svg]][package-url] [![github actions][actions-image]][actions-url] [![coverage][codecov-image]][codecov-url] From 56763c12ec4fbf723333cbb32371cbd386c33cbb Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Sun, 26 Jun 2022 21:49:17 -0700 Subject: [PATCH 278/278] v6.11.0 --- CHANGELOG.md | 4 ++++ dist/qs.js | 28 +++++++++++++++++++--------- package.json | 2 +- 3 files changed, 24 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 017c2b3b..37b1d3f0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## **6.11.0 +- [New] [Fix] `stringify`: revert 0e903c0; add `commaRoundTrip` option (#442) +- [readme] fix version badge + ## **6.10.5** - [Fix] `stringify`: with `arrayFormat: comma`, properly include an explicit `[]` on a single-item array (#434) diff --git a/dist/qs.js b/dist/qs.js index 94baf8ff..1c620a48 100644 --- a/dist/qs.js +++ b/dist/qs.js @@ -366,6 +366,7 @@ var stringify = function stringify( object, prefix, generateArrayPrefix, + commaRoundTrip, strictNullHandling, skipNulls, encoder, @@ -430,7 +431,7 @@ var stringify = function stringify( 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) + (commaRoundTrip && isArray(obj) && valuesArray.length === 1 ? '[]' : '') + '=' + valuesJoined]; } return [formatter(keyValue) + '=' + formatter(encoder(obj, defaults.encoder, charset, 'value', format))]; } @@ -454,6 +455,8 @@ var stringify = function stringify( objKeys = sort ? keys.sort(sort) : keys; } + var adjustedPrefix = commaRoundTrip && isArray(obj) && obj.length === 1 ? prefix + '[]' : prefix; + 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]; @@ -463,8 +466,8 @@ var stringify = function stringify( } var keyPrefix = isArray(obj) - ? typeof generateArrayPrefix === 'function' ? generateArrayPrefix(prefix, key) : prefix - : prefix + (allowDots ? '.' + key : '[' + key + ']'); + ? typeof generateArrayPrefix === 'function' ? generateArrayPrefix(adjustedPrefix, key) : adjustedPrefix + : adjustedPrefix + (allowDots ? '.' + key : '[' + key + ']'); sideChannel.set(object, step); var valueSideChannel = getSideChannel(); @@ -473,6 +476,7 @@ var stringify = function stringify( value, keyPrefix, generateArrayPrefix, + commaRoundTrip, strictNullHandling, skipNulls, encoder, @@ -569,6 +573,10 @@ module.exports = function (object, opts) { } var generateArrayPrefix = arrayPrefixGenerators[arrayFormat]; + if (opts && 'commaRoundTrip' in opts && typeof opts.commaRoundTrip !== 'boolean') { + throw new TypeError('`commaRoundTrip` must be a boolean, or absent'); + } + var commaRoundTrip = generateArrayPrefix === 'comma' && opts && opts.commaRoundTrip; if (!objKeys) { objKeys = Object.keys(obj); @@ -589,6 +597,7 @@ module.exports = function (object, opts) { obj[key], key, generateArrayPrefix, + commaRoundTrip, options.strictNullHandling, options.skipNulls, options.encode ? options.encoder : null, @@ -1469,8 +1478,9 @@ function addNumericSeparator(num, str) { return $replace.call(str, sepRegex, '$&_'); } -var inspectCustom = require('./util.inspect').custom; -var inspectSymbol = inspectCustom && isSymbol(inspectCustom) ? inspectCustom : null; +var utilInspect = require('./util.inspect'); +var inspectCustom = utilInspect.custom; +var inspectSymbol = isSymbol(inspectCustom) ? inspectCustom : null; module.exports = function inspect_(obj, options, depth, seen) { var opts = options || {}; @@ -1560,7 +1570,7 @@ module.exports = function inspect_(obj, options, depth, seen) { return inspect_(value, opts, depth + 1, seen); } - if (typeof obj === 'function') { + if (typeof obj === 'function' && !isRegExp(obj)) { // in older engines, regexes are callable var name = nameOf(obj); var keys = arrObjKeys(obj, inspect); return '[Function' + (name ? ': ' + name : ' (anonymous)') + ']' + (keys.length > 0 ? ' { ' + $join.call(keys, ', ') + ' }' : ''); @@ -1590,15 +1600,15 @@ module.exports = function inspect_(obj, options, depth, seen) { } if (isError(obj)) { var parts = arrObjKeys(obj, inspect); - if ('cause' in obj && !isEnumerable.call(obj, 'cause')) { + if (!('cause' in Error.prototype) && 'cause' in obj && !isEnumerable.call(obj, 'cause')) { return '{ [' + String(obj) + '] ' + $join.call($concat.call('[cause]: ' + inspect(obj.cause), parts), ', ') + ' }'; } if (parts.length === 0) { return '[' + String(obj) + ']'; } return '{ [' + String(obj) + '] ' + $join.call(parts, ', ') + ' }'; } if (typeof obj === 'object' && customInspect) { - if (inspectSymbol && typeof obj[inspectSymbol] === 'function') { - return obj[inspectSymbol](); + if (inspectSymbol && typeof obj[inspectSymbol] === 'function' && utilInspect) { + return utilInspect(obj, { depth: maxDepth - depth }); } else if (customInspect !== 'symbol' && typeof obj.inspect === 'function') { return obj.inspect(); } diff --git a/package.json b/package.json index d07ebd9b..2ff42f37 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "qs", "description": "A querystring parser that supports nesting and arrays, with a depth limit", "homepage": "https://github.com/ljharb/qs", - "version": "6.10.5", + "version": "6.11.0", "repository": { "type": "git", "url": "https://github.com/ljharb/qs.git"