Skip to content

Commit 290a4ca

Browse files
authored
[chore] Make rawBody be the bytes (#2215)
* change type of rawBody to Uint8Array * update raw body test * update `parse_body` * remove debug log from taw body test * update `getRawBody` * update adapter-cloudflare-workers * update adapter-netlify * ensure `rawBody` is `Uint8Array` * add changeset * update types inside docs * make isContentType textual private * ensure `rawBody` of `Incoming` is bytes * remove unnecessary rawBody check * make `null` assignable to `RawBody` * use `Headers` type in `parse_body`
1 parent 9c8f074 commit 290a4ca

File tree

17 files changed

+58
-71
lines changed

17 files changed

+58
-71
lines changed

.changeset/short-beds-punch.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
'@sveltejs/adapter-cloudflare-workers': patch
3+
'@sveltejs/adapter-netlify': patch
4+
'@sveltejs/kit': patch
5+
---
6+
7+
Ensure the raw body is an Uint8Array before passing it to request handlers

documentation/docs/01-routing.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ type Request<Locals = Record<string, any>, Body = unknown> = {
6060
path: string;
6161
params: Record<string, string>;
6262
query: URLSearchParams;
63-
rawBody: string | Uint8Array;
63+
rawBody: Uint8Array;
6464
body: ParameterizedBody<Body>;
6565
locals: Locals; // populated by hooks handle
6666
};

documentation/docs/04-hooks.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ type Request<Locals = Record<string, any>> = {
2424
path: string;
2525
params: Record<string, string>;
2626
query: URLSearchParams;
27-
rawBody: string | Uint8Array;
27+
rawBody: Uint8Array;
2828
body: ParameterizedBody<Body>;
2929
locals: Locals; // populated by hooks handle
3030
};

packages/adapter-cloudflare-workers/files/entry.js

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
// TODO hardcoding the relative location makes this brittle
22
import { init, render } from '../output/server/app.js'; // eslint-disable-line import/no-unresolved
33
import { getAssetFromKV, NotFoundError } from '@cloudflare/kv-asset-handler'; // eslint-disable-line import/no-unresolved
4-
import { isContentTypeTextual } from '@sveltejs/kit/adapter-utils'; // eslint-disable-line import/no-unresolved
54

65
init();
76

@@ -34,7 +33,7 @@ async function handle(event) {
3433
host: request_url.host,
3534
path: request_url.pathname,
3635
query: request_url.searchParams,
37-
rawBody: request.body ? await read(request) : null,
36+
rawBody: await read(request),
3837
headers: Object.fromEntries(request.headers),
3938
method: request.method
4039
});
@@ -57,10 +56,5 @@ async function handle(event) {
5756

5857
/** @param {Request} request */
5958
async function read(request) {
60-
const type = request.headers.get('content-type') || '';
61-
if (isContentTypeTextual(type)) {
62-
return request.text();
63-
}
64-
6559
return new Uint8Array(await request.arrayBuffer());
6660
}

packages/adapter-netlify/files/entry.js

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
// TODO hardcoding the relative location makes this brittle
22
import { init, render } from '../output/server/app.js'; // eslint-disable-line import/no-unresolved
3-
import { isContentTypeTextual } from '@sveltejs/kit/adapter-utils'; // eslint-disable-line import/no-unresolved
43

54
init();
65

@@ -9,13 +8,8 @@ export async function handler(event) {
98

109
const query = new URLSearchParams(rawQuery);
1110

12-
const type = headers['content-type'];
13-
const rawBody =
14-
type && isContentTypeTextual(type)
15-
? isBase64Encoded
16-
? Buffer.from(body, 'base64').toString()
17-
: body
18-
: new TextEncoder('base64').encode(body);
11+
const encoding = isBase64Encoded ? 'base64' : headers['content-encoding'] || 'utf-8';
12+
const rawBody = typeof body === 'string' ? Buffer.from(body, encoding) : body;
1913

2014
const rendered = await render({
2115
method: httpMethod,

packages/kit/package.json

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -77,9 +77,6 @@
7777
"./install-fetch": {
7878
"import": "./dist/install-fetch.js"
7979
},
80-
"./adapter-utils": {
81-
"import": "./dist/adapter-utils.js"
82-
},
8380
"./types": "./types/index.d.ts"
8481
},
8582
"types": "types/index.d.ts",

packages/kit/rollup.config.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,7 @@ export default [
4949
ssr: 'src/runtime/server/index.js',
5050
node: 'src/core/node/index.js',
5151
hooks: 'src/runtime/hooks.js',
52-
'install-fetch': 'src/install-fetch.js',
53-
'adapter-utils': 'src/core/adapter-utils.js'
52+
'install-fetch': 'src/install-fetch.js'
5453
},
5554
output: {
5655
dir: 'dist',

packages/kit/src/core/adapt/prerender.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,7 @@ export async function prerender({ cwd, out, log, config, build_data, fallback, a
156156
method: 'GET',
157157
headers: {},
158158
path,
159-
rawBody: '',
159+
rawBody: null,
160160
query: new URLSearchParams()
161161
},
162162
{
@@ -289,7 +289,7 @@ export async function prerender({ cwd, out, log, config, build_data, fallback, a
289289
method: 'GET',
290290
headers: {},
291291
path: '[fallback]', // this doesn't matter, but it's easiest if it's a string
292-
rawBody: '',
292+
rawBody: null,
293293
query: new URLSearchParams()
294294
},
295295
{

packages/kit/src/core/adapter-utils.js

Lines changed: 0 additions & 18 deletions
This file was deleted.

packages/kit/src/core/node/index.js

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,13 @@
1-
import { isContentTypeTextual } from '../adapter-utils.js';
2-
31
/**
42
* @param {import('http').IncomingMessage} req
5-
* @returns {Promise<import('types/hooks').StrictBody>}
3+
* @returns {Promise<import('types/hooks').RawBody>}
64
*/
75
export function getRawBody(req) {
86
return new Promise((fulfil, reject) => {
97
const h = req.headers;
108

119
if (!h['content-type']) {
12-
return fulfil('');
10+
return fulfil(null);
1311
}
1412

1513
req.on('error', reject);
@@ -18,7 +16,7 @@ export function getRawBody(req) {
1816

1917
// https://github.com/jshttp/type-is/blob/c1f4388c71c8a01f79934e68f630ca4a15fffcd6/index.js#L81-L95
2018
if (isNaN(length) && h['transfer-encoding'] == null) {
21-
return fulfil('');
19+
return fulfil(null);
2220
}
2321

2422
let data = new Uint8Array(length || 0);
@@ -48,13 +46,6 @@ export function getRawBody(req) {
4846
}
4947

5048
req.on('end', () => {
51-
const [type] = (h['content-type'] || '').split(/;\s*/);
52-
53-
if (isContentTypeTextual(type)) {
54-
const encoding = h['content-encoding'] || 'utf-8';
55-
return fulfil(new TextDecoder(encoding).decode(data));
56-
}
57-
5849
fulfil(data);
5950
});
6051
});

packages/kit/src/runtime/server/endpoint.js

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { isContentTypeTextual } from '../../core/adapter-utils.js';
21
import { lowercase_keys } from './utils.js';
32

43
/** @param {string} body */
@@ -15,6 +14,23 @@ function is_string(s) {
1514
return typeof s === 'string' || s instanceof String;
1615
}
1716

17+
/**
18+
* Decides how the body should be parsed based on its mime type. Should match what's in parse_body
19+
*
20+
* @param {string | undefined | null} content_type The `content-type` header of a request/response.
21+
* @returns {boolean}
22+
*/
23+
function is_content_type_textual(content_type) {
24+
if (!content_type) return true; // defaults to json
25+
const [type] = content_type.split(';'); // get the mime type
26+
return (
27+
type === 'text/plain' ||
28+
type === 'application/json' ||
29+
type === 'application/x-www-form-urlencoded' ||
30+
type === 'multipart/form-data'
31+
);
32+
}
33+
1834
/**
1935
* @param {import('types/hooks').ServerRequest} request
2036
* @param {import('types/internal').SSREndpoint} route
@@ -48,7 +64,7 @@ export async function render_endpoint(request, route, match) {
4864
headers = lowercase_keys(headers);
4965
const type = headers['content-type'];
5066

51-
const is_type_textual = isContentTypeTextual(type);
67+
const is_type_textual = is_content_type_textual(type);
5268

5369
if (!is_type_textual && !(body instanceof Uint8Array || is_string(body))) {
5470
return error(

packages/kit/src/runtime/server/page/load_node.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ export async function load_node({
149149
method: opts.method || 'GET',
150150
headers,
151151
path: relative,
152-
rawBody: /** @type {string} */ (opts.body),
152+
rawBody: new TextEncoder().encode(/** @type {string} */ (opts.body)),
153153
query: new URLSearchParams(search)
154154
},
155155
options,

packages/kit/src/runtime/server/parse_body/index.js

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,34 @@
11
import { read_only_form_data } from './read_only_form_data.js';
22

33
/**
4-
* @param {import('types/hooks').StrictBody} raw
4+
* @param {import('types/hooks.js').RawBody} raw
55
* @param {import('types/helper').Headers} headers
66
*/
77
export function parse_body(raw, headers) {
8-
if (!raw || typeof raw !== 'string') return raw;
8+
if (!raw) return raw;
99

10-
const [type, ...directives] = headers['content-type'].split(/;\s*/);
10+
const content_type = headers['content-type'];
11+
const [type, ...directives] = content_type ? content_type.split(/;\s*/) : [];
12+
13+
const text = () => new TextDecoder(headers['content-encoding'] || 'utf-8').decode(raw);
1114

1215
switch (type) {
1316
case 'text/plain':
14-
return raw;
17+
return text();
1518

1619
case 'application/json':
17-
return JSON.parse(raw);
20+
return JSON.parse(text());
1821

1922
case 'application/x-www-form-urlencoded':
20-
return get_urlencoded(raw);
23+
return get_urlencoded(text());
2124

2225
case 'multipart/form-data': {
2326
const boundary = directives.find((directive) => directive.startsWith('boundary='));
2427
if (!boundary) throw new Error('Missing boundary');
25-
return get_multipart(raw, boundary.slice('boundary='.length));
28+
return get_multipart(text(), boundary.slice('boundary='.length));
2629
}
2730
default:
28-
throw new Error(`Invalid Content-Type ${type}`);
31+
return raw;
2932
}
3033
}
3134

packages/kit/test/apps/basics/src/routes/load/raw-body.json.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ export function post(request) {
33
return {
44
body: {
55
body: /** @type {string} */ (request.body),
6-
rawBody: /** @type {string} */ (request.rawBody)
6+
rawBody: new TextDecoder().decode(/** @type {Uint8Array} */ (request.rawBody))
77
}
88
};
99
}

packages/kit/types/helper.d.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { RawBody } from './hooks';
2+
13
interface ReadOnlyFormData {
24
get(key: string): string;
35
getAll(key: string): string[];
@@ -8,7 +10,7 @@ interface ReadOnlyFormData {
810
[Symbol.iterator](): Generator<[string, string], void>;
911
}
1012

11-
type BaseBody = string | Uint8Array | ReadOnlyFormData;
13+
type BaseBody = string | RawBody | ReadOnlyFormData;
1214
export type ParameterizedBody<Body = unknown> = Body extends FormData
1315
? ReadOnlyFormData
1416
: BaseBody & Body;

packages/kit/types/hooks.d.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@ import { Headers, Location, MaybePromise, ParameterizedBody } from './helper';
22

33
export type StrictBody = string | Uint8Array;
44

5+
export type RawBody = null | Uint8Array;
6+
57
export interface ServerRequest<Locals = Record<string, any>, Body = unknown> extends Location {
68
method: string;
79
headers: Headers;
8-
rawBody: StrictBody;
10+
rawBody: RawBody;
911
body: ParameterizedBody<Body>;
1012
locals: Locals;
1113
}

packages/kit/types/internal.d.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@ import {
44
GetSession,
55
Handle,
66
HandleError,
7+
RawBody,
78
ServerFetch,
89
ServerRequest,
9-
ServerResponse,
10-
StrictBody
10+
ServerResponse
1111
} from './hooks';
1212
import { Load } from './page';
1313

@@ -16,7 +16,7 @@ type PageId = string;
1616
export interface Incoming extends Omit<Location, 'params'> {
1717
method: string;
1818
headers: Headers;
19-
rawBody: StrictBody;
19+
rawBody: RawBody;
2020
body?: ParameterizedBody;
2121
}
2222

0 commit comments

Comments
 (0)