Click here to Skip to main content
15,886,258 members
Articles / Programming Languages / C#

.NET POP3 MIME Client

Rate me:
Please Sign up or sign in to vote.
4.89/5 (53 votes)
8 Feb 2008CPOL9 min read 1.1M   6.8K   174  
This article provides an implementation of a POP3 MIME client using .NET 2.0 and C#.
using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Net;
using System.Net.Mail;
using System.Net.Sockets;
using System.Net.Security;
using System.Security.Authentication;

using Net.Mime;

namespace Net.Mail
{
    /// <summary>
    /// The Pop3Client class provides a wrapper for the Pop3 commands
    /// that can be executed against a Pop3Server.  This class will 
    /// execute and return results for the various commands that are 
    /// executed.
    /// </summary>
    public sealed class Pop3Client : IDisposable
    {
        private static readonly int DefaultPort = 110;

        private TcpClient _client;
        private Stream _clientStream;

        /// <summary>
        /// Traces the various command objects that executed during this objects
        /// lifetime.
        /// </summary>
        public event Action<string> Trace;

        private void OnTrace(string message)
        {
            if (Trace != null)
            {
                Trace(message);
            }
        }

        private string _hostname;
        /// <summary>
        /// Gets the hostname.
        /// </summary>
        /// <value>The hostname.</value>
        public string Hostname
        {
            get { return _hostname; }
        }

        private int _port;
        /// <summary>
        /// Gets the port.
        /// </summary>
        /// <value>The port.</value>
        public int Port
        {
            get { return _port; }
        }

        private bool _useSsl;
        /// <summary>
        /// Gets a value indicating whether [use SSL].
        /// </summary>
        /// <value><c>true</c> if [use SSL]; otherwise, <c>false</c>.</value>
        public bool UseSsl
        {
            get { return _useSsl; }
        }
	
        private string _username;
        /// <summary>
        /// Gets or sets the username.
        /// </summary>
        /// <value>The username.</value>
        public string Username
        {
            get { return _username; }
            set { _username = value; }
        }

        private string _password;
        /// <summary>
        /// Gets or sets the password.
        /// </summary>
        /// <value>The password.</value>
        public string Password
        {
            get { return _password; }
            set { _password = value; }
        }

        private Pop3State _currentState;
        /// <summary>
        /// Gets the state of the current.
        /// </summary>
        /// <value>The state of the current.</value>
        public Pop3State CurrentState
        {
            get { return _currentState; }
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="Pop3Client"/> class using the default POP3 port 110
        /// without using SSL.
        /// </summary>
        /// <param name="hostname">The hostname.</param>
        /// <param name="username">The username.</param>
        /// <param name="password">The password.</param>
        public Pop3Client(string hostname, string username, string password) 
            : this(hostname, DefaultPort, false, username, password) { }

        /// <summary>
        /// Initializes a new instance of the <see cref="Pop3Client"/> class using the default POP3 port 110.
        /// </summary>
        /// <param name="hostname">The hostname.</param>
        /// <param name="useSsl">if set to <c>true</c> [use SSL].</param>
        /// <param name="username">The username.</param>
        /// <param name="password">The password.</param>
        public Pop3Client(string hostname, bool useSsl, string username, string password)
            : this(hostname, DefaultPort, useSsl, username, password) { }

        /// <summary>
        /// Initializes a new instance of the <see cref="Pop3Client"/> class.
        /// </summary>
        /// <param name="hostname">The hostname.</param>
        /// <param name="port">The port.</param>
        /// <param name="useSsl">if set to <c>true</c> [use SSL].</param>
        /// <param name="username">The username.</param>
        /// <param name="password">The password.</param>
        public Pop3Client(string hostname, int port, bool useSsl, string username, string password) 
            : this()
        {
            if (string.IsNullOrEmpty(hostname))
            {
                throw new ArgumentNullException("hostname");
            }

            if (port < 0)
            {
                throw new ArgumentOutOfRangeException("port");
            }

            if (string.IsNullOrEmpty(username))
            {
                throw new ArgumentNullException("username");
            }

            if (string.IsNullOrEmpty(password)) 
            {
                throw new ArgumentNullException("password");
            }

            _hostname = hostname;
            _port = port;
            _useSsl = useSsl;
            _username = username;
            _password = password;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="Pop3Client"/> class.
        /// </summary>
        private Pop3Client()
        {
            _client = new TcpClient();
            _currentState = Pop3State.Unknown;
        }

        /// <summary>
        /// Checks the connection.
        /// </summary>
        private void EnsureConnection()
        {
            if (!_client.Connected)
            {
                throw new Pop3Exception("Pop3 client is not connected.");
            }
        }

        /// <summary>
        /// Resets the state.
        /// </summary>
        /// <param name="state">The state.</param>
        private void SetState(Pop3State state)
        {
            _currentState = state;
        }

        /// <summary>
        /// Ensures the response.
        /// </summary>
        /// <param name="response">The response.</param>
        /// <param name="error">The error.</param>
        private void EnsureResponse(Pop3Response response, string error)
        {
            if (response == null)
            {
                throw new Pop3Exception("Unable to get Response.  Response object null.");
            }

            if (response.StatusIndicator)
            {
                return;
            } //the command execution was successful.

            string errorMessage = string.Empty;

            if (string.IsNullOrEmpty(error))
            {
                errorMessage = response.HostMessage;
            }
            else
            {
                errorMessage = string.Concat(error, ": ", error);
            }

            throw new Pop3Exception(errorMessage);
        }

        /// <summary>
        /// Ensures the response.
        /// </summary>
        /// <param name="response">The response.</param>
        private void EnsureResponse(Pop3Response response)
        {
            EnsureResponse(response, string.Empty);
        }

        /// <summary>
        /// Traces the command.
        /// </summary>
        /// <param name="command">The command.</param>
        private void TraceCommand<TCommand, TResponse>(TCommand command) 
            where TCommand : Pop3Command<TResponse> where TResponse : Pop3Response
        {
            if (Trace != null)
            {
                command.Trace += delegate(string message) { OnTrace(message); };
            }
        }

        /// <summary>
        /// Connects this instance and properly sets the 
        /// client stream to Use Ssl if it is specified.
        /// </summary>
        private void Connect()
        {
            if (_client == null)
            {
                _client = new TcpClient();
            } //If a previous quit command was issued, the client would be disposed of.

            if (_client.Connected)
            {
                return;
            } //if the connection already is established no need to reconnect.

            SetState(Pop3State.Unknown);
            ConnectResponse response;
            using (ConnectCommand command = new ConnectCommand(_client, _hostname, _port, _useSsl))
            {
                TraceCommand<ConnectCommand, ConnectResponse>(command);
                response = command.Execute(CurrentState);
                EnsureResponse(response);
            }

            SetClientStream(response.NetworkStream);

            SetState(Pop3State.Authorization);
        }

        /// <summary>
        /// Sets the client stream.  If UseSsl <c>true</c> then wrap 
        /// the client's <c>NetworkStream</c> in an <c>SslStream</c>, if UseSsl <c>false</c> 
        /// then set the client stream to the <c>NetworkStream</c>
        /// </summary>
        private void SetClientStream(Stream networkStream)
        {
            if (_clientStream != null)
            {
                _clientStream.Dispose();
            }

            _clientStream = networkStream;
        }

        /// <summary>
        /// Authenticates this instance.
        /// </summary>
        /// <remarks>A successful execution of this method will result in a Current State of Transaction.
        /// Unsuccessful USER or PASS commands can be reattempted by resetting the Username or Password 
        /// properties and re-execution of the methods.</remarks>
        /// <exception cref="Pop3Exception">
        /// If the Pop3Server is unable to be connected.
        /// If the User command is unable to be successfully executed.
        /// If the Pass command is unable to be successfully executed.
        /// </exception>
        public void Authenticate()
        {
            Connect();

            //execute the user command.
            using (UserCommand userCommand = new UserCommand(_clientStream, _username))
            {
                ExecuteCommand<Pop3Response, UserCommand>(userCommand);
            }

            //execute the pass command.
            using (PassCommand passCommand = new PassCommand(_clientStream, _password))
            {
                ExecuteCommand<Pop3Response, PassCommand>(passCommand);
            }

            _currentState = Pop3State.Transaction;
        }

        /// <summary>
        /// Executes the POP3 DELE command.
        /// </summary>
        /// <param name="item">The item.</param>
        /// /// <exception cref="Pop3Exception">If the DELE command was unable to be executed successfully.</exception>
        public void Dele(Pop3ListItem item)
        {
            if (item == null)
            {
                throw new ArgumentNullException("item");
            }

            using (DeleCommand command = new DeleCommand(_clientStream, item.MessageId))
            {
                ExecuteCommand<Pop3Response, DeleCommand>(command);
            }
        }

        /// <summary>
        /// Executes the POP3 NOOP command.
        /// </summary>
        /// <exception cref="Pop3Exception">If the NOOP command was unable to be executed successfully.</exception>
        public void Noop()
        {
            using (NoopCommand command = new NoopCommand(_clientStream))
            {
                ExecuteCommand<Pop3Response, NoopCommand>(command);
            }
        }

        /// <summary>
        /// Executes the POP3 RSET command.
        /// </summary>
        /// <exception cref="Pop3Exception">If the RSET command was unable to be executed successfully.</exception>
        public void Rset()
        {
            using (RsetCommand command = new RsetCommand(_clientStream))
            {
                ExecuteCommand<Pop3Response, RsetCommand>(command);
            }
        }

        /// <summary>
        /// Executes the POP3 STAT command.
        /// </summary>
        /// <returns>A Stat object containing the results of STAT command.</returns>
        /// <exception cref="Pop3Exception">If the STAT command was unable to be executed successfully.</exception>
        public Stat Stat()
        {
            StatResponse response;
            using (StatCommand command = new StatCommand(_clientStream))
            {
                response = ExecuteCommand<StatResponse, StatCommand>(command);
            }

            return new Stat(response.MessageCount, response.Octets);
        }

        /// <summary>
        /// Executes the POP3 List command.
        /// </summary>
        /// <returns>A generic List of Pop3Items containing the results of the LIST command.</returns>
        /// <exception cref="Pop3Exception">If the LIST command was unable to be executed successfully.</exception>
        public List<Pop3ListItem> List()
        {
            ListResponse response;
            using (ListCommand command = new ListCommand(_clientStream))
            {
                response = ExecuteCommand<ListResponse, ListCommand>(command);
            }
            return response.Items;
        }

        /// <summary>
        /// Lists the specified message.
        /// </summary>
        /// <param name="message">The message.</param>
        /// <returns>A <c>Pop3ListItem</c> for the requested Pop3Item.</returns>
        /// <exception cref="Pop3Exception">If the LIST command was unable to be executed successfully for the provided message id.</exception>
        public Pop3ListItem List(int messageId)
        {
            ListResponse response;
            using (ListCommand command = new ListCommand(_clientStream, messageId))
            {
                response = ExecuteCommand<ListResponse, ListCommand>(command);
            }
            return new Pop3ListItem(response.MessageNumber, response.Octets);
        }

        /// <summary>
        /// Retrs the specified message.
        /// </summary>
        /// <param name="item">The item.</param>
        /// <returns>A MimeEntity for the requested Pop3 Mail Item.</returns>
        public MimeEntity RetrMimeEntity(Pop3ListItem item)
        {
            if (item == null)
            {
                throw new ArgumentNullException("item");
            }

            if (item.MessageId < 1)
            {
                throw new ArgumentOutOfRangeException("item.MessageId");
            }

            RetrResponse response;
            using (RetrCommand command = new RetrCommand(_clientStream, item.MessageId))
            {
                response = ExecuteCommand<RetrResponse, RetrCommand>(command);
            }

            MimeReader reader = new MimeReader(response.MessageLines);
            return reader.CreateMimeEntity();
        }

        public MailMessageEx Top(int messageId, int lineCount)
        {
            if (messageId < 1)
            {
                throw new ArgumentOutOfRangeException("messageId");
            }

            if (lineCount < 0)
            {
                throw new ArgumentOutOfRangeException("lineCount");
            }

            RetrResponse response;
            using (TopCommand command = new TopCommand(_clientStream, messageId, lineCount))
            {
                response = ExecuteCommand<RetrResponse, TopCommand>(command);
            }

            MimeReader reader = new MimeReader(response.MessageLines);
            MimeEntity entity = reader.CreateMimeEntity();
            MailMessageEx message = entity.ToMailMessageEx();
            message.Octets = response.Octets;
            message.MessageNumber = messageId;
            return entity.ToMailMessageEx();
        }

        /// <summary>
        /// Retrs the mail message ex.
        /// </summary>
        /// <param name="item">The item.</param>
        /// <returns></returns>
        public MailMessageEx RetrMailMessageEx(Pop3ListItem item)
        {
            MailMessageEx message = RetrMimeEntity(item).ToMailMessageEx();
            message.MessageNumber = item.MessageId;
            return message;
        }


        /// <summary>
        /// Executes the Pop3 QUIT command.
        /// </summary>
        /// <exception cref="Pop3Exception">If the quit command returns a -ERR server message.</exception>
        public void Quit()
        {
            using (QuitCommand command = new QuitCommand(_clientStream))
            {
                ExecuteCommand<Pop3Response, QuitCommand>(command);
                
                if (CurrentState.Equals(Pop3State.Transaction))
                {
                    SetState(Pop3State.Update);
                } // Messages could have been deleted, reflect the server state.

                Disconnect();

                //Quit command can only be called in Authorization or Transaction state, reset to Unknown.
                SetState(Pop3State.Unknown);
            }
        }

        /// <summary>
        /// Provides a common way to execute all commands.  This method
        /// validates the connection, traces the command and finally
        /// validates the response message for a -ERR response.
        /// </summary>
        /// <param name="command">The command.</param>
        /// <returns>The Pop3Response for the provided command</returns>
        /// <exception cref="Pop3Exception">If the HostMessage does not start with '+OK'.</exception>
        /// <exception cref="Pop3Exception">If the client is no longer connected.</exception>
        private TResponse ExecuteCommand<TResponse, TCommand>(TCommand command) 
            where TResponse : Pop3Response where TCommand : Pop3Command<TResponse>
        {
            EnsureConnection();
            TraceCommand<TCommand, TResponse>(command);
            TResponse response = (TResponse)command.Execute(CurrentState);
            EnsureResponse(response);
            return response;
        }

        /// <summary>
        /// Disconnects this instance.
        /// </summary>
        private void Disconnect()
        {
            if (_clientStream != null)
            {
                _clientStream.Close();
            }  //release underlying socket.

            if (_client != null)
            {
                _client.Close();
                _client = null;
            }
        }

        public void Dispose()
        {
            Disconnect();
        }
    }
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions