深入了解 POP3 邮件协议:第二部分
本文档为邮件协议初学者描述了邮件发送过程。
引言
使用电脑或移动设备的人都使用过邮件。邮件系统是一个古老、传统且简单的协议。本文(第二部分)的目的是探讨 POP3 协议的内部细节,并向您展示如何使用 C# 实现它。
您可以通过 https://github.com/higty/higlabo.netstandard 获取完整的库。
POP3
您可以通过两个协议从邮箱接收邮件。本文描述了 POP3 协议。以下是接收邮件的基本流程
- 打开连接
- Authenticate
- 列表
- Retr
- 退出
打开连接
请参阅这篇博文中的“打开连接”部分。
首先,您必须使用您的用户名和密码对邮箱进行身份验证。这是 POP 身份验证流程图
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 身份验证。
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 邮件是一种文本邮件。您可以使用 MailMessage
或 MailContent
对象的 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 应用程序中,您可以选择某些控件,如 HtmlGenericControl
、Label
控件等。以下是一个 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 日:修改了文章