qs 6.2.0 → 6.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/utils.js CHANGED
@@ -9,8 +9,23 @@ var hexTable = (function () {
9
9
  return array;
10
10
  }());
11
11
 
12
+ var has = Object.prototype.hasOwnProperty;
13
+
12
14
  exports.arrayToObject = function (source, options) {
13
- var obj = options.plainObjects ? Object.create(null) : {};
15
+ var obj = options && options.plainObjects ? Object.create(null) : {};
16
+ for (var i = 0; i < source.length; ++i) {
17
+ if (typeof source[i] !== 'undefined') {
18
+ obj[i] = source[i];
19
+ }
20
+ }
21
+
22
+ return obj;
23
+ };
24
+
25
+ var isArray = Array.isArray;
26
+
27
+ var arrayToObject = function arrayToObject(source, options) {
28
+ var obj = options && options.plainObjects ? Object.create(null) : {};
14
29
  for (var i = 0; i < source.length; ++i) {
15
30
  if (typeof source[i] !== 'undefined') {
16
31
  obj[i] = source[i];
@@ -20,16 +35,19 @@ exports.arrayToObject = function (source, options) {
20
35
  return obj;
21
36
  };
22
37
 
23
- exports.merge = function (target, source, options) {
38
+ exports.merge = function merge(target, source, options) {
39
+ /* eslint no-param-reassign: 0 */
24
40
  if (!source) {
25
41
  return target;
26
42
  }
27
43
 
28
44
  if (typeof source !== 'object') {
29
- if (Array.isArray(target)) {
45
+ if (isArray(target)) {
30
46
  target.push(source);
31
- } else if (typeof target === 'object') {
32
- target[source] = true;
47
+ } else if (target && typeof target === 'object') {
48
+ if ((options && (options.plainObjects || options.allowPrototypes)) || !has.call(Object.prototype, source)) {
49
+ target[source] = true;
50
+ }
33
51
  } else {
34
52
  return [target, source];
35
53
  }
@@ -37,20 +55,36 @@ exports.merge = function (target, source, options) {
37
55
  return target;
38
56
  }
39
57
 
40
- if (typeof target !== 'object') {
58
+ if (!target || typeof target !== 'object') {
41
59
  return [target].concat(source);
42
60
  }
43
61
 
44
62
  var mergeTarget = target;
45
- if (Array.isArray(target) && !Array.isArray(source)) {
46
- mergeTarget = exports.arrayToObject(target, options);
63
+ if (isArray(target) && !isArray(source)) {
64
+ mergeTarget = arrayToObject(target, options);
65
+ }
66
+
67
+ if (isArray(target) && isArray(source)) {
68
+ source.forEach(function (item, i) {
69
+ if (has.call(target, i)) {
70
+ var targetItem = target[i];
71
+ if (targetItem && typeof targetItem === 'object' && item && typeof item === 'object') {
72
+ target[i] = merge(targetItem, item, options);
73
+ } else {
74
+ target.push(item);
75
+ }
76
+ } else {
77
+ target[i] = item;
78
+ }
79
+ });
80
+ return target;
47
81
  }
48
82
 
49
83
  return Object.keys(source).reduce(function (acc, key) {
50
84
  var value = source[key];
51
85
 
52
- if (Object.prototype.hasOwnProperty.call(acc, key)) {
53
- acc[key] = exports.merge(acc[key], value, options);
86
+ if (has.call(acc, key)) {
87
+ acc[key] = merge(acc[key], value, options);
54
88
  } else {
55
89
  acc[key] = value;
56
90
  }
@@ -80,13 +114,13 @@ exports.encode = function (str) {
80
114
  var c = string.charCodeAt(i);
81
115
 
82
116
  if (
83
- c === 0x2D || // -
84
- c === 0x2E || // .
85
- c === 0x5F || // _
86
- c === 0x7E || // ~
87
- (c >= 0x30 && c <= 0x39) || // 0-9
88
- (c >= 0x41 && c <= 0x5A) || // a-z
89
- (c >= 0x61 && c <= 0x7A) // A-Z
117
+ c === 0x2D // -
118
+ || c === 0x2E // .
119
+ || c === 0x5F // _
120
+ || c === 0x7E // ~
121
+ || (c >= 0x30 && c <= 0x39) // 0-9
122
+ || (c >= 0x41 && c <= 0x5A) // a-z
123
+ || (c >= 0x61 && c <= 0x7A) // A-Z
90
124
  ) {
91
125
  out += string.charAt(i);
92
126
  continue;
@@ -109,7 +143,11 @@ exports.encode = function (str) {
109
143
 
110
144
  i += 1;
111
145
  c = 0x10000 + (((c & 0x3FF) << 10) | (string.charCodeAt(i) & 0x3FF));
112
- out += hexTable[0xF0 | (c >> 18)] + hexTable[0x80 | ((c >> 12) & 0x3F)] + hexTable[0x80 | ((c >> 6) & 0x3F)] + hexTable[0x80 | (c & 0x3F)];
146
+ /* eslint operator-linebreak: [2, "before"] */
147
+ out += hexTable[0xF0 | (c >> 18)]
148
+ + hexTable[0x80 | ((c >> 12) & 0x3F)]
149
+ + hexTable[0x80 | ((c >> 6) & 0x3F)]
150
+ + hexTable[0x80 | (c & 0x3F)];
113
151
  }
114
152
 
115
153
  return out;
@@ -128,7 +166,7 @@ exports.compact = function (obj, references) {
128
166
 
129
167
  refs.push(obj);
130
168
 
131
- if (Array.isArray(obj)) {
169
+ if (isArray(obj)) {
132
170
  var compacted = [];
133
171
 
134
172
  for (var i = 0; i < obj.length; ++i) {
package/package.json CHANGED
@@ -1,48 +1,54 @@
1
1
  {
2
- "name": "qs",
3
- "description": "A querystring parser that supports nesting and arrays, with a depth limit",
4
- "homepage": "https://github.com/ljharb/qs",
5
- "version": "6.2.0",
6
- "repository": {
7
- "type": "git",
8
- "url": "https://github.com/ljharb/qs.git"
9
- },
10
- "main": "lib/index.js",
11
- "contributors": [
12
- {
13
- "name": "Jordan Harband",
14
- "email": "ljharb@gmail.com",
15
- "url": "http://ljharb.codes"
16
- }
17
- ],
18
- "keywords": [
19
- "querystring",
20
- "qs"
21
- ],
22
- "engines": {
23
- "node": ">=0.6"
24
- },
25
- "dependencies": {},
26
- "devDependencies": {
27
- "browserify": "^13.0.1",
28
- "tape": "^4.5.1",
29
- "covert": "^1.1.0",
30
- "mkdirp": "^0.5.1",
31
- "eslint": "^2.9.0",
32
- "@ljharb/eslint-config": "^4.0.0",
33
- "parallelshell": "^2.0.0",
34
- "iconv-lite": "^0.4.13",
35
- "evalmd": "^0.0.17"
36
- },
37
- "scripts": {
38
- "pretest": "parallelshell 'npm run --silent readme' 'npm run --silent lint'",
39
- "test": "npm run --silent coverage",
40
- "tests-only": "node test",
41
- "readme": "evalmd README.md",
42
- "lint": "eslint lib/*.js text/*.js",
43
- "coverage": "covert test",
44
- "dist": "mkdirp dist && browserify --standalone Qs lib/index.js > dist/qs.js",
45
- "prepublish": "npm run dist"
46
- },
47
- "license": "BSD-3-Clause"
2
+ "name": "qs",
3
+ "description": "A querystring parser that supports nesting and arrays, with a depth limit",
4
+ "homepage": "https://github.com/ljharb/qs",
5
+ "version": "6.2.4",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://github.com/ljharb/qs.git"
9
+ },
10
+ "main": "lib/index.js",
11
+ "contributors": [
12
+ {
13
+ "name": "Jordan Harband",
14
+ "email": "ljharb@gmail.com",
15
+ "url": "http://ljharb.codes"
16
+ }
17
+ ],
18
+ "keywords": [
19
+ "querystring",
20
+ "qs"
21
+ ],
22
+ "engines": {
23
+ "node": ">=0.6"
24
+ },
25
+ "devDependencies": {
26
+ "@ljharb/eslint-config": "^20.1.0",
27
+ "aud": "^1.1.5",
28
+ "browserify": "^16.5.2",
29
+ "eclint": "^2.8.1",
30
+ "eslint": "^8.6.0",
31
+ "evalmd": "^0.0.17",
32
+ "iconv-lite": "^0.4.24",
33
+ "in-publish": "^2.0.1",
34
+ "mkdirp": "^0.5.1",
35
+ "nyc": "^10.3.2",
36
+ "qs-iconv": "^1.0.4",
37
+ "safe-publish-latest": "^2.0.0",
38
+ "safer-buffer": "^2.1.2",
39
+ "tape": "^5.4.0"
40
+ },
41
+ "scripts": {
42
+ "prepublishOnly": "safe-publish-latest && npm run dist",
43
+ "prepublish": "not-in-publish || npm run prepublishOnly",
44
+ "pretest": "npm run --silent readme && npm run --silent lint",
45
+ "test": "npm run --silent tests-only",
46
+ "tests-only": "nyc tape 'test/**/*.js'",
47
+ "posttest": "aud --production",
48
+ "readme": "evalmd README.md",
49
+ "postlint": "eclint check $(git ls-files | xargs find 2> /dev/null | grep -vE 'node_modules|\\.git')",
50
+ "lint": "eslint --ext=js,mjs .",
51
+ "dist": "mkdirp dist && browserify --standalone Qs lib/index.js > dist/qs.js"
52
+ },
53
+ "license": "BSD-3-Clause"
48
54
  }
package/test/index.js CHANGED
@@ -1,3 +1,5 @@
1
+ 'use strict';
2
+
1
3
  require('./parse');
2
4
 
3
5
  require('./stringify');
package/test/parse.js CHANGED
@@ -3,10 +3,11 @@
3
3
  var test = require('tape');
4
4
  var qs = require('../');
5
5
  var iconv = require('iconv-lite');
6
+ var SaferBuffer = require('safer-buffer').Buffer;
6
7
 
7
8
  test('parse()', function (t) {
8
9
  t.test('parses a simple string', function (st) {
9
- st.deepEqual(qs.parse('0=foo'), { '0': 'foo' });
10
+ st.deepEqual(qs.parse('0=foo'), { 0: 'foo' });
10
11
  st.deepEqual(qs.parse('foo=c++'), { foo: 'c ' });
11
12
  st.deepEqual(qs.parse('a[>=]=23'), { a: { '>=': '23' } });
12
13
  st.deepEqual(qs.parse('a[<=>]==23'), { a: { '<=>': '=23' } });
@@ -84,7 +85,7 @@ test('parse()', function (t) {
84
85
 
85
86
  t.test('limits specific array indices to 20', function (st) {
86
87
  st.deepEqual(qs.parse('a[20]=a'), { a: ['a'] });
87
- st.deepEqual(qs.parse('a[21]=a'), { a: { '21': 'a' } });
88
+ st.deepEqual(qs.parse('a[21]=a'), { a: { 21: 'a' } });
88
89
  st.end();
89
90
  });
90
91
 
@@ -115,14 +116,17 @@ test('parse()', function (t) {
115
116
  });
116
117
 
117
118
  t.test('transforms arrays to objects', function (st) {
118
- st.deepEqual(qs.parse('foo[0]=bar&foo[bad]=baz'), { foo: { '0': 'bar', bad: 'baz' } });
119
- st.deepEqual(qs.parse('foo[bad]=baz&foo[0]=bar'), { foo: { bad: 'baz', '0': 'bar' } });
120
- st.deepEqual(qs.parse('foo[bad]=baz&foo[]=bar'), { foo: { bad: 'baz', '0': 'bar' } });
121
- st.deepEqual(qs.parse('foo[]=bar&foo[bad]=baz'), { foo: { '0': 'bar', bad: 'baz' } });
122
- st.deepEqual(qs.parse('foo[bad]=baz&foo[]=bar&foo[]=foo'), { foo: { bad: 'baz', '0': 'bar', '1': 'foo' } });
119
+ st.deepEqual(qs.parse('foo[0]=bar&foo[bad]=baz'), { foo: { 0: 'bar', bad: 'baz' } });
120
+ st.deepEqual(qs.parse('foo[bad]=baz&foo[0]=bar'), { foo: { bad: 'baz', 0: 'bar' } });
121
+ st.deepEqual(qs.parse('foo[bad]=baz&foo[]=bar'), { foo: { bad: 'baz', 0: 'bar' } });
122
+ st.deepEqual(qs.parse('foo[]=bar&foo[bad]=baz'), { foo: { 0: 'bar', bad: 'baz' } });
123
+ st.deepEqual(qs.parse('foo[bad]=baz&foo[]=bar&foo[]=foo'), { foo: { bad: 'baz', 0: 'bar', 1: 'foo' } });
123
124
  st.deepEqual(qs.parse('foo[0][a]=a&foo[0][b]=b&foo[1][a]=aa&foo[1][b]=bb'), { foo: [{ a: 'a', b: 'b' }, { a: 'aa', b: 'bb' }] });
124
- st.deepEqual(qs.parse('a[]=b&a[t]=u&a[hasOwnProperty]=c'), { a: { '0': 'b', t: 'u', c: true } });
125
- st.deepEqual(qs.parse('a[]=b&a[hasOwnProperty]=c&a[x]=y'), { a: { '0': 'b', '1': 'c', x: 'y' } });
125
+
126
+ st.deepEqual(qs.parse('a[]=b&a[t]=u&a[hasOwnProperty]=c', { allowPrototypes: false }), { a: { 0: 'b', t: 'u' } });
127
+ st.deepEqual(qs.parse('a[]=b&a[t]=u&a[hasOwnProperty]=c', { allowPrototypes: true }), { a: { 0: 'b', t: 'u', hasOwnProperty: 'c' } });
128
+ st.deepEqual(qs.parse('a[]=b&a[hasOwnProperty]=c&a[x]=y', { allowPrototypes: false }), { a: { 0: 'b', x: 'y' } });
129
+ st.deepEqual(qs.parse('a[]=b&a[hasOwnProperty]=c&a[x]=y', { allowPrototypes: true }), { a: { 0: 'b', hasOwnProperty: 'c', x: 'y' } });
126
130
  st.end();
127
131
  });
128
132
 
@@ -132,18 +136,16 @@ test('parse()', function (t) {
132
136
  st.deepEqual(qs.parse('foo[0][0].baz=bar&fool.bad=baz', { allowDots: true }), { foo: [[{ baz: 'bar' }]], fool: { bad: 'baz' } });
133
137
  st.deepEqual(qs.parse('foo[0].baz[0]=15&foo[0].bar=2', { allowDots: true }), { foo: [{ baz: ['15'], bar: '2' }] });
134
138
  st.deepEqual(qs.parse('foo[0].baz[0]=15&foo[0].baz[1]=16&foo[0].bar=2', { allowDots: true }), { foo: [{ baz: ['15', '16'], bar: '2' }] });
135
- st.deepEqual(qs.parse('foo.bad=baz&foo[0]=bar', { allowDots: true }), { foo: { bad: 'baz', '0': 'bar' } });
136
- st.deepEqual(qs.parse('foo.bad=baz&foo[]=bar', { allowDots: true }), { foo: { bad: 'baz', '0': 'bar' } });
137
- st.deepEqual(qs.parse('foo[]=bar&foo.bad=baz', { allowDots: true }), { foo: { '0': 'bar', bad: 'baz' } });
138
- st.deepEqual(qs.parse('foo.bad=baz&foo[]=bar&foo[]=foo', { allowDots: true }), { foo: { bad: 'baz', '0': 'bar', '1': 'foo' } });
139
+ st.deepEqual(qs.parse('foo.bad=baz&foo[0]=bar', { allowDots: true }), { foo: { bad: 'baz', 0: 'bar' } });
140
+ st.deepEqual(qs.parse('foo.bad=baz&foo[]=bar', { allowDots: true }), { foo: { bad: 'baz', 0: 'bar' } });
141
+ st.deepEqual(qs.parse('foo[]=bar&foo.bad=baz', { allowDots: true }), { foo: { 0: 'bar', bad: 'baz' } });
142
+ st.deepEqual(qs.parse('foo.bad=baz&foo[]=bar&foo[]=foo', { allowDots: true }), { foo: { bad: 'baz', 0: 'bar', 1: 'foo' } });
139
143
  st.deepEqual(qs.parse('foo[0].a=a&foo[0].b=b&foo[1].a=aa&foo[1].b=bb', { allowDots: true }), { foo: [{ a: 'a', b: 'b' }, { a: 'aa', b: 'bb' }] });
140
144
  st.end();
141
145
  });
142
146
 
143
- t.deepEqual(qs.parse('a[b]=c&a=d'), { a: { b: 'c', d: true } }, 'can add keys to objects');
144
-
145
147
  t.test('correctly prunes undefined values when converting an array to an object', function (st) {
146
- st.deepEqual(qs.parse('a[2]=b&a[99999999]=c'), { a: { '2': 'b', '99999999': 'c' } });
148
+ st.deepEqual(qs.parse('a[2]=b&a[99999999]=c'), { a: { 2: 'b', 99999999: 'c' } });
147
149
  st.end();
148
150
  });
149
151
 
@@ -155,7 +157,7 @@ test('parse()', function (t) {
155
157
  });
156
158
 
157
159
  t.test('doesn\'t produce empty keys', function (st) {
158
- st.deepEqual(qs.parse('_r=1&'), { '_r': '1' });
160
+ st.deepEqual(qs.parse('_r=1&'), { _r: '1' });
159
161
  st.end();
160
162
  });
161
163
 
@@ -174,9 +176,34 @@ test('parse()', function (t) {
174
176
 
175
177
  t.test('allows for empty strings in arrays', function (st) {
176
178
  st.deepEqual(qs.parse('a[]=b&a[]=&a[]=c'), { a: ['b', '', 'c'] });
177
- st.deepEqual(qs.parse('a[0]=b&a[1]&a[2]=c&a[19]=', { strictNullHandling: true }), { a: ['b', null, 'c', ''] });
178
- st.deepEqual(qs.parse('a[0]=b&a[1]=&a[2]=c&a[19]', { strictNullHandling: true }), { a: ['b', '', 'c', null] });
179
- st.deepEqual(qs.parse('a[]=&a[]=b&a[]=c'), { a: ['', 'b', 'c'] });
179
+
180
+ st.deepEqual(
181
+ qs.parse('a[0]=b&a[1]&a[2]=c&a[19]=', { strictNullHandling: true, arrayLimit: 20 }),
182
+ { a: ['b', null, 'c', ''] },
183
+ 'with arrayLimit 20 + array indices: null then empty string works'
184
+ );
185
+ st.deepEqual(
186
+ qs.parse('a[]=b&a[]&a[]=c&a[]=', { strictNullHandling: true, arrayLimit: 0 }),
187
+ { a: ['b', null, 'c', ''] },
188
+ 'with arrayLimit 0 + array brackets: null then empty string works'
189
+ );
190
+
191
+ st.deepEqual(
192
+ qs.parse('a[0]=b&a[1]=&a[2]=c&a[19]', { strictNullHandling: true, arrayLimit: 20 }),
193
+ { a: ['b', '', 'c', null] },
194
+ 'with arrayLimit 20 + array indices: empty string then null works'
195
+ );
196
+ st.deepEqual(
197
+ qs.parse('a[]=b&a[]=&a[]=c&a[]', { strictNullHandling: true, arrayLimit: 0 }),
198
+ { a: ['b', '', 'c', null] },
199
+ 'with arrayLimit 0 + array brackets: empty string then null works'
200
+ );
201
+
202
+ st.deepEqual(
203
+ qs.parse('a[]=&a[]=b&a[]=c'),
204
+ { a: ['', 'b', 'c'] },
205
+ 'array brackets: empty strings work'
206
+ );
180
207
  st.end();
181
208
  });
182
209
 
@@ -195,14 +222,14 @@ test('parse()', function (t) {
195
222
  });
196
223
 
197
224
  t.test('parses buffers correctly', function (st) {
198
- var b = new Buffer('test');
225
+ var b = SaferBuffer.from('test');
199
226
  st.deepEqual(qs.parse({ a: b }), { a: b });
200
227
  st.end();
201
228
  });
202
229
 
203
230
  t.test('continues parsing when no parent is found', function (st) {
204
- st.deepEqual(qs.parse('[]=&a=b'), { '0': '', a: 'b' });
205
- st.deepEqual(qs.parse('[]&a=b', { strictNullHandling: true }), { '0': null, a: 'b' });
231
+ st.deepEqual(qs.parse('[]=&a=b'), { 0: '', a: 'b' });
232
+ st.deepEqual(qs.parse('[]&a=b', { strictNullHandling: true }), { 0: null, a: 'b' });
206
233
  st.deepEqual(qs.parse('[foo]=bar'), { foo: 'bar' });
207
234
  st.end();
208
235
  });
@@ -218,7 +245,7 @@ test('parse()', function (t) {
218
245
  st.end();
219
246
  });
220
247
 
221
- t.test('should not throw when a native prototype has an enumerable property', { parallel: false }, function (st) {
248
+ t.test('should not throw when a native prototype has an enumerable property', function (st) {
222
249
  Object.prototype.crash = '';
223
250
  Array.prototype.crash = '';
224
251
  st.doesNotThrow(qs.parse.bind(null, 'a=b'));
@@ -256,14 +283,21 @@ test('parse()', function (t) {
256
283
  });
257
284
 
258
285
  t.test('allows overriding array limit', function (st) {
259
- st.deepEqual(qs.parse('a[0]=b', { arrayLimit: -1 }), { a: { '0': 'b' } });
286
+ st.deepEqual(qs.parse('a[0]=b', { arrayLimit: -1 }), { a: { 0: 'b' } });
260
287
  st.deepEqual(qs.parse('a[-1]=b', { arrayLimit: -1 }), { a: { '-1': 'b' } });
261
- st.deepEqual(qs.parse('a[0]=b&a[1]=c', { arrayLimit: 0 }), { a: { '0': 'b', '1': 'c' } });
288
+ st.deepEqual(qs.parse('a[0]=b&a[1]=c', { arrayLimit: 0 }), { a: { 0: 'b', 1: 'c' } });
262
289
  st.end();
263
290
  });
264
291
 
265
292
  t.test('allows disabling array parsing', function (st) {
266
- st.deepEqual(qs.parse('a[0]=b&a[1]=c', { parseArrays: false }), { a: { '0': 'b', '1': 'c' } });
293
+ var indices = qs.parse('a[0]=b&a[1]=c', { parseArrays: false });
294
+ st.deepEqual(indices, { a: { 0: 'b', 1: 'c' } });
295
+ st.equal(Array.isArray(indices.a), false, 'parseArrays:false, indices case is not an array');
296
+
297
+ var emptyBrackets = qs.parse('a[]=b', { parseArrays: false });
298
+ st.deepEqual(emptyBrackets, { a: { 0: 'b' } });
299
+ st.equal(Array.isArray(emptyBrackets.a), false, 'parseArrays:false, empty brackets case is not an array');
300
+
267
301
  st.end();
268
302
  });
269
303
 
@@ -307,13 +341,13 @@ test('parse()', function (t) {
307
341
 
308
342
  t.test('parses an object and not child values', function (st) {
309
343
  var input = {
310
- 'user[name]': { 'pop[bob]': { 'test': 3 } },
344
+ 'user[name]': { 'pop[bob]': { test: 3 } },
311
345
  'user[email]': null
312
346
  };
313
347
 
314
348
  var expected = {
315
349
  user: {
316
- name: { 'pop[bob]': { 'test': 3 } },
350
+ name: { 'pop[bob]': { test: 3 } },
317
351
  email: null
318
352
  }
319
353
  };
@@ -374,13 +408,135 @@ test('parse()', function (t) {
374
408
  st.end();
375
409
  });
376
410
 
411
+ t.test('does not allow overwriting prototype properties', function (st) {
412
+ st.deepEqual(qs.parse('a[hasOwnProperty]=b', { allowPrototypes: false }), {});
413
+ st.deepEqual(qs.parse('hasOwnProperty=b', { allowPrototypes: false }), {});
414
+
415
+ st.deepEqual(
416
+ qs.parse('toString', { allowPrototypes: false }),
417
+ {},
418
+ 'bare "toString" results in {}'
419
+ );
420
+
421
+ st.end();
422
+ });
423
+
377
424
  t.test('can allow overwriting prototype properties', function (st) {
378
- st.deepEqual(qs.parse('a[hasOwnProperty]=b', { allowPrototypes: true }), { a: { hasOwnProperty: 'b' } }, { prototype: false });
379
- st.deepEqual(qs.parse('hasOwnProperty=b', { allowPrototypes: true }), { hasOwnProperty: 'b' }, { prototype: false });
425
+ st.deepEqual(qs.parse('a[hasOwnProperty]=b', { allowPrototypes: true }), { a: { hasOwnProperty: 'b' } });
426
+ st.deepEqual(qs.parse('hasOwnProperty=b', { allowPrototypes: true }), { hasOwnProperty: 'b' });
427
+
428
+ st.deepEqual(
429
+ qs.parse('toString', { allowPrototypes: true }),
430
+ { toString: '' },
431
+ 'bare "toString" results in { toString: "" }'
432
+ );
433
+
434
+ st.end();
435
+ });
436
+
437
+ t.test('params starting with a closing bracket', function (st) {
438
+ st.deepEqual(qs.parse(']=toString'), { ']': 'toString' });
439
+ st.deepEqual(qs.parse(']]=toString'), { ']]': 'toString' });
440
+ st.deepEqual(qs.parse(']hello]=toString'), { ']hello]': 'toString' });
441
+ st.end();
442
+ });
443
+
444
+ t.test('params starting with a starting bracket', function (st) {
445
+ st.deepEqual(qs.parse('[=toString'), { '[': 'toString' });
446
+ st.deepEqual(qs.parse('[[=toString'), { '[[': 'toString' });
447
+ st.deepEqual(qs.parse('[hello[=toString'), { '[hello[': 'toString' });
448
+ st.end();
449
+ });
450
+
451
+ t.test('add keys to objects', function (st) {
452
+ st.deepEqual(
453
+ qs.parse('a[b]=c&a=d'),
454
+ { a: { b: 'c', d: true } },
455
+ 'can add keys to objects'
456
+ );
457
+
458
+ st.deepEqual(
459
+ qs.parse('a[b]=c&a=toString'),
460
+ { a: { b: 'c' } },
461
+ 'can not overwrite prototype'
462
+ );
463
+
464
+ st.deepEqual(
465
+ qs.parse('a[b]=c&a=toString', { allowPrototypes: true }),
466
+ { a: { b: 'c', toString: true } },
467
+ 'can overwrite prototype with allowPrototypes true'
468
+ );
469
+
470
+ st.deepEqual(
471
+ qs.parse('a[b]=c&a=toString', { plainObjects: true }),
472
+ { __proto__: null, a: { __proto__: null, b: 'c', toString: true } },
473
+ 'can overwrite prototype with plainObjects true'
474
+ );
475
+
476
+ st.end();
477
+ });
478
+
479
+ t.test('dunder proto is ignored', function (st) {
480
+ var payload = 'categories[__proto__]=login&categories[__proto__]&categories[length]=42';
481
+ var result = qs.parse(payload, { allowPrototypes: true });
482
+
483
+ st.deepEqual(
484
+ result,
485
+ {
486
+ categories: {
487
+ length: '42'
488
+ }
489
+ },
490
+ 'silent [[Prototype]] payload'
491
+ );
492
+
493
+ var plainResult = qs.parse(payload, { allowPrototypes: true, plainObjects: true });
494
+
495
+ st.deepEqual(
496
+ plainResult,
497
+ {
498
+ __proto__: null,
499
+ categories: {
500
+ __proto__: null,
501
+ length: '42'
502
+ }
503
+ },
504
+ 'silent [[Prototype]] payload: plain objects'
505
+ );
506
+
507
+ var query = qs.parse('categories[__proto__]=cats&categories[__proto__]=dogs&categories[some][json]=toInject', { allowPrototypes: true });
508
+
509
+ st.notOk(Array.isArray(query.categories), 'is not an array');
510
+ st.notOk(query.categories instanceof Array, 'is not instanceof an array');
511
+ st.deepEqual(query.categories, { some: { json: 'toInject' } });
512
+ st.equal(JSON.stringify(query.categories), '{"some":{"json":"toInject"}}', 'stringifies as a non-array');
513
+
514
+ st.deepEqual(
515
+ qs.parse('foo[__proto__][hidden]=value&foo[bar]=stuffs', { allowPrototypes: true }),
516
+ {
517
+ foo: {
518
+ bar: 'stuffs'
519
+ }
520
+ },
521
+ 'hidden values'
522
+ );
523
+
524
+ st.deepEqual(
525
+ qs.parse('foo[__proto__][hidden]=value&foo[bar]=stuffs', { allowPrototypes: true, plainObjects: true }),
526
+ {
527
+ __proto__: null,
528
+ foo: {
529
+ __proto__: null,
530
+ bar: 'stuffs'
531
+ }
532
+ },
533
+ 'hidden values: plain objects'
534
+ );
535
+
380
536
  st.end();
381
537
  });
382
538
 
383
- t.test('can return plain objects', function (st) {
539
+ t.test('can return null objects', { skip: !Object.create }, function (st) {
384
540
  var expected = Object.create(null);
385
541
  expected.a = Object.create(null);
386
542
  expected.a.b = 'c';
@@ -398,25 +554,23 @@ test('parse()', function (t) {
398
554
  t.test('can parse with custom encoding', function (st) {
399
555
  st.deepEqual(qs.parse('%8c%a7=%91%e5%8d%e3%95%7b', {
400
556
  decoder: function (str) {
401
- var reg = /\%([0-9A-F]{2})/ig;
557
+ var reg = /%([0-9A-F]{2})/ig;
402
558
  var result = [];
403
559
  var parts;
404
- var last = 0;
405
- while (parts = reg.exec(str)) {
560
+ // var last = 0;
561
+ while ((parts = reg.exec(str))) {
406
562
  result.push(parseInt(parts[1], 16));
407
- last = parts.index + parts[0].length;
563
+ // last = parts.index + parts[0].length;
408
564
  }
409
- return iconv.decode(new Buffer(result), 'shift_jis').toString();
565
+ return iconv.decode(SaferBuffer.from(result), 'shift_jis').toString();
410
566
  }
411
567
  }), { 県: '大阪府' });
412
568
  st.end();
413
569
  });
414
570
 
415
571
  t.test('throws error with wrong decoder', function (st) {
416
- st.throws(function () {
417
- qs.parse({}, {
418
- decoder: 'string'
419
- });
572
+ st['throws'](function () {
573
+ qs.parse({}, { decoder: 'string' });
420
574
  }, new TypeError('Decoder has to be a function.'));
421
575
  st.end();
422
576
  });