使用 C# 访问 Hotmail – .NET 下的 HTTPMail 客户端






4.83/5 (59投票s)
2003年3月26日
8分钟阅读

573040

2349
一个使用未公开的 HTTPMail 协议访问 Hotmail 的 C# 客户端库。
- 下载 示例 C# 源代码 - 7 Kb
引言
POP 邮件协议的好处在于它是一个文档齐全的开放标准,这使得编写一个邮件客户端来从 POP 邮箱收集邮件的过程相对轻松。掌握了 POP 或 SMTP 的基本知识,就有可能编写代理服务器来执行各种有用的任务,例如过滤垃圾邮件或提供电子邮件自动回复服务。不幸的是,在尝试编写一个独立的 Hotmail(世界上最受欢迎的基于 Web 的邮件系统)客户端时,没有 POP 网关的存在很快就成了一个问题。
尽管缺乏 POP 支持,但无需使用 Web 浏览器也能连接到 Hotmail。Outlook Express 允许用户检索、删除、移动和发送消息,直接连接到标准的 Hotmail 或 MSN 邮箱。通过使用 HTTP 数据包嗅探器,可以监视 Outlook Express 和 Hotmail 之间的通信,从而确定直接邮箱连接是如何建立的。
Outlook Express 使用一种通常被称为 HTTPMail 的未公开协议,允许客户端使用一组 HTTP/1.1 扩展来访问 Hotmail。本文将介绍 HTTPMail 的一些特性,以及如何最好地使用 C# 客户端连接到 Hotmail。本文附带的示例源代码使用 COM 互操作来利用 XMLHTTP 作为传输服务。XMLHTTP 组件提供了完整的 HTTP 实现,包括身份验证以及在发送 HTTP 请求到服务器之前设置自定义标头的能力。
连接到 HTTPMail Hotmail 网关
Hotmail 邮箱的默认 HTTPMail 网关位于 http://services.msn.com/svcs/hotmail/httpmail.asp。尽管 HTTPMail 协议未公开,但它实际上是一个标准的 WebDAV 服务。由于我们使用的是 C#,可以使用 .NET 框架在 System.Net
命名空间中提供的 TCP 和 HTTP 类。由于我们处理的是 WebDAV,使用 XMLHTTP 连接到 C# 下的 Hotmail 会更简单。引用 MSXML2 组件,它提供了一个可以被直接访问的互操作程序集。请注意,在本文中的代码片段中,带有下划线的变量是指在示例代码的其他地方声明的成员字段。
// Get the namespace.
using MSXML2;
...
// Create the object.
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 请求。
// Specify the server URL.
string serverUrl = "http://services.msn.com/svcs/hotmail/httpmail.asp";
// Build the query.
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>";
HTTPXML 组件提供了一个 open()
方法,用于建立与 HTTP 服务器的连接。
void open(string method, string url, bool async, string user, string password);
第一个参数指定用于打开连接的 HTTP 方法,例如 GET
、POST
、PUT
或 PROPFIND
。要连接到 Hotmail 网关,我们指定 PROPFIND
方法来查询邮箱。此方法和其他 HTTP 方法用于检索文件夹信息、收集邮件项和发送新邮件。请注意,open()
方法支持异步调用(默认启用),这对于图形界面邮件客户端来说是首选。由于示例代码是一个控制台应用程序,我们将此参数设置为 false。对于身份验证,我们指定用户名和密码。请注意,在 XMLHTTP 下,如果缺少这些参数且网站需要身份验证,该组件将显示登录窗口。要连接到 Hotmail 网关,我们打开连接,将 PROPFIND
请求标头设置为我们的基于 XML 的查询,然后发送带有空主体的请求。
// Open a connection to the Hotmail server.
xmlHttp_.open("PROPFIND", serverUrl, false, username, password);
// Send the request.
xmlHttp_.setRequestHeader("PROPFIND", folderQuery);
xmlHttp_.send(null);
解析邮箱文件夹列表
发送到 services.msn.com 的请求通常会重定向几次。在负载均衡后,我们最终连接到一个空闲的 Hotmail 服务器并完成身份验证。XMLHTTP 组件会处理这种重定向和适当的身份验证。连接后,服务器还会要求我们设置各种适用于当前会话的 Cookie(同样,所有这些都由 XMLHTTP 自动处理)。在发送初始连接请求后,服务器将返回一个基于 XML 的响应。
// Get the response.
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
提供了快速的、仅向前访问。我们通过将 XML 字符串数据转换为字符串流来初始化 XML 读取器。
// Initiate.
inboxUrl_ = null;
sendUrl_ = null;
// Load the Xml.
StringReader reader = new StringReader(folderList);
XmlTextReader xml = new XmlTextReader(reader);
通过遍历每个节点来解析 XML,提取 hm:inbox
和 hm:sendmsg
节点。
// Read the Xml.
while(xml.Read())
{
// Got an element?
if(xml.NodeType == XmlNodeType.Element)
{
// Get this node.
string name = xml.Name;
// Got the inbox?
if(name == "hm:inbox")
{
// Set folder.
xml.Read();
inboxUrl_ = xml.Value;
}
// Got the send message page?
if(name == "hm:sendmsg")
{
// Set folder.
xml.Read();
sendUrl_ = xml.Value;
}
}
}
一旦确定了该会话有效的收件箱和发件箱的 URL,就可以发送和检索电子邮件。
枚举文件夹邮件项
给定一个邮箱文件夹的 URL(例如收件箱文件夹),我们可以向该文件夹的 URL 发送一个 WebDAV 请求,以列出该文件夹内的邮件项。示例控制台应用程序定义了一个托管类型 MailItem
,用于存储文件夹项的邮件信息。文件夹枚举开始时,会初始化一个 MailItems
数组。
// Initiate.
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>
以下 C# 代码生成 XML 查询字符串:
// Build the query.
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()
调用中再次提供用户名和密码。
// Get the mail info.
xmlHttp_.open("PROPFIND", folderUrl, false, null, null);
xmlHttp_.send(getMailQuery);
string folderInfo = xmlHttp_.responseText;
在成功请求后,服务器将响应一个 XML 流,其中包含文件夹中每个 MailItem
的信息。
<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>
...
查看上面的 XML 片段,我们发现每个 <D:response>
节点内都包含一组标识 MailItem
的字段,包括 <D:href>
标签,该标签稍后将允许我们检索该项。我们也可以使用 System.XML.XmlTextReader
来解析此 XML 文本流。我们首先初始化流读取器。
// Holders.
MailItem mailItem = null;
// Load the Xml.
StringReader reader = new StringReader(folderInfo);
XmlTextReader xml = new XmlTextReader(reader);
解析文件夹信息
为了在单次传递中解析 XML,我们在打开 <D:response>
元素时创建一个新的 MailItem
实例,并在到达标签末尾时存储该实例。在此期间,我们提取并设置 MailItem
字段。
// Read the Xml.
while(xml.Read())
{
// Sections.
string name = xml.Name;
XmlNodeType nodeType = xml.NodeType;
// E-mail?
if(name == "D:response")
{
// Start?
if(nodeType == XmlNodeType.Element)
{
// Create a new mail item.
mailItem = new MailItem();
}
// End?
if(nodeType == XmlNodeType.EndElement)
{
// Store the last mail.
mailItems.Add(mailItem);
// Reset.
mailItem = null;
}
}
// Got an element?
if(nodeType == XmlNodeType.Element)
{
// Mail field.
if(name == "D:href")
{
// Load.
xml.Read();
mailItem.Url = xml.Value;
}
// Mail field.
if(name == "hm:read")
{
// Load.
xml.Read();
mailItem.IsRead = (xml.Value == "1");
}
// Load other MailItem fields, etc...
}
}
上面的代码(示例控制台应用程序中的一部分)枚举了给定文件夹中找到的 MailItems
。对于每个 MailItem
,我们提取以下字段:
XML 节点 | 描述 |
D:href | 用于检索此 HttpMail 项的 URL。 |
hm:read | 如果此电子邮件已读,则设置此标志。 |
m:to | 邮件发送给了谁。 |
m:from | 邮件来自谁。 |
m:subject | 邮件主题。 |
m:date | 时间戳,格式为 [date]T[time] |
D:getcontentlength | 此电子邮件的大小(以字节为单位)。 |
示例代码读取如上所示的 XML 节点,以提取在返回的文件夹信息 XML 数据流中找到的每个邮件项的信息。
检索文件夹邮件
在枚举了文件夹中的 MailItems
后,可以使用 MailItem
的邮件 URL(适用于给定会话)来检索实际的电子邮件。这是通过向 Hotmail 服务器发送 HTTP/1.1 GET
请求到给定 URL 来完成的。示例代码中定义的 LoadMail()
函数接受一个 MailItem
实例,并返回邮箱电子邮件的内容。
/// <summary>
/// Loads the given mail item.
/// </summary>
public string LoadMail(MailItem mailItem)
{
// Get the Url.
string mailUrl = mailItem.Url;
// Open a connection to the Hotmail server.
xmlHttp_.open("GET", mailUrl, false, null, null);
// Send the request.
xmlHttp_.send(null);
// Get the response.
string mailData = xmlHttp_.responseText;
// Return the mail data.
return mailData;
}
发送新邮件
为了检索邮件,LoadMail()
方法(见上文)执行 HTTP/1.1 GET
请求。类似地,会向 sendmsg URL 发送一个 POST
请求,以便从 Hotmail 邮箱发送电子邮件。示例控制台应用程序还包含一个 SendMail()
方法,可以在连接到邮箱后调用。
/// <summary>
/// Sends an e-mail.
/// </summary>
public void SendMail(string from, string fromName,
string to, string subject, string body)
{
...
}
我们首先设置一个引用字符串(稍后使用),并生成一个邮件时间戳。
// Quote character.
string quote = "\u0022";
// Generate the time stamp.
DateTime now = DateTime.Now;
string timeStamp = now.ToString("ddd, dd MMM yyyy hh:mm:ss");
HTTPMail 协议遵循类似 SMTP 的通信方案(参见 RFC 821)。Outlook Express 以 MIME 格式发送邮件,但出于演示目的,我们仅发送纯文本电子邮件。
// Build the post body.
string postBody = null;
// Dump mail headers.
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";
// Dump mail body.
postBody += body;
要发送邮件,我们需要将 Content-Type
请求标头设置为“message/rfc821”,表明此请求的正文遵循 RFC 821。我们将生成的请求正文 POST
到连接时获得的 sendmsg URL。
// Open the connection.
xmlHttp_.open("POST", sendUrl_, false, null, null);
// Send the request.
xmlHttp_.setRequestHeader("Content-Type", "message/rfc821");
xmlHttp_.send(postBody);
给定一个有效的目标邮箱,Hotmail 会将邮件发送到我们期望的位置。
结论
Hotmail 是世界上最大的免费、基于 Web 的电子邮件提供商。然而,唯一可以直接访问 Hotmail 的非 Web 邮件客户端是 Outlook Express。由于 HTTPMail 协议是未公开的,因此不鼓励其他供应商提供类似的服务。在本文中,我们介绍了如何使用 C# 和 XMLHTTP 组件连接到 Hotmail 邮箱,枚举收件箱邮件项,以及发送和检索电子邮件。本文附带的示例代码包含一个 .NET 程序集,证明通过 HTTPMail 连接到 Hotmail 可以像处理 POP3、IMAP4 或 SMTP 等其他邮件协议一样简单。