From 76a7b00f5b1f1247e07a99dc3d7c124adf4a0488 Mon Sep 17 00:00:00 2001 From: Blake Embrey Date: Tue, 1 Oct 2024 16:32:37 -0700 Subject: [PATCH 1/7] Add test coverage for quotes and whitespace --- test/parse.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/test/parse.js b/test/parse.js index 76229ca..cc69bc9 100644 --- a/test/parse.js +++ b/test/parse.js @@ -34,6 +34,16 @@ 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' }) + }) + + 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 ' }) + }) + it('should return original value on escape error', function () { assert.deepEqual(cookie.parse('foo=%1;bar=bar'), { foo: '%1', bar: 'bar' }) }) From cbc94b4d368476620c074b15007ef5eb64956976 Mon Sep 17 00:00:00 2001 From: Blake Embrey Date: Tue, 1 Oct 2024 19:05:16 -0700 Subject: [PATCH 2/7] Iterate whitespace for perf --- index.js | 53 +++++++++++++++++++++++++++++++++-------------------- 1 file changed, 33 insertions(+), 20 deletions(-) diff --git a/index.js b/index.js index 6fe633d..027b1c0 100644 --- a/index.js +++ b/index.js @@ -95,40 +95,39 @@ 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; - // no more cookie pairs - if (eqIdx === -1) { - break - } + while (index < str.length) { + var eqIdx = str.indexOf('=', index); + if (eqIdx === -1) break; - var endIdx = str.indexOf(';', index) + var endIdx = str.indexOf(';', index); + if (endIdx === -1) endIdx = str.length; - if (endIdx === -1) { - endIdx = str.length - } else if (endIdx < eqIdx) { - // backtrack on prior semicolon - index = str.lastIndexOf(';', eqIdx - 1) + 1 - continue + if (eqIdx > endIdx) { + index = endIdx + 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 +137,20 @@ function parse(str, options) { return obj; } +function startIndex(str, index, max) { + do { + if (str.charCodeAt(index) !== 0x20 /* */) break; + } while (++index < max); + return index; +} + +function endIndex(str, index, min) { + do { + if (str.charCodeAt(index - 1) !== 0x20 /* */) break; + } while (--index >= min); + return index; +} + /** * Serialize data into a cookie header. * From eca9f8f0f956e07b23e8961a767718376f186874 Mon Sep 17 00:00:00 2001 From: Blake Embrey Date: Tue, 1 Oct 2024 19:37:43 -0700 Subject: [PATCH 3/7] Use loop for = --- index.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/index.js b/index.js index 027b1c0..d8765ee 100644 --- a/index.js +++ b/index.js @@ -102,13 +102,13 @@ function parse(str, options) { var index = 0; while (index < str.length) { - var eqIdx = str.indexOf('=', index); - if (eqIdx === -1) break; - var endIdx = str.indexOf(';', index); if (endIdx === -1) endIdx = str.length; - if (eqIdx > endIdx) { + var eqIdx = index; + while (eqIdx < endIdx && str.charCodeAt(eqIdx) !== 0x3D /* = */) eqIdx++; + + if (eqIdx === endIdx) { index = endIdx + 1; continue; } From b487a1836a87899f3c6803ea5865f715751a983a Mon Sep 17 00:00:00 2001 From: Blake Embrey Date: Tue, 1 Oct 2024 20:39:25 -0700 Subject: [PATCH 4/7] Undo = loop --- index.js | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/index.js b/index.js index d8765ee..558a1cf 100644 --- a/index.js +++ b/index.js @@ -102,14 +102,19 @@ function parse(str, options) { var index = 0; while (index < str.length) { - var endIdx = str.indexOf(';', index); - if (endIdx === -1) endIdx = str.length; + var eqIdx = str.indexOf('=', index); + + // no more cookie pairs + if (eqIdx === -1) { + break; + } - var eqIdx = index; - while (eqIdx < endIdx && str.charCodeAt(eqIdx) !== 0x3D /* = */) eqIdx++; + var endIdx = str.indexOf(';', index); - if (eqIdx === endIdx) { - index = endIdx + 1; + if (endIdx === -1) { + endIdx = str.length; + } else if (eqIdx > endIdx) { + index = str.lastIndexOf(';', eqIdx - 1) + 1; continue; } From 96c30a8a133357f17129cef314719a5a329bfe00 Mon Sep 17 00:00:00 2001 From: Blake Embrey Date: Tue, 1 Oct 2024 22:34:21 -0700 Subject: [PATCH 5/7] Simplify code --- index.js | 7 ++++--- test/parse.js | 8 ++++++-- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/index.js b/index.js index 558a1cf..77e9fbe 100644 --- a/index.js +++ b/index.js @@ -114,6 +114,7 @@ function parse(str, options) { if (endIdx === -1) { endIdx = str.length; } else if (eqIdx > endIdx) { + // backtrack on prior semicolon index = str.lastIndexOf(';', eqIdx - 1) + 1; continue; } @@ -150,9 +151,9 @@ function startIndex(str, index, max) { } function endIndex(str, index, min) { - do { - if (str.charCodeAt(index - 1) !== 0x20 /* */) break; - } while (--index >= min); + while (index > min) { + if (str.charCodeAt(--index) !== 0x20 /* */) return index + 1; + } return index; } diff --git a/test/parse.js b/test/parse.js index cc69bc9..b4d64d2 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 () { @@ -36,12 +36,16 @@ describe('cookie.parse(str)', function () { 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(' foo = " a b c " '), { foo: ' a b c ' }) + assert.deepEqual(cookie.parse(' = bar '), { '': 'bar' }) + assert.deepEqual(cookie.parse(' foo = '), { foo: '' }) + assert.deepEqual(cookie.parse(' = '), { '': '' }) }) it('should return original value on escape error', function () { From a1ceb810f21aaf813a186061bcd30d88159a8832 Mon Sep 17 00:00:00 2001 From: Blake Embrey Date: Wed, 2 Oct 2024 14:31:34 -0700 Subject: [PATCH 6/7] Add tab char from BWS def --- index.js | 10 ++++++---- test/parse.js | 1 + 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/index.js b/index.js index 77e9fbe..4ea6b80 100644 --- a/index.js +++ b/index.js @@ -145,16 +145,18 @@ function parse(str, options) { function startIndex(str, index, max) { do { - if (str.charCodeAt(index) !== 0x20 /* */) break; + var code = str.charCodeAt(index); + if (code !== 0x20 /* */ && code !== 0x09 /* \t */) return index; } while (++index < max); - return index; + return max; } function endIndex(str, index, min) { while (index > min) { - if (str.charCodeAt(--index) !== 0x20 /* */) return index + 1; + var code = str.charCodeAt(--index); + if (code !== 0x20 /* */ && code !== 0x09 /* \t */) return index + 1; } - return index; + return min; } /** diff --git a/test/parse.js b/test/parse.js index b4d64d2..c11b4fc 100644 --- a/test/parse.js +++ b/test/parse.js @@ -46,6 +46,7 @@ describe('cookie.parse(str)', function () { 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 () { From 5f4d8c7d3011cf5f6980c0ead27803f6af5a70e9 Mon Sep 17 00:00:00 2001 From: Blake Embrey Date: Wed, 2 Oct 2024 14:51:01 -0700 Subject: [PATCH 7/7] Cache str.length --- index.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/index.js b/index.js index 4ea6b80..0eeccaf 100644 --- a/index.js +++ b/index.js @@ -100,19 +100,23 @@ function parse(str, options) { var dec = opt.decode || decode; var index = 0; + var eqIdx = 0; + var endIdx = 0; + var len = str.length; + var max = len - 2; - while (index < str.length) { - var eqIdx = str.indexOf('=', index); + while (index < max) { + eqIdx = str.indexOf('=', index); // no more cookie pairs if (eqIdx === -1) { break; } - var endIdx = str.indexOf(';', index); + endIdx = str.indexOf(';', index); if (endIdx === -1) { - endIdx = str.length; + endIdx = len; } else if (eqIdx > endIdx) { // backtrack on prior semicolon index = str.lastIndexOf(';', eqIdx - 1) + 1;