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&#39;t use Buffer 
global if it&#39;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&#39;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&#39;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();
+    });
+});


Reply via email to