Skip to content

Commit

Permalink
[fix] ensure serialized headers check is always applied (#7221)
Browse files Browse the repository at this point in the history
* [fix] ensure serialized headers check is always applied

Closes #7112

* fix test, add check only when client side rendering enabled
  • Loading branch information
dummdidumm committed Oct 19, 2022
1 parent 930a3a0 commit 779d575
Show file tree
Hide file tree
Showing 8 changed files with 56 additions and 21 deletions.
5 changes: 5 additions & 0 deletions .changeset/strong-experts-begin.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@sveltejs/kit': patch
---

[fix] ensure serialized headers check is always applied
5 changes: 4 additions & 1 deletion packages/kit/src/runtime/server/page/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,8 @@ export async function render_page(event, route, page, options, state, resolve_op
});
});

const csr = get_option(nodes, 'csr') ?? true;

/** @type {Array<Promise<Record<string, any> | null>>} */
const load_promises = nodes.map((node, i) => {
if (load_error) throw load_error;
Expand All @@ -178,7 +180,8 @@ export async function render_page(event, route, page, options, state, resolve_op
},
resolve_opts,
server_data_promise: server_promises[i],
state
state,
csr
});
} catch (e) {
load_error = /** @type {Error} */ (e);
Expand Down
40 changes: 22 additions & 18 deletions packages/kit/src/runtime/server/page/load_data.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ export async function load_server_data({ event, state, node, parent }) {
* resolve_opts: import('types').RequiredResolveOptions;
* server_data_promise: Promise<import('types').ServerDataNode | null>;
* state: import('types').SSRState;
* csr: boolean;
* }} opts
* @returns {Promise<Record<string, any> | null>}
*/
Expand All @@ -84,7 +85,8 @@ export async function load_data({
parent,
server_data_promise,
state,
resolve_opts
resolve_opts,
csr
}) {
const server_data_node = await server_data_promise;

Expand Down Expand Up @@ -129,23 +131,6 @@ export async function load_data({
response_body: body,
response: response
});

// ensure that excluded headers can't be read
const get = response.headers.get;
response.headers.get = (key) => {
const lower = key.toLowerCase();
const value = get.call(response.headers, lower);
if (value && !lower.startsWith('x-sveltekit-')) {
const included = resolve_opts.filterSerializedResponseHeaders(lower, value);
if (!included) {
throw new Error(
`Failed to get response header "${lower}" — it must be included by the \`filterSerializedResponseHeaders\` option: https://kit.svelte.dev/docs/hooks#handle`
);
}
}

return value;
};
}

if (dependency) {
Expand Down Expand Up @@ -186,6 +171,25 @@ export async function load_data({
}
});

if (csr) {
// ensure that excluded headers can't be read
const get = response.headers.get;
response.headers.get = (key) => {
const lower = key.toLowerCase();
const value = get.call(response.headers, lower);
if (value && !lower.startsWith('x-sveltekit-')) {
const included = resolve_opts.filterSerializedResponseHeaders(lower, value);
if (!included) {
throw new Error(
`Failed to get response header "${lower}" — it must be included by the \`filterSerializedResponseHeaders\` option: https://kit.svelte.dev/docs/hooks#handle (at ${event.routeId})`
);
}
}

return value;
};
}

return proxy;
},
setHeaders: event.setHeaders,
Expand Down
4 changes: 3 additions & 1 deletion packages/kit/src/runtime/server/page/respond_with_error.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export async function respond_with_error({ event, options, state, status, error,
const branch = [];
const default_layout = await options.manifest._.nodes[0](); // 0 is always the root layout
const ssr = get_option([default_layout], 'ssr') ?? true;
const csr = get_option([default_layout], 'csr') ?? true;

if (ssr) {
state.initiator = GENERIC_ERROR;
Expand All @@ -53,7 +54,8 @@ export async function respond_with_error({ event, options, state, status, error,
parent: async () => ({}),
resolve_opts,
server_data_promise,
state
state,
csr
});

branch.push(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/** @type {import('./$types').PageLoad} */
export async function load({ fetch }) {
let res = await fetch('./irrelevant', {
method: 'GET'
});

res.headers.get('content-type');
}
11 changes: 11 additions & 0 deletions packages/kit/test/apps/basics/test/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -851,6 +851,17 @@ test.describe('Load', () => {
}
});

test('errors when trying to access non-serialized request headers on the server', async ({
page,
read_errors
}) => {
await page.goto('/load/fetch-request-headers-invalid-access');

expect(read_errors(`/load/fetch-request-headers-invalid-access`).message).toContain(
'Failed to get response header "content-type" — it must be included by the `filterSerializedResponseHeaders` option'
);
});

test('exposes rawBody as a DataView to endpoints', async ({ page, clicknav }) => {
await page.goto('/load/raw-body');
await clicknav('[href="/load/raw-body/dataview"]');
Expand Down
4 changes: 3 additions & 1 deletion packages/kit/test/prerendering/basics/src/hooks.server.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,7 @@ export const handle = async ({ event, resolve }) => {
.replace('__PRERENDERING__', String(prerendering))
});
}
return await resolve(event);
return await resolve(event, {
filterSerializedResponseHeaders: (name) => name === 'content-type'
});
};

0 comments on commit 779d575

Please sign in to comment.