1// Copyright Joyent, Inc. and other Node contributors. 2// 3// Permission is hereby granted, free of charge, to any person obtaining a 4// copy of this software and associated documentation files (the 5// "Software"), to deal in the Software without restriction, including 6// without limitation the rights to use, copy, modify, merge, publish, 7// distribute, sublicense, and/or sell copies of the Software, and to permit 8// persons to whom the Software is furnished to do so, subject to the 9// following conditions: 10// 11// The above copyright notice and this permission notice shall be included 12// in all copies or substantial portions of the Software. 13// 14// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 15// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN 17// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 18// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 19// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE 20// USE OR OTHER DEALINGS IN THE SOFTWARE. 21 22'use strict'; 23require('../common'); 24const assert = require('assert'); 25const inspect = require('util').inspect; 26 27// test using assert 28const qs = require('querystring'); 29 30function createWithNoPrototype(properties) { 31 const noProto = Object.create(null); 32 properties.forEach((property) => { 33 noProto[property.key] = property.value; 34 }); 35 return noProto; 36} 37// Folding block, commented to pass gjslint 38// {{{ 39// [ wonkyQS, canonicalQS, obj ] 40const qsTestCases = [ 41 ['__proto__=1', 42 '__proto__=1', 43 createWithNoPrototype([{ key: '__proto__', value: '1' }])], 44 ['__defineGetter__=asdf', 45 '__defineGetter__=asdf', 46 JSON.parse('{"__defineGetter__":"asdf"}')], 47 ['foo=918854443121279438895193', 48 'foo=918854443121279438895193', 49 { 'foo': '918854443121279438895193' }], 50 ['foo=bar', 'foo=bar', { 'foo': 'bar' }], 51 ['foo=bar&foo=quux', 'foo=bar&foo=quux', { 'foo': ['bar', 'quux'] }], 52 ['foo=1&bar=2', 'foo=1&bar=2', { 'foo': '1', 'bar': '2' }], 53 ['my+weird+field=q1%212%22%27w%245%267%2Fz8%29%3F', 54 'my%20weird%20field=q1!2%22\'w%245%267%2Fz8)%3F', 55 { 'my weird field': 'q1!2"\'w$5&7/z8)?' }], 56 ['foo%3Dbaz=bar', 'foo%3Dbaz=bar', { 'foo=baz': 'bar' }], 57 ['foo=baz=bar', 'foo=baz%3Dbar', { 'foo': 'baz=bar' }], 58 ['str=foo&arr=1&arr=2&arr=3&somenull=&undef=', 59 'str=foo&arr=1&arr=2&arr=3&somenull=&undef=', 60 { 'str': 'foo', 61 'arr': ['1', '2', '3'], 62 'somenull': '', 63 'undef': '' }], 64 [' foo = bar ', '%20foo%20=%20bar%20', { ' foo ': ' bar ' }], 65 ['foo=%zx', 'foo=%25zx', { 'foo': '%zx' }], 66 ['foo=%EF%BF%BD', 'foo=%EF%BF%BD', { 'foo': '\ufffd' }], 67 // See: https://github.com/joyent/node/issues/1707 68 ['hasOwnProperty=x&toString=foo&valueOf=bar&__defineGetter__=baz', 69 'hasOwnProperty=x&toString=foo&valueOf=bar&__defineGetter__=baz', 70 { hasOwnProperty: 'x', 71 toString: 'foo', 72 valueOf: 'bar', 73 __defineGetter__: 'baz' }], 74 // See: https://github.com/joyent/node/issues/3058 75 ['foo&bar=baz', 'foo=&bar=baz', { foo: '', bar: 'baz' }], 76 ['a=b&c&d=e', 'a=b&c=&d=e', { a: 'b', c: '', d: 'e' }], 77 ['a=b&c=&d=e', 'a=b&c=&d=e', { a: 'b', c: '', d: 'e' }], 78 ['a=b&=c&d=e', 'a=b&=c&d=e', { 'a': 'b', '': 'c', 'd': 'e' }], 79 ['a=b&=&c=d', 'a=b&=&c=d', { 'a': 'b', '': '', 'c': 'd' }], 80 ['&&foo=bar&&', 'foo=bar', { foo: 'bar' }], 81 ['&', '', {}], 82 ['&&&&', '', {}], 83 ['&=&', '=', { '': '' }], 84 ['&=&=', '=&=', { '': [ '', '' ] }], 85 ['=', '=', { '': '' }], 86 ['+', '%20=', { ' ': '' }], 87 ['+=', '%20=', { ' ': '' }], 88 ['+&', '%20=', { ' ': '' }], 89 ['=+', '=%20', { '': ' ' }], 90 ['+=&', '%20=', { ' ': '' }], 91 ['a&&b', 'a=&b=', { 'a': '', 'b': '' }], 92 ['a=a&&b=b', 'a=a&b=b', { 'a': 'a', 'b': 'b' }], 93 ['&a', 'a=', { 'a': '' }], 94 ['&=', '=', { '': '' }], 95 ['a&a&', 'a=&a=', { a: [ '', '' ] }], 96 ['a&a&a&', 'a=&a=&a=', { a: [ '', '', '' ] }], 97 ['a&a&a&a&', 'a=&a=&a=&a=', { a: [ '', '', '', '' ] }], 98 ['a=&a=value&a=', 'a=&a=value&a=', { a: [ '', 'value', '' ] }], 99 ['foo+bar=baz+quux', 'foo%20bar=baz%20quux', { 'foo bar': 'baz quux' }], 100 ['+foo=+bar', '%20foo=%20bar', { ' foo': ' bar' }], 101 ['a+', 'a%20=', { 'a ': '' }], 102 ['=a+', '=a%20', { '': 'a ' }], 103 ['a+&', 'a%20=', { 'a ': '' }], 104 ['=a+&', '=a%20', { '': 'a ' }], 105 ['%20+', '%20%20=', { ' ': '' }], 106 ['=%20+', '=%20%20', { '': ' ' }], 107 ['%20+&', '%20%20=', { ' ': '' }], 108 ['=%20+&', '=%20%20', { '': ' ' }], 109 [null, '', {}], 110 [undefined, '', {}], 111]; 112 113// [ wonkyQS, canonicalQS, obj ] 114const qsColonTestCases = [ 115 ['foo:bar', 'foo:bar', { 'foo': 'bar' }], 116 ['foo:bar;foo:quux', 'foo:bar;foo:quux', { 'foo': ['bar', 'quux'] }], 117 ['foo:1&bar:2;baz:quux', 118 'foo:1%26bar%3A2;baz:quux', 119 { 'foo': '1&bar:2', 'baz': 'quux' }], 120 ['foo%3Abaz:bar', 'foo%3Abaz:bar', { 'foo:baz': 'bar' }], 121 ['foo:baz:bar', 'foo:baz%3Abar', { 'foo': 'baz:bar' }], 122]; 123 124// [wonkyObj, qs, canonicalObj] 125function extendedFunction() {} 126extendedFunction.prototype = { a: 'b' }; 127const qsWeirdObjects = [ 128 // eslint-disable-next-line node-core/no-unescaped-regexp-dot 129 [{ regexp: /./g }, 'regexp=', { 'regexp': '' }], 130 // eslint-disable-next-line node-core/no-unescaped-regexp-dot 131 [{ regexp: new RegExp('.', 'g') }, 'regexp=', { 'regexp': '' }], 132 [{ fn: () => {} }, 'fn=', { 'fn': '' }], 133 [{ fn: new Function('') }, 'fn=', { 'fn': '' }], 134 [{ math: Math }, 'math=', { 'math': '' }], 135 [{ e: extendedFunction }, 'e=', { 'e': '' }], 136 [{ d: new Date() }, 'd=', { 'd': '' }], 137 [{ d: Date }, 'd=', { 'd': '' }], 138 [ 139 { f: new Boolean(false), t: new Boolean(true) }, 140 'f=&t=', 141 { 'f': '', 't': '' }, 142 ], 143 [{ f: false, t: true }, 'f=false&t=true', { 'f': 'false', 't': 'true' }], 144 [{ n: null }, 'n=', { 'n': '' }], 145 [{ nan: NaN }, 'nan=', { 'nan': '' }], 146 [{ inf: Infinity }, 'inf=', { 'inf': '' }], 147 [{ a: [], b: [] }, '', {}], 148 [{ a: 1, b: [] }, 'a=1', { 'a': '1' }], 149]; 150// }}} 151 152const vm = require('vm'); 153const foreignObject = vm.runInNewContext('({"foo": ["bar", "baz"]})'); 154 155const qsNoMungeTestCases = [ 156 ['', {}], 157 ['foo=bar&foo=baz', { 'foo': ['bar', 'baz'] }], 158 ['foo=bar&foo=baz', foreignObject], 159 ['blah=burp', { 'blah': 'burp' }], 160 ['a=!-._~\'()*', { 'a': '!-._~\'()*' }], 161 ['a=abcdefghijklmnopqrstuvwxyz', { 'a': 'abcdefghijklmnopqrstuvwxyz' }], 162 ['a=ABCDEFGHIJKLMNOPQRSTUVWXYZ', { 'a': 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' }], 163 ['a=0123456789', { 'a': '0123456789' }], 164 ['gragh=1&gragh=3&goo=2', { 'gragh': ['1', '3'], 'goo': '2' }], 165 ['frappucino=muffin&goat%5B%5D=scone&pond=moose', 166 { 'frappucino': 'muffin', 'goat[]': 'scone', 'pond': 'moose' }], 167 ['trololol=yes&lololo=no', { 'trololol': 'yes', 'lololo': 'no' }], 168]; 169 170const qsUnescapeTestCases = [ 171 ['there is nothing to unescape here', 172 'there is nothing to unescape here'], 173 ['there%20are%20several%20spaces%20that%20need%20to%20be%20unescaped', 174 'there are several spaces that need to be unescaped'], 175 ['there%2Qare%0-fake%escaped values in%%%%this%9Hstring', 176 'there%2Qare%0-fake%escaped values in%%%%this%9Hstring'], 177 ['%20%21%22%23%24%25%26%27%28%29%2A%2B%2C%2D%2E%2F%30%31%32%33%34%35%36%37', 178 ' !"#$%&\'()*+,-./01234567'], 179 ['%%2a', '%*'], 180 ['%2sf%2a', '%2sf*'], 181 ['%2%2af%2a', '%2*f*'], 182]; 183 184assert.strictEqual(qs.parse('id=918854443121279438895193').id, 185 '918854443121279438895193'); 186 187function check(actual, expected, input) { 188 assert(!(actual instanceof Object)); 189 const actualKeys = Object.keys(actual).sort(); 190 const expectedKeys = Object.keys(expected).sort(); 191 let msg; 192 if (typeof input === 'string') { 193 msg = `Input: ${inspect(input)}\n` + 194 `Actual keys: ${inspect(actualKeys)}\n` + 195 `Expected keys: ${inspect(expectedKeys)}`; 196 } 197 assert.deepStrictEqual(actualKeys, expectedKeys, msg); 198 expectedKeys.forEach((key) => { 199 if (typeof input === 'string') { 200 msg = `Input: ${inspect(input)}\n` + 201 `Key: ${inspect(key)}\n` + 202 `Actual value: ${inspect(actual[key])}\n` + 203 `Expected value: ${inspect(expected[key])}`; 204 } else { 205 msg = undefined; 206 } 207 assert.deepStrictEqual(actual[key], expected[key], msg); 208 }); 209} 210 211// Test that the canonical qs is parsed properly. 212qsTestCases.forEach((testCase) => { 213 check(qs.parse(testCase[0]), testCase[2], testCase[0]); 214}); 215 216// Test that the colon test cases can do the same 217qsColonTestCases.forEach((testCase) => { 218 check(qs.parse(testCase[0], ';', ':'), testCase[2], testCase[0]); 219}); 220 221// Test the weird objects, that they get parsed properly 222qsWeirdObjects.forEach((testCase) => { 223 check(qs.parse(testCase[1]), testCase[2], testCase[1]); 224}); 225 226qsNoMungeTestCases.forEach((testCase) => { 227 assert.deepStrictEqual(qs.stringify(testCase[1], '&', '='), testCase[0]); 228}); 229 230// Test the nested qs-in-qs case 231{ 232 const f = qs.parse('a=b&q=x%3Dy%26y%3Dz'); 233 check(f, createWithNoPrototype([ 234 { key: 'a', value: 'b' }, 235 { key: 'q', value: 'x=y&y=z' }, 236 ])); 237 238 f.q = qs.parse(f.q); 239 const expectedInternal = createWithNoPrototype([ 240 { key: 'x', value: 'y' }, 241 { key: 'y', value: 'z' }, 242 ]); 243 check(f.q, expectedInternal); 244} 245 246// nested in colon 247{ 248 const f = qs.parse('a:b;q:x%3Ay%3By%3Az', ';', ':'); 249 check(f, createWithNoPrototype([ 250 { key: 'a', value: 'b' }, 251 { key: 'q', value: 'x:y;y:z' }, 252 ])); 253 f.q = qs.parse(f.q, ';', ':'); 254 const expectedInternal = createWithNoPrototype([ 255 { key: 'x', value: 'y' }, 256 { key: 'y', value: 'z' }, 257 ]); 258 check(f.q, expectedInternal); 259} 260 261// Now test stringifying 262 263// basic 264qsTestCases.forEach((testCase) => { 265 assert.strictEqual(qs.stringify(testCase[2]), testCase[1]); 266}); 267 268qsColonTestCases.forEach((testCase) => { 269 assert.strictEqual(qs.stringify(testCase[2], ';', ':'), testCase[1]); 270}); 271 272qsWeirdObjects.forEach((testCase) => { 273 assert.strictEqual(qs.stringify(testCase[0]), testCase[1]); 274}); 275 276// BigInt values 277 278assert.strictEqual(qs.stringify({ foo: 2n ** 1023n }), 279 'foo=' + 2n ** 1023n); 280assert.strictEqual(qs.stringify([0n, 1n, 2n]), 281 '0=0&1=1&2=2'); 282 283assert.strictEqual(qs.stringify({ foo: 2n ** 1023n }, 284 null, 285 null, 286 { encodeURIComponent: (c) => c }), 287 'foo=' + 2n ** 1023n); 288assert.strictEqual(qs.stringify([0n, 1n, 2n], 289 null, 290 null, 291 { encodeURIComponent: (c) => c }), 292 '0=0&1=1&2=2'); 293 294// Invalid surrogate pair throws URIError 295assert.throws( 296 () => qs.stringify({ foo: '\udc00' }), 297 { 298 code: 'ERR_INVALID_URI', 299 name: 'URIError', 300 message: 'URI malformed' 301 } 302); 303 304// Coerce numbers to string 305assert.strictEqual(qs.stringify({ foo: 0 }), 'foo=0'); 306assert.strictEqual(qs.stringify({ foo: -0 }), 'foo=0'); 307assert.strictEqual(qs.stringify({ foo: 3 }), 'foo=3'); 308assert.strictEqual(qs.stringify({ foo: -72.42 }), 'foo=-72.42'); 309assert.strictEqual(qs.stringify({ foo: NaN }), 'foo='); 310assert.strictEqual(qs.stringify({ foo: 1e21 }), 'foo=1e%2B21'); 311assert.strictEqual(qs.stringify({ foo: Infinity }), 'foo='); 312 313// nested 314{ 315 const f = qs.stringify({ 316 a: 'b', 317 q: qs.stringify({ 318 x: 'y', 319 y: 'z' 320 }) 321 }); 322 assert.strictEqual(f, 'a=b&q=x%3Dy%26y%3Dz'); 323} 324 325qs.parse(undefined); // Should not throw. 326 327// nested in colon 328{ 329 const f = qs.stringify({ 330 a: 'b', 331 q: qs.stringify({ 332 x: 'y', 333 y: 'z' 334 }, ';', ':') 335 }, ';', ':'); 336 assert.strictEqual(f, 'a:b;q:x%3Ay%3By%3Az'); 337} 338 339// empty string 340assert.strictEqual(qs.stringify(), ''); 341assert.strictEqual(qs.stringify(0), ''); 342assert.strictEqual(qs.stringify([]), ''); 343assert.strictEqual(qs.stringify(null), ''); 344assert.strictEqual(qs.stringify(true), ''); 345 346check(qs.parse(), {}); 347 348// empty sep 349check(qs.parse('a', []), { a: '' }); 350 351// empty eq 352check(qs.parse('a', null, []), { '': 'a' }); 353 354// Test limiting 355assert.strictEqual( 356 Object.keys(qs.parse('a=1&b=1&c=1', null, null, { maxKeys: 1 })).length, 357 1); 358 359// Test limiting with a case that starts from `&` 360assert.strictEqual( 361 Object.keys(qs.parse('&a', null, null, { maxKeys: 1 })).length, 362 0); 363 364// Test removing limit 365{ 366 function testUnlimitedKeys() { 367 const query = {}; 368 369 for (let i = 0; i < 2000; i++) query[i] = i; 370 371 const url = qs.stringify(query); 372 373 assert.strictEqual( 374 Object.keys(qs.parse(url, null, null, { maxKeys: 0 })).length, 375 2000); 376 } 377 378 testUnlimitedKeys(); 379} 380 381{ 382 const b = qs.unescapeBuffer('%d3%f2Ug%1f6v%24%5e%98%cb' + 383 '%0d%ac%a2%2f%9d%eb%d8%a2%e6'); 384 // <Buffer d3 f2 55 67 1f 36 76 24 5e 98 cb 0d ac a2 2f 9d eb d8 a2 e6> 385 assert.strictEqual(b[0], 0xd3); 386 assert.strictEqual(b[1], 0xf2); 387 assert.strictEqual(b[2], 0x55); 388 assert.strictEqual(b[3], 0x67); 389 assert.strictEqual(b[4], 0x1f); 390 assert.strictEqual(b[5], 0x36); 391 assert.strictEqual(b[6], 0x76); 392 assert.strictEqual(b[7], 0x24); 393 assert.strictEqual(b[8], 0x5e); 394 assert.strictEqual(b[9], 0x98); 395 assert.strictEqual(b[10], 0xcb); 396 assert.strictEqual(b[11], 0x0d); 397 assert.strictEqual(b[12], 0xac); 398 assert.strictEqual(b[13], 0xa2); 399 assert.strictEqual(b[14], 0x2f); 400 assert.strictEqual(b[15], 0x9d); 401 assert.strictEqual(b[16], 0xeb); 402 assert.strictEqual(b[17], 0xd8); 403 assert.strictEqual(b[18], 0xa2); 404 assert.strictEqual(b[19], 0xe6); 405} 406 407assert.strictEqual(qs.unescapeBuffer('a+b', true).toString(), 'a b'); 408assert.strictEqual(qs.unescapeBuffer('a+b').toString(), 'a+b'); 409assert.strictEqual(qs.unescapeBuffer('a%').toString(), 'a%'); 410assert.strictEqual(qs.unescapeBuffer('a%2').toString(), 'a%2'); 411assert.strictEqual(qs.unescapeBuffer('a%20').toString(), 'a '); 412assert.strictEqual(qs.unescapeBuffer('a%2g').toString(), 'a%2g'); 413assert.strictEqual(qs.unescapeBuffer('a%%').toString(), 'a%%'); 414 415// Test invalid encoded string 416check(qs.parse('%\u0100=%\u0101'), { '%Ā': '%ā' }); 417 418// Test custom decode 419{ 420 function demoDecode(str) { 421 return str + str; 422 } 423 424 check( 425 qs.parse('a=a&b=b&c=c', null, null, { decodeURIComponent: demoDecode }), 426 { aa: 'aa', bb: 'bb', cc: 'cc' }); 427 check( 428 qs.parse('a=a&b=b&c=c', null, '==', { decodeURIComponent: (str) => str }), 429 { 'a=a': '', 'b=b': '', 'c=c': '' }); 430} 431 432// Test QueryString.unescape 433{ 434 function errDecode(str) { 435 throw new Error('To jump to the catch scope'); 436 } 437 438 check(qs.parse('a=a', null, null, { decodeURIComponent: errDecode }), 439 { a: 'a' }); 440} 441 442// Test custom encode 443{ 444 function demoEncode(str) { 445 return str[0]; 446 } 447 448 const obj = { aa: 'aa', bb: 'bb', cc: 'cc' }; 449 assert.strictEqual( 450 qs.stringify(obj, null, null, { encodeURIComponent: demoEncode }), 451 'a=a&b=b&c=c'); 452} 453 454// Test custom encode for different types 455{ 456 const obj = { number: 1, bigint: 2n, true: true, false: false, object: {} }; 457 assert.strictEqual( 458 qs.stringify(obj, null, null, { encodeURIComponent: (v) => v }), 459 'number=1&bigint=2&true=true&false=false&object='); 460} 461 462// Test QueryString.unescapeBuffer 463qsUnescapeTestCases.forEach((testCase) => { 464 assert.strictEqual(qs.unescape(testCase[0]), testCase[1]); 465 assert.strictEqual(qs.unescapeBuffer(testCase[0]).toString(), testCase[1]); 466}); 467 468// Test overriding .unescape 469{ 470 const prevUnescape = qs.unescape; 471 qs.unescape = (str) => { 472 return str.replace(/o/g, '_'); 473 }; 474 check( 475 qs.parse('foo=bor'), 476 createWithNoPrototype([{ key: 'f__', value: 'b_r' }])); 477 qs.unescape = prevUnescape; 478} 479// Test separator and "equals" parsing order 480check(qs.parse('foo&bar', '&', '&'), { foo: '', bar: '' }); 481