Skip to content
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/red-bikes-beg.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@sveltejs/kit': minor
---

feat: add new method to create cookie from a string
11 changes: 11 additions & 0 deletions packages/kit/src/exports/public.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,17 @@ 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
* @since 2.21.0
*/
setFromString: (cookie: string) => void;

/**
* Deletes a cookie by setting its value to an empty string and setting the expiry date in the past.
*
Expand Down
39 changes: 39 additions & 0 deletions packages/kit/src/runtime/server/cookie.js
Original file line number Diff line number Diff line change
@@ -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]/;
Expand Down Expand Up @@ -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
Expand Down
40 changes: 40 additions & 0 deletions packages/kit/src/runtime/server/cookie.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.'
);
});
10 changes: 10 additions & 0 deletions packages/kit/types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
Expand Down
Loading