65.9K
CodeProject 正在变化。 阅读更多。
Home

深入了解 POP3 邮件协议:第二部分

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.96/5 (32投票s)

2012年6月14日

MIT

5分钟阅读

viewsIcon

124448

downloadIcon

3205

本文档为邮件协议初学者描述了邮件发送过程。

引言

使用电脑或移动设备的人都使用过邮件。邮件系统是一个古老、传统且简单的协议。本文(第二部分)的目的是探讨 POP3 协议的内部细节,并向您展示如何使用 C# 实现它。

您可以通过 https://github.com/higty/higlabo.netstandard 获取完整的库。

POP3

您可以通过两个协议从邮箱接收邮件。本文描述了 POP3 协议。以下是接收邮件的基本流程

  • 打开连接
  • Authenticate
  • 列表
  • Retr
  • 退出

打开连接

请参阅这篇博文中的“打开连接”部分。

首先,您必须使用您的用户名和密码对邮箱进行身份验证。这是 POP 身份验证流程图

Pop3Client.cs
public Boolean AuthenticateByPop()
{
    String s = "";
    if (this.EnsureOpen() == Pop3ConnectionState.Connected)
    {
        s = this.Execute("user " + this.UserName, false);
        if (s.StartsWith("+OK") == true)
        {
            s = this.Execute("pass " + this.Password, false);
            if (s.StartsWith("+OK") == true)
            {
                this._State = Pop3ConnectionState.Authenticated;
            }
        }
    }
    return this._State == Pop3ConnectionState.Authenticated;
}

这是 POP3 协议的响应文本

+OK send PASS please

这是失败时的响应文本

-ERR [AUTH] Username and password not accepted.

单行响应格式如下

成功时

+OK[whitespace][message]

失败时

-ERR[whitespace][message]

POP3 身份验证非常简单,但安全性较低。为了提高安全性,您可以使用 A-POP 身份验证。

Pop3Client.cs
public Boolean AuthenticateByAPop()
{
    String s = "";
    String TimeStamp = "";
    Int32 StartIndex = 0;
    Int32 EndIndex = 0;

    if (this.EnsureOpen() == Pop3ConnectionState.Connected)
    {
        s = this.Execute("user " + this.UserName, false);
        if (s.StartsWith("+OK") == true)
        {
            if (s.IndexOf("<") > -1 &&
                s.IndexOf(">") > -1)
            {
                StartIndex = s.IndexOf("<");
                EndIndex = s.IndexOf(">");
                TimeStamp = s.Substring(StartIndex, EndIndex - StartIndex + 1);
                s = this.Execute("pass " + MailParser.ToMd5DigestString
                                 (TimeStamp + this.Password), false);
                if (s.StartsWith("+OK") == true)
                {
                    this._State = Pop3ConnectionState.Authenticated;
                }
            }
        }
    }
    return this._State == Pop3ConnectionState.Authenticated;
}

密码安全

POP3 身份验证的安全性较低,因为用户名和密码以纯文本形式发送到网络。恶意软件或人员可以窃听您的密码并恶意使用。A-POP 与 POP3 不同,因为发送的密码通过 MD5Digest 和从服务器收到的挑战文本进行编码。服务器将保存的挑战文本与从客户端收到的文本进行比较。无法接收实际密码文本。因此,A-POP 比 POP 身份验证更安全。

获取邮件列表

身份验证后,您可以使用 LIST 命令获取邮件列表。LIST 命令的响应文本如下

+OK 250 messages (338834 bytes)
1 2762
2 1704
3 2259
4 1748
5 1799
6 1802
7 1109
8 1701
9 1075
10 1130
...
245 1310
246 1725
247 1092
248 1237
249 1161
250 1099
.

格式如下

+OK [message]
[mail index][white space][mail size]
[mail index][white space][mail size]
...repeat...
[mail index][white space][mail size]
[period]

您可以通过调用 Pop3Client 类的 ExecuteList 方法来获取它

public List<ListCommandResult> ExecuteList()
{
    ListCommand cm = new ListCommand();
    List<ListCommandResult> l = new List<ListCommandResult>();
    StringReader sr = null;
    String s = "";
    String line = "";

    this.CheckAuthenticate();
    s = this.Execute(cm);
    this.CheckResponseError(s);
            
    sr = new StringReader(s);
    while (sr.Peek() > -1)
    {
        line = sr.ReadLine();
        if (line == ".")
        { break; }
        if (line.StartsWith("+OK", StringComparison.OrdinalIgnoreCase) == true)
        { continue; }

        l.Add(new ListCommandResult(line));
    }
    return l;
}

此结果由 ListCommandResult 类表示。

获取消息

您可以通过调用 Pop3Client 类的 GetMessage 方法获取实际的邮件消息数据。以下是接收邮件消息的一些示例代码

private static void Pop3MailReceive()
{
    MailMessage mg = null;

    using (Pop3Client cl = new Pop3Client("pop.gmail.com"))
    {
        cl.Port = 995;
        cl.UserName = "xxxxx";
        cl.Password = "yyyyy";
        cl.Ssl = true;
        cl.AuthenticateMode = Pop3AuthenticateMode.Auto;
        var bl = cl.Authenticate();
        if (bl == true)
        {
            var l = cl.ExecuteList();
            for (int i = 0; i < l.Count; i++)
            {
                mg = cl.GetMessage(l[i].MailIndex);
            }
        }
    }
}

您必须将大于 1 的索引传递给 GetMessage 方法。请注意,第一个值是 1,而不是零。

MailMessage 类架构如下

MailMessage 类继承自 InternetTextMessage

您可以使用此示例代码获取主题和正文

String subject = mg.Subject;
String body = mg.BodyText;

您可以使用定义在 InternetTextMessage 类上的 Data 属性获取原始文本。

String rawText = mg.Data;

附件

邮件有时会包含附件。您可以使用 MailMessage 类的 Contents 属性获取附件。

var mg = cl.GetMessage(1);
foreach (var ct in mg.Contents)
{
    ct.DecodeData("C:\\" + ct.FileName);
}

您可以通过调用 MailContent 对象的 DecodeData 方法来保存附件。

在极少数情况下,MailMessage 本身就是附件。您可以通过检查 MailMessage 对象的 IsAttachment 属性来确认 MailMessage 是否是附件。

var mg = cl.GetMessage(1);
if (mg.IsAttachment == true)
{
    mg.DecodeData("C:\\MyFile.png");
}

HTML 邮件

HTML 邮件是一种文本邮件。您可以使用 MailMessageMailContent 对象的 BodyText 属性获取 HTML 文本。

String htmlText = mg.BodyText;
foreach (var ct in mg.Contents)
{
    if (ct.IsHtml == false) { continue; }
    String htmlText1 = ct.BodyText;
}

要在 UI 组件中显示 HTML,您必须将此 htmlText 设置给您的控件。在 Windows Forms 或 WPF 应用程序中,请使用浏览器控件显示它。在 Web 应用程序中,您可以选择某些控件,如 HtmlGenericControlLabel 控件等。以下是一个 Web 应用程序的示例代码

protected void Page_Load(object sender, EventArgs e)
{
    MailMessage mg = null;
    String htmlText = "";

    using (Pop3Client cl = new Pop3Client("pop.gmail.com"))
    {
        cl.Port = 995;
        cl.UserName = "xxxxx";
        cl.Password = "yyyyy";
        cl.Ssl = true;
        cl.AuthenticateMode = Pop3AuthenticateMode.Auto;
        if (cl.Authenticate() == false) { return; }
        if (mg.IsHtml == true)
        {
            htmlText = mg.BodyText;
        }
        else 
        {
            foreach (var ct in mg.Contents)
            {
                if (ct.IsHtml == true) 
                {
                    htmlText = ct.BodyText;
                    break; 
                }
            }
        }
    }
    this.Label1.Text = htmlText;
}

.eml 文件

Outlook 和其他邮件客户端程序会创建 .eml 文件。实际上,这些文件是简单的文本文件,如下所示

Return-Path: <xxxxx@gmail.com>
Received: from  (bf240.xxx.xxxx.com. [61.197.23.240])
        by mx.google.com with ESMTPS id pb4sm20464671pbc.55.2012.05.13.17.56.12
        (version=SSLv3 cipher=OTHER);
        Sun, 13 May 2012 17:56:13 -0700 (PDT)
Message-ID: <4fc05e2d.e4a9440a.302b.ffffe399@mx.google.com>
Date: Mon, 14 May 2012 00:56:10 +0900
From: xxxxx@gmail.com
Subject: Test
Content-Transfer-Encoding: Quoted-Printable
Content-Disposition: inline
Mime-Version: 1.0
Reply-To: xxxxx@hotmail.com
X-Priority: 3
To: yyyyy@gmail.com
Content-Type: text/plain; charset="iso-8859-1"

=E4=C4=F6=D6=FC=DC=DF


.

您可以从文本数据创建 MailMessage 对象。

String text = File.ReadAllText("C:\\MyMail.eml");
var mg = new MailMessage(text);

删除邮件

以下是 POP3 中的删除过程

您可以看到 DELE 命令的响应文本如下

+OK marked for deletion

Dele 命令将您指定的邮件标记为待删除。当您发送 quit 命令时,这些标记的邮件将被删除。您可以使用 Pop3Client 对象的 DeleteMail 方法删除邮件。

protected void Button1_Click(object sender, EventArgs e)
{
    MailMessage mg = null;
    String htmlText = "";

    using (Pop3Client cl = new Pop3Client("pop.gmail.com"))
    {
        cl.Port = 995;
        cl.UserName = "xxxxx";
        cl.Password = "yyyyy";
        cl.Ssl = true;
        cl.AuthenticateMode = Pop3AuthenticateMode.Auto;
        
        cl.DeleteMail(1, 2, 3);
    }
}

由于所有过程(打开连接、身份验证、dele、quit)都将在 DeleteMail 方法内自动执行,因此不需要身份验证。这是 DeleteMail 方法的实现

public Boolean DeleteMail(params Int64[] mailIndex)
{
    DeleCommand cm = null;
    String s = "";

    if (this.EnsureOpen() == Pop3ConnectionState.Disconnected) { return false; }
    if (this.Authenticate() == false) { return false; }
    for (int i = 0; i < mailIndex.Length; i++)
    {
        cm = new DeleCommand(mailIndex[i]);
        s = this.Execute(cm);
        if (MailParser.IsResponseOk(s) == false) { return false; }
    }
    this.ExecuteQuit();
    return true;
}

您可以看到 DeleteMail 方法内部执行了打开连接、身份验证、发送 dele 命令,最后发送 quit 命令。

邮件格式

这是 POP3 消息的实际原始文本数据

Return-Path: <xxxxx@gmail.com>
Received: from  (bf240.xxx.xxxx.com. [61.197.23.240])
        by mx.google.com with ESMTPS id pb4sm20464671pbc.55.2012.05.13.17.56.12
        (version=SSLv3 cipher=OTHER);
        Sun, 13 May 2012 17:56:13 -0700 (PDT)
Message-ID: <4fc05e2d.e4a9440a.302b.ffffe399@mx.google.com>
Date: Mon, 14 May 2012 00:56:10 +0900
From: xxxxx@gmail.com
Subject: Test
Content-Transfer-Encoding: Quoted-Printable
Content-Disposition: inline
Mime-Version: 1.0
Reply-To: xxxxx@hotmail.com
X-Priority: 3
To: yyyyy@gmail.com
Content-Type: text/plain; charset="iso-8859-1"

=E4=C4=F6=D6=FC=DC=DF


.

消息包含两部分:头部和正文。红色是头部,蓝色是正文。

头部和正文由一个空行分隔。头部包含键值对的列表。

[key][colon][value]

多行格式如下

[key][colon][value]
[tab or white space][value]
...repeat...
[tab or white space][value]

消息正文中可以使用字符仅限于 7 位字符。如果您想发送“ありがとうございます”(这是一个日语字符,表示“谢谢”),则必须使用 Quoted-Printable 或 Base64 编码进行编码。由于其历史原因,邮件格式已变得非常复杂。

重要的 RCF 包括 822、2045-2049、2231、2282、2407。如果您想了解更多详细信息,请进行查阅。

使用 UIDL 命令管理已读或未读

POP3 不具备管理已读状态的功能。您必须使用自己的实现来管理已读状态。您可以使用 UIDL 命令来实现已读状态管理。这是 UIDL 命令的响应文本

+OK
1 GmailId136106770caf88db
2 GmailId136106acda5c2ff1
3 GmailId136106ae7f1b631b
4 GmailId136106b691ad4a79
5 GmailId136106ba4df09d7f
6 GmailId136106bdfbd92cdb
7 GmailId136106c5168c87fe
...
245 GmailId13610723f4c51313
246 GmailId13610724acde6b5b
247 GmailId13610724dc2d28b0
248 GmailId136107261a4d3b0c
249 GmailId1361072652837e0f
250 GmailId136107269afc66ae
.

格式如下

+OK
[mail index][white space][unique id]
...repeat
[mail index][white space][unique id]
[period]

UidlCommandResult 类表示此响应文本

为了管理已读状态,您需要保存此 UID 列表供下次使用。我将在这里展示概念性代码

private static void Pop3MailReceiveUnRead()
{
    MailMessage mg = null;

    using (Pop3Client cl = new Pop3Client("pop.gmail.com"))
    {
        cl.Port = 995;
        cl.UserName = "xxxxx";
        cl.Password = "yyyyy";
        cl.Ssl = true;
        cl.AuthenticateMode = Pop3AuthenticateMode.Auto;
        var bl = cl.Authenticate();
        if (bl == true)
        {
            var ul = cl.ExecuteUidl();
            String path = "C:\\MailUidl.txt";
            List<String> l = new List<String>();
            //Get read mail index list from file
            if (File.Exists(path) == true)
            {
                l.AddRange(File.ReadAllLines(path));
            }
            List<UidlCommandResult> unreadList = new List<UidlCommandResult>();
            for (int i = 0; i < ul.Count; i++)
            {
                if (l.Contains(ul[i].Uid) == true) { continue; }
                unreadList.Add(ul[i]);
            }
            //Save UIDL data to text file
            StringBuilder sb = new StringBuilder(ul.Count * 10);
            for (int i = 0; i < ul.Count; i++)
            {
                sb.AppendLine(ul[i].Uid);
            }
            File.WriteAllText(path, sb.ToString());
        }
    }
}

您可以根据您的需求将 UIDL 数据保存到数据库或 XML 文件。

通过 SMTP 转发邮件

如果您想转发收到的邮件,可以使用 MailMessage 对象的 CreateSmtpMessage 方法。
以下是一个执行此操作的示例代码

private static void Pop3MailReceiveUnRead()
{
    MailMessage mg = null;

    using (Pop3Client cl = new Pop3Client("pop.gmail.com"))
    {
        cl.Port = 995;
        cl.UserName = "xxxxx";
        cl.Password = "yyyyy";
        cl.Ssl = true;
        cl.AuthenticateMode = Pop3AuthenticateMode.Auto;
        var bl = cl.Authenticate();
        if (bl == true)
        {
            mg = cl.GetMessage(1);
            SmtpMessage smg = mg.CreateSmtpMessage();
            // you can send smg by SmtpClient object
        }
    }
}

然后,您可以使用 SmtpClient 类发送 SmtpMessage 类。
您可以阅读以下文章了解如何使用 SmtpClient 类发送邮件:深入了解 SMTP 邮件协议:第一部分

下一篇文章关于 IMAP:深入了解 IMAP 邮件协议:第三部分

历史

  • 2012 年 6 月 14 日:首次发布
  • 2012 年 7 月 6 日:修改了源代码和文章
  • 2012 年 7 月 24 日:修改了文章并添加了 CreateSmtpMessage 部分
  • 2012 年 12 月 10 日:修改了文章
© . All rights reserved.