Opo Perspective on CodePlex

After I released the 0.1 version of Opo.Net (http://www.codeplex.com/OpoNet) I finally started with my original project: Opo Perspective - An ASP.NET MVC Webmail Client. I wrote my first post about this project back in June this year. Yep, that's a long time and I'm very excited that I really made it and published some Perspctive code on CodePlext (http://www.codeplex.com/OpoPerspective).

Posted by Dave on 12/26/2008 at 9:34 PM
Tags: , ,
Categories: .NET | ASP.NET MVC | Opo Perspective
Actions: E-mail | Kick it! | DZone it! | del.icio.us
Post Information: Permalink | Comments (1) | Post RSSRSS comment feed

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 on 11/7/2008 at 10:17 PM
Tags: , , , ,
Categories: .NET
Actions: E-mail | Kick it! | DZone it! | del.icio.us
Post Information: Permalink | Comments (2) | Post RSSRSS comment feed

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 on 10/21/2008 at 8:40 PM
Tags: , ,
Categories: .NET
Actions: E-mail | Kick it! | DZone it! | del.icio.us
Post Information: Permalink | Comments (0) | Post RSSRSS comment feed

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 on 5/18/2008 at 8:12 PM
Tags: ,
Categories: .NET | Opo Perspective
Actions: E-mail | Kick it! | DZone it! | del.icio.us
Post Information: Permalink | Comments (0) | Post RSSRSS comment feed

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 on 5/16/2008 at 10:49 PM
Tags: , , ,
Categories: Opo Perspective | .NET
Actions: E-mail | Kick it! | DZone it! | del.icio.us
Post Information: Permalink | Comments (3) | Post RSSRSS comment feed

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 on 5/10/2008 at 12:42 AM
Tags: ,
Categories: Opo Perspective | .NET
Actions: E-mail | Kick it! | DZone it! | del.icio.us
Post Information: Permalink | Comments (0) | Post RSSRSS comment feed

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 on 5/3/2008 at 12:18 AM
Tags: ,
Categories: Opo Perspective | .NET
Actions: E-mail | Kick it! | DZone it! | del.icio.us
Post Information: Permalink | Comments (3) | Post RSSRSS comment feed

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 on 4/30/2008 at 12:57 AM
Tags: ,
Categories: Opo Perspective | .NET
Actions: E-mail | Kick it! | DZone it! | del.icio.us
Post Information: Permalink | Comments (1) | Post RSSRSS comment feed

Developing a C# POP3 client: Part 01 - POP3 commands

For my project Opo Perspective I needed a POP3 client component for retrieving the emails from a mail server. Searching the web for some info or free/open source component I did not find any complete component ready for use, or at least none that satisfies my needs. But I stumbled upon some nice articles about the topic:

After studying these articles and some others I decided to write my own POP3 client. First it seemed not very complicated, and indeed the pure POP3 client is relatively easy the hard part is parsing the MIME messages.

POP3 commands

First of all you have to understand what the communication between the client and the server looks like. There are only a few commands, and some of them are optional.

All responses from the server start with either "+OK" or "-ERR".

USER <username>
Sends the username for authentication (username=email address). The server should, because of security issues, always answer with "+OK" even if the username is wrong.

Example:

USER accountname@domain.com
+OK

PASS <password>
Used right after the USER command to finish the authentication.

Example:

PASS password
+OK

STAT
Returns the number of messages on the server and their overall size in bytes.

Example:

STAT
+OK 5 8359

LIST
Lists message number and size of each message on the server.

Example:

LIST
+OK  4 3489665
1 1976
2 1977
3 1975
4 3483737
.

RETR <messages number>
Retrieve the message specified by the <message number>. The retrieved message is automatically marked for deletion.

Example:

RETR 1
+OK 1976 octets
[all headers and message data, MIME format]
.

DELE <message number>
Marks a message for deleting. The deletion occurs not till you send the QUIT command.

Example:

DELE 1
+OK

RSET
Resets the session to its initial state. All "deleted" messages are "undeleted".

Example:

RSET
+OK

NOOP
Does not really do something. Returns "+OK" when the client is authenticated, returns "-ERR" when connected but not authenticated.

Example:
NOOP
-ERR Invalid state

QUIT
Quits the current session and deletes the messages marked for deletion.

Example:

QUIT
+OK GOODBYE

APOP <username> <digest> (optional command)
APOP is an alternative authentication command. If the mail server supports APOP, after connecting the server responds with a message like that:

+OK Welcome to the Mail Server <16587.654715@FKEPO3482-34>

<16587.654715@FKEPO3482-34> is the timestamp of the server and can be used for the APOP login where the password is not sent as plain text. Using the APOP command needs generating the <digest> which is a MD5-Hash of the timestamp (including the angle-brackets) combined with the password. In this example that might be "<16587.654715@FKEPO3482-34>password" which gives us an MD5-Hash of "fc1997823d6e890693b8b2fd12f084ec".

Example:

+OK Welcome to the Mail Server <16587.654715@FKEPO3482-34>
APOP <accountname@domain.com> fc1997823d6e890693b8b2fd12f084ec
+OK

TOP <message number> [<number of lines>] (optional command)
Retrieve only the the first <number of lines> lines of the message specified by the <message number>. The headers are always returned the <number of lines> only refer to the message "body". If <number of lines> is not specified the server returns only the headers.

Examples:

TOP 1
+OK
[headers of message 1]
.
TOP 1 3
+OK
[headers of message 1]
-------=_Part_7245_2349853.2340985098374
Content-Type: text/plain; charset=ISO-8859-1
Content-Transfer-Encoding: 7bit
.

UIDL [<message number>] (optional command)
Returns a list of the message numbers and some server internal unique id. If you specify the <message number> only the uid of the correlating message is returned.

Examples:

UIDL
+OK
1 4239F2D2DED94A21A801F4A14796B0C0
2 16CAE91D6A9B488E89587A225EAC8F3E
3 A7FE63D715B849489B3EE60903AD9E94
4 83CA8D76A6EB4229B6D6F501BABCFD13
.
UIDL 1
+OK
1 4239F2D2DED94A21A801F4A14796B0C0
  

Pop3 using telnet

To try the above commands out, you can use telnet. Open cmd.exe and type the following command:

telnet pop.yourmailserver.com 110

110 is the standard port for POP3 servers but it may differ on yours. After connecting you can play around with the commands. Even DELE you can use. But take care to user the RSET command before QUIT. The messages are only deleted if the session is properly quitted by the QUIT command. Loosing the connection will not delete them.

No C# code yet?

Yes, you're right. The post got longer than I thought, thus I'll move the code for a really simple C# Pop3 Client to my next post: Developing a C# POP3 client: Part 02 - The SimplePop3Client


Posted by Dave on 4/29/2008 at 10:16 PM
Tags:
Categories: Opo Perspective
Actions: E-mail | Kick it! | DZone it! | del.icio.us
Post Information: Permalink | Comments (4) | Post RSSRSS comment feed