Developing a C# POP3 client: First Release on CodePlex

Finally a first release of Opo.Net on CodePlex! Yeah! :-)

With this release it's possible to connect to a POP3 server, recieve messages and convert it into Opo.Net.MailMessage instances. Sending mail messages via SMTP is the next step. I think it's now a lot easier becaus some classes can be reused for the other way round.

A very simple example for using the Pop3Client:

Pop3Client pop3 = new Pop3Client("pop.example.org", 110, "accountName", "password");
pop3.Connect();
pop3.Login();
string mimeData = pop3.GetMessage(1) // recieve first message on server
pop3.Logout();
pop3.Disconnect(); 
IMailMessageConverter converter = new MimeMailMessageConverter();
IMailMessage message = converter.ConvertFrom(mimeData);

Console.WriteLine("Subject: " + message.Subject);
Console.WriteLine("From: " + message.From.ToString());
Console.WriteLine("To: " + message.To.ToString());
Console.WriteLine("");
Console.WriteLine(message.Body);

This will output something like this:

Subject: Test message
From: "Example Email 1" <email1@example.org>
To: "Example Email 2" <email2@example.org>, "Example Email 3" <email3@example.org>

This is the message body.

Posted by: Dave
Posted on: 11/7/2008 at 10:17 PM
Tags: , , , , Categories: Actions: E-mail | Kick it! | DZone it! | del.icio.us
Post Information: Permalink | Comments (4) | Post RSSRSS comment feed
Administration:

RFC 2822 compliant date parser

You may have read my series of posts on writing a POP3 client in C#. Normally you'll recieve mail messages in MIME format. MIME dates conform to RFC 2822 (http://www.ietf.org/rfc/rfc2822.txt, 3.3):

Example: Mon, 01 Jan 2001 00:00:00 +0100 (non-bold: optional)

It's also possible that the time offset (+0100) has a format like "GMT" or "PST". These are actually obsolete but nonetheless used sometimes. The goal is now to parse these dates and save them as UTC date. So this is the code with some additional comments:

public DateTime ParseRfc2822Date(string dateTime)
{
	// replace alphabetical time zones with numerical
	date = date.ToLower();
	date = date.Replace("bst", "+0100");
	date = date.Replace("gmt", "-0000");
	date = date.Replace("edt", "-0400");
	date = date.Replace("est", "-0500");
	date = date.Replace("cdt", "-0500");
	date = date.Replace("cst", "-0600");
	date = date.Replace("mdt", "-0600");
	date = date.Replace("mst", "-0700");
	date = date.Replace("pdt", "-0700");
	date = date.Replace("pst", "-0800");

	DateTime parsedDateTime = DateTime.MinValue;

	// Regular expression that matches RFC 2822 compliant dates, contains two groups "DateTime" and "TimeZone"
	string pattern = @"(?:(?:Mon|Tue|Wed|Thu|Fri|Sat|Sun), )?";
	pattern += @"(?<DateTime>\d{1,2} (?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) \d{4} \d{2}\:\d{2}(?:\:\d{2})?)";
	pattern += @"(?: (?<TimeZone>[\+-]\d{4}))?";
	Regex r = new Regex(pattern, RegexOptions.IgnoreCase);
	Match m = r.Match(date);
	if (m.Success)
	{
		// Remove preceding "0" that all dates match the same pattern ("d MMM yyyy")
		string dateTime = m.Groups["DateTime"].Value.TrimStart('0');
		
		// Parse the date and time parts (without time zone)
		parsedDateTime = DateTime.ParseExact(dateTime, new string[] { "d MMM yyyy hh:mm", "d MMM yyyy hh:mm:ss" }, CultureInfo.InvariantCulture, DateTimeStyles.None);

		// If time zone is declared, set the offset
		string timeZone = m.Groups["TimeZone"].Value;
		if (timeZone.Length == 5)
		{
			// Create new TimeSpan representing the time zone offset to UTC
			int hour = Int32.Parse(timeZone.Substring(0, 3));
			int minute = Int32.Parse(timeZone.Substring(3));
			TimeSpan offset = new TimeSpan(hour, minute, 0);

			// Set the offset using DateTimeOffset
			parsedDateTime = new DateTimeOffset(parsedDateTime, offset).UtcDateTime;
		}
	}
	return parsedDateTime;
}

You can download this code as part of the Opo.Net project on CodePlex.


Posted by: Dave
Posted on: 11/2/2008 at 5:54 PM
Tags: , Categories: Actions: E-mail | Kick it! | DZone it! | del.icio.us
Post Information: Permalink | Comments (4) | Post RSSRSS comment feed
Administration:

Developing a C# POP3 client: Part 07 - Design considerations and some refactoring

This is number seven in a series of posts on developing a POP3 client in C#. These are the previous posts on this topic:

It's quite a while since my last "real" post about my POP3 client project. Along some other projects I'm working on, I noticed that I had to reconsider some of my design decisions. Some months ago, in March or April I read the (by the way very recommendable) book Head First Design Patterns. One thing that is pointed out very clearly is to "coding to interfaces, not to concrete implementations". While I had that in mind I didn't get an overall picture of the project and which interfaces and classes are really needed and how they relate. In the last couple of weeks I also dealt with Dependency Inversion, Inversion of Control and Dependency Injection.

So I made some thoughts about how to achieve a larger flexibility and less "hard coded" dependencies between the different classes. Also another fact forced me to rethink my design. While programming the code for converting string MIME data to MailMessage classes, I came at a point where I had to add a reference in Opo.Net.Mime to Opo.Net.Mail but got the error "circular reference" because I already had a reference from Opo.Net.Mail to Opo.Net.Mime:

I decided to change the MimeEntity to only hold string values and to add a MailMessageConverter class that then converts the MimeEntity to a MailMessage. Furthermore I wanted to let the user plug in custom MIME parsers and converters for other formats (e.g. XML). For that purpose I introduced the interfaces IMimeParser and IMailMessageConverter. For now I implemented the RegexMimeParser class (uses regular expression to parse the MIME data), a MimeMailMessageConverter (not fully implemented now) and a XmlMailMessageConverter.

Lets have a look at some code samples:

// download a message from a server and then convert it to MailMessage instance
using Opo.Net.Mail;
using Opo.Net.Mime; 

// MimeMailMessageConverter uses default IMimeParser (RegexMimeParser)
public void LoadMessage()
{
	Pop3Client pop3Client = new Pop3Client("example.org", 25);
	string mimeData = pop3Client.GetMessage(1);
	IMailMessageConverter mailMessageConverter = new MimeMailMessageConverter();
	IMailMessage mailMessage = mailMessageConverter.ConvertFrom(mimeData);
}

// MimeMailMessageConverter uses custom IMimeParser
public void LoadMessage()
{
	Pop3Client pop3Client = new Pop3Client("example.org", 25);
	string mimeData = pop3Client.GetMessage(1);
	IMailMessageConverter mailMessageConverter = new MimeMailMessageConverter();
	IMimeParser mimeParser = new RegexMimeParser();
	IMailMessage mailMessage = mailMessageConverter.ConvertFrom(mimeParser, mimeData);
}

The MimeMailMessageConverter uses IMimeParser and IMimeEntity internally. May the converter interface will change to something like this, but I'm not sure if this makes more sense...

public interface IMailMessageConverter
{
	IMailMessage ConvertFrom(ref IMailMessage, object data);
	object ConvertTo(ref IMailMessage, IMailMessage mailMessage);
	IMailMessage LoadFromFile(string path);
	string SaveToFile(IMailMessage mailMessage, string path);
	string SaveToFile(IMailMessage mailMessage, string path, string fileName);
}

This is what the dependencies look now:

Get the updated source on Codeplex: http://www.codeplex.com/OpoNet


Posted by: Dave
Posted on: 10/21/2008 at 8:40 PM
Tags: , , Categories: Actions: E-mail | Kick it! | DZone it! | del.icio.us
Post Information: Permalink | Comments (2) | Post RSSRSS comment feed
Administration:

Developing a C# POP3 client: Now on CodePlex

It was quite still the last few months. But now I'm back with some news about my POP3 client project. Since this little project grows and it's a bit cumbersome to always make the zip file and upload it to the blog, I decided to host it on ClodePlex. You'll find it here: http://www.codeplex.com/OpoNet

I also made some changes since my last blog post about this topic. It's mainly some additional classes like MailMessage, MailAddress etc., which I decided to implement myself rather than using the built-in ones. I thought about writing a decorator to extend it or inherit from System.Net.Mail.MailMessage. But my idea of MailMessage is slightly different from what's already in the framework.


Posted by: Dave
Posted on: 9/6/2008 at 4:55 PM
Tags: , , Categories: Actions: E-mail | Kick it! | DZone it! | del.icio.us
Post Information: Permalink | Comments (2) | Post RSSRSS comment feed
Administration:

Developing a C# POP3 client: Part 06 - Parsing MIME Messages

This is number six in a series of posts on developing a POP3 client in C#. These are the previous posts on this topic:

Two possibilities

There are actualy two options when writing a MIME parser. The first one is to download the whole message and then parse the content with regular expressions, the second option is to parse the message line by line while downloading. I'm not sure if there are some major advantages of one of the two possibilities. I could imagine that regarding performance the line by line method would be better, but I decided to take the regex approach because I think, it's easier to handle faulty MIME messages.

Parsing the message

First I shortly outline how the message parsing will happen. A MIME message consists of different MIME entities (as mentioned in my last post) which can hold some other entities, which are separated by a "boundary". This boundary string is defined in the header section of each entity (if it's a multipart entity). So first we have to parse the boundary string, then split the message so we get each part separated. We repeat this step with each part that has a content type of "multipart...".

Parsing Headers

MIME message headers are always formatted in the following way:

HeaderName: Header Value

There are some headers which hold more than one value, for example the "Content-Type" header what gives then the following format:

Content-Type: multipart/mixed;
      boundary="----=_NextPart_000_001A_034CE23.234EB34

As mentioned in my previous post, there are some standard headers, but there could also be headers appended by mail clients or spam filters. This means that there are some headers where we only want to get the value because we just know the name of the header. But there are also headers, we have no clue about. So there are two methods:

// parsing a "known" header 
public string ParseHeader(string mimeMessage, string headerName)
{
	if (String.IsNotNullOrEmpty(mimeMessage) && String.IsNotNullOrEmpty(headerName))
	{
		Regex r = new Regex(headerName + @":\s+(?<HeaderValue>.*\n");
		Match m = r.Match(mimeMessage);
		if (m.Groups["HeaderValue"] != null)
		return m.Groups["HeaderValue"].Value.Trim();
	}
	return "";
}

// parsing all headers
// the regex matches single line and double line headers
public Dictionary<string, string> ParseHeaders(string mimeMessage)
{
	Dictionary<string, string> headers = new Dictionary<string, string>();
	if (String.IsNotNullOrEmpty(mimeMessage))
	{
		Regex r = new Regex(@"(?<HeaderName>[^\r\n:]+):\s+(?<HeaderValue>(.+[\r\n][\t\x20]+.+)|(.+))");
		MatchCollection m = r.Matches(rawMessage);
		foreach (Match match in m)
		{
			headers.Add(match.Groups["HeaderName"].Value.Trim(), match.Groups["HeaderValue"].Value.Trim());
		}
	}
	return headers;
} 

Parsing the MIME entities

To show you how basically works I'll give you a simplified "MimePart". In the code I provide for download in my next post, there are some other properties and methods, but for this example this works fine:

public class MimePart
{
	public string ContentType { get; set; }
	public string Content { get; set; }
	public Dictionary<string, string> Headers { get; set; }
	public List<MimePart> Parts { get; set; }
}

Let's assume we get the raw MIME message data from the POP3 client. After recieving we iterate recursively through the MIME entities:

string rawMimeData = Pop3.GetMessage(32);

MimePart message = GetMimePart(rawMimeData);

private MimePart GetMimePart(string rawData)
{
	MimePart part = new MimePart();
	part.Parts = new List<MimePart>();
	part.ContentType = ParseContentType(rawData);
	if (part.ContentType.StartsWith("multipart"))
	{
		// split into parts
		string boundary = GetBoundary(mimeData); 
		Regex r = new Regex(@"[\s]*--" + boundary + @"[\s.]*\n", RegexOptions.IgnoreCase | RegexOptions.Multiline); 
		string[] rawParts = r.Split(mimeData);
		part.Headers = parseHeaders(part[0]);
		for (i = 1; i < rawParts.Length; i++)
		{
			if (part[i].Trim() != "")
			part.Parts.Add(GetMimePart(part[i]));
		}
	}
	else
	{
		part.Headers = parseHeaders(rawData);
		part.Content = rawData.Substring(rawData.IndexOf("\r\n\r\n"));
	}
	return part;
} 

That's basically all we have to do. Sure, this MimePart is not very comfortable to use, there's some need to decode encoded parts like attachments or get the plain text or html version of the body. Also basic data like the sender's email address or the subject of the message are not easily accessable. But at least the MIME message has now a structure which we can work with.


Posted by: Dave
Posted on: 5/18/2008 at 8:12 PM
Tags: , Categories: Actions: E-mail | Kick it! | DZone it! | del.icio.us
Post Information: Permalink | Comments (2) | Post RSSRSS comment feed
Administration:

Developing a C# POP3 Client: Part 05 - MIME Messages

This is number five in a series of posts on developing a POP3 client in C#. Take a look at the previous ones:

MIME - Multipurpose Internet Mail Extensions

With the POP3 client developed so far, we are able to download mail messages from the server. We receive these messages in plain text, not very comfortable to read and definively not unsuitable for displaying in an application. First these messages may look a bit chaotic but in fact they are sort of object oriented.

I'll now overview the structure of MIME messages in short. If you'd like some more detailed info I recommend you to read the MIME article on Wikipedia and the RFCs related to MIME, starting with RFC 2045.

A MIME messages consists of one or several parts called entities. Each entity has some headers and a body part which can either hold some content like the message or an attachment, or other entities.

There's only one header which occurs in every header: Content-Type. This can be any kind of Internet Media Type like text/html, image/gif or audio/mpeg. There are two types you may are not familiar with but are important for MIME messages. These are multipart/mixed and multipart/alternative.

Multipart/mixed defines an entity which contains other entities of various content and content types. Multipart/alternative is used to give a alternative view of the same content as plain text message or HTML formatted message.

The sample MIME message shown in the figure below is a message with the content type "multipart/mixed" which contains a multipart/alternative part with a plain text and the HTML view and two attachments:

MIME Message structure

So I said that MIME is some sort of object oriented. Each box in the figure is an object with some headers and some content or a collection of other object of the same type (maybe not the same content type, but always with a header and some content or a collection or other objects of the same type :-)

MIME Headers

The headers section of a MIME entity is composed as follows (example):
From: <sender@example.org>
To: "Recipient" <recipient@example.org>
Subject: Here comes the subject of the email
Date: Fri, 16 Mai 2008 20:23:48 +0100
MIME-Version: 1.0
Content-Type: multipart/mixed;
      boundary="-----=_NextPart_000_001A_123849.12A98DE
X-Priority: 3
...

It's always the name of the header followed by a colon and a value. Maybe you have noticed the indention seventh line. The Content-Type header can contain more than one value, so after the first one there's a semicolon and the second value is on the next line with some whitespace(s) at the beginning.

So parsing the headers should really not be very difficult.

Multipart/...

Entities with a Content-Type of "multipart/..." must define a boundary string which is used to separate the different parts of the multipart entity. It can exists of whatever characters but it must be unique in the message. MIME entities are always surrounded by these boundary strings with a precedent "--". So for two parts there are three boundaries, one before the first part, one after the second and one between them.

...
Content-Type: multipart/mixed;
      boundary="-----=_NextPart_000_001A_123849.12A98DE
...

-------=_NextPart_000_001A_123849.12A98DE
Content-Type: multipart/alternative;
      boundary="-----=_NextPart_001_001B_159832.124F987

-------=_NextPart_001_001B_159832.124F987
Content-Type: text/plain;
      charset="iso-8859-1"
Content-Transfer-Encoding: quoted-printable

This is the plain text content of the message.

-------=_NextPart_001_001B_159832.124F987
Content-Type: text/html;
      charset="iso-8859-1"
Content-Transfer-Encoding: quoted-printable

<html><body>
<p>This is the html text content of the message.<p>
</body></html>
-------=_NextPart_001_001B_159832.124F987

-------=_NextPart_000_001A_123849.12A98DE
Content-Type: image/gif;
      name="image.gif"
Content-Transfer-Encoding: base64
Content-Disposition: attachment;
      filename="image.gif"

Nt2YcPes8V1lfZc7GvoeAMEBrhps15YA9K6EABRe8VivewAIvwIAbO08iC/n4raleCZIXtuFi7wB
Aj8QFwx+8JXjSN8K0DB5JT5ecpk/QOK15bgleD7rjOtcOTBPOQCE7geaD3zoO1f6AAjw8AsIAADL
HQPSmc4anN/26QAo7wkKAAACVP2xHbc2ym8bAAB0HdXlDTvIQxB1rDNH67slLG99LnconL3ub996
/3fRq371rG+9618P+9jLfva0r73tb4/73Ot+97zvve9/D/zgC3/4xC++8Y+P/ORDPAEAOw==

-------=_NextPart_000_001A_123849.12A98DE

Content-Transfer-Encoding

quoted-printable

Quoted-printable is an encoding using printable characters. Characters other than alphanumerics are encoded using the "=" and a hexadecimal double figure which represents the character's numeric value. For more details have a look at Quoted-printable on Wikipedia. For decoding a quoted-printable encoded text we only have to search the text for =xx where xx is a hexadecimal number and replace it with the appropriate character:

//... 
Regex hexRegex = new Regex(@"(\=([0-9A-F][0-9A-F]))", RegexOptions.IgnoreCase); 
content = hexRegex.Replace(content, new MatchEvaluator(HexMatchEvaluator)); 
//... 
private static string HexMatchEvaluator(Match m) 
{ 
    int dec = Convert.ToInt32(m.Groups[2].Value, 16); 
    char character = Convert.ToChar(dec); 
    return character.ToString(); 
} 

base64

Base64 encoding is mostly used for attachments. For more details on how it works, read the Base64 article on Wikipedia. I'll just show you the code to decode a base64 encoded string. The easiest way may be the following:

public static T Base64Deserialize<T>(string s) 
{ 
    using (MemoryStream ms = new MemoryStream(Convert.FromBase64String(s))) 
    { 
        return (T)new BinaryFormatter().Deserialize(ms); 
    } 
} 

Parsing MIME messages

These are the basics you have to know about MIME messages. Now we could begin to parse the messages and bring them in a usable format - and this will be the topic of my next post. Just have a little patience. :-)


Posted by: Dave
Posted on: 5/16/2008 at 10:49 PM
Tags: , , , Categories: Actions: E-mail | Kick it! | DZone it! | del.icio.us
Post Information: Permalink | Comments (8) | Post RSSRSS comment feed
Administration:

Argument Validation

Some days ago Fredrik Normén wrote a post about how to validate a method's arguments. The validation methods or even using extension methods is not the problem, but offering a useful error message, when the argument is not valid and an exception is thrown. For such an exception we need the name of the argument. He brings up a bunch of ideas, but at the end he concentrates on two approaches. One takes the argument name as an argument to the validation method, the other one uses reflection to get the argument's name.

Each of these two approaches has a drawback. With the first one, you have to declare the name of the argument in each validation method, what's really painful. The second one is easier to use but reflection is not the best solution regarding performance. While I do not think, that this would really hurt most of the application that use such validation and as a commenter noted, the methods could be written in a way reflection only is used in case of validation failure, I made some thoughts about this validation thing.

I started with the first approach, where I think it's mainly a problem, if you want to chain some validation methods (using extension methods for validation). This would lead to some unesthetic statements like so:

argumentToValidate.IsRequired("argumentToValidate").IsShorterThan(20, "argumentToValidate").IsUpperCase("argumentToValidate"); 

So, if we could shorten this to something like in the code below, I think it would be acceptable.

argumentToValidate.IsRequired("argumentToValidate").IsShorterThan(20).IsUpperCase(); 

And what's really nice, is the fluent validation by using extension methods.  

My approach to a fluent validation

So how to accomplish that? I thought about the following:

// a generic object that holds the argument's value and the argument's name
public class Validator<T>
{
	public T Argument { get; set; }
	public string ArgumentName { get; set; }
	public Validator(T argument, string argumentName)
	{
		this.Argument = argument;
		this.ArgumentName = argumentName;
	}
}

// extension method which returns a Validator object
public static Validator<T> Validate<T>(this T argument, string argumentName)
{
	if (argument == null) 
		throw new ArgumentNullException(argumentName);
	return new Validator<T>(argument, argumentName);
} 

// some validation methods (also extension methods to make the validation fluent). 
public static Validator<int> GreaterThan(this Validator<int> v, int value)
{
	if (v.Argument <= value)
		throw new ArgumentException(String.Format("{0} must be greater than {1}", v.ArgumentName, value), v.ArgumentName);
	return v;
}

public static Validator<int> InRange(this Validator<int> v, int min, int max)
{
	if (v.Argument < min || v.Argument > max)
		throw new ArgumentException(String.Format("{0} must be between {1} and {2}", v.ArgumentName, min, max), v.ArgumentName);
	return v;
} 

Using this approach we can write validation as follows:

int primeNumber = 43;
primeNumber.Validate("primeNumber").GreaterThan(40).Odd().PrimeNumber(); 

While the Validate() method is the same for all types of objects it returns a type specific Validator<T> which has the effect that only appropriate validation extension methods are shown by intellisense.

Conclusion

I'm not sure, if this brings really some advantage. In some cases where you only need one validation method it may adds some code. But I think, it's a clean approach to validating arguments. What do you think about this?

Edit:

Should have read all comments. Roger Alsing came up with almost the same solution. One addition he makes, is the [DebuggerHidden] attribute on the validation methods, which has the effect, that it will break on the method which performs the Validate call, and not in the validation method.


Posted by: Dave
Posted on: 5/12/2008 at 10:07 PM
Tags: , Categories: Actions: E-mail | Kick it! | DZone it! | del.icio.us
Post Information: Permalink | Comments (7) | Post RSSRSS comment feed
Administration:

Developing a C# POP3 Client: Part 04 - Somewhat more comfort

This is number four in a series of posts on developing a POP3 client in C#. These are the previous ones:

  1. Developing a C# POP3 client: Part 01 - POP3 commands
  2. Developing a C# POP3 client: Part 02 - The SimplePop3Client
  3. Developing a C# POP3 client: Part 03 - Session States, Exception Handling, SSL

New methods for easier handling

The POP3 client works quite well for now and it could also be used in other projects. But it's handling is a bit cumbersome. Normally you don't want to handle each POP3 command and then parse it's response. So why not introduce some methods to simplify the common actions as Login() for USER() and PASS().

It also makes more sense to return a boolean value instaed of the original server response for commands like USER, PASS, NOOP, DELE and QUIT. Since there are many checks, if the response starts with "+OK" or "-ERR", I used two extensions methods which are extremely simple (I know) but very handy.

public static bool IsOK(this string s)
{
    return s.StartsWith("+OK");
}

public static bool IsErr(this string s)
{
    return s.StartsWith("-ERR");
} 

Login methods

There are several methods for logging in to the mail server. They are really simple therefor I'll show you only the most advanced here.

public bool Login(string username, string password, bool useApop)
{
    bool loggedin = false;
    if (useApop && this.ApopTimestamp.IsNotNullOrEmpty()) //IsNotNullOrEmpty() is an extension method
    {
        // use apop login
        if (APOP(username, password).IsOK())
        {
            loggedin = true;
        }
    }
    else
    {
        // use user/pass login
        if (USER(username).IsOK())
        {
            if (PASS(password).IsOK())
            {
                loggedin = true;
            }
        }
    }
    return loggedin;
} 

Because we implemented the session state handling last time, we don't have to set a LoggedIn property something similar. All that is done in the standard POP3 command methods.

Messages

Getting messages is as simple as calling the RETR() method. The headers of a message can be retrieved with TOP(messageNumber, 0). The size we get with LIST(messageNumber) and the unique id with UIDL(messageNumber). DELE(messageNumber) for deleting a message from the server is also pretty easy. So there's nothing to it! But there is something to keep in mind when downloading messages.

Download a message is easy but...

As I said above this is a really simple one: RETR(messageNumber) and you get a string with the message. The only little problem is, that it's the raw text of (normally) a MIME message (MIME = Multipurpose Internet Mail Extension). And that's the really hard part of developing a useful POP3 client: parsing the MIME message. And that will be the topic of my next post in this series.

To delete or not to delete...

It may depend on the mail server's configuration but usually messages that were downloaded are deleted when QUIT command is sent. Thus I introduced a property called "DeleteMessagesAfterDownload" which indicates whether this should be done or not. To prevent the automatic deletion on the server the RSET command can be sent befor quitting. So far, so good. But with sending RSET to the server not only the state of the downloaded messages is resetted but the whole session. This means that also messages marked for deletion with the DELE command will be unmarked.

To solve this there's actually only one possiblity: Remember which messages must be deleted, do a reset, delete the messages again and then quit the session.

// ...
private List<int> deletedMessageNumbers= new List<int>;

// ...
public bool DeleteMessage(int messageNumber)
{
    if (DELE(messageNumber).IsOK())
    {
        deletedMessageNumbers.Add(messageNumber);
        return true;
    }
    else
        return false;
} 

public bool Disconnect()
{
    if (!DeleteMessagesAfterDownload)
    {
        RSET();
        foreach (int messageNumber in this.deletedMessageNumbers)
        {
            DELE(messageNumber);
        }
     }
    else
    {
        foreach (int messageNumber in this.downloadedMessageNumbers)
        {
            DELE(messageNumber);
        }
    }
    return QUIT().IsOK();
}

Mailbox stats

In this version of the Pop3Client I implemented two more properties:

  • Mailbox
  • Messages

The Mailbox property contains values for the numbers of messages on the server and the total size of all messages. These values are retrieved right after login using the method STAT().

Messages is a collection of MessageInfo objects which have three properties: MessageNumber, Size, UniqueID. These values are also updated immediately after the successful login. This collection is mainly useful if want to use the unique id of the messages to retrieve or delete them. And that's no bad idea because this is the only clue we have about a specific message. Messabe numbers will change after disconnecting from the server, unique ids won't. For the messages info first the LIST command is sent. After storing these values for each entry the GetUid(messageNumber) method is called which uses the UIDL <messageNumber> command. I first tried to make two collections - one with message numbers and sizes and one with message numbers and unique ids - and then to combine them. But funny enough that took twice as much time.

private void UpdateMailboxStats()
{
    string response = "";
    bool success = SendCommand("STAT");
    if (success && (response = GetSingleLineResponse()).IsOK())
    {
        int numberOfMessages = -1;
        int mailboxSize = -1;
        string[] values = response.Split(' ');
        int.TryParse(values[1], out numberOfMessages);
        int.TryParse(values[2], out mailboxSize);
        this.Mailbox = new MailboxInfo(numberOfMessages, mailboxSize);
        return true;
    }
    else
    {
        this.Mailbox = new MailboxInfo(-1, -1);
        return false;
    }
} 

public bool UpdateMessagesInfo()
{
    bool messagesUpdated = false;
    MessageInfoCollection messagesInfo = new MessageInfoCollection();
    string response = "";
    // get message numbers and message sizes
    bool success = SendCommand("LIST");
    if (success && GetSingleLineResponse().IsOK())
    {
        while (ProcessMultiLineResponse(out response))
        {
            int messageNumber = -1;
            int messageSize = -1;
            string[] values = response.Split(' ');
            int.TryParse(values[0], out messageNumber);
            int.TryParse(values[1], out messageSize);
            messagesInfo.Add(new MessageInfo(messageNumber, messageSize, ""));
        }
        foreach (MessageInfo mi in messagesInfo)
        {
            mi.UniqueID = GetUid(mi.MessageNumber);
        }
        this.messages = messagesInfo;
        messagesUpdated = true;
    }
    else
    {
        this.messages = messagesInfo;
    }
    return messagesUpdated;
} 

public bool Login(string username, string password, bool useApop)
{
    //...
    if (loggedin)
    {
        UpdateMailboxStats();
        UpdateMessagesInfo();
    }
    return loggedin;
} 

Events

Perhaps it's useful sometime to get information about session state changes. So I introduced two events: SessionStateChanging (right before the session state is changed) and SessionStateChanged (after the session state has changed).

//...
public event Pop3EventHandler SessionStateChanging;
private void OnSessionStateChanging(Pop3EventArgs e)
{
    if (SessionStateChanging != null)
        SessionStateChanging(this, e);
}

public class Pop3EventArgs : EventArgs
{
    public Pop3Client.Pop3SessionState SessionState { get; set; }
    public Pop3EventArgs(Pop3Client.Pop3SessionState currentState)
    {
        this.SessionState = currentState;
    }
}

//...
private void SetSessionState(Pop3SessionState state)
{
    OnSessionStateChanging(new Pop3EventArgs(this.State));
    this.State = state;
    OnSessionStateChanged(new Pop3EventArgs(this.State));
} 

Code download

Grab the code, test it, look at it and write a comment on what you think about: Pop3Client.zip (9.28 kb)


Posted by: Dave
Posted on: 5/10/2008 at 12:42 AM
Tags: , Categories: Actions: E-mail | Kick it! | DZone it! | del.icio.us
Post Information: Permalink | Comments (17) | Post RSSRSS comment feed
Administration:

Developing a C# POP3 client: Part 03 - Session States, Exception Handling, SSL

In my last post I described how to build a simple POP3 client in C#. We implemented the standard POP3 commands like authenticating, receiving stats, retrieving and deleting messages. In this post I'll update the SimplePop3Client and implement some exception handling and pop3 session states.

POP3 session states

During a POP3 session there are the following different states:

  • Authorization
  • Transaction
  • Update

Authorization state

As soon as the connection to the mail server has been established, the session is in Authorization state. This is the state where the client is authenticating with the commands USER and PASS or APOP.

Transaction state

For all manipulations like retrieving stats, listing and retrieving emails and marking messages for deletion. If you try to use these commands when the session is not in transaction state, the server response will be "-ERR Invalid state".

Update state

As soon as the QUIT command is sent the session state is set to Update. That means that now the messages marked for deletion are really deleted. Afterwards the client is being disconnected from the mail server.

Implementing session states support

First we need a Pop3SessionState enumeration:

public enum Pop3SessionState
{
	undefined = 0
	Disconnected = 1
	Authorization = 2
	Update = 3
} 

There are actually four places where the session state must be updated.

public string Connect()
{
	// connect to the server
	SetSessionState(Pop3SessionState.Authorization);
} 

public string PASS()
{
	//...
	// if PASS was successful update session state
	SetSessionState(Pop3SessionState.Transaction);
} 

public string APOP()
{
	//...
	// if APOP was successful update session state
	SetSessionState(Pop3SessionState.Transaction);
} 

public string QUIT()
{
	SetSessionState(Pop3SessionState.Update);
	// send QUIT command here
	SetSessionState(Pop3SessionState.Disconnected);
} 

For updating the session state I use a function (SetSessionState) thus it's easier to implement session state update events if needed.

As we know each command needs a specific state. So before sending a command we can now test if the current state is adequate. For this purpose we use the function EnsureStateSession:

private bool EnsureSessionState(string command)
{
	switch (command.Substring(0, 4).ToLower())
	{
		case "user":
		case "pass":
		case "apop":
			return (this.State == Pop3SessionState.Authorization);
		default:
			return (this.State == Pop3SessionState.Transaction);
	}
} 

Some other improvements

Exception handling

Dealing with network connection there are pretty much potential for errors. Thus I updated the Pop3Client with some exception handling and the custom exception Pop3Exception.

public class Pop3Exception : Exception
{
	public Pop3Exception() { }
	public Pop3Exception(string message) : base(message) { }
	public Pop3Exception(string message, Exception innerException) : base(message, innerException) { }
} 

SSL connection

Implementing the possibility to use a SSL connection is really easy:

public string Connect()
{
	//...

	client = new TcpClient(this.Host, this.Port);

	// connect to the client
	client = new TcpClient(this.Host, this.Port)
	if (this.UseSSL)
	{
		try
		{
			// get ssl stream
			stream = new SslStream(client.GetStream(), false);

			// authenticate
			((SslStream)stream).AuthenticateAsClient(this.Host);
		}
		catch (Exception e)
		{
			throw new Pop3Exception(String.Concat("Host found but SSL authentication failed. May your mail server does not support SSL.", Environment.NewLine, "Error: ", e.Message), e.InnerException);
		}
	}
	else
	{
		stream = client.GetStream();
	}

	//...
} 

Updated code

Download the Pop3Client Solution: Pop3Client.zip (7.20 kb) (Yes, I renamed it from SimplePop3Client since it's that simple any more ;-)


Posted by: Dave
Posted on: 5/3/2008 at 12:18 AM
Tags: , Categories: Actions: E-mail | Kick it! | DZone it! | del.icio.us
Post Information: Permalink | Comments (11) | Post RSSRSS comment feed
Administration:

Developing a C# POP3 client: Part 02 - The SimplePop3Client

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:

SimplePop3Client class diagram

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:

  1. Send command and check if it was successful
  2. 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.


Posted by: Dave
Posted on: 4/30/2008 at 12:57 AM
Tags: , Categories: Actions: E-mail | Kick it! | DZone it! | del.icio.us
Post Information: Permalink | Comments (12) | Post RSSRSS comment feed
Administration: