7 Bit Encoding and Email

最近工作上比較常接觸到 email 的東西,然後比較認真的看了 HTML email 信件的內容,以前我以為都要用 base64 編碼來處理,可是用 base64 來處理 HTML email 我一直覺得很不合理,一來大小會變 1.33 倍,二來整個 HTML 原始碼傳送時會變的幾乎無法辨識,收信軟體還要先解碼一次才可以 parse HTML,感覺完全不需要多此一舉,總之就是覺得為什麼要做這麼愚蠢的事情,明明看起來 MIME 就沒這樣限制,所以我應該可以這樣寫:

Content-Type: text/html; charset=utf-8

然後內文直接放 HTML 原始碼,可是不知道為什麼沒人這樣做,事實上也不 work;最近多看了一些郵件原始碼才發現其實還有個 Quoted-Printable encoding 也很常用,看起來比 Base64 的結果還要接近原始碼許多了,所以就研究了一下它到底是什麼格式。

Quoted-Printable encoding 的基本原理就是用=作為 escape 字元,然後可以把要轉換的字元轉成=字碼的形式,例如 Big5 中文的就要轉成=A7=DA,規範上要轉換的是除了可見(printable)ASCII 字元以外的字元都要轉,而 ASCII 是個 7bit 編碼,字碼只有從 0 到 127 而已,而 email 要用 Quoted-Printable encoding 的主要原因其實就是為了讓文件內的每個字元編碼都維持在 7bit 編碼範圍內,現在大家常用的編碼像是 UTF-8 和以前常用的 Big5 等都是 8bit 編碼,兩者差別就在於每個傳輸的 byte 中有沒有使用到第 8 個 bit,轉成二進位的時候,7bit 系統編碼不會用到最左(higher-order)邊的那個 bit。

為什麼需要用 7bit 的文字編碼呢?主因是計算機和電信網路早期很多系統是只支援 7bit 編碼的,SMTP 的規範就直接要求 TCP 傳輸時,每個 byte 最左邊的 higher-order bit 要填 0:

The TCP connection supports the transmission of 8-bit bytes. The SMTP data is 7-bit ASCII characters. Each character is transmitted as an 8-bit byte with the high-order bit cleared to zero.

當然這規範很落後時代,所以在 MIME(Multipurpose Internet Mail Extensions) 規範其實也有Content-Transfer-Encoding可以指定傳輸用的是什麼編碼:

Content-Transfer-Encoding: 8bit

不過為了相容舊系統,還是很少真的這樣使用的信件在傳遞,因為要是傳到了 7bit 系統,小則亂碼、大則程式當機。不過這就帶出另外一個問題了,難道 7bit 系統只能傳輸 ASCII 字集嗎?因為我還蠻常看到日文的純文字郵件,就去找了一些來看看,結果發現到有的是用ISO-2022-JP,而且是使用 7bit 的傳輸:

Content-Type: text/plain; charset=ISO-2022-JP
Content-Transfer-Encoding: 7bit

信件內容的文字也都很正確,沒有亂碼:

iso-2022-jp

於是就看一下 ISO-2022 的介紹,發現原來是個很早就有的 7bit 編碼方法,後來根據這方法有訂出了 CN、JP、KR 等語言的編碼,不過比較通行的看來只有 ISO-2022-JP,然後我也找到 HTML email 用 ISO-2022-JP 的:

ISO-2022-JP

看起來就像是我理想中的 HTML email 原始碼啊,所以問題的癥結其實是,大家為了相容於舊系統,所以都用 7bit 傳輸,要 7bit safe 的 encoding 選擇有限,除了比較通行的 ISO-2022-JP 可以給日文用、字元太少只能給英文用的 ASCII 之外,其它語言就只能用 Base64 encoding 和 Quoted-Printable encoding 了,所以事實上其它 7bit 編碼的內容,也是可以直接透過 SMTP 協定來傳輸的,只是要看收信端的軟體能不能支援解碼,像是已經不太有人用的 UTF-7 就是 7bit 的 unicode 編碼。

最後,就是假設我們已經不用擔心老舊系統的時候,其實只要這樣寫在 MIME header 裡就可以直接傳 UTF-8 的 HTML source,不用再經過任何編碼處理了:

Content-Transfer-Encoding: 8bit
Content-Type: text/html; charset=utf-8

不過距離這一步不知道還有多遠就是了。