Welcome

首页 / 软件开发 / C# / C#访问Hotmail

C#访问Hotmail2007-09-30仙人掌工作室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,才可以发送和接收邮件。