This is the second in a series of
articles on Internet programming with Microsoft's new C# programming language.
In the first article, I wrote a simple SMTP class. In this article, I'm going to write a simple POP3 class. The SMTP
class that I wrote was not very useful, except maybe as an exercise, as there
already exists a similar SMTP class in the Web.Mail
namespace of the dot-NET framework called SmtpMail.
Our POP3 class in this article will be a little more useful as it doesn't
already exist in the dot-NET framework. I have encountered many POP3 C# classes
in my searches of the Internet and most were sufficient to begin programming
email clients.
I usually begin writing new classes by
introducing an exception class that I can use to throw and catch all exceptions
of the class.
Listing Error! Bookmark not defined.: POP3
Exception Class
public class Pop3Exception : System.ApplicationException
{
public Pop3Exception(string str)
:base(str)
{
}
};
I will not explain the exception class,
but rather I expect the reader have enough expertise with C# to understand this
exception class before reading the rest of the article.
Next I created a small class that defines
a POP3 message.
Listing Error! Bookmark not defined.: POP3
Message Class
public class Pop3Message
{
public long number;
public long bytes;
public bool retrieved;
public string message;
};
When you retrieve lists of POP3 messages
from a POP3 server, the list includes a message number and number of bytes. You
can then use the message number to retrieve the message content. You'll see
this later when we define our List and Retrieve methods.
We derive our Pop3 class from the System.Net.Sockets.TcpClient class in the dot-NET
framework.
Listing Error! Bookmark not defined.: POP3
Class Declaration
public class Pop3 : System.Net.Sockets.TcpClient
The TcpClient
class and the other classes in the System.Net.Sockets
namespace of the dot-NET framework are great encapsulations of the familiar
function-oriented socket library.
The first method of our Pop3 class is the
Connect method. This method takes a server name, username and password
parameter to connect to a remote (sometimes local) POP3 server.
Listing Error! Bookmark not defined.: POP3
Connect Method
public void Connect(string server, string
username, string password)
{
string message;
string response;
Connect(server, 110);
response = Response();
if (response.Substring(0,
3) != "+OK")
{
throw new
Pop3Exception(response);
}
message = "USER " + username + "\r\n";
Write(message);
response = Response();
if (response.Substring(0,
3) != "+OK")
{
throw new Pop3Exception(response);
}
message = "PASS " + password + "\r\n";
Write(message);
response = Response();
if (response.Substring(0,
3) != "+OK")
{
throw new
Pop3Exception(response);
}
}
We begin by calling the TcpClient.Connect method passing the server name and the
110 port. The 110 port number is the well known port number for POP3
operations. What that means is that POP3 servers by default should listen for
connections on port 110. When the POP3 server connects to a client, it should
immediately respond with the +OK acknowledgement message. Next we send two
messages, USER and PASS, back to the server. The POP3 server should acknowledge
a successful login by acknowledging both messages. If the POP3 server returns
anything but +OK, then the message will contain the reason for the failure. In
the advent of a failure, I attach that failure message to our exception class
and throw it back to the client.
It should be noted that some POP3 servers
don't require authentication and may reject the calls to USER and PASS. I
haven't encountered such a POP3 server, but the protocol allows it. In those
cases, you'll have to slightly modify the class to make things work.
Any use of our Pop3 class should begin
with a call to Connect and end with a class to Disconnect.
Listing Error! Bookmark not defined.: POP3
Disconnect Method
public void Disconnect()
{
string message;
string response;
message = "QUIT\r\n";
Write(message);
response = Response();
if (response.Substring(0,
3) != "+OK")
{
throw new
Pop3Exception(response);
}
}
The Disconnect method sends a QUIT
message to the POP3 server.
Between the Connect and Disconnect class,
the client may call three other methods, List, Retrieve and Delete, any number
of times. The client will usually begin by calling our List method to retrieve
an array of messages that are queued on the POP3 server.
Listing Error! Bookmark not defined.: POP3
List Method
public ArrayList List()
{
string message;
string response;
ArrayList retval = new ArrayList();
message = "LIST\r\n";
Write(message);
response = Response();
if (response.Substring(0,
3) != "+OK")
{
throw new
Pop3Exception(response);
}
while (true)
{
response =
Response();
if (response == ".\r\n")
{
return retval;
}
else
{
Pop3Message msg
= new Pop3Message();
char[] seps = { ' ' };
string[] values = response.Split(seps);
msg.number = Int32.Parse(values[0]);
msg.bytes = Int32.Parse(values[1]);
msg.retrieved =
false;
retval.Add(msg);
continue;
}
}
}
After sending the LIST message to the
POP3 server, the server will respond with a +OK acknowledgement, followed by
several lines representing one message each and finally by a line with a single
period indicating the end of the messages. Each message line has two numbers,
the first indicating the unique number of the message and the second indicating
the message size in bytes.
Our List method will return a list of
Pop3Message objects. The objects will only contain the message number and size
of each message. In order to retrieve the full message, you can pass the
message object to the Retrieve method. The Retrieve method will then respond
with another Pop3Message containing the message content.
Listing Error! Bookmark not defined.: POP3
Retrieve Method
public Pop3Message Retrieve(Pop3Message rhs)
{
string message;
string response;
Pop3Message
msg = new Pop3Message();
msg.bytes = rhs.bytes;
msg.number = rhs.number;
message = "RETR " + rhs.number
+ "\r\n";
Write(message);
response = Response();
if (response.Substring(0,
3) != "+OK")
{
throw new
Pop3Exception(response);
}
msg.retrieved = true;
while (true)
{
response =
Response();
if (response == ".\r\n")
{
break;
}
else
{
msg.message +=
response;
}
}
return msg;
}
To retrieve a message from a POP3 server,
we send a RETR message with the unique message number. The server then responds
with the +OK acknowledgement, the message content and finally the single period
terminating line.
Retrieving a message does not remove the
message from the POP3 server. A further call to LIST will still return the
message. To remove a message from POP3 server, you have to cal the Delete
method.
Listing Error! Bookmark not defined.: POP3
Delete Method
public void Delete(Pop3Message rhs)
{
string message;
string response;
message = "DELE " + rhs.number
+ "\r\n";
Write(message);
response = Response();
if (response.Substring(0,
3) != "+OK")
{
throw new
Pop3Exception(response);
}
}
The Delete method sends a DELE message
with the message number to the POP3 server. The server will respond with the
+OK acknowledgment message, if successful.
The List, Retrieve and Delete methods used
two private methods, Write and Response, to send and receive messages from the
POP3 server.
Listing Error! Bookmark not defined.: POP3
Write Method
private void Write(string
message)
{
System.Text.ASCIIEncoding en = new
System.Text.ASCIIEncoding() ;
byte[] WriteBuffer
= new byte[1024]
;
WriteBuffer = en.GetBytes(message)
;
NetworkStream stream = GetStream()
;
stream.Write(WriteBuffer,0,WriteBuffer.Length);
Debug.WriteLine("WRITE:" + message);
}
C# native strings, like Java native
strings, are UNICODE. We therefore need to encode and decode the strings to and
from ASCII. After encoding the string, we can then retrieve the socket stream
by calling the TcpClient.GetStream method. I finish
the Write method by called the Debug.Writeline
method. This sends the string to the debug stream for help with debugging.
Listing Error! Bookmark not defined.: POP3
Response Method
private string Response()
{
System.Text.ASCIIEncoding enc = new System.Text.ASCIIEncoding();
byte []serverbuff = new
Byte[1024];
NetworkStream stream = GetStream();
int count = 0;
while (true)
{
byte []buff =
new Byte[2];
int bytes = stream.Read( buff, 0, 1 );
if (bytes == 1)
{
serverbuff[count]
= buff[0];
count++;
if (buff[0] ==
'\n')
{
break;
}
}
else
{
break;
};
};
string retval = enc.GetString( serverbuff, 0,
count );
Debug.WriteLine("READ:" + retval);
return retval;
}
The Response method is similar to the
Write method accept that we retrieve bytes from the stream before decoding
them. Again we call the Debug.WriteLine method to
send the read data to the debug stream and help with debugging.
Note that we have a limitation in the
Response method. We can only retrieve or send up to 1024 bytes at a time. I'll
fix this in a later release. If you intend to use this in production, then
you'll have to do the same first.
Using the new class is pretty easy.
Listing Error! Bookmark not defined.: Usage
static void Main(string[] args)
{
try
{
Pop3 obj = new Pop3();
obj.Connect("mail.xxx.com",
"yyy", "zzz");
ArrayList list
= obj.List();
foreach (
Pop3Message msg in list )
{
Pop3Message msg2 = obj.Retrieve(msg);
System.Console.WriteLine("Message
{0}: {1}",
msg2.number, msg2.message);
}
obj.Disconnect();
}
catch ( Pop3Exception e )
{
System.Console.WriteLine(e.ToString());
}
catch ( System.Exception e)
{
System.Console.WriteLine(e.ToString());
}
}
Instantiate a new object, then call the
List method. The List method will return an array of Pop3Message objects. You
can then iterate through the Pop3Message objects and retrieve each in turn.
Finally, you call the Disconnect method to release the socket.
POP3 is described in RFC 1939. You can
read the full specification from the IETF website
[http://www.ietf.org/rfc/rfc1939.txt?number=1939].
About the Author
Randy Charles Morin is the Chief
Architect of SportMarkets Development from Toronto,
Canada and lives with his wife, Bernadette and two kids, Adelaine
and Brayden in Brampton, Canada. He is the author of
the KBCafe.com website [http://www.kbcafe.com], many programming books and many
articles.