Hello community, here is the log from the commit of package nodejs-qs for openSUSE:Factory checked in at 2015-06-30 10:17:56 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/nodejs-qs (Old) and /work/SRC/openSUSE:Factory/.nodejs-qs.new (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "nodejs-qs" Changes: -------- --- /work/SRC/openSUSE:Factory/nodejs-qs/nodejs-qs.changes 2015-04-27 13:02:44.000000000 +0200 +++ /work/SRC/openSUSE:Factory/.nodejs-qs.new/nodejs-qs.changes 2015-06-30 10:17:56.000000000 +0200 @@ -1,0 +2,5 @@ +Sat Jun 27 06:48:20 UTC 2015 - [email protected] + +- update version 3.1.0 + +------------------------------------------------------------------- Old: ---- qs-2.2.4.tgz New: ---- qs-3.1.0.tgz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ nodejs-qs.spec ++++++ --- /var/tmp/diff_new_pack.88MgMY/_old 2015-06-30 10:17:57.000000000 +0200 +++ /var/tmp/diff_new_pack.88MgMY/_new 2015-06-30 10:17:57.000000000 +0200 @@ -19,7 +19,7 @@ %define base_name qs Name: nodejs-qs -Version: 2.2.4 +Version: 3.1.0 Release: 0 Summary: A querystring parsing and stringifying library with some added security License: BSD-2-Clause @@ -37,13 +37,12 @@ %prep %setup -q -n package -chmod 0644 package.json index.js lib/*js README.md LICENSE %build %install mkdir -p %{buildroot}%{nodejs_modulesdir}/%{base_name} -cp -pr package.json index.js lib \ +cp -pr bower.json package.json index.js lib \ %{buildroot}%{nodejs_modulesdir}/%{base_name}/ %files ++++++ qs-2.2.4.tgz -> qs-3.1.0.tgz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/package/.eslintignore new/package/.eslintignore --- old/package/.eslintignore 1970-01-01 01:00:00.000000000 +0100 +++ new/package/.eslintignore 2015-05-22 21:40:29.000000000 +0200 @@ -0,0 +1 @@ +dist diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/package/.npmignore new/package/.npmignore --- old/package/.npmignore 2014-08-05 00:17:36.000000000 +0200 +++ new/package/.npmignore 2015-05-22 21:28:05.000000000 +0200 @@ -16,3 +16,4 @@ coverage.* lib-cov complexity.md +dist diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/package/.travis.yml new/package/.travis.yml --- old/package/.travis.yml 2014-07-31 22:30:14.000000000 +0200 +++ new/package/.travis.yml 2015-03-12 18:22:27.000000000 +0100 @@ -1,4 +1,6 @@ language: node_js node_js: - - 0.10 \ No newline at end of file + - 0.10 + - 0.12 + - iojs diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/package/CHANGELOG.md new/package/CHANGELOG.md --- old/package/CHANGELOG.md 2014-09-05 20:43:09.000000000 +0200 +++ new/package/CHANGELOG.md 2015-05-27 18:11:17.000000000 +0200 @@ -1,4 +1,46 @@ +## [**3.1.0**](https://github.com/hapijs/qs/issues?milestone=24&state=open) +- [**#89**](https://github.com/hapijs/qs/issues/89) Add option to disable "Transform dot notation to bracket notation" + +## [**3.0.0**](https://github.com/hapijs/qs/issues?milestone=23&state=closed) +- [**#77**](https://github.com/hapijs/qs/issues/77) Perf boost +- [**#60**](https://github.com/hapijs/qs/issues/60) Add explicit option to disable array parsing +- [**#80**](https://github.com/hapijs/qs/issues/80) qs.parse silently drops properties +- [**#74**](https://github.com/hapijs/qs/issues/74) Bad parse when turning array into object +- [**#81**](https://github.com/hapijs/qs/issues/81) Add a `filter` option +- [**#68**](https://github.com/hapijs/qs/issues/68) Fixed issue with recursion and passing strings into objects. +- [**#66**](https://github.com/hapijs/qs/issues/66) Add mixed array and object dot notation support Closes: #47 +- [**#76**](https://github.com/hapijs/qs/issues/76) RFC 3986 +- [**#85**](https://github.com/hapijs/qs/issues/85) No equal sign +- [**#84**](https://github.com/hapijs/qs/issues/84) update license attribute + +## [**2.4.1**](https://github.com/hapijs/qs/issues?milestone=20&state=closed) +- [**#73**](https://github.com/hapijs/qs/issues/73) Property 'hasOwnProperty' of object #<Object> is not a function + +## [**2.4.0**](https://github.com/hapijs/qs/issues?milestone=19&state=closed) +- [**#70**](https://github.com/hapijs/qs/issues/70) Add arrayFormat option + +## [**2.3.3**](https://github.com/hapijs/qs/issues?milestone=18&state=closed) +- [**#59**](https://github.com/hapijs/qs/issues/59) make sure array indexes are >= 0, closes #57 +- [**#58**](https://github.com/hapijs/qs/issues/58) make qs usable for browser loader + +## [**2.3.2**](https://github.com/hapijs/qs/issues?milestone=17&state=closed) +- [**#55**](https://github.com/hapijs/qs/issues/55) allow merging a string into an object + +## [**2.3.1**](https://github.com/hapijs/qs/issues?milestone=16&state=closed) +- [**#52**](https://github.com/hapijs/qs/issues/52) Return "undefined" and "false" instead of throwing "TypeError". + +## [**2.3.0**](https://github.com/hapijs/qs/issues?milestone=15&state=closed) +- [**#50**](https://github.com/hapijs/qs/issues/50) add option to omit array indices, closes #46 + +## [**2.2.5**](https://github.com/hapijs/qs/issues?milestone=14&state=closed) +- [**#39**](https://github.com/hapijs/qs/issues/39) Is there an alternative to Buffer.isBuffer? +- [**#49**](https://github.com/hapijs/qs/issues/49) refactor utils.merge, fixes #45 +- [**#41**](https://github.com/hapijs/qs/issues/41) avoid browserifying Buffer, for #39 + +## [**2.2.4**](https://github.com/hapijs/qs/issues?milestone=13&state=closed) +- [**#38**](https://github.com/hapijs/qs/issues/38) how to handle object keys beginning with a number + ## [**2.2.3**](https://github.com/hapijs/qs/issues?milestone=12&state=closed) - [**#37**](https://github.com/hapijs/qs/issues/37) parser discards first empty value in array - [**#36**](https://github.com/hapijs/qs/issues/36) Update to lab 4.x @@ -13,9 +55,9 @@ - [**#31**](https://github.com/hapijs/qs/issues/31) qs.parse stackoverflow on circular objects ## [**2.2.0**](https://github.com/hapijs/qs/issues?milestone=9&state=closed) -- [**#26**](https://github.com/hapijs/qs/issues/26) Don't use Buffer global if it's not present +- [**#26**](https://github.com/hapijs/qs/issues/26) Don't use Buffer global if it's not present - [**#30**](https://github.com/hapijs/qs/issues/30) Bug when merging non-object values into arrays -- [**#29**](https://github.com/hapijs/qs/issues/29) Don't call Utils.clone at the top of Utils.merge +- [**#29**](https://github.com/hapijs/qs/issues/29) Don't call Utils.clone at the top of Utils.merge - [**#23**](https://github.com/hapijs/qs/issues/23) Ability to not limit parameters? ## [**2.1.0**](https://github.com/hapijs/qs/issues?milestone=8&state=closed) @@ -27,7 +69,7 @@ - [**#21**](https://github.com/hapijs/qs/issues/21) make all limits optional, for #18, for #20 ## [**1.2.2**](https://github.com/hapijs/qs/issues?milestone=6&state=closed) -- [**#19**](https://github.com/hapijs/qs/issues/19) Don't overwrite null values +- [**#19**](https://github.com/hapijs/qs/issues/19) Don't overwrite null values ## [**1.2.1**](https://github.com/hapijs/qs/issues?milestone=5&state=closed) - [**#16**](https://github.com/hapijs/qs/issues/16) ignore non-string delimiters @@ -44,4 +86,3 @@ ## [**1.0.2**](https://github.com/hapijs/qs/issues?milestone=2&state=closed) - [**#5**](https://github.com/hapijs/qs/issues/5) array holes incorrectly copied into object on large index - diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/package/Makefile new/package/Makefile --- old/package/Makefile 2014-07-31 22:30:14.000000000 +0200 +++ new/package/Makefile 2014-11-14 01:30:20.000000000 +0100 @@ -1,8 +1,8 @@ test: - @node node_modules/lab/bin/lab + @node node_modules/lab/bin/lab -a code -L test-cov: - @node node_modules/lab/bin/lab -t 100 + @node node_modules/lab/bin/lab -a code -t 100 -L test-cov-html: - @node node_modules/lab/bin/lab -r html -o coverage.html + @node node_modules/lab/bin/lab -a code -L -r html -o coverage.html -.PHONY: test test-cov test-cov-html \ No newline at end of file +.PHONY: test test-cov test-cov-html diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/package/README.md new/package/README.md --- old/package/README.md 2014-08-26 00:44:38.000000000 +0200 +++ new/package/README.md 2015-05-22 21:21:42.000000000 +0200 @@ -23,7 +23,7 @@ Qs.parse(string, [options]); ``` -**qs** allows you to create nested objects within your query strings, by surrounding the name of sub-keys with square brackets `[]`. +**qs** allows you to create nested objects within your query strings, by surrounding the name of sub-keys with square brackets `[]`, or prefixing the sub-key with a dot `.`. For example, the string `'foo[bar]=baz'` converts to: ```javascript @@ -34,6 +34,13 @@ } ``` +The parsed value is returned as a plain object, created via `Object.create(null)` and as such you should be aware that prototype methods do not exist on it and a user may set those names to whatever value they like: + +```javascript +Qs.parse('a.hasOwnProperty=b'); +// { a: { hasOwnProperty: 'b' } } +``` + URI encoded strings work too: ```javascript @@ -153,6 +160,13 @@ // { a: { '1': 'b' } } ``` +To disable array parsing entirely, set `parseArrays` to `false`. + +```javascript +Qs.parse('a[]=b', { parseArrays: false }); +// { a: { '0': 'b' } } +``` + If you mix notations, **qs** will merge the two items into an object: ```javascript @@ -184,13 +198,31 @@ Examples beyond this point will be shown as though the output is not URI encoded for clarity. Please note that the return values in these cases *will* be URI encoded during real usage. -When arrays are stringified, they are always given explicit indices: +When arrays are stringified, by default they are given explicit indices: ```javascript Qs.stringify({ a: ['b', 'c', 'd'] }); // 'a[0]=b&a[1]=c&a[2]=d' ``` +You may override this by setting the `indices` option to `false`: + +```javascript +Qs.stringify({ a: ['b', 'c', 'd'] }, { indices: false }); +// 'a=b&a=c&a=d' +``` + +You may use the `arrayFormat` option to specify the format of the output array + +```javascript +Qs.stringify({ a: ['b', 'c'] }, { arrayFormat: 'indices' }) +// 'a[0]=b&a[1]=c' +Qs.stringify({ a: ['b', 'c'] }, { arrayFormat: 'brackets' }) +// 'a[]=b&a[]=c' +Qs.stringify({ a: ['b', 'c'] }, { arrayFormat: 'repeat' }) +// 'a=b&a=c' +``` + Empty strings and null values will omit the value, but the equals sign (=) remains in place: ```javascript @@ -211,3 +243,61 @@ Qs.stringify({ a: 'b', c: 'd' }, { delimiter: ';' }); // 'a=b;c=d' ``` + +Finally, you can use the `filter` option to restrict which keys will be included in the stringified output. +If you pass a function, it will be called for each key to obtain the replacement value. Otherwise, if you +pass an array, it will be used to select properties and array indices for stringification: + +```javascript +function filterFunc(prefix, value) { + if (prefix == 'b') { + // Return an `undefined` value to omit a property. + return; + } + if (prefix == 'e[f]') { + return value.getTime(); + } + if (prefix == 'e[g][0]') { + return value * 2; + } + return value; +} +Qs.stringify({ a: 'b', c: 'd', e: { f: new Date(123), g: [2] } }, { filter: filterFunc }) +// 'a=b&c=d&e[f]=123&e[g][0]=4' +Qs.stringify({ a: 'b', c: 'd', e: 'f' }, { filter: ['a', 'e'] }) +// 'a=b&e=f' +Qs.stringify({ a: ['b', 'c', 'd'], e: 'f' }, { filter: ['a', 0, 2] }) +// 'a[0]=b&a[2]=d' +``` + +### Handling of `null` values + +By default, `null` values are treated like empty strings: + +```javascript +Qs.stringify({ a: null, b: '' }); +// 'a=&b=' +``` + +Parsing does not distinguish between parameters with and without equal signs. Both are converted to empty strings. + +```javascript +Qs.parse('a&b=') +// { a: '', b: '' } +``` + +To distinguish between `null` values and empty strings use the `strictNullHandling` flag. In the result string the `null` +values have no `=` sign: + +```javascript +Qs.stringify({ a: null, b: '' }, { strictNullHandling: true }); +// 'a&b=' +``` + +To parse values without `=` back to `null` use the `strictNullHandling` flag: + +```javascript +Qs.parse('a&b=', { strictNullHandling: true }); +// { a: null, b: '' } + +``` diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/package/bower.json new/package/bower.json --- old/package/bower.json 1970-01-01 01:00:00.000000000 +0100 +++ new/package/bower.json 2015-05-22 21:30:35.000000000 +0200 @@ -0,0 +1,22 @@ +{ + "name": "qs", + "main": "dist/qs.js", + "version": "3.0.0", + "homepage": "https://github.com/hapijs/qs", + "authors": [ + "Nathan LaFreniere <[email protected]>" + ], + "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 -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/package/index.js new/package/index.js --- old/package/index.js 2014-07-31 00:45:01.000000000 +0200 +++ new/package/index.js 2014-11-14 01:30:20.000000000 +0100 @@ -1 +1 @@ -module.exports = require('./lib'); +module.exports = require('./lib/'); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/package/lib/parse.js new/package/lib/parse.js --- old/package/lib/parse.js 2014-09-19 00:56:41.000000000 +0200 +++ new/package/lib/parse.js 2015-05-27 18:01:47.000000000 +0200 @@ -9,7 +9,8 @@ delimiter: '&', depth: 5, arrayLimit: 20, - parameterLimit: 1000 + parameterLimit: 1000, + strictNullHandling: false }; @@ -24,12 +25,16 @@ if (pos === -1) { obj[Utils.decode(part)] = ''; + + if (options.strictNullHandling) { + obj[Utils.decode(part)] = null; + } } else { var key = Utils.decode(part.slice(0, pos)); var val = Utils.decode(part.slice(pos + 1)); - if (!obj.hasOwnProperty(key)) { + if (!Object.prototype.hasOwnProperty.call(obj, key)) { obj[key] = val; } else { @@ -50,19 +55,22 @@ var root = chain.shift(); - var obj = {}; + var obj; if (root === '[]') { obj = []; obj = obj.concat(internals.parseObject(chain, val, options)); } else { + obj = Object.create(null); var cleanRoot = root[0] === '[' && root[root.length - 1] === ']' ? root.slice(1, root.length - 1) : root; var index = parseInt(cleanRoot, 10); var indexString = '' + index; if (!isNaN(index) && root !== cleanRoot && indexString === cleanRoot && - index <= options.arrayLimit) { + index >= 0 && + (options.parseArrays && + index <= options.arrayLimit)) { obj = []; obj[index] = internals.parseObject(chain, val, options); @@ -82,6 +90,12 @@ return; } + // Transform dot notation to bracket notation + + if (options.allowDots) { + key = key.replace(/\.([^\.\[]+)/g, '[$1]'); + } + // The regex chunks var parent = /^([^\[\]]*)/; @@ -91,12 +105,6 @@ var segment = parent.exec(key); - // Don't allow them to overwrite object prototype properties - - if (Object.prototype.hasOwnProperty(segment[1])) { - return; - } - // Stash the parent if it exists var keys = []; @@ -110,9 +118,7 @@ while ((segment = child.exec(key)) !== null && i < options.depth) { ++i; - if (!Object.prototype.hasOwnProperty(segment[1].replace(/\[|\]/g, ''))) { - keys.push(segment[1]); - } + keys.push(segment[1]); } // If there's a remainder, just add whatever is left @@ -131,17 +137,21 @@ str === null || typeof str === 'undefined') { - return {}; + return Object.create(null); } options = options || {}; options.delimiter = typeof options.delimiter === 'string' || Utils.isRegExp(options.delimiter) ? options.delimiter : internals.delimiter; options.depth = typeof options.depth === 'number' ? options.depth : internals.depth; options.arrayLimit = typeof options.arrayLimit === 'number' ? options.arrayLimit : internals.arrayLimit; + options.parseArrays = options.parseArrays !== false; + options.allowDots = options.allowDots !== false; options.parameterLimit = typeof options.parameterLimit === 'number' ? options.parameterLimit : internals.parameterLimit; + options.strictNullHandling = typeof options.strictNullHandling === 'boolean' ? options.strictNullHandling : internals.strictNullHandling; + var tempObj = typeof str === 'string' ? internals.parseValues(str, options) : str; - var obj = {}; + var obj = Object.create(null); // Iterate over the keys and setup the new object diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/package/lib/stringify.js new/package/lib/stringify.js --- old/package/lib/stringify.js 2014-08-29 20:29:59.000000000 +0200 +++ new/package/lib/stringify.js 2015-05-22 20:53:21.000000000 +0200 @@ -6,19 +6,41 @@ // Declare internals var internals = { - delimiter: '&' + delimiter: '&', + arrayPrefixGenerators: { + brackets: function (prefix, key) { + + return prefix + '[]'; + }, + indices: function (prefix, key) { + + return prefix + '[' + key + ']'; + }, + repeat: function (prefix, key) { + + return prefix; + } + }, + strictNullHandling: false }; -internals.stringify = function (obj, prefix) { +internals.stringify = function (obj, prefix, generateArrayPrefix, strictNullHandling, filter) { - if (Utils.isBuffer(obj)) { + if (typeof filter === 'function') { + obj = filter(prefix, obj); + } + else if (Utils.isBuffer(obj)) { obj = obj.toString(); } else if (obj instanceof Date) { obj = obj.toISOString(); } else if (obj === null) { + if (strictNullHandling) { + return Utils.encode(prefix); + } + obj = ''; } @@ -26,14 +48,24 @@ typeof obj === 'number' || typeof obj === 'boolean') { - return [encodeURIComponent(prefix) + '=' + encodeURIComponent(obj)]; + return [Utils.encode(prefix) + '=' + Utils.encode(obj)]; } var values = []; - for (var key in obj) { - if (obj.hasOwnProperty(key)) { - values = values.concat(internals.stringify(obj[key], prefix + '[' + key + ']')); + if (typeof obj === 'undefined') { + return values; + } + + var objKeys = Array.isArray(filter) ? filter : Object.keys(obj); + for (var i = 0, il = objKeys.length; i < il; ++i) { + var key = objKeys[i]; + + if (Array.isArray(obj)) { + values = values.concat(internals.stringify(obj[key], generateArrayPrefix(prefix, key), generateArrayPrefix, strictNullHandling, filter)); + } + else { + values = values.concat(internals.stringify(obj[key], prefix + '[' + key + ']', generateArrayPrefix, strictNullHandling, filter)); } } @@ -45,13 +77,44 @@ options = options || {}; var delimiter = typeof options.delimiter === 'undefined' ? internals.delimiter : options.delimiter; + var strictNullHandling = typeof options.strictNullHandling === 'boolean' ? options.strictNullHandling : internals.strictNullHandling; + var objKeys; + var filter; + if (typeof options.filter === 'function') { + filter = options.filter; + obj = filter('', obj); + } + else if (Array.isArray(options.filter)) { + objKeys = filter = options.filter; + } var keys = []; - for (var key in obj) { - if (obj.hasOwnProperty(key)) { - keys = keys.concat(internals.stringify(obj[key], key)); - } + if (typeof obj !== 'object' || + obj === null) { + + return ''; + } + + var arrayFormat; + if (options.arrayFormat in internals.arrayPrefixGenerators) { + arrayFormat = options.arrayFormat; + } + else if ('indices' in options) { + arrayFormat = options.indices ? 'indices' : 'repeat'; + } + else { + arrayFormat = 'indices'; + } + + var generateArrayPrefix = internals.arrayPrefixGenerators[arrayFormat]; + + if (!objKeys) { + objKeys = Object.keys(obj); + } + for (var i = 0, il = objKeys.length; i < il; ++i) { + var key = objKeys[i]; + keys = keys.concat(internals.stringify(obj[key], key, generateArrayPrefix, strictNullHandling, filter)); } return keys.join(delimiter); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/package/lib/utils.js new/package/lib/utils.js --- old/package/lib/utils.js 2014-08-29 22:34:26.000000000 +0200 +++ new/package/lib/utils.js 2015-05-22 21:04:30.000000000 +0200 @@ -4,11 +4,15 @@ // Declare internals var internals = {}; +internals.hexTable = new Array(256); +for (var i = 0; i < 256; ++i) { + internals.hexTable[i] = '%' + ((i < 16 ? '0' : '') + i.toString(16)).toUpperCase(); +} exports.arrayToObject = function (source) { - var obj = {}; + var obj = Object.create(null); for (var i = 0, il = source.length; i < il; ++i) { if (typeof source[i] !== 'undefined') { @@ -26,29 +30,29 @@ return target; } - if (Array.isArray(source)) { - for (var i = 0, il = source.length; i < il; ++i) { - if (typeof source[i] !== 'undefined') { - if (typeof target[i] === 'object') { - target[i] = exports.merge(target[i], source[i]); - } - else { - target[i] = source[i]; - } - } + if (typeof source !== 'object') { + if (Array.isArray(target)) { + target.push(source); + } + else if (typeof target === 'object') { + target[source] = true; + } + else { + target = [target, source]; } return target; } - if (Array.isArray(target)) { - if (typeof source !== 'object') { - target.push(source); - return target; - } - else { - target = exports.arrayToObject(target); - } + if (typeof target !== 'object') { + target = [target].concat(source); + return target; + } + + if (Array.isArray(target) && + !Array.isArray(source)) { + + target = exports.arrayToObject(target); } var keys = Object.keys(source); @@ -56,18 +60,11 @@ var key = keys[k]; var value = source[key]; - if (value && - typeof value === 'object') { - - if (!target[key]) { - target[key] = value; - } - else { - target[key] = exports.merge(target[key], value); - } + if (!target[key]) { + target[key] = value; } else { - target[key] = value; + target[key] = exports.merge(target[key], value); } } @@ -84,6 +81,56 @@ } }; +exports.encode = function (str) { + + // This code was originally written by Brian White (mscdex) for the io.js core querystring library. + // It has been adapted here for stricter adherence to RFC 3986 + if (str.length === 0) { + return str; + } + + if (typeof str !== 'string') { + str = '' + str; + } + + var out = ''; + for (var i = 0, il = str.length; i < il; ++i) { + var c = str.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 + + out += str[i]; + continue; + } + + if (c < 0x80) { + out += internals.hexTable[c]; + continue; + } + + if (c < 0x800) { + out += internals.hexTable[0xC0 | (c >> 6)] + internals.hexTable[0x80 | (c & 0x3F)]; + continue; + } + + if (c < 0xD800 || c >= 0xE000) { + out += internals.hexTable[0xE0 | (c >> 12)] + internals.hexTable[0x80 | ((c >> 6) & 0x3F)] + internals.hexTable[0x80 | (c & 0x3F)]; + continue; + } + + ++i; + c = 0x10000 + (((c & 0x3FF) << 10) | (str.charCodeAt(i) & 0x3FF)); + out += internals.hexTable[0xF0 | (c >> 18)] + internals.hexTable[0x80 | ((c >> 12) & 0x3F)] + internals.hexTable[0x80 | ((c >> 6) & 0x3F)] + internals.hexTable[0x80 | (c & 0x3F)]; + } + + return out; +}; exports.compact = function (obj, refs) { @@ -104,7 +151,7 @@ if (Array.isArray(obj)) { var compacted = []; - for (var i = 0, l = obj.length; i < l; ++i) { + for (var i = 0, il = obj.length; i < il; ++i) { if (typeof obj[i] !== 'undefined') { compacted.push(obj[i]); } @@ -114,7 +161,7 @@ } var keys = Object.keys(obj); - for (var i = 0, il = keys.length; i < il; ++i) { + for (i = 0, il = keys.length; i < il; ++i) { var key = keys[i]; obj[key] = exports.compact(obj[key], refs); } @@ -124,16 +171,20 @@ exports.isRegExp = function (obj) { + return Object.prototype.toString.call(obj) === '[object RegExp]'; }; exports.isBuffer = function (obj) { - if (typeof Buffer !== 'undefined') { - return Buffer.isBuffer(obj); - } - else { + if (obj === null || + typeof obj === 'undefined') { + return false; } + + return !!(obj.constructor && + obj.constructor.isBuffer && + obj.constructor.isBuffer(obj)); }; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/package/package.json new/package/package.json --- old/package/package.json 2014-09-19 01:00:08.000000000 +0200 +++ new/package/package.json 2015-05-27 18:07:37.000000000 +0200 @@ -1,15 +1,18 @@ { "name": "qs", - "version": "2.2.4", + "version": "3.1.0", "description": "A querystring parser that supports nesting and arrays, with a depth limit", "homepage": "https://github.com/hapijs/qs", "main": "index.js", "dependencies": {}, "devDependencies": { - "lab": "4.x.x" + "browserify": "^10.2.1", + "code": "1.x.x", + "lab": "5.x.x" }, "scripts": { - "test": "make test-cov" + "test": "make test-cov", + "dist": "browserify --standalone Qs index.js > dist/qs.js" }, "repository": { "type": "git", @@ -19,11 +22,5 @@ "querystring", "qs" ], - "author": "Nathan LaFreniere <[email protected]>", - "licenses": [ - { - "type": "BSD", - "url": "http://github.com/hapijs/qs/raw/master/LICENSE" - } - ] + "license": "BSD-3-Clause" } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/package/test/parse.js new/package/test/parse.js --- old/package/test/parse.js 2014-09-19 00:56:22.000000000 +0200 +++ new/package/test/parse.js 2015-05-27 18:06:08.000000000 +0200 @@ -1,5 +1,7 @@ +/* eslint no-extend-native:0 */ // Load modules +var Code = require('code'); var Lab = require('lab'); var Qs = require('../'); @@ -12,168 +14,203 @@ // Test shortcuts var lab = exports.lab = Lab.script(); -var expect = Lab.expect; -var before = lab.before; -var after = lab.after; +var expect = Code.expect; var describe = lab.experiment; var it = lab.test; -describe('#parse', function () { +describe('parse()', function () { it('parses a simple string', function (done) { - expect(Qs.parse('0=foo')).to.deep.equal({ '0': 'foo' }); - expect(Qs.parse('foo=c++')).to.deep.equal({ foo: 'c ' }); - expect(Qs.parse('a[>=]=23')).to.deep.equal({ a: { '>=': '23' } }); - expect(Qs.parse('a[<=>]==23')).to.deep.equal({ a: { '<=>': '=23' } }); - expect(Qs.parse('a[==]=23')).to.deep.equal({ a: { '==': '23' } }); - expect(Qs.parse('foo')).to.deep.equal({ foo: '' }); - expect(Qs.parse('foo=bar')).to.deep.equal({ foo: 'bar' }); - expect(Qs.parse(' foo = bar = baz ')).to.deep.equal({ ' foo ': ' bar = baz ' }); - expect(Qs.parse('foo=bar=baz')).to.deep.equal({ foo: 'bar=baz' }); - expect(Qs.parse('foo=bar&bar=baz')).to.deep.equal({ foo: 'bar', bar: 'baz' }); - expect(Qs.parse('foo=bar&baz')).to.deep.equal({ foo: 'bar', baz: '' }); + expect(Qs.parse('0=foo')).to.deep.equal({ '0': 'foo' }, { prototype: false }); + expect(Qs.parse('foo=c++')).to.deep.equal({ foo: 'c ' }, { prototype: false }); + expect(Qs.parse('a[>=]=23')).to.deep.equal({ a: { '>=': '23' } }, { prototype: false }); + expect(Qs.parse('a[<=>]==23')).to.deep.equal({ a: { '<=>': '=23' } }, { prototype: false }); + expect(Qs.parse('a[==]=23')).to.deep.equal({ a: { '==': '23' } }, { prototype: false }); + expect(Qs.parse('foo', {strictNullHandling: true})).to.deep.equal({ foo: null }, { prototype: false }); + expect(Qs.parse('foo' )).to.deep.equal({ foo: '' }, { prototype: false }); + expect(Qs.parse('foo=')).to.deep.equal({ foo: '' }, { prototype: false }); + expect(Qs.parse('foo=bar')).to.deep.equal({ foo: 'bar' }, { prototype: false }); + expect(Qs.parse(' foo = bar = baz ')).to.deep.equal({ ' foo ': ' bar = baz ' }, { prototype: false }); + expect(Qs.parse('foo=bar=baz')).to.deep.equal({ foo: 'bar=baz' }, { prototype: false }); + expect(Qs.parse('foo=bar&bar=baz')).to.deep.equal({ foo: 'bar', bar: 'baz' }, { prototype: false }); + expect(Qs.parse('foo2=bar2&baz2=')).to.deep.equal({ foo2: 'bar2', baz2: '' }, { prototype: false }); + expect(Qs.parse('foo=bar&baz', {strictNullHandling: true})).to.deep.equal({ foo: 'bar', baz: null }, { prototype: false }); + expect(Qs.parse('foo=bar&baz')).to.deep.equal({ foo: 'bar', baz: '' }, { prototype: false }); expect(Qs.parse('cht=p3&chd=t:60,40&chs=250x100&chl=Hello|World')).to.deep.equal({ cht: 'p3', chd: 't:60,40', chs: '250x100', chl: 'Hello|World' - }); + }, { prototype: false }); + done(); + }); + + it('allows disabling dot notation', function (done) { + + expect(Qs.parse('a.b=c')).to.deep.equal({ a: { b: 'c' } }, { prototype: false }); + expect(Qs.parse('a.b=c', { allowDots: false })).to.deep.equal({ 'a.b': 'c' }, { prototype: false }); done(); }); it('parses a single nested string', function (done) { - expect(Qs.parse('a[b]=c')).to.deep.equal({ a: { b: 'c' } }); + expect(Qs.parse('a[b]=c')).to.deep.equal({ a: { b: 'c' } }, { prototype: false }); done(); }); it('parses a double nested string', function (done) { - expect(Qs.parse('a[b][c]=d')).to.deep.equal({ a: { b: { c: 'd' } } }); + expect(Qs.parse('a[b][c]=d')).to.deep.equal({ a: { b: { c: 'd' } } }, { prototype: false }); done(); }); it('defaults to a depth of 5', function (done) { - expect(Qs.parse('a[b][c][d][e][f][g][h]=i')).to.deep.equal({ a: { b: { c: { d: { e: { f: { '[g][h]': 'i' } } } } } } }); + expect(Qs.parse('a[b][c][d][e][f][g][h]=i')).to.deep.equal({ a: { b: { c: { d: { e: { f: { '[g][h]': 'i' } } } } } } }, { prototype: false }); done(); }); it('only parses one level when depth = 1', function (done) { - expect(Qs.parse('a[b][c]=d', { depth: 1 })).to.deep.equal({ a: { b: { '[c]': 'd' } } }); - expect(Qs.parse('a[b][c][d]=e', { depth: 1 })).to.deep.equal({ a: { b: { '[c][d]': 'e' } } }); + expect(Qs.parse('a[b][c]=d', { depth: 1 })).to.deep.equal({ a: { b: { '[c]': 'd' } } }, { prototype: false }); + expect(Qs.parse('a[b][c][d]=e', { depth: 1 })).to.deep.equal({ a: { b: { '[c][d]': 'e' } } }, { prototype: false }); done(); }); it('parses a simple array', function (done) { - expect(Qs.parse('a=b&a=c')).to.deep.equal({ a: ['b', 'c'] }); + expect(Qs.parse('a=b&a=c')).to.deep.equal({ a: ['b', 'c'] }, { prototype: false }); done(); }); it('parses an explicit array', function (done) { - expect(Qs.parse('a[]=b')).to.deep.equal({ a: ['b'] }); - expect(Qs.parse('a[]=b&a[]=c')).to.deep.equal({ a: ['b', 'c'] }); - expect(Qs.parse('a[]=b&a[]=c&a[]=d')).to.deep.equal({ a: ['b', 'c', 'd'] }); + expect(Qs.parse('a[]=b')).to.deep.equal({ a: ['b'] }, { prototype: false }); + expect(Qs.parse('a[]=b&a[]=c')).to.deep.equal({ a: ['b', 'c'] }, { prototype: false }); + expect(Qs.parse('a[]=b&a[]=c&a[]=d')).to.deep.equal({ a: ['b', 'c', 'd'] }, { prototype: false }); + done(); + }); + + it('parses a mix of simple and explicit arrays', function (done) { + + expect(Qs.parse('a=b&a[]=c')).to.deep.equal({ a: ['b', 'c'] }, { prototype: false }); + expect(Qs.parse('a[]=b&a=c')).to.deep.equal({ a: ['b', 'c'] }, { prototype: false }); + expect(Qs.parse('a[0]=b&a=c')).to.deep.equal({ a: ['b', 'c'] }, { prototype: false }); + expect(Qs.parse('a=b&a[0]=c')).to.deep.equal({ a: ['b', 'c'] }, { prototype: false }); + expect(Qs.parse('a[1]=b&a=c')).to.deep.equal({ a: ['b', 'c'] }, { prototype: false }); + expect(Qs.parse('a=b&a[1]=c')).to.deep.equal({ a: ['b', 'c'] }, { prototype: false }); done(); }); it('parses a nested array', function (done) { - expect(Qs.parse('a[b][]=c&a[b][]=d')).to.deep.equal({ a: { b: ['c', 'd'] } }); - expect(Qs.parse('a[>=]=25')).to.deep.equal({ a: { '>=': '25' } }); + expect(Qs.parse('a[b][]=c&a[b][]=d')).to.deep.equal({ a: { b: ['c', 'd'] } }, { prototype: false }); + expect(Qs.parse('a[>=]=25')).to.deep.equal({ a: { '>=': '25' } }, { prototype: false }); done(); }); it('allows to specify array indices', function (done) { - expect(Qs.parse('a[1]=c&a[0]=b&a[2]=d')).to.deep.equal({ a: ['b', 'c', 'd'] }); - expect(Qs.parse('a[1]=c&a[0]=b')).to.deep.equal({ a: ['b', 'c'] }); - expect(Qs.parse('a[1]=c')).to.deep.equal({ a: ['c'] }); + expect(Qs.parse('a[1]=c&a[0]=b&a[2]=d')).to.deep.equal({ a: ['b', 'c', 'd'] }, { prototype: false }); + expect(Qs.parse('a[1]=c&a[0]=b')).to.deep.equal({ a: ['b', 'c'] }, { prototype: false }); + expect(Qs.parse('a[1]=c')).to.deep.equal({ a: ['c'] }, { prototype: false }); done(); }); it('limits specific array indices to 20', function (done) { - expect(Qs.parse('a[20]=a')).to.deep.equal({ a: ['a'] }); - expect(Qs.parse('a[21]=a')).to.deep.equal({ a: { '21': 'a' } }); + expect(Qs.parse('a[20]=a')).to.deep.equal({ a: ['a'] }, { prototype: false }); + expect(Qs.parse('a[21]=a')).to.deep.equal({ a: { '21': 'a' } }, { prototype: false }); done(); }); it('supports keys that begin with a number', function (done) { - expect(Qs.parse('a[12b]=c')).to.deep.equal({ a: { '12b': 'c' } }); + expect(Qs.parse('a[12b]=c')).to.deep.equal({ a: { '12b': 'c' } }, { prototype: false }); done(); }); it('supports encoded = signs', function (done) { - expect(Qs.parse('he%3Dllo=th%3Dere')).to.deep.equal({ 'he=llo': 'th=ere' }); + expect(Qs.parse('he%3Dllo=th%3Dere')).to.deep.equal({ 'he=llo': 'th=ere' }, { prototype: false }); done(); }); it('is ok with url encoded strings', function (done) { - expect(Qs.parse('a[b%20c]=d')).to.deep.equal({ a: { 'b c': 'd' } }); - expect(Qs.parse('a[b]=c%20d')).to.deep.equal({ a: { b: 'c d' } }); + expect(Qs.parse('a[b%20c]=d')).to.deep.equal({ a: { 'b c': 'd' } }, { prototype: false }); + expect(Qs.parse('a[b]=c%20d')).to.deep.equal({ a: { b: 'c d' } }, { prototype: false }); done(); }); it('allows brackets in the value', function (done) { - expect(Qs.parse('pets=["tobi"]')).to.deep.equal({ pets: '["tobi"]' }); - expect(Qs.parse('operators=[">=", "<="]')).to.deep.equal({ operators: '[">=", "<="]' }); + expect(Qs.parse('pets=["tobi"]')).to.deep.equal({ pets: '["tobi"]' }, { prototype: false }); + expect(Qs.parse('operators=[">=", "<="]')).to.deep.equal({ operators: '[">=", "<="]' }, { prototype: false }); done(); }); it('allows empty values', function (done) { - expect(Qs.parse('')).to.deep.equal({}); - expect(Qs.parse(null)).to.deep.equal({}); - expect(Qs.parse(undefined)).to.deep.equal({}); + expect(Qs.parse('')).to.deep.equal({}, { prototype: false }); + expect(Qs.parse(null)).to.deep.equal({}, { prototype: false }); + expect(Qs.parse(undefined)).to.deep.equal({}, { prototype: false }); done(); }); it('transforms arrays to objects', function (done) { - expect(Qs.parse('foo[0]=bar&foo[bad]=baz')).to.deep.equal({ foo: { '0': 'bar', bad: 'baz' } }); - expect(Qs.parse('foo[bad]=baz&foo[0]=bar')).to.deep.equal({ foo: { bad: 'baz', '0': 'bar' } }); - expect(Qs.parse('foo[bad]=baz&foo[]=bar')).to.deep.equal({ foo: { bad: 'baz', '0': 'bar' } }); - expect(Qs.parse('foo[]=bar&foo[bad]=baz')).to.deep.equal({ foo: { '0': 'bar', bad: 'baz' } }); - expect(Qs.parse('foo[bad]=baz&foo[]=bar&foo[]=foo')).to.deep.equal({ foo: { bad: 'baz', '0': 'bar', '1': 'foo' } }); - expect(Qs.parse('foo[0][a]=a&foo[0][b]=b&foo[1][a]=aa&foo[1][b]=bb')).to.deep.equal({foo: [ {a: 'a', b: 'b'}, {a: 'aa', b: 'bb'} ]}); + expect(Qs.parse('foo[0]=bar&foo[bad]=baz')).to.deep.equal({ foo: { '0': 'bar', bad: 'baz' } }, { prototype: false }); + expect(Qs.parse('foo[bad]=baz&foo[0]=bar')).to.deep.equal({ foo: { bad: 'baz', '0': 'bar' } }, { prototype: false }); + expect(Qs.parse('foo[bad]=baz&foo[]=bar')).to.deep.equal({ foo: { bad: 'baz', '0': 'bar' } }, { prototype: false }); + expect(Qs.parse('foo[]=bar&foo[bad]=baz')).to.deep.equal({ foo: { '0': 'bar', bad: 'baz' } }, { prototype: false }); + expect(Qs.parse('foo[bad]=baz&foo[]=bar&foo[]=foo')).to.deep.equal({ foo: { bad: 'baz', '0': 'bar', '1': 'foo' } }, { prototype: false }); + expect(Qs.parse('foo[0][a]=a&foo[0][b]=b&foo[1][a]=aa&foo[1][b]=bb')).to.deep.equal({foo: [ {a: 'a', b: 'b'}, {a: 'aa', b: 'bb'} ]}, { prototype: false }); + expect(Qs.parse('a[]=b&a[t]=u&a[hasOwnProperty]=c')).to.deep.equal({ a: { '0': 'b', t: 'u', hasOwnProperty: 'c' } }, { prototype: false }); + expect(Qs.parse('a[]=b&a[hasOwnProperty]=c&a[x]=y')).to.deep.equal({ a: { '0': 'b', hasOwnProperty: 'c', x: 'y' } }, { prototype: false }); done(); }); - it('correctly prunes undefined values when converting an array to an object', function (done) { + it('transforms arrays to objects (dot notation)', function (done) { - expect(Qs.parse('a[2]=b&a[99999999]=c')).to.deep.equal({ a: { '2': 'b', '99999999': 'c' } }); + expect(Qs.parse('foo[0].baz=bar&fool.bad=baz')).to.deep.equal({ foo: [ { baz: 'bar'} ], fool: { bad: 'baz' } }, { prototype: false }); + expect(Qs.parse('foo[0].baz=bar&fool.bad.boo=baz')).to.deep.equal({ foo: [ { baz: 'bar'} ], fool: { bad: { boo: 'baz' } } }, { prototype: false }); + expect(Qs.parse('foo[0][0].baz=bar&fool.bad=baz')).to.deep.equal({ foo: [[ { baz: 'bar'} ]], fool: { bad: 'baz' } }, { prototype: false }); + expect(Qs.parse('foo[0].baz[0]=15&foo[0].bar=2')).to.deep.equal({ foo: [{ baz: ['15'], bar: '2' }] }, { prototype: false }); + expect(Qs.parse('foo[0].baz[0]=15&foo[0].baz[1]=16&foo[0].bar=2')).to.deep.equal({ foo: [{ baz: ['15', '16'], bar: '2' }] }, { prototype: false }); + expect(Qs.parse('foo.bad=baz&foo[0]=bar')).to.deep.equal({ foo: { bad: 'baz', '0': 'bar' } }, { prototype: false }); + expect(Qs.parse('foo.bad=baz&foo[]=bar')).to.deep.equal({ foo: { bad: 'baz', '0': 'bar' } }, { prototype: false }); + expect(Qs.parse('foo[]=bar&foo.bad=baz')).to.deep.equal({ foo: { '0': 'bar', bad: 'baz' } }, { prototype: false }); + expect(Qs.parse('foo.bad=baz&foo[]=bar&foo[]=foo')).to.deep.equal({ foo: { bad: 'baz', '0': 'bar', '1': 'foo' } }, { prototype: false }); + expect(Qs.parse('foo[0].a=a&foo[0].b=b&foo[1].a=aa&foo[1].b=bb')).to.deep.equal({foo: [ {a: 'a', b: 'b'}, {a: 'aa', b: 'bb'} ]}, { prototype: false }); done(); }); - it('supports malformed uri characters', function (done) { + it('can add keys to objects', function (done) { - expect(Qs.parse('{%:%}')).to.deep.equal({ '{%:%}': '' }); - expect(Qs.parse('foo=%:%}')).to.deep.equal({ foo: '%:%}' }); + expect(Qs.parse('a[b]=c&a=d')).to.deep.equal({ a: { b: 'c', d: true } }, { prototype: false }); done(); }); - it('doesn\'t produce empty keys', function (done) { + it('correctly prunes undefined values when converting an array to an object', function (done) { + + expect(Qs.parse('a[2]=b&a[99999999]=c')).to.deep.equal({ a: { '2': 'b', '99999999': 'c' } }, { prototype: false }); + done(); + }); + + it('supports malformed uri characters', function (done) { - expect(Qs.parse('_r=1&')).to.deep.equal({ '_r': '1' }); + expect(Qs.parse('{%:%}', {strictNullHandling: true})).to.deep.equal({ '{%:%}': null }, { prototype: false }); + expect(Qs.parse('{%:%}=')).to.deep.equal({ '{%:%}': '' }, { prototype: false }); + expect(Qs.parse('foo=%:%}')).to.deep.equal({ foo: '%:%}' }, { prototype: false }); done(); }); - it('cannot override prototypes', function (done) { + it('doesn\'t produce empty keys', function (done) { - var obj = Qs.parse('toString=bad&bad[toString]=bad&constructor=bad'); - expect(typeof obj.toString).to.equal('function'); - expect(typeof obj.bad.toString).to.equal('function'); - expect(typeof obj.constructor).to.equal('function'); + expect(Qs.parse('_r=1&')).to.deep.equal({ '_r': '1' }, { prototype: false }); done(); }); @@ -187,43 +224,45 @@ it('parses arrays of objects', function (done) { - expect(Qs.parse('a[][b]=c')).to.deep.equal({ a: [{ b: 'c' }] }); - expect(Qs.parse('a[0][b]=c')).to.deep.equal({ a: [{ b: 'c' }] }); + expect(Qs.parse('a[][b]=c')).to.deep.equal({ a: [{ b: 'c' }] }, { prototype: false }); + expect(Qs.parse('a[0][b]=c')).to.deep.equal({ a: [{ b: 'c' }] }, { prototype: false }); done(); }); it('allows for empty strings in arrays', function (done) { - expect(Qs.parse('a[]=b&a[]=&a[]=c')).to.deep.equal({ a: ['b', '', 'c'] }); - expect(Qs.parse('a[0]=b&a[1]=&a[2]=c&a[19]=')).to.deep.equal({ a: ['b', '', 'c', ''] }); - expect(Qs.parse('a[]=&a[]=b&a[]=c')).to.deep.equal({ a: ['', 'b', 'c'] }); + expect(Qs.parse('a[]=b&a[]=&a[]=c')).to.deep.equal({ a: ['b', '', 'c'] }, { prototype: false }); + expect(Qs.parse('a[0]=b&a[1]&a[2]=c&a[19]=', {strictNullHandling: true})).to.deep.equal({ a: ['b', null, 'c', ''] }, { prototype: false }); + expect(Qs.parse('a[0]=b&a[1]=&a[2]=c&a[19]', {strictNullHandling: true})).to.deep.equal({ a: ['b', '', 'c', null] }, { prototype: false }); + expect(Qs.parse('a[]=&a[]=b&a[]=c')).to.deep.equal({ a: ['', 'b', 'c'] }, { prototype: false }); done(); }); it('compacts sparse arrays', function (done) { - expect(Qs.parse('a[10]=1&a[2]=2')).to.deep.equal({ a: ['2', '1'] }); + expect(Qs.parse('a[10]=1&a[2]=2')).to.deep.equal({ a: ['2', '1'] }, { prototype: false }); done(); }); it('parses semi-parsed strings', function (done) { - expect(Qs.parse({ 'a[b]': 'c' })).to.deep.equal({ a: { b: 'c' } }); - expect(Qs.parse({ 'a[b]': 'c', 'a[d]': 'e' })).to.deep.equal({ a: { b: 'c', d: 'e' } }); + expect(Qs.parse({ 'a[b]': 'c' })).to.deep.equal({ a: { b: 'c' } }, { prototype: false }); + expect(Qs.parse({ 'a[b]': 'c', 'a[d]': 'e' })).to.deep.equal({ a: { b: 'c', d: 'e' } }, { prototype: false }); done(); }); it('parses buffers correctly', function (done) { var b = new Buffer('test'); - expect(Qs.parse({ a: b })).to.deep.equal({ a: b }); + expect(Qs.parse({ a: b })).to.deep.equal({ a: b }, { prototype: false }); done(); }); it('continues parsing when no parent is found', function (done) { - expect(Qs.parse('[]&a=b')).to.deep.equal({ '0': '', a: 'b' }); - expect(Qs.parse('[foo]=bar')).to.deep.equal({ foo: 'bar' }); + expect(Qs.parse('[]=&a=b')).to.deep.equal({ '0': '', a: 'b' }, { prototype: false }); + expect(Qs.parse('[]&a=b', {strictNullHandling: true})).to.deep.equal({ '0': null, a: 'b' }, { prototype: false }); + expect(Qs.parse('[foo]=bar')).to.deep.equal({ foo: 'bar' }, { prototype: false }); done(); }); @@ -247,9 +286,9 @@ Object.prototype.crash = ''; Array.prototype.crash = ''; expect(Qs.parse.bind(null, 'a=b')).to.not.throw(); - expect(Qs.parse('a=b')).to.deep.equal({ a: 'b' }); + expect(Qs.parse('a=b')).to.deep.equal({ a: 'b' }, { prototype: false }); expect(Qs.parse.bind(null, 'a[][b]=c')).to.not.throw(); - expect(Qs.parse('a[][b]=c')).to.deep.equal({ a: [{ b: 'c' }] }); + expect(Qs.parse('a[][b]=c')).to.deep.equal({ a: [{ b: 'c' }] }, { prototype: false }); delete Object.prototype.crash; delete Array.prototype.crash; done(); @@ -257,77 +296,105 @@ it('parses a string with an alternative string delimiter', function (done) { - expect(Qs.parse('a=b;c=d', { delimiter: ';' })).to.deep.equal({ a: 'b', c: 'd' }); + expect(Qs.parse('a=b;c=d', { delimiter: ';' })).to.deep.equal({ a: 'b', c: 'd' }, { prototype: false }); done(); }); it('parses a string with an alternative RegExp delimiter', function (done) { - expect(Qs.parse('a=b; c=d', { delimiter: /[;,] */ })).to.deep.equal({ a: 'b', c: 'd' }); + expect(Qs.parse('a=b; c=d', { delimiter: /[;,] */ })).to.deep.equal({ a: 'b', c: 'd' }, { prototype: false }); done(); }); it('does not use non-splittable objects as delimiters', function (done) { - expect(Qs.parse('a=b&c=d', { delimiter: true })).to.deep.equal({ a: 'b', c: 'd' }); + expect(Qs.parse('a=b&c=d', { delimiter: true })).to.deep.equal({ a: 'b', c: 'd' }, { prototype: false }); done(); }); it('allows overriding parameter limit', function (done) { - expect(Qs.parse('a=b&c=d', { parameterLimit: 1 })).to.deep.equal({ a: 'b' }); + expect(Qs.parse('a=b&c=d', { parameterLimit: 1 })).to.deep.equal({ a: 'b' }, { prototype: false }); done(); }); it('allows setting the parameter limit to Infinity', function (done) { - expect(Qs.parse('a=b&c=d', { parameterLimit: Infinity })).to.deep.equal({ a: 'b', c: 'd' }); + expect(Qs.parse('a=b&c=d', { parameterLimit: Infinity })).to.deep.equal({ a: 'b', c: 'd' }, { prototype: false }); done(); }); it('allows overriding array limit', function (done) { - expect(Qs.parse('a[0]=b&a[1]=c', { arrayLimit: 0 })).to.deep.equal({ a: { '0': 'b', '1': 'c' } }); + expect(Qs.parse('a[0]=b', { arrayLimit: -1 })).to.deep.equal({ a: { '0': 'b' } }, { prototype: false }); + expect(Qs.parse('a[-1]=b', { arrayLimit: -1 })).to.deep.equal({ a: { '-1': 'b' } }, { prototype: false }); + expect(Qs.parse('a[0]=b&a[1]=c', { arrayLimit: 0 })).to.deep.equal({ a: { '0': 'b', '1': 'c' } }, { prototype: false }); + done(); + }); + + it('allows disabling array parsing', function (done) { + + expect(Qs.parse('a[0]=b&a[1]=c', { parseArrays: false })).to.deep.equal({ a: { '0': 'b', '1': 'c' } }, { prototype: false }); done(); }); it('parses an object', function (done) { var input = { - "user[name]": {"pop[bob]": 3}, - "user[email]": null + 'user[name]': {'pop[bob]': 3}, + 'user[email]': null + }; + + var expected = { + 'user': { + 'name': {'pop[bob]': 3}, + 'email': null + } + }; + + var result = Qs.parse(input); + + expect(result).to.deep.equal(expected, { prototype: false }); + done(); + }); + + it('parses an object in dot notation', function (done) { + + var input = { + 'user.name': {'pop[bob]': 3}, + 'user.email.': null }; var expected = { - "user": { - "name": {"pop[bob]": 3}, - "email": null + 'user': { + 'name': {'pop[bob]': 3}, + 'email': null } }; var result = Qs.parse(input); - expect(result).to.deep.equal(expected); + expect(result).to.deep.equal(expected, { prototype: false }); done(); }); it('parses an object and not child values', function (done) { var input = { - "user[name]": {"pop[bob]": { "test": 3 }}, - "user[email]": null + 'user[name]': {'pop[bob]': { 'test': 3 }}, + 'user[email]': null }; var expected = { - "user": { - "name": {"pop[bob]": { "test": 3 }}, - "email": null + 'user': { + 'name': {'pop[bob]': { 'test': 3 }}, + 'email': null } }; var result = Qs.parse(input); - expect(result).to.deep.equal(expected); + expect(result).to.deep.equal(expected, { prototype: false }); done(); }); @@ -335,14 +402,9 @@ var tempBuffer = global.Buffer; delete global.Buffer; - expect(Qs.parse('a=b&c=d')).to.deep.equal({ a: 'b', c: 'd' }); + var result = Qs.parse('a=b&c=d'); global.Buffer = tempBuffer; - done(); - }); - - it('does not crash when using invalid dot notation', function (done) { - - expect(Qs.parse('roomInfoList[0].childrenAges[0]=15&roomInfoList[0].numberOfAdults=2')).to.deep.equal({ roomInfoList: [['15', '2']] }); + expect(result).to.deep.equal({ a: 'b', c: 'd' }, { prototype: false }); done(); }); @@ -356,12 +418,12 @@ expect(function () { parsed = Qs.parse({ 'foo[bar]': 'baz', 'foo[baz]': a }); - }).to.not.throw(Error); + }).to.not.throw(); - expect(parsed).to.have.key('foo'); - expect(parsed.foo).to.have.keys('bar', 'baz'); + expect(parsed).to.contain('foo'); + expect(parsed.foo).to.contain('bar', 'baz'); expect(parsed.foo.bar).to.equal('baz'); - expect(parsed.foo.baz).to.deep.equal(a); + expect(parsed.foo.baz).to.deep.equal(a, { prototype: false }); done(); }); @@ -369,23 +431,25 @@ var a = Object.create(null); a.b = 'c'; - - expect(Qs.parse(a)).to.deep.equal({ b: 'c' }); - expect(Qs.parse({ a: a })).to.deep.equal({ a: { b: 'c' } }); + + expect(Qs.parse(a)).to.deep.equal({ b: 'c' }, { prototype: false }); + var result = Qs.parse({ a: a }); + expect(result).to.contain('a'); + expect(result.a).to.deep.equal(a, { prototype: false }); done(); }); it('parses dates correctly', function (done) { var now = new Date(); - expect(Qs.parse({ a: now })).to.deep.equal({ a: now }); + expect(Qs.parse({ a: now })).to.deep.equal({ a: now }, { prototype: false }); done(); }); it('parses regular expressions correctly', function (done) { var re = /^test$/; - expect(Qs.parse({ a: re })).to.deep.equal({ a: re }); + expect(Qs.parse({ a: re })).to.deep.equal({ a: re }, { prototype: false }); done(); }); }); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/package/test/stringify.js new/package/test/stringify.js --- old/package/test/stringify.js 2014-09-03 19:25:02.000000000 +0200 +++ new/package/test/stringify.js 2015-05-22 21:17:54.000000000 +0200 @@ -1,5 +1,7 @@ +/* eslint no-extend-native:0 */ // Load modules +var Code = require('code'); var Lab = require('lab'); var Qs = require('../'); @@ -12,20 +14,23 @@ // Test shortcuts var lab = exports.lab = Lab.script(); -var expect = Lab.expect; -var before = lab.before; -var after = lab.after; +var expect = Code.expect; var describe = lab.experiment; var it = lab.test; -describe('#stringify', function () { +describe('stringify()', function () { it('stringifies a querystring object', function (done) { expect(Qs.stringify({ a: 'b' })).to.equal('a=b'); expect(Qs.stringify({ a: 1 })).to.equal('a=1'); expect(Qs.stringify({ a: 1, b: 2 })).to.equal('a=1&b=2'); + expect(Qs.stringify({ a: 'A_Z' })).to.equal('a=A_Z'); + expect(Qs.stringify({ a: '€' })).to.equal('a=%E2%82%AC'); + expect(Qs.stringify({ a: '' })).to.equal('a=%EE%80%80'); + expect(Qs.stringify({ a: 'א' })).to.equal('a=%D7%90'); + expect(Qs.stringify({ a: '𐐷' })).to.equal('a=%F0%90%90%B7'); done(); }); @@ -42,6 +47,12 @@ done(); }); + it('omits array indices when asked', function (done) { + + expect(Qs.stringify({ a: ['b', 'c', 'd'] }, { indices: false })).to.equal('a=b&a=c&a=d'); + done(); + }); + it('stringifies a nested array value', function (done) { expect(Qs.stringify({ a: { b: ['c', 'd'] } })).to.equal('a%5Bb%5D%5B0%5D=c&a%5Bb%5D%5B1%5D=d'); @@ -55,6 +66,42 @@ done(); }); + it('does not omit object keys when indices = false', function (done) { + + expect(Qs.stringify({ a: [{ b: 'c' }] }, { indices: false })).to.equal('a%5Bb%5D=c'); + done(); + }); + + it('uses indices notation for arrays when indices=true', function (done) { + + expect(Qs.stringify({ a: ['b', 'c'] }, { indices: true })).to.equal('a%5B0%5D=b&a%5B1%5D=c'); + done(); + }); + + it('uses indices notation for arrays when no arrayFormat is specified', function (done) { + + expect(Qs.stringify({ a: ['b', 'c'] })).to.equal('a%5B0%5D=b&a%5B1%5D=c'); + done(); + }); + + it('uses indices notation for arrays when no arrayFormat=indices', function (done) { + + expect(Qs.stringify({ a: ['b', 'c'] }, { arrayFormat: 'indices' })).to.equal('a%5B0%5D=b&a%5B1%5D=c'); + done(); + }); + + it('uses repeat notation for arrays when no arrayFormat=repeat', function (done) { + + expect(Qs.stringify({ a: ['b', 'c'] }, { arrayFormat: 'repeat' })).to.equal('a=b&a=c'); + done(); + }); + + it('uses brackets notation for arrays when no arrayFormat=brackets', function (done) { + + expect(Qs.stringify({ a: ['b', 'c'] }, { arrayFormat: 'brackets' })).to.equal('a%5B%5D=b&a%5B%5D=c'); + done(); + }); + it('stringifies a complicated object', function (done) { expect(Qs.stringify({ a: { b: 'c', d: 'e' } })).to.equal('a%5Bb%5D=c&a%5Bd%5D=e'); @@ -64,16 +111,53 @@ it('stringifies an empty value', function (done) { expect(Qs.stringify({ a: '' })).to.equal('a='); + expect(Qs.stringify({ a: null }, {strictNullHandling: true})).to.equal('a'); + expect(Qs.stringify({ a: '', b: '' })).to.equal('a=&b='); - expect(Qs.stringify({ a: null })).to.equal('a='); - expect(Qs.stringify({ a: { b: null } })).to.equal('a%5Bb%5D='); + expect(Qs.stringify({ a: null, b: '' }, {strictNullHandling: true})).to.equal('a&b='); + + expect(Qs.stringify({ a: { b: '' } })).to.equal('a%5Bb%5D='); + expect(Qs.stringify({ a: { b: null } }, {strictNullHandling: true})).to.equal('a%5Bb%5D'); + expect(Qs.stringify({ a: { b: null } }, {strictNullHandling: false})).to.equal('a%5Bb%5D='); + + done(); + }); + + it('stringifies an empty object', function (done) { + + var obj = Object.create(null); + obj.a = 'b'; + expect(Qs.stringify(obj)).to.equal('a=b'); + done(); + }); + + it('returns an empty string for invalid input', function (done) { + + expect(Qs.stringify(undefined)).to.equal(''); + expect(Qs.stringify(false)).to.equal(''); + expect(Qs.stringify(null)).to.equal(''); + expect(Qs.stringify('')).to.equal(''); + done(); + }); + + it('stringifies an object with an empty object as a child', function (done) { + + var obj = { + a: Object.create(null) + }; + + obj.a.b = 'c'; + expect(Qs.stringify(obj)).to.equal('a%5Bb%5D=c'); done(); }); it('drops keys with a value of undefined', function (done) { expect(Qs.stringify({ a: undefined })).to.equal(''); - expect(Qs.stringify({ a: { b: undefined, c: null } })).to.equal('a%5Bc%5D='); + + expect(Qs.stringify({ a: { b: undefined, c: null } }, {strictNullHandling: true})).to.equal('a%5Bc%5D'); + expect(Qs.stringify({ a: { b: undefined, c: null } }, {strictNullHandling: false})).to.equal('a%5Bc%5D='); + expect(Qs.stringify({ a: { b: undefined, c: '' } })).to.equal('a%5Bc%5D='); done(); }); @@ -93,7 +177,7 @@ it('stringifies the weird object from qs', function (done) { - expect(Qs.stringify({ 'my weird field': 'q1!2"\'w$5&7/z8)?' })).to.equal('my%20weird%20field=q1!2%22\'w%245%267%2Fz8)%3F'); + expect(Qs.stringify({ 'my weird field': '~q1!2"\'w$5&7/z8)?' })).to.equal('my%20weird%20field=~q1%212%22%27w%245%267%2Fz8%29%3F'); done(); }); @@ -136,4 +220,40 @@ global.Buffer = tempBuffer; done(); }); + + it('selects properties when filter=array', function (done) { + + expect(Qs.stringify({ a: 'b' }, { filter: ['a'] })).to.equal('a=b'); + expect(Qs.stringify({ a: 1}, { filter: [] })).to.equal(''); + expect(Qs.stringify({ a: { b: [1, 2, 3, 4], c: 'd' }, c: 'f' }, { filter: ['a', 'b', 0, 2]})).to.equal('a%5Bb%5D%5B0%5D=1&a%5Bb%5D%5B2%5D=3'); + done(); + + }); + + it('supports custom representations when filter=function', function (done) { + + var calls = 0; + var obj = { a: 'b', c: 'd', e: { f: new Date(1257894000000) } }; + var filterFunc = function (prefix, value) { + + calls++; + if (calls === 1) { + expect(prefix).to.be.empty(); + expect(value).to.equal(obj); + } + else if (prefix === 'c') { + return; + } + else if (value instanceof Date) { + expect(prefix).to.equal('e[f]'); + return value.getTime(); + } + return value; + }; + + expect(Qs.stringify(obj, { filter: filterFunc })).to.equal('a=b&e%5Bf%5D=1257894000000'); + expect(calls).to.equal(5); + done(); + + }); }); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/package/test/utils.js new/package/test/utils.js --- old/package/test/utils.js 1970-01-01 01:00:00.000000000 +0100 +++ new/package/test/utils.js 2015-05-21 22:44:26.000000000 +0200 @@ -0,0 +1,28 @@ +// Load modules + +var Code = require('code'); +var Lab = require('lab'); +var Utils = require('../lib/utils'); + + +// Declare internals + +var internals = {}; + + +// Test shortcuts + +var lab = exports.lab = Lab.script(); +var expect = Code.expect; +var describe = lab.experiment; +var it = lab.test; + + +describe('merge()', function () { + + it('can merge two objects with the same key', function (done) { + + expect(Utils.merge({ a: 'b' }, { a: 'c' })).to.deep.equal({ a: ['b', 'c'] }); + done(); + }); +});
