qs 6.5.3 → 6.6.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/stringify.js CHANGED
@@ -4,13 +4,13 @@ var utils = require('./utils');
4
4
  var formats = require('./formats');
5
5
 
6
6
  var arrayPrefixGenerators = {
7
- brackets: function brackets(prefix) {
7
+ brackets: function brackets(prefix) { // eslint-disable-line func-name-matching
8
8
  return prefix + '[]';
9
9
  },
10
- indices: function indices(prefix, key) {
10
+ indices: function indices(prefix, key) { // eslint-disable-line func-name-matching
11
11
  return prefix + '[' + key + ']';
12
12
  },
13
- repeat: function repeat(prefix) {
13
+ repeat: function repeat(prefix) { // eslint-disable-line func-name-matching
14
14
  return prefix;
15
15
  }
16
16
  };
@@ -24,18 +24,24 @@ var pushToArray = function (arr, valueOrArray) {
24
24
  var toISO = Date.prototype.toISOString;
25
25
 
26
26
  var defaults = {
27
+ addQueryPrefix: false,
28
+ allowDots: false,
29
+ charset: 'utf-8',
30
+ charsetSentinel: false,
27
31
  delimiter: '&',
28
32
  encode: true,
29
33
  encoder: utils.encode,
30
34
  encodeValuesOnly: false,
31
- serializeDate: function serializeDate(date) {
35
+ // deprecated
36
+ indices: false,
37
+ serializeDate: function serializeDate(date) { // eslint-disable-line func-name-matching
32
38
  return toISO.call(date);
33
39
  },
34
40
  skipNulls: false,
35
41
  strictNullHandling: false
36
42
  };
37
43
 
38
- var stringify = function stringify(
44
+ var stringify = function stringify( // eslint-disable-line func-name-matching
39
45
  object,
40
46
  prefix,
41
47
  generateArrayPrefix,
@@ -47,7 +53,8 @@ var stringify = function stringify(
47
53
  allowDots,
48
54
  serializeDate,
49
55
  formatter,
50
- encodeValuesOnly
56
+ encodeValuesOnly,
57
+ charset
51
58
  ) {
52
59
  var obj = object;
53
60
  if (typeof filter === 'function') {
@@ -58,7 +65,7 @@ var stringify = function stringify(
58
65
 
59
66
  if (obj === null) {
60
67
  if (strictNullHandling) {
61
- return encoder && !encodeValuesOnly ? encoder(prefix, defaults.encoder) : prefix;
68
+ return encoder && !encodeValuesOnly ? encoder(prefix, defaults.encoder, charset) : prefix;
62
69
  }
63
70
 
64
71
  obj = '';
@@ -66,8 +73,8 @@ var stringify = function stringify(
66
73
 
67
74
  if (typeof obj === 'string' || typeof obj === 'number' || typeof obj === 'boolean' || utils.isBuffer(obj)) {
68
75
  if (encoder) {
69
- var keyValue = encodeValuesOnly ? prefix : encoder(prefix, defaults.encoder);
70
- return [formatter(keyValue) + '=' + formatter(encoder(obj, defaults.encoder))];
76
+ var keyValue = encodeValuesOnly ? prefix : encoder(prefix, defaults.encoder, charset);
77
+ return [formatter(keyValue) + '=' + formatter(encoder(obj, defaults.encoder, charset))];
71
78
  }
72
79
  return [formatter(prefix) + '=' + formatter(String(obj))];
73
80
  }
@@ -79,7 +86,7 @@ var stringify = function stringify(
79
86
  }
80
87
 
81
88
  var objKeys;
82
- if (isArray(filter)) {
89
+ if (Array.isArray(filter)) {
83
90
  objKeys = filter;
84
91
  } else {
85
92
  var keys = Object.keys(obj);
@@ -93,7 +100,7 @@ var stringify = function stringify(
93
100
  continue;
94
101
  }
95
102
 
96
- if (isArray(obj)) {
103
+ if (Array.isArray(obj)) {
97
104
  pushToArray(values, stringify(
98
105
  obj[key],
99
106
  generateArrayPrefix(prefix, key),
@@ -106,7 +113,8 @@ var stringify = function stringify(
106
113
  allowDots,
107
114
  serializeDate,
108
115
  formatter,
109
- encodeValuesOnly
116
+ encodeValuesOnly,
117
+ charset
110
118
  ));
111
119
  } else {
112
120
  pushToArray(values, stringify(
@@ -121,7 +129,8 @@ var stringify = function stringify(
121
129
  allowDots,
122
130
  serializeDate,
123
131
  formatter,
124
- encodeValuesOnly
132
+ encodeValuesOnly,
133
+ charset
125
134
  ));
126
135
  }
127
136
  }
@@ -133,7 +142,7 @@ module.exports = function (object, opts) {
133
142
  var obj = object;
134
143
  var options = opts ? utils.assign({}, opts) : {};
135
144
 
136
- if (options.encoder !== null && typeof options.encoder !== 'undefined' && typeof options.encoder !== 'function') {
145
+ if (options.encoder !== null && options.encoder !== undefined && typeof options.encoder !== 'function') {
137
146
  throw new TypeError('Encoder has to be a function.');
138
147
  }
139
148
 
@@ -143,9 +152,14 @@ module.exports = function (object, opts) {
143
152
  var encode = typeof options.encode === 'boolean' ? options.encode : defaults.encode;
144
153
  var encoder = typeof options.encoder === 'function' ? options.encoder : defaults.encoder;
145
154
  var sort = typeof options.sort === 'function' ? options.sort : null;
146
- var allowDots = typeof options.allowDots === 'undefined' ? false : options.allowDots;
155
+ var allowDots = typeof options.allowDots === 'undefined' ? defaults.allowDots : !!options.allowDots;
147
156
  var serializeDate = typeof options.serializeDate === 'function' ? options.serializeDate : defaults.serializeDate;
148
157
  var encodeValuesOnly = typeof options.encodeValuesOnly === 'boolean' ? options.encodeValuesOnly : defaults.encodeValuesOnly;
158
+ var charset = options.charset || defaults.charset;
159
+ if (typeof options.charset !== 'undefined' && options.charset !== 'utf-8' && options.charset !== 'iso-8859-1') {
160
+ throw new Error('The charset option must be either utf-8, iso-8859-1, or undefined');
161
+ }
162
+
149
163
  if (typeof options.format === 'undefined') {
150
164
  options.format = formats['default'];
151
165
  } else if (!Object.prototype.hasOwnProperty.call(formats.formatters, options.format)) {
@@ -158,7 +172,7 @@ module.exports = function (object, opts) {
158
172
  if (typeof options.filter === 'function') {
159
173
  filter = options.filter;
160
174
  obj = filter('', obj);
161
- } else if (isArray(options.filter)) {
175
+ } else if (Array.isArray(options.filter)) {
162
176
  filter = options.filter;
163
177
  objKeys = filter;
164
178
  }
@@ -206,12 +220,23 @@ module.exports = function (object, opts) {
206
220
  allowDots,
207
221
  serializeDate,
208
222
  formatter,
209
- encodeValuesOnly
223
+ encodeValuesOnly,
224
+ charset
210
225
  ));
211
226
  }
212
227
 
213
228
  var joined = keys.join(delimiter);
214
229
  var prefix = options.addQueryPrefix === true ? '?' : '';
215
230
 
231
+ if (options.charsetSentinel) {
232
+ if (charset === 'iso-8859-1') {
233
+ // encodeURIComponent('✓'), the "numeric entity" representation of a checkmark
234
+ prefix += 'utf8=%26%2310003%3B&';
235
+ } else {
236
+ // encodeURIComponent('✓')
237
+ prefix += 'utf8=%E2%9C%93&';
238
+ }
239
+ }
240
+
216
241
  return joined.length > 0 ? prefix + joined : '';
217
242
  };
package/lib/utils.js CHANGED
@@ -12,11 +12,9 @@ var hexTable = (function () {
12
12
  }());
13
13
 
14
14
  var compactQueue = function compactQueue(queue) {
15
- var obj;
16
-
17
- while (queue.length) {
15
+ while (queue.length > 1) {
18
16
  var item = queue.pop();
19
- obj = item.obj[item.prop];
17
+ var obj = item.obj[item.prop];
20
18
 
21
19
  if (Array.isArray(obj)) {
22
20
  var compacted = [];
@@ -30,8 +28,6 @@ var compactQueue = function compactQueue(queue) {
30
28
  item.obj[item.prop] = compacted;
31
29
  }
32
30
  }
33
-
34
- return obj;
35
31
  };
36
32
 
37
33
  var arrayToObject = function arrayToObject(source, options) {
@@ -53,7 +49,7 @@ var merge = function merge(target, source, options) {
53
49
  if (typeof source !== 'object') {
54
50
  if (Array.isArray(target)) {
55
51
  target.push(source);
56
- } else if (target && typeof target === 'object') {
52
+ } else if (typeof target === 'object') {
57
53
  if ((options && (options.plainObjects || options.allowPrototypes)) || !has.call(Object.prototype, source)) {
58
54
  target[source] = true;
59
55
  }
@@ -64,7 +60,7 @@ var merge = function merge(target, source, options) {
64
60
  return target;
65
61
  }
66
62
 
67
- if (!target || typeof target !== 'object') {
63
+ if (typeof target !== 'object') {
68
64
  return [target].concat(source);
69
65
  }
70
66
 
@@ -76,9 +72,8 @@ var merge = function merge(target, source, options) {
76
72
  if (Array.isArray(target) && Array.isArray(source)) {
77
73
  source.forEach(function (item, i) {
78
74
  if (has.call(target, i)) {
79
- var targetItem = target[i];
80
- if (targetItem && typeof targetItem === 'object' && item && typeof item === 'object') {
81
- target[i] = merge(targetItem, item, options);
75
+ if (target[i] && typeof target[i] === 'object') {
76
+ target[i] = merge(target[i], item, options);
82
77
  } else {
83
78
  target.push(item);
84
79
  }
@@ -108,15 +103,21 @@ var assign = function assignSingleSource(target, source) {
108
103
  }, target);
109
104
  };
110
105
 
111
- var decode = function (str) {
106
+ var decode = function (str, decoder, charset) {
107
+ var strWithoutPlus = str.replace(/\+/g, ' ');
108
+ if (charset === 'iso-8859-1') {
109
+ // unescape never throws, no try...catch needed:
110
+ return strWithoutPlus.replace(/%[0-9a-f]{2}/gi, unescape);
111
+ }
112
+ // utf-8
112
113
  try {
113
- return decodeURIComponent(str.replace(/\+/g, ' '));
114
+ return decodeURIComponent(strWithoutPlus);
114
115
  } catch (e) {
115
- return str;
116
+ return strWithoutPlus;
116
117
  }
117
118
  };
118
119
 
119
- var encode = function encode(str) {
120
+ var encode = function encode(str, defaultEncoder, charset) {
120
121
  // This code was originally written by Brian White (mscdex) for the io.js core querystring library.
121
122
  // It has been adapted here for stricter adherence to RFC 3986
122
123
  if (str.length === 0) {
@@ -125,6 +126,12 @@ var encode = function encode(str) {
125
126
 
126
127
  var string = typeof str === 'string' ? str : String(str);
127
128
 
129
+ if (charset === 'iso-8859-1') {
130
+ return escape(string).replace(/%u[0-9a-f]{4}/gi, function ($0) {
131
+ return '%26%23' + parseInt($0.slice(2), 16) + '%3B';
132
+ });
133
+ }
134
+
128
135
  var out = '';
129
136
  for (var i = 0; i < string.length; ++i) {
130
137
  var c = string.charCodeAt(i);
@@ -159,7 +166,6 @@ var encode = function encode(str) {
159
166
 
160
167
  i += 1;
161
168
  c = 0x10000 + (((c & 0x3FF) << 10) | (string.charCodeAt(i) & 0x3FF));
162
- /* eslint operator-linebreak: [2, "before"] */
163
169
  out += hexTable[0xF0 | (c >> 18)]
164
170
  + hexTable[0x80 | ((c >> 12) & 0x3F)]
165
171
  + hexTable[0x80 | ((c >> 6) & 0x3F)]
@@ -188,7 +194,9 @@ var compact = function compact(value) {
188
194
  }
189
195
  }
190
196
 
191
- return compactQueue(queue);
197
+ compactQueue(queue);
198
+
199
+ return value;
192
200
  };
193
201
 
194
202
  var isRegExp = function isRegExp(obj) {
@@ -203,9 +211,14 @@ var isBuffer = function isBuffer(obj) {
203
211
  return !!(obj.constructor && obj.constructor.isBuffer && obj.constructor.isBuffer(obj));
204
212
  };
205
213
 
214
+ var combine = function combine(a, b) {
215
+ return [].concat(a, b);
216
+ };
217
+
206
218
  module.exports = {
207
219
  arrayToObject: arrayToObject,
208
220
  assign: assign,
221
+ combine: combine,
209
222
  compact: compact,
210
223
  decode: decode,
211
224
  encode: encode,
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.5.3",
5
+ "version": "6.6.0",
6
6
  "repository": {
7
7
  "type": "git",
8
8
  "url": "https://github.com/ljharb/qs.git"
@@ -22,32 +22,30 @@
22
22
  "engines": {
23
23
  "node": ">=0.6"
24
24
  },
25
+ "dependencies": {},
25
26
  "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",
27
+ "@ljharb/eslint-config": "^13.0.0",
28
+ "browserify": "^16.2.3",
29
+ "covert": "^1.1.0",
30
+ "editorconfig-tools": "^0.1.1",
31
+ "eslint": "^5.9.0",
31
32
  "evalmd": "^0.0.17",
32
33
  "iconv-lite": "^0.4.24",
33
- "in-publish": "^2.0.1",
34
34
  "mkdirp": "^0.5.1",
35
- "nyc": "^10.3.2",
36
35
  "qs-iconv": "^1.0.4",
37
- "safe-publish-latest": "^2.0.0",
36
+ "safe-publish-latest": "^1.1.2",
38
37
  "safer-buffer": "^2.1.2",
39
- "tape": "^5.4.0"
38
+ "tape": "^4.9.1"
40
39
  },
41
40
  "scripts": {
42
- "prepublishOnly": "safe-publish-latest && npm run dist",
43
- "prepublish": "not-in-publish || npm run prepublishOnly",
41
+ "prepublish": "safe-publish-latest && npm run dist",
44
42
  "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",
43
+ "test": "npm run --silent coverage",
44
+ "tests-only": "node test",
48
45
  "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 .",
46
+ "postlint": "editorconfig-tools check * lib/* test/*",
47
+ "lint": "eslint lib/*.js test/*.js",
48
+ "coverage": "covert test",
51
49
  "dist": "mkdirp dist && browserify --standalone Qs lib/index.js > dist/qs.js"
52
50
  },
53
51
  "license": "BSD-3-Clause"
package/test/.eslintrc ADDED
@@ -0,0 +1,17 @@
1
+ {
2
+ "rules": {
3
+ "array-bracket-newline": 0,
4
+ "array-element-newline": 0,
5
+ "consistent-return": 2,
6
+ "function-paren-newline": 0,
7
+ "max-lines": 0,
8
+ "max-lines-per-function": 0,
9
+ "max-nested-callbacks": [2, 3],
10
+ "max-statements": 0,
11
+ "no-buffer-constructor": 0,
12
+ "no-extend-native": 0,
13
+ "no-magic-numbers": 0,
14
+ "object-curly-newline": 0,
15
+ "sort-keys": 0
16
+ }
17
+ }
package/test/parse.js CHANGED
@@ -237,14 +237,6 @@ test('parse()', function (t) {
237
237
  st.end();
238
238
  });
239
239
 
240
- t.test('parses jquery-param strings', function (st) {
241
- // readable = 'filter[0][]=int1&filter[0][]==&filter[0][]=77&filter[]=and&filter[2][]=int2&filter[2][]==&filter[2][]=8'
242
- var encoded = 'filter%5B0%5D%5B%5D=int1&filter%5B0%5D%5B%5D=%3D&filter%5B0%5D%5B%5D=77&filter%5B%5D=and&filter%5B2%5D%5B%5D=int2&filter%5B2%5D%5B%5D=%3D&filter%5B2%5D%5B%5D=8';
243
- var expected = { filter: [['int1', '=', '77'], 'and', ['int2', '=', '8']] };
244
- st.deepEqual(qs.parse(encoded), expected);
245
- st.end();
246
- });
247
-
248
240
  t.test('continues parsing when no parent is found', function (st) {
249
241
  st.deepEqual(qs.parse('[]=&a=b'), { 0: '', a: 'b' });
250
242
  st.deepEqual(qs.parse('[]&a=b', { strictNullHandling: true }), { 0: null, a: 'b' });
@@ -523,73 +515,13 @@ test('parse()', function (t) {
523
515
 
524
516
  st.deepEqual(
525
517
  qs.parse('a[b]=c&a=toString', { plainObjects: true }),
526
- { __proto__: null, a: { __proto__: null, b: 'c', toString: true } },
518
+ { a: { b: 'c', toString: true } },
527
519
  'can overwrite prototype with plainObjects true'
528
520
  );
529
521
 
530
522
  st.end();
531
523
  });
532
524
 
533
- t.test('dunder proto is ignored', function (st) {
534
- var payload = 'categories[__proto__]=login&categories[__proto__]&categories[length]=42';
535
- var result = qs.parse(payload, { allowPrototypes: true });
536
-
537
- st.deepEqual(
538
- result,
539
- {
540
- categories: {
541
- length: '42'
542
- }
543
- },
544
- 'silent [[Prototype]] payload'
545
- );
546
-
547
- var plainResult = qs.parse(payload, { allowPrototypes: true, plainObjects: true });
548
-
549
- st.deepEqual(
550
- plainResult,
551
- {
552
- __proto__: null,
553
- categories: {
554
- __proto__: null,
555
- length: '42'
556
- }
557
- },
558
- 'silent [[Prototype]] payload: plain objects'
559
- );
560
-
561
- var query = qs.parse('categories[__proto__]=cats&categories[__proto__]=dogs&categories[some][json]=toInject', { allowPrototypes: true });
562
-
563
- st.notOk(Array.isArray(query.categories), 'is not an array');
564
- st.notOk(query.categories instanceof Array, 'is not instanceof an array');
565
- st.deepEqual(query.categories, { some: { json: 'toInject' } });
566
- st.equal(JSON.stringify(query.categories), '{"some":{"json":"toInject"}}', 'stringifies as a non-array');
567
-
568
- st.deepEqual(
569
- qs.parse('foo[__proto__][hidden]=value&foo[bar]=stuffs', { allowPrototypes: true }),
570
- {
571
- foo: {
572
- bar: 'stuffs'
573
- }
574
- },
575
- 'hidden values'
576
- );
577
-
578
- st.deepEqual(
579
- qs.parse('foo[__proto__][hidden]=value&foo[bar]=stuffs', { allowPrototypes: true, plainObjects: true }),
580
- {
581
- __proto__: null,
582
- foo: {
583
- __proto__: null,
584
- bar: 'stuffs'
585
- }
586
- },
587
- 'hidden values: plain objects'
588
- );
589
-
590
- st.end();
591
- });
592
-
593
525
  t.test('can return null objects', { skip: !Object.create }, function (st) {
594
526
  var expected = Object.create(null);
595
527
  expected.a = Object.create(null);
@@ -615,7 +547,7 @@ test('parse()', function (t) {
615
547
  result.push(parseInt(parts[1], 16));
616
548
  parts = reg.exec(str);
617
549
  }
618
- return String(iconv.decode(SaferBuffer.from(result), 'shift_jis'));
550
+ return iconv.decode(SaferBuffer.from(result), 'shift_jis').toString();
619
551
  }
620
552
  }), { 県: '大阪府' });
621
553
  st.end();
@@ -645,5 +577,83 @@ test('parse()', function (t) {
645
577
  st.end();
646
578
  });
647
579
 
580
+ t.test('throws if an invalid charset is specified', function (st) {
581
+ st['throws'](function () {
582
+ qs.parse('a=b', { charset: 'foobar' });
583
+ }, new TypeError('The charset option must be either utf-8, iso-8859-1, or undefined'));
584
+ st.end();
585
+ });
586
+
587
+ t.test('parses an iso-8859-1 string if asked to', function (st) {
588
+ st.deepEqual(qs.parse('%A2=%BD', { charset: 'iso-8859-1' }), { '¢': '½' });
589
+ st.end();
590
+ });
591
+
592
+ var urlEncodedCheckmarkInUtf8 = '%E2%9C%93';
593
+ var urlEncodedOSlashInUtf8 = '%C3%B8';
594
+ var urlEncodedNumCheckmark = '%26%2310003%3B';
595
+ var urlEncodedNumSmiley = '%26%239786%3B';
596
+
597
+ t.test('prefers an utf-8 charset specified by the utf8 sentinel to a default charset of iso-8859-1', function (st) {
598
+ st.deepEqual(qs.parse('utf8=' + urlEncodedCheckmarkInUtf8 + '&' + urlEncodedOSlashInUtf8 + '=' + urlEncodedOSlashInUtf8, { charsetSentinel: true, charset: 'iso-8859-1' }), { ø: 'ø' });
599
+ st.end();
600
+ });
601
+
602
+ t.test('prefers an iso-8859-1 charset specified by the utf8 sentinel to a default charset of utf-8', function (st) {
603
+ st.deepEqual(qs.parse('utf8=' + urlEncodedNumCheckmark + '&' + urlEncodedOSlashInUtf8 + '=' + urlEncodedOSlashInUtf8, { charsetSentinel: true, charset: 'utf-8' }), { 'ø': 'ø' });
604
+ st.end();
605
+ });
606
+
607
+ t.test('does not require the utf8 sentinel to be defined before the parameters whose decoding it affects', function (st) {
608
+ st.deepEqual(qs.parse('a=' + urlEncodedOSlashInUtf8 + '&utf8=' + urlEncodedNumCheckmark, { charsetSentinel: true, charset: 'utf-8' }), { a: 'ø' });
609
+ st.end();
610
+ });
611
+
612
+ t.test('should ignore an utf8 sentinel with an unknown value', function (st) {
613
+ st.deepEqual(qs.parse('utf8=foo&' + urlEncodedOSlashInUtf8 + '=' + urlEncodedOSlashInUtf8, { charsetSentinel: true, charset: 'utf-8' }), { ø: 'ø' });
614
+ st.end();
615
+ });
616
+
617
+ t.test('uses the utf8 sentinel to switch to utf-8 when no default charset is given', function (st) {
618
+ st.deepEqual(qs.parse('utf8=' + urlEncodedCheckmarkInUtf8 + '&' + urlEncodedOSlashInUtf8 + '=' + urlEncodedOSlashInUtf8, { charsetSentinel: true }), { ø: 'ø' });
619
+ st.end();
620
+ });
621
+
622
+ t.test('uses the utf8 sentinel to switch to iso-8859-1 when no default charset is given', function (st) {
623
+ st.deepEqual(qs.parse('utf8=' + urlEncodedNumCheckmark + '&' + urlEncodedOSlashInUtf8 + '=' + urlEncodedOSlashInUtf8, { charsetSentinel: true }), { 'ø': 'ø' });
624
+ st.end();
625
+ });
626
+
627
+ t.test('interprets numeric entities in iso-8859-1 when `interpretNumericEntities`', function (st) {
628
+ st.deepEqual(qs.parse('foo=' + urlEncodedNumSmiley, { charset: 'iso-8859-1', interpretNumericEntities: true }), { foo: '☺' });
629
+ st.end();
630
+ });
631
+
632
+ t.test('handles a custom decoder returning `null`, in the `iso-8859-1` charset, when `interpretNumericEntities`', function (st) {
633
+ st.deepEqual(qs.parse('foo=&bar=' + urlEncodedNumSmiley, {
634
+ charset: 'iso-8859-1',
635
+ decoder: function (str, defaultDecoder, charset) {
636
+ return str ? defaultDecoder(str, defaultDecoder, charset) : null;
637
+ },
638
+ interpretNumericEntities: true
639
+ }), { foo: null, bar: '☺' });
640
+ st.end();
641
+ });
642
+
643
+ t.test('does not interpret numeric entities in iso-8859-1 when `interpretNumericEntities` is absent', function (st) {
644
+ st.deepEqual(qs.parse('foo=' + urlEncodedNumSmiley, { charset: 'iso-8859-1' }), { foo: '&#9786;' });
645
+ st.end();
646
+ });
647
+
648
+ t.test('does not interpret numeric entities when the charset is utf-8, even when `interpretNumericEntities`', function (st) {
649
+ st.deepEqual(qs.parse('foo=' + urlEncodedNumSmiley, { charset: 'utf-8', interpretNumericEntities: true }), { foo: '&#9786;' });
650
+ st.end();
651
+ });
652
+
653
+ t.test('does not interpret %uXXXX syntax in iso-8859-1 mode', function (st) {
654
+ st.deepEqual(qs.parse('%u263A=%u263A', { charset: 'iso-8859-1' }), { '%u263A': '%u263A' });
655
+ st.end();
656
+ });
657
+
648
658
  t.end();
649
659
  });
package/test/stringify.js CHANGED
@@ -19,15 +19,6 @@ test('stringify()', function (t) {
19
19
  st.end();
20
20
  });
21
21
 
22
- t.test('stringifies falsy values', function (st) {
23
- st.equal(qs.stringify(undefined), '');
24
- st.equal(qs.stringify(null), '');
25
- st.equal(qs.stringify(null, { strictNullHandling: true }), '');
26
- st.equal(qs.stringify(false), '');
27
- st.equal(qs.stringify(0), '');
28
- st.end();
29
- });
30
-
31
22
  t.test('adds query prefix', function (st) {
32
23
  st.equal(qs.stringify({ a: 'b' }, { addQueryPrefix: true }), '?a=b');
33
24
  st.end();
@@ -38,13 +29,6 @@ test('stringify()', function (t) {
38
29
  st.end();
39
30
  });
40
31
 
41
- t.test('stringifies nested falsy values', function (st) {
42
- st.equal(qs.stringify({ a: { b: { c: null } } }), 'a%5Bb%5D%5Bc%5D=');
43
- st.equal(qs.stringify({ a: { b: { c: null } } }, { strictNullHandling: true }), 'a%5Bb%5D%5Bc%5D');
44
- st.equal(qs.stringify({ a: { b: { c: false } } }), 'a%5Bb%5D%5Bc%5D=false');
45
- st.end();
46
- });
47
-
48
32
  t.test('stringifies a nested object', function (st) {
49
33
  st.equal(qs.stringify({ a: { b: 'c' } }), 'a%5Bb%5D=c');
50
34
  st.equal(qs.stringify({ a: { b: { c: { d: 'e' } } } }), 'a%5Bb%5D%5Bc%5D%5Bd%5D=e');
@@ -506,12 +490,6 @@ test('stringify()', function (t) {
506
490
  return String.fromCharCode(buffer.readUInt8(0) + 97);
507
491
  }
508
492
  }), 'a=b');
509
-
510
- st.equal(qs.stringify({ a: SaferBuffer.from('a b') }, {
511
- encoder: function (buffer) {
512
- return buffer;
513
- }
514
- }), 'a=a b');
515
493
  st.end();
516
494
  });
517
495
 
@@ -552,20 +530,17 @@ test('stringify()', function (t) {
552
530
  t.test('RFC 1738 spaces serialization', function (st) {
553
531
  st.equal(qs.stringify({ a: 'b c' }, { format: qs.formats.RFC1738 }), 'a=b+c');
554
532
  st.equal(qs.stringify({ 'a b': 'c d' }, { format: qs.formats.RFC1738 }), 'a+b=c+d');
555
- st.equal(qs.stringify({ 'a b': SaferBuffer.from('a b') }, { format: qs.formats.RFC1738 }), 'a+b=a+b');
556
533
  st.end();
557
534
  });
558
535
 
559
536
  t.test('RFC 3986 spaces serialization', function (st) {
560
537
  st.equal(qs.stringify({ a: 'b c' }, { format: qs.formats.RFC3986 }), 'a=b%20c');
561
538
  st.equal(qs.stringify({ 'a b': 'c d' }, { format: qs.formats.RFC3986 }), 'a%20b=c%20d');
562
- st.equal(qs.stringify({ 'a b': SaferBuffer.from('a b') }, { format: qs.formats.RFC3986 }), 'a%20b=a%20b');
563
539
  st.end();
564
540
  });
565
541
 
566
542
  t.test('Backward compatibility to RFC 3986', function (st) {
567
543
  st.equal(qs.stringify({ a: 'b c' }), 'a=b%20c');
568
- st.equal(qs.stringify({ 'a b': SaferBuffer.from('a b') }), 'a%20b=a%20b');
569
544
  st.end();
570
545
  });
571
546
 
@@ -611,6 +586,38 @@ test('stringify()', function (t) {
611
586
  st.end();
612
587
  });
613
588
 
589
+ t.test('throws if an invalid charset is specified', function (st) {
590
+ st['throws'](function () {
591
+ qs.stringify({ a: 'b' }, { charset: 'foobar' });
592
+ }, new TypeError('The charset option must be either utf-8, iso-8859-1, or undefined'));
593
+ st.end();
594
+ });
595
+
596
+ t.test('respects a charset of iso-8859-1', function (st) {
597
+ st.equal(qs.stringify({ æ: 'æ' }, { charset: 'iso-8859-1' }), '%E6=%E6');
598
+ st.end();
599
+ });
600
+
601
+ t.test('encodes unrepresentable chars as numeric entities in iso-8859-1 mode', function (st) {
602
+ st.equal(qs.stringify({ a: '☺' }, { charset: 'iso-8859-1' }), 'a=%26%239786%3B');
603
+ st.end();
604
+ });
605
+
606
+ t.test('respects an explicit charset of utf-8 (the default)', function (st) {
607
+ st.equal(qs.stringify({ a: 'æ' }, { charset: 'utf-8' }), 'a=%C3%A6');
608
+ st.end();
609
+ });
610
+
611
+ t.test('adds the right sentinel when instructed to and the charset is utf-8', function (st) {
612
+ st.equal(qs.stringify({ a: 'æ' }, { charsetSentinel: true, charset: 'utf-8' }), 'utf8=%E2%9C%93&a=%C3%A6');
613
+ st.end();
614
+ });
615
+
616
+ t.test('adds the right sentinel when instructed to and the charset is iso-8859-1', function (st) {
617
+ st.equal(qs.stringify({ a: 'æ' }, { charsetSentinel: true, charset: 'iso-8859-1' }), 'utf8=%26%2310003%3B&a=%E6');
618
+ st.end();
619
+ });
620
+
614
621
  t.test('does not mutate the options argument', function (st) {
615
622
  var options = {};
616
623
  qs.stringify({}, options);