From fbe076b70457f8fbd9f214938ab0031bd803058c Mon Sep 17 00:00:00 2001 From: Frederik Prijck Date: Mon, 31 Mar 2025 13:50:15 +0200 Subject: [PATCH 1/2] fix: Ensure to delete cookies when switching from single to chunks and vica versa --- src/server/chunked-cookies.test.ts | 43 ++++++++++++++++++++++++++++++ src/server/cookies.ts | 11 ++++++++ 2 files changed, 54 insertions(+) diff --git a/src/server/chunked-cookies.test.ts b/src/server/chunked-cookies.test.ts index fe831701..3952cab8 100644 --- a/src/server/chunked-cookies.test.ts +++ b/src/server/chunked-cookies.test.ts @@ -166,6 +166,49 @@ describe("Chunked Cookie Utils", () => { ); }); + it("should clear existing chunked cookies when setting a single cookie", () => { + const name = "testCookie"; + const value = "small value"; + const options = { path: "/" } as CookieOptions; + + const chunk0 = "chunk0 value"; + const chunk1 = "chunk1 value"; + const chunk2 = "chunk2 value"; + + cookieStore.set(`${name}__1`, chunk1); + cookieStore.set(`${name}__0`, chunk0); + cookieStore.set(`${name}__2`, chunk2); + + setChunkedCookie(name, value, options, reqCookies, resCookies); + + expect(resCookies.set).toHaveBeenCalledTimes(1); + expect(resCookies.set).toHaveBeenCalledWith(name, value, options); + expect(reqCookies.set).toHaveBeenCalledTimes(1); + expect(reqCookies.set).toHaveBeenCalledWith(name, value); + expect(reqCookies.delete).toHaveBeenCalledTimes(3); + expect(reqCookies.delete).toHaveBeenCalledWith(`${name}__0`); + expect(reqCookies.delete).toHaveBeenCalledWith(`${name}__1`); + expect(reqCookies.delete).toHaveBeenCalledWith(`${name}__2`); + }); + + it("should clear existing single cookies when setting a chunked cookie", () => { + const name = "testCookie"; + const value = "small value"; + + cookieStore.set(`${name}`, value); + + // Create a large string (8000 bytes) + const largeValue = "a".repeat(8000); + const options = { path: "/" } as CookieOptions; + + setChunkedCookie(name, largeValue, options, reqCookies, resCookies); + + expect(reqCookies.delete).toHaveBeenCalledTimes(1); + expect(reqCookies.delete).toHaveBeenCalledWith(`${name}`); + expect(resCookies.set).toHaveBeenCalledTimes(3); + expect(reqCookies.set).toHaveBeenCalledTimes(3); + }); + it("should clean up unused chunks when cookie shrinks", () => { const name = "testCookie"; const options = { path: "/" } as CookieOptions; diff --git a/src/server/cookies.ts b/src/server/cookies.ts index bee82106..c0f317e1 100644 --- a/src/server/cookies.ts +++ b/src/server/cookies.ts @@ -196,6 +196,13 @@ export function setChunkedCookie( resCookies.set(name, value, options); // to enable read-after-write in the same request for middleware reqCookies.set(name, value); + + // When we are writing a non-chunked cookie, we should remove the chunked cookies + getAllChunkedCookies(reqCookies, name).forEach(cookieChunk => { + resCookies.delete(cookieChunk.name); + reqCookies.delete(cookieChunk.name); + }); + return; } @@ -226,6 +233,10 @@ export function setChunkedCookie( reqCookies.delete(chunkName); } } + + // When we have written chunked cookies, we should remove the non-chunked cookie + resCookies.delete(name); + reqCookies.delete(name); } /** From a717b5c2dcea79fcef0cec89ccc91c487e718394 Mon Sep 17 00:00:00 2001 From: Frederik Prijck Date: Tue, 1 Apr 2025 11:57:29 +0200 Subject: [PATCH 2/2] fix test --- src/server/chunked-cookies.test.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/server/chunked-cookies.test.ts b/src/server/chunked-cookies.test.ts index 09fa9465..aba497bf 100644 --- a/src/server/chunked-cookies.test.ts +++ b/src/server/chunked-cookies.test.ts @@ -228,9 +228,13 @@ describe("Chunked Cookie Utils", () => { const largeValue = "a".repeat(8000); setChunkedCookie(name, largeValue, options, reqCookies, resCookies); - expect(reqCookies.delete).toHaveBeenCalledTimes(2); + // It is called 3 times. + // 2 times for the chunks + // 1 time for the non chunked cookie + expect(reqCookies.delete).toHaveBeenCalledTimes(3); expect(reqCookies.delete).toHaveBeenCalledWith(`${name}__3`); expect(reqCookies.delete).toHaveBeenCalledWith(`${name}__4`); + expect(reqCookies.delete).toHaveBeenCalledWith(name); }); describe("getChunkedCookie", () => {