一般處理電子郵件時,如果是全部都使用 ASCII 字元的電子郵件倒還相當直覺,一旦遇到中文(Big5)或其他種語系的時候,就一定會遇到郵件標頭(message headers)內的資訊(如標題 / subject)是經過編碼的,如以下例子的 Subject

Delivered-To: your_mail_box@mail.com
From: =?Big5?B?pKS12KrAt3y61qdRwXCmWMRVttKo87d8?=
<world@mail.com>
Subject: =?Big5?B?reyo06FBpc2k6cRAseazb7zLpc6n86fWvNahSQ==?=
To: your_mail_box@mail.com
Content-Type: multipart/alternative;
boundary="=_NextPart_gfa16yplmtrdgwhukyrk8"
MIME-Version: 1.0
Reply-To: sender_mail_box@mail.com
Date: Wed, 11 Feb 2011 17:23:24 +0800

上述例子 Subject 被編碼為 =?Big5?B?reyo06FBpc2k6cRAseazb7zLpc6n86fWvNahSQ==?= 。這是因為在 MIME(Multipurpose Internet Mail Extensions) RFC2047 標準中規範了郵件標頭內 非ASCII 的字元必須被轉成能以 ASCII 表達的字串 ,此種經過編碼的字串會以 =? 作為開頭,以 ?= 作為結尾,以 ? 分隔,整個字串裡會包含的資訊依序為語系、編碼方式、編碼後的字串,如下所示:

=? 語系 ? 編碼方式 ? 編碼後的字串 ?=

其中, 編碼方式分為 Base64 及 Quoted-Printable 兩種,分別以 b 及 q 作為表示(大小寫皆可) 。因此上述的 =?Big5?B?reyo06FBpc2k6cRAseazb7zLpc6n86fWvNahSQ==?= ,可解讀為 reyo06FBpc2k6cRAseazb7zLpc6n86fWvNahSQ= 是經過 Base64 編碼過後的 Big5 語系的字串。

因為中文字不是 ASCII 字元,所以當你遇到電子郵件無法正常顯示中文時,就看看是不是該用 MIME 的標準進行編碼。

Python

Python 已經內建好 email 模組可以使用,所以可以運用 email 模組幫忙 encode / decode ,例如:

from email.header import Header
print(Header('我是中文', 'utf-8').encode())
# =?utf-8?b?5oiR5piv5Lit5paH?=

from email.header import decode_header
print(decode_header('=?utf-8?b?5oiR5piv5Lit5paH?='))
# [(b'\xe6\x88\x91\xe6\x98\xaf\xe4\xb8\xad\xe6\x96\x87', 'utf-8')]

上述就是利用 Header.encode & decode_header 幫忙 encode / decode ,其中 decode_header 會把 decode 出來的文字回傳存有多個 (text, charset) tuple 結構的 list 。

另外,如果想要用 Quoted-Printable 編碼方式的話,可以用 quopri 模組。

PHP

如果以 PHP 內建的 function decode ,可以簡單使用 iconv_mime_decode() decode ,使用方式如下:

不過這樣的方式還是可能會有問題產生,例如字串內 同時包含 ASCII 字元及非 ASCII 字元的情況下,就會導致 iconv_mime_decode() 無法順利解譯,而回傳(return) false 。

這時候還可以使用 imap_mime_header_decode() 來輔助除錯,此函數會回傳一個陣列類別(array object),裡面每一個元素都會包含兩種屬性 charset, text ,例如:

<?php
$text = "=?ISO-8859-1?Q?Keld_J=F8rn_Simonsen?= <keld@example.com>";

$elements = imap_mime_header_decode($text);
for ($i=0; $i<count($elements); $i++) {
    echo "Charset: {$elements[$i]->charset}\n";
    echo "Text: {$elements[$i]->text}\n\n";
}
?>

輸出結果:

Charset: ISO-8859-1
Text: Keld Jørn Simonsen

Charset: default
Text:  <keld@example.com>

上述 charset 輸出分別為 ISO-8859-1, default ,其中 default 就是指 ASCII 編碼。

p.s. imap_mime_header_decode() 需安裝 imap 模組

結合上述 2 種函數就能夠產生更好的函數,彌補 iconv_mime_decode() 的不足,並且能夠靈活地將字串轉為其他語系。

參考資料:

https://docs.python.org/3/library/email.html

https://docs.python.org/3/library/quopri.html

http://php.net/manual/en/function.iconv-mime-decode.php

http://php.net/manual/en/function.imap-mime-header-decode.php