From f4796d5f6e227182180266bcee222555bfad6f2a Mon Sep 17 00:00:00 2001 From: Brian White Date: Tue, 17 Jan 2017 01:07:42 -0500 Subject: [PATCH] querystring: improve stringify() performance PR-URL: https://github.com/nodejs/node/pull/10852 Reviewed-By: Anna Henningsen Reviewed-By: James M Snell Reviewed-By: Claudio Rodriguez --- lib/querystring.js | 49 ++++++++++++++++++------------- test/parallel/test-querystring.js | 4 +++ 2 files changed, 32 insertions(+), 21 deletions(-) diff --git a/lib/querystring.js b/lib/querystring.js index c22c7c998dbd21..2ced10c72d1452 100644 --- a/lib/querystring.js +++ b/lib/querystring.js @@ -100,12 +100,29 @@ function qsUnescape(s, decodeSpaces) { } -var hexTable = new Array(256); +const hexTable = []; for (var i = 0; i < 256; ++i) hexTable[i] = '%' + ((i < 16 ? '0' : '') + i.toString(16)).toUpperCase(); + +// These characters do not need escaping when generating query strings: +// ! - . _ ~ +// ' ( ) * +// digits +// alpha (uppercase) +// alpha (lowercase) +const noEscape = [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0 - 15 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 16 - 31 + 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, // 32 - 47 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, // 48 - 63 + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 64 - 79 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, // 80 - 95 + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 96 - 111 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0 // 112 - 127 +]; +// QueryString.escape() replaces encodeURIComponent() +// http://www.ecma-international.org/ecma-262/5.1/#sec-15.1.3.4 function qsEscape(str) { - // replaces encodeURIComponent - // http://www.ecma-international.org/ecma-262/5.1/#sec-15.1.3.4 if (typeof str !== 'string') { if (typeof str === 'object') str = String(str); @@ -118,30 +135,20 @@ function qsEscape(str) { for (var i = 0; i < str.length; ++i) { var c = str.charCodeAt(i); - // These characters do not need escaping (in order): - // ! - . _ ~ - // ' ( ) * - // digits - // alpha (uppercase) - // alpha (lowercase) - if (c === 0x21 || c === 0x2D || c === 0x2E || c === 0x5F || c === 0x7E || - (c >= 0x27 && c <= 0x2A) || - (c >= 0x30 && c <= 0x39) || - (c >= 0x41 && c <= 0x5A) || - (c >= 0x61 && c <= 0x7A)) { - continue; - } - - if (i - lastPos > 0) - out += str.slice(lastPos, i); - - // Other ASCII characters + // ASCII if (c < 0x80) { + if (noEscape[c] === 1) + continue; + if (lastPos < i) + out += str.slice(lastPos, i); lastPos = i + 1; out += hexTable[c]; continue; } + if (lastPos < i) + out += str.slice(lastPos, i); + // Multi-byte characters ... if (c < 0x800) { lastPos = i + 1; diff --git a/test/parallel/test-querystring.js b/test/parallel/test-querystring.js index 89c880c7e0e3ce..b45ab66e7b3ffc 100644 --- a/test/parallel/test-querystring.js +++ b/test/parallel/test-querystring.js @@ -95,6 +95,10 @@ const qsNoMungeTestCases = [ ['foo=bar&foo=baz', {'foo': ['bar', 'baz']}], ['foo=bar&foo=baz', foreignObject], ['blah=burp', {'blah': 'burp'}], + ['a=!-._~\'()*', {'a': '!-._~\'()*'}], + ['a=abcdefghijklmnopqrstuvwxyz', {'a': 'abcdefghijklmnopqrstuvwxyz'}], + ['a=ABCDEFGHIJKLMNOPQRSTUVWXYZ', {'a': 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'}], + ['a=0123456789', {'a': '0123456789'}], ['gragh=1&gragh=3&goo=2', {'gragh': ['1', '3'], 'goo': '2'}], ['frappucino=muffin&goat%5B%5D=scone&pond=moose', {'frappucino': 'muffin', 'goat[]': 'scone', 'pond': 'moose'}],