From f1a00b7482385c9f6eaaa997360ad0f80f60f660 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Wed, 26 Jun 2019 10:06:17 -0700 Subject: [PATCH 01/20] [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 02/20] [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 03/20] [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 04/20] [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 05/20] [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 06/20] [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 07/20] [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 08/20] [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 09/20] [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 10/20] [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 11/20] [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 7d4670fca6ed46a1fc6237bccffe0ea82a641411 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Sun, 9 Jan 2022 22:40:45 -0800 Subject: [PATCH 12/20] [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 13/20] [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 14/20] =?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 15/20] [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 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 16/20] [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 45e987c6038db47199a560294c20a67da9ab49e3 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Wed, 1 Sep 2021 14:11:20 -0700 Subject: [PATCH 17/20] [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 cd1874eb179950de3f5b32e708b4a3a2d0619501 Mon Sep 17 00:00:00 2001 From: Mikhail Bodrov Date: Mon, 27 Dec 2021 22:09:36 +0200 Subject: [PATCH 18/20] [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 727ef5d34605108acb3513f72d5435972ed15b68 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Mon, 27 Dec 2021 19:15:57 -0800 Subject: [PATCH 19/20] [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 486aa46547b4e878d6e87183de95dd26d46fb020 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Mon, 10 Jan 2022 14:33:08 -0800 Subject: [PATCH 20/20] 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"