在理解EML格式的時候,先回顧一下歷史,這樣有助於理解郵件的格式,比如郵件傳輸時為何會有多種編碼方式。此外,理解EML格式也有助於理解HTTP協議。
歷史溯源
由於歷史原因,我們目前看到的大部分的網絡協議都是基於ASCII碼這種純文本方式,也就是基於字符串的命令行方式,比如HTTP、FTP、POP3、SMTP、TELNET等。
早期操作系統比如Unix或DOS沒有圖形界面,用戶與電腦之間只能通過控制台進行交互,也就是通過鍵盤將命令(或請求)輸入到電腦,當用戶回車換行(\r\n)時,表示命令輸入完畢。如果存在網絡通信,則該命令包括用戶敲擊的回車換行會一並發送到服務器端,服務器端接收數據時,就會以回車換行來界定一條完整的命令是否獲取完畢,否則會繼續等待直到接收到"\r\n"或超時,當請求接收完畢后,服務端會執行該請求並將響應結果返回給客戶端。
理解這一點非常重要,這就是為什么很多網絡應用層協議都是以"\r\n"來作為結束標記。對於多行請求時,通常以兩個連續的回車換行表示請求結束,也就是"\r\n\r\n",直觀的用肉眼觀察就是存在一個空行。
比如HTTP協議,與主機建立連接后,輸入"GET / HTTP/1.1\r\n\r\n"即可獲取網站的主頁。
回到Email協議(SMTP和POP3),早期的電子郵件只支持ASCII碼這種純文本傳輸方式,但隨着全世界人民對物質文化生活的不斷向往,這種落后的傳輸方式,已經無法滿足世界人民對美好生活的追求,比如圖像、視頻、音頻、Office文件如何在郵件中展現?不同國家(非英語國家)字符集該如何傳輸和展現?
換句話說,就是這種非ASCII的二進制富文本,該如何傳輸和呈現?
MIME的誕生
此時MIME標准誕生了,MIME的出現更多的是一種向下兼容的無奈,而不是革命。通過對二進制數據或非ASCII碼數據進行base64或quoted-printable編碼,來實現純ASCII碼的傳輸。顯然這種方式會讓你的郵件體變大,傳輸效率下降。尤其附件很多時,通過MIME的boundary來解析郵件的附件也是一筆額外的負擔。
同時MIME的標准也被HTTP協議所采用,我們可以通過content-type字段指定傳輸的內容是什么類型,通過MIME的boundary來對Form-Data數據進行擴展,讓我們在POST數據時也能夠在“表格”數據中插入文件,從而達到上傳文件的效果。
下面是一個EML格式的文件截圖,該截圖是一個郵件頭,該郵件頭包含幾個重要字段,Subject(主題),From(發件人)To(接收人),Cc(抄送人),Date(發送日期)。
其中一個最重要的字段就是Content-Type。只有解析了Content-type字段我們才能准確的將郵件體給解析並展現出來。
通過上圖我們可以觀察到該郵件的內容是一個混合類型,由多個部分組成,Content-type字段中的boundary被用來分割不同的部分。下圖顯示的是一個郵件的附件文件,該文件類型為pdf。
boundary的前綴加上"--"表示新的郵件內容的開始,緊接着為幾行MIME頭,仔細觀察MIME頭則會發現,這些字段其實和HTTP協議的頭的字段是相同的。空行后則是具體的郵件內容,這些郵件內容的傳輸編碼方式有兩種:一種是base64,一種是quoted-printale,當然也可能是未編碼。通常情況下,采用quoted-printable方式對HTML(郵件正文)進行編碼,而對於附件文件(比如二進制文件),則采用base64進行編碼。
郵件的正文可能有兩個:一個是HTML格式,一個是Plain格式。為什么會有兩個格式的正文?其實這兩個格式展現出來的內容都是一樣的,這樣做的目的是為了客戶端的兼容,早期的的郵件客戶端可能無法顯示HTML格式,或者對HTML格式的兼容性不是很好,當然嘍這也是歷史原因造成的。現在的郵件客戶端都能顯示HTML的正文了。
當郵件結束時,是以"--"+boundary+"--"來標記,也就是給boundary加上前綴"--"和后綴"--"。
通過對MIME格式的分析我們可以看到解析Content-type字段很重要,通過提取boundary並循環查找,就可以解析出不同的郵件體,從而將一封EML格式的電子郵件展現出來。
最后注意一點: MIME頭中字符串采用特有的簡潔的編碼方式,將字符集和編碼方式集成到了一起,以"=?"開始,以"?="結束。"=?"后為字符集,緊接着"?"后表示編碼方式。B(b)為base64編碼,Q(q)為quoted-printable編碼。如下圖:
EmlReader是一個客戶端程序,比這更輕量級的是下面的EmlParse工具,不到150K,可批量解析eml格式郵件,並提取郵件中的正文和附件到指定目錄中:
EmlParse:一款超輕量級的批量解析EML格式電子郵件的工具