From fde5bb712218e1bef9337d2d1fc7fd0862509816 Mon Sep 17 00:00:00 2001 From: Wes Roberts Date: Mon, 4 Dec 2017 22:23:12 -0500 Subject: [PATCH 001/139] [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/139] [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/139] [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/139] [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/139] [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/139] [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/139] [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/139] [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/139] [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/139] [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/139] [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/139] [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/139] [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/139] [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/139] [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/139] [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/139] [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/139] [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/139] [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/139] [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/139] [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/139] [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/139] [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/139] [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/139] [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/139] [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/139] [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/139] [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/139] [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/139] [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/139] [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/139] [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/139] [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/139] [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/139] [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/139] [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/139] [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/139] [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/139] [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/139] =?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/139] [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 2b9b41bbbe4e14d70c908254156d51064dac88ed Mon Sep 17 00:00:00 2001 From: Cas Cornelissen Date: Sat, 8 Dec 2018 17:00:28 +0100 Subject: [PATCH 042/139] 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 043/139] 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 044/139] [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 045/139] [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 046/139] [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 047/139] [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 048/139] [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 049/139] [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 050/139] [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 051/139] [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 052/139] [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 053/139] [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 054/139] [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 055/139] [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 056/139] [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 057/139] [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 058/139] [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 059/139] [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 060/139] [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 061/139] [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 062/139] [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 063/139] [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 064/139] [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 065/139] [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 066/139] [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 067/139] =?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 068/139] [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 069/139] =?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 070/139] [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 7db8e63e76e743230c0433c775f2a20ebfbfafc6 Mon Sep 17 00:00:00 2001 From: Yipeng Zhao Date: Sun, 9 Jun 2019 22:47:25 +0800 Subject: [PATCH 071/139] [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 16148bd636339d514d54654e976ccfb72b4abb45 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Wed, 26 Jun 2019 10:06:17 -0700 Subject: [PATCH 072/139] 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 073/139] =?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 074/139] 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 075/139] =?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 f399189db39b382b729c0df14afd10f3adbf8239 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Sat, 8 Jan 2022 21:24:12 -0800 Subject: [PATCH 076/139] [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 077/139] [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 078/139] 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 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 079/139] [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 080/139] [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 081/139] [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 082/139] [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 083/139] [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 084/139] [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 085/139] [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 086/139] [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 087/139] [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 088/139] [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 089/139] [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 090/139] [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 091/139] [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 092/139] [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 749a58467c1a28744de66d5193a1a19c079927e0 Mon Sep 17 00:00:00 2001 From: Ryan Wheale Date: Fri, 9 Apr 2021 14:22:19 -0600 Subject: [PATCH 093/139] [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 094/139] [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 095/139] [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 4113a5f245987800ef2a8166f809a941661f1542 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Mon, 19 Apr 2021 08:31:51 -0700 Subject: [PATCH 096/139] [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 097/139] [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 098/139] [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 c0e13e9fc80aab01ef777cc06d7411c0df1676a7 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Wed, 1 Sep 2021 14:11:20 -0700 Subject: [PATCH 099/139] [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 100/139] [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 101/139] [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 102/139] [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 103/139] [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 104/139] [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 6024134d365168ca110a7fa16d32f31310b42e2b Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Wed, 1 Sep 2021 14:11:20 -0700 Subject: [PATCH 105/139] [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 106/139] [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 107/139] [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 02ca358155297dc68fcc4c2ac312c26e10524e47 Mon Sep 17 00:00:00 2001 From: Mikhail Bodrov Date: Mon, 27 Dec 2021 22:09:36 +0200 Subject: [PATCH 108/139] [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 109/139] [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 110/139] [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 111/139] [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 112/139] [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 113/139] [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 114/139] [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 115/139] [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 e799ba57e573a30c14b67c1889c7c04d508b9105 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Mon, 27 Dec 2021 19:15:57 -0800 Subject: [PATCH 116/139] [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 117/139] [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 118/139] [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 119/139] [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 120/139] [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 121/139] [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 122/139] [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 123/139] [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 124/139] [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 125/139] [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 126/139] [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 127/139] [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 4cd003291fe3b347884f797e548b58a12150a0e3 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Sun, 9 Jan 2022 09:25:38 -0800 Subject: [PATCH 128/139] 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 129/139] [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 130/139] 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 131/139] [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 132/139] [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 133/139] [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 134/139] 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 135/139] 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 136/139] 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 137/139] 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 138/139] 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 139/139] 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"