diff --git a/packages/cookies/src/serialize.ts b/packages/cookies/src/serialize.ts index b9a3e1e1..8eeb624b 100644 --- a/packages/cookies/src/serialize.ts +++ b/packages/cookies/src/serialize.ts @@ -1,5 +1,13 @@ import type { RequestCookie, ResponseCookie } from './types' +function maybeDecodeURIComponent(s: string) { + try { + return decodeURIComponent(s) + } catch { + return s + } +} + export function stringifyCookie(c: ResponseCookie | RequestCookie): string { const attrs = [ 'path' in c && c.path && `Path=${c.path}`, @@ -19,7 +27,9 @@ export function stringifyCookie(c: ResponseCookie | RequestCookie): string { ].filter(Boolean) const stringified = `${c.name}=${encodeURIComponent(c.value ?? '')}` - return attrs.length === 0 ? stringified : `${stringified}; ${attrs.join('; ')}` + return attrs.length === 0 + ? stringified + : `${stringified}; ${attrs.join('; ')}` } /** Parse a `Cookie` header value */ @@ -72,7 +82,9 @@ export function parseSetCookie(setCookie: string): undefined | ResponseCookie { ) const cookie: ResponseCookie = { name, - value: decodeURIComponent(value), + // parseCookie already decoded the value, so if the value contains special chars + // decoding it again will cause problems + value: maybeDecodeURIComponent(value), domain, ...(expires && { expires: new Date(expires) }), ...(httponly && { httpOnly: true }), diff --git a/packages/cookies/test/response-cookies.test.ts b/packages/cookies/test/response-cookies.test.ts index 8c341bdd..f5547dad 100644 --- a/packages/cookies/test/response-cookies.test.ts +++ b/packages/cookies/test/response-cookies.test.ts @@ -262,3 +262,11 @@ test('splitting multiple set-cookie', () => { expect(cookies2.get('foo')?.value).toBe(undefined) expect(cookies2.get('fooz')?.value).toBe('barz') }) + +test('cookie with special chars', () => { + const headers = new Headers() + const specialChars = 'bar 50%!@#$%^&*()_+' + headers.set('set-cookie', `foo=${encodeURIComponent(specialChars)}`) + const cookies = new ResponseCookies(headers) + expect(cookies.getAll()).toEqual([{ name: 'foo', value: specialChars }]) +})