雜記 › 技術筆記 › 二進位轉 Base64 這一頁:前綴只有 5 檔,換行能解、連字號和底線直接報錯

二進位轉 Base64 這一頁:前綴只有 5 檔,換行能解、連字號和底線直接報錯

把這一頁產生的 Base64 貼到別處時,空格、換行、被吃掉的補位符都能照舊解回原位元組;可把加號與斜線寫成連字號和底線,三條解碼路徑一起報錯。前綴實測只有五檔,整條串無換行也不截斷。

字串遞出去之後才開始出問題

事情是從一次多餘的清理開始的。別人遞來的 base64 中間每 76 字元斷了行,我怕解碼器撐不住,先手動刪乾淨才敢往下走,對方說換行本來就不用管。我拿自己生成的串反著試,試到第二種改動才真出錯:有人按 URL-safe 的習慣把 + 和 / 寫成 - 和 _,atob 立刻報 InvalidCharacterError。這篇把兩端對齊記一次。

整條串是 readAsDataURL 給的,前綴只有五檔

範本第 80 行只有一次 readAsDataURL,頁面自己不做編碼:每個檔案恰好新建 1 個 FileReader,產出的 body 與 btoa 編同一批位元組逐字全等,連補位符個數都一樣。前綴也不按副檔名拼,恆為 data: 加 File 物件的 type 再加 ;base64,,22 個樣本測下來前綴(含逗號)長度只有 22、23、25、29、37 這五檔。

type 為空時瀏覽器寫 application/octet-stream,不嗅探內容:同一段 70 B 的 PNG 位元組按空 type 丟進去,前綴退到 37 字元那一檔;39587 B 的 JPEG 位元組宣告成 image/webp,前綴就寫 image/webp,預覽仍解出 1200x900。能否出圖由解碼器按內容決定,與統計那一欄無關。

前綴長度實測五檔 22 23 25 29 37 image/png image/jpeg x-icon application/gzip octet-stream

圖 1 前綴含逗號的五檔,末檔是空 type 的退路

body 長度恆等於 4*ceil(n/3)

編碼交給瀏覽器之後,字元數就是純算術。四檔輸入實測如下,body 長度與公式 4*ceil(n/3) 全等。

輸入位元組與產出字元
輸入(B)body(字元)前綴含逗號
709622
97121295223
395875278423
4194304559240837

「Base64 大小」只數 body,Data URI 的真實串長要再加前綴:96 字元的整串是 118,12952 的是 12975。

複製與下載拿到的是同一條無換行的串

textarea 的 maxlength 實測是 null,也沒有「僅顯示前 N 字元」的分支,4194304 B 的檔案完整持有 5592408 字元,兩個模式來回切值逐字相同。把檔案拖進 二進位轉 Base64 之後取走的兩個出口同樣不加工:複製收到的字元數與框裡一致(12952、5592408 都測過,含換行全為 false),下載走 new Blob,位元組數等於字元數,無 BOM 也無末尾換行。22 個樣本的整串換行數 0,這一頁不替你做 76 列折行。

換行、空格、缺補位符:三條路徑都吞

拿這一頁的 body 去解,容忍度比直覺寬。以 photo_400 的 12952 字元為基準:每 76 字元插一個換行變 13122,插 CRLF 變 13292,atob、fetch 一個 data: URI、新 Image 都照舊解回 9712 位元組;任意位置插空格或製表符、首尾各加空白,結論不變。補位符也不是硬要求:去掉 = 後 12950 字元仍得 9712 位元組,那張 70 B 的 PNG 從 96 變 94 也一樣。

但連字號和底線一檔都不容

寬容只到空白與補位符為止。把 + 換成 -、/ 換成 _ 之後,atob 報 InvalidCharacterError、fetch 報 TypeError、Image 走 error 事件,三條路徑全滅。實測 4 個樣本裡 3 個直接失敗,剩下那個 100 B 純文字樣例本來不含 + 也不含 /,替換後串一個字沒變,只是沒被改壞。

更隱蔽的是查詢字串:photo_400 的 body 裡 + 出現 119 次、/ 出現 139 次,塞進 URLSearchParams 再取出,長度仍是 12952 卻與原串不全等——加號被解成了空格,隨後 atob 報錯。拿長度做檢查在這裡完全失效。

同一串 body 的五種送法 原樣 12952 解回 9712 位元組 每 76 字元換行 13122 解回 9712 位元組 換成 CRLF 13292 解回 9712 位元組 去掉末尾 = 剩 12950 解回 9712 位元組 加號斜線寫成 -_ 三條路徑全部報錯 空白被容忍,換字母表不容

圖 2 改動後的解碼結果,綠色是能解回的四種送法

頁面上那幾個統計數各自的口徑

三個數各按各的演算法。formatSize(第 140-142 行)用 1024 進位,KB 與 MB 固定 2 位小數,9712 顯示成 9.48 KB;「Base64 大小」(第 90-91 行)取 body 的字元數、不含前綴;「增大」(第 92 行)是增量比,實測從 0.0% 擺到 50.0%:16 B 顯示 50.0%、70 B 顯示 37.1%、4194304 B 顯示 33.3%,0 位元組被釘成 0.0%。顯示值之間不能互推:12.65 除以 9.48 是 1.3344,那一行的增大顯示 33.4%。

這一頁沒有解碼入口

上面那組容忍度是瀏覽器側對這一頁所產出串的行為,不是這一頁的功能:真實存在的輸入只有第 11 行那個 accept 為 null 的 input type=file,第 35 行的輸出框是 readonly,沒有第二個可輸入文字的控件;拖曳與選擇兩處都只用第 1 個檔案(第 170、155 行),也沒有批次。要把一串 base64 轉回檔案得另找解碼方向。至於編碼側為什麼會變大、末尾的 = 怎麼來,base64 編碼為什麼會讓檔案變大 那篇已經記過,不複述。

另外幾種會讓人以為頁面壞了的輸入

假副檔名和壞圖都拿不到錯誤:72 B 的壞 PNG 照樣產出 96 字元、照樣開啟預覽容器,整串餵 Image 報 error,頁面上只留瀏覽器自帶的破圖示(第 64 行只判斷 type 是否以 image/ 開頭,不看解碼結果)。反過來,型別寫成 text/plain 的 PNG 位元組容器是關的,可整串餵給 Image 仍解出 1x1。

0 位元組更彆扭:純 Base64 下 body 長為 0,第 117、124 行雙雙短路;切到 Data URI 才有 37 字元,複製能收到 37,下載是個只含前綴的 37 位元組 txt。

還常碰到的幾件事

Q:貼到別處時換行要先手動刪掉嗎?

瀏覽器側不用。12952 折成 13122 或 13292 都照舊解回 9712 位元組。會出事的是傳輸那一層:字串一進入查詢字串,加號先被解成空格。

Q:順手改成 URL-safe 形態發出去行不行?

先確認對方按哪套字母表解。這次 4 個樣本裡 3 個改成 -_ 之後直接報錯,瀏覽器不會替你把它們認回來,接收側得顯式做一次反向替換。

Q:0 位元組檔案為什麼複製按鈕沒反應?

值長為 0 時第 117 行的短路先命中,剪貼簿一次都沒被呼叫;切到 Data URI 才有 37 字元可複製,而那 37 個字元全是前綴。

📎 相關文章

16 組開關實測 HTML 去標籤:報 19 個標籤,實際刪掉 148 個字元 2026-10-30 SVG 轉 PNG 後外部引用的圖丟了一半:同一份 594 B 檔案預覽有 3 張圖、成品只剩 1 張 2026-10-30 替應用做商店介紹圖,點匯出卻拿到 1242×2688:8 檔畫布預設的像素帳、噪點 24.83 倍體積與預設被裁掉 134.93 px 的手機框 2026-10-30 16 個鍵進、4 個欄出:JSON 轉表格的表頭聯集、點號展平與 CSV 引號規則 2026-10-30 取消勾選「保留註解」後,format-js 把 24 字元 URL 壓成 13 字元殘缺碼 2026-10-30