【转自】用C#访问Hotmail
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"?>
<D:propfind xmlns:D="DAV:" xmlns:h="http://schemas.microsoft.com/hotmail/"
xmlns:hm="urn:schemas:httpmail:">
<D:prop>
<h:adbar/>
<hm:contacts/>
<hm:inbox/>
<hm:outbox/>
<hm:sendmsg/>
<hm:sentitems/>
<hm:deleteditems/>
<hm:drafts/>
<hm:msgfolderroot/>
<h:maxpoll/>
<h:sig/>
</D:prop>
</D:propfind>
通过XMLHTTP发送第一个请求时,首先指定WebDAV服务器URL,然后生成XML请求的内容:
// 指定服务器的URL
string serverUrl = "http://services.msn.com/svcs/hotmail/httpmail.asp";
// 构造查询
string folderQuery = null;
folderQuery += "<?xml version='1.0'?><D:propfind xmlns:D='DAV:' ";
folderQuery += "xmlns:h='http://schemas.microsoft.com/hotmail/' ";
folderQuery += "xmlns:hm='urn:schemas:httpmail:'><D:prop><h:adbar/>";
folderQuery += "<hm:contacts/><hm:inbox/><hm:outbox/><hm:sendmsg/>";
folderQuery += "<hm:sentitems/><hm:deleteditems/><hm:drafts/>";
folderQuery += "<hm:msgfolderroot/><h:maxpoll/><h:sig/></D:prop></D:propfind>";
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"?>
<D:response>
...
<D:propstat>
<D:prop>
<h:adbar>AdPane=Off*...</h:adbar>
<hm:contacts>http://law15.oe.hotmail.com/...</hm:contacts>
<hm:inbox>http://law15.oe.hotmail.com/...</hm:inbox>
<hm:sendmsg>http://law15.oe.hotmail.com/...</hm:sendmsg>
<hm:sentitems>http://law15.oe.hotmail.com/...</hm:sentitems>
<hm:deleteditems>http://law15.oe.hotmail.com/...</hm:deleteditems>
<hm:msgfolderroot>http://law15.oe.hotmail.com/...</hm:msgfolderroot>
...
</D:prop>
</D:response>
</D:multistatus>
在本文的控制台示例程序中,我们感兴趣的两个文件夹是收件箱和发件箱的文件夹,它们分别用于接收和发送邮件。
在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"?>
<D:propfind xmlns:D="DAV:" xmlns:hm="urn:schemas:httpmail:" xmlns:m="
urn:schemas:mailheader:">
<D:prop>
<D:isfolder/>
<hm:read/>
<m:hasattachment/>
<m:to/>
<m:from/>
<m:subject/>
<m:date/>
<D:getcontentlength/>
</D:prop>
</D:propfind>
生成上述XML查询字符串的C#代码:
// 构造查询
string getMailQuery = null;
getMailQuery += "<?xml version='1.0'?><D:propfind xmlns:D='DAV:' ";
getMailQuery += "xmlns:hm='urn:schemas:httpmail:' ";
getMailQuery += "xmlns:m='urn:schemas:mailheader:'><D:prop><D:isfolder/>";
getMailQuery += "<hm:read/><m:hasattachment/><m:to/><m:from/><m:subject/>";
getMailQuery += "<m:date/><D:getcontentlength/></D:prop></D:propfind>";
就象前面获取信箱文件夹清单的方式一样,上述请求也通过XMLHTTP用PROPFIND方法发送,这次我们把请求的正文设置成查询字符串。由于当前会话的用户身份已经通过验证,所以XMLHTTP open()调用中不必再提供用户名字和密码:
// 获取邮件信息
xmlHttp_.open("PROPFIND", folderUrl, false, null, null);
xmlHttp_.send(getMailQuery);
string folderInfo = xmlHttp_.responseText;
如果请求成功,服务器返回的应答XML流包含了该文件夹中各个邮件的信息:
<D:multistatus>
<D:response>
<D:href>
http://sea1.oe.hotmail.com/cgi-bin/hmdata/...
</D:href>
<D:propstat>
<D:prop>
<hm:read>1</hm:read>
<m:to/>
<m:from>Mark Anderson</m:from>
<m:subject>RE: New Information</m:subject>
<m:date>2002-08-06T16:38:39</m:date>
<D:getcontentlength>1238</D:getcontentlength>
</D:prop>
<D:status>HTTP/1.1 200 OK</D:status>
</D:propstat>
</D:response>
...
观察服务器返回的应答,我们发现每一个节点包含一组标识邮件的域,例如通过标记可提取出邮件。下面我们再次使用System.XML.XmlTextReader解析这个XML数据流,首先初始化流读取器:
MailItem mailItem = null;
// 装入XML
StringReader reader = new StringReader(folderInfo);
XmlTextReader xml = new XmlTextReader(reader);
四、分析邮件基本信息
为了遍历一次就解析好整个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>
/// 下载MailItem指定的邮件
/// </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>
/// 发送一个邮件
/// </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:<" + from + ">"r"n";
postBody += "RCPT TO:<" + to + ">"r"n";
postBody += ""r"n";
postBody += "From: " + quote + fromName + quote + " <" + from + ">"r"n";
postBody += "To: <" + 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)。 )
--------------------------------------------------------------------------------------------------------------------------
【转自】 C#访问hotmail邮件 (csdn)
如何使用程序来访问HOTMAIL的邮件
using System;2
using System.Diagnostics;3
using System.IO;4
using System.Net;5
using System.Text;6
using System.Xml;7
using System.Web;8

9
namespace Hotmail10
{11
/// <summary>12
/// HotmailProxy is able to speak HttpMail to various13
/// Hotmail servers across the world.14
/// </summary>15
public class HotmailProxy16
{17
/// <summary>18
/// Storage area for cookies.19
/// </summary>20
private CookieContainer ccContainer;21

22
/// <summary>23
/// Creates a new instance of HotmailHttp.24
/// </summary>25
public HotmailProxy()26
{27
Trace.WriteLine("Creating new instance.");28
ccContainer = new CookieContainer();29
}30

31
/// <summary>32
/// Sends the given request to the given destination33
/// and returns the answer from the server.34
/// </summary>35
/// <param name="request">The request to send.</param>36
/// <param name="destination">The destination to send the request to.</param>37
/// <returns>The answer from the remote host.</returns>38
public string SendRequest(string request, Uri destination)39
{40
// pass along41
return SendRequest(request,destination,null);42
}43

44
/// <summary>45
/// Sends the given request to the given destination46
/// and returns the answer from the server.47
/// </summary>48
/// <param name="request">The request to send.</param>49
/// <param name="destination">The destination to send the request to.</param>50
/// <param name="credential">The network credentials to use with the request.</param>51
/// <returns>The answer from the remote host.</returns>52
public string SendRequest(string request, Uri destination, NetworkCredential credential)53
{54
// Verify input55
if(request == null || request.Trim().Length == 0)56
throw new ArgumentNullException("request");57
else if (destination == null)58
throw new ArgumentNullException("destination");59
else60
{61
// Get byte[] and send the request using private method.62
byte[] xmlBytes = Encoding.ASCII.GetBytes(request);63
return SendRequestTo(xmlBytes,destination, credential);64
}65
}66

67
/// <summary>68
/// Sends the given byte[] to the given remote host using69
/// authentication with the supplied credentials.70
/// </summary>71
/// <param name="requestBytes"></param>72
/// <param name="destination"></param>73
/// <param name="credential"></param>74
/// <returns></returns>75
private string SendRequestTo(byte[] requestBytes, Uri destination, NetworkCredential credential)76
{77
Trace.WriteLine("Sending request to url:" + destination.AbsoluteUri);78

79
// Build the request.80
HttpWebRequest webRequest = (HttpWebRequest)WebRequest.Create(destination);81
webRequest.Method = "PROPFIND";82
webRequest.Accept = "*/*";83
webRequest.AllowAutoRedirect = false;84
webRequest.UserAgent = "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0; .NET CLR v1.1.4322)";85
webRequest.CookieContainer = new CookieContainer();86
webRequest.ContentLength = requestBytes.Length;87
webRequest.ContentType = "text/xml";88
// Set the credentials89
webRequest.Credentials = credential;90
// Add cookies for this request91
webRequest.CookieContainer.Add(ccContainer.GetCookies(destination));92

93
try94
{95
// Write the request96
Stream reqStream = webRequest.GetRequestStream();97
reqStream.Write(requestBytes,0,requestBytes.Length);98
reqStream.Close();99
// Get a response100
HttpWebResponse webResponse = (HttpWebResponse)webRequest.GetResponse();101
if (webRequest.HaveResponse)102
{103
// Handle returned cookies104
foreach(Cookie retCookie in webResponse.Cookies)105
{106
bool cookieFound = false;107
108
foreach(Cookie oldCookie in ccContainer.GetCookies(destination))109
{110
if (retCookie.Name.Equals(oldCookie.Name))111
{112
oldCookie.Value = retCookie.Value;113
cookieFound = true;114
}115
}116
if (!cookieFound)117
ccContainer.Add(retCookie);118
119
} 120
// Handle redirection headers121
if ((webResponse.StatusCode == HttpStatusCode.Found) || 122
(webResponse.StatusCode == HttpStatusCode.Redirect) ||123
(webResponse.StatusCode == HttpStatusCode.Moved) ||124
(webResponse.StatusCode == HttpStatusCode.MovedPermanently))125
{ 126
WebHeaderCollection headers = webResponse.Headers;127
return SendRequestTo(requestBytes,new Uri(headers["location"]),credential); 128
}129
// Read response130
StreamReader stream = new StreamReader(webResponse.GetResponseStream());131
string responseString = stream.ReadToEnd();132
stream.Close();133
134
return responseString;135
}136
// No response137
throw new ApplicationException("No response received from host.");138
}139
catch(WebException e)140
{141
// Error occured142
Trace.WriteLine("Exception occured " + e.Message);143
throw new ApplicationException("Exception occured while sending request."+e.Message,e);144
}145
146
}147
}148
}149
using System;150
using System.Diagnostics;151
using System.Net;152
using System.Xml;153

154
namespace Hotmail155
{156
public class HotmailClient157
{158
Class Instances and Properties168

169
Public Interface( Constructor, Connect, Disconnect, RetrieveFilledMailBox)227

228
Private Interface( ParseConnectResponse, ParseFillResponse, RetrieveMailboxes, CreateNamespaceManager)352

353
}354
}355

356

357
程序进入点358

359
private void button1_Click(object sender, System.EventArgs e)360
{361
HotmailClient client=new HotmailClient();362
client.Connect("xxxx@hotmail.com","urPassword");363
}


浙公网安备 33010602011771号