qs 6.1.2 → 6.2.0

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/parse.js CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  var Utils = require('./utils');
4
4
 
5
- var internals = {
5
+ var defaults = {
6
6
  delimiter: '&',
7
7
  depth: 5,
8
8
  arrayLimit: 20,
@@ -10,12 +10,11 @@ var internals = {
10
10
  strictNullHandling: false,
11
11
  plainObjects: false,
12
12
  allowPrototypes: false,
13
- allowDots: false
13
+ allowDots: false,
14
+ decoder: Utils.decode
14
15
  };
15
16
 
16
- var has = Object.prototype.hasOwnProperty;
17
-
18
- internals.parseValues = function (str, options) {
17
+ var parseValues = function parseValues(str, options) {
19
18
  var obj = {};
20
19
  var parts = str.split(options.delimiter, options.parameterLimit === Infinity ? undefined : options.parameterLimit);
21
20
 
@@ -24,16 +23,16 @@ internals.parseValues = function (str, options) {
24
23
  var pos = part.indexOf(']=') === -1 ? part.indexOf('=') : part.indexOf(']=') + 1;
25
24
 
26
25
  if (pos === -1) {
27
- obj[Utils.decode(part)] = '';
26
+ obj[options.decoder(part)] = '';
28
27
 
29
28
  if (options.strictNullHandling) {
30
- obj[Utils.decode(part)] = null;
29
+ obj[options.decoder(part)] = null;
31
30
  }
32
31
  } else {
33
- var key = Utils.decode(part.slice(0, pos));
34
- var val = Utils.decode(part.slice(pos + 1));
32
+ var key = options.decoder(part.slice(0, pos));
33
+ var val = options.decoder(part.slice(pos + 1));
35
34
 
36
- if (has.call(obj, key)) {
35
+ if (Object.prototype.hasOwnProperty.call(obj, key)) {
37
36
  obj[key] = [].concat(obj[key]).concat(val);
38
37
  } else {
39
38
  obj[key] = val;
@@ -44,7 +43,7 @@ internals.parseValues = function (str, options) {
44
43
  return obj;
45
44
  };
46
45
 
47
- internals.parseObject = function (chain, val, options) {
46
+ var parseObject = function parseObject(chain, val, options) {
48
47
  if (!chain.length) {
49
48
  return val;
50
49
  }
@@ -54,10 +53,10 @@ internals.parseObject = function (chain, val, options) {
54
53
  var obj;
55
54
  if (root === '[]') {
56
55
  obj = [];
57
- obj = obj.concat(internals.parseObject(chain, val, options));
56
+ obj = obj.concat(parseObject(chain, val, options));
58
57
  } else {
59
58
  obj = options.plainObjects ? Object.create(null) : {};
60
- var cleanRoot = root.charAt(0) === '[' && root.charAt(root.length - 1) === ']' ? root.slice(1, -1) : root;
59
+ var cleanRoot = root[0] === '[' && root[root.length - 1] === ']' ? root.slice(1, root.length - 1) : root;
61
60
  var index = parseInt(cleanRoot, 10);
62
61
  if (
63
62
  !isNaN(index) &&
@@ -67,16 +66,16 @@ internals.parseObject = function (chain, val, options) {
67
66
  (options.parseArrays && index <= options.arrayLimit)
68
67
  ) {
69
68
  obj = [];
70
- obj[index] = internals.parseObject(chain, val, options);
69
+ obj[index] = parseObject(chain, val, options);
71
70
  } else {
72
- obj[cleanRoot] = internals.parseObject(chain, val, options);
71
+ obj[cleanRoot] = parseObject(chain, val, options);
73
72
  }
74
73
  }
75
74
 
76
75
  return obj;
77
76
  };
78
77
 
79
- internals.parseKeys = function (givenKey, val, options) {
78
+ var parseKeys = function parseKeys(givenKey, val, options) {
80
79
  if (!givenKey) {
81
80
  return;
82
81
  }
@@ -86,27 +85,26 @@ internals.parseKeys = function (givenKey, val, options) {
86
85
 
87
86
  // The regex chunks
88
87
 
89
- var brackets = /(\[[^[\]]*])/;
90
- var child = /(\[[^[\]]*])/g;
88
+ var parent = /^([^\[\]]*)/;
89
+ var child = /(\[[^\[\]]*\])/g;
91
90
 
92
91
  // Get the parent
93
92
 
94
- var segment = brackets.exec(key);
95
- var parent = segment ? key.slice(0, segment.index) : key;
93
+ var segment = parent.exec(key);
96
94
 
97
95
  // Stash the parent if it exists
98
96
 
99
97
  var keys = [];
100
- if (parent) {
98
+ if (segment[1]) {
101
99
  // If we aren't using plain objects, optionally prefix keys
102
100
  // that would overwrite object prototype properties
103
- if (!options.plainObjects && has.call(Object.prototype, parent)) {
101
+ if (!options.plainObjects && Object.prototype.hasOwnProperty(segment[1])) {
104
102
  if (!options.allowPrototypes) {
105
103
  return;
106
104
  }
107
105
  }
108
106
 
109
- keys.push(parent);
107
+ keys.push(segment[1]);
110
108
  }
111
109
 
112
110
  // Loop through children appending to the array until we hit depth
@@ -114,9 +112,9 @@ internals.parseKeys = function (givenKey, val, options) {
114
112
  var i = 0;
115
113
  while ((segment = child.exec(key)) !== null && i < options.depth) {
116
114
  i += 1;
117
- if (!options.plainObjects && has.call(Object.prototype, segment[1].slice(1, -1))) {
115
+ if (!options.plainObjects && Object.prototype.hasOwnProperty(segment[1].replace(/\[|\]/g, ''))) {
118
116
  if (!options.allowPrototypes) {
119
- return;
117
+ continue;
120
118
  }
121
119
  }
122
120
  keys.push(segment[1]);
@@ -128,30 +126,32 @@ internals.parseKeys = function (givenKey, val, options) {
128
126
  keys.push('[' + key.slice(segment.index) + ']');
129
127
  }
130
128
 
131
- return internals.parseObject(keys, val, options);
129
+ return parseObject(keys, val, options);
132
130
  };
133
131
 
134
132
  module.exports = function (str, opts) {
135
133
  var options = opts || {};
136
- options.delimiter = typeof options.delimiter === 'string' || Utils.isRegExp(options.delimiter) ? options.delimiter : internals.delimiter;
137
- options.depth = typeof options.depth === 'number' ? options.depth : internals.depth;
138
- options.arrayLimit = typeof options.arrayLimit === 'number' ? options.arrayLimit : internals.arrayLimit;
134
+
135
+ if (options.decoder !== null && options.decoder !== undefined && typeof options.decoder !== 'function') {
136
+ throw new TypeError('Decoder has to be a function.');
137
+ }
138
+
139
+ options.delimiter = typeof options.delimiter === 'string' || Utils.isRegExp(options.delimiter) ? options.delimiter : defaults.delimiter;
140
+ options.depth = typeof options.depth === 'number' ? options.depth : defaults.depth;
141
+ options.arrayLimit = typeof options.arrayLimit === 'number' ? options.arrayLimit : defaults.arrayLimit;
139
142
  options.parseArrays = options.parseArrays !== false;
140
- options.allowDots = typeof options.allowDots === 'boolean' ? options.allowDots : internals.allowDots;
141
- options.plainObjects = typeof options.plainObjects === 'boolean' ? options.plainObjects : internals.plainObjects;
142
- options.allowPrototypes = typeof options.allowPrototypes === 'boolean' ? options.allowPrototypes : internals.allowPrototypes;
143
- options.parameterLimit = typeof options.parameterLimit === 'number' ? options.parameterLimit : internals.parameterLimit;
144
- options.strictNullHandling = typeof options.strictNullHandling === 'boolean' ? options.strictNullHandling : internals.strictNullHandling;
145
-
146
- if (
147
- str === '' ||
148
- str === null ||
149
- typeof str === 'undefined'
150
- ) {
143
+ options.decoder = typeof options.decoder === 'function' ? options.decoder : defaults.decoder;
144
+ options.allowDots = typeof options.allowDots === 'boolean' ? options.allowDots : defaults.allowDots;
145
+ options.plainObjects = typeof options.plainObjects === 'boolean' ? options.plainObjects : defaults.plainObjects;
146
+ options.allowPrototypes = typeof options.allowPrototypes === 'boolean' ? options.allowPrototypes : defaults.allowPrototypes;
147
+ options.parameterLimit = typeof options.parameterLimit === 'number' ? options.parameterLimit : defaults.parameterLimit;
148
+ options.strictNullHandling = typeof options.strictNullHandling === 'boolean' ? options.strictNullHandling : defaults.strictNullHandling;
149
+
150
+ if (str === '' || str === null || typeof str === 'undefined') {
151
151
  return options.plainObjects ? Object.create(null) : {};
152
152
  }
153
153
 
154
- var tempObj = typeof str === 'string' ? internals.parseValues(str, options) : str;
154
+ var tempObj = typeof str === 'string' ? parseValues(str, options) : str;
155
155
  var obj = options.plainObjects ? Object.create(null) : {};
156
156
 
157
157
  // Iterate over the keys and setup the new object
@@ -159,7 +159,7 @@ module.exports = function (str, opts) {
159
159
  var keys = Object.keys(tempObj);
160
160
  for (var i = 0; i < keys.length; ++i) {
161
161
  var key = keys[i];
162
- var newObj = internals.parseKeys(key, tempObj[key], options);
162
+ var newObj = parseKeys(key, tempObj[key], options);
163
163
  obj = Utils.merge(obj, newObj, options);
164
164
  }
165
165
 
package/lib/stringify.js CHANGED
@@ -2,45 +2,45 @@
2
2
 
3
3
  var Utils = require('./utils');
4
4
 
5
- var internals = {
6
- delimiter: '&',
7
- arrayPrefixGenerators: {
8
- brackets: function (prefix) {
9
- return prefix + '[]';
10
- },
11
- indices: function (prefix, key) {
12
- return prefix + '[' + key + ']';
13
- },
14
- repeat: function (prefix) {
15
- return prefix;
16
- }
5
+ var arrayPrefixGenerators = {
6
+ brackets: function brackets(prefix) {
7
+ return prefix + '[]';
8
+ },
9
+ indices: function indices(prefix, key) {
10
+ return prefix + '[' + key + ']';
17
11
  },
12
+ repeat: function repeat(prefix) {
13
+ return prefix;
14
+ }
15
+ };
16
+
17
+ var defaults = {
18
+ delimiter: '&',
18
19
  strictNullHandling: false,
19
20
  skipNulls: false,
20
- encode: true
21
+ encode: true,
22
+ encoder: Utils.encode
21
23
  };
22
24
 
23
- internals.stringify = function (object, prefix, generateArrayPrefix, strictNullHandling, skipNulls, encode, filter, sort, allowDots) {
25
+ var stringify = function stringify(object, prefix, generateArrayPrefix, strictNullHandling, skipNulls, encoder, filter, sort, allowDots) {
24
26
  var obj = object;
25
27
  if (typeof filter === 'function') {
26
28
  obj = filter(prefix, obj);
27
- } else if (Utils.isBuffer(obj)) {
28
- obj = String(obj);
29
29
  } else if (obj instanceof Date) {
30
30
  obj = obj.toISOString();
31
31
  } else if (obj === null) {
32
32
  if (strictNullHandling) {
33
- return encode ? Utils.encode(prefix) : prefix;
33
+ return encoder ? encoder(prefix) : prefix;
34
34
  }
35
35
 
36
36
  obj = '';
37
37
  }
38
38
 
39
- if (typeof obj === 'string' || typeof obj === 'number' || typeof obj === 'boolean') {
40
- if (encode) {
41
- return [Utils.encode(prefix) + '=' + Utils.encode(obj)];
39
+ if (typeof obj === 'string' || typeof obj === 'number' || typeof obj === 'boolean' || Utils.isBuffer(obj)) {
40
+ if (encoder) {
41
+ return [encoder(prefix) + '=' + encoder(obj)];
42
42
  }
43
- return [prefix + '=' + obj];
43
+ return [prefix + '=' + String(obj)];
44
44
  }
45
45
 
46
46
  var values = [];
@@ -65,9 +65,9 @@ internals.stringify = function (object, prefix, generateArrayPrefix, strictNullH
65
65
  }
66
66
 
67
67
  if (Array.isArray(obj)) {
68
- values = values.concat(internals.stringify(obj[key], generateArrayPrefix(prefix, key), generateArrayPrefix, strictNullHandling, skipNulls, encode, filter, sort, allowDots));
68
+ values = values.concat(stringify(obj[key], generateArrayPrefix(prefix, key), generateArrayPrefix, strictNullHandling, skipNulls, encoder, filter, sort, allowDots));
69
69
  } else {
70
- values = values.concat(internals.stringify(obj[key], prefix + (allowDots ? '.' + key : '[' + key + ']'), generateArrayPrefix, strictNullHandling, skipNulls, encode, filter, sort, allowDots));
70
+ values = values.concat(stringify(obj[key], prefix + (allowDots ? '.' + key : '[' + key + ']'), generateArrayPrefix, strictNullHandling, skipNulls, encoder, filter, sort, allowDots));
71
71
  }
72
72
  }
73
73
 
@@ -77,14 +77,20 @@ internals.stringify = function (object, prefix, generateArrayPrefix, strictNullH
77
77
  module.exports = function (object, opts) {
78
78
  var obj = object;
79
79
  var options = opts || {};
80
- var delimiter = typeof options.delimiter === 'undefined' ? internals.delimiter : options.delimiter;
81
- var strictNullHandling = typeof options.strictNullHandling === 'boolean' ? options.strictNullHandling : internals.strictNullHandling;
82
- var skipNulls = typeof options.skipNulls === 'boolean' ? options.skipNulls : internals.skipNulls;
83
- var encode = typeof options.encode === 'boolean' ? options.encode : internals.encode;
80
+ var delimiter = typeof options.delimiter === 'undefined' ? defaults.delimiter : options.delimiter;
81
+ var strictNullHandling = typeof options.strictNullHandling === 'boolean' ? options.strictNullHandling : defaults.strictNullHandling;
82
+ var skipNulls = typeof options.skipNulls === 'boolean' ? options.skipNulls : defaults.skipNulls;
83
+ var encode = typeof options.encode === 'boolean' ? options.encode : defaults.encode;
84
+ var encoder = encode ? (typeof options.encoder === 'function' ? options.encoder : defaults.encoder) : null;
84
85
  var sort = typeof options.sort === 'function' ? options.sort : null;
85
86
  var allowDots = typeof options.allowDots === 'undefined' ? false : options.allowDots;
86
87
  var objKeys;
87
88
  var filter;
89
+
90
+ if (options.encoder !== null && options.encoder !== undefined && typeof options.encoder !== 'function') {
91
+ throw new TypeError('Encoder has to be a function.');
92
+ }
93
+
88
94
  if (typeof options.filter === 'function') {
89
95
  filter = options.filter;
90
96
  obj = filter('', obj);
@@ -99,7 +105,7 @@ module.exports = function (object, opts) {
99
105
  }
100
106
 
101
107
  var arrayFormat;
102
- if (options.arrayFormat in internals.arrayPrefixGenerators) {
108
+ if (options.arrayFormat in arrayPrefixGenerators) {
103
109
  arrayFormat = options.arrayFormat;
104
110
  } else if ('indices' in options) {
105
111
  arrayFormat = options.indices ? 'indices' : 'repeat';
@@ -107,7 +113,7 @@ module.exports = function (object, opts) {
107
113
  arrayFormat = 'indices';
108
114
  }
109
115
 
110
- var generateArrayPrefix = internals.arrayPrefixGenerators[arrayFormat];
116
+ var generateArrayPrefix = arrayPrefixGenerators[arrayFormat];
111
117
 
112
118
  if (!objKeys) {
113
119
  objKeys = Object.keys(obj);
@@ -124,7 +130,7 @@ module.exports = function (object, opts) {
124
130
  continue;
125
131
  }
126
132
 
127
- keys = keys.concat(internals.stringify(obj[key], key, generateArrayPrefix, strictNullHandling, skipNulls, encode, filter, sort, allowDots));
133
+ keys = keys.concat(stringify(obj[key], key, generateArrayPrefix, strictNullHandling, skipNulls, encoder, filter, sort, allowDots));
128
134
  }
129
135
 
130
136
  return keys.join(delimiter);
package/lib/utils.js CHANGED
@@ -9,8 +9,6 @@ var hexTable = (function () {
9
9
  return array;
10
10
  }());
11
11
 
12
- var has = Object.prototype.hasOwnProperty;
13
-
14
12
  exports.arrayToObject = function (source, options) {
15
13
  var obj = options.plainObjects ? Object.create(null) : {};
16
14
  for (var i = 0; i < source.length; ++i) {
@@ -31,9 +29,7 @@ exports.merge = function (target, source, options) {
31
29
  if (Array.isArray(target)) {
32
30
  target.push(source);
33
31
  } else if (typeof target === 'object') {
34
- if (options.plainObjects || options.allowPrototypes || !has.call(Object.prototype, source)) {
35
- target[source] = true;
36
- }
32
+ target[source] = true;
37
33
  } else {
38
34
  return [target, source];
39
35
  }
@@ -50,15 +46,15 @@ exports.merge = function (target, source, options) {
50
46
  mergeTarget = exports.arrayToObject(target, options);
51
47
  }
52
48
 
53
- return Object.keys(source).reduce(function (acc, key) {
49
+ return Object.keys(source).reduce(function (acc, key) {
54
50
  var value = source[key];
55
51
 
56
- if (has.call(acc, key)) {
52
+ if (Object.prototype.hasOwnProperty.call(acc, key)) {
57
53
  acc[key] = exports.merge(acc[key], value, options);
58
54
  } else {
59
55
  acc[key] = value;
60
56
  }
61
- return acc;
57
+ return acc;
62
58
  }, mergeTarget);
63
59
  };
64
60
 
@@ -113,7 +109,7 @@ exports.encode = function (str) {
113
109
 
114
110
  i += 1;
115
111
  c = 0x10000 + (((c & 0x3FF) << 10) | (string.charCodeAt(i) & 0x3FF));
116
- out += (hexTable[0xF0 | (c >> 18)] + hexTable[0x80 | ((c >> 12) & 0x3F)] + hexTable[0x80 | ((c >> 6) & 0x3F)] + hexTable[0x80 | (c & 0x3F)]);
112
+ out += hexTable[0xF0 | (c >> 18)] + hexTable[0x80 | ((c >> 12) & 0x3F)] + hexTable[0x80 | ((c >> 6) & 0x3F)] + hexTable[0x80 | (c & 0x3F)];
117
113
  }
118
114
 
119
115
  return out;
@@ -136,7 +132,9 @@ exports.compact = function (obj, references) {
136
132
  var compacted = [];
137
133
 
138
134
  for (var i = 0; i < obj.length; ++i) {
139
- if (typeof obj[i] !== 'undefined') {
135
+ if (obj[i] && typeof obj[i] === 'object') {
136
+ compacted.push(exports.compact(obj[i], refs));
137
+ } else if (typeof obj[i] !== 'undefined') {
140
138
  compacted.push(obj[i]);
141
139
  }
142
140
  }
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "qs",
3
3
  "description": "A querystring parser that supports nesting and arrays, with a depth limit",
4
4
  "homepage": "https://github.com/ljharb/qs",
5
- "version": "6.1.2",
5
+ "version": "6.2.0",
6
6
  "repository": {
7
7
  "type": "git",
8
8
  "url": "https://github.com/ljharb/qs.git"
@@ -24,18 +24,19 @@
24
24
  },
25
25
  "dependencies": {},
26
26
  "devDependencies": {
27
- "browserify": "^12.0.1",
28
- "tape": "^4.3.0",
27
+ "browserify": "^13.0.1",
28
+ "tape": "^4.5.1",
29
29
  "covert": "^1.1.0",
30
30
  "mkdirp": "^0.5.1",
31
- "eslint": "^1.10.3",
32
- "@ljharb/eslint-config": "^1.6.1",
31
+ "eslint": "^2.9.0",
32
+ "@ljharb/eslint-config": "^4.0.0",
33
33
  "parallelshell": "^2.0.0",
34
- "evalmd": "^0.0.16"
34
+ "iconv-lite": "^0.4.13",
35
+ "evalmd": "^0.0.17"
35
36
  },
36
37
  "scripts": {
37
- "pretest": "npm run lint && npm run readme",
38
- "test": "npm run coverage",
38
+ "pretest": "parallelshell 'npm run --silent readme' 'npm run --silent lint'",
39
+ "test": "npm run --silent coverage",
39
40
  "tests-only": "node test",
40
41
  "readme": "evalmd README.md",
41
42
  "lint": "eslint lib/*.js text/*.js",
package/test/parse.js CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  var test = require('tape');
4
4
  var qs = require('../');
5
+ var iconv = require('iconv-lite');
5
6
 
6
7
  test('parse()', function (t) {
7
8
  t.test('parses a simple string', function (st) {
@@ -120,11 +121,8 @@ test('parse()', function (t) {
120
121
  st.deepEqual(qs.parse('foo[]=bar&foo[bad]=baz'), { foo: { '0': 'bar', bad: 'baz' } });
121
122
  st.deepEqual(qs.parse('foo[bad]=baz&foo[]=bar&foo[]=foo'), { foo: { bad: 'baz', '0': 'bar', '1': 'foo' } });
122
123
  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' }] });
123
-
124
- st.deepEqual(qs.parse('a[]=b&a[t]=u&a[hasOwnProperty]=c', { allowPrototypes: false }), { a: { 0: 'b', t: 'u' } });
125
- st.deepEqual(qs.parse('a[]=b&a[t]=u&a[hasOwnProperty]=c', { allowPrototypes: true }), { a: { 0: 'b', t: 'u', hasOwnProperty: 'c' } });
126
- st.deepEqual(qs.parse('a[]=b&a[hasOwnProperty]=c&a[x]=y', { allowPrototypes: false }), { a: { 0: 'b', x: 'y' } });
127
- st.deepEqual(qs.parse('a[]=b&a[hasOwnProperty]=c&a[x]=y', { allowPrototypes: true }), { a: { 0: 'b', hasOwnProperty: 'c', x: 'y' } });
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' } });
128
126
  st.end();
129
127
  });
130
128
 
@@ -142,6 +140,8 @@ test('parse()', function (t) {
142
140
  st.end();
143
141
  });
144
142
 
143
+ t.deepEqual(qs.parse('a[b]=c&a=d'), { a: { b: 'c', d: true } }, 'can add keys to objects');
144
+
145
145
  t.test('correctly prunes undefined values when converting an array to an object', function (st) {
146
146
  st.deepEqual(qs.parse('a[2]=b&a[99999999]=c'), { a: { '2': 'b', '99999999': 'c' } });
147
147
  st.end();
@@ -182,6 +182,9 @@ test('parse()', function (t) {
182
182
 
183
183
  t.test('compacts sparse arrays', function (st) {
184
184
  st.deepEqual(qs.parse('a[10]=1&a[2]=2'), { a: ['2', '1'] });
185
+ st.deepEqual(qs.parse('a[1][b][2][c]=1'), { a: [{ b: [{ c: '1' }] }] });
186
+ st.deepEqual(qs.parse('a[1][2][3][c]=1'), { a: [[[{ c: '1' }]]] });
187
+ st.deepEqual(qs.parse('a[1][2][3][c][1]=1'), { a: [[[{ c: ['1'] }]]] });
185
188
  st.end();
186
189
  });
187
190
 
@@ -371,75 +374,13 @@ test('parse()', function (t) {
371
374
  st.end();
372
375
  });
373
376
 
374
- t.test('does not allow overwriting prototype properties', function (st) {
375
- st.deepEqual(qs.parse('a[hasOwnProperty]=b', { allowPrototypes: false }), {});
376
- st.deepEqual(qs.parse('hasOwnProperty=b', { allowPrototypes: false }), {});
377
-
378
- st.deepEqual(
379
- qs.parse('toString', { allowPrototypes: false }),
380
- {},
381
- 'bare "toString" results in {}'
382
- );
383
-
384
- st.end();
385
- });
386
-
387
377
  t.test('can allow overwriting prototype properties', function (st) {
388
- st.deepEqual(qs.parse('a[hasOwnProperty]=b', { allowPrototypes: true }), { a: { hasOwnProperty: 'b' } });
389
- st.deepEqual(qs.parse('hasOwnProperty=b', { allowPrototypes: true }), { hasOwnProperty: 'b' });
390
-
391
- st.deepEqual(
392
- qs.parse('toString', { allowPrototypes: true }),
393
- { toString: '' },
394
- 'bare "toString" results in { toString: "" }'
395
- );
396
-
397
- st.end();
398
- });
399
-
400
- t.test('params starting with a closing bracket', function (st) {
401
- st.deepEqual(qs.parse(']=toString'), { ']': 'toString' });
402
- st.deepEqual(qs.parse(']]=toString'), { ']]': 'toString' });
403
- st.deepEqual(qs.parse(']hello]=toString'), { ']hello]': 'toString' });
404
- st.end();
405
- });
406
-
407
- t.test('params starting with a starting bracket', function (st) {
408
- st.deepEqual(qs.parse('[=toString'), { '[': 'toString' });
409
- st.deepEqual(qs.parse('[[=toString'), { '[[': 'toString' });
410
- st.deepEqual(qs.parse('[hello[=toString'), { '[hello[': 'toString' });
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 });
411
380
  st.end();
412
381
  });
413
382
 
414
- t.test('add keys to objects', function (st) {
415
- st.deepEqual(
416
- qs.parse('a[b]=c&a=d'),
417
- { a: { b: 'c', d: true } },
418
- 'can add keys to objects'
419
- );
420
-
421
- st.deepEqual(
422
- qs.parse('a[b]=c&a=toString'),
423
- { a: { b: 'c' } },
424
- 'can not overwrite prototype'
425
- );
426
-
427
- st.deepEqual(
428
- qs.parse('a[b]=c&a=toString', { allowPrototypes: true }),
429
- { a: { b: 'c', toString: true } },
430
- 'can overwrite prototype with allowPrototypes true'
431
- );
432
-
433
- st.deepEqual(
434
- qs.parse('a[b]=c&a=toString', { plainObjects: true }),
435
- { a: { b: 'c', toString: true } },
436
- 'can overwrite prototype with plainObjects true'
437
- );
438
-
439
- st.end();
440
- });
441
-
442
- t.test('can return null objects', { skip: !Object.create }, function (st) {
383
+ t.test('can return plain objects', function (st) {
443
384
  var expected = Object.create(null);
444
385
  expected.a = Object.create(null);
445
386
  expected.a.b = 'c';
@@ -453,4 +394,30 @@ test('parse()', function (t) {
453
394
  st.deepEqual(qs.parse('a[]=b&a[c]=d', { plainObjects: true }), expectedArray);
454
395
  st.end();
455
396
  });
397
+
398
+ t.test('can parse with custom encoding', function (st) {
399
+ st.deepEqual(qs.parse('%8c%a7=%91%e5%8d%e3%95%7b', {
400
+ decoder: function (str) {
401
+ var reg = /\%([0-9A-F]{2})/ig;
402
+ var result = [];
403
+ var parts;
404
+ var last = 0;
405
+ while (parts = reg.exec(str)) {
406
+ result.push(parseInt(parts[1], 16));
407
+ last = parts.index + parts[0].length;
408
+ }
409
+ return iconv.decode(new Buffer(result), 'shift_jis').toString();
410
+ }
411
+ }), { 県: '大阪府' });
412
+ st.end();
413
+ });
414
+
415
+ t.test('throws error with wrong decoder', function (st) {
416
+ st.throws(function () {
417
+ qs.parse({}, {
418
+ decoder: 'string'
419
+ });
420
+ }, new TypeError('Decoder has to be a function.'));
421
+ st.end();
422
+ });
456
423
  });
package/test/stringify.js CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  var test = require('tape');
4
4
  var qs = require('../');
5
+ var iconv = require('iconv-lite');
5
6
 
6
7
  test('stringify()', function (t) {
7
8
  t.test('stringifies a querystring object', function (st) {
@@ -21,7 +22,7 @@ test('stringify()', function (t) {
21
22
  st.equal(qs.stringify({ a: { b: { c: { d: 'e' } } } }), 'a%5Bb%5D%5Bc%5D%5Bd%5D=e');
22
23
  st.end();
23
24
  });
24
-
25
+
25
26
  t.test('stringifies a nested object with dots notation', function (st) {
26
27
  st.equal(qs.stringify({ a: { b: 'c' } }, { allowDots: true }), 'a.b=c');
27
28
  st.equal(qs.stringify({ a: { b: { c: { d: 'e' } } } }, { allowDots: true }), 'a.b.c.d=e');
@@ -53,7 +54,7 @@ test('stringify()', function (t) {
53
54
  st.equal(qs.stringify({ a: { b: ['c', 'd'] } }), 'a%5Bb%5D%5B0%5D=c&a%5Bb%5D%5B1%5D=d');
54
55
  st.end();
55
56
  });
56
-
57
+
57
58
  t.test('stringifies a nested array value with dots notation', function (st) {
58
59
  st.equal(qs.stringify({ a: { b: ['c', 'd'] } }, { allowDots: true, encode: false }), 'a.b[0]=c&a.b[1]=d');
59
60
  st.end();
@@ -64,7 +65,12 @@ test('stringify()', function (t) {
64
65
  st.equal(qs.stringify({ a: [{ b: { c: [1] } }] }), 'a%5B0%5D%5Bb%5D%5Bc%5D%5B0%5D=1');
65
66
  st.end();
66
67
  });
67
-
68
+
69
+ t.test('stringifies an array with mixed objects and primitives', function (st) {
70
+ st.equal(qs.stringify({ a: [{ b: 1 }, 2, 3] }, { encode: false }), 'a[0][b]=1&a[1]=2&a[2]=3');
71
+ st.end();
72
+ });
73
+
68
74
  t.test('stringifies an object inside an array with dots notation', function (st) {
69
75
  st.equal(qs.stringify({ a: [{ b: 'c' }] }, { allowDots: true, encode: false }), 'a[0].b=c');
70
76
  st.equal(qs.stringify({ a: [{ b: { c: [1] } }] }, { allowDots: true, encode: false }), 'a[0].b.c[0]=1');
@@ -249,11 +255,51 @@ test('stringify()', function (t) {
249
255
  st.equal(qs.stringify({ a: 'c', z: { j: 'a', i: 'b' }, b: 'f' }, { sort: sort }), 'a=c&b=f&z%5Bi%5D=b&z%5Bj%5D=a');
250
256
  st.end();
251
257
  });
252
-
258
+
253
259
  t.test('can sort the keys at depth 3 or more too', function (st) {
254
260
  var sort = function (a, b) { return a.localeCompare(b); };
255
261
  st.equal(qs.stringify({ a: 'a', z: { zj: {zjb: 'zjb', zja: 'zja'}, zi: {zib: 'zib', zia: 'zia'} }, b: 'b' }, { sort: sort, encode: false }), 'a=a&b=b&z[zi][zia]=zia&z[zi][zib]=zib&z[zj][zja]=zja&z[zj][zjb]=zjb');
256
262
  st.equal(qs.stringify({ a: 'a', z: { zj: {zjb: 'zjb', zja: 'zja'}, zi: {zib: 'zib', zia: 'zia'} }, b: 'b' }, { sort: null, encode: false }), 'a=a&z[zj][zjb]=zjb&z[zj][zja]=zja&z[zi][zib]=zib&z[zi][zia]=zia&b=b');
257
263
  st.end();
258
264
  });
265
+
266
+ t.test('can stringify with custom encoding', function (st) {
267
+ st.equal(qs.stringify({ 県: '大阪府', '': ''}, {
268
+ encoder: function (str) {
269
+ if (str.length === 0) {
270
+ return '';
271
+ }
272
+ var buf = iconv.encode(str, 'shiftjis');
273
+ var result = [];
274
+ for (var i=0; i < buf.length; ++i) {
275
+ result.push(buf.readUInt8(i).toString(16));
276
+ }
277
+ return '%' + result.join('%');
278
+ }
279
+ }), '%8c%a7=%91%e5%8d%e3%95%7b&=');
280
+ st.end();
281
+ });
282
+
283
+ t.test('throws error with wrong encoder', function (st) {
284
+ st.throws(function () {
285
+ qs.stringify({}, {
286
+ encoder: 'string'
287
+ });
288
+ }, new TypeError('Encoder has to be a function.'));
289
+ st.end();
290
+ });
291
+
292
+ t.test('can use custom encoder for a buffer object', {
293
+ skip: typeof Buffer === 'undefined'
294
+ }, function (st) {
295
+ st.equal(qs.stringify({ a: new Buffer([1]) }, {
296
+ encoder: function (buffer) {
297
+ if (typeof buffer === 'string') {
298
+ return buffer;
299
+ }
300
+ return String.fromCharCode(buffer.readUInt8(0) + 97);
301
+ }
302
+ }), 'a=b');
303
+ st.end();
304
+ });
259
305
  });
package/test/utils.js CHANGED
File without changes