August 23, 2004

POP Client in C#

/*
POPClient class provides some methods to connect to a POP server and work with the emails.
Some functionality is implemented using public methods.
Open - Open Connection to the POP server, Login and Get Status
Close - QUIT and close the streams
Login - Login to the POP mail account
GetStatus - Retrieves the status of the account (Number of mails and total size of the mails (returned as a long array of two elements, first being the message count). It also initialises the member variables that are accessible through properties MessageCount and TotalSize
GetList - Returns ArrayList that has long elements representing the size of each mail
GetHeader - Accepts a message number as argument (starting from 1 and not 0) and returns Hashtable with some common header elements, and entire header as string. e.g. MESSAGEID_FIELD, FROM_FIELD, HEADER_FIELD, etc.
GetMessage - Retrieves given message
DeleteMessage - Deletes given message from the POP server

In addition, there are two generic methods to send other commands to the server.
[b]SendCommand(POPCommands)[/b]
It sends given command to the server without any arguments
[b]SendCommand(POPCommands, string)[/b]
It sends given command to the server with given arguments (a string with blank separated arguments)

These two methods will not raise an exception in case the server returns an error message.

Server returns '[b]+OK[/b]' followed by the result data in case the command is successful.
The result can be of two types, multi-line and single-line.
Multi-line results end with a line having only one character '.', these results do not have any data in the first line (+OK)
e.g. For command "LIST" the result would be

[code]+OK
1 1451
2 10359
.[/code]

An example of single-line result.
For command "STAT":
[code]+OK 2 11810[/code]

All the possible commands are defined in the [b]POPCommands[/b] enum.

[b]multilineCommands[/b] Hashtable stores all the commands that return multi-line result.

In case of an error, the server returns message with prefix "-ERR", e.g.
-ERR not that many messages
(returned if "LIST n" command is given and there are less than n mails in account)
*/

using System;
using System.Collections;
using System.IO;
using System.Net.Sockets;
using System.Text;
using System.Text.RegularExpressions;

namespace Common
{
///


/// Summary description for POPClient.
///

public class POPClient
{
public enum POPCommands
{
USER,
PASS,
QUIT,
STAT,
LIST,
RETR,
DELE,
NOOP,
LAST,
RSET,
TOP,
//RPOP,
}
public const string CRLF = "\r\n";
public const string MESSAGEID_FIELD = "messageid";
public const string FROM_FIELD = "from";
public const string FROMEMAIL_FIELD = "fromemail";
public const string REPLYTO_FIELD = "replyto";
public const string REPLYTOEMAIL_FIELD = "replytoemail";
public const string TO_FIELD = "to";
public const string CC_FIELD = "cc";
public const string SUBJECT_FIELD = "subject";
public const string HEADER_FIELD = "header";
public const int RECEIVETIMEOUT = 20000;

public static Hashtable multilineCommands;

static POPClient()
{
multilineCommands = new Hashtable();
multilineCommands.Add(POPCommands.LIST,true);
multilineCommands.Add(POPCommands.RETR,false);
multilineCommands.Add(POPCommands.TOP,false);
}

private bool isopen;
private string serveraddress;
private int port;
private string username;
private string password;

private string connresponse;
private long msgcount;
private long totalsize;

public bool IsOpen
{
get { return isopen; }
}

public string ServerAddress
{
get { return serveraddress; }
set { serveraddress = value; }
}

public int Port
{
get { return port; }
set { port = value; }
}

public string UserName
{
get { return username; }
set { username = value; }
}

public string Password
{
get { return password; }
set { password = value; }
}

public string ConnectionResponse
{
get { return connresponse; }
}

public long MessageCount
{
get { return msgcount; }
}

public long TotalSize
{
get { return totalsize; }
}

private TcpClient server;
private NetworkStream writer;
private StreamReader reader;

public POPClient()
{
port = 110;
isopen = false;
}

protected void Dispose( bool disposing )
{
if( disposing )
{
Close();
}
}

public void Open()
{
Close();
server = new TcpClient(serveraddress,port);
server.ReceiveTimeout = RECEIVETIMEOUT;
writer = server.GetStream();
reader = new StreamReader(server.GetStream());
connresponse = reader.ReadLine();
isopen = true;
Login();
GetStatus();
}

public void Close()
{
try
{
SendCommand(POPCommands.QUIT);
}
catch{}
try
{
if(null != writer) writer.Close();
}
catch{}
finally
{
writer = null;
}
try
{
if(null != reader) reader.Close();
}
catch{}
finally
{
reader = null;
}
isopen = false;
}

public string SendCommand(POPCommands pCmd)
{
return SendCommand(pCmd, null);
}

public string SendCommand(POPCommands pCmd, string args)
{
if(!isopen) throw new Exception("Invalid State");
string response = null;
if(null != writer)
{
string cmd = pCmd.ToString() + " " + args + CRLF;
byte[] data = Encoding.ASCII.GetBytes(cmd.ToCharArray());
writer.Write(data,0,data.Length);

response = reader.ReadLine();
if(multilineCommands.ContainsKey(pCmd))
{
bool withoutArgsOnly = (bool)multilineCommands[pCmd];
if(!withoutArgsOnly || null == args || 0 == args.Trim().Length)
{
string tresp = null;
do
{
tresp = reader.ReadLine();
response += CRLF + tresp;
}while("." != tresp);
}
}
}
return response;
}

private void ValidateResponse(string cmd, string response)
{
if('-' == response[0])
{
throw new Exception(cmd + ":" + response.Substring(4).Trim());
}
}

public void Login()
{
string response = SendCommand(POPCommands.USER, username);
ValidateResponse(POPCommands.USER.ToString(),response);
if(null != password && password.Length > 0)
{
response = SendCommand(POPCommands.PASS, password);
ValidateResponse(POPCommands.PASS.ToString(),response);
}
}

public long[] GetStatus()
{
string response = SendCommand(POPCommands.STAT);
ValidateResponse(POPCommands.STAT.ToString(),response);
string[] rparts = response.Substring(3).Trim().Split(new char[] {' '});
long[] lary = new long[2];
msgcount = lary[0] = Int64.Parse(rparts[0].Trim());
totalsize = lary[1] = Int64.Parse(rparts[1].Trim());
return lary;
}

public ArrayList GetList()
{
string response = SendCommand(POPCommands.LIST);
ValidateResponse(POPCommands.LIST.ToString(),response);
string[] rlines = response.Split(new char[] {'\n'});
ArrayList aList = new ArrayList();
for(int i = 1;i < rlines.Length-1;i++)
{
string[] sdata = rlines[i].Trim().Split(new char[] {' '});
aList.Add(Int64.Parse(sdata[1]));
}
return aList;
}

///
/// MAtches the Regular expression and returns the groups
///

/// Regular Express
/// Source string to be processed
/// Collection of Groups yield from teh source string
public static GroupCollection MatchRegexAndGetGroups(string regexp,string source)
{
Regex r = new Regex(regexp,RegexOptions.IgnoreCase);
Match m = r.Match(source);
GroupCollection gc = null;
if(m.Success) gc = m.Groups;
return gc;
}

///
/// MAtches the Regular expression and returns the groups, and return
/// value of specific Group
///

/// Regular Express
/// Source string to be processed
/// group id to be returned
/// String containing the value of the specific indexed group
public static string MatchRegexAndGetGroup(string regexp,string source,int group)
{
GroupCollection gc = MatchRegexAndGetGroups(regexp,source);
string result = null;
if(null != gc && group <= gc.Count)
result = gc[group-1].Value;
return result;
}

public Hashtable GetHeader(int msg)
{
string response = SendCommand(POPCommands.TOP,msg + " 0");
ValidateResponse(POPCommands.TOP.ToString(),response);
Hashtable ht = new Hashtable();
ht.Add(HEADER_FIELD,response);
ht.Add(MESSAGEID_FIELD,MatchRegexAndGetGroup("\\r\\nMessage-ID:[\\s]*<([^>]*)>",
response,2));
string fromEmail = MatchRegexAndGetGroup("\\r\\nFrom:[\\s]*([^\\r\\n]*)",response,2);
ht.Add(FROM_FIELD,fromEmail);
Regex r = new Regex(@"([\w-\.]+)@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.)|(([\w-]+\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\]?)");
Match m = r.Match(fromEmail);
if(m.Success) fromEmail = m.Value;
ht.Add(FROMEMAIL_FIELD,fromEmail);
string replyto = MatchRegexAndGetGroup("\\r\\nReply-To:[\\s]*([^\\r\\n]*)",
response,2);
if(null == replyto)
{
ht.Add(REPLYTO_FIELD,fromEmail);
ht.Add(REPLYTOEMAIL_FIELD,fromEmail);
}
else
{
ht.Add(REPLYTO_FIELD,replyto);
m = r.Match(replyto);
if(m.Success) replyto = m.Value;
ht.Add(REPLYTOEMAIL_FIELD,replyto);
}
ht.Add(TO_FIELD,MatchRegexAndGetGroup("\\r\\nTo:[\\s]*([^\\r\\n]*)",
response,2));
ht.Add(CC_FIELD,MatchRegexAndGetGroup("\\r\\nCC:[\\s]*([^\\r\\n]*)",
response,2));
ht.Add(SUBJECT_FIELD,MatchRegexAndGetGroup("\\r\\nSubject:[\\s]*([^\\r\\n]*)",
response,2));
return ht;
}

public string GetMessage(int msg)
{
string response = SendCommand(POPCommands.RETR,msg.ToString());
ValidateResponse(POPCommands.RETR.ToString(),response);
return response;
}

public string DeleteMessage(int msg)
{
string response = SendCommand(POPCommands.DELE,msg.ToString());
ValidateResponse(POPCommands.DELE.ToString(), response);
return response;
}
}
}

No comments: