用C#訪問Hotmail

iteye_20683發表於2009-12-17

出處:仙人掌工作室 作者:仙人掌工作室 時間:2005-9-13 8:52:00
263企業郵箱,註冊即可免費試用

POP郵件協議的優點在於它是一個開放的標準,有著完善的文件,這就使得編寫POP郵件客戶程式不那麼困難,只要掌握了POP、SMTP的基礎知識,就可以寫出代理程式來執行各種任務,例如過濾廣告和垃圾郵件,或提供e-mail自動應答服務。
Hotmail是世界上影響最廣的Web郵件系統,遺憾的是,當我們要為Hotmail編寫獨立的客戶程式(不通過瀏覽器訪問的客戶程式)時,馬上就會遇到Hotmail不提供POP閘道器這一障礙。
雖然Hotmail不提供POP支援,但瀏覽器並非訪問Hotmail的唯一途徑。例如,利用Outlook Express可以直接連線到標準的Hotmail或MSN信箱,提取、刪除、移動或傳送郵件。利用HTTP包監視器,我們可以監視到Outlook Express和Hotmail的通訊過程,分析出客戶程式如何連線到Hotmail信箱。
Outlook Express利用了一種通常稱為HTTPMail的未公開的協議,藉助一組HTTP/1.1擴充套件訪問Hotmail。本文將介紹HTTPMail的一些特點以及利用C#客戶程式訪問Hotmail的過程。本文的示例程式利用COM互操作將XMLHTTP用作一種傳輸服務。XMLHTTP元件提供了一個完善的HTTP實現,除了包括認證功能,還能夠在傳送HTTP請求給伺服器之前設定定製的HTTP頭。


一、連線HTTPMail閘道器
Hotmail信箱預設的HTTPMail閘道器在http://services.msn.com/svcs/hotmail/httpmail.asp。HTTPMail協議實際上是一個標準的WebDAV服務,只不過尚未公開而已。在編寫C#程式時,我們可以方便地呼叫.NET框架在System.Net名稱空間中提供的各個TCP和HTTP類。另外,由於我們要操作WebDAV,在C#環境下利用XMLHTTP連線Hotmail最為簡便,只需引用一下MSXML2元件就可以直接訪問。注意在本文的程式碼片斷中,帶有下滑線字尾的變數是示例程式碼中宣告的成員域:
// 獲得名稱空間
using MSXML2;
...
// 建立物件
xmlHttp_ = new XMLHTTP();
為了連線到安全伺服器,WebDAV協議要求執行HTTP/1.1驗證。HTTPMail客戶程式發出的第一個請求利用WebDAV PROPFIND方法查詢一組屬性,其中包括Hotmail廣告條的URL以及信箱資料夾的位置:
<?xml version="1.0"?>
<propfind xmlns:d="DAV:" xmlns:h="http://schemas.microsoft.com/hotmail/"></propfind>xmlns:hm="urn:schemas:httpmail:">
<prop><br><adbar></adbar><br><contacts></contacts><br><inbox></inbox><br><outbox></outbox><br><sendmsg></sendmsg><br><sentitems></sentitems><br><deleteditems></deleteditems><br><drafts></drafts><br><msgfolderroot></msgfolderroot><br><maxpoll></maxpoll><br><sig></sig><br></prop>

通過XMLHTTP傳送第一個請求時,首先指定WebDAV伺服器URL,然後生成XML請求的內容:
// 指定伺服器的URL
string serverUrl = "http://services.msn.com/svcs/hotmail/httpmail.asp";
// 構造查詢
string folderQuery = null;
folderQuery += "<?xml version='1.0'?><propfind xmlns:d="DAV:"></propfind> folderQuery += "xmlns:h='http://schemas.microsoft.com/hotmail/' ";
folderQuery += "xmlns:hm='urn:schemas:httpmail:'><prop><adbar></adbar>"; <br> folderQuery += "<contacts></contacts><inbox></inbox><outbox></outbox><sendmsg></sendmsg>"; <br> folderQuery += "<sentitems></sentitems><deleteditems></deleteditems><drafts></drafts>"; <br> folderQuery += "<msgfolderroot></msgfolderroot><maxpoll></maxpoll><sig></sig></prop>";
XMLHTTP元件提供了一個open()方法來建立與HTTP伺服器的連線:
void open(string method, string url, bool async, string user, string password);
open()方法的第一個引數指定了用來開啟連線的HTTP方法,例如GET、POST、PUT或PROPFIND,通過這些HTTP方法我們可以提取資料夾資訊、收集郵件或傳送新郵件。為連線到Hotmail閘道器,我們指定用PROPFIND方法來查詢信箱。注意open()方法允許執行非同步呼叫(預設啟用),對於帶圖形使用者介面的郵件客戶程式來說,非同步呼叫是最理想的呼叫方式。由於本文的示例程式是一個控制檯應用,我們把這個引數設定成false。
為了執行身份驗證,我們在open()方法中指定了使用者名稱字和密碼。在使用XMLHTTP元件時,如果open()方法沒有提供使用者名稱字和密碼引數,但網站要求執行身份驗證,XMLHTTP將顯示出一個登入視窗。為了開啟通向Hotmail閘道器的連線,我們把PROPFIND請求的頭設定成XML查詢的內容,訊息的正文保持空白,然後傳送訊息:
// 開啟一個通向Hotmail伺服器的連線
xmlHttp_.open("PROPFIND", serverUrl, false, username, password);
// 傳送請求
xmlHttp_.setRequestHeader("PROPFIND", folderQuery);
xmlHttp_.send(null);


二、分析信箱的資料夾列表
傳送給services.msn.com的請求通常要經歷幾次重定向,經過伺服器端的負載平衡處理,最後請求會被傳遞到一個空閒的Hotmail伺服器,並執行身份驗證。在客戶端,這個重定向、執行身份驗證的互動過程由XMLHTTP元件負責處理。成功建立連線後,伺服器還會要求設定一些Cookie、驗證當前會話的合法性,這部分工作同樣也由XMLHTTP元件自動處理。初始的連線請求發出之後,伺服器將返回一個XML格式的應答:
// 獲得應答的內容
string folderList = xmlHttp_.responseText;
伺服器返回的應答包含許多有用的資訊,其中包括信箱中資料夾的URL位置,下面是一個例子:
<?xml version="1.0" encoding="Windows-1252"?>
<response><br> ... <br><propstat><br><prop><br><adbar>AdPane=Off*...</adbar><br><contacts>http://law15.oe.hotmail.com/...</contacts><br><inbox>http://law15.oe.hotmail.com/...</inbox><br><sendmsg>http://law15.oe.hotmail.com/...</sendmsg><br><sentitems>http://law15.oe.hotmail.com/...</sentitems><br><deleteditems>http://law15.oe.hotmail.com/...</deleteditems><br><msgfolderroot>http://law15.oe.hotmail.com/...</msgfolderroot><br> ... <br></prop><br></propstat></response>

在本文的控制檯示例程式中,我們感興趣的兩個資料夾是收件箱和發件箱的資料夾,它們分別用於接收和傳送郵件。
在C#環境中解析XML的方法很多,由於我們肯定程式碼涉及的所有XML文件總是合法的,所以可以利用System.XML.XmlTextReader速度快的優勢。XmlTextReader是一個“只向前”的讀取器,下面把XML字元資料轉換成字元流,初始化XML讀取器:
// 初始化
inboxUrl_ = null;
sendUrl_ = null;
// 裝入XML
StringReader reader = new StringReader(folderList);
XmlTextReader xml = new XmlTextReader(reader);
遍歷各個節點,選取出hm:inbox和hm:sendmsg節點,這兩個節點分別代表收件箱和發件箱:
// 讀取XML資料
while(xml.Read())
{
// 是一個XML元素?
if(xml.NodeType == XmlNodeType.Element)
{
// 獲取該節點
string name = xml.Name;
// 該節點代表收件箱?
if(name == "hm:inbox")
{
// 儲存收件箱URL
xml.Read();
inboxUrl_ = xml.Value;
}
// 該節點代表發件箱?
if(name == "hm:sendmsg")
{
// 儲存發件箱URL
xml.Read();
sendUrl_ = xml.Value;
}
}
}
只有先獲取當前這次會話的合法的收件箱和發件箱URL,才可以傳送和接收郵件。


三、列舉資料夾內容
得到了信箱資料夾(如收件箱)的URL之後,就可以向該資料夾的URL傳送WebDAV請求列舉其內容。示例程式定義了一個託管型別MailItem,用來儲存資料夾裡一項內容(即一個郵件)的資訊。資料夾內容列舉從初始化一個MailItems陣列開始:
// 初始化
ArrayList mailItems = new ArrayList();
為獲得郵件主題、收件人地址、發件人地址之類的郵件基本資訊,我們要用到下面XML格式的WebDAV查詢:
<?xml version="1.0"?>
<propfind xmlns:d="DAV:" xmlns:hm="urn:schemas:httpmail:" xmlns:m=" &lt;br /&gt; urn:schemas:mailheader:"><br><prop><br><isfolder></isfolder><br><read></read><br><hasattachment></hasattachment><br><to></to><br><from></from><br><subject></subject><br><date></date><br><getcontentlength></getcontentlength><br></prop><br></propfind>
生成上述XML查詢字串的C#程式碼:
// 構造查詢
string getMailQuery = null;
getMailQuery += "<?xml version='1.0'?><propfind xmlns:d="DAV:"></propfind> getMailQuery += "xmlns:hm='urn:schemas:httpmail:' ";
getMailQuery += "xmlns:m='urn:schemas:mailheader:'><prop><isfolder></isfolder>"; <br> getMailQuery += "<read></read><hasattachment></hasattachment><to></to><from></from><subject></subject>"; <br> getMailQuery += "<date></date><getcontentlength></getcontentlength></prop>";
就象前面獲取信箱資料夾清單的方式一樣,上述請求也通過XMLHTTP用PROPFIND方法傳送,這次我們把請求的正文設定成查詢字串。由於當前會話的使用者身份已經通過驗證,所以XMLHTTP open()呼叫中不必再提供使用者名稱字和密碼:
// 獲取郵件資訊
xmlHttp_.open("PROPFIND", folderUrl, false, null, null);
xmlHttp_.send(getMailQuery);
string folderInfo = xmlHttp_.responseText;
如果請求成功,伺服器返回的應答XML流包含了該資料夾中各個郵件的資訊:
<multistatus><br><response><br><href><br> http://sea1.oe.hotmail.com/cgi-bin/hmdata/... <br></href><br><propstat><br><prop><br><read>1</read><br><to></to><br><from>Mark Anderson</from><br><subject>RE: New Information</subject><br><date>2002-08-06T16:38:39</date><br><getcontentlength>1238</getcontentlength><br></prop><br><status>HTTP/1.1 200 OK</status><br></propstat><br></response><br> ... <br> 觀察伺服器返回的應答,我們發現每一個節點包含一組標識郵件的域,例如通過標記可提取出郵件。下面我們再次使用System.XML.XmlTextReader解析這個XML資料流,首先初始化流讀取器: <br>MailItem mailItem = null; <br> // 裝入XML <br> StringReader reader = new StringReader(folderInfo); <br> XmlTextReader xml = new XmlTextReader(reader);</multistatus>


四、分析郵件基本資訊
為了遍歷一次就解析好整個XML文件,我們在每次開啟元素時就建立一個新的MailItem例項,一遇到標記的末尾就儲存該例項,在此期間,我們提取並設定MailItem的域:
// 讀取XML資料
while(xml.Read())
{
string name = xml.Name;
XmlNodeType nodeType = xml.NodeType;
// 是一個email?
if(name == "D:response")
{
// 開始?
if(nodeType == XmlNodeType.Element)
{
// 建立一個新的MailItem
mailItem = new MailItem();
}
// 結束?
if(nodeType == XmlNodeType.EndElement)
{
// 儲存email
mailItems.Add(mailItem);
// 清除變數
mailItem = null;
}
}
// 是一個元素?
if(nodeType == XmlNodeType.Element)
{
// 郵件的URL屬性
if(name == "D:href")
{
// 繼續讀取
xml.Read();
mailItem.Url = xml.Value;
}
// 郵件的“已閱讀”屬性
if(name == "hm:read")
{
// 繼續讀取
xml.Read();
mailItem.IsRead = (xml.Value == "1");
}
// 其他MailItem的屬性...
}
}
上面的程式碼列舉指定資料夾內的每一個MailItem,分別提取各個MailItem的下列屬性:
XML節點 說明
D:href 用來提取郵件的URL
hm:read 如果郵件已閱讀,則該標記被設定
m:to 收件人
m:from 發件人
m:subject 郵件主題
m:date 時間標記
D:getcontentlength 郵件的大小(位元組數)


五、接收郵件
列舉出資料夾裡面的MailItem之後,我們就可以利用MailItem的URL獲得郵件本身,只需要向Hotmail伺服器傳送一個HTTP/1.1 GET請求就可以了。示例程式碼中的LoadMail()函式輸入一個MailItem例項作為引數,返回郵件的內容:
/// <summary><br> /// 下載MailItem指定的郵件 <br> /// </summary>
public string LoadMail(MailItem mailItem)
{
// 郵件的URL
string mailUrl = mailItem.Url;
// 開啟Hotmail伺服器連線
xmlHttp_.open("GET", mailUrl, false, null, null);
// 傳送請求
xmlHttp_.send(null);
// 獲取應答
string mailData = xmlHttp_.responseText;
// 返回郵件資料
return mailData;
}
六、傳送郵件
LoadMail()方法通過傳送HTTP/1.1 GET請求獲取郵件,類似地,用Hotmail發件箱傳送郵件時我們提交一個POST請求,如下面的SendMail()方法所示。
/// <summary><br> /// 傳送一個郵件 <br> /// </summary>
public void SendMail(string from, string fromName,
string to, string subject, string body)
{
...
}
首先準備好後面要用到的引號字元以及郵件的時間標記:
// 引號字元
string quote = "/u0022";
// 時間標記
DateTime now = DateTime.Now;
string timeStamp = now.ToString("ddd, dd MMM yyyy hh:mm:ss");
HTTPMail協議採用與SMTP相似的通訊模式。Outlook Express用MIME格式傳送郵件,但為簡單計,本例我們只傳送純文字的郵件:
// 構造POST請求的內容
string postBody = null;
// 郵件頭.
postBody += "MAIL FROM:/r/n";
postBody += "RCPT TO:/r/n";
postBody += "/r/n";
postBody += "From: " + quote + fromName + quote + " /r/n";
postBody += "To: /r/n";
postBody += "Subject: " + subject +"/r/n";
postBody += "Date: " + timeStamp + " -0000/n";
postBody += "/r/n";
// 郵件正文
postBody += body;
傳送郵件時,我們要把Content-Type請求頭設定成message/rfc821,表示這個請求包含一個遵從RFC821的訊息。最後要做的就是把郵件傳送到伺服器:
// 開啟連線
xmlHttp_.open("POST", sendUrl_, false, null, null);
// 傳送請求
xmlHttp_.setRequestHeader("Content-Type", "message/rfc821");
xmlHttp_.send(postBody);
只要目標地址正確無誤,Hotmail就會把郵件傳送到目的地。
結束語:
Hotmail是世界上最大的免費Web郵件提供商。但是,Hotmail使用的HTTPMail協議是非公開的,從而為編寫直接訪問Hotmail的客戶程式帶來了困難。本文示範瞭如何在C#環境中利用XMLHTTP元件直接連線到Hotmail,以及如何傳送和接收郵件,證明了通過HTTPMail連線Hotmail可以做到象使用POP3、IMAP4、SMTP等協議一樣簡單。 (下載本文的完整程式碼: HotmailCSharp_code.zip(8K)。 )

相關文章