[fix] Expand list of allowed mime types for binary bodies
ADD
.changeset/great-guests-visit.md
+---
+'@sveltejs/adapter-cloudflare-workers': patch
+'@sveltejs/adapter-netlify': patch
+'@sveltejs/kit': patch
+---
+
+Update and consolidate checks for binary body types

MODIFY
packages/adapter-cloudflare-workers/files/entry.js
packages/adapter-cloudflare-workers/files/entry.js
// TODO hardcoding the relative location makes this brittle
import { init, render } from '../output/server/app.js'; // eslint-disable-line import/no-unresolved
import { getAssetFromKV, NotFoundError } from '@cloudflare/kv-asset-handler'; // eslint-disable-line import/no-unresolved
+import { isContentTypeBinary } from '@sveltejs/kit/adapter-utils'; // eslint-disable-line import/no-unresolved
init();
@@ -57,7 +58,7 @@ async function handle(event) {
/** @param {Request} request */
async function read(request) {
const type = request.headers.get('content-type') || '';
- if (type.includes('application/octet-stream')) {
+ if (isContentTypeBinary(type)) {
return new Uint8Array(await request.arrayBuffer());
}

MODIFY
packages/adapter-netlify/files/entry.js
packages/adapter-netlify/files/entry.js
// TODO hardcoding the relative location makes this brittle
import { init, render } from '../output/server/app.js'; // eslint-disable-line import/no-unresolved
+import { isContentTypeBinary } from '@sveltejs/kit/adapter-utils'; // eslint-disable-line import/no-unresolved
init();
@@ -8,8 +9,9 @@ export async function handler(event) {
const query = new URLSearchParams(rawQuery);
+ const type = headers['content-type'];
const rawBody =
- headers['content-type'] === 'application/octet-stream'
+ type && isContentTypeBinary(type)
? new TextEncoder('base64').encode(body)
: isBase64Encoded
? Buffer.from(body, 'base64').toString()

MODIFY
packages/kit/package.json
packages/kit/package.json
"./install-fetch": {
"import": "./dist/install-fetch.js"
},
+ "./adapter-utils": {
+ "import": "./dist/adapter-utils.js"
+ },
"./types": "./types/index.d.ts"
},
"types": "types/index.d.ts",

MODIFY
packages/kit/rollup.config.js
packages/kit/rollup.config.js
@@ -48,7 +48,8 @@ export default [
cli: 'src/cli.js',
ssr: 'src/runtime/server/index.js',
node: 'src/core/node/index.js',
- 'install-fetch': 'src/install-fetch.js'
+ 'install-fetch': 'src/install-fetch.js',
+ 'adapter-utils': 'src/core/adapter-utils.js'
},
output: {
dir: 'dist',

ADD
packages/kit/src/core/adapter-utils.js
+/**
+ * Decides how the body should be parsed based on its mime type.
+ *
+ * This is intended to be used with both requests and responses, to have a consistent body parsing across adapters.
+ *
+ * @param {string} content_type The `content-type` header of a request/response.
+ * @returns {boolean}
+ */
+export function isContentTypeBinary(content_type) {
+ return (
+ content_type.startsWith('image/') ||
+ content_type.startsWith('audio/') ||
+ content_type.startsWith('video/') ||
+ content_type.startsWith('application/octet-stream')
+ );
+}

MODIFY
packages/kit/src/core/node/index.js
packages/kit/src/core/node/index.js
+import { isContentTypeBinary } from '../adapter-utils.js';
+
/**
* @param {import('http').IncomingMessage} req
* @returns {Promise<import('types/hooks').StrictBody>}
@@ -48,7 +50,7 @@ export function getRawBody(req) {
req.on('end', () => {
const [type] = h['content-type'].split(/;\s*/);
- if (type === 'application/octet-stream') {
+ if (isContentTypeBinary(type)) {
return fulfil(data);
}

MODIFY
packages/kit/src/runtime/server/endpoint.js
packages/kit/src/runtime/server/endpoint.js
+import { isContentTypeBinary } from '../../core/adapter-utils.js';
import { lowercase_keys } from './utils.js';
/** @param {string} body */
@@ -37,24 +38,28 @@ export default async function render_route(request, route) {
headers = lowercase_keys(headers);
const type = headers['content-type'];
+ const is_type_binary = type && isContentTypeBinary(type);
+
// validation
- if (type === 'application/octet-stream' && !(body instanceof Uint8Array)) {
+ if (is_type_binary && !(body instanceof Uint8Array)) {
return error(
- `${preface}: body must be an instance of Uint8Array if content type is application/octet-stream`
+ `${preface}: body must be an instance of Uint8Array if content type is image/*, audio/*, video/* or application/octet-stream`
);
}
- if (body instanceof Uint8Array && type !== 'application/octet-stream') {
+ if (body instanceof Uint8Array && !is_type_binary) {
return error(
- `${preface}: Uint8Array body must be accompanied by content-type: application/octet-stream header`
+ `${preface}: Uint8Array body must have content-type header of image/*, audio/*, video/* or application/octet-stream`
);
}
/** @type {import('types/hooks').StrictBody} */
let normalized_body;
+ // ensure the body is an object
if (
(typeof body === 'object' || typeof body === 'undefined') &&
+ !(body instanceof Uint8Array) &&
(!type || type.startsWith('application/json'))
) {
headers = { ...headers, 'content-type': 'application/json; charset=utf-8' };

[fix] Expand list of allowed mime types for binary bodies  (#1687)