MIME詳解

MIME,英文全稱爲"Multipurpose Internet Mail Extensions",即多用途互聯網郵件擴展,是目前互聯網電子郵件廣泛遵循的郵件技術規範。在MIME出現以前,互聯網電子郵件主要遵循由RFC 822所制定的標準,電子郵件通常只用來傳遞基本的ASCII碼文本信息,MIME在 RFC 822的基礎上對電子郵件規範作了大量的擴展,引入了新的格式規範和編碼方式,在MIME的支持下,圖像、聲音、動畫等二進制文件均可方便的經過電子郵件來進行傳遞,極大地豐富了電子郵件的功能。目前互聯網上使用的基本都是遵循MIME規範的電子郵件。 html

    電子郵件的分析和讀取通常都經過專用的郵件軟件來實現,好比Outlook、Foxmail,但這種第三方軟件沒法和開發者本身的系統整合,經過對MIME郵件格式的分析,咱們能夠在本身的應用程序中實現對MIME郵件所含信息的讀取。 正則表達式

1  MIME郵件格式分析 編程

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

1.1 函數

    MIME郵件的基本信息、格式信息、編碼方式等重要內容都記錄在郵件內的各類域中,域的基本格式:{域名}:{內容},域由域名後面跟":"再加上域的信息內容構成,一條域在郵件中佔一行或者多行,域的首行左側不能有空白字符,好比空格或者製表符,佔用多行的域其後續行則必須以空白字符開頭。域的信息內容中還能夠包含屬性,屬性之間以";"分隔,屬性的格式以下:{屬性名稱}="{屬性值}"。 動畫

    表1是一封示例郵件的內容,其中行1-五、行8都是單行的域,行6-7則是一個多行的域,並帶有一個名爲charset的屬性,屬性值爲us-ascii。 編碼

   

表1 示例電子郵件 spa

行1 From: "suntao" <suntao@fimmu.com> .net

行2 To: <yxj@fimmu.com> code

行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的相關文檔

相關文章
相關標籤/搜索