From 47917c9c2c56887c53d68e23159a7e1c3f7cb958 Mon Sep 17 00:00:00 2001 From: Blake Embrey Date: Wed, 2 Oct 2024 14:56:24 -0700 Subject: [PATCH] Iterate whitespace for perf (#170) --- index.js | 55 +++++++++++++++++++++++++++++++++++++-------------- test/parse.js | 17 +++++++++++++++- 2 files changed, 56 insertions(+), 16 deletions(-) diff --git a/index.js b/index.js index 6fe633d..0eeccaf 100644 --- a/index.js +++ b/index.js @@ -95,40 +95,49 @@ function parse(str, options) { throw new TypeError('argument str must be a string'); } - var obj = {} + var obj = {}; var opt = options || {}; var dec = opt.decode || decode; - var index = 0 - while (index < str.length) { - var eqIdx = str.indexOf('=', index) + var index = 0; + var eqIdx = 0; + var endIdx = 0; + var len = str.length; + var max = len - 2; + + while (index < max) { + eqIdx = str.indexOf('=', index); // no more cookie pairs if (eqIdx === -1) { - break + break; } - var endIdx = str.indexOf(';', index) + endIdx = str.indexOf(';', index); if (endIdx === -1) { - endIdx = str.length - } else if (endIdx < eqIdx) { + endIdx = len; + } else if (eqIdx > endIdx) { // backtrack on prior semicolon - index = str.lastIndexOf(';', eqIdx - 1) + 1 - continue + index = str.lastIndexOf(';', eqIdx - 1) + 1; + continue; } - var key = str.slice(index, eqIdx).trim() + var keyStartIdx = startIndex(str, index, eqIdx); + var keyEndIdx = endIndex(str, eqIdx, keyStartIdx); + var key = str.slice(keyStartIdx, keyEndIdx); // only assign once if (undefined === obj[key]) { - var val = str.slice(eqIdx + 1, endIdx).trim() + var valStartIdx = startIndex(str, eqIdx + 1, endIdx); + var valEndIdx = endIndex(str, endIdx, valStartIdx); - // quoted values - if (val.charCodeAt(0) === 0x22) { - val = val.slice(1, -1) + if (str.charCodeAt(valStartIdx) === 0x22 /* " */ && str.charCodeAt(valEndIdx - 1) === 0x22 /* " */) { + valStartIdx++; + valEndIdx--; } + var val = str.slice(valStartIdx, valEndIdx); obj[key] = tryDecode(val, dec); } @@ -138,6 +147,22 @@ function parse(str, options) { return obj; } +function startIndex(str, index, max) { + do { + var code = str.charCodeAt(index); + if (code !== 0x20 /* */ && code !== 0x09 /* \t */) return index; + } while (++index < max); + return max; +} + +function endIndex(str, index, min) { + while (index > min) { + var code = str.charCodeAt(--index); + if (code !== 0x20 /* */ && code !== 0x09 /* \t */) return index + 1; + } + return min; +} + /** * Serialize data into a cookie header. * diff --git a/test/parse.js b/test/parse.js index 76229ca..c11b4fc 100644 --- a/test/parse.js +++ b/test/parse.js @@ -24,7 +24,7 @@ describe('cookie.parse(str)', function () { }) it('should parse cookie with empty value', function () { - assert.deepEqual(cookie.parse('foo= ; bar='), { foo: '', bar: '' }) + assert.deepEqual(cookie.parse('foo=; bar='), { foo: '', bar: '' }) }) it('should URL-decode values', function () { @@ -34,6 +34,21 @@ describe('cookie.parse(str)', function () { assert.deepEqual(cookie.parse('email=%20%22%2c%3b%2f'), { email: ' ",;/' }) }) + it('should parse quoted values', function () { + assert.deepEqual(cookie.parse('foo="bar"'), { foo: 'bar' }) + assert.deepEqual(cookie.parse('foo=" a b c "'), { foo: ' a b c ' }) + }) + + it('should trim whitespace around key and value', function () { + assert.deepEqual(cookie.parse(' foo = "bar" '), { foo: 'bar' }) + assert.deepEqual(cookie.parse(' foo = bar ; fizz = buzz '), { foo: 'bar', fizz: 'buzz' }) + assert.deepEqual(cookie.parse(' foo = " a b c " '), { foo: ' a b c ' }) + assert.deepEqual(cookie.parse(' = bar '), { '': 'bar' }) + assert.deepEqual(cookie.parse(' foo = '), { foo: '' }) + assert.deepEqual(cookie.parse(' = '), { '': '' }) + assert.deepEqual(cookie.parse('\tfoo\t=\tbar\t'), { foo: 'bar' }) + }) + it('should return original value on escape error', function () { assert.deepEqual(cookie.parse('foo=%1;bar=bar'), { foo: '%1', bar: 'bar' }) })