Skip to content
Open
Show file tree
Hide file tree
Changes from all 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/fix-set-cookie-304.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@sveltejs/kit': patch
---

fix: preserve multiple `Set-Cookie` headers on 304 responses
17 changes: 8 additions & 9 deletions packages/kit/src/runtime/server/respond.js
Original file line number Diff line number Diff line change
Expand Up @@ -511,19 +511,18 @@ export async function internal_respond(request, options, manifest, state) {
if (if_none_match_value === etag) {
const headers = new Headers({ etag });

// https://datatracker.ietf.org/doc/html/rfc7232#section-4.1 + set-cookie
for (const key of [
'cache-control',
'content-location',
'date',
'expires',
'vary',
'set-cookie'
]) {
// https://datatracker.ietf.org/doc/html/rfc7232#section-4.1
for (const key of ['cache-control', 'content-location', 'date', 'expires', 'vary']) {
const value = response.headers.get(key);
if (value) headers.set(key, value);
}

// `Headers.get('set-cookie')` collapses multiple values into a single
// comma-joined string that browsers cannot parse correctly
for (const cookie of response.headers.getSetCookie()) {
headers.append('set-cookie', cookie);
}

return new Response(undefined, {
status: 304,
headers
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/** @type {import('./$types').PageServerLoad} */
export function load({ cookies }) {
cookies.set('one', '1', { path: '', httpOnly: false });
cookies.set('two', '2', { path: '', httpOnly: false });
cookies.set('three', '3', { path: '', httpOnly: false });
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<script>
import { onMount } from 'svelte';

/** @type {string} */
let cookies;

onMount(() => {
cookies = document.cookie
.split('; ')
.filter((c) => c.startsWith('one=') || c.startsWith('two=') || c.startsWith('three='))
.sort()
.join('; ');
});
</script>

<button
on:click={() => {
for (const name of ['one', 'two', 'three']) {
document.cookie = `${name}=;expires=Thu, 01 Jan 1970 00:00:01 GMT;`;
}
location.reload();
}}
>
Delete cookies and reload the page
</button>

<p>{cookies}</p>
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
test.describe.configure({ mode: 'parallel' });

test.describe('a11y', () => {
test('resets focus', async ({ page, clicknav, browserName }) => {

Check warning on line 12 in packages/kit/test/apps/basics/test/cross-platform/client.test.js

View workflow job for this annotation

GitHub Actions / test-kit-cross-browser (24, windows-latest, chromium, dev)

flaky test: resets focus

retries: 2
const tab = browserName === 'webkit' ? 'Alt+Tab' : 'Tab';

await page.goto('/accessibility/a');
Expand All @@ -32,7 +32,7 @@
expect(await page.evaluate(() => document.documentElement.getAttribute('tabindex'))).toBe(null);
});

test('applies autofocus after a navigation', async ({ page, clicknav }) => {

Check warning on line 35 in packages/kit/test/apps/basics/test/cross-platform/client.test.js

View workflow job for this annotation

GitHub Actions / test-kit-cross-browser (24, windows-latest, chromium, dev)

flaky test: applies autofocus after a navigation

retries: 2
await page.goto('/accessibility/autofocus/a');

await clicknav('[href="/accessibility/autofocus/b"]', {
Expand All @@ -42,7 +42,7 @@
expect(await page.evaluate(() => (document.activeElement || {}).nodeName)).toBe('INPUT');
});

test('sets focus for valid hash but invalid selector', async ({ page }) => {

Check warning on line 45 in packages/kit/test/apps/basics/test/cross-platform/client.test.js

View workflow job for this annotation

GitHub Actions / test-kit-cross-browser (24, windows-latest, chromium, build)

flaky test: sets focus for valid hash but invalid selector

retries: 2
await page.goto('/reset-focus#an:invalid+selector');
await expect(page.locator('button')).toBeFocused();
});
Expand Down Expand Up @@ -203,7 +203,7 @@
expect(await page.innerHTML('pre')).toBe('0 false undefined');
});

test('beforeNavigate is not triggered on click or popstate for hash links', async ({ page }) => {

Check warning on line 206 in packages/kit/test/apps/basics/test/cross-platform/client.test.js

View workflow job for this annotation

GitHub Actions / test-kit (20, ubuntu-latest, chromium, current)

flaky test: beforeNavigate is not triggered on click or popstate for hash links

retries: 2
await page.goto('/navigation-lifecycle/before-navigate/hash-links');

await page.click('a[href="#x"]');
Expand Down Expand Up @@ -739,7 +739,7 @@
);
});

test('client-side error from load()', async ({ page }) => {

Check warning on line 742 in packages/kit/test/apps/basics/test/cross-platform/client.test.js

View workflow job for this annotation

GitHub Actions / test-kit-cross-browser (24, windows-latest, chromium, build)

flaky test: client-side error from load()

retries: 2
await page.goto('/errors/load-error-client');

expect(await page.textContent('footer')).toBe('Custom layout');
Expand Down Expand Up @@ -1111,7 +1111,7 @@
expect(tabs.length > 1);
});

test('responds to <button formtarget="_blank" submission with new tab', async ({ page }) => {

Check warning on line 1114 in packages/kit/test/apps/basics/test/cross-platform/client.test.js

View workflow job for this annotation

GitHub Actions / test-kit-cross-browser (24, macOS-latest, webkit, dev)

flaky test: responds to <button formtarget="_blank" submission with new tab

retries: 2

Check warning on line 1114 in packages/kit/test/apps/basics/test/cross-platform/client.test.js

View workflow job for this annotation

GitHub Actions / test-kit-cross-browser (24, macOS-latest, webkit, build)

flaky test: responds to <button formtarget="_blank" submission with new tab

retries: 2
await page.goto('/routing/form-target-blank');

let tabs = page.context().pages();
Expand Down Expand Up @@ -1181,13 +1181,20 @@
});

test.describe('cookies', () => {
test('etag forwards cookies', async ({ page }) => {

Check warning on line 1184 in packages/kit/test/apps/basics/test/cross-platform/client.test.js

View workflow job for this annotation

GitHub Actions / test-kit-cross-browser (24, macOS-latest, webkit, dev)

flaky test: etag forwards cookies

retries: 2
await page.goto('/cookies/forwarded-in-etag');
await expect(page.locator('p')).toHaveText('foo=bar');
await page.locator('button').click();
await expect(page.locator('p')).toHaveText('foo=bar');
});

test('etag forwards multiple cookies', async ({ page }) => {
await page.goto('/cookies/forwarded-in-etag-multiple');
await expect(page.locator('p')).toHaveText('one=1; three=3; two=2');
await page.locator('button').click();
await expect(page.locator('p')).toHaveText('one=1; three=3; two=2');
});

test("fetch during SSR doesn't un- and re-escape cookies", async ({ page }) => {
await page.goto('/cookies/collect-without-re-escaping');
await expect(page.locator('p')).toHaveText('cookie-special-characters="foo"');
Expand Down
Loading