From 12cafd953004a66cfed70cd449eb7edf1bc4f46e Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Fri, 19 Jan 2024 09:53:11 -0500 Subject: [PATCH 1/2] fix: properly decode base64 strings inside `read` --- .changeset/fluffy-schools-develop.md | 5 +++ packages/kit/src/runtime/app/server/index.js | 14 +++++++- packages/kit/src/runtime/client/fetcher.js | 17 +--------- .../kit/src/runtime/server/page/load_data.js | 20 +---------- packages/kit/src/runtime/utils.js | 34 +++++++++++++++++++ 5 files changed, 54 insertions(+), 36 deletions(-) create mode 100644 .changeset/fluffy-schools-develop.md create mode 100644 packages/kit/src/runtime/utils.js diff --git a/.changeset/fluffy-schools-develop.md b/.changeset/fluffy-schools-develop.md new file mode 100644 index 000000000000..a370a536148d --- /dev/null +++ b/.changeset/fluffy-schools-develop.md @@ -0,0 +1,5 @@ +--- +'@sveltejs/kit': patch +--- + +fix: properly decode base64 strings inside `read` diff --git a/packages/kit/src/runtime/app/server/index.js b/packages/kit/src/runtime/app/server/index.js index 9865bd8cb4dd..3702c171bb6a 100644 --- a/packages/kit/src/runtime/app/server/index.js +++ b/packages/kit/src/runtime/app/server/index.js @@ -1,6 +1,7 @@ import { read_implementation, manifest } from '__sveltekit/server'; import { base } from '__sveltekit/paths'; import { DEV } from 'esm-env'; +import { b64_decode } from '../../utils.js'; /** * Read the contents of an imported asset from the filesystem @@ -30,7 +31,18 @@ export function read(asset) { const [prelude, data] = asset.split(';'); const type = prelude.slice(5) || 'application/octet-stream'; - const decoded = data.startsWith('base64,') ? atob(data.slice(7)) : decodeURIComponent(data); + if (data.startsWith('base64,')) { + const decoded = b64_decode(data.slice(7)); + + return new Response(decoded, { + headers: { + 'Content-Length': decoded.byteLength.toString(), + 'Content-Type': type + } + }); + } + + const decoded = decodeURIComponent(data); return new Response(decoded, { headers: { diff --git a/packages/kit/src/runtime/client/fetcher.js b/packages/kit/src/runtime/client/fetcher.js index 446ebbda8f9e..46d7a0b569a3 100644 --- a/packages/kit/src/runtime/client/fetcher.js +++ b/packages/kit/src/runtime/client/fetcher.js @@ -1,5 +1,6 @@ import { BROWSER, DEV } from 'esm-env'; import { hash } from '../hash.js'; +import { b64_decode } from '../utils.js'; let loading = 0; @@ -77,22 +78,6 @@ if (DEV && BROWSER) { const cache = new Map(); -/** - * @param {string} text - * @returns {ArrayBufferLike} - */ -function b64_decode(text) { - const d = atob(text); - - const u8 = new Uint8Array(d.length); - - for (let i = 0; i < d.length; i++) { - u8[i] = d.charCodeAt(i); - } - - return u8.buffer; -} - /** * Should be called on the initial run of load functions that hydrate the page. * Saves any requests with cache-control max-age to the cache. diff --git a/packages/kit/src/runtime/server/page/load_data.js b/packages/kit/src/runtime/server/page/load_data.js index 60f7d6570436..75cf68d335e8 100644 --- a/packages/kit/src/runtime/server/page/load_data.js +++ b/packages/kit/src/runtime/server/page/load_data.js @@ -1,6 +1,7 @@ import { DEV } from 'esm-env'; import { disable_search, make_trackable } from '../../../utils/url.js'; import { validate_depends } from '../../shared.js'; +import { b64_encode } from '../../utils.js'; /** * Calls the user's server `load` function. @@ -207,25 +208,6 @@ export async function load_data({ return result ?? null; } -/** - * @param {ArrayBuffer} buffer - * @returns {string} - */ -function b64_encode(buffer) { - if (globalThis.Buffer) { - return Buffer.from(buffer).toString('base64'); - } - - const little_endian = new Uint8Array(new Uint16Array([1]).buffer)[0] > 0; - - // The Uint16Array(Uint8Array(...)) ensures the code points are padded with 0's - return btoa( - new TextDecoder(little_endian ? 'utf-16le' : 'utf-16be').decode( - new Uint16Array(new Uint8Array(buffer)) - ) - ); -} - /** * @param {Pick} event * @param {import('types').SSRState} state diff --git a/packages/kit/src/runtime/utils.js b/packages/kit/src/runtime/utils.js new file mode 100644 index 000000000000..f73f5372a91e --- /dev/null +++ b/packages/kit/src/runtime/utils.js @@ -0,0 +1,34 @@ +/** + * @param {string} text + * @returns {ArrayBufferLike} + */ +export function b64_decode(text) { + const d = atob(text); + + const u8 = new Uint8Array(d.length); + + for (let i = 0; i < d.length; i++) { + u8[i] = d.charCodeAt(i); + } + + return u8.buffer; +} + +/** + * @param {ArrayBuffer} buffer + * @returns {string} + */ +export function b64_encode(buffer) { + if (globalThis.Buffer) { + return Buffer.from(buffer).toString('base64'); + } + + const little_endian = new Uint8Array(new Uint16Array([1]).buffer)[0] > 0; + + // The Uint16Array(Uint8Array(...)) ensures the code points are padded with 0's + return btoa( + new TextDecoder(little_endian ? 'utf-16le' : 'utf-16be').decode( + new Uint16Array(new Uint8Array(buffer)) + ) + ); +} From 2d2cefb6b6ebf11ccbea6e0770c6c5c319acc890 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Fri, 19 Jan 2024 09:58:31 -0500 Subject: [PATCH 2/2] test --- packages/kit/test/apps/basics/src/routes/read-file/auto.txt | 2 +- packages/kit/test/apps/basics/src/routes/read-file/url.txt | 2 +- packages/kit/test/apps/basics/test/test.js | 5 +++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/kit/test/apps/basics/src/routes/read-file/auto.txt b/packages/kit/test/apps/basics/src/routes/read-file/auto.txt index f48c4ff3fa09..ab8b36813b36 100644 --- a/packages/kit/test/apps/basics/src/routes/read-file/auto.txt +++ b/packages/kit/test/apps/basics/src/routes/read-file/auto.txt @@ -1 +1 @@ -Imported without ?url +Imported without ?url 😎 diff --git a/packages/kit/test/apps/basics/src/routes/read-file/url.txt b/packages/kit/test/apps/basics/src/routes/read-file/url.txt index eeb75878cc2e..0ecd2bb31dcd 100644 --- a/packages/kit/test/apps/basics/src/routes/read-file/url.txt +++ b/packages/kit/test/apps/basics/src/routes/read-file/url.txt @@ -1 +1 @@ -Imported with ?url +Imported with ?url 😎 diff --git a/packages/kit/test/apps/basics/test/test.js b/packages/kit/test/apps/basics/test/test.js index e35a7e6f8cec..b408d1b56c3e 100644 --- a/packages/kit/test/apps/basics/test/test.js +++ b/packages/kit/test/apps/basics/test/test.js @@ -713,8 +713,9 @@ test.describe('$app/server', () => { const auto = await page.textContent('[data-testid="auto"]'); const url = await page.textContent('[data-testid="url"]'); - expect(auto.trim()).toBe('Imported without ?url'); - expect(url.trim()).toBe('Imported with ?url'); + // the emoji is there to check that base64 decoding works correctly + expect(auto.trim()).toBe('Imported without ?url 😎'); + expect(url.trim()).toBe('Imported with ?url 😎'); }); });