MIME詳解

MIME,英文全稱爲“Multipurpose Internet Mail Extensions”,即多用途互聯網郵件擴展,是目前互聯網電子郵件普遍遵循的郵件技術規範。在MIME出現之前,互聯網電子郵件主要遵循由RFC 822所制定的標準,電子郵件一般只用來傳遞基本的ASCII碼文本信息,MIME在 RFC 822的基礎上對電子郵件規範做了大量的擴展,引入了新的格式規範和編碼方式,在MIME的支持下,圖像、聲音、動畫等二進制文件都可方便的通過電子郵件來進行傳遞,極大地豐富了電子郵件的功能。目前互聯網上使用的基本都是遵循MIME規範的電子郵件。
    電子郵件的分析和讀取一般都通過專用的郵件軟件來實現,比如Outlook、Foxmail,但這種第三方軟件無法和開發者自己的系統整合,通過對MIME郵件格式的分析,我們可以在自己的應用程序中實現對MIME郵件所含信息的讀取。

1  MIME郵件格式分析

    MIME技術規範的完整內容由RFC 2045-2049定義,包括了信息格式、媒體類型、編碼方式等各方面的內容,這裏我們只介紹其中的一些關鍵的格式和規範,通過了解這些格式規範,我們就可以實現以編程的方式從MIME郵件中提取基本的郵件信息。

1.1 域

    MIME郵件的基本信息、格式信息、編碼方式等重要內容都記錄在郵件內的各種域中,域的基本格式:{域名}:{內容},域由域名後面跟“:”再加上域的信息內容構成,一條域在郵件中佔一行或者多行,域的首行左側不能有空白字符,比如空格或者製表符,佔用多行的域其後續行則必須以空白字符開頭。域的信息內容中還可以包含屬性,屬性之間以“;”分隔,屬性的格式如下:{屬性名稱}=”{屬性值}”。
    表1是一封示例郵件的內容,其中行1-5、行8都是單行的域,行6-7則是一個多行的域,並帶有一個名爲charset的屬性,屬性值爲us-ascii。
 
表1 示例電子郵件
行1 From: ”suntao” <[email protected]>
行2 To: <[email protected]>
行3 Subject: hello world
行4 Date: Mon, 9 Oct 2006 16:51:34 +0800
行5 MIME-Version: 1.0
行6 Content-Type: text/plain;
行7            charset="us-ascii"
行8 Date: Mon, 9 Oct 2006 16:48:25 +0800
行9
行10 Hello world
行11
    郵件規範中定義了大量域,分別用來存儲同郵件相關的各種信息,比如發件人的名字和郵件地址信息存儲在From域中,收件人的郵件地址信息存儲在To域中,開發人員可通過查詢RFC文檔得到完整的郵件域定義列表。

1.2  Content-Type域

    Content-Type域定義了郵件中所含各種內容的類型以及相關屬性。郵件所含的文本、超文本、附件等信息都按照對應Content-Type域所指定的媒體類型、存儲位置、編碼方式等信息存儲在郵件中。Content-Type域基本格式:Content-Type:{主類型}/{子類型}。
示例郵件中的行6-7就是一個Content-Type域,主類型爲text,子類型爲plain,字符集屬性爲us-ascii。
表2:MIME郵件中常見的主類型
主類型 常見屬性 參數含義
text charset 文本信息所使用的字符集
image name 圖像的名稱
application name 應用程序的名稱
multipart boundary 郵件分段邊界標識

1.3  multipart類型

    MIME郵件中各種不同類型的內容是分段存儲的,各個段的排列方式、位置信息都通過Content-Type域的multipart類型來定義。multipart類型主要有三種子類型:mixed、alternative、related。
1.3.1  multipart類型基本格式
    ●  multipart/mixed類型
    如果一封郵件中含有附件,那郵件的Content-Type域中必須定義multipart/mixed類型,郵件通過multipart/mixed類型中定義的boundary標識將附件內容同郵件其它內容分成不同的段。基本格式如下:
Content-Type: multipart/mixed;
                   boundary="{分段標識}"
    ●  multipart/alternative類型
    MIME郵件可以傳送超文本內容,但出於兼容性的考慮,一般在發送超文本格式內容的同時會同時發送一個純文本內容的副本,如果郵件中同時存在純文本和超文本內容,則郵件需要在Content-Type域中定義multipart/alternative類型,郵件通過其boundary中的分段標識將純文本、超文本和郵件的其它內容分成不同的段。基本格式如下:
Content-Type: multipart/alternative;
                   boundary="{分段標識}"
    ●  multipart/related類型
    MIME郵件中除了可以攜帶各種附件外,還可以將其它內容以內嵌資源的方式存儲在郵件中。比如我們在發送html格式的郵件內容時,可能使用圖像作爲html的背景,html文本會被存儲在alternative段中,而作爲背景的圖像則會存儲在multipart/related類型定義的段中。基本格式如下:
Content-Type: multipart/related;
                   type="multipart/alternative";
                   boundary="{分段標識}"
1.3.2  multipart類型的boundary屬性
    multipart的子類型中都定義了各自的boundary屬性,郵件使用這些boundary中定義的字符串作爲標識,將郵件內容分成不同的段,段體內的每個子段以“--”+boundary行開始,父段則以“--”+boundary+“--”行結束,不同段之間用空行分隔。
1.3.3  multipart類型的層次關係
表3:multipart子類型之間的層次關係
Multipart/mixed
Multipart/related
Multipart/alternative
純文本正文
超文本正文
內嵌資源
附件
    MIME郵件通過多個Content-Type域的multipart類型將內容分成不同的段,這些段在郵件中不是線形順序排列的,而是存在一個互相包含的層次關係,multipart子類型之間的層次關係結構如表3。

1.4  Content-Transfer-Encoding域

    MIME郵件可以傳送圖像、聲音、視頻以及附件,這些非ASCII碼的數據都是通過一定的編碼規則進行轉換後附着在郵件中進行傳遞的。編碼方式存儲在郵件的Content-Transfer-Encoding域中,一封郵件中可能有多個Content-Transfer-Encoding域,分別對應郵件不同部分內容的編碼方式。目前MIME郵件中的數據編碼普遍採用Base64編碼或Quoted-printable編碼來實現。
1.4.1  Base64編碼
    Base64編碼的目的是將輸入的數據全部轉換成由64個指定ASCII字符組成的字符序列, 這64個字符由{'A'-'Z', 'a'-'z', '0'-'9', '+', '/'}構成。編碼時將需要轉換的數據每次取出6bit,然後將其轉換成十進制數字,這個數字的範圍最小爲0,最大爲63,然後查詢{'A'-'Z', 'a'-'z', '0'-'9', '+', '/'}構成的字典表,輸出對應位置的ASCII碼字符,這樣每3個字節的數據內容會被轉換成4個字典中的ASCII碼字符,當轉換到數據末尾不足3個字節時,則用“=”來填充。
1.4.2  Quoted-printable編碼
    Quoted-printable編碼的目的也是將輸入的信息轉換成可打印的ASCII碼字符,但它是根據信息的內容來決定是否進行編碼,如果讀入的字節處於33-60、62-126範圍內的,這些都是可直接打印的ASCII字符,則直接輸出,如果不是,則將該字節分爲兩個4bit,每個用一個16進制數字來表示,然後在前面加“=”,這樣每個需要編碼的字節會被轉換成三個字符來表示。

2  MIME郵件信息提取

    從上面的分析可以看出,MIME郵件傳遞的實際是一個經過特殊編碼並以約定格式排列的字符序列,我們只需要提取存儲在郵件各種域中的格式、位置和編碼信息,按照根據這些信息從字符序列中提取出對應的字符內容並對其進行反向解碼,就可以得到我們需要的有關內容。
下面給出.Net環境下,利用C#結合正則表達式從郵件中提取相關信息的基本思路和部分代碼。

2.1 收件人/發件人/郵件主題的提取

    收件人、發件人、郵件主題是一封郵件的基本組成信息,分別存郵件的From域、To域、Subject域中。開發中只需要通過正則表達式來匹配這些指定的域,然後從匹配結果中取出相關信息即可。
    示例代碼:提取郵件主題
string emailContent = “……”;//emailContent中存儲的是郵件內容
pat = @"^Subject:\s*(?<title>.*)\s*\r\n";
myMatches = Regex.Matches(emailContent,pat,RegexOptions.Multiline);
foreach(Match nextMatch in myMatches)
{
         GroupCollection myGroup = nextMatch.Groups;
         string title = myGroup["title"].ToString();//title變量存儲From域的內容
         ……
}
    需要注意的是上面的代碼提取的是跟隨在Subject:後面的字符串,如果郵件的主題內容是中文或者其它需要編碼的地區文字,則還需要對其進行解碼。比如,如果郵件的Subject域中的信息是“你好”,那麼提取出來的字符串會是這種形式:=?gb2312?B?xOO6ww==?=,第一個?同第二個?之間的gb2312代表標題內容所使用的字符集,第二個?和第三個?之間的B代表這部分內容採用的是base64編碼方式,如果採用Quoted-printabel編碼方式則顯示Q,第三個?和第四個?之間則是“你好”經過base64編碼後的字符串。

2.2  multipart分段信息的提取

    郵件通過multipart類型將內容分隔成不同的段,各段之間的邊界標識由對應multipart類型的boundary屬性定義。要從郵件中提取出需要的內容,首先需要提取出郵件中的分段信息。下面的代碼從一封郵件中提取出所有的multipart類型的名稱和boundary屬性。
示例代碼:提取multipart信息
string emailContent = “……”;//emailContent中存儲的是郵件內容
string pat = @"\bContent-Type:\s*(?<type>\w+/\w+);\s+(type=\S(?<subtype>\S+)\S)?\s+boundary=""(?<flag>\S+)""";
MatchCollection myMatches = Regex.Matches(emailContent,pat);
foreach(Match nextMatch in myMatches)
{
         GroupCollection myGroup = nextMatch.Groups;
         string type = myGroup["type"].ToString();//type變量存儲multipart類型的名稱
         string flag = myGroup["flag"].ToString();//flag變量存儲multipart類型的boundary屬性
         ……
}

2.3  郵件附件的提取

    郵件中的附件信息由對應的Content-Type域、Content-Transfer-Encoding域、Content-Disposition域和multipart/mixed類型定義,前三個域定義附件的類型、名稱和編碼方式,multipart/mixed則定義附件同郵件其它內容的分段標識。基本格式如下:
--boundary分段標識
Content-Type: application/msword;
         name="readme.doc"
Content-Transfer-Encoding: base64
Content-Disposition: attachment;
         filename=" readme.doc "
……
文件內容的Base64編碼
……
--boundary分段標識
    示例代碼:提取郵件附件
//boundaryMixed代表已經提取出的multipart/mixed類型的boundary標識
//DecodeBase64爲自定義的base64解碼函數
//DecodeQuotedPrintable爲自定義的quoted-printable解碼函數
string emailContent = “……”;//emailContent中存儲的是郵件內容
string pat = @"\r\nContent-Type:\s*(?<filetype>\S*);\s*name=""(?<name>\S*)""\s*Content-Transfer-Encoding:\s*(?<encoding>\S*)\s*Content-Disposition:\s*attachment;\s*filename=""(?<filename>\S+)""\s+(?<content>[\S|\r\n]+)" + "--" + boundaryMixed;
MatchCollection myMatches = Regex.Matches(emailContent,pat,RegexOptions.Singleline);   
foreach(Match nextMatch in myMatches)
{
         //提取附件的類型、編碼方式、文件名、內容信息
         GroupCollection myGroup = nextMatch.Groups;
         string fileType = myGroup["filetype"].ToString();
         string encoding = myGroup["encoding"].ToString();
         string fileName = myGroup["filename"].ToString();
         string content = myGroup["content"].ToString().Trim();
         byte[] attachFile;
         //根據附件的編碼方式對提取出的附件內容進行解碼
         if(encoding == “base64”)
         {
                   attachFile = DecodeBase64 (content);
}
if(encoding == “quoted-printable”)
{
                   attachFile = DecodeQuotedPrintable (content);
}
//將解碼後的內容寫入磁盤
         FileStream fs = new FileStream("c:\\" + fileName,
FileMode.CreateNew);
         BinaryWriter bw = new BinaryWriter(fs);
         bw.Write(attachFile);
         bw.Close();
         fs.Close();
}
    上面的程序從郵件原文中提取出附件信息,並根據附件採用的編碼類型進行解碼,然後將解碼後的內容按照原文件名存儲到C盤根目錄。同樣,如果附件的文件名是中文或者其它需要編碼的文字,則首先需要對文件名進行解碼。

3  總結

    本文對MIME郵件的基本格式做了分析和闡述,介紹了MIME中幾個重要的規範和定義,並給出了利用正則表達式從郵件內容中提取相關信息的基本思路和方法。在開發中需要注意的是,郵件中所含的內容決定了郵件的具體格式,multipart類型以及對應的分段標識只有在有相關內容的時候纔會在郵件中出現,在開發時需要具體分析。MIME的詳細技術規範可以查詢RFC的相關文檔

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章