字串遞出去之後才開始出問題
事情是從一次多餘的清理開始的。別人遞來的 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。能否出圖由解碼器按內容決定,與統計那一欄無關。
圖 1 前綴含逗號的五檔,末檔是空 type 的退路
body 長度恆等於 4*ceil(n/3)
編碼交給瀏覽器之後,字元數就是純算術。四檔輸入實測如下,body 長度與公式 4*ceil(n/3) 全等。
| 輸入(B) | body(字元) | 前綴含逗號 |
|---|---|---|
| 70 | 96 | 22 |
| 9712 | 12952 | 23 |
| 39587 | 52784 | 23 |
| 4194304 | 5592408 | 37 |
「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 報錯。拿長度做檢查在這裡完全失效。
圖 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 個字元全是前綴。