From 25fb52dc5c587acca2f4457d5c6f7b8032b7829c Mon Sep 17 00:00:00 2001 From: Gab Virebent Date: Thu, 2 Jul 2026 18:01:21 +0200 Subject: Fix base64 encoding crash on file attachments over ~1MB String.fromCharCode(...bytes) spreads every byte as a separate call argument, exceeding the engine's max-arguments limit on multi-MB files (RangeError: Maximum call stack size exceeded). Chunk the conversion instead. Reported by a real submitter trying a 1.5MB file. --- static/crypto.js | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) (limited to 'static/crypto.js') diff --git a/static/crypto.js b/static/crypto.js index 04a1400..f1cc6c9 100644 --- a/static/crypto.js +++ b/static/crypto.js @@ -8,6 +8,19 @@ const NYMDROP_PUBKEY_HEX = "6aa99a672c48cb8eee65ddd5c5e5f8947a8d52d39a8b8eb4e11b // server's --pow-difficulty flag. const NYMDROP_POW_DIFFICULTY = 18; +// String.fromCharCode(...bytes) blows the call stack on large files: the +// spread passes every byte as a separate function argument, and engines cap +// argument count well below what a multi-MB file needs. Chunking keeps each +// call under that limit. +function bytesToBase64(bytes) { + const chunkSize = 0x8000; + let binary = ""; + for (let i = 0; i < bytes.length; i += chunkSize) { + binary += String.fromCharCode.apply(null, bytes.subarray(i, i + chunkSize)); + } + return btoa(binary); +} + async function hexToBytes(hex) { const bytes = new Uint8Array(hex.length / 2); for (let i = 0; i < hex.length; i += 2) @@ -126,7 +139,7 @@ document.getElementById("drop-form").addEventListener("submit", async (e) => { const fileInput = document.getElementById("file"); if (fileInput.files.length > 0) { const fileBytes = await fileInput.files[0].arrayBuffer(); - const fileB64 = btoa(String.fromCharCode(...new Uint8Array(fileBytes))); + const fileB64 = bytesToBase64(new Uint8Array(fileBytes)); plaintext += "\n---FILE:" + fileInput.files[0].name + "---\n" + fileB64; } -- cgit v1.2.3