From 102ff7a664bfb1a409a88cb29cac9c1c08b0794c Mon Sep 17 00:00:00 2001 From: saripovdenis Date: Tue, 28 Apr 2026 18:23:42 +0800 Subject: [PATCH 1/5] fix: keep undefined decoded cookie values --- src/index.ts | 8 ++++++-- src/parse-cookie.spec.ts | 10 ++++++++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/index.ts b/src/index.ts index a95a2e9..36e8ad5 100644 --- a/src/index.ts +++ b/src/index.ts @@ -111,7 +111,8 @@ export function parseCookie(str: string, options?: ParseOptions): Cookies { // RFC 6265 sec 4.1.1, RFC 2616 2.2 defines a cookie name consists of one char minimum, plus '='. if (len < 2) return obj; - const dec = options?.decode || decode; + const customDecode = options?.decode; + const dec = customDecode || decode; let index = 0; do { @@ -129,7 +130,10 @@ export function parseCookie(str: string, options?: ParseOptions): Cookies { const key = valueSlice(str, index, eqIdx); // only assign once - if (obj[key] === undefined) { + if ( + obj[key] === undefined && + (customDecode === undefined || !(key in obj)) + ) { obj[key] = dec(valueSlice(str, eqIdx + 1, endIdx)); } diff --git a/src/parse-cookie.spec.ts b/src/parse-cookie.spec.ts index 2444909..6bbaf8c 100644 --- a/src/parse-cookie.spec.ts +++ b/src/parse-cookie.spec.ts @@ -110,5 +110,15 @@ describe("cookie.parseCookie", function () { }), ).toEqual({ foo: "bar" }); }); + + it("should not overwrite undefined decoded duplicates", function () { + expect( + cookie.parseCookie("foo=bar; foo=baz", { + decode: function () { + return undefined; + }, + }), + ).toEqual({ foo: undefined }); + }); }); }); From 73cd0be2086d3ae7566ea78f23fe9d195395c346 Mon Sep 17 00:00:00 2001 From: saripovdenis Date: Wed, 29 Apr 2026 00:32:37 +0800 Subject: [PATCH 2/5] test: strengthen undefined duplicate decode coverage --- src/parse-cookie.spec.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/parse-cookie.spec.ts b/src/parse-cookie.spec.ts index 6bbaf8c..7b94509 100644 --- a/src/parse-cookie.spec.ts +++ b/src/parse-cookie.spec.ts @@ -112,13 +112,17 @@ describe("cookie.parseCookie", function () { }); it("should not overwrite undefined decoded duplicates", function () { + let calls = 0; + expect( cookie.parseCookie("foo=bar; foo=baz", { decode: function () { - return undefined; + return calls++ === 0 ? undefined : "baz"; }, }), ).toEqual({ foo: undefined }); + + expect(calls).toBe(1); }); }); }); From fac6cc3a3a2ff7151156eda68bd2ee514f6b99ff Mon Sep 17 00:00:00 2001 From: saripovdenis Date: Sun, 3 May 2026 01:21:01 +0800 Subject: [PATCH 3/5] test: document undefined decode duplicate behavior --- README.md | 3 ++- src/index.ts | 9 +++------ src/parse-cookie.spec.ts | 11 ++++++----- 3 files changed, 11 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 09cb444..a761e89 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ const cookieObject = cookie.parseCookie("foo=bar; equation=E%3Dmc%5E2"); #### Options -- `decode` Specifies the function to decode a [cookie-value](https://datatracker.ietf.org/doc/html/rfc6265#section-4.1.1). Defaults to [`decodeURIComponent`](#encode-and-decode). +- `decode` Specifies the function to decode a [cookie-value](https://datatracker.ietf.org/doc/html/rfc6265#section-4.1.1). Defaults to [`decodeURIComponent`](#encode-and-decode). Returning `undefined` will skip the value so a later duplicate can be used. ### cookie.stringifyCookie(cookieObj, options) @@ -183,6 +183,7 @@ The default `encode` function is the global `encodeURIComponent`. The default `decode` function is the global `decodeURIComponent`, wrapped in a `try..catch`. If an error is thrown it will return the cookie's original value. If you provide your own encode/decode scheme you must ensure errors are appropriately handled. +Returning `undefined` from `decode` when parsing a `Cookie` header will skip the value so a later duplicate can be used. ## Example diff --git a/src/index.ts b/src/index.ts index 36e8ad5..5adf24d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -88,6 +88,7 @@ export interface ParseOptions { * The default function is the global `decodeURIComponent`, wrapped in a `try..catch`. If an error * is thrown it will return the cookie's original value. If you provide your own encode/decode * scheme you must ensure errors are appropriately handled. + * Returning `undefined` will skip the value so a later duplicate can be used. * * @default decode */ @@ -111,8 +112,7 @@ export function parseCookie(str: string, options?: ParseOptions): Cookies { // RFC 6265 sec 4.1.1, RFC 2616 2.2 defines a cookie name consists of one char minimum, plus '='. if (len < 2) return obj; - const customDecode = options?.decode; - const dec = customDecode || decode; + const dec = options?.decode || decode; let index = 0; do { @@ -130,10 +130,7 @@ export function parseCookie(str: string, options?: ParseOptions): Cookies { const key = valueSlice(str, index, eqIdx); // only assign once - if ( - obj[key] === undefined && - (customDecode === undefined || !(key in obj)) - ) { + if (obj[key] === undefined) { obj[key] = dec(valueSlice(str, eqIdx + 1, endIdx)); } diff --git a/src/parse-cookie.spec.ts b/src/parse-cookie.spec.ts index 7b94509..dc17fad 100644 --- a/src/parse-cookie.spec.ts +++ b/src/parse-cookie.spec.ts @@ -111,18 +111,19 @@ describe("cookie.parseCookie", function () { ).toEqual({ foo: "bar" }); }); - it("should not overwrite undefined decoded duplicates", function () { + it("should skip undefined decoded duplicates", function () { let calls = 0; expect( cookie.parseCookie("foo=bar; foo=baz", { - decode: function () { - return calls++ === 0 ? undefined : "baz"; + decode: function (value) { + calls++; + return value === "bar" ? undefined : value; }, }), - ).toEqual({ foo: undefined }); + ).toEqual({ foo: "baz" }); - expect(calls).toBe(1); + expect(calls).toBe(2); }); }); }); From 83d193bd3dd7d6c1a096ecbdfc5e41ac84bb61cf Mon Sep 17 00:00:00 2001 From: Blake Embrey Date: Thu, 7 May 2026 16:21:48 -0700 Subject: [PATCH 4/5] Update README to improve decode function description Clarify behavior of custom decode functions in README. --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index a761e89..9d735a1 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ const cookieObject = cookie.parseCookie("foo=bar; equation=E%3Dmc%5E2"); #### Options -- `decode` Specifies the function to decode a [cookie-value](https://datatracker.ietf.org/doc/html/rfc6265#section-4.1.1). Defaults to [`decodeURIComponent`](#encode-and-decode). Returning `undefined` will skip the value so a later duplicate can be used. +- `decode` Specifies the function to decode a [cookie-value](https://datatracker.ietf.org/doc/html/rfc6265#section-4.1.1). Defaults to [`decodeURIComponent`](#encode-and-decode). ### cookie.stringifyCookie(cookieObj, options) @@ -182,8 +182,8 @@ The default `encode` function is the global `encodeURIComponent`. The default `decode` function is the global `decodeURIComponent`, wrapped in a `try..catch`. If an error is thrown it will return the cookie's original value. If you provide your own encode/decode -scheme you must ensure errors are appropriately handled. -Returning `undefined` from `decode` when parsing a `Cookie` header will skip the value so a later duplicate can be used. +scheme you must ensure errors are appropriately handled. Custom decode functions can return `undefined`, +which will skip the cookie during `parse` and try again with a future cookie of the same name. ## Example From add4def8e32cc831591199e38d3aaf6dbfcfc898 Mon Sep 17 00:00:00 2001 From: Blake Embrey Date: Thu, 7 May 2026 16:22:35 -0700 Subject: [PATCH 5/5] Update comments for custom decode functions Clarify behavior of custom decode functions in comments. --- src/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/index.ts b/src/index.ts index 5adf24d..2e0022e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -87,8 +87,8 @@ export interface ParseOptions { * * The default function is the global `decodeURIComponent`, wrapped in a `try..catch`. If an error * is thrown it will return the cookie's original value. If you provide your own encode/decode - * scheme you must ensure errors are appropriately handled. - * Returning `undefined` will skip the value so a later duplicate can be used. + * scheme you must ensure errors are appropriately handled. Custom decode functions can return `undefined`, + * which will skip the cookie during `parse` and try again with a future cookie of the same name. * * @default decode */