Figure 1 - POP settings dialog
Figure 2 - Mail viewer
Figure 3 - POP3 chat logging
Introduction
I needed a POP3 library for a .NET project that a friend and I were working
on, and while a quick Google-search revealed several free implementations, none
of them fully fit my requirements. I wanted a reliable POP3 class with full
support for asynchronous fetching, cancellation, SSL, attachments, HTML email, and an uncomplicated and simple
interface so that calling applications wouldn't need to do hideous workarounds
to integrate it into their existing system. This article explains how this
library can be used, talks about the class implementation, and also includes a
demo WPF application that shows how a typical app might use this library.
This library's goal is to provide developers with a very simple way to read
email from POP3 servers, and with that in mind, it hides the underlying POP3
protocol from calling code. What that means is that this library is not for
those who are writing their own POP3 based code or want to extend the POP3
protocol in some way (for example writing a spam filter or an email
auto-forwarding application). In some ways this library can be considered as a
POP3 analogy to the SmtpClient
class from the System.Net.Mail
namespace and in fact it actually uses the MailMessage
class (from
the same namespace) to represent an email (although that had its disadvantages
as I will explain in this article).
Safety note about the demo app
The PopClient
class has a DeleteMailAfterPop
property that's used to specify whether mails should be left on the server or if
they should be deleted after they are fetched. Unless it's a throwaway POP
account, chances are extremely low that you'd want to delete mails when running
the demo app, and so as a precautionary measure, that property has been disabled
both in the UI and in the code, so you will not inadvertently delete any of your
email.
VS 2010 and .NET 4.0
While the code and the the demo project has been written and tested using VS
2010 and .NET 4.0, you should not have too much difficulty using this from VS
2008 and .NET 3.5. As far as I know, I have not used any 4.0 specific features
in any of the code.
Using the library
All the code is in the PopClient
assembly, so you need to add a
reference to that. And all the public
classes are under the
Extra.Mail
namespace, so you may want to add a
using
declaration in your code for that namespace. The
PopClient
class is
the only class that you need to directly instantiate and use, and it implements
IDisposable
and it's important that you dispose it off when you are
finished with the class. That said, since the class fetches mail asynchronously,
you should not dispose off the class until either the mail fetch operation has
completed, or you have manually aborted the mail fetch operation. This basically
means that it's not the best suited class for an
using
-block
(typically used for
IDisposable
types). The demo app shows one
typical way that the class be be disposed, and I'll also discuss another simple
way to dispose off the instance safely. Ironically, after saying all this, the
first bit of code I am about to show you does use an
using
-block,
although this is a console application and I take precautions to ensure that the
object stays alive until the POP3 operation has completed.
static void Main()
{
using (var popClient = new PopClient(host, port) {
Username = username, Password = password, EnableSsl = true })
{
popClient.MailPopCompleted += PopClient_MailPopCompleted;
popClient.MailPopped += PopClient_MailPopped;
popClient.QueryPopInfoCompleted += PopClient_QueryPopInfoCompleted;
popClient.ChatCommandLog += PopClient_ChatCommandLog;
popClient.ChatResponseLog += PopClient_ChatResponseLog;
try
{
popClient.PopMail();
}
catch(PopClientException pce)
{
Console.WriteLine("PopClientException caught!");
Console.WriteLine(
"PopClientException.PopClientBusy : {0}", pce.PopClientBusy);
}
Console.ReadKey();
}
}
The class is instantiated with the required POP3 settings such as host,
username, and password. I have also hooked onto several events which will be
fired asynchronously during the POP3 fetch operation. Once this is all done, a
call is made to the PopMail
method (which immediately returns), and
it's called from a try
-catch
block since the method
can throw exceptions. Although the code above only catches a
PopClientException
be aware that it can also throw an
InvalidOperationException
(the demo app handles both). And finally notice
the crafty positioning of the
Console.ReadKey
call to keep the
object alive and un-disposed until the fetch is completed. The
ChatXXXLog
events basically log protocol-chat used for the POP3 connection, with the
ChatCommandLog
firing for all commands we send to the server and the
ChatResponseLog
firing for all single-line server responses (for
usability reasons, I do not log multi-line responses, since that will quickly be
unmanageable when fetching large emails with bulky attachments). It's purely
optional to handle these events and their use is primarily to diagnose
connectivity issues, although you could also follow the chat log to get an idea
of how POP3 chat is executed (it's a very simple protocol, so it's doubtful that
anyone will want to do it for that purpose more than once or twice).
static void PopClient_ChatResponseLog(object sender, PopClientLogEventArgs e)
{
Console.WriteLine("<< {0}", e.Line);
}
static void PopClient_ChatCommandLog(object sender, PopClientLogEventArgs e)
{
Console.WriteLine(">> {0}", e.Line);
}
The effect of those two handlers is to give you a full chat
conversation (with beautiful TCP direction indicators in text). *grin*
>> STAT
<< +OK 7 703466
>> LIST 1
<< +OK 1 243160
>> UIDL 1
<< +OK 1 717619000000008003
>> RETR 1
<< +OK
>> LIST 2
<< +OK 2 2
>> UIDL 2
<< +OK 2 717619000000009001
>> RETR 2
<< +OK
>> LIST 3
<< +OK 3 216991
>> UIDL 3
<< +OK 3 717619000000010001
>> RETR 3
<< +OK
>> LIST 4
<< +OK 4 12
>> UIDL 4
<< +OK 4 717619000000011001
>> RETR 4
<< +OK
The WPF demo app shows a better way to show this information. The other three
events are what you really need to handle for reading the fetched emails. The
first one to fire will be the QueryPopInfoCompleted
event, which
gives you the number of emails and their total size. You can potentially use
this information to show a progress-bar or to update a summary status UI panel.
static void PopClient_QueryPopInfoCompleted(object sender, MailPopInfoFetchedEventArgs e)
{
Console.WriteLine("Event Fired: QueryPopInfoCompleted");
Console.WriteLine("Count: {0}, Total Size: {1} bytes", e.Count, e.Size);
}
Next, the MailPopped
event fires once for each mail that's
retrieved, so if you've handled the QueryPopInfoCompleted
event you
will know how many times this will fire (unless you cancel the operation with a
call to Cancel()
). The Class Reference section details all the
information, arguments, and properties that are available, so I will not
explain every single item in the code, although the property names are fairly
self descriptive, so anyone writing code to fetch email probably won't need to
look at the documentation.
static void PopClient_MailPopped(object sender, MailPoppedEventArgs e)
{
Console.WriteLine("Event Fired: MailPopped");
Console.WriteLine("Mail No. {0}", e.Index);
Console.WriteLine("Size: {0}", e.Size);
Console.WriteLine("Uidl: {0}", e.Uidl);
Console.WriteLine("Received: {0}", e.ReceivedTime);
Console.WriteLine("From: {0}, {1}", e.Message.From.Address, e.Message.From.DisplayName);
Console.WriteLine("To: {0}", e.Message.To);
Console.WriteLine("Subject: {0}", e.Message.Subject);
Console.WriteLine("Attachments: {0}", e.Message.Attachments.Count);
foreach (var attachment in e.Message.Attachments)
{
Console.WriteLine("File: {0}", attachment.Name);
}
for (int i = 0; i < e.Message.Headers.Count; i++)
{
Console.WriteLine("{0} = {1}",
e.Message.Headers.GetKey(i),
new String(e.Message.Headers[i].Take(40).ToArray()));
}
}
The MailPoppedEventArgs.Message
property is of type
MailMessage
, so anyone who's used that class before will recognize the
other properties that I've used there. The developers who wrote
MailMessage
intended it primarily for the
SmtpClient
class,
which meant that it does not have certain properties that you need when you are
retrieving mail, such as
Uidl
,
ReceivedTime
,
Size
etc. So I had to provide them though the
MailPoppedEventArgs
class. I realized this only after I had written half the code and while it would
not have been terribly difficult to write my custom
MailMessage
-like
class, I decided to continue using
MailMessage
, largely out of my
inconsiderate need to ensure that my class remained analogous and similar to the
SmtpClient
class which used
MailMessage
. Developers
like familiarity and I believe that using recognizable types will certainly help
with that. I also added an extension method to the
Attachment
class
so that you won't have to mess with memory streams and file IO, and instead can
just call a nice
Save(filePath)
method that will save the
attachment (the WPF demo app does make use of this). The last event that's fired
is the
MailPopCompleted
event. Note that this is always fired,
whether the POP fetch completed
harmoniously, or if it got user cancelled, or
even if it had to abort because of an exception. In fact it's the only way to be
notified of unexpected errors, so you should always handle this event.
static void PopClient_MailPopCompleted(object sender, MailPopCompletedEventArgs e)
{
Console.WriteLine("Event Fired: MailPopCompleted");
Console.WriteLine("MailPopCompletedEventArgs.Aborted : {0}", e.Aborted);
if (e.Exception != null)
{
Console.WriteLine(e.Exception.Message);
PopClientException pce = e.Exception as PopClientException;
if(pce != null)
{
Console.WriteLine("PopClientException.PopClientUserCancelled : {0}",
pce.PopClientUserCancelled);
}
}
}
This code does not fully demonstrate how the PopClientException
can be used to determine POP3 errors (like a bad password, or a POP3 server
error), but the demo app does a better job with that.
Retrieving only new messages
- Support for retrieving only new messages was added in
the November 19th 2010 update.
If you've noticed with mail clients that have an option to leave mail on the
server, they only retrieve mail that has not been previously fetched. They
achieve this by storing the UIDL values for each mail message and then not
downloading messages that match an existing UIDL. The PopClient
class now has a collection property called UidlsToIgnore
.
So prior to calling PopMail
, the calling client can
optionally populate this collection and those messages will be skipped. Changes
have been made to the library so that the count and total size reported will be
adjusted to accommodate these skipped messages. Be aware though that the index
value provided by the MailPoppedEventArgs
class will continue to represent the index value of the message in the mail
server. So you will potentially see non-contiguous indices when fetching mail if
you have opted to skip existing messages. This is not a bug and is correct
behavior. Example code showing how skip-UIDLs are added:
popClient.UidlsToIgnore.Add("717619000000008003");
popClient.UidlsToIgnore.Add("717619000000009001");
popClient.UidlsToIgnore.Add("717619000000010001");
Warning : Event handlers and thread context
One very important thing to keep in mind is that all the events except for
the MailPopCompleted
event are fired on a different thread (the
worker thread that the class internally uses for the POP3 connection). So if you
are handling UI from these event handlers, you need to take the typical
precautions that you need to take when accessing UI controls from auxiliary
threads. There are well-known methods to work around this where the events can
be made to fire on the same thread that created the PopClient
object, but I decided not to go that route for two specific reasons. The first
reason is that this forces the PopClient
class to be aware of such
UI/threading issues and I wanted to keep the class design clean which meant that
it had to be kept unaware of such frivolous side effects. A second important
reason is that whether the calling code uses Windows Forms or WPF would dictate
the need to handle thread context in distinct ways, not to mention that the
class may be used from other .NET UI frameworks which may have their own thread
related quirks. A third minor reason is that it may actually be a hazardous
approach to hide this from the caller by attempting to handle this in the
PopClient
class, since the caller will remain blissfully unaware of these
threading issues and will thus be unprepared to deal with any potential
consequences arising out of inter-thread access. And finally, as you will see
from the demo project, it's quite trivial to handle this in the calling UI code.
The demo application
Figure 4 - Saving an attachment
Figure 5 - Detailed logging/exception handling
The WPF demo application was written using Visual Studio 2010 and targets the
.NET 4.0 framework. I will completely refrain from discussing the XAML code here
and will instead focus on how the PopClient
class is typically used
in a UI application. Those of you who are interested in the XAML/data-binding
can go through the project source, and if you have specific questions (unlikely
given how simple the code is), please feel free to ask me through the forum for
this article. The demo project uses MVVM and all of the PopClient
code is in a single View-Model class. Every method that I discuss below will
thus be a part of this class.
The View-Model has a PopClient
field (needs to be a field, so we
can support cancellation and disposing). Note that the socket connection is not
established when you instantiate the class (not surprising since it does not
have connection info yet).
private PopClient popClient = new PopClient();
The event handlers are hooked up in the V-M constructor.
public MainWindowViewModel()
{
popClient.QueryPopInfoCompleted += PopClient_QueryPopInfoCompleted;
popClient.MailPopped += PopClient_MailPopped;
popClient.MailPopCompleted += PopClient_MailPopCompleted;
popClient.ChatCommandLog += PopClient_ChatCommandLog;
popClient.ChatResponseLog += PopClient_ChatResponseLog;
Application.Current.Exit += Current_Exit;
}
void Current_Exit(object sender, ExitEventArgs e)
{
popClient.Dispose();
}
In addition, I also handle the application's Exit
event so I can
dispose off the popClient
instance. Technically this can be
considered a superficial thing to do since the process will have terminated and
all process-specific resources would be released. But it keeps the code clean
and encourages this practice in applications where the V-M may be created and
destroyed multiple times during the life of an application (in which case, the
V-M itself should probably be an IDisposable
). It's a little easier
with WinForms since all Control
s and Form
s are
IDisposable
, and thus there's a documented well-established place to
dispose the
PopClient
instance. WPF windows don't do it that way,
since the WPF approach of using weak references everywhere makes this
unnecessary. One alternate way to dispose off the
PopClient
instance is in the
MailPopCompleted
event handler although it is
not a very clean approach and is a bit of a hack. I say that because when the
event fires, the
PopClient
instance is still in use, so for a few
microseconds you actually have a disposed object that's still executing a method
(albeit its last one). Of course since I wrote the class, I know it's safe to do
this (as of today) and there's no risk of the GC firing when it's still not done
executing a method, but if you run code profilers or memory leak analyzers, they
may complain and throw up an agitated warning message. So I wouldn't recommend
doing this unless it's in code that you are in full control of.
The following fields are used to propagate information to the UI, namely the
main window and the log window. The LogInfo
class is merely a
convenience class
I created to help with data-binding. One
important member here is the mainDispatcher
field which is used to
handle the thread context issues arising from how the events are fired on
secondary threads (and not from the main UI thread where the handlers were
originally setup from).
private Dispatcher mainDispatcher;
private ObservableCollection<MailPoppedEventArgs> mails
= new ObservableCollection<MailPoppedEventArgs>();
class LogInfo
{
public string Line { get; set; }
public bool Response { get; set; }
}
private ObservableCollection<LogInfo> logs
= new ObservableCollection<LogInfo>();
The RefreshCommand
property is a command object exposed by the
V-M that basically performs the mail-fetch operation.
public ICommand RefreshCommand
{
get
{
return refreshCommand ??
(refreshCommand = new DelegateCommand(Refresh, CanRefresh));
}
}
public bool CanRefresh()
{
return !popClient.IsWorking();
}
public void Refresh()
{
popClient.Host = Settings.Default.Host;
popClient.Port = Settings.Default.Port;
popClient.Username = Settings.Default.Username;
popClient.Password = this.PopPassword;
popClient.EnableSsl = Settings.Default.EnableSsl;
popClient.DeleteMailAfterPop = false;
popClient.Timeout = Settings.Default.Timeout;
try
{
mainDispatcher = Dispatcher.CurrentDispatcher;
FetchStatusText = "Fetching...";
mails.Clear();
logs.Clear();
popClient.PopMail();
}
catch (InvalidOperationException ex)
{
FetchStatusText = String.Format(
"Connection error - {0}", ex.Message);
}
catch (PopClientException ex)
{
FetchStatusText = String.Format(
"POP3 error - {0}", ex.Message);
}
}
Notice how CanRefresh
delegates the call to
PopClient.IsWorking
which will return
true
if a fetch
operation is current under way. I assign the various POP3 properties that are
required for a POP3 connection here, although for this particular demo project
this was actually a mistake to do this here - since it gets called every time
Refresh
is called. Originally I had planned to allow the user to
change POP settings after a fetch, but eventually I ended up showing the
settings dialog only once, at startup. Of course that's a rather insignificant
issue, but I thought I'd mention that here in case someone wonders why I did it
that way. Notice how I save the current
Dispatcher
instance in the
mainDispatcher
field, this is what I'll use later to update my
ObservableCollection<>
s because they will be bound to the View and
will thus need to execute on the UI thread.
Cancelling is fairly straightforward.
public ICommand CancelCommand
{
get
{
return cancelCommand ??
(cancelCommand = new DelegateCommand(Cancel, CanCancel));
}
}
public bool CanCancel()
{
return popClient.IsWorking();
}
public void Cancel()
{
popClient.Cancel();
}
There is a potential race condition where CanCancel
returns
true
, but the fetch operation completes before Cancel
is called. But it's safe to call Cancel
even in that scenario, so
there's no need to handle that race condition.
Here's the code that's used to save an attachment (this is accessible via the
context menu on attachment icons).
public ICommand SaveFileCommand
{
get
{
return saveFileCommand ??
(saveFileCommand = new DelegateCommand<Attachment>(SaveFile));
}
}
public void SaveFile(Attachment attachment)
{
SaveFileDialog dialog = new SaveFileDialog()
{
FileName = attachment.Name
};
if (dialog.ShowDialog().GetValueOrDefault())
{
attachment.Save(dialog.FileName);
}
}
The Save
method is an extension method I wrote on the
Attachment
class. Here's the code that brings up the Chat-Log window.
public ICommand ShowChatLogCommand
{
get
{
return showChatLogCommand ??
(showChatLogCommand = new DelegateCommand(
ShowChatLog, CanShowChatLog));
}
}
private LogWindow logWindow;
public bool CanShowChatLog()
{
return logWindow == null;
}
public void ShowChatLog()
{
logWindow = new LogWindow()
{ DataContext = logs, Owner = Application.Current.MainWindow };
logWindow.Show();
logWindow.Closed += (s,e) => logWindow = null;
}
As you can see it uses the logs
collection, which is populated
via the ChatXXXLog
event handlers.
void PopClient_ChatResponseLog(object sender, PopClientLogEventArgs e)
{
mainDispatcher.Invoke((Action)(() => logs.Add(
new LogInfo() { Line = e.Line, Response = true })), null);
}
void PopClient_ChatCommandLog(object sender, PopClientLogEventArgs e)
{
mainDispatcher.Invoke((Action)(() => logs.Add(
new LogInfo() { Line = e.Line })), null);
}
Notice how I use mainDispatcher
to invoke my code on the UI
thread. Here are the event handlers for the QueryPopInfoCompleted
and MailPopped
events.
void PopClient_MailPopped(object sender, MailPoppedEventArgs e)
{
mainDispatcher.Invoke((Action)(() => mails.Add(e)), null);
}
void PopClient_QueryPopInfoCompleted(
object sender, MailPopInfoFetchedEventArgs e)
{
MailStatsText = String.Format(
"{0} mails, Size = {1}", e.Count, e.Size);
}
And, here's the event handler for the MailPopCompleted
event.
void PopClient_MailPopCompleted(object sender, MailPopCompletedEventArgs e)
{
if (e.Aborted)
{
PopClientException popex = e.Exception as PopClientException;
if (popex == null)
{
FetchStatusText = "Aborted!";
}
else
{
FetchStatusText = popex.PopClientUserCancelled
? "User cancelled!" :
String.Format("POP3 error - {0}", popex.Message);
}
}
else
{
FetchStatusText = "Done!";
}
CommandManager.InvalidateRequerySuggested();
}
The code here is a better example of how the various exceptions are handled
(compared to the code I showed earlier). It demonstrates how the application can
handle POP3 server errors and display those messages back to the user.
Class Reference
All public types are in the Extra.Mail
namespace.
PopClient
Class
public class PopClient : IDisposable
{
public PopClient();
public PopClient(string host);
public PopClient(string host, int port);
public bool DeleteMailAfterPop { get; set; }
public bool EnableSsl { get; set; }
public string Host { get; set; }
public string Password { get; set; }
public int Port { get; set; }
public int Timeout { get; set; }
public string Username { get; set; }
public HashSet<string> UidlsToIgnore { get; private set; }
public event EventHandler<PopClientLogEventArgs> ChatCommandLog;
public event EventHandler<PopClientLogEventArgs> ChatResponseLog;
public event EventHandler<MailPopCompletedEventArgs> MailPopCompleted;
public event EventHandler<MailPoppedEventArgs> MailPopped;
public event EventHandler<MailPopInfoFetchedEventArgs> QueryPopInfoCompleted;
public void Cancel();
public void Dispose();
public bool IsWorking();
public void PopMail();
}
PopClientException
Class
[Serializable]
public class PopClientException : Exception
{
public PopClientException();
public PopClientException(string message);
public bool PopClientBusy { get; }
public bool PopClientUserCancelled { get; }
public override void GetObjectData(SerializationInfo info, StreamingContext context);
}
MailPopInfoFetchedEventArgs
Class
public class MailPopInfoFetchedEventArgs : EventArgs
{
public MailPopInfoFetchedEventArgs(int count, int size);
public int Count { get; }
public int Size { get; }
}
MailPoppedEventArgs
Class
public class MailPoppedEventArgs : EventArgs
{
public MailPoppedEventArgs(
int index, MailMessage message, int size, string uidl, DateTime receivedTime);
public int Index { get; }
public MailMessage Message { get; }
public DateTime ReceivedTime { get; }
public int Size { get; }
public string Uidl { get; }
}
MailPopCompletedEventArgs
Class
public class MailPopCompletedEventArgs : EventArgs
{
public MailPopCompletedEventArgs();
public MailPopCompletedEventArgs(Exception ex);
public bool Aborted { get; }
public Exception Exception { get; }
}
PopClientLogEventArgs
Class
public class PopClientLogEventArgs : EventArgs
{
public PopClientLogEventArgs(string line);
public string Line { get; }
}
Implementation details
This class is based on the POP3 protocol which is standardized via RFC1939
(Post Office Protocol v3).
One of the design intentions was to hide the implementation details from the
public interface and this meant that I could opt to selectively implement the
minimal protocol needed to fetch email from a POP3 server without losing out on
any POP3 functionality. The internal
class PopConnection
is used to establish a socket connection and to send/receive POP3 commands and
responses from a server. It's a very thin socket based class that uses a
TcpClient
object to communicate with a POP server.
internal class PopConnection : IDisposable
{
private char[] endMarker;
private Stream stream;
private TcpClient tcpClient;
public PopConnection(PopClient popClient);
private bool CheckForEndOfData(StringBuilder sb);
public void Dispose();
protected virtual void Dispose(bool disposing);
public string ReadMultiLineResponseString();
public int ReadResponse(out byte[] bytes);
public string ReadResponseString();
private string SendCommandInternal(string command);
public string SendDele(int messageId);
public string SendList();
public string SendList(int messageId);
public string SendPass(string pass);
public string SendQuit();
public string SendRetr(int messageId);
public string SendStat();
public string SendUidl();
public string SendUidl(int messageId);
public string SendUser(string user);
}
Here's a partial list of the SendXXX
-method implementations.
private string SendCommandInternal(string command)
{
byte[] bytes = Encoding.UTF8.GetBytes(command);
stream.Write(bytes, 0, bytes.Length);
return command;
}
public string SendQuit()
{
return SendCommandInternal("QUIT\r\n");
}
public string SendStat()
{
return SendCommandInternal("STAT\r\n");
}
public string SendList(int messageId)
{
return SendCommandInternal(String.Format("LIST {0}\r\n", messageId));
}
public string SendDele(int messageId)
{
return SendCommandInternal(String.Format("DELE {0}\r\n", messageId));
}
It's pretty straightforward socket code, specially since the POP3 protocol is
quite uncomplicated. The receive methods are also quite straightforward.
public string ReadResponseString()
{
byte[] bytes;
int count = this.ReadResponse(out bytes);
return bytes.GetString(count);
}
public string ReadMultiLineResponseString()
{
StringBuilder sb = new StringBuilder();
byte[] bytes;
do
{
int count = this.ReadResponse(out bytes);
sb.Append(bytes.GetString(count));
}
while( !CheckForEndOfData(sb) );
return sb.ToString();
}
private char[] endMarker = { '\r', '\n', '.', '\r', '\n' };
private bool CheckForEndOfData(StringBuilder sb)
{
if (sb.Length < 5)
return false;
char[] compare = new char[5];
sb.CopyTo(sb.Length - 5, compare, 0, 5);
return (endMarker as IStructuralEquatable).Equals(
compare, EqualityComparer<char>.Default);
}
Every POP3 command has an associated command object, and all command objects
implement the IPopCommand
interface.
internal interface IPopCommand
{
event EventHandler<PopClientDirectionalLogEventArgs> PopClientLog;
void Execute(params object[] arguments);
bool IsMultiLineResponse { get; }
}
There is an abstract
BasePopCommand
class
that implements some of the common stuff and makes it easy to implement command
classes.
internal abstract class BasePopCommand : IPopCommand
{
private EventHandler<PopClientDirectionalLogEventArgs> PopClientLog;
public event EventHandler<PopClientDirectionalLogEventArgs> PopClientLog;
public BasePopCommand(PopConnection popConnection);
private void CheckForErrorResponse();
public void Execute(params object[] arguments);
private void Execute(object argument);
protected abstract string ExecuteInternal(object argument);
protected void OnPopClientLog(string line, bool isServerResponse);
protected virtual void ParseInternal();
public virtual bool IsMultiLineResponse { get; }
protected string MultiLineResponse { get; private set; }
protected PopConnection PopConnection { get; private set; }
protected string Response { get; private set; }
}
Here are some of the more important method implementations.
public void Execute(params object[] arguments)
{
this.Execute(arguments.FirstOrDefault());
}
private void Execute(object argument)
{
this.OnPopClientLog(ExecuteInternal(argument), false);
this.Response = this.PopConnection.ReadResponseString();
this.OnPopClientLog(this.Response, true);
this.CheckForErrorResponse();
if (this.IsMultiLineResponse)
{
this.MultiLineResponse =
this.PopConnection.ReadMultiLineResponseString();
}
this.ParseInternal();
}
protected virtual void ParseInternal()
{
}
private void CheckForErrorResponse()
{
if (this.Response.StartsWith("-ERR"))
{
throw new PopClientException(
new String(this.Response.Skip(4).ToArray()).Trim());
}
}
Derived classes now just need to implement ExecuteInternal
and
optionally ParseInternal
(if they need to parse the response
stream). Here's how the User
command is implemented, it's one of
the more simple ones as it does not do custom parsing on the response.
internal class UserPopCommand : BasePopCommand
{
public UserPopCommand(PopConnection popConnection)
: base(popConnection)
{
}
protected override string ExecuteInternal(object argument)
{
return PopConnection.SendUser(argument as string);
}
}
Here's a slightly more involved derived class that implements the Stat
command.
internal class StatPopCommand : BasePopCommand
{
public StatPopCommand(PopConnection popConnection)
: base(popConnection)
{
}
protected override string ExecuteInternal(object argument)
{
return PopConnection.SendStat();
}
private int count;
public int Count
{
get { return count; }
}
private int size;
public int Size
{
get { return size; }
}
protected override void ParseInternal()
{
string[] parts = this.Response.Trim().Split();
if (parts.Length != 3 || !Int32.TryParse(parts[1], out count)
|| !Int32.TryParse(parts[2], out size))
{
throw new PopClientException("Unknown STAT response from server.");
}
}
}
One of the most important commands is the Retr
command, and
here's how that's been implemented.
internal class RetrPopCommand : BasePopCommand
{
public MailMessage Message { get; private set; }
public DateTime ReceivedTime { get; private set; }
public RetrPopCommand(PopConnection popConnection)
: base(popConnection)
{
}
protected override string ExecuteInternal(object argument)
{
return PopConnection.SendRetr((int)argument);
}
protected override void ParseInternal()
{
var converter = new CDOMessageConverter(this.MultiLineResponse);
this.Message = converter.ToMailMessage();
this.ReceivedTime = converter.ReceivedTime;
}
public override bool IsMultiLineResponse
{
get
{
return true;
}
}
}
Notice the use of the CDOMessageConverter
class. CDO
(Collaboration Data Objects) is a COM component that's been part of Windows OSes
since Windows 2000, and it has built in functionality to parse MIME based email
(including full support for attachments). CDO has no idea about the
MailMessage
class, so I wrote a converter that will take a MIME string,
construct a CDO message from it, and then convert the CDO message into a
MailMessage
object. It's fairly straightforward COM-interop, so I won't
go into the details. The two classes do not have a one-to-one structural
equivalence, so I had to make certain compromises, but as long as I was able to
obtain all fields required to read an email message with attachments, I was not
too concerned about superficial differences between the types.
The PopChat
class provides a single point interface from where
the PopClient
class can invoke POP commands. Here's a snipped code
listing that shows what the class does. In addition to exposing properties for
each command, it also makes sure that all commands have their log events
subscribed to via a common event handler.
internal class PopChat : IDisposable
{
internal NothingPopCommand Nothing { get; private set; }
internal UserPopCommand User { get; private set; }
internal PassPopCommand Pass { get; private set; }
internal StatPopCommand Stat { get; private set; }
public PopChat(PopClient client)
{
new IPopCommand[]
{
Nothing = new NothingPopCommand(popConnection),
User = new UserPopCommand(popConnection),
Pass = new PassPopCommand(popConnection),
Stat = new StatPopCommand(popConnection),
Retr = new RetrPopCommand(popConnection),
Quit = new QuitPopCommand(popConnection),
List = new ListPopCommand(popConnection),
Uidl = new UidlPopCommand(popConnection),
Dele = new DelePopCommand(popConnection),
ListAll = new ListAllPopCommand(popConnection),
UidlAll = new UidlAllPopCommand(popConnection)
}.ToList().ForEach(pc => pc.PopClientLog += PopCommand_PopClientLog);
}
private void PopCommand_PopClientLog(
object sender, PopClientDirectionalLogEventArgs e)
{
this.client.LogLine(e.Line, e.IsServerResponse);
}
}
And finally, the actual POP chat is invoked from the BackgroundWorker
thread that's used by the PopClient
class.
private void Worker_DoWork(object sender, DoWorkEventArgs e)
{
using (PopChat chat = new PopChat(this))
{
chat.Nothing.Execute();
chat.User.Execute(this.Username);
chat.Pass.Execute(this.Password);
chat.Stat.Execute();
chat.ListAll.Execute();
chat.UidlAll.Execute();
int count = chat.Stat.Count;
int size = chat.Stat.Size;
if (this.UidlsToIgnore.Count > 0)
{
var skipList = chat.UidlAll.Uidls.Values.Intersect(this.UidlsToIgnore);
count -= skipList.Count();
foreach (var uidl in skipList)
{
size -= chat.ListAll.MessageSizes[chat.UidlAll.Indices[uidl]];
}
}
this.OnMailPopInfoFetched(count, size);
for (int fetchIndex = 1; fetchIndex <= chat.Stat.Count; fetchIndex++)
{
if (worker.CancellationPending)
{
e.Cancel = true;
break;
}
if (!this.UidlsToIgnore.Contains(chat.UidlAll.Uidls[fetchIndex]))
{
chat.Retr.Execute(fetchIndex);
this.OnMailPopped(fetchIndex, chat.Retr.Message,
chat.ListAll.MessageSizes[fetchIndex],
chat.UidlAll.Uidls[fetchIndex], chat.Retr.ReceivedTime);
if (this.DeleteMailAfterPop)
{
chat.Dele.Execute(fetchIndex);
}
}
}
chat.Quit.Execute();
}
}
Conclusion
If you run into specific issues with a POP server, the chat log (easily
accessible via the event handlers) should tell you exactly what's going on. As
always, feedback and criticism is welcome, and please feel free to post your
comments through the discussion forum for this article. Although if you post
something nasty or vote anything less than a 5, I have a Yaqui curse that
I recently learned all ready to apply on you! *grin*
History
- November 8th, 2010 - Article first published.
- November 19th, 2010
- Default POP port was incorrectly set to the SMTP port. This is now
fixed.
- Added support for skippable UIDLs. This lets you only retrieve mail that
was not fetched earlier.
- New command objects added for UIDL and LIST that supplement the existing
versions, except that these do an index-less full fetch.