Skip to content

Commit 5dae367

Browse files
authored
fix: properly decode base64 strings inside read (#11682)
* fix: properly decode base64 strings inside `read` * test --------- Co-authored-by: Rich Harris <[email protected]>
1 parent e228f89 commit 5dae367

File tree

8 files changed

+59
-40
lines changed

8 files changed

+59
-40
lines changed

Diff for: .changeset/fluffy-schools-develop.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@sveltejs/kit': patch
3+
---
4+
5+
fix: properly decode base64 strings inside `read`

Diff for: packages/kit/src/runtime/app/server/index.js

+13-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { read_implementation, manifest } from '__sveltekit/server';
22
import { base } from '__sveltekit/paths';
33
import { DEV } from 'esm-env';
4+
import { b64_decode } from '../../utils.js';
45

56
/**
67
* Read the contents of an imported asset from the filesystem
@@ -30,7 +31,18 @@ export function read(asset) {
3031
const [prelude, data] = asset.split(';');
3132
const type = prelude.slice(5) || 'application/octet-stream';
3233

33-
const decoded = data.startsWith('base64,') ? atob(data.slice(7)) : decodeURIComponent(data);
34+
if (data.startsWith('base64,')) {
35+
const decoded = b64_decode(data.slice(7));
36+
37+
return new Response(decoded, {
38+
headers: {
39+
'Content-Length': decoded.byteLength.toString(),
40+
'Content-Type': type
41+
}
42+
});
43+
}
44+
45+
const decoded = decodeURIComponent(data);
3446

3547
return new Response(decoded, {
3648
headers: {

Diff for: packages/kit/src/runtime/client/fetcher.js

+1-16
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { BROWSER, DEV } from 'esm-env';
22
import { hash } from '../hash.js';
3+
import { b64_decode } from '../utils.js';
34

45
let loading = 0;
56

@@ -77,22 +78,6 @@ if (DEV && BROWSER) {
7778

7879
const cache = new Map();
7980

80-
/**
81-
* @param {string} text
82-
* @returns {ArrayBufferLike}
83-
*/
84-
function b64_decode(text) {
85-
const d = atob(text);
86-
87-
const u8 = new Uint8Array(d.length);
88-
89-
for (let i = 0; i < d.length; i++) {
90-
u8[i] = d.charCodeAt(i);
91-
}
92-
93-
return u8.buffer;
94-
}
95-
9681
/**
9782
* Should be called on the initial run of load functions that hydrate the page.
9883
* Saves any requests with cache-control max-age to the cache.

Diff for: packages/kit/src/runtime/server/page/load_data.js

+1-19
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { DEV } from 'esm-env';
22
import { disable_search, make_trackable } from '../../../utils/url.js';
33
import { validate_depends } from '../../shared.js';
4+
import { b64_encode } from '../../utils.js';
45

56
/**
67
* Calls the user's server `load` function.
@@ -207,25 +208,6 @@ export async function load_data({
207208
return result ?? null;
208209
}
209210

210-
/**
211-
* @param {ArrayBuffer} buffer
212-
* @returns {string}
213-
*/
214-
function b64_encode(buffer) {
215-
if (globalThis.Buffer) {
216-
return Buffer.from(buffer).toString('base64');
217-
}
218-
219-
const little_endian = new Uint8Array(new Uint16Array([1]).buffer)[0] > 0;
220-
221-
// The Uint16Array(Uint8Array(...)) ensures the code points are padded with 0's
222-
return btoa(
223-
new TextDecoder(little_endian ? 'utf-16le' : 'utf-16be').decode(
224-
new Uint16Array(new Uint8Array(buffer))
225-
)
226-
);
227-
}
228-
229211
/**
230212
* @param {Pick<import('@sveltejs/kit').RequestEvent, 'fetch' | 'url' | 'request' | 'route'>} event
231213
* @param {import('types').SSRState} state

Diff for: packages/kit/src/runtime/utils.js

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/**
2+
* @param {string} text
3+
* @returns {ArrayBufferLike}
4+
*/
5+
export function b64_decode(text) {
6+
const d = atob(text);
7+
8+
const u8 = new Uint8Array(d.length);
9+
10+
for (let i = 0; i < d.length; i++) {
11+
u8[i] = d.charCodeAt(i);
12+
}
13+
14+
return u8.buffer;
15+
}
16+
17+
/**
18+
* @param {ArrayBuffer} buffer
19+
* @returns {string}
20+
*/
21+
export function b64_encode(buffer) {
22+
if (globalThis.Buffer) {
23+
return Buffer.from(buffer).toString('base64');
24+
}
25+
26+
const little_endian = new Uint8Array(new Uint16Array([1]).buffer)[0] > 0;
27+
28+
// The Uint16Array(Uint8Array(...)) ensures the code points are padded with 0's
29+
return btoa(
30+
new TextDecoder(little_endian ? 'utf-16le' : 'utf-16be').decode(
31+
new Uint16Array(new Uint8Array(buffer))
32+
)
33+
);
34+
}
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
Imported without ?url
1+
Imported without ?url 😎
+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
Imported with ?url
1+
Imported with ?url 😎

Diff for: packages/kit/test/apps/basics/test/test.js

+3-2
Original file line numberDiff line numberDiff line change
@@ -713,8 +713,9 @@ test.describe('$app/server', () => {
713713
const auto = await page.textContent('[data-testid="auto"]');
714714
const url = await page.textContent('[data-testid="url"]');
715715

716-
expect(auto.trim()).toBe('Imported without ?url');
717-
expect(url.trim()).toBe('Imported with ?url');
716+
// the emoji is there to check that base64 decoding works correctly
717+
expect(auto.trim()).toBe('Imported without ?url 😎');
718+
expect(url.trim()).toBe('Imported with ?url 😎');
718719
});
719720
});
720721

0 commit comments

Comments
 (0)