In my last post I talked about the standard POP3 commands, today we'll write a simple C# POP3 client.
The class diagram
The SimplePop3Client consist of only one class:
Properties
We have only three Properties: Host, Port and ApopTimestamp. ApopTimestamp cannot be set and will only be available after connecting.
Methods
We'll implement all standard POP3 commands (including the optional ones) as public methods. We need two extra methods for connecting and disconnecting to the mail server. We could also establish the connection in the constructor, but I think it's better to have the control over the moment when we connect to the server.
There are three helper methods:
- SendCommand(string command)
Sends the command string to the server - GetSingleLineResult()
Returns the response of the server. We'll use that for commands which return only one line (USER, PASS, APOP, STAT, LIST n, UIDL n, NOOP, DELE n,RSET, QUIT) - GetMultiLineResult()
Returns the response of the server. We'll need this method for commands which return more than one line (LIST, UIDL, RETR, TOP n, TOP n m)
Fields
For the connection and the handling of the responses we need a TcpClient, a NetworkStream and a StreamReader. The string lastResponse is often used on receiving the response.
Connect and Disconnect
When connecting we try to get the APOP timestamp because we possibly need it later.
using System.Text.RegularExpressions
public string Connect()
{
string response = "";
try
{
// connect to the server
client = new TcpClient(this.Host, this.Port);
stream = client.GetStream();
reader = new StreamReader(stream);
lastResponse = streamReader.ReadLine();
if (lastResponse.StartsWith("+OK"))
{
// try to extract apop timestamp
Regex r = new Regex(@"(?<TimeStamp>\x3C[^\x3C\x3E]+\x3E)");
Match m = r.Match(lastResponse);
if (m.Groups["TimeStamp"].Value != "")
{
this._ApopTimeStamp = m.Groups["TimeStamp"].Value;
}
}
response = lastResponse;
}
catch (Exception e)
{
response = "An error occured: " + e.ToString()
}
return response;
}
public bool Disconnect()
{
return QUIT().StartsWith("+OK");
}
The helper methods
SendCommand
private bool SendCommand(string command)
{
bool success = true;
try
{
byte[] buffer = Encoding.ASCII.GetBytes(command + Environment.NewLine);
networkStream.Write(buffer, 0, buffer.Length);
}
catch (Exception)
{
success = false;
}
return success;
}
GetSingleLineResponse
private string GetSingleLineResponse()
{
string response = ""
try
{
response = reader.ReadLine() ?? ""
}
catch (Exception e)
{
response = "An error occured: " + e.ToString();
}
return response;
}
GetMultiLineResponse
private string GetMultiLineResponse()
{
StringBuilder response = new StringBuilder();
try
{
lastResponse = reader.ReadLine() ?? "";
response.AppendLine(lastResponse);
if(lastResponse.StartsWith("+OK"))
{
// multiline responses always end with a single dot in the last line
while (lastResponse != ".")
{
lastResponse = reader.ReadLine();
response.AppendLine(lastResponse);
}
}
}
catch (Exception e)
{
response = "An error occured: " + e.ToString();
}
return response;
}
Implementing the standard POP3 commands
For each POP3 command we have a public method. They all work the same:
- Send command and check if it was successful
- Get response and return it
So I show you only a few sample commands:
public string STAT()
{
bool success = SendCommand("LIST");
return success ? GetSingleLineResponse() : "";
}
public string RETR(int messageNumber)
{
bool success = SendCommand(String.Format("RETR {0}", messageNumber));
return success ? GetMultiLineResponse() : "";
}
APOP authentication
The APOP method is a little because an additional step for the md5 hash is needed:
using System.Security.Cryptography;
private string APOP(string username, string password)
{
// calculate md5 has
MD5 md5Hasher = MD5.Create();
byte[] data = md5Hasher.ComputeHash(Encoding.Default.GetBytes(this.ApopTimestamp + username));
StringBuilder digest = new StringBuilder();
for (int i = 0; i < data.Length; i++)
{
digest.Append(data[i].ToString("x2"));
}
// send apop command
bool success = SendCommand(String.Format("APOP {0} {1}", username, digest.ToString()));
return success ? streamReader.ReadToEnd() : "";
}
Testing the SimplePop3Client
Yes, I know, I should do real unit tests. At least there's a lot written about it, and I'd really like to do but unfortunatly this is a topic I'm not well versed in. But it's definitely something I'd like to try out developing Opo Perspective. For this simple POP3 client I think a simple console application will be adequate for our needs:
namespace Opo.Net.Mail
{
class Program
{
static void Main(string[] args)
{
SimplePop3Client pop3 = new SimplePop3Client("pop.yourmailserver.com", 110, "AccountName@Domain.com", "Password");
Console.Write(pop3.Connect());
Console.Write(pop3.USER());
Console.Write(pop3.PASS());
Console.Write(pop3.STAT());
Console.Write(pop3.LIST());
Console.Write(pop3.RETR(1));
Console.Write(pop3.DELE(2));
Console.Write(pop3.LIST());
Console.Write(pop3.RSET());
Console.Write(pop3.LIST());
Console.Write(pop3.QUIT());
Console.Write(pop3.Disconnect());
Console.ReadKey();
}
}
}
Code download
Download the SimplePop3Client Solution: SimplePop3Client.zip (5.48 kb)
Make sure you set appropriate values for host and port in program.cs before you start debugging.
0f5c3f98-ed6d-411a-a837-bb7a0627cd11|3|4.7