Skip to content

Commit 26d2b7f

Browse files
fix: Don't set defaults when parsing cookies from headers (#9908)
Closes #9901 Bypasses setting Kit defaults on cookies parsed from fetch response headers. To illustrate what the problem was: - Set a cookie in +server.ts: cookies.set('foo', 'bar', { httpOnly: false }) - fetch this endpoint from +page.server.ts - As part of fetch, parse the cookies from the headers - httpOnly isn't set (because false === unset) - Adding this cookie to the cookies causes the default httpOnly: true to be added The better solution here is not to apply defaults to cookies we parse from fetch response headers. If these cookies were set through cookies.set in a +server.ts endpoint, we've already set the defaults, and if they're not, we shouldn't touch them anyway.
1 parent bc70b4e commit 26d2b7f

File tree

5 files changed

+67
-31
lines changed

5 files changed

+67
-31
lines changed

Diff for: .changeset/olive-dogs-bathe.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@sveltejs/kit': patch
3+
---
4+
5+
fix: stop setting Kit cookie defaults on cookies parsed from headers

Diff for: packages/kit/src/runtime/server/cookie.js

+35-27
Original file line numberDiff line numberDiff line change
@@ -107,32 +107,7 @@ export function get_cookies(request, url, trailing_slash) {
107107
* @param {import('cookie').CookieSerializeOptions} opts
108108
*/
109109
set(name, value, opts = {}) {
110-
let path = opts.path ?? default_path;
111-
112-
new_cookies[name] = {
113-
name,
114-
value,
115-
options: {
116-
...defaults,
117-
...opts,
118-
path
119-
}
120-
};
121-
122-
if (__SVELTEKIT_DEV__) {
123-
const serialized = serialize(name, value, new_cookies[name].options);
124-
if (new TextEncoder().encode(serialized).byteLength > MAX_COOKIE_SIZE) {
125-
throw new Error(`Cookie "${name}" is too large, and will be discarded by the browser`);
126-
}
127-
128-
cookie_paths[name] ??= new Set();
129-
130-
if (!value) {
131-
cookie_paths[name].delete(path);
132-
} else {
133-
cookie_paths[name].add(path);
134-
}
135-
}
110+
set_internal(name, value, { ...defaults, ...opts });
136111
},
137112

138113
/**
@@ -193,7 +168,40 @@ export function get_cookies(request, url, trailing_slash) {
193168
.join('; ');
194169
}
195170

196-
return { cookies, new_cookies, get_cookie_header };
171+
/**
172+
* @param {string} name
173+
* @param {string} value
174+
* @param {import('cookie').CookieSerializeOptions} opts
175+
*/
176+
function set_internal(name, value, opts) {
177+
let path = opts.path ?? default_path;
178+
179+
new_cookies[name] = {
180+
name,
181+
value,
182+
options: {
183+
...opts,
184+
path
185+
}
186+
};
187+
188+
if (__SVELTEKIT_DEV__) {
189+
const serialized = serialize(name, value, new_cookies[name].options);
190+
if (new TextEncoder().encode(serialized).byteLength > MAX_COOKIE_SIZE) {
191+
throw new Error(`Cookie "${name}" is too large, and will be discarded by the browser`);
192+
}
193+
194+
cookie_paths[name] ??= new Set();
195+
196+
if (!value) {
197+
cookie_paths[name].delete(path);
198+
} else {
199+
cookie_paths[name].add(path);
200+
}
201+
}
202+
}
203+
204+
return { cookies, new_cookies, get_cookie_header, set_internal };
197205
}
198206

199207
/**

Diff for: packages/kit/src/runtime/server/cookie.spec.js

+15
Original file line numberDiff line numberDiff line change
@@ -195,4 +195,19 @@ test('get all cookies from header and set calls', () => {
195195
]);
196196
});
197197

198+
test("set_internal isn't affected by defaults", () => {
199+
const { cookies, new_cookies, set_internal } = cookies_setup({
200+
href: 'https://example.com/a/b/c'
201+
});
202+
const options = /** @type {const} */ ({
203+
secure: false,
204+
httpOnly: false,
205+
sameSite: 'none',
206+
path: '/a/b/c'
207+
});
208+
set_internal('test', 'foo', options);
209+
assert.equal(cookies.get('test'), 'foo');
210+
assert.equal(new_cookies['test']?.options, options);
211+
});
212+
198213
test.run();

Diff for: packages/kit/src/runtime/server/fetch.js

+3-2
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,11 @@ import * as paths from '__sveltekit/paths';
99
* manifest: import('types').SSRManifest;
1010
* state: import('types').SSRState;
1111
* get_cookie_header: (url: URL, header: string | null) => string;
12+
* set_internal: (name: string, value: string, opts: import('cookie').CookieSerializeOptions) => void;
1213
* }} opts
1314
* @returns {typeof fetch}
1415
*/
15-
export function create_fetch({ event, options, manifest, state, get_cookie_header }) {
16+
export function create_fetch({ event, options, manifest, state, get_cookie_header, set_internal }) {
1617
return async (info, init) => {
1718
const original_request = normalize_fetch_input(info, init, event.url);
1819

@@ -131,7 +132,7 @@ export function create_fetch({ event, options, manifest, state, get_cookie_heade
131132
const { name, value, ...options } = set_cookie_parser.parseString(str);
132133

133134
// options.sameSite is string, something more specific is required - type cast is safe
134-
event.cookies.set(
135+
set_internal(
135136
name,
136137
value,
137138
/** @type {import('cookie').CookieSerializeOptions} */ (options)

Diff for: packages/kit/src/runtime/server/respond.js

+9-2
Original file line numberDiff line numberDiff line change
@@ -244,15 +244,22 @@ export async function respond(request, options, manifest, state) {
244244
}
245245
}
246246

247-
const { cookies, new_cookies, get_cookie_header } = get_cookies(
247+
const { cookies, new_cookies, get_cookie_header, set_internal } = get_cookies(
248248
request,
249249
url,
250250
trailing_slash ?? 'never'
251251
);
252252

253253
cookies_to_add = new_cookies;
254254
event.cookies = cookies;
255-
event.fetch = create_fetch({ event, options, manifest, state, get_cookie_header });
255+
event.fetch = create_fetch({
256+
event,
257+
options,
258+
manifest,
259+
state,
260+
get_cookie_header,
261+
set_internal
262+
});
256263

257264
if (state.prerendering && !state.prerendering.fallback) disable_search(url);
258265

0 commit comments

Comments
 (0)