From b6bc8dfac40b0b362bd7bf9670c3cbe625d95f07 Mon Sep 17 00:00:00 2001 From: Dejan Zdravkovic Date: Fri, 4 Apr 2025 18:14:51 +0200 Subject: [PATCH 01/14] add new method cookies.setFromString to create a cookie from a string --- packages/kit/src/exports/public.d.ts | 10 +++++ packages/kit/src/runtime/server/cookie.js | 39 ++++++++++++++++++ .../kit/src/runtime/server/cookie.spec.js | 40 +++++++++++++++++++ packages/kit/types/index.d.ts | 10 +++++ 4 files changed, 99 insertions(+) diff --git a/packages/kit/src/exports/public.d.ts b/packages/kit/src/exports/public.d.ts index 732df205d0ae..acec456e57a6 100644 --- a/packages/kit/src/exports/public.d.ts +++ b/packages/kit/src/exports/public.d.ts @@ -239,6 +239,16 @@ export interface Cookies { opts: import('cookie').CookieSerializeOptions & { path: string } ) => void; + /** + * Sets a cookie from a string. This will add a `set-cookie` header to the response, but also make the cookie available via `cookies.get` or `cookies.getAll` during the current request. + * + * No default values. It will set only properties you specified in a cookie. + * + * If you do not specify name, value and path, it will throw an error. + * @param cookie the cookie represented as string + */ + setFromString: (cookie: string) => void; + /** * Deletes a cookie by setting its value to an empty string and setting the expiry date in the past. * diff --git a/packages/kit/src/runtime/server/cookie.js b/packages/kit/src/runtime/server/cookie.js index 2e683543a534..e35dbb46aecb 100644 --- a/packages/kit/src/runtime/server/cookie.js +++ b/packages/kit/src/runtime/server/cookie.js @@ -1,6 +1,7 @@ import { parse, serialize } from 'cookie'; import { normalize_path, resolve } from '../../utils/url.js'; import { add_data_suffix } from '../pathname.js'; +import * as set_cookie_parser from 'set-cookie-parser'; // eslint-disable-next-line no-control-regex -- control characters are invalid in cookie names const INVALID_COOKIE_CHARACTER_REGEX = /[\x00-\x1F\x7F()<>@,;:"/[\]?={} \t]/; @@ -129,6 +130,44 @@ export function get_cookies(request, url) { set_internal(name, value, { ...defaults, ...options }); }, + /** + * @param {string} cookie + */ + setFromString(cookie) { + if (cookie === '') { + throw new Error('Cannot pass empty string'); + } + + const parsed = set_cookie_parser.parseString(cookie); + const { name, value, path, sameSite, secure, httpOnly, ...opts } = parsed; + + if (name === undefined || value === undefined || path === undefined) { + throw new Error('Name, Value and Path are mandatory for cookie to be created.'); + } + + /** + * @type {true|false|"lax"|"strict"|"none"|undefined} + */ + const normalized_same_site = (() => { + if (sameSite === undefined || typeof sameSite === 'boolean') { + return sameSite; + } + const lower = sameSite.toLowerCase(); + if (lower === 'lax' || lower === 'strict' || lower === 'none') { + return /** @type {"lax"|"strict"|"none"} */ (lower); + } + return undefined; + })(); + + this.set(name, value, { + ...opts, + path, + sameSite: normalized_same_site, + secure: secure ?? false, + httpOnly: httpOnly ?? false + }); + }, + /** * @param {string} name * @param {import('./page/types.js').Cookie['options']} options diff --git a/packages/kit/src/runtime/server/cookie.spec.js b/packages/kit/src/runtime/server/cookie.spec.js index e8f2cb08e623..a3e496673b31 100644 --- a/packages/kit/src/runtime/server/cookie.spec.js +++ b/packages/kit/src/runtime/server/cookie.spec.js @@ -213,3 +213,43 @@ test("set_internal isn't affected by defaults", () => { expect(cookies.get('test')).toEqual('foo'); expect(new_cookies['test']?.options).toEqual(options); }); + +test('no default values when setFromString is called', () => { + const { cookies, new_cookies } = cookies_setup(); + const cookie_string = 'a=b; Path=/;'; + cookies.setFromString(cookie_string); + const opts = new_cookies['a']?.options; + assert.equal(opts?.path, '/'); + assert.equal(opts?.secure, false); + assert.equal(opts?.httpOnly, false); + assert.equal(opts?.sameSite, undefined); +}); + +test('set all parameters when setFromString is called', () => { + const { cookies, new_cookies } = cookies_setup(); + const cookie_string = + 'a=b; Path=/; Max-Age=3600; Expires=Thu, 03 Apr 2025 00:41:07 GMT; Secure; HttpOnly; SameSite=Strict; domain=example.com'; + cookies.setFromString(cookie_string); + const opts = new_cookies['a']?.options; + assert.equal(opts?.path, '/'); + assert.equal(opts?.secure, true); + assert.equal(opts?.httpOnly, true); + assert.equal(opts?.sameSite, 'strict'); + assert.equal(opts?.domain, 'example.com'); + assert.equal(opts?.maxAge, 3600); + assert.isNotNull(opts.expires); +}); + +test('throw error when setFromString is called with empty string', () => { + const { cookies } = cookies_setup(); + assert.throws(() => cookies.setFromString(''), 'Cannot pass empty string'); +}); + +test('throw error when setFromString is called without name, value and path', () => { + const { cookies } = cookies_setup(); + const cookie_string = 'Max-Age=3600; Expires=Thu, 03 Apr 2025 00:41:07 GMT; Secure; HttpOnly;'; + assert.throws( + () => cookies.setFromString(cookie_string), + 'Name, Value and Path are mandatory for cookie to be created.' + ); +}); diff --git a/packages/kit/types/index.d.ts b/packages/kit/types/index.d.ts index 3ac640b17196..faab0e950983 100644 --- a/packages/kit/types/index.d.ts +++ b/packages/kit/types/index.d.ts @@ -221,6 +221,16 @@ declare module '@sveltejs/kit' { opts: import('cookie').CookieSerializeOptions & { path: string } ) => void; + /** + * Sets a cookie from a string. This will add a `set-cookie` header to the response, but also make the cookie available via `cookies.get` or `cookies.getAll` during the current request. + * + * No default values. It will set only properties you specified in a cookie. + * + * If you do not specify name, value and path, it will throw an error. + * @param cookie the cookie represented as string + */ + setFromString: (cookie: string) => void; + /** * Deletes a cookie by setting its value to an empty string and setting the expiry date in the past. * From 568c4a30da750eb88bc1fed3d99514ad2c76f844 Mon Sep 17 00:00:00 2001 From: Dejan Zdravkovic Date: Fri, 4 Apr 2025 18:17:34 +0200 Subject: [PATCH 02/14] changelog --- .changeset/red-bikes-beg.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/red-bikes-beg.md diff --git a/.changeset/red-bikes-beg.md b/.changeset/red-bikes-beg.md new file mode 100644 index 000000000000..43618d9ffc57 --- /dev/null +++ b/.changeset/red-bikes-beg.md @@ -0,0 +1,5 @@ +--- +'@sveltejs/kit': major +--- + +feat: add new method to create cookie from a string From 1a2bc359d9efffd90e3c6d39bb7a9bcaa4a4e8a7 Mon Sep 17 00:00:00 2001 From: Tee Ming Date: Thu, 10 Apr 2025 10:49:27 +0800 Subject: [PATCH 03/14] Update .changeset/red-bikes-beg.md --- .changeset/red-bikes-beg.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/red-bikes-beg.md b/.changeset/red-bikes-beg.md index 43618d9ffc57..d010d2a45180 100644 --- a/.changeset/red-bikes-beg.md +++ b/.changeset/red-bikes-beg.md @@ -1,5 +1,5 @@ --- -'@sveltejs/kit': major +'@sveltejs/kit': minor --- feat: add new method to create cookie from a string From 854e1f07a6f69bf96eaa29078b27064fa7b1a315 Mon Sep 17 00:00:00 2001 From: Tee Ming Date: Thu, 10 Apr 2025 10:54:23 +0800 Subject: [PATCH 04/14] Update packages/kit/src/exports/public.d.ts --- packages/kit/src/exports/public.d.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/kit/src/exports/public.d.ts b/packages/kit/src/exports/public.d.ts index acec456e57a6..7b43ef9c0dae 100644 --- a/packages/kit/src/exports/public.d.ts +++ b/packages/kit/src/exports/public.d.ts @@ -246,6 +246,7 @@ export interface Cookies { * * If you do not specify name, value and path, it will throw an error. * @param cookie the cookie represented as string + * @since 2.21.0 */ setFromString: (cookie: string) => void; From 9f37dc0a426247b3b5b2d64f35682313bce24171 Mon Sep 17 00:00:00 2001 From: Tee Ming Date: Thu, 10 Apr 2025 10:56:48 +0800 Subject: [PATCH 05/14] Update packages/kit/types/index.d.ts --- packages/kit/types/index.d.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/kit/types/index.d.ts b/packages/kit/types/index.d.ts index faab0e950983..8b68567523ee 100644 --- a/packages/kit/types/index.d.ts +++ b/packages/kit/types/index.d.ts @@ -228,6 +228,7 @@ declare module '@sveltejs/kit' { * * If you do not specify name, value and path, it will throw an error. * @param cookie the cookie represented as string + * @since 2.21.0 */ setFromString: (cookie: string) => void; From 0d3f8fdd2cbd2c73afde9dacbe04c1ce8beb0bb7 Mon Sep 17 00:00:00 2001 From: Tee Ming Date: Thu, 10 Apr 2025 10:57:59 +0800 Subject: [PATCH 06/14] Update .changeset/red-bikes-beg.md --- .changeset/red-bikes-beg.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/red-bikes-beg.md b/.changeset/red-bikes-beg.md index d010d2a45180..bfee04bdb3ae 100644 --- a/.changeset/red-bikes-beg.md +++ b/.changeset/red-bikes-beg.md @@ -2,4 +2,4 @@ '@sveltejs/kit': minor --- -feat: add new method to create cookie from a string +feat: add `cookie.setFromString()` to set a cookie from a string From 392285c650f61b6a5bfc294795ee670e8955d320 Mon Sep 17 00:00:00 2001 From: Dejan Zdravkovic Date: Thu, 17 Apr 2025 00:37:24 +0200 Subject: [PATCH 07/14] Update packages/kit/src/exports/public.d.ts Co-authored-by: Ben McCann <322311+benmccann@users.noreply.github.com> --- packages/kit/src/exports/public.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/kit/src/exports/public.d.ts b/packages/kit/src/exports/public.d.ts index 7b43ef9c0dae..f6a1c686d1e5 100644 --- a/packages/kit/src/exports/public.d.ts +++ b/packages/kit/src/exports/public.d.ts @@ -240,7 +240,7 @@ export interface Cookies { ) => void; /** - * Sets a cookie from a string. This will add a `set-cookie` header to the response, but also make the cookie available via `cookies.get` or `cookies.getAll` during the current request. + * Sets a cookie from a string representing the value of the `set-cookie` header. This will add the `set-cookie` header to the response, and also make the cookie available via `cookies.get` or `cookies.getAll` during the current request. * * No default values. It will set only properties you specified in a cookie. * From 472126500c198d6964c9be2ff0c1eaf91146eb51 Mon Sep 17 00:00:00 2001 From: Dejan Zdravkovic Date: Thu, 17 Apr 2025 00:37:48 +0200 Subject: [PATCH 08/14] Update packages/kit/src/exports/public.d.ts Co-authored-by: Ben McCann <322311+benmccann@users.noreply.github.com> --- packages/kit/src/exports/public.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/kit/src/exports/public.d.ts b/packages/kit/src/exports/public.d.ts index f6a1c686d1e5..686ddda321da 100644 --- a/packages/kit/src/exports/public.d.ts +++ b/packages/kit/src/exports/public.d.ts @@ -245,7 +245,7 @@ export interface Cookies { * No default values. It will set only properties you specified in a cookie. * * If you do not specify name, value and path, it will throw an error. - * @param cookie the cookie represented as string + * @param cookie the serialized cookie * @since 2.21.0 */ setFromString: (cookie: string) => void; From aed775de6efb0b4bbc505e981b27ffa9fd4c3df3 Mon Sep 17 00:00:00 2001 From: Dejan Zdravkovic Date: Thu, 17 Apr 2025 00:37:56 +0200 Subject: [PATCH 09/14] Update packages/kit/src/runtime/server/cookie.js Co-authored-by: Ben McCann <322311+benmccann@users.noreply.github.com> --- packages/kit/src/runtime/server/cookie.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/kit/src/runtime/server/cookie.js b/packages/kit/src/runtime/server/cookie.js index e35dbb46aecb..5002f14410b7 100644 --- a/packages/kit/src/runtime/server/cookie.js +++ b/packages/kit/src/runtime/server/cookie.js @@ -142,7 +142,7 @@ export function get_cookies(request, url) { const { name, value, path, sameSite, secure, httpOnly, ...opts } = parsed; if (name === undefined || value === undefined || path === undefined) { - throw new Error('Name, Value and Path are mandatory for cookie to be created.'); + throw new Error('The name, value and path must be provided to create a cookie.'); } /** From a337ca921d05d6c797c42b9ade2cb4cb5c9cb4ba Mon Sep 17 00:00:00 2001 From: Dejan Zdravkovic Date: Thu, 17 Apr 2025 00:38:04 +0200 Subject: [PATCH 10/14] Update packages/kit/src/runtime/server/cookie.js Co-authored-by: Ben McCann <322311+benmccann@users.noreply.github.com> --- packages/kit/src/runtime/server/cookie.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/kit/src/runtime/server/cookie.js b/packages/kit/src/runtime/server/cookie.js index 5002f14410b7..0b1d3e89f5d5 100644 --- a/packages/kit/src/runtime/server/cookie.js +++ b/packages/kit/src/runtime/server/cookie.js @@ -146,7 +146,7 @@ export function get_cookies(request, url) { } /** - * @type {true|false|"lax"|"strict"|"none"|undefined} + * @type {true|false|'lax'|'strict'|'none'|undefined} */ const normalized_same_site = (() => { if (sameSite === undefined || typeof sameSite === 'boolean') { From 48bac4f6b3398a9e56832d9d5475efaee3285087 Mon Sep 17 00:00:00 2001 From: Dejan Zdravkovic Date: Thu, 17 Apr 2025 00:38:13 +0200 Subject: [PATCH 11/14] Update packages/kit/src/runtime/server/cookie.js Co-authored-by: Ben McCann <322311+benmccann@users.noreply.github.com> --- packages/kit/src/runtime/server/cookie.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/kit/src/runtime/server/cookie.js b/packages/kit/src/runtime/server/cookie.js index 0b1d3e89f5d5..7359aa17bced 100644 --- a/packages/kit/src/runtime/server/cookie.js +++ b/packages/kit/src/runtime/server/cookie.js @@ -154,7 +154,7 @@ export function get_cookies(request, url) { } const lower = sameSite.toLowerCase(); if (lower === 'lax' || lower === 'strict' || lower === 'none') { - return /** @type {"lax"|"strict"|"none"} */ (lower); + return /** @type {'lax'|'strict'|'none'} */ (lower); } return undefined; })(); From f282b702cdc21142f7a78c83c1fd10775d4a7900 Mon Sep 17 00:00:00 2001 From: Dejan Zdravkovic Date: Thu, 17 Apr 2025 00:38:20 +0200 Subject: [PATCH 12/14] Update packages/kit/src/exports/public.d.ts Co-authored-by: Ben McCann <322311+benmccann@users.noreply.github.com> --- packages/kit/src/exports/public.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/kit/src/exports/public.d.ts b/packages/kit/src/exports/public.d.ts index 686ddda321da..13635335c72d 100644 --- a/packages/kit/src/exports/public.d.ts +++ b/packages/kit/src/exports/public.d.ts @@ -248,7 +248,7 @@ export interface Cookies { * @param cookie the serialized cookie * @since 2.21.0 */ - setFromString: (cookie: string) => void; + setSerialized: (cookie: string) => void; /** * Deletes a cookie by setting its value to an empty string and setting the expiry date in the past. From fc7aba909e94ef2491ee2267673959f33c827169 Mon Sep 17 00:00:00 2001 From: Dejan Zdravkovic Date: Thu, 17 Apr 2025 00:44:41 +0200 Subject: [PATCH 13/14] rename method to setSerialized --- .changeset/red-bikes-beg.md | 2 +- packages/kit/src/runtime/server/cookie.js | 2 +- packages/kit/src/runtime/server/cookie.spec.js | 16 ++++++++-------- packages/kit/types/index.d.ts | 6 +++--- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/.changeset/red-bikes-beg.md b/.changeset/red-bikes-beg.md index bfee04bdb3ae..38694d88b46c 100644 --- a/.changeset/red-bikes-beg.md +++ b/.changeset/red-bikes-beg.md @@ -2,4 +2,4 @@ '@sveltejs/kit': minor --- -feat: add `cookie.setFromString()` to set a cookie from a string +feat: add `cookie.setSerialized()` to set a cookie from a string diff --git a/packages/kit/src/runtime/server/cookie.js b/packages/kit/src/runtime/server/cookie.js index 7359aa17bced..198b9612ca3b 100644 --- a/packages/kit/src/runtime/server/cookie.js +++ b/packages/kit/src/runtime/server/cookie.js @@ -133,7 +133,7 @@ export function get_cookies(request, url) { /** * @param {string} cookie */ - setFromString(cookie) { + setSerialized(cookie) { if (cookie === '') { throw new Error('Cannot pass empty string'); } diff --git a/packages/kit/src/runtime/server/cookie.spec.js b/packages/kit/src/runtime/server/cookie.spec.js index a3e496673b31..da76c4e0c632 100644 --- a/packages/kit/src/runtime/server/cookie.spec.js +++ b/packages/kit/src/runtime/server/cookie.spec.js @@ -214,10 +214,10 @@ test("set_internal isn't affected by defaults", () => { expect(new_cookies['test']?.options).toEqual(options); }); -test('no default values when setFromString is called', () => { +test('no default values when setSerialized is called', () => { const { cookies, new_cookies } = cookies_setup(); const cookie_string = 'a=b; Path=/;'; - cookies.setFromString(cookie_string); + cookies.setSerialized(cookie_string); const opts = new_cookies['a']?.options; assert.equal(opts?.path, '/'); assert.equal(opts?.secure, false); @@ -225,11 +225,11 @@ test('no default values when setFromString is called', () => { assert.equal(opts?.sameSite, undefined); }); -test('set all parameters when setFromString is called', () => { +test('set all parameters when setSerialized is called', () => { const { cookies, new_cookies } = cookies_setup(); const cookie_string = 'a=b; Path=/; Max-Age=3600; Expires=Thu, 03 Apr 2025 00:41:07 GMT; Secure; HttpOnly; SameSite=Strict; domain=example.com'; - cookies.setFromString(cookie_string); + cookies.setSerialized(cookie_string); const opts = new_cookies['a']?.options; assert.equal(opts?.path, '/'); assert.equal(opts?.secure, true); @@ -240,16 +240,16 @@ test('set all parameters when setFromString is called', () => { assert.isNotNull(opts.expires); }); -test('throw error when setFromString is called with empty string', () => { +test('throw error when setSerialized is called with empty string', () => { const { cookies } = cookies_setup(); - assert.throws(() => cookies.setFromString(''), 'Cannot pass empty string'); + assert.throws(() => cookies.setSerialized(''), 'Cannot pass empty string'); }); -test('throw error when setFromString is called without name, value and path', () => { +test('throw error when setSerialized is called without name, value and path', () => { const { cookies } = cookies_setup(); const cookie_string = 'Max-Age=3600; Expires=Thu, 03 Apr 2025 00:41:07 GMT; Secure; HttpOnly;'; assert.throws( - () => cookies.setFromString(cookie_string), + () => cookies.setSerialized(cookie_string), 'Name, Value and Path are mandatory for cookie to be created.' ); }); diff --git a/packages/kit/types/index.d.ts b/packages/kit/types/index.d.ts index 8b68567523ee..9de19d6c064e 100644 --- a/packages/kit/types/index.d.ts +++ b/packages/kit/types/index.d.ts @@ -222,15 +222,15 @@ declare module '@sveltejs/kit' { ) => void; /** - * Sets a cookie from a string. This will add a `set-cookie` header to the response, but also make the cookie available via `cookies.get` or `cookies.getAll` during the current request. + * Sets a cookie from a string representing the value of the `set-cookie` header. This will add the `set-cookie` header to the response, and also make the cookie available via `cookies.get` or `cookies.getAll` during the current request. * * No default values. It will set only properties you specified in a cookie. * * If you do not specify name, value and path, it will throw an error. - * @param cookie the cookie represented as string + * @param cookie the serialized cookie * @since 2.21.0 */ - setFromString: (cookie: string) => void; + setSerialized: (cookie: string) => void; /** * Deletes a cookie by setting its value to an empty string and setting the expiry date in the past. From 31de519ca7b197fd48c5296e1bca317ee0e7815f Mon Sep 17 00:00:00 2001 From: Dejan Zdravkovic Date: Thu, 17 Apr 2025 00:50:35 +0200 Subject: [PATCH 14/14] fix setSerialized test --- packages/kit/src/runtime/server/cookie.spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/kit/src/runtime/server/cookie.spec.js b/packages/kit/src/runtime/server/cookie.spec.js index da76c4e0c632..e8e376bc4efa 100644 --- a/packages/kit/src/runtime/server/cookie.spec.js +++ b/packages/kit/src/runtime/server/cookie.spec.js @@ -250,6 +250,6 @@ test('throw error when setSerialized is called without name, value and path', () const cookie_string = 'Max-Age=3600; Expires=Thu, 03 Apr 2025 00:41:07 GMT; Secure; HttpOnly;'; assert.throws( () => cookies.setSerialized(cookie_string), - 'Name, Value and Path are mandatory for cookie to be created.' + 'The name, value and path must be provided to create a cookie.' ); });