OpenPop.NET Helper


namespace ConsoleApplication
{
    using System;
    using System.Collections.Generic;
    using System.Threading;
    using System.Threading.Tasks;
    using System.IO;
    using System.Net;
    using System.Net.Mail;
    using System.Net.Mime;
    using Microshaoft;
    using OpenPop.Mime;
    using OpenPop.Pop3;
    /// <summary>
    /// Class1 的摘要说明。
    /// </summary>
    public class Program
    {
        /// <summary>
        /// 应用程序的主入口点。
        /// </summary>
        //[STAThread]
        static void Main(string[] args)
        {
            OpenPopHelper.FetchAllMessages
                                (
                                    "pop3.live.com"
                                    , 995
                                    , true
                                    , "XXXX@live.com"
                                    , "!@#123QWE"
                                    , (message, i, client) =>
                                                    {
                                                        return MessageFunc(message, i, client);
                                                    }
                                );
            Console.WriteLine("Hello World");
            Console.WriteLine(Environment.Version.ToString());
            Console.ReadLine();
        }
        static bool MessageFunc(Message message, int messageNumber, Pop3Client client)
        {
            Console.WriteLine(message.Headers.Subject);
            Console.WriteLine(message.Headers.From);
            Console.WriteLine(message.Headers.MessageId);
            Console.WriteLine(messageNumber);
            MemoryStream stream = new MemoryStream();
            message.Save(stream);
            byte[] data = StreamDataHelper.ReadDataToBytes(stream);
            stream.Close();
            stream.Dispose();
            stream = null;
            // to do
            /*
             *  .eml byte[] 入: SharePoint Document Library 
             */
            List<MessagePart> list = message.FindAllAttachments();
            Parallel.ForEach<MessagePart>
                (
                    list
                    , part =>
                    {
                        Console.WriteLine(part.ContentType.MediaType);
                        string fileName = string.Format
                                                    (
                                                        "{1}{0}{2}"
                                                        , "."
                                                        , Guid.NewGuid().ToString()
                                                        , part.FileName.Trim
                                                                (
                                                                    new char[]
                                                                                {
                                                                                    '"'
                                                                                    ,' '
                                                                                    ,'\t'
                                                                                }
                                                                 )
                                                     );
                        Console.WriteLine(fileName);
                        stream = new MemoryStream();
                        part.Save(stream);
                        data = StreamDataHelper.ReadDataToBytes(stream);
                        stream.Close();
                        stream.Dispose();
                        stream = null;
                        // to do
                        /*
                         * 附件入 SharePoint document Library
                         */
                    }
                    );
            //删除
            return false;
        }
        static void SendMailSmtp(string[] args)
        {
            string html = "<html><body><a href=\"http://www.live.com\"><img src=\"cid:attachment1\"></a>";
            html += "<script src=\"cid:attachment2\"></script>中国字";
            html += "<a href=\"http://www.google.com\"><br><img src=\"cid:attachment1\"></a><script>alert('mail body xss')<script></body></html>";
            AlternateView view = AlternateView.CreateAlternateViewFromString(html, null, MediaTypeNames.Text.Html);
            LinkedResource picture = new LinkedResource(@"d:\pic.JPG", MediaTypeNames.Image.Jpeg);
            picture.ContentId = "attachment1";
            view.LinkedResources.Add(picture);
            //LinkedResource script = new LinkedResource(@"a.js", MediaTypeNames.Text.Plain);
            //script.ContentId = "attachment2";
            //view.LinkedResources.Add(script);
            MailMessage mail = new MailMessage();
            mail.AlternateViews.Add(view);
            mail.From = new MailAddress("test@qqt.com", "<script>alert('mail from xss')</script>");
            mail.To.Add(new MailAddress("qq@gmail.com", "<script>alert('mail to xss')</script>"));
            mail.To.Add(new MailAddress("qq@qq.com", "<script>alert('mail to xss')</script>"));
            mail.Subject = "<script>alert('mail subject xss')</script>" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
            SmtpClient client = new SmtpClient("smtp.live.com");
            //client.Port = 465;
            client.Credentials = new NetworkCredential("XXXX@live.com","!@#123QWE");
            client.EnableSsl = true;
            client.Send(mail);
            Console.WriteLine("Hello World");
            Console.WriteLine(Environment.Version.ToString());
        }
    }
}
//==========================================================================================================
namespace Microshaoft
{
    using System.IO;
    public static class StreamDataHelper
    {
        public static byte[] ReadDataToBytes(Stream stream)
        {
            byte[] buffer = new byte[64 * 1024];
            MemoryStream ms = new MemoryStream();
            int r = 0;
            int l = 0;
            long position = -1;
            if (stream.CanSeek)
            {
                position = stream.Position;
                stream.Position = 0;
            }
            while (true)
            {
                r = stream.Read(buffer, 0, buffer.Length);
                if (r > 0)
                {
                    l += r;
                    ms.Write(buffer, 0, r);
                }
                else
                {
                    break;
                }
            }
            byte[] bytes = new byte[l];
            ms.Position = 0;
            ms.Read(bytes, 0, (int)l);
            ms.Close();
            ms.Dispose();
            ms = null;
            if (position >= 0)
            {
                stream.Position = position;
            }
            return bytes;
        }
    }
}
//============================================================================================================
namespace Microshaoft
{
    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Net.Security;
    using System.Security.Cryptography.X509Certificates;
    using System.Text;
    using OpenPop.Common.Logging;
    using OpenPop.Mime;
    using OpenPop.Mime.Decode;
    using OpenPop.Mime.Header;
    using OpenPop.Pop3;
    /// <summary>
    /// These are small examples problems for the
    /// <see cref="OpenPop"/>.NET POP3 library
    /// </summary>
    public class OpenPopHelper
    {
        public static void FetchAllMessages
                                    (
                                        string hostname
                                        , int port
                                        , bool useSsl
                                        , string username
                                        , string password
                                        , Func<Message, int, Pop3Client, bool> messageFunc
                                    )
        {
            // The client disconnects from the server when being disposed
            using (Pop3Client client = new Pop3Client())
            {
                // Connect to the server
                client.Connect(hostname, port, useSsl);
                // Authenticate ourselves towards the server
                client.Authenticate(username, password);
                // Get the number of messages in the inbox
                int messageCount = client.GetMessageCount();
                // We want to download all messages
                //List<Message> allMessages = new List<Message>(messageCount);
                // Messages are numbered in the interval: [1, messageCount]
                // Ergo: message numbers are 1-based.
                for (int i = 1; i <= messageCount; i++)
                {
                    //allMessages.Add(client.GetMessage(i));
                    Message message = client.GetMessage(i);
                    bool r = messageFunc(message, i, client);
                    if (r)
                    {
                        client.DeleteMessage(i);
                    }
                }
                // Now return the fetched messages
                //return allMessages;
            }
        }
        /// <summary>
        /// Example showing:
        ///  - how to fetch all messages from a POP3 server
        /// </summary>
        /// <param name="hostname">Hostname of the server. For example: pop3.live.com</param>
        /// <param name="port">Host port to connect to. Normally: 110 for plain POP3, 995 for SSL POP3</param>
        /// <param name="useSsl">Whether or not to use SSL to connect to server</param>
        /// <param name="username">Username of the user on the server</param>
        /// <param name="password">Password of the user on the server</param>
        /// <returns>All Messages on the POP3 server</returns>
        public static List<Message> FetchAllMessages
                                            (
                                                string hostname
                                                , int port
                                                , bool useSsl
                                                , string username
                                                , string password
                                            )
        {
            // The client disconnects from the server when being disposed
            using (Pop3Client client = new Pop3Client())
            {
                // Connect to the server
                client.Connect(hostname, port, useSsl);
                // Authenticate ourselves towards the server
                client.Authenticate(username, password);
                // Get the number of messages in the inbox
                int messageCount = client.GetMessageCount();
                // We want to download all messages
                List<Message> allMessages = new List<Message>(messageCount);
                // Messages are numbered in the interval: [1, messageCount]
                // Ergo: message numbers are 1-based.
                for (int i = 1; i <= messageCount; i++)
                {
                    allMessages.Add(client.GetMessage(i));
                }
                // Now return the fetched messages
                return allMessages;
            }
        }
        /// <summary>
        /// Example showing:
        ///  - how to delete fetch an emails headers only
        ///  - how to delete a message from the server
        /// </summary>
        /// <param name="client">A connected and authenticated Pop3Client from which to delete a message</param>
        /// <param name="messageId">A message ID of a message on the POP3 server. Is located in <see cref="MessageHeader.MessageId"/></param>
        /// <returns><see langword="true"/> if message was deleted, <see langword="false"/> otherwise</returns>
        public bool DeleteMessageByMessageId(Pop3Client client, string messageId)
        {
            // Get the number of messages on the POP3 server
            int messageCount = client.GetMessageCount();
            // Run trough each of these messages and download the headers
            for (int messageItem = messageCount; messageItem > 0; messageItem--)
            {
                // If the Message ID of the current message is the same as the parameter given, delete that message
                if (client.GetMessageHeaders(messageItem).MessageId == messageId)
                {
                    // Delete
                    client.DeleteMessage(messageItem);
                    return true;
                }
            }
            // We did not find any message with the given messageId, report this back
            return false;
        }
        /// <summary>
        /// Example showing:
        ///  - how to a find plain text version in a Message
        ///  - how to save MessageParts to file
        /// </summary>
        /// <param name="message">The message to examine for plain text</param>
        public static void FindPlainTextInMessage(Message message)
        {
            MessagePart plainText = message.FindFirstPlainTextVersion();
            if (plainText != null)
            {
                // Save the plain text to a file, database or anything you like
                plainText.Save(new FileInfo("plainText.txt"));
            }
        }
        /// <summary>
        /// Example showing:
        ///  - how to find a html version in a Message
        ///  - how to save MessageParts to file
        /// </summary>
        /// <param name="message">The message to examine for html</param>
        public static void FindHtmlInMessage(Message message)
        {
            MessagePart html = message.FindFirstHtmlVersion();
            if (html != null)
            {
                // Save the plain text to a file, database or anything you like
                html.Save(new FileInfo("html.txt"));
            }
        }
        /// <summary>
        /// Example showing:
        ///  - how to find a MessagePart with a specified MediaType
        ///  - how to get the body of a MessagePart as a string
        /// </summary>
        /// <param name="message">The message to examine for xml</param>
        public static void FindXmlInMessage(Message message)
        {
            MessagePart xml = message.FindFirstMessagePartWithMediaType("text/xml");
            if (xml != null)
            {
                // Get out the XML string from the email
                string xmlString = xml.GetBodyAsText();
                System.Xml.XmlDocument doc = new System.Xml.XmlDocument();
                // Load in the XML read from the email
                doc.LoadXml(xmlString);
                // Save the xml to the filesystem
                doc.Save("test.xml");
            }
        }
        /// <summary>
        /// Example showing:
        ///  - how to fetch only headers from a POP3 server
        ///  - how to examine some of the headers
        ///  - how to fetch a full message
        ///  - how to find a specific attachment and save it to a file
        /// </summary>
        /// <param name="hostname">Hostname of the server. For example: pop3.live.com</param>
        /// <param name="port">Host port to connect to. Normally: 110 for plain POP3, 995 for SSL POP3</param>
        /// <param name="useSsl">Whether or not to use SSL to connect to server</param>
        /// <param name="username">Username of the user on the server</param>
        /// <param name="password">Password of the user on the server</param>
        /// <param name="messageNumber">
        /// The number of the message to examine.
        /// Must be in range [1, messageCount] where messageCount is the number of messages on the server.
        /// </param>
        public static void HeadersFromAndSubject(string hostname, int port, bool useSsl, string username, string password, int messageNumber)
        {
            // The client disconnects from the server when being disposed
            using (Pop3Client client = new Pop3Client())
            {
                // Connect to the server
                client.Connect(hostname, port, useSsl);
                // Authenticate ourselves towards the server
                client.Authenticate(username, password);
                // We want to check the headers of the message before we download
                // the full message
                MessageHeader headers = client.GetMessageHeaders(messageNumber);
                RfcMailAddress from = headers.From;
                string subject = headers.Subject;
                // Only want to download message if:
                //  - is from test@xample.com
                //  - has subject "Some subject"
                if (from.HasValidMailAddress && from.Address.Equals("test@example.com") && "Some subject".Equals(subject))
                {
                    // Download the full message
                    Message message = client.GetMessage(messageNumber);
                    // We know the message contains an attachment with the name "useful.pdf".
                    // We want to save this to a file with the same name
                    foreach (MessagePart attachment in message.FindAllAttachments())
                    {
                        if (attachment.FileName.Equals("useful.pdf"))
                        {
                            // Save the raw bytes to a file
                            File.WriteAllBytes(attachment.FileName, attachment.Body);
                        }
                    }
                }
            }
        }
        /// <summary>
        /// Example showing:
        ///  - how to delete a specific message from a server
        /// </summary>
        /// <param name="hostname">Hostname of the server. For example: pop3.live.com</param>
        /// <param name="port">Host port to connect to. Normally: 110 for plain POP3, 995 for SSL POP3</param>
        /// <param name="useSsl">Whether or not to use SSL to connect to server</param>
        /// <param name="username">Username of the user on the server</param>
        /// <param name="password">Password of the user on the server</param>
        /// <param name="messageNumber">
        /// The number of the message to delete.
        /// Must be in range [1, messageCount] where messageCount is the number of messages on the server.
        /// </param>
        public static void DeleteMessageOnServer(string hostname, int port, bool useSsl, string username, string password, int messageNumber)
        {
            // The client disconnects from the server when being disposed
            using (Pop3Client client = new Pop3Client())
            {
                // Connect to the server
                client.Connect(hostname, port, useSsl);
                // Authenticate ourselves towards the server
                client.Authenticate(username, password);
                // Mark the message as deleted
                // Notice that it is only MARKED as deleted
                // POP3 requires you to "commit" the changes
                // which is done by sending a QUIT command to the server
                // You can also reset all marked messages, by sending a RSET command.
                client.DeleteMessage(messageNumber);
                // When a QUIT command is sent to the server, the connection between them are closed.
                // When the client is disposed, the QUIT command will be sent to the server
                // just as if you had called the Disconnect method yourself.
            }
        }
        /// <summary>
        /// Example showing:
        ///  - how to use UID's (unique ID's) of messages from the POP3 server
        ///  - how to download messages not seen before
        ///    (notice that the POP3 protocol cannot see if a message has been read on the server
        ///     before. Therefore the client need to maintain this state for itself)
        /// </summary>
        /// <param name="hostname">Hostname of the server. For example: pop3.live.com</param>
        /// <param name="port">Host port to connect to. Normally: 110 for plain POP3, 995 for SSL POP3</param>
        /// <param name="useSsl">Whether or not to use SSL to connect to server</param>
        /// <param name="username">Username of the user on the server</param>
        /// <param name="password">Password of the user on the server</param>
        /// <param name="seenUids">
        /// List of UID's of all messages seen before.
        /// New message UID's will be added to the list.
        /// Consider using a HashSet if you are using >= 3.5 .NET
        /// </param>
        /// <returns>A List of new Messages on the server</returns>
        public static List<Message> FetchUnseenMessages(string hostname, int port, bool useSsl, string username, string password, List<string> seenUids)
        {
            // The client disconnects from the server when being disposed
            using (Pop3Client client = new Pop3Client())
            {
                // Connect to the server
                client.Connect(hostname, port, useSsl);
                // Authenticate ourselves towards the server
                client.Authenticate(username, password);
                // Fetch all the current uids seen
                List<string> uids = client.GetMessageUids();
                // Create a list we can return with all new messages
                List<Message> newMessages = new List<Message>();
                // All the new messages not seen by the POP3 client
                for (int i = 0; i < uids.Count; i++)
                {
                    string currentUidOnServer = uids[i];
                    if (!seenUids.Contains(currentUidOnServer))
                    {
                        // We have not seen this message before.
                        // Download it and add this new uid to seen uids
                        // the uids list is in messageNumber order - meaning that the first
                        // uid in the list has messageNumber of 1, and the second has 
                        // messageNumber 2. Therefore we can fetch the message using
                        // i + 1 since messageNumber should be in range [1, messageCount]
                        Message unseenMessage = client.GetMessage(i + 1);
                        // Add the message to the new messages
                        newMessages.Add(unseenMessage);
                        // Add the uid to the seen uids, as it has now been seen
                        seenUids.Add(currentUidOnServer);
                    }
                }
                // Return our new found messages
                return newMessages;
            }
        }
        /// <summary>
        /// Example showing:
        ///  - how to set timeouts
        ///  - how to override the SSL certificate checks with your own implementation
        /// </summary>
        /// <param name="hostname">Hostname of the server. For example: pop3.live.com</param>
        /// <param name="port">Host port to connect to. Normally: 110 for plain POP3, 995 for SSL POP3</param>
        /// <param name="timeouts">Read and write timeouts used by the Pop3Client</param>
        public static void BypassSslCertificateCheck(string hostname, int port, int timeouts)
        {
            // The client disconnects from the server when being disposed
            using (Pop3Client client = new Pop3Client())
            {
                // Connect to the server using SSL with specified settings
                // true here denotes that we connect using SSL
                // The certificateValidator can validate the SSL certificate of the server.
                // This might be needed if the server is using a custom normally untrusted certificate
                client.Connect(hostname, port, true, timeouts, timeouts, certificateValidator);
                // Do something extra now that we are connected to the server
            }
        }
        private static bool certificateValidator(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslpolicyerrors)
        {
            // We should check if there are some SSLPolicyErrors, but here we simply say that
            // the certificate is okay - we trust it.
            return true;
        }
        /// <summary>
        /// Example showing:
        ///  - how to save a message to a file
        ///  - how to load a message from a file at a later point
        /// </summary>
        /// <param name="message">The message to save and load at a later point</param>
        /// <returns>The Message, but loaded from the file system</returns>
        public static Message SaveAndLoadFullMessage(Message message)
        {
            // FileInfo about the location to save/load message
            FileInfo file = new FileInfo("someFile.eml");
            // Save the full message to some file
            message.Save(file);
            // Now load the message again. This could be done at a later point
            Message loadedMessage = Message.Load(file);
            // use the message again
            return loadedMessage;
        }
        /// <summary>
        /// Example showing:
        ///  - How to change logging
        ///  - How to implement your own logger
        /// </summary>
        public static void ChangeLogging()
        {
            // All logging is sent trough logger defined at DefaultLogger.Log
            // The logger can be changed by calling DefaultLogger.SetLog(someLogger)
            // By default all logging is sent to the System.Diagnostics.Trace facilities.
            // These are not very useful if you are not debugging
            // Instead, lets send logging to a file:
            DefaultLogger.SetLog(new FileLogger());
            FileLogger.LogFile = new FileInfo("MyLoggingFile.log");
            // It is also possible to implement your own logging:
            DefaultLogger.SetLog(new MyOwnLogger());
        }
        class MyOwnLogger : ILog
        {
            public void LogError(string message)
            {
                Console.WriteLine("ERROR!!!: " + message);
            }
            public void LogDebug(string message)
            {
                // Dont want to log debug messages
            }
        }
        /// <summary>
        /// Example showing:
        ///  - How to provide custom Encoding class
        ///  - How to use UTF8 as default Encoding
        /// </summary>
        /// <param name="customEncoding">Own Encoding implementation</param>
        public void InsertCustomEncodings(Encoding customEncoding)
        {
            // Lets say some email contains a characterSet of "iso-9999-9" which
            // is fictional, but is really just UTF-8.
            // Lets add that mapping to the class responsible for finding
            // the Encoding from the name of it
            EncodingFinder.AddMapping("iso-9999-9", Encoding.UTF8);
            // It is also possible to implement your own Encoding if
            // the framework does not provide what you need
            EncodingFinder.AddMapping("specialEncoding", customEncoding);
            // Now, if the EncodingFinder is not able to find an encoding, lets
            // see if we can find one ourselves
            EncodingFinder.FallbackDecoder = CustomFallbackDecoder;
        }
        Encoding CustomFallbackDecoder(string characterSet)
        {
            // Is it a "foo" encoding?
            if (characterSet.StartsWith("foo"))
                return Encoding.ASCII; // then use ASCII
            // If no special encoding could be found, provide UTF8 as default.
            // You can also return null here, which would tell OpenPop that
            // no encoding could be found. This will then throw an exception.
            return Encoding.UTF8;
        }
        // Other examples to show, that is in the library
        // Show how to build a TreeNode representation of the Message hierarchy using the
        // TreeNodeBuilder class in OpenPopTest
    }
}
//============================================================================================================
namespace OpenPop.Pop3
{
    using System;
    using System.Collections.Generic;
    using System.Globalization;
    using System.Net;
    using System.Net.Security;
    using System.Net.Sockets;
    using System.IO;
    using System.Text;
    using System.Text.RegularExpressions;
    using OpenPop.Mime;
    using OpenPop.Mime.Header;
    using OpenPop.Pop3.Exceptions;
    using OpenPop.Common;
    using OpenPop.Common.Logging;
    /// <summary>
    /// POP3 compliant POP Client<br/>
    /// <br/>    
    /// If you want to override where logging is sent, look at <see cref="DefaultLogger"/>
    /// </summary>
    /// <example>
    /// Examples are available on the <a href="http://hpop.sourceforge.net/">project homepage</a>.
    /// </example>
    public class Pop3Client : Disposable
    {
        #region Private member properties
        /// <summary>
        /// The stream used to communicate with the server
        /// </summary>
        private Stream Stream { get; set; }
        /// <summary>
        /// This is the last response the server sent back when a command was issued to it
        /// </summary>
        private string LastServerResponse { get; set; }
        /// <summary>
        /// The APOP time stamp sent by the server in it's welcome message if APOP is supported.
        /// </summary>
        private string ApopTimeStamp { get; set; }
        /// <summary>
        /// Describes what state the <see cref="Pop3Client"/> is in
        /// </summary>
        private ConnectionState State { get; set; }
        #endregion
        #region Public member properties
        /// <summary>
        /// Tells whether the <see cref="Pop3Client"/> is connected to a POP server or not
        /// </summary>
        public bool Connected { get; private set; }
        /// <summary>
        /// Allows you to check if the server supports
        /// the <see cref="AuthenticationMethod.Apop"/> authentication method.<br/>
        /// <br/>
        /// This value is filled when the connect method has returned,
        /// as the server tells in its welcome message if APOP is supported.
        /// </summary>
        public bool ApopSupported { get; private set; }
        #endregion
        #region Constructors
        /// <summary>
        /// Constructs a new Pop3Client for you to use.
        /// </summary>
        public Pop3Client()
        {
            SetInitialValues();
        }
        #endregion
        #region IDisposable implementation
        /// <summary>
        /// Disposes the <see cref="Pop3Client"/>.<br/>
        /// This is the implementation of the <see cref="IDisposable"/> interface.<br/>
        /// Sends the QUIT command to the server before closing the streams.
        /// </summary>
        /// <param name="disposing"><see langword="true"/> if managed and unmanaged code should be disposed, <see langword="false"/> if only managed code should be disposed</param>
        protected override void Dispose(bool disposing)
        {
            if (disposing && !IsDisposed)
            {
                if (Connected)
                {
                    Disconnect();
                }
            }
            base.Dispose(disposing);
        }
        #endregion
        #region Connection managing methods
        /// <summary>
        /// Connect to the server using user supplied stream
        /// </summary>
        /// <param name="stream">The stream used to communicate with the server</param>
        /// <exception cref="ArgumentNullException">If <paramref name="stream"/> is <see langword="null"/></exception>
        public void Connect(Stream stream)
        {
            AssertDisposed();
            if (State != ConnectionState.Disconnected)
                throw new InvalidUseException("You cannot ask to connect to a POP3 server, when we are already connected to one. Disconnect first.");
            if (stream == null)
                throw new ArgumentNullException("stream");
            Stream = stream;
            // Fetch the server one-line welcome greeting
            string response = StreamUtility.ReadLineAsAscii(Stream);
            // Check if the response was an OK response
            try
            {
                // Assume we now need the user to supply credentials
                // If we do not connect correctly, Disconnect will set the
                // state to Disconnected
                // If this is not set, Disconnect will throw an exception
                State = ConnectionState.Authorization;
                IsOkResponse(response);
                ExtractApopTimestamp(response);
                Connected = true;
            }
            catch (PopServerException e)
            {
                // If not close down the connection and abort
                DisconnectStreams();
                DefaultLogger.Log.LogError("Connect(): " + "Error with connection, maybe POP3 server not exist");
                DefaultLogger.Log.LogDebug("Last response from server was: " + LastServerResponse);
                throw new PopServerNotAvailableException("Server is not available", e);
            }
        }
        /// <summary>
        /// Connects to a remote POP3 server using default timeouts of 60.000 milliseconds
        /// </summary>
        /// <param name="hostname">The <paramref name="hostname"/> of the POP3 server</param>
        /// <param name="port">The port of the POP3 server</param>
        /// <param name="useSsl"><see langword="true"/> if SSL should be used. <see langword="false"/> if plain TCP should be used.</param>
        /// <exception cref="PopServerNotAvailableException">If the server did not send an OK message when a connection was established</exception>
        /// <exception cref="PopServerNotFoundException">If it was not possible to connect to the server</exception>
        /// <exception cref="ArgumentNullException">If <paramref name="hostname"/> is <see langword="null"/></exception>
        /// <exception cref="ArgumentOutOfRangeException">If port is not in the range [<see cref="IPEndPoint.MinPort"/>, <see cref="IPEndPoint.MaxPort"/></exception>
        public void Connect(string hostname, int port, bool useSsl)
        {
            const int defaultTimeOut = 60000;
            Connect(hostname, port, useSsl, defaultTimeOut, defaultTimeOut, null);
        }
        /// <summary>
        /// Connects to a remote POP3 server
        /// </summary>
        /// <param name="hostname">The <paramref name="hostname"/> of the POP3 server</param>
        /// <param name="port">The port of the POP3 server</param>
        /// <param name="useSsl"><see langword="true"/> if SSL should be used. <see langword="false"/> if plain TCP should be used.</param>
        /// <param name="receiveTimeout">Timeout in milliseconds before a socket should time out from reading. Set to 0 or -1 to specify infinite timeout.</param>
        /// <param name="sendTimeout">Timeout in milliseconds before a socket should time out from sending. Set to 0 or -1 to specify infinite timeout.</param>
        /// <param name="certificateValidator">If you want to validate the certificate in a SSL connection, pass a reference to your validator. Supply <see langword="null"/> if default should be used.</param>
        /// <exception cref="PopServerNotAvailableException">If the server did not send an OK message when a connection was established</exception>
        /// <exception cref="PopServerNotFoundException">If it was not possible to connect to the server</exception>
        /// <exception cref="ArgumentNullException">If <paramref name="hostname"/> is <see langword="null"/></exception>
        /// <exception cref="ArgumentOutOfRangeException">If port is not in the range [<see cref="IPEndPoint.MinPort"/>, <see cref="IPEndPoint.MaxPort"/> or if any of the timeouts is less than -1.</exception>
        public void Connect(string hostname, int port, bool useSsl, int receiveTimeout, int sendTimeout, RemoteCertificateValidationCallback certificateValidator)
        {
            AssertDisposed();
            if (hostname == null)
                throw new ArgumentNullException("hostname");
            if (hostname.Length == 0)
                throw new ArgumentException("hostname cannot be empty", "hostname");
            if (port > IPEndPoint.MaxPort || port < IPEndPoint.MinPort)
                throw new ArgumentOutOfRangeException("port");
            if (receiveTimeout < -1)
                throw new ArgumentOutOfRangeException("receiveTimeout");
            if (sendTimeout < -1)
                throw new ArgumentOutOfRangeException("sendTimeout");
            if (State != ConnectionState.Disconnected)
                throw new InvalidUseException("You cannot ask to connect to a POP3 server, when we are already connected to one. Disconnect first.");
            TcpClient clientSocket = new TcpClient();
            clientSocket.ReceiveTimeout = receiveTimeout;
            clientSocket.SendTimeout = sendTimeout;
            try
            {
                clientSocket.Connect(hostname, port);
            }
            catch (SocketException e)
            {
                // Close the socket - we are not connected, so no need to close stream underneath
                clientSocket.Close();
                DefaultLogger.Log.LogError("Connect(): " + e.Message);
                throw new PopServerNotFoundException("Server not found", e);
            }
            Stream stream;
            if (useSsl)
            {
                // If we want to use SSL, open a new SSLStream on top of the open TCP stream.
                // We also want to close the TCP stream when the SSL stream is closed
                // If a validator was passed to us, use it.
                SslStream sslStream;
                if (certificateValidator == null)
                {
                    sslStream = new SslStream(clientSocket.GetStream(), false);
                }
                else
                {
                    sslStream = new SslStream(clientSocket.GetStream(), false, certificateValidator);
                }
                sslStream.ReadTimeout = receiveTimeout;
                sslStream.WriteTimeout = sendTimeout;
                // Authenticate the server
                sslStream.AuthenticateAsClient(hostname);
                stream = sslStream;
            }
            else
            {
                // If we do not want to use SSL, use plain TCP
                stream = clientSocket.GetStream();
            }
            // Now do the connect with the same stream being used to read and write to
            Connect(stream);
        }
        /// <summary>
        /// Disconnects from POP3 server.
        /// Sends the QUIT command before closing the connection, which deletes all the messages that was marked as such.
        /// </summary>
        public void Disconnect()
        {
            AssertDisposed();
            if (State == ConnectionState.Disconnected)
                throw new InvalidUseException("You cannot disconnect a connection which is already disconnected");
            try
            {
                SendCommand("QUIT");
            }
            finally
            {
                DisconnectStreams();
            }
        }
        #endregion
        #region Authentication methods
        /// <summary>
        /// Authenticates a user towards the POP server using <see cref="AuthenticationMethod.Auto"/>.<br/>
        /// If this authentication fails but you are sure that the username and password is correct, it might
        /// be that that the POP3 server is wrongly telling the client it supports <see cref="AuthenticationMethod.Apop"/>.
        /// You should try using <see cref="Authenticate(string, string, AuthenticationMethod)"/> while passing <see cref="AuthenticationMethod.UsernameAndPassword"/> to the method.
        /// </summary>
        /// <param name="username">The username</param>
        /// <param name="password">The user password</param>
        /// <exception cref="InvalidLoginException">If the user credentials was not accepted</exception>
        /// <exception cref="PopServerLockedException">If the server said the the mailbox was locked</exception>
        /// <exception cref="ArgumentNullException">If <paramref name="username"/> or <paramref name="password"/> is <see langword="null"/></exception>
        /// <exception cref="LoginDelayException">If the server rejects the login because of too recent logins</exception>
        public void Authenticate(string username, string password)
        {
            AssertDisposed();
            Authenticate(username, password, AuthenticationMethod.Auto);
        }
        /// <summary>
        /// Authenticates a user towards the POP server using some <see cref="AuthenticationMethod"/>.
        /// </summary>
        /// <param name="username">The username</param>
        /// <param name="password">The user password</param>
        /// <param name="authenticationMethod">The way that the client should authenticate towards the server</param>
        /// <exception cref="NotSupportedException">If <see cref="AuthenticationMethod.Apop"/> is used, but not supported by the server</exception>
        /// <exception cref="InvalidLoginException">If the user credentials was not accepted</exception>
        /// <exception cref="PopServerLockedException">If the server said the the mailbox was locked</exception>
        /// <exception cref="ArgumentNullException">If <paramref name="username"/> or <paramref name="password"/> is <see langword="null"/></exception>
        /// <exception cref="LoginDelayException">If the server rejects the login because of too recent logins</exception>
        public void Authenticate(string username, string password, AuthenticationMethod authenticationMethod)
        {
            AssertDisposed();
            if (username == null)
                throw new ArgumentNullException("username");
            if (password == null)
                throw new ArgumentNullException("password");
            if (State != ConnectionState.Authorization)
                throw new InvalidUseException("You have to be connected and not authorized when trying to authorize yourself");
            try
            {
                switch (authenticationMethod)
                {
                    case AuthenticationMethod.UsernameAndPassword:
                        AuthenticateUsingUserAndPassword(username, password);
                        break;
                    case AuthenticationMethod.Apop:
                        AuthenticateUsingApop(username, password);
                        break;
                    case AuthenticationMethod.Auto:
                        if (ApopSupported)
                            AuthenticateUsingApop(username, password);
                        else
                            AuthenticateUsingUserAndPassword(username, password);
                        break;
                    case AuthenticationMethod.CramMd5:
                        AuthenticateUsingCramMd5(username, password);
                        break;
                }
            }
            catch (PopServerException e)
            {
                DefaultLogger.Log.LogError("Problem logging in using method " + authenticationMethod + ". Server response was: " + LastServerResponse);
                // Throw a more specific exception if special cases of failure is detected
                // using the response the server generated when the last command was sent
                CheckFailedLoginServerResponse(LastServerResponse, e);
                // If no special failure is detected, tell that the login credentials were wrong
                throw new InvalidLoginException(e);
            }
            // We are now authenticated and therefore we enter the transaction state
            State = ConnectionState.Transaction;
        }
        /// <summary>
        /// Authenticates a user towards the POP server using the USER and PASSWORD commands
        /// </summary>
        /// <param name="username">The username</param>
        /// <param name="password">The user password</param>
        /// <exception cref="PopServerException">If the server responded with -ERR</exception>
        private void AuthenticateUsingUserAndPassword(string username, string password)
        {
            SendCommand("USER " + username);
            SendCommand("PASS " + password);
            // Authentication was successful if no exceptions thrown before getting here
        }
        /// <summary>
        /// Authenticates a user towards the POP server using APOP
        /// </summary>
        /// <param name="username">The username</param>
        /// <param name="password">The user password</param>
        /// <exception cref="NotSupportedException">Thrown when the server does not support APOP</exception>
        /// <exception cref="PopServerException">If the server responded with -ERR</exception>
        private void AuthenticateUsingApop(string username, string password)
        {
            if (!ApopSupported)
                throw new NotSupportedException("APOP is not supported on this server");
            SendCommand("APOP " + username + " " + Apop.ComputeDigest(password, ApopTimeStamp));
            // Authentication was successful if no exceptions thrown before getting here
        }
        /// <summary>
        /// Authenticates using the CRAM-MD5 authentication method
        /// </summary>
        /// <param name="username">The username</param>
        /// <param name="password">The user password</param>
        /// <exception cref="NotSupportedException">Thrown when the server does not support AUTH CRAM-MD5</exception>
        /// <exception cref="InvalidLoginException">If the user credentials was not accepted</exception>
        /// <exception cref="PopServerLockedException">If the server said the the mailbox was locked</exception>
        /// <exception cref="LoginDelayException">If the server rejects the login because of too recent logins</exception>
        private void AuthenticateUsingCramMd5(string username, string password)
        {
            // Example of communication:
            // C: AUTH CRAM-MD5
            // S: + PDE4OTYuNjk3MTcwOTUyQHBvc3RvZmZpY2UucmVzdG9uLm1jaS5uZXQ+
            // C: dGltIGI5MTNhNjAyYzdlZGE3YTQ5NWI0ZTZlNzMzNGQzODkw
            // S: +OK CRAM authentication successful
            // Other example, where AUTH CRAM-MD5 is not supported
            // C: AUTH CRAM-MD5
            // S: -ERR Authentication method CRAM-MD5 not supported
            try
            {
                SendCommand("AUTH CRAM-MD5");
            }
            catch (PopServerException e)
            {
                // A PopServerException will be thrown if the server responds with a -ERR not supported
                throw new NotSupportedException("CRAM-MD5 authentication not supported", e);
            }
            // Fetch out the challenge from the server response
            string challenge = LastServerResponse.Substring(2);
            // Compute the challenge response
            string response = CramMd5.ComputeDigest(username, password, challenge);
            // Send the response to the server
            SendCommand(response);
            // Authentication was successful if no exceptions thrown before getting here
        }
        #endregion
        #region Public POP3 commands
        /// <summary>
        /// Get the number of messages on the server using a STAT command
        /// </summary>
        /// <returns>The message count on the server</returns>
        /// <exception cref="PopServerException">If the server did not accept the STAT command</exception>
        public int GetMessageCount()
        {
            AssertDisposed();
            if (State != ConnectionState.Transaction)
                throw new InvalidUseException("You cannot get the message count without authenticating yourself towards the server first");
            return SendCommandIntResponse("STAT", 1);
        }
        /// <summary>
        /// Marks the message with the given message number as deleted.<br/>
        /// <br/>
        /// The message will not be deleted until a QUIT command is sent to the server.<br/>
        /// This is done when you call <see cref="Disconnect()"/> or when the Pop3Client is <see cref="Dispose">Disposed</see>.
        /// </summary>
        /// <param name="messageNumber">
        /// The number of the message to be deleted. This message may not already have been deleted.<br/>
        /// The <paramref name="messageNumber"/> must be inside the range [1, messageCount]
        /// </param>
        /// <exception cref="PopServerException">If the server did not accept the delete command</exception>
        public void DeleteMessage(int messageNumber)
        {
            AssertDisposed();
            ValidateMessageNumber(messageNumber);
            if (State != ConnectionState.Transaction)
                throw new InvalidUseException("You cannot delete any messages without authenticating yourself towards the server first");
            SendCommand("DELE " + messageNumber);
        }
        /// <summary>
        /// Marks all messages as deleted.<br/>
        /// <br/>
        /// The messages will not be deleted until a QUIT command is sent to the server.<br/>
        /// This is done when you call <see cref="Disconnect()"/> or when the Pop3Client is <see cref="Dispose">Disposed</see>.<br/>
        /// The method assumes that no prior message has been marked as deleted, and is not valid to call if this is wrong.
        /// </summary>
        /// <exception cref="PopServerException">If the server did not accept one of the delete commands. All prior marked messages will still be marked.</exception>
        public void DeleteAllMessages()
        {
            AssertDisposed();
            int messageCount = GetMessageCount();
            for (int messageItem = messageCount; messageItem > 0; messageItem--)
            {
                DeleteMessage(messageItem);
            }
        }
        /// <summary>
        /// Keep server active by sending a NOOP command.<br/>
        /// This might keep the server from closing the connection due to inactivity.<br/>
        /// <br/>
        /// RFC:<br/>
        /// The POP3 server does nothing, it merely replies with a positive response.
        /// </summary>
        /// <exception cref="PopServerException">If the server did not accept the NOOP command</exception>
        public void NoOperation()
        {
            AssertDisposed();
            if (State != ConnectionState.Transaction)
                throw new InvalidUseException("You cannot use the NOOP command unless you are authenticated to the server");
            SendCommand("NOOP");
        }
        /// <summary>
        /// Send a reset command to the server.<br/>
        /// <br/>
        /// RFC:<br/>
        /// If any messages have been marked as deleted by the POP3
        /// server, they are unmarked. The POP3 server then replies
        /// with a positive response.
        /// </summary>
        /// <exception cref="PopServerException">If the server did not accept the RSET command</exception>
        public void Reset()
        {
            AssertDisposed();
            if (State != ConnectionState.Transaction)
                throw new InvalidUseException("You cannot use the RSET command unless you are authenticated to the server");
            SendCommand("RSET");
        }
        /// <summary>
        /// Get a unique ID for a single message.<br/>
        /// </summary>
        /// <param name="messageNumber">
        /// Message number, which may not be marked as deleted.<br/>
        /// The <paramref name="messageNumber"/> must be inside the range [1, messageCount]
        /// </param>
        /// <returns>The unique ID for the message</returns>
        /// <exception cref="PopServerException">If the server did not accept the UIDL command. This could happen if the <paramref name="messageNumber"/> does not exist</exception>
        public string GetMessageUid(int messageNumber)
        {
            AssertDisposed();
            ValidateMessageNumber(messageNumber);
            if (State != ConnectionState.Transaction)
                throw new InvalidUseException("Cannot get message ID, when the user has not been authenticated yet");
            // Example from RFC:
            //C: UIDL 2
            //S: +OK 2 QhdPYR:00WBw1Ph7x7
            SendCommand("UIDL " + messageNumber);
            // Parse out the unique ID
            return LastServerResponse.Split(' ')[2];
        }
        /// <summary>
        /// Gets a list of unique IDs for all messages.<br/>
        /// Messages marked as deleted are not listed.
        /// </summary>
        /// <returns>
        /// A list containing the unique IDs in sorted order from message number 1 and upwards.
        /// </returns>
        /// <exception cref="PopServerException">If the server did not accept the UIDL command</exception>
        public List<string> GetMessageUids()
        {
            AssertDisposed();
            if (State != ConnectionState.Transaction)
                throw new InvalidUseException("Cannot get message IDs, when the user has not been authenticated yet");
            // RFC Example:
            // C: UIDL
            // S: +OK
            // S: 1 whqtswO00WBw418f9t5JxYwZ
            // S: 2 QhdPYR:00WBw1Ph7x7
            // S: .      // this is the end
            SendCommand("UIDL");
            List<string> uids = new List<string>();
            string response;
            // Keep reading until multi-line ends with a "."
            while (!IsLastLineInMultiLineResponse(response = StreamUtility.ReadLineAsAscii(Stream)))
            {
                // Add the unique ID to the list
                uids.Add(response.Split(' ')[1]);
            }
            return uids;
        }
        /// <summary>
        /// Gets the size in bytes of a single message
        /// </summary>
        /// <param name="messageNumber">
        /// The number of a message which may not be a message marked as deleted.<br/>
        /// The <paramref name="messageNumber"/> must be inside the range [1, messageCount]
        /// </param>
        /// <returns>Size of the message</returns>
        /// <exception cref="PopServerException">If the server did not accept the LIST command</exception>
        public int GetMessageSize(int messageNumber)
        {
            AssertDisposed();
            ValidateMessageNumber(messageNumber);
            if (State != ConnectionState.Transaction)
                throw new InvalidUseException("Cannot get message size, when the user has not been authenticated yet");
            // RFC Example:
            // C: LIST 2
            // S: +OK 2 200
            return SendCommandIntResponse("LIST " + messageNumber, 2);
        }
        /// <summary>
        /// Get the sizes in bytes of all the messages.<br/>
        /// Messages marked as deleted are not listed.
        /// </summary>
        /// <returns>Size of each message excluding deleted ones</returns>
        /// <exception cref="PopServerException">If the server did not accept the LIST command</exception>
        public List<int> GetMessageSizes()
        {
            AssertDisposed();
            if (State != ConnectionState.Transaction)
                throw new InvalidUseException("Cannot get message sizes, when the user has not been authenticated yet");
            // RFC Example:
            // C: LIST
            // S: +OK 2 messages (320 octets)
            // S: 1 120
            // S: 2 200
            // S: .       // End of multi-line
            SendCommand("LIST");
            List<int> sizes = new List<int>();
            string response;
            // Read until end of multi-line
            while (!".".Equals(response = StreamUtility.ReadLineAsAscii(Stream)))
            {
                sizes.Add(int.Parse(response.Split(' ')[1], CultureInfo.InvariantCulture));
            }
            return sizes;
        }
        /// <summary>
        /// Fetches a message from the server and parses it
        /// </summary>
        /// <param name="messageNumber">
        /// Message number on server, which may not be marked as deleted.<br/>
        /// Must be inside the range [1, messageCount]
        /// </param>
        /// <returns>The message, containing the email message</returns>
        /// <exception cref="PopServerException">If the server did not accept the command sent to fetch the message</exception>
        public Message GetMessage(int messageNumber)
        {
            AssertDisposed();
            ValidateMessageNumber(messageNumber);
            if (State != ConnectionState.Transaction)
                throw new InvalidUseException("Cannot fetch a message, when the user has not been authenticated yet");
            byte[] messageContent = GetMessageAsBytes(messageNumber);
            return new Message(messageContent);
        }
        /// <summary>
        /// Fetches a message in raw form from the server
        /// </summary>
        /// <param name="messageNumber">
        /// Message number on server, which may not be marked as deleted.<br/>
        /// Must be inside the range [1, messageCount]
        /// </param>
        /// <returns>The raw bytes of the message</returns>
        /// <exception cref="PopServerException">If the server did not accept the command sent to fetch the message</exception>
        public byte[] GetMessageAsBytes(int messageNumber)
        {
            AssertDisposed();
            ValidateMessageNumber(messageNumber);
            if (State != ConnectionState.Transaction)
                throw new InvalidUseException("Cannot fetch a message, when the user has not been authenticated yet");
            // Get the full message
            return GetMessageAsBytes(messageNumber, false);
        }
        /// <summary>
        /// Get all the headers for a message.<br/>
        /// The server will not need to send the body of the message.
        /// </summary>
        /// <param name="messageNumber">
        /// Message number, which may not be marked as deleted.<br/>
        /// Must be inside the range [1, messageCount]
        /// </param>
        /// <returns>MessageHeaders object</returns>
        /// <exception cref="PopServerException">If the server did not accept the command sent to fetch the message</exception>
        public MessageHeader GetMessageHeaders(int messageNumber)
        {
            AssertDisposed();
            ValidateMessageNumber(messageNumber);
            if (State != ConnectionState.Transaction)
                throw new InvalidUseException("Cannot fetch a message, when the user has not been authenticated yet");
            // Only fetch the header part of the message
            byte[] messageContent = GetMessageAsBytes(messageNumber, true);
            // Do not parse the body - as it is not in the byte array
            return new Message(messageContent, false).Headers;
        }
        /// <summary>
        /// Asks the server to return it's capability listing.<br/>
        /// This is an optional command, which a server is not enforced to accept.
        /// </summary>
        /// <returns>
        /// The returned Dictionary keys are the capability names.<br/>
        /// The Lists pointed to are the capability parameters fitting that certain capability name.
        /// See <a href="http://tools.ietf.org/html/rfc2449#section-6">RFC section 6</a> for explanation for some of the capabilities.
        /// </returns>
        /// <remarks>
        /// Capabilities are case-insensitive.<br/>
        /// The dictionary uses case-insensitive searching, but the Lists inside
        /// does not. Therefore you will have to use something like the code below
        /// to search for a capability parameter.<br/>
        /// foo is the capability name and bar is the capability parameter.
        /// <code>
        /// List&lt;string&gt; arguments = capabilities["foo"];
        ///    bool contains = null != arguments.Find(delegate(string str)
        ///                {
        ///                    return String.Compare(str, "bar", true) == 0;
        ///                });
        /// </code>
        /// If we were running on .NET framework >= 3.5, a HashSet could have been used.
        /// </remarks>
        /// <exception cref="PopServerException">If the server did not accept the capability command</exception>
        public Dictionary<string, List<string>> Capabilities()
        {
            AssertDisposed();
            if (State != ConnectionState.Authorization && State != ConnectionState.Transaction)
                throw new InvalidUseException("Capability command only available while connected or authenticated");
            // RFC Example
            // Examples:
            // C: CAPA
            // S: +OK Capability list follows
            // S: TOP
            // S: USER
            // S: SASL CRAM-MD5 KERBEROS_V4
            // S: RESP-CODES
            // S: LOGIN-DELAY 900
            // S: PIPELINING
            // S: EXPIRE 60
            // S: UIDL
            // S: IMPLEMENTATION Shlemazle-Plotz-v302
            // S: .
            SendCommand("CAPA");
            // Capablities are case-insensitive
            Dictionary<string, List<string>> capabilities = new Dictionary<string, List<string>>(StringComparer.OrdinalIgnoreCase);
            string lineRead;
            // Keep reading until we are at the end of the multi line response
            while (!IsLastLineInMultiLineResponse(lineRead = StreamUtility.ReadLineAsAscii(Stream)))
            {
                // Example of read line
                // SASL CRAM-MD5 KERBEROS_V4
                // SASL is the name of the capability while
                // CRAM-MD5 and KERBEROS_V4 are arguments to SASL
                string[] splitted = lineRead.Split(' ');
                // There should always be a capability name
                string capabilityName = splitted[0];
                // Find all the arguments
                List<string> capabilityArguments = new List<string>();
                for (int i = 1; i < splitted.Length; i++)
                {
                    capabilityArguments.Add(splitted[i]);
                }
                // Add the capability found to the dictionary
                capabilities.Add(capabilityName, capabilityArguments);
            }
            return capabilities;
        }
        #endregion
        #region Private helper methods
        /// <summary>
        /// Examines string to see if it contains a time stamp to use with the APOP command.<br/>
        /// If it does, sets the <see cref="ApopTimeStamp"/> property to this value.
        /// </summary>
        /// <param name="response">The string to examine</param>
        private void ExtractApopTimestamp(string response)
        {
            // RFC Example:
            // +OK POP3 server ready <1896.697170952@dbc.mtview.ca.us>
            Match match = Regex.Match(response, "<.+>");
            if (match.Success)
            {
                ApopTimeStamp = match.Value;
                ApopSupported = true;
            }
        }
        /// <summary>
        /// Tests a string to see if it is a "+" string.<br/>
        /// An "+" string should be returned by a compliant POP3
        /// server if the request could be served.<br/>
        /// <br/>
        /// The method does only check if it starts with "+".
        /// </summary>
        /// <param name="response">The string to examine</param>
        /// <exception cref="PopServerException">Thrown if server did not respond with "+" message</exception>
        private static void IsOkResponse(string response)
        {
            if (response == null)
                throw new PopServerException("The stream used to retrieve responses from was closed");
            if (response.StartsWith("+", StringComparison.OrdinalIgnoreCase))
                return;
            throw new PopServerException("The server did not respond with a + response. The response was: \"" + response + "\"");
        }
        /// <summary>
        /// Sends a command to the POP server.<br/>
        /// If this fails, an exception is thrown.
        /// </summary>
        /// <param name="command">The command to send to server</param>
        /// <exception cref="PopServerException">If the server did not send an OK message to the command</exception>
        private void SendCommand(string command)
        {
            // Convert the command with CRLF afterwards as per RFC to a byte array which we can write
            byte[] commandBytes = Encoding.ASCII.GetBytes(command + "\r\n");
            // Write the command to the server
            Stream.Write(commandBytes, 0, commandBytes.Length);
            Stream.Flush(); // Flush the content as we now wait for a response
            // Read the response from the server. The response should be in ASCII
            LastServerResponse = StreamUtility.ReadLineAsAscii(Stream);
            IsOkResponse(LastServerResponse);
        }
        /// <summary>
        /// Sends a command to the POP server, expects an integer reply in the response
        /// </summary>
        /// <param name="command">command to send to server</param>
        /// <param name="location">
        /// The location of the int to return.<br/>
        /// Example:<br/>
        /// <c>S: +OK 2 200</c><br/>
        /// Set <paramref name="location"/>=1 to get 2<br/>
        /// Set <paramref name="location"/>=2 to get 200<br/>
        /// </param>
        /// <returns>Integer value in the reply</returns>
        /// <exception cref="PopServerException">If the server did not accept the command</exception>
        private int SendCommandIntResponse(string command, int location)
        {
            SendCommand(command);
            return int.Parse(LastServerResponse.Split(' ')[location], CultureInfo.InvariantCulture);
        }
        /// <summary>
        /// Asks the server for a message and returns the message response as a byte array.
        /// </summary>
        /// <param name="messageNumber">
        /// Message number on server, which may not be marked as deleted.<br/>
        /// Must be inside the range [1, messageCount]
        /// </param>
        /// <param name="askOnlyForHeaders">If <see langword="true"/> only the header part of the message is requested from the server. If <see langword="false"/> the full message is requested</param>
        /// <returns>A byte array that the message requested consists of</returns>
        /// <exception cref="PopServerException">If the server did not accept the command sent to fetch the message</exception>
        private byte[] GetMessageAsBytes(int messageNumber, bool askOnlyForHeaders)
        {
            AssertDisposed();
            ValidateMessageNumber(messageNumber);
            if (State != ConnectionState.Transaction)
                throw new InvalidUseException("Cannot fetch a message, when the user has not been authenticated yet");
            if (askOnlyForHeaders)
            {
                // 0 is the number of lines of the message body to fetch, therefore it is set to zero to fetch only headers
                SendCommand("TOP " + messageNumber + " 0");
            }
            else
            {
                // Ask for the full message
                SendCommand("RETR " + messageNumber);
            }
            // RFC 1939 Example
            // C: RETR 1
            // S: +OK 120 octets
            // S: <the POP3 server sends the entire message here>
            // S: .
            // Create a byte array builder which we use to write the bytes too
            // When done, we can get the byte array out
            using (MemoryStream byteArrayBuilder = new MemoryStream())
            {
                bool first = true;
                byte[] lineRead;
                // Keep reading until we are at the end of the multi line response
                while (!IsLastLineInMultiLineResponse(lineRead = StreamUtility.ReadLineAsBytes(Stream)))
                {
                    // We should not write CRLF on the very last line, therefore we do this
                    if (!first)
                    {
                        // Write CRLF which was not included in the lineRead bytes of last line
                        byte[] crlfPair = Encoding.ASCII.GetBytes("\r\n");
                        byteArrayBuilder.Write(crlfPair, 0, crlfPair.Length);
                    }
                    else
                    {
                        // We are now not the first anymore
                        first = false;
                    }
                    // This is a multi-line. See http://tools.ietf.org/html/rfc1939#section-3
                    // It says that a line starting with "." and not having CRLF after it
                    // is a multi line, and the "." should be stripped
                    if (lineRead.Length > 0 && lineRead[0] == '.')
                    {
                        // Do not write the first period
                        byteArrayBuilder.Write(lineRead, 1, lineRead.Length - 1);
                    }
                    else
                    {
                        // Write everything
                        byteArrayBuilder.Write(lineRead, 0, lineRead.Length);
                    }
                }
                // If we are fetching a header - add an extra line to denote the headers ended
                if (askOnlyForHeaders)
                {
                    byte[] crlfPair = Encoding.ASCII.GetBytes("\r\n");
                    byteArrayBuilder.Write(crlfPair, 0, crlfPair.Length);
                }
                // Get out the bytes we have written to byteArrayBuilder
                byte[] receivedBytes = byteArrayBuilder.ToArray();
                return receivedBytes;
            }
        }
        /// <summary>
        /// Check if the bytes received is the last line in a multi line response
        /// from the pop3 server. It is the last line if the line contains only a "."
        /// </summary>
        /// <param name="bytesReceived">The last line received from the server, which could be the last response line</param>
        /// <returns><see langword="true"/> if last line in a multi line response, <see langword="false"/> otherwise</returns>
        /// <exception cref="ArgumentNullException">If <paramref name="bytesReceived"/> is <see langword="null"/></exception>
        private static bool IsLastLineInMultiLineResponse(byte[] bytesReceived)
        {
            if (bytesReceived == null)
                throw new ArgumentNullException("bytesReceived");
            return bytesReceived.Length == 1 && bytesReceived[0] == '.';
        }
        /// <see cref="IsLastLineInMultiLineResponse(byte[])"> for documentation</see>
        private static bool IsLastLineInMultiLineResponse(string lineReceived)
        {
            if (lineReceived == null)
                throw new ArgumentNullException("lineReceived");
            // If the string is indeed the last line, then it is okay to do ASCII encoding
            // on it. For performance reasons we check if the length is equal to 1
            // so that we do not need to decode a long message string just to see if
            // it is the last line
            return lineReceived.Length == 1 && IsLastLineInMultiLineResponse(Encoding.ASCII.GetBytes(lineReceived));
        }
        /// <summary>
        /// Method for checking that a <paramref name="messageNumber"/> argument given to some method
        /// is indeed valid. If not, <see cref="InvalidUseException"/> will be thrown.
        /// </summary>
        /// <param name="messageNumber">The message number to validate</param>
        private static void ValidateMessageNumber(int messageNumber)
        {
            if (messageNumber <= 0)
                throw new InvalidUseException("The messageNumber argument cannot have a value of zero or less. Valid messageNumber is in the range [1, messageCount]");
        }
        /// <summary>
        /// Closes down the streams and sets the Pop3Client into the initial configuration
        /// </summary>
        private void DisconnectStreams()
        {
            try
            {
                Stream.Close();
            }
            finally
            {
                // Reset values to initial state
                SetInitialValues();
            }
        }
        /// <summary>
        /// Sets the initial values on the public properties of this Pop3Client.
        /// </summary>
        private void SetInitialValues()
        {
            // We have not seen the APOPTimestamp yet
            ApopTimeStamp = null;
            // We are not connected
            Connected = false;
            State = ConnectionState.Disconnected;
            // APOP is not supported before we check on login
            ApopSupported = false;
        }
        /// <summary>
        /// Checks for extra response codes when an authentication has failed and throws
        /// the correct exception.
        /// If no such response codes is found, nothing happens.
        /// </summary>
        /// <param name="serverErrorResponse">The server response string</param>
        /// <param name="e">The exception thrown because the server responded with -ERR</param>
        /// <exception cref="PopServerLockedException">If the account is locked or in use</exception>
        /// <exception cref="LoginDelayException">If the server rejects the login because of too recent logins</exception>
        private static void CheckFailedLoginServerResponse(string serverErrorResponse, PopServerException e)
        {
            string upper = serverErrorResponse.ToUpperInvariant();
            // Bracketed strings are extra response codes addded
            // in RFC http://tools.ietf.org/html/rfc2449
            // together with the CAPA command.
            // Specifies the account is in use
            if (upper.Contains("[IN-USE]") || upper.Contains("LOCK"))
            {
                DefaultLogger.Log.LogError("Authentication: maildrop is locked or in-use");
                throw new PopServerLockedException(e);
            }
            // Specifies that there must go some time between logins
            if (upper.Contains("[LOGIN-DELAY]"))
            {
                throw new LoginDelayException(e);
            }
        }
        #endregion
    }
}
namespace OpenPop.Pop3
{
    using System;
    /// <summary>
    /// Utility class that simplifies the usage of <see cref="IDisposable"/>
    /// </summary>
    public abstract class Disposable : IDisposable
    {
        /// <summary>
        /// Returns <see langword="true"/> if this instance has been disposed of, <see langword="false"/> otherwise
        /// </summary>
        protected bool IsDisposed { get; private set; }
        /// <summary>
        /// Releases unmanaged resources and performs other cleanup operations before the
        /// <see cref="Disposable"/> is reclaimed by garbage collection.
        /// </summary>
        ~Disposable()
        {
            Dispose(false);
        }
        /// <summary>
        /// Releases unmanaged and - optionally - managed resources
        /// </summary>
        public void Dispose()
        {
            if (!IsDisposed)
            {
                try
                {
                    Dispose(true);
                }
                finally
                {
                    IsDisposed = true;
                    GC.SuppressFinalize(this);
                }
            }
        }
        /// <summary>
        /// Releases unmanaged and - optionally - managed resources. Remember to call this method from your derived classes.
        /// </summary>
        /// <param name="disposing">
        /// Set to <c>true</c> to release both managed and unmanaged resources.<br/>
        /// Set to <c>false</c> to release only unmanaged resources.
        /// </param>
        protected virtual void Dispose(bool disposing)
        {
        }
        /// <summary>
        /// Used to assert that the object has not been disposed
        /// </summary>
        /// <exception cref="ObjectDisposedException">Thrown if the object is in a disposed state.</exception>
        /// <remarks>
        /// The method is to be used by the subclasses in order to provide a simple method for checking the 
        /// disposal state of the object.
        /// </remarks>
        protected void AssertDisposed()
        {
            if (IsDisposed)
            {
                string typeName = GetType().FullName;
                throw new ObjectDisposedException(typeName, String.Format(System.Globalization.CultureInfo.InvariantCulture, "Cannot access a disposed {0}.", typeName));
            }
        }
    }
}
namespace OpenPop.Pop3
{
    using System;
    using System.Security.Cryptography;
    using System.Text;
    /// <summary>
    /// Implements the CRAM-MD5 algorithm as specified in <a href="http://tools.ietf.org/html/rfc2195">RFC 2195</a>.
    /// </summary>
    internal static class CramMd5
    {
        /// <summary>
        /// Defined by <a href="http://tools.ietf.org/html/rfc2104#section-2">RFC 2104</a>
        /// Is a 64 byte array with all entries set to 0x36.
        /// </summary>
        private static readonly byte[] ipad;
        /// <summary>
        /// Defined by <a href="http://tools.ietf.org/html/rfc2104#section-2">RFC 2104</a>
        /// Is a 64 byte array with all entries set to 0x5C.
        /// </summary>
        private static readonly byte[] opad;
        /// <summary>
        /// Initializes the static fields
        /// </summary>
        static CramMd5()
        {
            ipad = new byte[64];
            opad = new byte[64];
            for (int i = 0; i < ipad.Length; i++)
            {
                ipad[i] = 0x36;
                opad[i] = 0x5C;
            }
        }
        /// <summary>
        /// Computes the digest needed to login to a server using CRAM-MD5.<br/>
        /// <br/>
        /// This computes:<br/>
        /// MD5((password XOR opad), MD5((password XOR ipad), challenge))
        /// </summary>
        /// <param name="username">The username of the user who wants to log in</param>
        /// <param name="password">The password for the <paramref name="username"/></param>
        /// <param name="challenge">
        /// The challenge received from the server when telling it CRAM-MD5 authenticated is wanted.
        /// Is a base64 encoded string.
        /// </param>
        /// <returns>The response to the challenge, which the server can validate and log in the user if correct</returns>
        /// <exception cref="ArgumentNullException">
        /// If <paramref name="username"/>, 
        /// <paramref name="password"/> or 
        /// <paramref name="challenge"/> is <see langword="null"/>
        /// </exception>
        internal static string ComputeDigest(string username, string password, string challenge)
        {
            if (username == null)
                throw new ArgumentNullException("username");
            if (password == null)
                throw new ArgumentNullException("password");
            if (challenge == null)
                throw new ArgumentNullException("challenge");
            // Get the password bytes
            byte[] passwordBytes = GetSharedSecretInBytes(password);
            // The challenge is encoded in base64
            byte[] challengeBytes = Convert.FromBase64String(challenge);
            // Now XOR the password with the opad and ipad magic bytes
            byte[] passwordOpad = Xor(passwordBytes, opad);
            byte[] passwordIpad = Xor(passwordBytes, ipad);
            // Now do the computation: MD5((password XOR opad), MD5((password XOR ipad), challenge))
            byte[] digestValue = Hash(Concatenate(passwordOpad, Hash(Concatenate(passwordIpad, challengeBytes))));
            // Convert the bytes to a hex string
            // BitConverter writes the output as AF-B3-...
            // We need lower-case output without "-"
            string hex = BitConverter.ToString(digestValue).Replace("-", "").ToLowerInvariant();
            // Include the username in the resulting base64 encoded response
            return Convert.ToBase64String(Encoding.ASCII.GetBytes(username + " " + hex));
        }
        /// <summary>
        /// Hashes a byte array using the MD5 algorithm.
        /// </summary>
        /// <param name="toHash">The byte array to hash</param>
        /// <returns>The result of hashing the <paramref name="toHash"/> bytes with the MD5 algorithm</returns>
        /// <exception cref="ArgumentNullException">If <paramref name="toHash"/> is <see langword="null"/></exception>
        private static byte[] Hash(byte[] toHash)
        {
            if (toHash == null)
                throw new ArgumentNullException("toHash");
            using (MD5 md5 = new MD5CryptoServiceProvider())
            {
                return md5.ComputeHash(toHash);
            }
        }
        /// <summary>
        /// Concatenates two byte arrays into one
        /// </summary>
        /// <param name="one">The first byte array</param>
        /// <param name="two">The second byte array</param>
        /// <returns>A concatenated byte array</returns>
        /// <exception cref="ArgumentNullException">If <paramref name="one"/> or <paramref name="two"/> is <see langword="null"/></exception>
        private static byte[] Concatenate(byte[] one, byte[] two)
        {
            if (one == null)
                throw new ArgumentNullException("one");
            if (two == null)
                throw new ArgumentNullException("two");
            // Create space for both byte arrays in one
            byte[] concatenated = new byte[one.Length + two.Length];
            // Copy the first one over
            Buffer.BlockCopy(one, 0, concatenated, 0, one.Length);
            // Copy the second one over
            Buffer.BlockCopy(two, 0, concatenated, one.Length, two.Length);
            // Return result
            return concatenated;
        }
        /// <summary>
        /// XORs a byte array with another.<br/>
        /// Each byte in <paramref name="toXor"/> is XORed with the corresponding byte
        /// in <paramref name="toXorWith"/> until the end of <paramref name="toXor"/> is encountered.
        /// </summary>
        /// <param name="toXor">The byte array to XOR</param>
        /// <param name="toXorWith">The byte array to XOR with</param>
        /// <returns>A new byte array with the XORed results</returns>
        /// <exception cref="ArgumentNullException">If <paramref name="toXor"/> or <paramref name="toXorWith"/> is <see langword="null"/></exception>
        /// <exception cref="ArgumentException">If the lengths of the arrays are not equal</exception>
        private static byte[] Xor(byte[] toXor, byte[] toXorWith)
        {
            if (toXor == null)
                throw new ArgumentNullException("toXor");
            if (toXorWith == null)
                throw new ArgumentNullException("toXorWith");
            if (toXor.Length != toXorWith.Length)
                throw new ArgumentException("The lengths of the arrays must be equal");
            // Create a new array to store results in
            byte[] xored = new byte[toXor.Length];
            // XOR each individual byte.
            for (int i = 0; i < toXor.Length; i++)
            {
                xored[i] = toXor[i];
                xored[i] ^= toXorWith[i];
            }
            // Return result
            return xored;
        }
        /// <summary>
        /// This method is responsible to generate the byte array needed
        /// from the shared secret - the password.<br/>
        /// 
        /// RFC 2195 says:<br/>
        /// The shared secret is null-padded to a length of 64 bytes. If the
        /// shared secret is longer than 64 bytes, the MD5 digest of the
        /// shared secret is used as a 16 byte input to the keyed MD5
        /// calculation.
        /// </summary>
        /// <param name="password">This is the shared secret</param>
        /// <returns>The 64 bytes that is to be used from the shared secret</returns>
        /// <exception cref="ArgumentNullException">If <paramref name="password"/> is <see langword="null"/></exception>
        private static byte[] GetSharedSecretInBytes(string password)
        {
            if (password == null)
                throw new ArgumentNullException("password");
            // Get the password in bytes
            byte[] passwordBytes = Encoding.ASCII.GetBytes(password);
            // If the length is larger than 64, we need to
            if (passwordBytes.Length > 64)
            {
                passwordBytes = new MD5CryptoServiceProvider().ComputeHash(passwordBytes);
            }
            if (passwordBytes.Length != 64)
            {
                byte[] returner = new byte[64];
                for (int i = 0; i < passwordBytes.Length; i++)
                {
                    returner[i] = passwordBytes[i];
                }
                return returner;
            }
            return passwordBytes;
        }
    }
}
namespace OpenPop.Pop3
{
    /// <summary>
    /// Some of these states are defined by <a href="http://tools.ietf.org/html/rfc1939">RFC 1939</a>.<br/>
    /// Which commands that are allowed in which state can be seen in the same RFC.<br/>
    /// <br/>
    /// Used to keep track of which state the <see cref="Pop3Client"/> is in.
    /// </summary>
    internal enum ConnectionState
    {
        /// <summary>
        /// This is when the Pop3Client is not even connected to the server
        /// </summary>
        Disconnected,
        /// <summary>
        /// This is when the server is awaiting user credentials
        /// </summary>
        Authorization,
        /// <summary>
        /// This is when the server has been given the user credentials, and we are allowed
        /// to use commands specific to this users mail drop
        /// </summary>
        Transaction
    }
}
namespace OpenPop.Pop3
{
    /// <summary>
    /// Describes the authentication method to use when authenticating towards a POP3 server.
    /// </summary>
    public enum AuthenticationMethod
    {
        /// <summary>
        /// Authenticate using the UsernameAndPassword method.<br/>
        /// This will pass the username and password to the server in cleartext.<br/>
        /// <see cref="Apop"/> is more secure but might not be supported on a server.<br/>
        /// This method is not recommended. Use <see cref="Auto"/> instead.
        /// <br/>
        /// If SSL is used, there is no loss of security by using this authentication method.
        /// </summary>
        UsernameAndPassword,
        /// <summary>
        /// Authenticate using the Authenticated Post Office Protocol method, which is more secure then
        /// <see cref="UsernameAndPassword"/> since it is a request-response protocol where server checks if the
        ///  client knows a shared secret, which is the password, without the password itself being transmitted.<br/>
        /// This authentication method uses MD5 under its hood.<br/>
        /// <br/>
        /// This authentication method is not supported by many servers.<br/>
        /// Choose this option if you want maximum security.
        /// </summary>
        Apop,
        /// <summary>
        /// This is the recomended method to authenticate with.<br/>
        /// If <see cref="Apop"/> is supported by the server, <see cref="Apop"/> is used for authentication.<br/>
        /// If <see cref="Apop"/> is not supported, Auto will fall back to <see cref="UsernameAndPassword"/> authentication.
        /// </summary>
        Auto,
        /// <summary>
        /// Logs in the the POP3 server using CRAM-MD5 authentication scheme.<br/>
        /// This in essence uses the MD5 hashing algorithm on the user password and a server challenge.
        /// </summary>
        CramMd5
    }
}
namespace OpenPop.Pop3
{
    using System;
    using System.Security.Cryptography;
    using System.Text;
    /// <summary>
    /// Class for computing the digest needed when issuing the APOP command to a POP3 server.
    /// </summary>
    internal static class Apop
    {
        /// <summary>
        /// Create the digest for the APOP command so that the server can validate
        /// we know the password for some user.
        /// </summary>
        /// <param name="password">The password for the user</param>
        /// <param name="serverTimestamp">The timestamp advertised in the server greeting to the POP3 client</param>
        /// <returns>The password and timestamp hashed with the MD5 algorithm outputted as a HEX string</returns>
        public static string ComputeDigest(string password, string serverTimestamp)
        {
            if (password == null)
                throw new ArgumentNullException("password");
            if (serverTimestamp == null)
                throw new ArgumentNullException("serverTimestamp");
            // The APOP command authorizes itself by using the password together
            // with the server timestamp. This way the password is not transmitted
            // in clear text, and the server can still verify we have the password.
            byte[] digestToHash = Encoding.ASCII.GetBytes(serverTimestamp + password);
            using (MD5 md5 = new MD5CryptoServiceProvider())
            {
                // MD5 hash the digest
                byte[] result = md5.ComputeHash(digestToHash);
                // Convert the bytes to a hex string
                // BitConverter writes the output as AF-B3-...
                // We need lower-case output without "-"
                return BitConverter.ToString(result).Replace("-", "").ToLowerInvariant();
            }
        }
    }
}
namespace OpenPop.Pop3.Exceptions
{
    using System;
    /// <summary>
    /// Thrown when the specified POP3 server can not be found or connected to.
    /// </summary>    
    public class PopServerNotFoundException : PopClientException
    {
        ///<summary>
        /// Creates a PopServerNotFoundException with the given message and InnerException
        ///</summary>
        ///<param name="message">The message to include in the exception</param>
        ///<param name="innerException">The exception that is the cause of this exception</param>
        public PopServerNotFoundException(string message, Exception innerException)
            : base(message, innerException)
        { }
    }
}
namespace OpenPop.Pop3.Exceptions
{
    using System;
    /// <summary>
    /// Thrown when the POP3 server sends an error "-ERR" during initial handshake "HELO".
    /// </summary>    
    public class PopServerNotAvailableException : PopClientException
    {
        ///<summary>
        /// Creates a PopServerNotAvailableException with the given message and InnerException
        ///</summary>
        ///<param name="message">The message to include in the exception</param>
        ///<param name="innerException">The exception that is the cause of this exception</param>
        public PopServerNotAvailableException(string message, Exception innerException)
            : base(message, innerException)
        { }
    }
}
namespace OpenPop.Pop3.Exceptions
{
    using System;
    /// <summary>
    /// Thrown when the user mailbox is locked or in-use.<br/>
    /// </summary>
    /// <remarks>
    /// The mail boxes are locked when an existing session is open on the POP3 server.<br/>
    /// Only one POP3 client can use a POP3 account at a time.
    /// </remarks>
    public class PopServerLockedException : PopClientException
    {
        ///<summary>
        /// Creates a PopServerLockedException with the given inner exception
        ///</summary>
        ///<param name="innerException">The exception that is the cause of this exception</param>
        public PopServerLockedException(PopServerException innerException)
            : base("The account is locked or in use", innerException)
        { }
    }
}
namespace OpenPop.Pop3.Exceptions
{
    /// <summary>
    /// Thrown when the server does not return "+" to a command.<br/>
    /// The server response is then placed inside.
    /// </summary>
    public class PopServerException : PopClientException
    {
        ///<summary>
        /// Creates a PopServerException with the given message
        ///</summary>
        ///<param name="message">The message to include in the exception</param>
        public PopServerException(string message)
            : base(message)
        { }
    }
}
namespace OpenPop.Pop3.Exceptions
{
    using System;
    /// <summary>
    /// This is the base exception for all <see cref="Pop3Client"/> exceptions.
    /// </summary>
    public abstract class PopClientException : Exception
    {
        ///<summary>
        /// Creates a PopClientException with the given message and InnerException
        ///</summary>
        ///<param name="message">The message to include in the exception</param>
        ///<param name="innerException">The exception that is the cause of this exception</param>
        protected PopClientException(string message, Exception innerException)
            : base(message, innerException)
        {
            if (message == null)
                throw new ArgumentNullException("message");
            if (innerException == null)
                throw new ArgumentNullException("innerException");
        }
        ///<summary>
        /// Creates a PopClientException with the given message
        ///</summary>
        ///<param name="message">The message to include in the exception</param>
        protected PopClientException(string message)
            : base(message)
        {
            if (message == null)
                throw new ArgumentNullException("message");
        }
    }
}
namespace OpenPop.Pop3.Exceptions
{
    /// <summary>
    /// This exception indicates that the user has logged in recently and
    /// will not be allowed to login again until the login delay period has expired.
    /// Check the parameter to the LOGIN-DELAY capability, that the server responds with when
    /// <see cref="Pop3Client.Capabilities()"/> is called, to see what the delay is.
    /// </summary>
    public class LoginDelayException : PopClientException
    {
        ///<summary>
        /// Creates a LoginDelayException with the given inner exception
        ///</summary>
        ///<param name="innerException">The exception that is the cause of this exception</param>
        public LoginDelayException(PopServerException innerException)
            : base("The account is locked or in use", innerException)
        { }
    }
}
namespace OpenPop.Pop3.Exceptions
{
    /// <summary>
    /// Thrown when the <see cref="Pop3Client"/> is being used in an invalid way.<br/>
    /// This could for example happen if a someone tries to fetch a message without authenticating.
    /// </summary>
    public class InvalidUseException : PopClientException
    {
        ///<summary>
        /// Creates a InvalidUseException with the given message
        ///</summary>
        ///<param name="message">The message to include in the exception</param>
        public InvalidUseException(string message)
            : base(message)
        { }
    }
}
namespace OpenPop.Pop3.Exceptions
{
    using System;
    /// <summary>
    /// Thrown when the supplied username or password is not accepted by the POP3 server.
    /// </summary>
    public class InvalidLoginException : PopClientException
    {
        ///<summary>
        /// Creates a InvalidLoginException with the given message and InnerException
        ///</summary>
        ///<param name="innerException">The exception that is the cause of this exception</param>
        public InvalidLoginException(Exception innerException)
            : base("Server did not accept user credentials", innerException)
        { }
    }
}
namespace OpenPop.Mime
{
    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Net.Mime;
    using System.Text;
    using OpenPop.Mime.Decode;
    using OpenPop.Mime.Header;
    using OpenPop.Common;
    /// <summary>
    /// A MessagePart is a part of an email message used to describe the whole email parse tree.<br/>
    /// <br/>
    /// <b>Email messages are tree structures</b>:<br/>
    /// Email messages may contain large tree structures, and the MessagePart are the nodes of the this structure.<br/>
    /// A MessagePart may either be a leaf in the structure or a internal node with links to other MessageParts.<br/>
    /// The root of the message tree is the <see cref="Message"/> class.<br/>
    /// <br/>
    /// <b>Leafs</b>:<br/>
    /// If a MessagePart is a leaf, the part is not a <see cref="IsMultiPart">MultiPart</see> message.<br/>
    /// Leafs are where the contents of an email are placed.<br/>
    /// This includes, but is not limited to: attachments, text or images referenced from HTML.<br/>
    /// The content of an attachment can be fetched by using the <see cref="Body"/> property.<br/>
    /// If you want to have the text version of a MessagePart, use the <see cref="GetBodyAsText"/> method which will<br/>
    /// convert the <see cref="Body"/> into a string using the encoding the message was sent with.<br/>
    /// <br/>
    /// <b>Internal nodes</b>:<br/>
    /// If a MessagePart is an internal node in the email tree structure, then the part is a <see cref="IsMultiPart">MultiPart</see> message.<br/>
    /// The <see cref="MessageParts"/> property will then contain links to the parts it contain.<br/>
    /// The <see cref="Body"/> property of the MessagePart will not be set.<br/>
    /// <br/>
    /// See the example for a parsing example.<br/>
    /// This class cannot be instantiated from outside the library.
    /// </summary>
    /// <example>
    /// This example illustrates how the message parse tree looks like given a specific message<br/>
    /// <br/>
    /// The message source in this example is:<br/>
    /// <code>
    /// MIME-Version: 1.0
    ///    Content-Type: multipart/mixed; boundary="frontier"
    ///    
    ///    This is a message with multiple parts in MIME format.
    ///    --frontier
    /// Content-Type: text/plain
    ///    
    ///    This is the body of the message.
    ///    --frontier
    ///    Content-Type: application/octet-stream
    ///    Content-Transfer-Encoding: base64
    ///    
    ///    PGh0bWw+CiAgPGHLYWQ+CiAgPC9oZWFkPgogIDxib2R5PgogICAgPHA+VGhpcyBpcyB0aGUg
    ///    Ym9keSBvZiB0aGUgbWVzc2FnZS48L3A+CiAgPC9ib2R5Pgo8L2h0bWw+Cg==
    ///    --frontier--
    /// </code>
    /// The tree will look as follows, where the content-type media type of the message is listed<br/>
    /// <code>
    /// - Message root
    ///   - multipart/mixed MessagePart
    ///     - text/plain MessagePart
    ///     - application/octet-stream MessagePart
    /// </code>
    /// It is possible to have more complex message trees like the following:<br/>
    /// <code>
    /// - Message root
    ///   - multipart/mixed MessagePart
    ///     - text/plain MessagePart
    ///     - text/plain MessagePart
    ///     - multipart/parallel
    ///       - audio/basic
    ///       - image/tiff
    ///     - text/enriched
    ///     - message/rfc822
    /// </code>
    /// But it is also possible to have very simple message trees like:<br/>
    /// <code>
    /// - Message root
    ///   - text/plain
    /// </code>
    /// </example>
    public class MessagePart
    {
        #region Public properties
        /// <summary>
        /// The Content-Type header field.<br/>
        /// <br/>
        /// If not set, the ContentType is created by the default "text/plain; charset=us-ascii" which is
        /// defined in <a href="http://tools.ietf.org/html/rfc2045#section-5.2">RFC 2045 section 5.2</a>.<br/>
        /// <br/>
        /// If set, the default is overridden.
        /// </summary>
        public ContentType ContentType { get; private set; }
        /// <summary>
        /// A human readable description of the body<br/>
        /// <br/>
        /// <see langword="null"/> if no Content-Description header was present in the message.<br/>
        /// </summary>
        public string ContentDescription { get; private set; }
        /// <summary>
        /// This header describes the Content encoding during transfer.<br/>
        /// <br/>
        /// If no Content-Transfer-Encoding header was present in the message, it is set
        /// to the default of <see cref="Header.ContentTransferEncoding.SevenBit">SevenBit</see> in accordance to the RFC.
        /// </summary>
        /// <remarks>See <a href="http://tools.ietf.org/html/rfc2045#section-6">RFC 2045 section 6</a> for details</remarks>
        public ContentTransferEncoding ContentTransferEncoding { get; private set; }
        /// <summary>
        /// ID of the content part (like an attached image). Used with MultiPart messages.<br/>
        /// <br/>
        /// <see langword="null"/> if no Content-ID header field was present in the message.
        /// </summary>
        public string ContentId { get; private set; }
        /// <summary>
        /// Used to describe if a <see cref="MessagePart"/> is to be displayed or to be though of as an attachment.<br/>
        /// Also contains information about filename if such was sent.<br/>
        /// <br/>
        /// <see langword="null"/> if no Content-Disposition header field was present in the message
        /// </summary>
        public ContentDisposition ContentDisposition { get; private set; }
        /// <summary>
        /// This is the encoding used to parse the message body if the <see cref="MessagePart"/><br/>
        /// is not a MultiPart message. It is derived from the <see cref="ContentType"/> character set property.
        /// </summary>
        public Encoding BodyEncoding { get; private set; }
        /// <summary>
        /// This is the parsed body of this <see cref="MessagePart"/>.<br/>
        /// It is parsed in that way, if the body was ContentTransferEncoded, it has been decoded to the
        /// correct bytes.<br/>
        /// <br/>
        /// It will be <see langword="null"/> if this <see cref="MessagePart"/> is a MultiPart message.<br/>
        /// Use <see cref="IsMultiPart"/> to check if this <see cref="MessagePart"/> is a MultiPart message.
        /// </summary>
        public byte[] Body { get; private set; }
        /// <summary>
        /// Describes if this <see cref="MessagePart"/> is a MultiPart message<br/>
        /// <br/>
        /// The <see cref="MessagePart"/> is a MultiPart message if the <see cref="ContentType"/> media type property starts with "multipart/"
        /// </summary>
        public bool IsMultiPart
        {
            get
            {
                return ContentType.MediaType.StartsWith("multipart/", StringComparison.OrdinalIgnoreCase);
            }
        }
        /// <summary>
        /// A <see cref="MessagePart"/> is considered to be holding text in it's body if the MediaType
        /// starts either "text/" or is equal to "message/rfc822"
        /// </summary>
        public bool IsText
        {
            get
            {
                string mediaType = ContentType.MediaType;
                return mediaType.StartsWith("text/", StringComparison.OrdinalIgnoreCase) || mediaType.Equals("message/rfc822", StringComparison.OrdinalIgnoreCase);
            }
        }
        /// <summary>
        /// A <see cref="MessagePart"/> is considered to be an attachment, if<br/>
        /// - it is not holding <see cref="IsText">text</see> and is not a <see cref="IsMultiPart">MultiPart</see> message<br/>
        /// or<br/>
        /// - it has a Content-Disposition header that says it is an attachment
        /// </summary>
        public bool IsAttachment
        {
            get
            {
                // Inline is the opposite of attachment
                return (!IsText && !IsMultiPart) || (ContentDisposition != null && !ContentDisposition.Inline);
            }
        }
        /// <summary>
        /// This is a convenient-property for figuring out a FileName for this <see cref="MessagePart"/>.<br/>
        /// If the <see cref="MessagePart"/> is a MultiPart message, then it makes no sense to try to find a FileName.<br/>
        /// <br/>
        /// The FileName can be specified in the <see cref="ContentDisposition"/> or in the <see cref="ContentType"/> properties.<br/>
        /// If none of these places two places tells about the FileName, a default "(no name)" is returned.
        /// </summary>
        public string FileName { get; private set; }
        /// <summary>
        /// If this <see cref="MessagePart"/> is a MultiPart message, then this property
        /// has a list of each of the Multiple parts that the message consists of.<br/>
        /// <br/>
        /// It is <see langword="null"/> if it is not a MultiPart message.<br/>
        /// Use <see cref="IsMultiPart"/> to check if this <see cref="MessagePart"/> is a MultiPart message.
        /// </summary>
        public List<MessagePart> MessageParts { get; private set; }
        #endregion
        #region Constructors
        /// <summary>
        /// Used to construct the topmost message part
        /// </summary>
        /// <param name="rawBody">The body that needs to be parsed</param>
        /// <param name="headers">The headers that should be used from the message</param>
        /// <exception cref="ArgumentNullException">If <paramref name="rawBody"/> or <paramref name="headers"/> is <see langword="null"/></exception>
        internal MessagePart(byte[] rawBody, MessageHeader headers)
        {
            if (rawBody == null)
                throw new ArgumentNullException("rawBody");
            if (headers == null)
                throw new ArgumentNullException("headers");
            ContentType = headers.ContentType;
            ContentDescription = headers.ContentDescription;
            ContentTransferEncoding = headers.ContentTransferEncoding;
            ContentId = headers.ContentId;
            ContentDisposition = headers.ContentDisposition;
            FileName = FindFileName(ContentType, ContentDisposition, "(no name)");
            BodyEncoding = ParseBodyEncoding(ContentType.CharSet);
            ParseBody(rawBody);
        }
        #endregion
        #region Parsing
        /// <summary>
        /// Parses a character set into an encoding
        /// </summary>
        /// <param name="characterSet">The character set that needs to be parsed. <see langword="null"/> is allowed.</param>
        /// <returns>The encoding specified by the <paramref name="characterSet"/> parameter, or ASCII if the character set was <see langword="null"/> or empty</returns>
        private static Encoding ParseBodyEncoding(string characterSet)
        {
            // Default encoding in Mime messages is US-ASCII
            Encoding encoding = Encoding.ASCII;
            // If the character set was specified, find the encoding that the character
            // set describes, and use that one instead
            if (!string.IsNullOrEmpty(characterSet))
                encoding = EncodingFinder.FindEncoding(characterSet);
            return encoding;
        }
        /// <summary>
        /// Figures out the filename of this message part from some headers.
        /// <see cref="FileName"/> property.
        /// </summary>
        /// <param name="contentType">The Content-Type header</param>
        /// <param name="contentDisposition">The Content-Disposition header</param>
        /// <param name="defaultName">The default filename to use, if no other could be found</param>
        /// <returns>The filename found, or the default one if not such filename could be found in the headers</returns>
        /// <exception cref="ArgumentNullException">if <paramref name="contentType"/> is <see langword="null"/></exception>
        private static string FindFileName(ContentType contentType, ContentDisposition contentDisposition, string defaultName)
        {
            if (contentType == null)
                throw new ArgumentNullException("contentType");
            if (contentDisposition != null && contentDisposition.FileName != null)
                return contentDisposition.FileName;
            if (contentType.Name != null)
                return contentType.Name;
            return defaultName;
        }
        /// <summary>
        /// Parses a byte array as a body of an email message.
        /// </summary>
        /// <param name="rawBody">The byte array to parse as body of an email message. This array may not contain headers.</param>
        private void ParseBody(byte[] rawBody)
        {
            if (IsMultiPart)
            {
                // Parses a MultiPart message
                ParseMultiPartBody(rawBody);
            }
            else
            {
                // Parses a non MultiPart message
                // Decode the body accodingly and set the Body property
                Body = DecodeBody(rawBody, ContentTransferEncoding);
            }
        }
        /// <summary>
        /// Parses the <paramref name="rawBody"/> byte array as a MultiPart message.<br/>
        /// It is not valid to call this method if <see cref="IsMultiPart"/> returned <see langword="false"/>.<br/>
        /// Fills the <see cref="MessageParts"/> property of this <see cref="MessagePart"/>.
        /// </summary>
        /// <param name="rawBody">The byte array which is to be parsed as a MultiPart message</param>
        private void ParseMultiPartBody(byte[] rawBody)
        {
            // Fetch out the boundary used to delimit the messages within the body
            string multipartBoundary = ContentType.Boundary;
            // Fetch the individual MultiPart message parts using the MultiPart boundary
            List<byte[]> bodyParts = GetMultiPartParts(rawBody, multipartBoundary);
            // Initialize the MessageParts property, with room to as many bodies as we have found
            MessageParts = new List<MessagePart>(bodyParts.Count);
            // Now parse each byte array as a message body and add it the the MessageParts property
            foreach (byte[] bodyPart in bodyParts)
            {
                MessagePart messagePart = GetMessagePart(bodyPart);
                MessageParts.Add(messagePart);
            }
        }
        /// <summary>
        /// Given a byte array describing a full message.<br/>
        /// Parses the byte array into a <see cref="MessagePart"/>.
        /// </summary>
        /// <param name="rawMessageContent">The byte array containing both headers and body of a message</param>
        /// <returns>A <see cref="MessagePart"/> which was described by the <paramref name="rawMessageContent"/> byte array</returns>
        private static MessagePart GetMessagePart(byte[] rawMessageContent)
        {
            // Find the headers and the body parts of the byte array
            MessageHeader headers;
            byte[] body;
            HeaderExtractor.ExtractHeadersAndBody(rawMessageContent, out headers, out body);
            // Create a new MessagePart from the headers and the body
            return new MessagePart(body, headers);
        }
        /// <summary>
        /// Gets a list of byte arrays where each entry in the list is a full message of a message part
        /// </summary>
        /// <param name="rawBody">The raw byte array describing the body of a message which is a MultiPart message</param>
        /// <param name="multipPartBoundary">The delimiter that splits the different MultiPart bodies from each other</param>
        /// <returns>A list of byte arrays, each a full message of a <see cref="MessagePart"/></returns>
        private static List<byte[]> GetMultiPartParts(byte[] rawBody, string multipPartBoundary)
        {
            // This is the list we want to return
            List<byte[]> messageBodies = new List<byte[]>();
            // Create a stream from which we can find MultiPart boundaries
            using (MemoryStream stream = new MemoryStream(rawBody))
            {
                bool lastMultipartBoundaryEncountered;
                // Find the start of the first message in this multipart
                // Since the method returns the first character on a the line containing the MultiPart boundary, we
                // need to add the MultiPart boundary with prepended "--" and appended CRLF pair to the position returned.
                int startLocation = FindPositionOfNextMultiPartBoundary(stream, multipPartBoundary, out lastMultipartBoundaryEncountered) + ("--" + multipPartBoundary + "\r\n").Length;
                while (true)
                {
                    // When we have just parsed the last multipart entry, stop parsing on
                    if (lastMultipartBoundaryEncountered)
                        break;
                    // Find the end location of the current multipart
                    // Since the method returns the first character on a the line containing the MultiPart boundary, we
                    // need to go a CRLF pair back, so that we do not get that into the body of the message part
                    int stopLocation = FindPositionOfNextMultiPartBoundary(stream, multipPartBoundary, out lastMultipartBoundaryEncountered) - "\r\n".Length;
                    // If we could not find the next multipart boundary, but we had not yet discovered the last boundary, then
                    // we will consider the rest of the bytes as contained in a last message part.
                    if (stopLocation <= -1)
                    {
                        // Include everything except the last CRLF.
                        stopLocation = (int)stream.Length - "\r\n".Length;
                        // We consider this as the last part
                        lastMultipartBoundaryEncountered = true;
                        // Special case: when the last multipart delimiter is not ending with "--", but is indeed the last
                        // one, then the next multipart would contain nothing, and we should not include such one.
                        if (startLocation >= stopLocation)
                            break;
                    }
                    // We have now found the start and end of a message part
                    // Now we create a byte array with the correct length and put the message part's bytes into
                    // it and add it to our list we want to return
                    int length = stopLocation - startLocation;
                    byte[] messageBody = new byte[length];
                    Array.Copy(rawBody, startLocation, messageBody, 0, length);
                    messageBodies.Add(messageBody);
                    // We want to advance to the next message parts start.
                    // We can find this by jumping forward the MultiPart boundary from the last
                    // message parts end position
                    startLocation = stopLocation + ("\r\n" + "--" + multipPartBoundary + "\r\n").Length;
                }
            }
            // We are done
            return messageBodies;
        }
        /// <summary>
        /// Method that is able to find a specific MultiPart boundary in a Stream.<br/>
        /// The Stream passed should not be used for anything else then for looking for MultiPart boundaries
        /// <param name="stream">The stream to find the next MultiPart boundary in. Do not use it for anything else then with this method.</param>
        /// <param name="multiPartBoundary">The MultiPart boundary to look for. This should be found in the <see cref="ContentType"/> header</param>
        /// <param name="lastMultipartBoundaryFound">Is set to <see langword="true"/> if the next MultiPart boundary was indicated to be the last one, by having -- appended to it. Otherwise set to <see langword="false"/></param>
        /// </summary>
        /// <returns>The position of the first character of the line that contained MultiPartBoundary or -1 if no (more) MultiPart boundaries was found</returns>
        private static int FindPositionOfNextMultiPartBoundary(Stream stream, string multiPartBoundary, out bool lastMultipartBoundaryFound)
        {
            lastMultipartBoundaryFound = false;
            while (true)
            {
                // Get the current position. This is the first position on the line - no characters of the line will
                // have been read yet
                int currentPos = (int)stream.Position;
                // Read the line
                string line = StreamUtility.ReadLineAsAscii(stream);
                // If we kept reading until there was no more lines, we did not meet
                // the MultiPart boundary. -1 is then returned to describe this.
                if (line == null)
                    return -1;
                // The MultiPart boundary is the MultiPartBoundary with "--" in front of it
                // which is to be at the very start of a line
                if (line.StartsWith("--" + multiPartBoundary, StringComparison.Ordinal))
                {
                    // Check if the found boundary was also the last one
                    lastMultipartBoundaryFound = line.StartsWith("--" + multiPartBoundary + "--", StringComparison.OrdinalIgnoreCase);
                    return currentPos;
                }
            }
        }
        /// <summary>
        /// Decodes a byte array into another byte array based upon the Content Transfer encoding
        /// </summary>
        /// <param name="messageBody">The byte array to decode into another byte array</param>
        /// <param name="contentTransferEncoding">The <see cref="ContentTransferEncoding"/> of the byte array</param>
        /// <returns>A byte array which comes from the <paramref name="contentTransferEncoding"/> being used on the <paramref name="messageBody"/></returns>
        /// <exception cref="ArgumentNullException">If <paramref name="messageBody"/> is <see langword="null"/></exception>
        /// <exception cref="ArgumentOutOfRangeException">Thrown if the <paramref name="contentTransferEncoding"/> is unsupported</exception>
        private static byte[] DecodeBody(byte[] messageBody, ContentTransferEncoding contentTransferEncoding)
        {
            if (messageBody == null)
                throw new ArgumentNullException("messageBody");
            switch (contentTransferEncoding)
            {
                case ContentTransferEncoding.QuotedPrintable:
                    // If encoded in QuotedPrintable, everything in the body is in US-ASCII
                    return QuotedPrintable.DecodeContentTransferEncoding(Encoding.ASCII.GetString(messageBody));
                case ContentTransferEncoding.Base64:
                    // If encoded in Base64, everything in the body is in US-ASCII
                    return Base64.Decode(Encoding.ASCII.GetString(messageBody));
                case ContentTransferEncoding.SevenBit:
                case ContentTransferEncoding.Binary:
                case ContentTransferEncoding.EightBit:
                    // We do not have to do anything
                    return messageBody;
                default:
                    throw new ArgumentOutOfRangeException("contentTransferEncoding");
            }
        }
        #endregion
        #region Public methods
        /// <summary>
        /// Gets this MessagePart's <see cref="Body"/> as text.<br/>
        /// This is simply the <see cref="BodyEncoding"/> being used on the raw bytes of the <see cref="Body"/> property.<br/>
        /// This method is only valid to call if it is not a MultiPart message and therefore contains a body.<br/>
        /// </summary>
        /// <returns>The <see cref="Body"/> property as a string</returns>
        public string GetBodyAsText()
        {
            return BodyEncoding.GetString(Body);
        }
        /// <summary>
        /// Save this <see cref="MessagePart"/>'s contents to a file.<br/>
        /// There are no methods to reload the file.
        /// </summary>
        /// <param name="file">The File location to save the <see cref="MessagePart"/> to. Existent files will be overwritten.</param>
        /// <exception cref="ArgumentNullException">If <paramref name="file"/> is <see langword="null"/></exception>
        /// <exception>Other exceptions relevant to using a <see cref="FileStream"/> might be thrown as well</exception>
        public void Save(FileInfo file)
        {
            if (file == null)
                throw new ArgumentNullException("file");
            using (FileStream stream = new FileStream(file.FullName, FileMode.OpenOrCreate))
            {
                Save(stream);
            }
        }
        /// <summary>
        /// Save this <see cref="MessagePart"/>'s contents to a stream.<br/>
        /// </summary>
        /// <param name="messageStream">The stream to write to</param>
        /// <exception cref="ArgumentNullException">If <paramref name="messageStream"/> is <see langword="null"/></exception>
        /// <exception>Other exceptions relevant to <see cref="Stream.Write"/> might be thrown as well</exception>
        public void Save(Stream messageStream)
        {
            if (messageStream == null)
                throw new ArgumentNullException("messageStream");
            messageStream.Write(Body, 0, Body.Length);
        }
        #endregion
    }
}
namespace OpenPop.Mime
{
    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Net.Mail;
    using System.Text;
    using OpenPop.Mime.Header;
    using OpenPop.Mime.Traverse;
    /// <summary>
    /// This is the root of the email tree structure.<br/>
    /// <see cref="Mime.MessagePart"/> for a description about the structure.<br/>
    /// <br/>
    /// A Message (this class) contains the headers of an email message such as:
    /// <code>
    ///  - To
    ///  - From
    ///  - Subject
    ///  - Content-Type
    ///  - Message-ID
    /// </code>
    /// which are located in the <see cref="Headers"/> property.<br/>
    /// <br/>
    /// Use the <see cref="Message.MessagePart"/> property to find the actual content of the email message.
    /// </summary>
    /// <example>
    /// Examples are available on the <a href="http://hpop.sourceforge.net/">project homepage</a>.
    /// </example>
    public class Message
    {
        #region Public properties
        /// <summary>
        /// Headers of the Message.
        /// </summary>
        public MessageHeader Headers { get; private set; }
        /// <summary>
        /// This is the body of the email Message.<br/>
        /// <br/>
        /// If the body was parsed for this Message, this property will never be <see langword="null"/>.
        /// </summary>
        public MessagePart MessagePart { get; private set; }
        /// <summary>
        /// The raw content from which this message has been constructed.<br/>
        /// These bytes can be persisted and later used to recreate the Message.
        /// </summary>
        public byte[] RawMessage { get; private set; }
        #endregion
        #region Constructors
        /// <summary>
        /// Convenience constructor for <see cref="Mime.Message(byte[], bool)"/>.<br/>
        /// <br/>
        /// Creates a message from a byte array. The full message including its body is parsed.
        /// </summary>
        /// <param name="rawMessageContent">The byte array which is the message contents to parse</param>
        public Message(byte[] rawMessageContent)
            : this(rawMessageContent, true)
        {
        }
        /// <summary>
        /// Constructs a message from a byte array.<br/>
        /// <br/>
        /// The headers are always parsed, but if <paramref name="parseBody"/> is <see langword="false"/>, the body is not parsed.
        /// </summary>
        /// <param name="rawMessageContent">The byte array which is the message contents to parse</param>
        /// <param name="parseBody">
        /// <see langword="true"/> if the body should be parsed,
        /// <see langword="false"/> if only headers should be parsed out of the <paramref name="rawMessageContent"/> byte array
        /// </param>
        public Message(byte[] rawMessageContent, bool parseBody)
        {
            RawMessage = rawMessageContent;
            // Find the headers and the body parts of the byte array
            MessageHeader headersTemp;
            byte[] body;
            HeaderExtractor.ExtractHeadersAndBody(rawMessageContent, out headersTemp, out body);
            // Set the Headers property
            Headers = headersTemp;
            // Should we also parse the body?
            if (parseBody)
            {
                // Parse the body into a MessagePart
                MessagePart = new MessagePart(body, Headers);
            }
        }
        #endregion
        /// <summary>
        /// This method will convert this <see cref="Message"/> into a <see cref="MailMessage"/> equivalent.<br/>
        /// The returned <see cref="MailMessage"/> can be used with <see cref="System.Net.Mail.SmtpClient"/> to forward the email.<br/>
        /// <br/>
        /// You should be aware of the following about this method:
        /// <list type="bullet">
        /// <item>
        ///    All sender and receiver mail addresses are set.
        ///    If you send this email using a <see cref="System.Net.Mail.SmtpClient"/> then all
        ///    receivers in To, From, Cc and Bcc will receive the email once again.
        /// </item>
        /// <item>
        ///    If you view the source code of this Message and looks at the source code of the forwarded
        ///    <see cref="MailMessage"/> returned by this method, you will notice that the source codes are not the same.
        ///    The content that is presented by a mail client reading the forwarded <see cref="MailMessage"/> should be the
        ///    same as the original, though.
        /// </item>
        /// <item>
        ///    Content-Disposition headers will not be copied to the <see cref="MailMessage"/>.
        ///    It is simply not possible to set these on Attachments.
        /// </item>
        /// <item>
        ///    HTML content will be treated as the preferred view for the <see cref="MailMessage.Body"/>. Plain text content will be used for the
        ///    <see cref="MailMessage.Body"/> when HTML is not available.
        /// </item>
        /// </list>
        /// </summary>
        /// <returns>A <see cref="MailMessage"/> object that contains the same information that this Message does</returns>
        public MailMessage ToMailMessage()
        {
            // Construct an empty MailMessage to which we will gradually build up to look like the current Message object (this)
            MailMessage message = new MailMessage();
            message.Subject = Headers.Subject;
            // We here set the encoding to be UTF-8
            // We cannot determine what the encoding of the subject was at this point.
            // But since we know that strings in .NET is stored in UTF, we can
            // use UTF-8 to decode the subject into bytes
            message.SubjectEncoding = Encoding.UTF8;
            // The HTML version should take precedent over the plain text if it is available
            MessagePart preferredVersion = FindFirstHtmlVersion();
            if (preferredVersion != null)
            {
                // Make sure that the IsBodyHtml property is being set correctly for our content
                message.IsBodyHtml = true;
            }
            else
            {
                // otherwise use the first plain text version as the body, if it exists
                preferredVersion = FindFirstPlainTextVersion();
            }
            if (preferredVersion != null)
            {
                message.Body = preferredVersion.GetBodyAsText();
                message.BodyEncoding = preferredVersion.BodyEncoding;
            }
            // Add body and alternative views (html and such) to the message
            IEnumerable<MessagePart> textVersions = FindAllTextVersions();
            foreach (MessagePart textVersion in textVersions)
            {
                // The textVersions also contain the preferred version, therefore
                // we should skip that one
                if (textVersion == preferredVersion)
                    continue;
                MemoryStream stream = new MemoryStream(textVersion.Body);
                AlternateView alternative = new AlternateView(stream);
                alternative.ContentId = textVersion.ContentId;
                alternative.ContentType = textVersion.ContentType;
                message.AlternateViews.Add(alternative);
            }
            // Add attachments to the message
            IEnumerable<MessagePart> attachments = FindAllAttachments();
            foreach (MessagePart attachmentMessagePart in attachments)
            {
                MemoryStream stream = new MemoryStream(attachmentMessagePart.Body);
                Attachment attachment = new Attachment(stream, attachmentMessagePart.ContentType);
                attachment.ContentId = attachmentMessagePart.ContentId;
                message.Attachments.Add(attachment);
            }
            if (Headers.From != null && Headers.From.HasValidMailAddress)
                message.From = Headers.From.MailAddress;
            if (Headers.ReplyTo != null && Headers.ReplyTo.HasValidMailAddress)
            {
                //于溪玥
                message.ReplyToList.Add(Headers.ReplyTo.MailAddress);
            }
            if (Headers.Sender != null && Headers.Sender.HasValidMailAddress)
                message.Sender = Headers.Sender.MailAddress;
            foreach (RfcMailAddress to in Headers.To)
            {
                if (to.HasValidMailAddress)
                    message.To.Add(to.MailAddress);
            }
            foreach (RfcMailAddress cc in Headers.Cc)
            {
                if (cc.HasValidMailAddress)
                    message.CC.Add(cc.MailAddress);
            }
            foreach (RfcMailAddress bcc in Headers.Bcc)
            {
                if (bcc.HasValidMailAddress)
                    message.Bcc.Add(bcc.MailAddress);
            }
            return message;
        }
        #region MessagePart Searching Methods
        /// <summary>
        /// Finds the first text/plain <see cref="MessagePart"/> in this message.<br/>
        /// This is a convenience method - it simply propagates the call to <see cref="FindFirstMessagePartWithMediaType"/>.<br/>
        /// <br/>
        /// If no text/plain version is found, <see langword="null"/> is returned.
        /// </summary>
        /// <returns>
        /// <see cref="MessagePart"/> which has a MediaType of text/plain or <see langword="null"/>
        /// if such <see cref="MessagePart"/> could not be found.
        /// </returns>
        public MessagePart FindFirstPlainTextVersion()
        {
            return FindFirstMessagePartWithMediaType("text/plain");
        }
        /// <summary>
        /// Finds the first text/html <see cref="MessagePart"/> in this message.<br/>
        /// This is a convenience method - it simply propagates the call to <see cref="FindFirstMessagePartWithMediaType"/>.<br/>
        /// <br/>
        /// If no text/html version is found, <see langword="null"/> is returned.
        /// </summary>
        /// <returns>
        /// <see cref="MessagePart"/> which has a MediaType of text/html or <see langword="null"/>
        /// if such <see cref="MessagePart"/> could not be found.
        /// </returns>
        public MessagePart FindFirstHtmlVersion()
        {
            return FindFirstMessagePartWithMediaType("text/html");
        }
        /// <summary>
        /// Finds all the <see cref="MessagePart"/>'s which contains a text version.<br/>
        /// <br/>
        /// <see cref="Mime.MessagePart.IsText"/> for MessageParts which are considered to be text versions.<br/>
        /// <br/>
        /// Examples of MessageParts media types are:
        /// <list type="bullet">
        ///    <item>text/plain</item>
        ///    <item>text/html</item>
        ///    <item>text/xml</item>
        /// </list>
        /// </summary>
        /// <returns>A List of MessageParts where each part is a text version</returns>
        public List<MessagePart> FindAllTextVersions()
        {
            return new TextVersionFinder().VisitMessage(this);
        }
        /// <summary>
        /// Finds all the <see cref="MessagePart"/>'s which are attachments to this message.<br/>
        /// <br/>
        /// <see cref="Mime.MessagePart.IsAttachment"/> for MessageParts which are considered to be attachments.
        /// </summary>
        /// <returns>A List of MessageParts where each is considered an attachment</returns>
        public List<MessagePart> FindAllAttachments()
        {
            return new AttachmentFinder().VisitMessage(this);
        }
        /// <summary>
        /// Finds the first <see cref="MessagePart"/> in the <see cref="Message"/> hierarchy with the given MediaType.<br/>
        /// <br/>
        /// The search in the hierarchy is a depth-first traversal.
        /// </summary>
        /// <param name="mediaType">The MediaType to search for. Case is ignored.</param>
        /// <returns>
        /// A <see cref="MessagePart"/> with the given MediaType or <see langword="null"/> if no such <see cref="MessagePart"/> was found
        /// </returns>
        public MessagePart FindFirstMessagePartWithMediaType(string mediaType)
        {
            return new FindFirstMessagePartWithMediaType().VisitMessage(this, mediaType);
        }
        /// <summary>
        /// Finds all the <see cref="MessagePart"/>s in the <see cref="Message"/> hierarchy with the given MediaType.
        /// </summary>
        /// <param name="mediaType">The MediaType to search for. Case is ignored.</param>
        /// <returns>
        /// A List of <see cref="MessagePart"/>s with the given MediaType.<br/>
        /// The List might be empty if no such <see cref="MessagePart"/>s were found.<br/>
        /// The order of the elements in the list is the order which they are found using
        /// a depth first traversal of the <see cref="Message"/> hierarchy.
        /// </returns>
        public List<MessagePart> FindAllMessagePartsWithMediaType(string mediaType)
        {
            return new FindAllMessagePartsWithMediaType().VisitMessage(this, mediaType);
        }
        #endregion
        #region Message Persistence
        /// <summary>
        /// Save this <see cref="Message"/> to a file.<br/>
        /// <br/>
        /// Can be loaded at a later time using the <see cref="Load(FileInfo)"/> method.
        /// </summary>
        /// <param name="file">The File location to save the <see cref="Message"/> to. Existent files will be overwritten.</param>
        /// <exception cref="ArgumentNullException">If <paramref name="file"/> is <see langword="null"/></exception>
        /// <exception>Other exceptions relevant to using a <see cref="FileStream"/> might be thrown as well</exception>
        public void Save(FileInfo file)
        {
            if (file == null)
                throw new ArgumentNullException("file");
            using (FileStream stream = new FileStream(file.FullName, FileMode.OpenOrCreate))
            {
                Save(stream);
            }
        }
        /// <summary>
        /// Save this <see cref="Message"/> to a stream.<br/>
        /// </summary>
        /// <param name="messageStream">The stream to write to</param>
        /// <exception cref="ArgumentNullException">If <paramref name="messageStream"/> is <see langword="null"/></exception>
        /// <exception>Other exceptions relevant to <see cref="Stream.Write"/> might be thrown as well</exception>
        public void Save(Stream messageStream)
        {
            if (messageStream == null)
                throw new ArgumentNullException("messageStream");
            messageStream.Write(RawMessage, 0, RawMessage.Length);
        }
        /// <summary>
        /// Loads a <see cref="Message"/> from a file containing a raw email.
        /// </summary>
        /// <param name="file">The File location to load the <see cref="Message"/> from. The file must exist.</param>
        /// <exception cref="ArgumentNullException">If <paramref name="file"/> is <see langword="null"/></exception>
        /// <exception cref="FileNotFoundException">If <paramref name="file"/> does not exist</exception>
        /// <exception>Other exceptions relevant to a <see cref="FileStream"/> might be thrown as well</exception>
        /// <returns>A <see cref="Message"/> with the content loaded from the <paramref name="file"/></returns>
        public static Message Load(FileInfo file)
        {
            if (file == null)
                throw new ArgumentNullException("file");
            if (!file.Exists)
                throw new FileNotFoundException("Cannot load message from non-existent file", file.FullName);
            using (FileStream stream = new FileStream(file.FullName, FileMode.Open))
            {
                return Load(stream);
            }
        }
        /// <summary>
        /// Loads a <see cref="Message"/> from a <see cref="Stream"/> containing a raw email.
        /// </summary>
        /// <param name="messageStream">The <see cref="Stream"/> from which to load the raw <see cref="Message"/></param>
        /// <exception cref="ArgumentNullException">If <paramref name="messageStream"/> is <see langword="null"/></exception>
        /// <exception>Other exceptions relevant to <see cref="Stream.Read"/> might be thrown as well</exception>
        /// <returns>A <see cref="Message"/> with the content loaded from the <paramref name="messageStream"/></returns>
        public static Message Load(Stream messageStream)
        {
            if (messageStream == null)
                throw new ArgumentNullException("messageStream");
            using (MemoryStream outStream = new MemoryStream())
            {
#if DOTNET4
                // TODO: Enable using native v4 framework methods when support is formally added.
                messageStream.CopyTo(outStream);
#else
                int bytesRead;
                byte[] buffer = new byte[4096];
                while ((bytesRead = messageStream.Read(buffer, 0, 4096)) > 0)
                {
                    outStream.Write(buffer, 0, bytesRead);
                }
#endif
                byte[] content = outStream.ToArray();
                return new Message(content);
            }
        }
        #endregion
    }
}
namespace OpenPop.Mime.Traverse
{
    using System;
    using System.Collections.Generic;
    /// <summary>
    /// Finds all text/[something] versions in a Message hierarchy
    /// </summary>
    internal class TextVersionFinder : MultipleMessagePartFinder
    {
        protected override List<MessagePart> CaseLeaf(MessagePart messagePart)
        {
            if (messagePart == null)
                throw new ArgumentNullException("messagePart");
            // Maximum space needed is one
            List<MessagePart> leafAnswer = new List<MessagePart>(1);
            if (messagePart.IsText)
                leafAnswer.Add(messagePart);
            return leafAnswer;
        }
    }
}
namespace OpenPop.Mime.Traverse
{
    using System;
    using System.Collections.Generic;
    ///<summary>
    /// An abstract class that implements the MergeLeafAnswers method.<br/>
    /// The method simply returns the union of all answers from the leaves.
    ///</summary>
    public abstract class MultipleMessagePartFinder : AnswerMessageTraverser<List<MessagePart>>
    {
        /// <summary>
        /// Adds all the <paramref name="leafAnswers"/> in one big answer
        /// </summary>
        /// <param name="leafAnswers">The answers to merge</param>
        /// <returns>A list with has all the elements in the <paramref name="leafAnswers"/> lists</returns>
        /// <exception cref="ArgumentNullException">if <paramref name="leafAnswers"/> is <see langword="null"/></exception>
        protected override List<MessagePart> MergeLeafAnswers(List<List<MessagePart>> leafAnswers)
        {
            if (leafAnswers == null)
                throw new ArgumentNullException("leafAnswers");
            // We simply create a list with all the answer generated from the leaves
            List<MessagePart> mergedResults = new List<MessagePart>();
            foreach (List<MessagePart> leafAnswer in leafAnswers)
            {
                mergedResults.AddRange(leafAnswer);
            }
            return mergedResults;
        }
    }
}
namespace OpenPop.Mime.Traverse
{
    /// <summary>
    /// This interface describes a MessageTraverser which is able to traverse a Message structure
    /// and deliver some answer given some question.
    /// </summary>
    /// <typeparam name="TAnswer">This is the type of the answer you want to have delivered.</typeparam>
    /// <typeparam name="TQuestion">This is the type of the question you want to have answered.</typeparam>
    public interface IQuestionAnswerMessageTraverser<TQuestion, TAnswer>
    {
        /// <summary>
        /// Call this when you want to apply this traverser on a <see cref="Message"/>.
        /// </summary>
        /// <param name="message">The <see cref="Message"/> which you want to traverse. Must not be <see langword="null"/>.</param>
        /// <param name="question">The question</param>
        /// <returns>An answer</returns>
        TAnswer VisitMessage(Message message, TQuestion question);
        /// <summary>
        /// Call this when you want to apply this traverser on a <see cref="MessagePart"/>.
        /// </summary>
        /// <param name="messagePart">The <see cref="MessagePart"/> which you want to traverse. Must not be <see langword="null"/>.</param>
        /// <param name="question">The question</param>
        /// <returns>An answer</returns>
        TAnswer VisitMessagePart(MessagePart messagePart, TQuestion question);
    }
}
namespace OpenPop.Mime.Traverse
{
    /// <summary>
    /// This interface describes a MessageTraverser which is able to traverse a Message hierarchy structure
    /// and deliver some answer.
    /// </summary>
    /// <typeparam name="TAnswer">This is the type of the answer you want to have delivered.</typeparam>
    public interface IAnswerMessageTraverser<TAnswer>
    {
        /// <summary>
        /// Call this when you want to apply this traverser on a <see cref="Message"/>.
        /// </summary>
        /// <param name="message">The <see cref="Message"/> which you want to traverse. Must not be <see langword="null"/>.</param>
        /// <returns>An answer</returns>
        TAnswer VisitMessage(Message message);
        /// <summary>
        /// Call this when you want to apply this traverser on a <see cref="MessagePart"/>.
        /// </summary>
        /// <param name="messagePart">The <see cref="MessagePart"/> which you want to traverse. Must not be <see langword="null"/>.</param>
        /// <returns>An answer</returns>
        TAnswer VisitMessagePart(MessagePart messagePart);
    }
}
namespace OpenPop.Mime.Traverse
{
    using System;
    ///<summary>
    /// Finds the first <see cref="MessagePart"/> which have a given MediaType in a depth first traversal.
    ///</summary>
    internal class FindFirstMessagePartWithMediaType : IQuestionAnswerMessageTraverser<string, MessagePart>
    {
        /// <summary>
        /// Finds the first <see cref="MessagePart"/> with the given MediaType
        /// </summary>
        /// <param name="message">The <see cref="Message"/> to start looking in</param>
        /// <param name="question">The MediaType to look for. Case is ignored.</param>
        /// <returns>A <see cref="MessagePart"/> with the given MediaType or <see langword="null"/> if no such <see cref="MessagePart"/> was found</returns>
        public MessagePart VisitMessage(Message message, string question)
        {
            if (message == null)
                throw new ArgumentNullException("message");
            return VisitMessagePart(message.MessagePart, question);
        }
        /// <summary>
        /// Finds the first <see cref="MessagePart"/> with the given MediaType
        /// </summary>
        /// <param name="messagePart">The <see cref="MessagePart"/> to start looking in</param>
        /// <param name="question">The MediaType to look for. Case is ignored.</param>
        /// <returns>A <see cref="MessagePart"/> with the given MediaType or <see langword="null"/> if no such <see cref="MessagePart"/> was found</returns>
        public MessagePart VisitMessagePart(MessagePart messagePart, string question)
        {
            if (messagePart == null)
                throw new ArgumentNullException("messagePart");
            if (messagePart.ContentType.MediaType.Equals(question, StringComparison.OrdinalIgnoreCase))
                return messagePart;
            if (messagePart.IsMultiPart)
            {
                foreach (MessagePart part in messagePart.MessageParts)
                {
                    MessagePart result = VisitMessagePart(part, question);
                    if (result != null)
                        return result;
                }
            }
            return null;
        }
    }
}
namespace OpenPop.Mime.Traverse
{
    using System;
    using System.Collections.Generic;
    ///<summary>
    /// Finds all the <see cref="MessagePart"/>s which have a given MediaType using a depth first traversal.
    ///</summary>
    internal class FindAllMessagePartsWithMediaType : IQuestionAnswerMessageTraverser<string, List<MessagePart>>
    {
        /// <summary>
        /// Finds all the <see cref="MessagePart"/>s with the given MediaType
        /// </summary>
        /// <param name="message">The <see cref="Message"/> to start looking in</param>
        /// <param name="question">The MediaType to look for. Case is ignored.</param>
        /// <returns>
        /// A List of <see cref="MessagePart"/>s with the given MediaType.<br/>
        /// <br/>
        /// The List might be empty if no such <see cref="MessagePart"/>s were found.<br/>
        /// The order of the elements in the list is the order which they are found using
        /// a depth first traversal of the <see cref="Message"/> hierarchy.
        /// </returns>
        public List<MessagePart> VisitMessage(Message message, string question)
        {
            if (message == null)
                throw new ArgumentNullException("message");
            return VisitMessagePart(message.MessagePart, question);
        }
        /// <summary>
        /// Finds all the <see cref="MessagePart"/>s with the given MediaType
        /// </summary>
        /// <param name="messagePart">The <see cref="MessagePart"/> to start looking in</param>
        /// <param name="question">The MediaType to look for. Case is ignored.</param>
        /// <returns>
        /// A List of <see cref="MessagePart"/>s with the given MediaType.<br/>
        /// <br/>
        /// The List might be empty if no such <see cref="MessagePart"/>s were found.<br/>
        /// The order of the elements in the list is the order which they are found using
        /// a depth first traversal of the <see cref="Message"/> hierarchy.
        /// </returns>
        public List<MessagePart> VisitMessagePart(MessagePart messagePart, string question)
        {
            if (messagePart == null)
                throw new ArgumentNullException("messagePart");
            List<MessagePart> results = new List<MessagePart>();
            if (messagePart.ContentType.MediaType.Equals(question, StringComparison.OrdinalIgnoreCase))
                results.Add(messagePart);
            if (messagePart.IsMultiPart)
            {
                foreach (MessagePart part in messagePart.MessageParts)
                {
                    List<MessagePart> result = VisitMessagePart(part, question);
                    results.AddRange(result);
                }
            }
            return results;
        }
    }
}
namespace OpenPop.Mime.Traverse
{
    using System;
    using System.Collections.Generic;
    /// <summary>
    /// Finds all <see cref="MessagePart"/>s which are considered to be attachments
    /// </summary>
    internal class AttachmentFinder : MultipleMessagePartFinder
    {
        protected override List<MessagePart> CaseLeaf(MessagePart messagePart)
        {
            if (messagePart == null)
                throw new ArgumentNullException("messagePart");
            // Maximum space needed is one
            List<MessagePart> leafAnswer = new List<MessagePart>(1);
            if (messagePart.IsAttachment)
                leafAnswer.Add(messagePart);
            return leafAnswer;
        }
    }
}
namespace OpenPop.Mime.Traverse
{
    using System;
    using System.Collections.Generic;
    /// <summary>
    /// This is an abstract class which handles traversing of a <see cref="Message"/> tree structure.<br/>
    /// It runs through the message structure using a depth-first traversal.
    /// </summary>
    /// <typeparam name="TAnswer">The answer you want from traversing the message tree structure</typeparam>
    public abstract class AnswerMessageTraverser<TAnswer> : IAnswerMessageTraverser<TAnswer>
    {
        /// <summary>
        /// Call this when you want an answer for a full message.
        /// </summary>
        /// <param name="message">The message you want to traverse</param>
        /// <returns>An answer</returns>
        /// <exception cref="ArgumentNullException">if <paramref name="message"/> is <see langword="null"/></exception>
        public TAnswer VisitMessage(Message message)
        {
            if (message == null)
                throw new ArgumentNullException("message");
            return VisitMessagePart(message.MessagePart);
        }
        /// <summary>
        /// Call this method when you want to find an answer for a <see cref="MessagePart"/>
        /// </summary>
        /// <param name="messagePart">The <see cref="MessagePart"/> part you want an answer from.</param>
        /// <returns>An answer</returns>
        /// <exception cref="ArgumentNullException">if <paramref name="messagePart"/> is <see langword="null"/></exception>
        public TAnswer VisitMessagePart(MessagePart messagePart)
        {
            if (messagePart == null)
                throw new ArgumentNullException("messagePart");
            if (messagePart.IsMultiPart)
            {
                List<TAnswer> leafAnswers = new List<TAnswer>(messagePart.MessageParts.Count);
                foreach (MessagePart part in messagePart.MessageParts)
                {
                    leafAnswers.Add(VisitMessagePart(part));
                }
                return MergeLeafAnswers(leafAnswers);
            }
            return CaseLeaf(messagePart);
        }
        /// <summary>
        /// For a concrete implementation an answer must be returned for a leaf <see cref="MessagePart"/>, which are
        /// MessageParts that are not <see cref="MessagePart.IsMultiPart">MultiParts.</see>
        /// </summary>
        /// <param name="messagePart">The message part which is a leaf and thereby not a MultiPart</param>
        /// <returns>An answer</returns>
        protected abstract TAnswer CaseLeaf(MessagePart messagePart);
        /// <summary>
        /// For a concrete implementation, when a MultiPart <see cref="MessagePart"/> has fetched it's answers from it's children, these
        /// answers needs to be merged. This is the responsibility of this method.
        /// </summary>
        /// <param name="leafAnswers">The answer that the leafs gave</param>
        /// <returns>A merged answer</returns>
        protected abstract TAnswer MergeLeafAnswers(List<TAnswer> leafAnswers);
    }
}
namespace OpenPop.Mime.Header
{
    using System;
    using System.Collections.Generic;
    using System.Net.Mail;
    using OpenPop.Mime.Decode;
    using OpenPop.Common.Logging;
    /// <summary>
    /// This class is used for RFC compliant email addresses.<br/>
    /// <br/>
    /// The class cannot be instantiated from outside the library.
    /// </summary>
    /// <remarks>
    /// The <seealso cref="MailAddress"/> does not cover all the possible formats 
    /// for <a href="http://tools.ietf.org/html/rfc5322#section-3.4">RFC 5322 section 3.4</a> compliant email addresses.
    /// This class is used as an address wrapper to account for that deficiency.
    /// </remarks>
    public class RfcMailAddress
    {
        #region Properties
        ///<summary>
        /// The email address of this <see cref="RfcMailAddress"/><br/>
        /// It is possibly string.Empty since RFC mail addresses does not require an email address specified.
        ///</summary>
        ///<example>
        /// Example header with email address:<br/>
        /// To: <c>Test test@mail.com</c><br/>
        /// Address will be <c>test@mail.com</c><br/>
        ///</example>
        ///<example>
        /// Example header without email address:<br/>
        /// To: <c>Test</c><br/>
        /// Address will be <see cref="string.Empty"/>.
        ///</example>
        public string Address { get; private set; }
        ///<summary>
        /// The display name of this <see cref="RfcMailAddress"/><br/>
        /// It is possibly <see cref="string.Empty"/> since RFC mail addresses does not require a display name to be specified.
        ///</summary>
        ///<example>
        /// Example header with display name:<br/>
        /// To: <c>Test test@mail.com</c><br/>
        /// DisplayName will be <c>Test</c>
        ///</example>
        ///<example>
        /// Example header without display name:<br/>
        /// To: <c>test@test.com</c><br/>
        /// DisplayName will be <see cref="string.Empty"/>
        ///</example>
        public string DisplayName { get; private set; }
        /// <summary>
        /// This is the Raw string used to describe the <see cref="RfcMailAddress"/>.
        /// </summary>
        public string Raw { get; private set; }
        /// <summary>
        /// The <see cref="MailAddress"/> associated with the <see cref="RfcMailAddress"/>. 
        /// </summary>
        /// <remarks>
        /// The value of this property can be <see lanword="null"/> in instances where the <see cref="MailAddress"/> cannot represent the address properly.<br/>
        /// Use <see cref="HasValidMailAddress"/> property to see if this property is valid.
        /// </remarks>
        public MailAddress MailAddress { get; private set; }
        /// <summary>
        /// Specifies if the object contains a valid <see cref="MailAddress"/> reference.
        /// </summary>
        public bool HasValidMailAddress
        {
            get { return MailAddress != null; }
        }
        #endregion
        #region Constructors
        /// <summary>
        /// Constructs an <see cref="RfcMailAddress"/> object from a <see cref="MailAddress"/> object.<br/>
        /// This constructor is used when we were able to construct a <see cref="MailAddress"/> from a string.
        /// </summary>
        /// <param name="mailAddress">The address that <paramref name="raw"/> was parsed into</param>
        /// <param name="raw">The raw unparsed input which was parsed into the <paramref name="mailAddress"/></param>
        /// <exception cref="ArgumentNullException">If <paramref name="mailAddress"/> or <paramref name="raw"/> is <see langword="null"/></exception>
        private RfcMailAddress(MailAddress mailAddress, string raw)
        {
            if (mailAddress == null)
                throw new ArgumentNullException("mailAddress");
            if (raw == null)
                throw new ArgumentNullException("raw");
            MailAddress = mailAddress;
            Address = mailAddress.Address;
            DisplayName = mailAddress.DisplayName;
            Raw = raw;
        }
        /// <summary>
        /// When we were unable to parse a string into a <see cref="MailAddress"/>, this constructor can be
        /// used. The Raw string is then used as the <see cref="DisplayName"/>.
        /// </summary>
        /// <param name="raw">The raw unparsed input which could not be parsed</param>
        /// <exception cref="ArgumentNullException">If <paramref name="raw"/> is <see langword="null"/></exception>
        private RfcMailAddress(string raw)
        {
            if (raw == null)
                throw new ArgumentNullException("raw");
            MailAddress = null;
            Address = string.Empty;
            DisplayName = raw;
            Raw = raw;
        }
        #endregion
        /// <summary>
        /// A string representation of the <see cref="RfcMailAddress"/> object
        /// </summary>
        /// <returns>Returns the string representation for the object</returns>
        public override string ToString()
        {
            if (HasValidMailAddress)
                return MailAddress.ToString();
            return Raw;
        }
        #region Parsing
        /// <summary>
        /// Parses an email address from a MIME header<br/>
        /// <br/>
        /// Examples of input:
        /// <c>Eksperten mailrobot &lt;noreply@mail.eksperten.dk&gt;</c><br/>
        /// <c>"Eksperten mailrobot" &lt;noreply@mail.eksperten.dk&gt;</c><br/>
        /// <c>&lt;noreply@mail.eksperten.dk&gt;</c><br/>
        /// <c>noreply@mail.eksperten.dk</c><br/>
        /// <br/>
        /// It might also contain encoded text, which will then be decoded.
        /// </summary>
        /// <param name="input">The value to parse out and email and/or a username</param>
        /// <returns>A <see cref="RfcMailAddress"/></returns>
        /// <exception cref="ArgumentNullException">If <paramref name="input"/> is <see langword="null"/></exception>
        /// <remarks>
        /// <see href="http://tools.ietf.org/html/rfc5322#section-3.4">RFC 5322 section 3.4</see> for more details on email syntax.<br/>
        /// <see cref="EncodedWord.Decode">For more information about encoded text</see>.
        /// </remarks>
        internal static RfcMailAddress ParseMailAddress(string input)
        {
            if (input == null)
                throw new ArgumentNullException("input");
            // Decode the value, if it was encoded
            input = EncodedWord.Decode(input.Trim());
            // Find the location of the email address
            int indexStartEmail = input.LastIndexOf('<');
            int indexEndEmail = input.LastIndexOf('>');
            try
            {
                if (indexStartEmail >= 0 && indexEndEmail >= 0)
                {
                    string username;
                    // Check if there is a username in front of the email address
                    if (indexStartEmail > 0)
                    {
                        // Parse out the user
                        username = input.Substring(0, indexStartEmail).Trim();
                    }
                    else
                    {
                        // There was no user
                        username = string.Empty;
                    }
                    // Parse out the email address without the "<"  and ">"
                    indexStartEmail = indexStartEmail + 1;
                    int emailLength = indexEndEmail - indexStartEmail;
                    string emailAddress = input.Substring(indexStartEmail, emailLength).Trim();
                    // There has been cases where there was no emailaddress between the < and >
                    if (!string.IsNullOrEmpty(emailAddress))
                    {
                        // If the username is quoted, MailAddress' constructor will remove them for us
                        return new RfcMailAddress(new MailAddress(emailAddress, username), input);
                    }
                }
                // This might be on the form noreply@mail.eksperten.dk
                // Check if there is an email, if notm there is no need to try
                if (input.Contains("@"))
                    return new RfcMailAddress(new MailAddress(input), input);
            }
            catch (FormatException)
            {
                // Sometimes invalid emails are sent, like sqlmap-user@sourceforge.net. (last period is illigal)
                DefaultLogger.Log.LogError("RfcMailAddress: Improper mail address: \"" + input + "\"");
            }
            // It could be that the format used was simply a name
            // which is indeed valid according to the RFC
            // Example:
            // Eksperten mailrobot
            return new RfcMailAddress(input);
        }
        /// <summary>
        /// Parses input of the form<br/>
        /// <c>Eksperten mailrobot &lt;noreply@mail.eksperten.dk&gt;, ...</c><br/>
        /// to a list of RFCMailAddresses
        /// </summary>
        /// <param name="input">The input that is a comma-separated list of EmailAddresses to parse</param>
        /// <returns>A List of <seealso cref="RfcMailAddress"/> objects extracted from the <paramref name="input"/> parameter.</returns>
        /// <exception cref="ArgumentNullException">If <paramref name="input"/> is <see langword="null"/></exception>
        internal static List<RfcMailAddress> ParseMailAddresses(string input)
        {
            if (input == null)
                throw new ArgumentNullException("input");
            List<RfcMailAddress> returner = new List<RfcMailAddress>();
            // MailAddresses are split by commas
            IEnumerable<string> mailAddresses = Utility.SplitStringWithCharNotInsideQuotes(input, ',');
            // Parse each of these
            foreach (string mailAddress in mailAddresses)
            {
                returner.Add(ParseMailAddress(mailAddress));
            }
            return returner;
        }
        #endregion
    }
}
namespace OpenPop.Mime.Header
{
    using System;
    using System.Collections.Generic;
    using System.Text.RegularExpressions;
    using OpenPop.Mime.Decode;
    /// <summary>
    /// Class that hold information about one "Received:" header line.
    /// 
    /// Visit these RFCs for more information:
    /// <see href="http://tools.ietf.org/html/rfc5321#section-4.4">RFC 5321 section 4.4</see>
    /// <see href="http://tools.ietf.org/html/rfc4021#section-3.6.7">RFC 4021 section 3.6.7</see>
    /// <see href="http://tools.ietf.org/html/rfc2822#section-3.6.7">RFC 2822 section 3.6.7</see>
    /// <see href="http://tools.ietf.org/html/rfc2821#section-4.4">RFC 2821 section 4.4</see>
    /// </summary>
    public class Received
    {
        /// <summary>
        /// The date of this received line.
        /// Is <see cref="DateTime.MinValue"/> if not present in the received header line.
        /// </summary>
        public DateTime Date { get; private set; }
        /// <summary>
        /// A dictionary that contains the names and values of the
        /// received header line.
        /// If the received header is invalid and contained one name
        /// multiple times, the first one is used and the rest is ignored.
        /// </summary>
        /// <example>
        /// If the header lines looks like:
        /// <code>
        /// from sending.com (localMachine [127.0.0.1]) by test.net (Postfix)
        /// </code>
        /// then the dictionary will contain two keys: "from" and "by" with the values
        /// "sending.com (localMachine [127.0.0.1])" and "test.net (Postfix)".
        /// </example>
        public Dictionary<string, string> Names { get; private set; }
        /// <summary>
        /// The raw input string that was parsed into this class.
        /// </summary>
        public string Raw { get; private set; }
        /// <summary>
        /// Parses a Received header value.
        /// </summary>
        /// <param name="headerValue">The value for the header to be parsed</param>
        /// <exception cref="ArgumentNullException"><exception cref="ArgumentNullException">If <paramref name="headerValue"/> is <see langword="null"/></exception></exception>
        public Received(string headerValue)
        {
            if (headerValue == null)
                throw new ArgumentNullException("headerValue");
            // Remember the raw input if someone whishes to use it
            Raw = headerValue;
            // Default Date value
            Date = DateTime.MinValue;
            // The date part is the last part of the string, and is preceeded by a semicolon
            // Some emails forgets to specify the date, therefore we need to check if it is there
            if (headerValue.Contains(";"))
            {
                string datePart = headerValue.Substring(headerValue.LastIndexOf(";") + 1);
                Date = Rfc2822DateTime.StringToDate(datePart);
            }
            Names = ParseDictionary(headerValue);
        }
        /// <summary>
        /// Parses the Received header name-value-list into a dictionary.
        /// </summary>
        /// <param name="headerValue">The full header value for the Received header</param>
        /// <returns>A dictionary where the name-value-list has been parsed into</returns>
        private static Dictionary<string, string> ParseDictionary(string headerValue)
        {
            Dictionary<string, string> dictionary = new Dictionary<string, string>();
            // Remove the date part from the full headerValue if it is present
            string headerValueWithoutDate = headerValue;
            if (headerValue.Contains(";"))
            {
                headerValueWithoutDate = headerValue.Substring(0, headerValue.LastIndexOf(";"));
            }
            // Reduce any whitespace character to one space only
            headerValueWithoutDate = Regex.Replace(headerValueWithoutDate, @"\s+", " ");
            // The regex below should capture the following:
            // The name consists of non-whitespace characters followed by a whitespace and then the value follows.
            // There are multiple cases for the value part:
            //   1: Value is just some characters not including any whitespace
            //   2: Value is some characters, a whitespace followed by an unlimited number of
            //      parenthesized values which can contain whitespaces, each delimited by whitespace
            //
            // Cheat sheet for regex:
            // \s means every whitespace character
            // [^\s] means every character except whitespace characters
            // +? is a non-greedy equivalent of +
            const string pattern = @"(?<name>[^\s]+)\s(?<value>[^\s]+(\s\(.+?\))*)";
            // Find each match in the string
            MatchCollection matches = Regex.Matches(headerValueWithoutDate, pattern);
            foreach (Match match in matches)
            {
                // Add the name and value part found in the matched result to the dictionary
                string name = match.Groups["name"].Value;
                string value = match.Groups["value"].Value;
                // Check if the name is really a comment.
                // In this case, the first entry in the header value
                // is a comment
                if (name.StartsWith("("))
                {
                    continue;
                }
                // Only add the first name pair
                // All subsequent pairs are ignored, as they are invalid anyway
                if (!dictionary.ContainsKey(name))
                    dictionary.Add(name, value);
            }
            return dictionary;
        }
    }
}
namespace OpenPop.Mime.Header
{
    using System;
    using System.Collections.Generic;
    using System.Collections.Specialized;
    using System.Net.Mail;
    using System.Net.Mime;
    using OpenPop.Mime.Decode;
    /// <summary>
    /// Class that holds all headers for a message<br/>
    /// Headers which are unknown the the parser will be held in the <see cref="UnknownHeaders"/> collection.<br/>
    /// <br/>
    /// This class cannot be instantiated from outside the library.
    /// </summary>
    /// <remarks>
    /// See <a href="http://tools.ietf.org/html/rfc4021">RFC 4021</a> for a large list of headers.<br/>
    /// </remarks>
    public sealed class MessageHeader
    {
        #region Properties
        /// <summary>
        /// All headers which were not recognized and explicitly dealt with.<br/>
        /// This should mostly be custom headers, which are marked as X-[name].<br/>
        /// <br/>
        /// This list will be empty if all headers were recognized and parsed.
        /// </summary>
        /// <remarks>
        /// If you as a user, feels that a header in this collection should
        /// be parsed, feel free to notify the developers.
        /// </remarks>
        public NameValueCollection UnknownHeaders { get; private set; }
        /// <summary>
        /// A human readable description of the body<br/>
        /// <br/>
        /// <see langword="null"/> if no Content-Description header was present in the message.
        /// </summary>
        public string ContentDescription { get; private set; }
        /// <summary>
        /// ID of the content part (like an attached image). Used with MultiPart messages.<br/>
        /// <br/>
        /// <see langword="null"/> if no Content-ID header field was present in the message.
        /// </summary>
        /// <see cref="MessageId">For an ID of the message</see>
        public string ContentId { get; private set; }
        /// <summary>
        /// Message keywords<br/>
        /// <br/>
        /// The list will be empty if no Keywords header was present in the message
        /// </summary>
        public List<string> Keywords { get; private set; }
        /// <summary>
        /// A List of emails to people who wishes to be notified when some event happens.<br/>
        /// These events could be email:
        /// <list type="bullet">
        ///   <item>deletion</item>
        ///   <item>printing</item>
        ///   <item>received</item>
        ///   <item>...</item>
        /// </list>
        /// The list will be empty if no Disposition-Notification-To header was present in the message
        /// </summary>
        /// <remarks>See <a href="http://tools.ietf.org/html/rfc3798">RFC 3798</a> for details</remarks>
        public List<RfcMailAddress> DispositionNotificationTo { get; private set; }
        /// <summary>
        /// This is the Received headers. This tells the path that the email went.<br/>
        /// <br/>
        /// The list will be empty if no Received header was present in the message
        /// </summary>
        public List<Received> Received { get; private set; }
        /// <summary>
        /// Importance of this email.<br/>
        /// <br/>
        /// The importance level is set to normal, if no Importance header field was mentioned or it contained
        /// unknown information. This is the expected behavior according to the RFC.
        /// </summary>
        public MailPriority Importance { get; private set; }
        /// <summary>
        /// This header describes the Content encoding during transfer.<br/>
        /// <br/>
        /// If no Content-Transfer-Encoding header was present in the message, it is set
        /// to the default of <see cref="Header.ContentTransferEncoding.SevenBit">SevenBit</see> in accordance to the RFC.
        /// </summary>
        /// <remarks>See <a href="http://tools.ietf.org/html/rfc2045#section-6">RFC 2045 section 6</a> for details</remarks>
        public ContentTransferEncoding ContentTransferEncoding { get; private set; }
        /// <summary>
        /// Carbon Copy. This specifies who got a copy of the message.<br/>
        /// <br/>
        /// The list will be empty if no Cc header was present in the message
        /// </summary>
        public List<RfcMailAddress> Cc { get; private set; }
        /// <summary>
        /// Blind Carbon Copy. This specifies who got a copy of the message, but others
        /// cannot see who these persons are.<br/>
        /// <br/>
        /// The list will be empty if no Received Bcc was present in the message
        /// </summary>
        public List<RfcMailAddress> Bcc { get; private set; }
        /// <summary>
        /// Specifies who this mail was for<br/>
        /// <br/>
        /// The list will be empty if no To header was present in the message
        /// </summary>
        public List<RfcMailAddress> To { get; private set; }
        /// <summary>
        /// Specifies who sent the email<br/>
        /// <br/>
        /// <see langword="null"/> if no From header field was present in the message
        /// </summary>
        public RfcMailAddress From { get; private set; }
        /// <summary>
        /// Specifies who a reply to the message should be sent to<br/>
        /// <br/>
        /// <see langword="null"/> if no Reply-To header field was present in the message
        /// </summary>
        public RfcMailAddress ReplyTo { get; private set; }
        /// <summary>
        /// The message identifier(s) of the original message(s) to which the
        /// current message is a reply.<br/>
        /// <br/>
        /// The list will be empty if no In-Reply-To header was present in the message
        /// </summary>
        public List<string> InReplyTo { get; private set; }
        /// <summary>
        /// The message identifier(s) of other message(s) to which the current
        /// message is related to.<br/>
        /// <br/>
        /// The list will be empty if no References header was present in the message
        /// </summary>
        public List<string> References { get; private set; }
        /// <summary>
        /// This is the sender of the email address.<br/>
        /// <br/>
        /// <see langword="null"/> if no Sender header field was present in the message
        /// </summary>
        /// <remarks>
        /// The RFC states that this field can be used if a secretary
        /// is sending an email for someone she is working for.
        /// The email here will then be the secretary's email, and
        /// the Reply-To field would hold the address of the person she works for.<br/>
        /// RFC states that if the Sender is the same as the From field,
        /// sender should not be included in the message.
        /// </remarks>
        public RfcMailAddress Sender { get; private set; }
        /// <summary>
        /// The Content-Type header field.<br/>
        /// <br/>
        /// If not set, the ContentType is created by the default "text/plain; charset=us-ascii" which is
        /// defined in <a href="http://tools.ietf.org/html/rfc2045#section-5.2">RFC 2045 section 5.2</a>.<br/>
        /// If set, the default is overridden.
        /// </summary>
        public ContentType ContentType { get; private set; }
        /// <summary>
        /// Used to describe if a <see cref="MessagePart"/> is to be displayed or to be though of as an attachment.<br/>
        /// Also contains information about filename if such was sent.<br/>
        /// <br/>
        /// <see langword="null"/> if no Content-Disposition header field was present in the message
        /// </summary>
        public ContentDisposition ContentDisposition { get; private set; }
        /// <summary>
        /// The Date when the email was sent.<br/>
        /// This is the raw value. <see cref="DateSent"/> for a parsed up <see cref="DateTime"/> value of this field.<br/>
        /// <br/>
        /// <see langword="DateTime.MinValue"/> if no Date header field was present in the message or if the date could not be parsed.
        /// </summary>
        /// <remarks>See <a href="http://tools.ietf.org/html/rfc5322#section-3.6.1">RFC 5322 section 3.6.1</a> for more details</remarks>
        public string Date { get; private set; }
        /// <summary>
        /// The Date when the email was sent.<br/>
        /// This is the parsed equivalent of <see cref="Date"/>.<br/>
        /// Notice that the <see cref="TimeZone"/> of the <see cref="DateTime"/> object is in UTC and has NOT been converted
        /// to local <see cref="TimeZone"/>.
        /// </summary>
        /// <remarks>See <a href="http://tools.ietf.org/html/rfc5322#section-3.6.1">RFC 5322 section 3.6.1</a> for more details</remarks>
        public DateTime DateSent { get; private set; }
        /// <summary>
        /// An ID of the message that is SUPPOSED to be in every message according to the RFC.<br/>
        /// The ID is unique.<br/>
        /// <br/>
        /// <see langword="null"/> if no Message-ID header field was present in the message
        /// </summary>
        public string MessageId { get; private set; }
        /// <summary>
        /// The Mime Version.<br/>
        /// This field will almost always show 1.0<br/>
        /// <br/>
        /// <see langword="null"/> if no Mime-Version header field was present in the message
        /// </summary>
        public string MimeVersion { get; private set; }
        /// <summary>
        /// A single <see cref="RfcMailAddress"/> with no username inside.<br/>
        /// This is a trace header field, that should be in all messages.<br/>
        /// Replies should be sent to this address.<br/>
        /// <br/>
        /// <see langword="null"/> if no Return-Path header field was present in the message
        /// </summary>
        public RfcMailAddress ReturnPath { get; private set; }
        /// <summary>
        /// The subject line of the message in decoded, one line state.<br/>
        /// This should be in all messages.<br/>
        /// <br/>
        /// <see langword="null"/> if no Subject header field was present in the message
        /// </summary>
        public string Subject { get; private set; }
        #endregion
        /// <summary>
        /// Parses a <see cref="NameValueCollection"/> to a MessageHeader
        /// </summary>
        /// <param name="headers">The collection that should be traversed and parsed</param>
        /// <returns>A valid MessageHeader object</returns>
        /// <exception cref="ArgumentNullException">If <paramref name="headers"/> is <see langword="null"/></exception>
        internal MessageHeader(NameValueCollection headers)
        {
            if (headers == null)
                throw new ArgumentNullException("headers");
            // Create empty lists as defaults. We do not like null values
            // List with an initial capacity set to zero will be replaced
            // when a corrosponding header is found
            To = new List<RfcMailAddress>(0);
            Cc = new List<RfcMailAddress>(0);
            Bcc = new List<RfcMailAddress>(0);
            Received = new List<Received>();
            Keywords = new List<string>();
            InReplyTo = new List<string>(0);
            References = new List<string>(0);
            DispositionNotificationTo = new List<RfcMailAddress>();
            UnknownHeaders = new NameValueCollection();
            // Default importancetype is Normal (assumed if not set)
            Importance = MailPriority.Normal;
            // 7BIT is the default ContentTransferEncoding (assumed if not set)
            ContentTransferEncoding = ContentTransferEncoding.SevenBit;
            // text/plain; charset=us-ascii is the default ContentType
            ContentType = new ContentType("text/plain; charset=us-ascii");
            // Now parse the actual headers
            ParseHeaders(headers);
        }
        /// <summary>
        /// Parses a <see cref="NameValueCollection"/> to a <see cref="MessageHeader"/>
        /// </summary>
        /// <param name="headers">The collection that should be traversed and parsed</param>
        /// <returns>A valid <see cref="MessageHeader"/> object</returns>
        /// <exception cref="ArgumentNullException">If <paramref name="headers"/> is <see langword="null"/></exception>
        private void ParseHeaders(NameValueCollection headers)
        {
            if (headers == null)
                throw new ArgumentNullException("headers");
            // Now begin to parse the header values
            foreach (string headerName in headers.Keys)
            {
                string[] headerValues = headers.GetValues(headerName);
                if (headerValues != null)
                {
                    foreach (string headerValue in headerValues)
                    {
                        ParseHeader(headerName, headerValue);
                    }
                }
            }
        }
        #region Header fields parsing
        /// <summary>
        /// Parses a single header and sets member variables according to it.
        /// </summary>
        /// <param name="headerName">The name of the header</param>
        /// <param name="headerValue">The value of the header in unfolded state (only one line)</param>
        /// <exception cref="ArgumentNullException">If <paramref name="headerName"/> or <paramref name="headerValue"/> is <see langword="null"/></exception>
        private void ParseHeader(string headerName, string headerValue)
        {
            if (headerName == null)
                throw new ArgumentNullException("headerName");
            if (headerValue == null)
                throw new ArgumentNullException("headerValue");
            switch (headerName.ToUpperInvariant())
            {
                // See http://tools.ietf.org/html/rfc5322#section-3.6.3
                case "TO":
                    To = RfcMailAddress.ParseMailAddresses(headerValue);
                    break;
                // See http://tools.ietf.org/html/rfc5322#section-3.6.3
                case "CC":
                    Cc = RfcMailAddress.ParseMailAddresses(headerValue);
                    break;
                // See http://tools.ietf.org/html/rfc5322#section-3.6.3
                case "BCC":
                    Bcc = RfcMailAddress.ParseMailAddresses(headerValue);
                    break;
                // See http://tools.ietf.org/html/rfc5322#section-3.6.2
                case "FROM":
                    // There is only one MailAddress in the from field
                    From = RfcMailAddress.ParseMailAddress(headerValue);
                    break;
                // http://tools.ietf.org/html/rfc5322#section-3.6.2
                // The implementation here might be wrong
                case "REPLY-TO":
                    // This field may actually be a list of addresses, but no
                    // such case has been encountered
                    ReplyTo = RfcMailAddress.ParseMailAddress(headerValue);
                    break;
                // http://tools.ietf.org/html/rfc5322#section-3.6.2
                case "SENDER":
                    Sender = RfcMailAddress.ParseMailAddress(headerValue);
                    break;
                // See http://tools.ietf.org/html/rfc5322#section-3.6.5
                // RFC 5322:
                // The "Keywords:" field contains a comma-separated list of one or more
                // words or quoted-strings.
                // The field are intended to have only human-readable content
                // with information about the message
                case "KEYWORDS":
                    string[] keywordsTemp = headerValue.Split(',');
                    foreach (string keyword in keywordsTemp)
                    {
                        // Remove the quotes if there is any
                        Keywords.Add(Utility.RemoveQuotesIfAny(keyword.Trim()));
                    }
                    break;
                // See http://tools.ietf.org/html/rfc5322#section-3.6.7
                case "RECEIVED":
                    // Simply add the value to the list
                    Received.Add(new Received(headerValue.Trim()));
                    break;
                case "IMPORTANCE":
                    Importance = HeaderFieldParser.ParseImportance(headerValue.Trim());
                    break;
                // See http://tools.ietf.org/html/rfc3798#section-2.1
                case "DISPOSITION-NOTIFICATION-TO":
                    DispositionNotificationTo = RfcMailAddress.ParseMailAddresses(headerValue);
                    break;
                case "MIME-VERSION":
                    MimeVersion = headerValue.Trim();
                    break;
                // See http://tools.ietf.org/html/rfc5322#section-3.6.5
                case "SUBJECT":
                    Subject = EncodedWord.Decode(headerValue);
                    break;
                // See http://tools.ietf.org/html/rfc5322#section-3.6.7
                case "RETURN-PATH":
                    // Return-paths does not include a username, but we 
                    // may still use the address parser 
                    ReturnPath = RfcMailAddress.ParseMailAddress(headerValue);
                    break;
                // See http://tools.ietf.org/html/rfc5322#section-3.6.4
                // Example Message-ID
                // <33cdd74d6b89ab2250ecd75b40a41405@nfs.eksperten.dk>
                case "MESSAGE-ID":
                    MessageId = HeaderFieldParser.ParseId(headerValue);
                    break;
                // See http://tools.ietf.org/html/rfc5322#section-3.6.4
                case "IN-REPLY-TO":
                    InReplyTo = HeaderFieldParser.ParseMultipleIDs(headerValue);
                    break;
                // See http://tools.ietf.org/html/rfc5322#section-3.6.4
                case "REFERENCES":
                    References = HeaderFieldParser.ParseMultipleIDs(headerValue);
                    break;
                // See http://tools.ietf.org/html/rfc5322#section-3.6.1))
                case "DATE":
                    Date = headerValue.Trim();
                    DateSent = Rfc2822DateTime.StringToDate(headerValue);
                    break;
                // See http://tools.ietf.org/html/rfc2045#section-6
                // See ContentTransferEncoding class for more details
                case "CONTENT-TRANSFER-ENCODING":
                    ContentTransferEncoding = HeaderFieldParser.ParseContentTransferEncoding(headerValue.Trim());
                    break;
                // See http://tools.ietf.org/html/rfc2045#section-8
                case "CONTENT-DESCRIPTION":
                    // Human description of for example a file. Can be encoded
                    ContentDescription = EncodedWord.Decode(headerValue.Trim());
                    break;
                // See http://tools.ietf.org/html/rfc2045#section-5.1
                // Example: Content-type: text/plain; charset="us-ascii"
                case "CONTENT-TYPE":
                    ContentType = HeaderFieldParser.ParseContentType(headerValue);
                    break;
                // See http://tools.ietf.org/html/rfc2183
                case "CONTENT-DISPOSITION":
                    ContentDisposition = HeaderFieldParser.ParseContentDisposition(headerValue);
                    break;
                // See http://tools.ietf.org/html/rfc2045#section-7
                // Example: <foo4*foo1@bar.net>
                case "CONTENT-ID":
                    ContentId = HeaderFieldParser.ParseId(headerValue);
                    break;
                default:
                    // This is an unknown header
                    // Custom headers are allowed. That means headers
                    // that are not mentionen in the RFC.
                    // Such headers start with the letter "X"
                    // We do not have any special parsing of such
                    // Add it to unknown headers
                    UnknownHeaders.Add(headerName, headerValue);
                    break;
            }
        }
        #endregion
    }
}
namespace OpenPop.Mime.Header
{
    using System;
    using System.Collections.Generic;
    using System.Globalization;
    using System.Net.Mail;
    using System.Net.Mime;
    using System.Text;
    using OpenPop.Mime.Decode;
    using OpenPop.Common.Logging;
    /// <summary>
    /// Class that can parse different fields in the header sections of a MIME message.
    /// </summary>
    internal static class HeaderFieldParser
    {
        /// <summary>
        /// Parses the Content-Transfer-Encoding header.
        /// </summary>
        /// <param name="headerValue">The value for the header to be parsed</param>
        /// <returns>A <see cref="ContentTransferEncoding"/></returns>
        /// <exception cref="ArgumentNullException">If <paramref name="headerValue"/> is <see langword="null"/></exception>
        /// <exception cref="ArgumentException">If the <paramref name="headerValue"/> could not be parsed to a <see cref="ContentTransferEncoding"/></exception>
        public static ContentTransferEncoding ParseContentTransferEncoding(string headerValue)
        {
            if (headerValue == null)
                throw new ArgumentNullException("headerValue");
            switch (headerValue.Trim().ToUpperInvariant())
            {
                case "7BIT":
                    return ContentTransferEncoding.SevenBit;
                case "8BIT":
                    return ContentTransferEncoding.EightBit;
                case "QUOTED-PRINTABLE":
                    return ContentTransferEncoding.QuotedPrintable;
                case "BASE64":
                    return ContentTransferEncoding.Base64;
                case "BINARY":
                    return ContentTransferEncoding.Binary;
                // If a wrong argument is passed to this parser method, then we assume
                // default encoding, which is SevenBit.
                // This is to ensure that we do not throw exceptions, even if the email not MIME valid.
                default:
                    DefaultLogger.Log.LogDebug("Wrong ContentTransferEncoding was used. It was: " + headerValue);
                    return ContentTransferEncoding.SevenBit;
            }
        }
        /// <summary>
        /// Parses an ImportanceType from a given Importance header value.
        /// </summary>
        /// <param name="headerValue">The value to be parsed</param>
        /// <returns>A <see cref="MailPriority"/>. If the <paramref name="headerValue"/> is not recognized, Normal is returned.</returns>
        /// <exception cref="ArgumentNullException">If <paramref name="headerValue"/> is <see langword="null"/></exception>
        public static MailPriority ParseImportance(string headerValue)
        {
            if (headerValue == null)
                throw new ArgumentNullException("headerValue");
            switch (headerValue.ToUpperInvariant())
            {
                case "5":
                case "HIGH":
                    return MailPriority.High;
                case "3":
                case "NORMAL":
                    return MailPriority.Normal;
                case "1":
                case "LOW":
                    return MailPriority.Low;
                default:
                    DefaultLogger.Log.LogDebug("HeaderFieldParser: Unknown importance value: \"" + headerValue + "\". Using default of normal importance.");
                    return MailPriority.Normal;
            }
        }
        /// <summary>
        /// Parses a the value for the header Content-Type to 
        /// a <see cref="ContentType"/> object.
        /// </summary>
        /// <param name="headerValue">The value to be parsed</param>
        /// <returns>A <see cref="ContentType"/> object</returns>
        /// <exception cref="ArgumentNullException">If <paramref name="headerValue"/> is <see langword="null"/></exception>
        public static ContentType ParseContentType(string headerValue)
        {
            if (headerValue == null)
                throw new ArgumentNullException("headerValue");
            // We create an empty Content-Type which we will fill in when we see the values
            ContentType contentType = new ContentType();
            // Now decode the parameters
            List<KeyValuePair<string, string>> parameters = Rfc2231Decoder.Decode(headerValue);
            foreach (KeyValuePair<string, string> keyValuePair in parameters)
            {
                string key = keyValuePair.Key.ToUpperInvariant().Trim();
                string value = Utility.RemoveQuotesIfAny(keyValuePair.Value.Trim());
                switch (key)
                {
                    case "":
                        // This is the MediaType - it has no key since it is the first one mentioned in the
                        // headerValue and has no = in it.
                        // Check for illegal content-type
                        if (value.ToUpperInvariant().Equals("TEXT"))
                            value = "text/plain";
                        contentType.MediaType = value;
                        break;
                    case "BOUNDARY":
                        contentType.Boundary = value;
                        break;
                    case "CHARSET":
                        contentType.CharSet = value;
                        break;
                    case "NAME":
                        contentType.Name = EncodedWord.Decode(value);
                        break;
                    default:
                        // This is to shut up the code help that is saying that contentType.Parameters
                        // can be null - which it cant!
                        if (contentType.Parameters == null)
                            throw new Exception("The ContentType parameters property is null. This will never be thrown.");
                        // We add the unknown value to our parameters list
                        // "Known" unknown values are:
                        // - title
                        // - report-type
                        contentType.Parameters.Add(key, value);
                        break;
                }
            }
            return contentType;
        }
        /// <summary>
        /// Parses a the value for the header Content-Disposition to a <see cref="ContentDisposition"/> object.
        /// </summary>
        /// <param name="headerValue">The value to be parsed</param>
        /// <returns>A <see cref="ContentDisposition"/> object</returns>
        /// <exception cref="ArgumentNullException">If <paramref name="headerValue"/> is <see langword="null"/></exception>
        public static ContentDisposition ParseContentDisposition(string headerValue)
        {
            if (headerValue == null)
                throw new ArgumentNullException("headerValue");
            // See http://www.ietf.org/rfc/rfc2183.txt for RFC definition
            // Create empty ContentDisposition - we will fill in details as we read them
            ContentDisposition contentDisposition = new ContentDisposition();
            // Now decode the parameters
            List<KeyValuePair<string, string>> parameters = Rfc2231Decoder.Decode(headerValue);
            foreach (KeyValuePair<string, string> keyValuePair in parameters)
            {
                string key = keyValuePair.Key.ToUpperInvariant().Trim();
                string value = keyValuePair.Value;
                switch (key)
                {
                    case "":
                        // This is the DispisitionType - it has no key since it is the first one
                        // and has no = in it.
                        contentDisposition.DispositionType = value;
                        break;
                    // The correct name of the parameter is filename, but some emails also contains the parameter
                    // name, which also holds the name of the file. Therefore we use both names for the same field.
                    case "NAME":
                    case "FILENAME":
                        // The filename might be in qoutes, and it might be encoded-word encoded
                        contentDisposition.FileName = EncodedWord.Decode(Utility.RemoveQuotesIfAny(value));
                        break;
                    case "CREATION-DATE":
                        // Notice that we need to create a new DateTime because of a failure in .NET 2.0.
                        // The failure is: you cannot give contentDisposition a DateTime with a Kind of UTC
                        // It will set the CreationDate correctly, but when trying to read it out it will throw an exception.
                        // It is the same with ModificationDate and ReadDate.
                        // This is fixed in 4.0 - maybe in 3.0 too.
                        // Therefore we create a new DateTime which have a DateTimeKind set to unspecified
                        DateTime creationDate = new DateTime(Rfc2822DateTime.StringToDate(Utility.RemoveQuotesIfAny(value)).Ticks);
                        contentDisposition.CreationDate = creationDate;
                        break;
                    case "MODIFICATION-DATE":
                        DateTime midificationDate = new DateTime(Rfc2822DateTime.StringToDate(Utility.RemoveQuotesIfAny(value)).Ticks);
                        contentDisposition.ModificationDate = midificationDate;
                        break;
                    case "READ-DATE":
                        DateTime readDate = new DateTime(Rfc2822DateTime.StringToDate(Utility.RemoveQuotesIfAny(value)).Ticks);
                        contentDisposition.ReadDate = readDate;
                        break;
                    case "SIZE":
                        contentDisposition.Size = int.Parse(Utility.RemoveQuotesIfAny(value), CultureInfo.InvariantCulture);
                        break;
                    default:
                        if (key.StartsWith("X-"))
                        {
                            contentDisposition.Parameters.Add(key, Utility.RemoveQuotesIfAny(value));
                            break;
                        }
                        throw new ArgumentException("Unknown parameter in Content-Disposition. Ask developer to fix! Parameter: " + key);
                }
            }
            return contentDisposition;
        }
        /// <summary>
        /// Parses an ID like Message-Id and Content-Id.<br/>
        /// Example:<br/>
        /// <c>&lt;test@test.com&gt;</c><br/>
        /// into<br/>
        /// <c>test@test.com</c>
        /// </summary>
        /// <param name="headerValue">The id to parse</param>
        /// <returns>A parsed ID</returns>
        public static string ParseId(string headerValue)
        {
            // Remove whitespace in front and behind since
            // whitespace is allowed there
            // Remove the last > and the first <
            return headerValue.Trim().TrimEnd('>').TrimStart('<');
        }
        /// <summary>
        /// Parses multiple IDs from a single string like In-Reply-To.
        /// </summary>
        /// <param name="headerValue">The value to parse</param>
        /// <returns>A list of IDs</returns>
        public static List<string> ParseMultipleIDs(string headerValue)
        {
            List<string> returner = new List<string>();
            // Split the string by >
            // We cannot use ' ' (space) here since this is a possible value:
            // <test@test.com><test2@test.com>
            string[] ids = headerValue.Trim().Split(new[] { '>' }, StringSplitOptions.RemoveEmptyEntries);
            foreach (string id in ids)
            {
                returner.Add(ParseId(id));
            }
            return returner;
        }
    }
}
namespace OpenPop.Mime.Header
{
    using System;
    using System.Collections.Generic;
    using System.Collections.Specialized;
    using System.IO;
    using System.Text;
    using OpenPop.Common;
    ///<summary>
    /// Utility class that divides a message into a body and a header.<br/>
    /// The header is then parsed to a strongly typed <see cref="MessageHeader"/> object.
    ///</summary>
    internal static class HeaderExtractor
    {
        /// <summary>
        /// Find the end of the header section in a byte array.<br/>
        /// The headers have ended when a blank line is found
        /// </summary>
        /// <param name="messageContent">The full message stored as a byte array</param>
        /// <returns>The position of the line just after the header end blank line</returns>
        private static int FindHeaderEndPosition(byte[] messageContent)
        {
            // Convert the byte array into a stream
            using (Stream stream = new MemoryStream(messageContent))
            {
                while (true)
                {
                    // Read a line from the stream. We know headers are in US-ASCII
                    // therefore it is not problem to read them as such
                    string line = StreamUtility.ReadLineAsAscii(stream);
                    // The end of headers is signaled when a blank line is found
                    // or if the line is null - in which case the email is actually an email with
                    // only headers but no body
                    if (string.IsNullOrEmpty(line))
                        return (int)stream.Position;
                }
            }
        }
        /// <summary>
        /// Extract the header part and body part of a message.<br/>
        /// The headers are then parsed to a strongly typed <see cref="MessageHeader"/> object.
        /// </summary>
        /// <param name="fullRawMessage">The full message in bytes where header and body needs to be extracted from</param>
        /// <param name="headers">The extracted header parts of the message</param>
        /// <param name="body">The body part of the message</param>
        /// <exception cref="ArgumentNullException">If <paramref name="fullRawMessage"/> is <see langword="null"/></exception>
        public static void ExtractHeadersAndBody(byte[] fullRawMessage, out MessageHeader headers, out byte[] body)
        {
            if (fullRawMessage == null)
                throw new ArgumentNullException("fullRawMessage");
            // Find the end location of the headers
            int endOfHeaderLocation = FindHeaderEndPosition(fullRawMessage);
            // The headers are always in ASCII - therefore we can convert the header part into a string
            // using US-ASCII encoding
            string headersString = Encoding.ASCII.GetString(fullRawMessage, 0, endOfHeaderLocation);
            // Now parse the headers to a NameValueCollection
            NameValueCollection headersUnparsedCollection = ExtractHeaders(headersString);
            // Use the NameValueCollection to parse it into a strongly-typed MessageHeader header
            headers = new MessageHeader(headersUnparsedCollection);
            // Since we know where the headers end, we also know where the body is
            // Copy the body part into the body parameter
            body = new byte[fullRawMessage.Length - endOfHeaderLocation];
            Array.Copy(fullRawMessage, endOfHeaderLocation, body, 0, body.Length);
        }
        /// <summary>
        /// Method that takes a full message and extract the headers from it.
        /// </summary>
        /// <param name="messageContent">The message to extract headers from. Does not need the body part. Needs the empty headers end line.</param>
        /// <returns>A collection of Name and Value pairs of headers</returns>
        /// <exception cref="ArgumentNullException">If <paramref name="messageContent"/> is <see langword="null"/></exception>
        private static NameValueCollection ExtractHeaders(string messageContent)
        {
            if (messageContent == null)
                throw new ArgumentNullException("messageContent");
            NameValueCollection headers = new NameValueCollection();
            using (StringReader messageReader = new StringReader(messageContent))
            {
                // Read until all headers have ended.
                // The headers ends when an empty line is encountered
                // An empty message might actually not have an empty line, in which
                // case the headers end with null value.
                string line;
                while (!string.IsNullOrEmpty(line = messageReader.ReadLine()))
                {
                    // Split into name and value
                    KeyValuePair<string, string> header = SeparateHeaderNameAndValue(line);
                    // First index is header name
                    string headerName = header.Key;
                    // Second index is the header value.
                    // Use a StringBuilder since the header value may be continued on the next line
                    StringBuilder headerValue = new StringBuilder(header.Value);
                    // Keep reading until we would hit next header
                    // This if for handling multi line headers
                    while (IsMoreLinesInHeaderValue(messageReader))
                    {
                        // Unfolding is accomplished by simply removing any CRLF
                        // that is immediately followed by WSP
                        // This was done using ReadLine (it discards CRLF)
                        // See http://tools.ietf.org/html/rfc822#section-3.1.1 for more information
                        string moreHeaderValue = messageReader.ReadLine();
                        // If this exception is ever raised, there is an serious algorithm failure
                        // IsMoreLinesInHeaderValue does not return true if the next line does not exist
                        // This check is only included to stop the nagging "possibly null" code analysis hint
                        if (moreHeaderValue == null)
                            throw new ArgumentException("This will never happen");
                        // Simply append the line just read to the header value
                        headerValue.Append(moreHeaderValue);
                    }
                    // Now we have the name and full value. Add it
                    headers.Add(headerName, headerValue.ToString());
                }
            }
            return headers;
        }
        /// <summary>
        /// Check if the next line is part of the current header value we are parsing by
        /// peeking on the next character of the <see cref="TextReader"/>.<br/>
        /// This should only be called while parsing headers.
        /// </summary>
        /// <param name="reader">The reader from which the header is read from</param>
        /// <returns><see langword="true"/> if multi-line header. <see langword="false"/> otherwise</returns>
        private static bool IsMoreLinesInHeaderValue(TextReader reader)
        {
            int peek = reader.Peek();
            if (peek == -1)
                return false;
            char peekChar = (char)peek;
            // A multi line header must have a whitespace character
            // on the next line if it is to be continued
            return peekChar == ' ' || peekChar == '\t';
        }
        /// <summary>
        /// Separate a full header line into a header name and a header value.
        /// </summary>
        /// <param name="rawHeader">The raw header line to be separated</param>
        /// <exception cref="ArgumentNullException">If <paramref name="rawHeader"/> is <see langword="null"/></exception>
        internal static KeyValuePair<string, string> SeparateHeaderNameAndValue(string rawHeader)
        {
            if (rawHeader == null)
                throw new ArgumentNullException("rawHeader");
            string key = string.Empty;
            string value = string.Empty;
            int indexOfColon = rawHeader.IndexOf(':');
            // Check if it is allowed to make substring calls
            if (indexOfColon >= 0 && rawHeader.Length >= indexOfColon + 1)
            {
                key = rawHeader.Substring(0, indexOfColon).Trim();
                value = rawHeader.Substring(indexOfColon + 1).Trim();
            }
            return new KeyValuePair<string, string>(key, value);
        }
    }
}
namespace OpenPop.Mime.Header
{
    using System;
    /// <summary>
    /// <see cref="Enum"/> that describes the ContentTransferEncoding header field
    /// </summary>
    /// <remarks>See <a href="http://tools.ietf.org/html/rfc2045#section-6">RFC 2045 section 6</a> for more details</remarks>
    public enum ContentTransferEncoding
    {
        /// <summary>
        /// 7 bit Encoding
        /// </summary>
        SevenBit,
        /// <summary>
        /// 8 bit Encoding
        /// </summary>
        EightBit,
        /// <summary>
        /// Quoted Printable Encoding
        /// </summary>
        QuotedPrintable,
        /// <summary>
        /// Base64 Encoding
        /// </summary>
        Base64,
        /// <summary>
        /// Binary Encoding
        /// </summary>
        Binary
    }
}
namespace OpenPop.Mime.Decode
{
    using System;
    using System.Collections.Generic;
    /// <summary>
    /// Contains common operations needed while decoding.
    /// </summary>
    internal static class Utility
    {
        /// <summary>
        /// Remove quotes, if found, around the string.
        /// </summary>
        /// <param name="text">Text with quotes or without quotes</param>
        /// <returns>Text without quotes</returns>
        /// <exception cref="ArgumentNullException">If <paramref name="text"/> is <see langword="null"/></exception>
        public static string RemoveQuotesIfAny(string text)
        {
            if (text == null)
                throw new ArgumentNullException("text");
            // Check if there are qoutes at both ends
            if (text[0] == '"' && text[text.Length - 1] == '"')
            {
                // Remove quotes at both ends
                return text.Substring(1, text.Length - 2);
            }
            // If no quotes were found, the text is just returned
            return text;
        }
        /// <summary>
        /// Split a string into a list of strings using a specified character.<br/>
        /// Everything inside quotes are ignored.
        /// </summary>
        /// <param name="input">A string to split</param>
        /// <param name="toSplitAt">The character to use to split with</param>
        /// <returns>A List of strings that was delimited by the <paramref name="toSplitAt"/> character</returns>
        public static List<string> SplitStringWithCharNotInsideQuotes(string input, char toSplitAt)
        {
            List<string> elements = new List<string>();
            int lastSplitLocation = 0;
            bool insideQuote = false;
            char[] characters = input.ToCharArray();
            for (int i = 0; i < characters.Length; i++)
            {
                char character = characters[i];
                if (character == '\"')
                    insideQuote = !insideQuote;
                // Only split if we are not inside quotes
                if (character == toSplitAt && !insideQuote)
                {
                    // We need to split
                    int length = i - lastSplitLocation;
                    elements.Add(input.Substring(lastSplitLocation, length));
                    // Update last split location
                    // + 1 so that we do not include the character used to split with next time
                    lastSplitLocation = i + 1;
                }
            }
            // Add the last part
            elements.Add(input.Substring(lastSplitLocation, input.Length - lastSplitLocation));
            return elements;
        }
    }
}
namespace OpenPop.Mime.Decode
{
    using System;
    using System.Globalization;
    using System.Text.RegularExpressions;
    using OpenPop.Common.Logging;
    /// <summary>
    /// Class used to decode RFC 2822 Date header fields.
    /// </summary>
    internal static class Rfc2822DateTime
    {
        /// <summary>
        /// Converts a string in RFC 2822 format into a <see cref="DateTime"/> object
        /// </summary>
        /// <param name="inputDate">The date to convert</param>
        /// <returns>
        /// A valid <see cref="DateTime"/> object, which represents the same time as the string that was converted. 
        /// If <paramref name="inputDate"/> is not a valid date representation, then <see cref="DateTime.MinValue"/> is returned.
        /// </returns>
        /// <exception cref="ArgumentNullException"><exception cref="ArgumentNullException">If <paramref name="inputDate"/> is <see langword="null"/></exception></exception>
        /// <exception cref="ArgumentException">If the <paramref name="inputDate"/> could not be parsed into a <see cref="DateTime"/> object</exception>
        public static DateTime StringToDate(string inputDate)
        {
            if (inputDate == null)
                throw new ArgumentNullException("inputDate");
            // Old date specification allows comments and a lot of whitespace
            inputDate = StripCommentsAndExcessWhitespace(inputDate);
            try
            {
                // Extract the DateTime
                DateTime dateTime = ExtractDateTime(inputDate);
                // If a day-name is specified in the inputDate string, check if it fits with the date
                ValidateDayNameIfAny(dateTime, inputDate);
                // Convert the date into UTC
                dateTime = new DateTime(dateTime.Ticks, DateTimeKind.Utc);
                // Adjust according to the time zone
                dateTime = AdjustTimezone(dateTime, inputDate);
                // Return the parsed date
                return dateTime;
            }
            catch (FormatException e)    // Convert.ToDateTime() Failure
            {
                throw new ArgumentException("Could not parse date: " + e.Message + ". Input was: \"" + inputDate + "\"", e);
            }
            catch (ArgumentException e)
            {
                throw new ArgumentException("Could not parse date: " + e.Message + ". Input was: \"" + inputDate + "\"", e);
            }
        }
        /// <summary>
        /// Adjust the <paramref name="dateTime"/> object given according to the timezone specified in the <paramref name="dateInput"/>.
        /// </summary>
        /// <param name="dateTime">The date to alter</param>
        /// <param name="dateInput">The input date, in which the timezone can be found</param>
        /// <returns>An date altered according to the timezone</returns>
        /// <exception cref="ArgumentException">If no timezone was found in <paramref name="dateInput"/></exception>
        private static DateTime AdjustTimezone(DateTime dateTime, string dateInput)
        {
            // We know that the timezones are always in the last part of the date input
            string[] parts = dateInput.Split(' ');
            string lastPart = parts[parts.Length - 1];
            // Convert timezones in older formats to [+-]dddd format.
            lastPart = Regex.Replace(lastPart, @"UT|GMT|EST|EDT|CST|CDT|MST|MDT|PST|PDT|[A-I]|[K-Y]|Z", MatchEvaluator);
            // Find the timezone specification
            // Example: Fri, 21 Nov 1997 09:55:06 -0600
            // finds -0600
            Match match = Regex.Match(lastPart, @"[\+-](?<hours>\d\d)(?<minutes>\d\d)");
            if (match.Success)
            {
                // We have found that the timezone is in +dddd or -dddd format
                // Add the number of hours and minutes to our found date
                int hours = int.Parse(match.Groups["hours"].Value);
                int minutes = int.Parse(match.Groups["minutes"].Value);
                int factor = match.Value[0] == '+' ? -1 : 1;
                dateTime = dateTime.AddHours(factor * hours);
                dateTime = dateTime.AddMinutes(factor * minutes);
                return dateTime;
            }
            DefaultLogger.Log.LogDebug("No timezone found in date: " + dateInput + ". Using -0000 as default.");
            // A timezone of -0000 is the same as doing nothing
            return dateTime;
        }
        /// <summary>
        /// Convert timezones in older formats to [+-]dddd format.
        /// </summary>
        /// <param name="match">The match that was found</param>
        /// <returns>The string to replace the matched string with</returns>
        private static string MatchEvaluator(Match match)
        {
            if (!match.Success)
            {
                throw new ArgumentException("Match success are always true");
            }
            switch (match.Value)
            {
                // "A" through "I"
                // are equivalent to "+0100" through "+0900" respectively
                case "A": return "+0100";
                case "B": return "+0200";
                case "C": return "+0300";
                case "D": return "+0400";
                case "E": return "+0500";
                case "F": return "+0600";
                case "G": return "+0700";
                case "H": return "+0800";
                case "I": return "+0900";
                // "K", "L", and "M"
                // are equivalent to "+1000", "+1100", and "+1200" respectively
                case "K": return "+1000";
                case "L": return "+1100";
                case "M": return "+1200";
                // "N" through "Y"
                // are equivalent to "-0100" through "-1200" respectively
                case "N": return "-0100";
                case "O": return "-0200";
                case "P": return "-0300";
                case "Q": return "-0400";
                case "R": return "-0500";
                case "S": return "-0600";
                case "T": return "-0700";
                case "U": return "-0800";
                case "V": return "-0900";
                case "W": return "-1000";
                case "X": return "-1100";
                case "Y": return "-1200";
                // "Z", "UT" and "GMT"
                // is equivalent to "+0000"
                case "Z":
                case "UT":
                case "GMT":
                    return "+0000";
                // US time zones
                case "EDT": return "-0400"; // EDT is semantically equivalent to -0400
                case "EST": return "-0500"; // EST is semantically equivalent to -0500
                case "CDT": return "-0500"; // CDT is semantically equivalent to -0500
                case "CST": return "-0600"; // CST is semantically equivalent to -0600
                case "MDT": return "-0600"; // MDT is semantically equivalent to -0600
                case "MST": return "-0700"; // MST is semantically equivalent to -0700
                case "PDT": return "-0700"; // PDT is semantically equivalent to -0700
                case "PST": return "-0800"; // PST is semantically equivalent to -0800
                default:
                    throw new ArgumentException("Unexpected input");
            }
        }
        /// <summary>
        /// Extracts the date and time parts from the <paramref name="dateInput"/>
        /// </summary>
        /// <param name="dateInput">The date input string, from which to extract the date and time parts</param>
        /// <returns>The extracted date part or <see langword="DateTime.MinValue"/> if <paramref name="dateInput"/> is not recognized as a valid date.</returns>
        private static DateTime ExtractDateTime(string dateInput)
        {
            // Matches the date and time part of a string
            // Example: Fri, 21 Nov 1997 09:55:06 -0600
            // Finds: 21 Nov 1997 09:55:06
            // Seconds does not need to be specified
            // Even though it is illigal, sometimes hours, minutes or seconds are only specified with one digit
            Match match = Regex.Match(dateInput, @"\d\d? .+ (\d\d\d\d|\d\d) \d?\d:\d?\d(:\d?\d)?");
            if (match.Success)
            {
                return Convert.ToDateTime(match.Value, CultureInfo.InvariantCulture);
            }
            DefaultLogger.Log.LogError("The given date does not appear to be in a valid format: " + dateInput);
            return DateTime.MinValue;
        }
        /// <summary>
        /// Validates that the given <paramref name="dateTime"/> agrees with a day-name specified
        /// in <paramref name="dateInput"/>.
        /// </summary>
        /// <param name="dateTime">The time to check</param>
        /// <param name="dateInput">The date input to extract the day-name from</param>
        /// <exception cref="ArgumentException">If <paramref name="dateTime"/> and <paramref name="dateInput"/> does not agree on the day</exception>
        private static void ValidateDayNameIfAny(DateTime dateTime, string dateInput)
        {
            // Check if there is a day name in front of the date
            // Example: Fri, 21 Nov 1997 09:55:06 -0600
            if (dateInput.Length >= 4 && dateInput[3] == ',')
            {
                string dayName = dateInput.Substring(0, 3);
                // If a dayName was specified. Check that the dateTime and the dayName
                // agrees on which day it is
                // This is just a failure-check and could be left out
                if ((dateTime.DayOfWeek == DayOfWeek.Monday && !dayName.Equals("Mon")) ||
                    (dateTime.DayOfWeek == DayOfWeek.Tuesday && !dayName.Equals("Tue")) ||
                    (dateTime.DayOfWeek == DayOfWeek.Wednesday && !dayName.Equals("Wed")) ||
                    (dateTime.DayOfWeek == DayOfWeek.Thursday && !dayName.Equals("Thu")) ||
                    (dateTime.DayOfWeek == DayOfWeek.Friday && !dayName.Equals("Fri")) ||
                    (dateTime.DayOfWeek == DayOfWeek.Saturday && !dayName.Equals("Sat")) ||
                    (dateTime.DayOfWeek == DayOfWeek.Sunday && !dayName.Equals("Sun")))
                {
                    DefaultLogger.Log.LogDebug("Day-name does not correspond to the weekday of the date: " + dateInput);
                }
            }
            // If no day name was found no checks can be made
        }
        /// <summary>
        /// Strips and removes all comments and excessive whitespace from the string
        /// </summary>
        /// <param name="input">The input to strip from</param>
        /// <returns>The stripped string</returns>
        private static string StripCommentsAndExcessWhitespace(string input)
        {
            // Strip out comments
            // Also strips out nested comments
            input = Regex.Replace(input, @"(\((?>\((?<C>)|\)(?<-C>)|.?)*(?(C)(?!))\))", "");
            // Reduce any whitespace character to one space only
            input = Regex.Replace(input, @"\s+", " ");
            // Remove all initial whitespace
            input = Regex.Replace(input, @"^\s+", "");
            // Remove all ending whitespace
            input = Regex.Replace(input, @"\s+$", "");
            // Remove spaces at colons
            // Example: 22: 33 : 44 => 22:33:44
            input = Regex.Replace(input, @" ?: ?", ":");
            return input;
        }
    }
}
namespace OpenPop.Mime.Decode
{
    using System;
    using System.Collections.Generic;
    using System.Text;
    using System.Text.RegularExpressions;
    using OpenPop.Common.Logging;
    /// <summary>
    /// This class is responsible for decoding parameters that has been encoded with:<br/>
    /// <list type="bullet">
    /// <item>
    ///    <b>Continuation</b><br/>
    ///    This is where a single parameter has such a long value that it could
    ///    be wrapped while in transit. Instead multiple parameters is used on each line.<br/>
    ///    <br/>
    ///    <b>Example</b><br/>
    ///    From: <c>Content-Type: text/html; boundary="someVeryLongStringHereWhichCouldBeWrappedInTransit"</c><br/>
    ///    To: <c>Content-Type: text/html; boundary*0="someVeryLongStringHere" boundary*1="WhichCouldBeWrappedInTransit"</c><br/>
    /// </item>
    /// <item>
    ///    <b>Encoding</b><br/>
    ///    Sometimes other characters then ASCII characters are needed in parameters.<br/>
    ///    The parameter is then given a different name to specify that it is encoded.<br/>
    ///    <br/>
    ///    <b>Example</b><br/>
    ///    From: <c>Content-Disposition attachment; filename="specialCharsÆØÅ"</c><br/>
    ///    To: <c>Content-Disposition attachment; filename*="ISO-8859-1'en-us'specialCharsC6D8C0"</c><br/>
    ///    This encoding is almost the same as <see cref="EncodedWord"/> encoding, and is used to decode the value.<br/>
    /// </item>
    /// <item>
    ///    <b>Continuation and Encoding</b><br/>
    ///    Both Continuation and Encoding can be used on the same time.<br/>
    ///    <br/>
    ///    <b>Example</b><br/>
    ///    From: <c>Content-Disposition attachment; filename="specialCharsÆØÅWhichIsSoLong"</c><br/>
    ///    To: <c>Content-Disposition attachment; filename*0*="ISO-8859-1'en-us'specialCharsC6D8C0"; filename*1*="WhichIsSoLong"</c><br/>
    ///    This could also be encoded as:<br/>
    ///    To: <c>Content-Disposition attachment; filename*0*="ISO-8859-1'en-us'specialCharsC6D8C0"; filename*1="WhichIsSoLong"</c><br/>
    ///    Notice that <c>filename*1</c> does not have an <c>*</c> after it - denoting it IS NOT encoded.<br/>
    ///    There are some rules about this:<br/>
    ///    <list type="number">
    ///      <item>The encoding must be mentioned in the first part (filename*0*), which has to be encoded.</item>
    ///      <item>No other part must specify an encoding, but if encoded it uses the encoding mentioned in the first part.</item>
    ///      <item>Parts may be encoded or not in any order.</item>
    ///    </list>
    ///    <br/>
    /// </item>
    /// </list>
    /// More information and the specification is available in <see href="http://tools.ietf.org/html/rfc2231">RFC 2231</see>.
    /// </summary>
    internal static class Rfc2231Decoder
    {
        /// <summary>
        /// Decodes a string of the form:<br/>
        /// <c>value0; key1=value1; key2=value2; key3=value3</c><br/>
        /// The returned List of key value pairs will have the key as key and the decoded value as value.<br/>
        /// The first value0 will have a key of <see cref="string.Empty"/>.<br/>
        /// <br/>
        /// If continuation is used, then multiple keys will be merged into one key with the different values
        /// decoded into on big value for that key.<br/>
        /// Example:<br/>
        /// <code>
        /// title*0=part1
        /// title*1=part2
        /// </code>
        /// will have key and value of:<br></br>
        /// <c>title=decode(part1)decode(part2)</c>
        /// </summary>
        /// <param name="toDecode">The string to decode.</param>
        /// <returns>A list of decoded key value pairs.</returns>
        /// <exception cref="ArgumentNullException">If <paramref name="toDecode"/> is <see langword="null"/></exception>
        public static List<KeyValuePair<string, string>> Decode(string toDecode)
        {
            if (toDecode == null)
                throw new ArgumentNullException("toDecode");
            // Normalize the input to take account for missing semicolons after parameters.
            // Example
            // text/plain; charset=\"iso-8859-1\" name=\"somefile.txt\" or
            // text/plain;\tcharset=\"iso-8859-1\"\tname=\"somefile.txt\"
            // is normalized to
            // text/plain; charset=\"iso-8859-1\"; name=\"somefile.txt\"
            // Only works for parameters inside quotes
            // \s = matches whitespace
            toDecode = Regex.Replace(toDecode, "=\\s*\"(?<value>[^\"]*)\"\\s", "=\"${value}\"; ");
            // Normalize 
            // Since the above only works for parameters inside quotes, we need to normalize
            // the special case with the first parameter.
            // Example:
            // attachment filename="foo"
            // is normalized to
            // attachment; filename="foo"
            // ^ = matches start of line (when not inside square bracets [])
            toDecode = Regex.Replace(toDecode, @"^(?<first>[^;\s]+)\s(?<second>[^;\s]+)", "${first}; ${second}");
            // Split by semicolon, but only if not inside quotes
            List<string> splitted = Utility.SplitStringWithCharNotInsideQuotes(toDecode.Trim(), ';');
            List<KeyValuePair<string, string>> collection = new List<KeyValuePair<string, string>>(splitted.Count);
            foreach (string part in splitted)
            {
                // Empty strings should not be processed
                if (part.Trim().Length == 0)
                    continue;
                string[] keyValue = part.Trim().Split(new[] { '=' }, 2);
                if (keyValue.Length == 1)
                {
                    collection.Add(new KeyValuePair<string, string>("", keyValue[0]));
                }
                else if (keyValue.Length == 2)
                {
                    collection.Add(new KeyValuePair<string, string>(keyValue[0], keyValue[1]));
                }
                else
                {
                    throw new ArgumentException("When splitting the part \"" + part + "\" by = there was " + keyValue.Length + " parts. Only 1 and 2 are supported");
                }
            }
            return DecodePairs(collection);
        }
        /// <summary>
        /// Decodes the list of key value pairs into a decoded list of key value pairs.<br/>
        /// There may be less keys in the decoded list, but then the values for the lost keys will have been appended
        /// to the new key.
        /// </summary>
        /// <param name="pairs">The pairs to decode</param>
        /// <returns>A decoded list of pairs</returns>
        private static List<KeyValuePair<string, string>> DecodePairs(List<KeyValuePair<string, string>> pairs)
        {
            if (pairs == null)
                throw new ArgumentNullException("pairs");
            List<KeyValuePair<string, string>> resultPairs = new List<KeyValuePair<string, string>>(pairs.Count);
            int pairsCount = pairs.Count;
            for (int i = 0; i < pairsCount; i++)
            {
                KeyValuePair<string, string> currentPair = pairs[i];
                string key = currentPair.Key;
                string value = Utility.RemoveQuotesIfAny(currentPair.Value);
                // Is it a continuation parameter? (encoded or not)
                if (key.EndsWith("*0", StringComparison.OrdinalIgnoreCase) || key.EndsWith("*0*", StringComparison.OrdinalIgnoreCase))
                {
                    // This encoding will not be used if we get into the if which tells us
                    // that the whole continuation is not encoded
                    string encoding = "notEncoded - Value here is never used";
                    // Now lets find out if it is encoded too.
                    if (key.EndsWith("*0*", StringComparison.OrdinalIgnoreCase))
                    {
                        // It is encoded.
                        // Fetch out the encoding for later use and decode the value
                        // If the value was not encoded as the email specified
                        // encoding will be set to null. This will be used later.
                        value = DecodeSingleValue(value, out encoding);
                        // Find the right key to use to store the full value
                        // Remove the start *0 which tells is it is a continuation, and the first one
                        // And remove the * afterwards which tells us it is encoded
                        key = key.Replace("*0*", "");
                    }
                    else
                    {
                        // It is not encoded, and no parts of the continuation is encoded either
                        // Find the right key to use to store the full value
                        // Remove the start *0 which tells is it is a continuation, and the first one
                        key = key.Replace("*0", "");
                    }
                    // The StringBuilder will hold the full decoded value from all continuation parts
                    StringBuilder builder = new StringBuilder();
                    // Append the decoded value
                    builder.Append(value);
                    // Now go trough the next keys to see if they are part of the continuation
                    for (int j = i + 1, continuationCount = 1; j < pairsCount; j++, continuationCount++)
                    {
                        string jKey = pairs[j].Key;
                        string valueJKey = Utility.RemoveQuotesIfAny(pairs[j].Value);
                        if (jKey.Equals(key + "*" + continuationCount))
                        {
                            // This value part of the continuation is not encoded
                            // Therefore remove qoutes if any and add to our stringbuilder
                            builder.Append(valueJKey);
                            // Remember to increment i, as we have now treated one more KeyValuePair
                            i++;
                        }
                        else if (jKey.Equals(key + "*" + continuationCount + "*"))
                        {
                            // We will not get into this part if the first part was not encoded
                            // Therefore the encoding will only be used if and only if the
                            // first part was encoded, in which case we have remembered the encoding used
                            // Sometimes an email creator says that a string was encoded, but it really
                            // `was not. This is to catch that problem.
                            if (encoding != null)
                            {
                                // This value part of the continuation is encoded
                                // the encoding is not given in the current value,
                                // but was given in the first continuation, which we remembered for use here
                                valueJKey = DecodeSingleValue(valueJKey, encoding);
                            }
                            builder.Append(valueJKey);
                            // Remember to increment i, as we have now treated one more KeyValuePair
                            i++;
                        }
                        else
                        {
                            // No more keys for this continuation
                            break;
                        }
                    }
                    // Add the key and the full value as a pair
                    value = builder.ToString();
                    resultPairs.Add(new KeyValuePair<string, string>(key, value));
                }
                else if (key.EndsWith("*", StringComparison.OrdinalIgnoreCase))
                {
                    // This parameter is only encoded - it is not part of a continuation
                    // We need to change the key from "<key>*" to "<key>" and decode the value
                    // To get the key we want, we remove the last * that denotes
                    // that the value hold by the key was encoded
                    key = key.Replace("*", "");
                    // Decode the value
                    string throwAway;
                    value = DecodeSingleValue(value, out throwAway);
                    // Now input the new value with the new key
                    resultPairs.Add(new KeyValuePair<string, string>(key, value));
                }
                else
                {
                    // Fully normal key - the value is not encoded
                    // Therefore nothing to do, and we can simply pass the pair
                    // as being decoded now
                    resultPairs.Add(currentPair);
                }
            }
            return resultPairs;
        }
        /// <summary>
        /// This will decode a single value of the form: <c>ISO-8859-1'en-us'%3D%3DIamHere</c><br/>
        /// Which is basically a <see cref="EncodedWord"/> form just using % instead of =<br/>
        /// Notice that 'en-us' part is not used for anything.<br/>
        /// <br/>
        /// If the single value given is not on the correct form, it will be returned without 
        /// being decoded and <paramref name="encodingUsed"/> will be set to <see langword="null"/>.
        /// </summary>
        /// <param name="encodingUsed">
        /// The encoding used to decode with - it is given back for later use.<br/>
        /// <see langword="null"/> if input was not in the correct form.
        /// </param>
        /// <param name="toDecode">The value to decode</param>
        /// <returns>
        /// The decoded value that corresponds to <paramref name="toDecode"/> or if
        /// <paramref name="toDecode"/> is not on the correct form, it will be non-decoded.
        /// </returns>
        /// <exception cref="ArgumentNullException">If <paramref name="toDecode"/> is <see langword="null"/></exception>
        private static string DecodeSingleValue(string toDecode, out string encodingUsed)
        {
            if (toDecode == null)
                throw new ArgumentNullException("toDecode");
            // Check if input has a part describing the encoding
            if (toDecode.IndexOf('\'') == -1)
            {
                // The input was not encoded (at least not valid) and it is returned as is
                DefaultLogger.Log.LogDebug("Rfc2231Decoder: Someone asked me to decode a string which was not encoded - returning raw string. Input: " + toDecode);
                encodingUsed = null;
                return toDecode;
            }
            encodingUsed = toDecode.Substring(0, toDecode.IndexOf('\''));
            toDecode = toDecode.Substring(toDecode.LastIndexOf('\'') + 1);
            return DecodeSingleValue(toDecode, encodingUsed);
        }
        /// <summary>
        /// This will decode a single value of the form: %3D%3DIamHere
        /// Which is basically a <see cref="EncodedWord"/> form just using % instead of =
        /// </summary>
        /// <param name="valueToDecode">The value to decode</param>
        /// <param name="encoding">The encoding used to decode with</param>
        /// <returns>The decoded value that corresponds to <paramref name="valueToDecode"/></returns>
        /// <exception cref="ArgumentNullException">If <paramref name="valueToDecode"/> is <see langword="null"/></exception>
        /// <exception cref="ArgumentNullException">If <paramref name="encoding"/> is <see langword="null"/></exception>
        private static string DecodeSingleValue(string valueToDecode, string encoding)
        {
            if (valueToDecode == null)
                throw new ArgumentNullException("valueToDecode");
            if (encoding == null)
                throw new ArgumentNullException("encoding");
            // The encoding used is the same as QuotedPrintable, we only
            // need to change % to =
            // And otherwise make it look like the correct EncodedWord encoding
            valueToDecode = "=?" + encoding + "?Q?" + valueToDecode.Replace("%", "=") + "?=";
            return EncodedWord.Decode(valueToDecode);
        }
    }
}
namespace OpenPop.Mime.Decode
{
    using System;
    using System.IO;
    using System.Text;
    using System.Text.RegularExpressions;
    /// <summary>
    /// Used for decoding Quoted-Printable text.<br/>
    /// This is a robust implementation of a Quoted-Printable decoder defined in <a href="http://tools.ietf.org/html/rfc2045">RFC 2045</a> and <a href="http://tools.ietf.org/html/rfc2047">RFC 2047</a>.<br/>
    /// Every measurement has been taken to conform to the RFC.
    /// </summary>
    internal static class QuotedPrintable
    {
        /// <summary>
        /// Decodes a Quoted-Printable string according to <a href="http://tools.ietf.org/html/rfc2047">RFC 2047</a>.<br/>
        /// RFC 2047 is used for decoding Encoded-Word encoded strings.
        /// </summary>
        /// <param name="toDecode">Quoted-Printable encoded string</param>
        /// <param name="encoding">Specifies which encoding the returned string will be in</param>
        /// <returns>A decoded string in the correct encoding</returns>
        /// <exception cref="ArgumentNullException">If <paramref name="toDecode"/> or <paramref name="encoding"/> is <see langword="null"/></exception>
        public static string DecodeEncodedWord(string toDecode, Encoding encoding)
        {
            if (toDecode == null)
                throw new ArgumentNullException("toDecode");
            if (encoding == null)
                throw new ArgumentNullException("encoding");
            // Decode the QuotedPrintable string and return it
            return encoding.GetString(Rfc2047QuotedPrintableDecode(toDecode, true));
        }
        /// <summary>
        /// Decodes a Quoted-Printable string according to <a href="http://tools.ietf.org/html/rfc2045">RFC 2045</a>.<br/>
        /// RFC 2045 specifies the decoding of a body encoded with Content-Transfer-Encoding of quoted-printable.
        /// </summary>
        /// <param name="toDecode">Quoted-Printable encoded string</param>
        /// <returns>A decoded byte array that the Quoted-Printable encoded string described</returns>
        /// <exception cref="ArgumentNullException">If <paramref name="toDecode"/> is <see langword="null"/></exception>
        public static byte[] DecodeContentTransferEncoding(string toDecode)
        {
            if (toDecode == null)
                throw new ArgumentNullException("toDecode");
            // Decode the QuotedPrintable string and return it
            return Rfc2047QuotedPrintableDecode(toDecode, false);
        }
        /// <summary>
        /// This is the actual decoder.
        /// </summary>
        /// <param name="toDecode">The string to be decoded from Quoted-Printable</param>
        /// <param name="encodedWordVariant">
        /// If <see langword="true"/>, specifies that RFC 2047 quoted printable decoding is used.<br/>
        /// This is for quoted-printable encoded words<br/>
        /// <br/>
        /// If <see langword="false"/>, specifies that RFC 2045 quoted printable decoding is used.<br/>
        /// This is for quoted-printable Content-Transfer-Encoding
        /// </param>
        /// <returns>A decoded byte array that was described by <paramref name="toDecode"/></returns>
        /// <exception cref="ArgumentNullException">If <paramref name="toDecode"/> is <see langword="null"/></exception>
        /// <remarks>See <a href="http://tools.ietf.org/html/rfc2047#section-4.2">RFC 2047 section 4.2</a> for RFC details</remarks>
        private static byte[] Rfc2047QuotedPrintableDecode(string toDecode, bool encodedWordVariant)
        {
            if (toDecode == null)
                throw new ArgumentNullException("toDecode");
            // Create a byte array builder which is roughly equivalent to a StringBuilder
            using (MemoryStream byteArrayBuilder = new MemoryStream())
            {
                // Remove illegal control characters
                toDecode = RemoveIllegalControlCharacters(toDecode);
                // Run through the whole string that needs to be decoded
                for (int i = 0; i < toDecode.Length; i++)
                {
                    char currentChar = toDecode[i];
                    if (currentChar == '=')
                    {
                        // Check that there is at least two characters behind the equal sign
                        if (toDecode.Length - i < 3)
                        {
                            // We are at the end of the toDecode string, but something is missing. Handle it the way RFC 2045 states
                            WriteAllBytesToStream(byteArrayBuilder, DecodeEqualSignNotLongEnough(toDecode.Substring(i)));
                            // Since it was the last part, we should stop parsing anymore
                            break;
                        }
                        // Decode the Quoted-Printable part
                        string quotedPrintablePart = toDecode.Substring(i, 3);
                        WriteAllBytesToStream(byteArrayBuilder, DecodeEqualSign(quotedPrintablePart));
                        // We now consumed two extra characters. Go forward two extra characters
                        i += 2;
                    }
                    else
                    {
                        // This character is not quoted printable hex encoded.
                        // Could it be the _ character, which represents space
                        // and are we using the encoded word variant of QuotedPrintable
                        if (currentChar == '_' && encodedWordVariant)
                        {
                            // The RFC specifies that the "_" always represents hexadecimal 20 even if the
                            // SPACE character occupies a different code position in the character set in use.
                            byteArrayBuilder.WriteByte(0x20);
                        }
                        else
                        {
                            // This is not encoded at all. This is a literal which should just be included into the output.
                            byteArrayBuilder.WriteByte((byte)currentChar);
                        }
                    }
                }
                return byteArrayBuilder.ToArray();
            }
        }
        /// <summary>
        /// Writes all bytes in a byte array to a stream
        /// </summary>
        /// <param name="stream">The stream to write to</param>
        /// <param name="toWrite">The bytes to write to the <paramref name="stream"/></param>
        private static void WriteAllBytesToStream(Stream stream, byte[] toWrite)
        {
            stream.Write(toWrite, 0, toWrite.Length);
        }
        /// <summary>
        /// RFC 2045 states about robustness:<br/>
        /// <code>
        /// Control characters other than TAB, or CR and LF as parts of CRLF pairs,
        /// must not appear. The same is true for octets with decimal values greater
        /// than 126.  If found in incoming quoted-printable data by a decoder, a
        /// robust implementation might exclude them from the decoded data and warn
        /// the user that illegal characters were discovered.
        /// </code>
        /// Control characters are defined in RFC 2396 as<br/>
        /// <c>control = US-ASCII coded characters 00-1F and 7F hexadecimal</c>
        /// </summary>
        /// <param name="input">String to be stripped from illegal control characters</param>
        /// <returns>A string with no illegal control characters</returns>
        /// <exception cref="ArgumentNullException">If <paramref name="input"/> is <see langword="null"/></exception>
        private static string RemoveIllegalControlCharacters(string input)
        {
            if (input == null)
                throw new ArgumentNullException("input");
            // First we remove any \r or \n which is not part of a \r\n pair
            input = RemoveCarriageReturnAndNewLinewIfNotInPair(input);
            // Here only legal \r\n is left over
            // We now simply keep them, and the \t which is also allowed
            // \x0A = \n
            // \x0D = \r
            // \x09 = \t)
            return Regex.Replace(input, "[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]", "");
        }
        /// <summary>
        /// This method will remove any \r and \n which is not paired as \r\n
        /// </summary>
        /// <param name="input">String to remove lonely \r and \n's from</param>
        /// <returns>A string without lonely \r and \n's</returns>
        /// <exception cref="ArgumentNullException">If <paramref name="input"/> is <see langword="null"/></exception>
        private static string RemoveCarriageReturnAndNewLinewIfNotInPair(string input)
        {
            if (input == null)
                throw new ArgumentNullException("input");
            // Use this for building up the new string. This is used for performance instead
            // of altering the input string each time a illegal token is found
            StringBuilder newString = new StringBuilder(input.Length);
            for (int i = 0; i < input.Length; i++)
            {
                // There is a character after it
                // Check for lonely \r
                // There is a lonely \r if it is the last character in the input or if there
                // is no \n following it
                if (input[i] == '\r' && (i + 1 >= input.Length || input[i + 1] != '\n'))
                {
                    // Illegal token \r found. Do not add it to the new string
                    // Check for lonely \n
                    // There is a lonely \n if \n is the first character or if there
                    // is no \r in front of it
                }
                else if (input[i] == '\n' && (i - 1 < 0 || input[i - 1] != '\r'))
                {
                    // Illegal token \n found. Do not add it to the new string
                }
                else
                {
                    // No illegal tokens found. Simply insert the character we are at
                    // in our new string
                    newString.Append(input[i]);
                }
            }
            return newString.ToString();
        }
        /// <summary>
        /// RFC 2045 says that a robust implementation should handle:<br/>
        /// <code>
        /// An "=" cannot be the ultimate or penultimate character in an encoded
        /// object. This could be handled as in case (2) above.
        /// </code>
        /// Case (2) is:<br/>
        /// <code>
        /// An "=" followed by a character that is neither a
        /// hexadecimal digit (including "abcdef") nor the CR character of a CRLF pair
        /// is illegal.  This case can be the result of US-ASCII text having been
        /// included in a quoted-printable part of a message without itself having
        /// been subjected to quoted-printable encoding.  A reasonable approach by a
        /// robust implementation might be to include the "=" character and the
        /// following character in the decoded data without any transformation and, if
        /// possible, indicate to the user that proper decoding was not possible at
        /// this point in the data.
        /// </code>
        /// </summary>
        /// <param name="decode">
        /// The string to decode which cannot have length above or equal to 3
        /// and must start with an equal sign.
        /// </param>
        /// <returns>A decoded byte array</returns>
        /// <exception cref="ArgumentNullException">If <paramref name="decode"/> is <see langword="null"/></exception>
        /// <exception cref="ArgumentException">Thrown if a the <paramref name="decode"/> parameter has length above 2 or does not start with an equal sign.</exception>
        private static byte[] DecodeEqualSignNotLongEnough(string decode)
        {
            if (decode == null)
                throw new ArgumentNullException("decode");
            // We can only decode wrong length equal signs
            if (decode.Length >= 3)
                throw new ArgumentException("decode must have length lower than 3", "decode");
            // First char must be =
            if (decode[0] != '=')
                throw new ArgumentException("First part of decode must be an equal sign", "decode");
            // We will now believe that the string sent to us, was actually not encoded
            // Therefore it must be in US-ASCII and we will return the bytes it corrosponds to
            return Encoding.ASCII.GetBytes(decode);
        }
        /// <summary>
        /// This helper method will decode a string of the form "=XX" where X is any character.<br/>
        /// This method will never fail, unless an argument of length not equal to three is passed.
        /// </summary>
        /// <param name="decode">The length 3 character that needs to be decoded</param>
        /// <returns>A decoded byte array</returns>
        /// <exception cref="ArgumentNullException">If <paramref name="decode"/> is <see langword="null"/></exception>
        /// <exception cref="ArgumentException">Thrown if a the <paramref name="decode"/> parameter does not have length 3 or does not start with an equal sign.</exception>
        private static byte[] DecodeEqualSign(string decode)
        {
            if (decode == null)
                throw new ArgumentNullException("decode");
            // We can only decode the string if it has length 3 - other calls to this function is invalid
            if (decode.Length != 3)
                throw new ArgumentException("decode must have length 3", "decode");
            // First char must be =
            if (decode[0] != '=')
                throw new ArgumentException("decode must start with an equal sign", "decode");
            // There are two cases where an equal sign might appear
            // It might be a
            //   - hex-string like =3D, denoting the character with hex value 3D
            //   - it might be the last character on the line before a CRLF
            //     pair, denoting a soft linebreak, which simply
            //     splits the text up, because of the 76 chars per line restriction
            if (decode.Contains("\r\n"))
            {
                // Soft break detected
                // We want to return string.Empty which is equivalent to a zero-length byte array
                return new byte[0];
            }
            // Hex string detected. Convertion needed.
            // It might be that the string located after the equal sign is not hex characters
            // An example: =JU
            // In that case we would like to catch the FormatException and do something else
            try
            {
                // The number part of the string is the last two digits. Here we simply remove the equal sign
                string numberString = decode.Substring(1);
                // Now we create a byte array with the converted number encoded in the string as a hex value (base 16)
                // This will also handle illegal encodings like =3d where the hex digits are not uppercase,
                // which is a robustness requirement from RFC 2045.
                byte[] oneByte = new[] { Convert.ToByte(numberString, 16) };
                // Simply return our one byte byte array
                return oneByte;
            }
            catch (FormatException)
            {
                // RFC 2045 says about robust implementation:
                // An "=" followed by a character that is neither a
                // hexadecimal digit (including "abcdef") nor the CR
                // character of a CRLF pair is illegal.  This case can be
                // the result of US-ASCII text having been included in a
                // quoted-printable part of a message without itself
                // having been subjected to quoted-printable encoding.  A
                // reasonable approach by a robust implementation might be
                // to include the "=" character and the following
                // character in the decoded data without any
                // transformation and, if possible, indicate to the user
                // that proper decoding was not possible at this point in
                // the data.
                // So we choose to believe this is actually an un-encoded string
                // Therefore it must be in US-ASCII and we will return the bytes it corrosponds to
                return Encoding.ASCII.GetBytes(decode);
            }
        }
    }
}
namespace OpenPop.Mime.Decode
{
    using System;
    using System.Collections.Generic;
    using System.Globalization;
    using System.Text;
    /// <summary>
    /// Utility class used by OpenPop for mapping from a characterSet to an <see cref="Encoding"/>.<br/>
    /// <br/>
    /// The functionality of the class can be altered by adding mappings
    /// using <see cref="AddMapping"/> and by adding a <see cref="FallbackDecoder"/>.<br/>
    /// <br/>
    /// Given a characterSet, it will try to find the Encoding as follows:
    /// <list type="number">
    ///     <item>
    ///         <description>If a mapping for the characterSet was added, use the specified Encoding from there. Mappings can be added using <see cref="AddMapping"/>.</description>
    ///     </item>
    ///     <item>
    ///         <description>Try to parse the characterSet and look it up using <see cref="Encoding.GetEncoding(int)"/> for codepages or <see cref="Encoding.GetEncoding(string)"/> for named encodings.</description>
    ///     </item>
    ///     <item>
    ///         <description>If an encoding is not found yet, use the <see cref="FallbackDecoder"/> if defined. The <see cref="FallbackDecoder"/> is user defined.</description>
    ///     </item>
    /// </list>
    /// </summary>
    public static class EncodingFinder
    {
        /// <summary>
        /// Delegate that is used when the EncodingFinder is unable to find an encoding by
        /// using the <see cref="EncodingFinder.EncodingMap"/> or general code.<br/>
        /// This is used as a last resort and can be used for setting a default encoding or
        /// for finding an encoding on runtime for some <paramref name="characterSet"/>.
        /// </summary>
        /// <param name="characterSet">The character set to find an encoding for.</param>
        /// <returns>An encoding for the <paramref name="characterSet"/> or <see langword="null"/> if none could be found.</returns>
        public delegate Encoding FallbackDecoderDelegate(string characterSet);
        /// <summary>
        /// Last resort decoder. <seealso cref="FallbackDecoderDelegate"/>.
        /// </summary>
        public static FallbackDecoderDelegate FallbackDecoder { private get; set; }
        /// <summary>
        /// Mapping from charactersets to encodings.
        /// </summary>
        private static Dictionary<string, Encoding> EncodingMap { get; set; }
        /// <summary>
        /// Initialize the EncodingFinder
        /// </summary>
        static EncodingFinder()
        {
            Reset();
        }
        /// <summary>
        /// Used to reset this static class to facilite isolated unit testing.
        /// </summary>
        internal static void Reset()
        {
            EncodingMap = new Dictionary<string, Encoding>();
            FallbackDecoder = null;
            // Some emails incorrectly specify the encoding as utf8, but it should have been utf-8.
            AddMapping("utf8", Encoding.UTF8);
        }
        /// <summary>
        /// Parses a character set into an encoding.
        /// </summary>
        /// <param name="characterSet">The character set to parse</param>
        /// <returns>An encoding which corresponds to the character set</returns>
        /// <exception cref="ArgumentNullException">If <paramref name="characterSet"/> is <see langword="null"/></exception>
        internal static Encoding FindEncoding(string characterSet)
        {
            if (characterSet == null)
                throw new ArgumentNullException("characterSet");
            string charSetUpper = characterSet.ToUpperInvariant();
            // Check if the characterSet is explicitly mapped to an encoding
            if (EncodingMap.ContainsKey(charSetUpper))
                return EncodingMap[charSetUpper];
            // Try to find the generally find the encoding
            try
            {
                if (charSetUpper.Contains("WINDOWS") || charSetUpper.Contains("CP"))
                {
                    // It seems the characterSet contains an codepage value, which we should use to parse the encoding
                    charSetUpper = charSetUpper.Replace("CP", ""); // Remove cp
                    charSetUpper = charSetUpper.Replace("WINDOWS", ""); // Remove windows
                    charSetUpper = charSetUpper.Replace("-", ""); // Remove - which could be used as cp-1554
                    // Now we hope the only thing left in the characterSet is numbers.
                    int codepageNumber = int.Parse(charSetUpper, CultureInfo.InvariantCulture);
                    return Encoding.GetEncoding(codepageNumber);
                }
                // It seems there is no codepage value in the characterSet. It must be a named encoding
                return Encoding.GetEncoding(characterSet);
            }
            catch (ArgumentException)
            {
                // The encoding could not be found generally. 
                // Try to use the FallbackDecoder if it is defined.
                // Check if it is defined
                if (FallbackDecoder == null)
                    throw; // It was not defined - throw catched exception
                // Use the FallbackDecoder
                Encoding fallbackDecoderResult = FallbackDecoder(characterSet);
                // Check if the FallbackDecoder had a solution
                if (fallbackDecoderResult != null)
                    return fallbackDecoderResult;
                // If no solution was found, throw catched exception
                throw;
            }
        }
        /// <summary>
        /// Puts a mapping from <paramref name="characterSet"/> to <paramref name="encoding"/>
        /// into the <see cref="EncodingFinder"/>'s internal mapping Dictionary.
        /// </summary>
        /// <param name="characterSet">The string that maps to the <paramref name="encoding"/></param>
        /// <param name="encoding">The <see cref="Encoding"/> that should be mapped from <paramref name="characterSet"/></param>
        /// <exception cref="ArgumentNullException">If <paramref name="characterSet"/> is <see langword="null"/></exception>
        /// <exception cref="ArgumentNullException">If <paramref name="encoding"/> is <see langword="null"/></exception>
        public static void AddMapping(string characterSet, Encoding encoding)
        {
            if (characterSet == null)
                throw new ArgumentNullException("characterSet");
            if (encoding == null)
                throw new ArgumentNullException("encoding");
            // Add the mapping using uppercase
            EncodingMap.Add(characterSet.ToUpperInvariant(), encoding);
        }
    }
}
namespace OpenPop.Mime.Decode
{
    using System;
    using System.Text;
    using System.Text.RegularExpressions;
    using OpenPop.Mime.Header;
    /// <summary>
    /// Utility class for dealing with encoded word strings<br/>
    /// <br/>
    /// EncodedWord encoded strings are only in ASCII, but can embed information
    /// about characters in other character sets.<br/>
    /// <br/>
    /// It is done by specifying the character set, an encoding that maps from ASCII to
    /// the correct bytes and the actual encoded string.<br/>
    /// <br/>
    /// It is specified in a format that is best summarized by a BNF:<br/>
    /// <c>"=?" character_set "?" encoding "?" encoded-text "?="</c><br/>
    /// </summary>
    /// <example>
    /// <c>=?ISO-8859-1?Q?=2D?=</c>
    /// Here <c>ISO-8859-1</c> is the character set.<br/>
    /// <c>Q</c> is the encoding method (quoted-printable). <c>B</c> is also supported (Base 64).<br/>
    /// The encoded text is the <c>=2D</c> part which is decoded to a space.
    /// </example>
    internal static class EncodedWord
    {
        /// <summary>
        /// Decode text that is encoded with the <see cref="EncodedWord"/> encoding.<br/>
        ///<br/>
        /// This method will decode any encoded-word found in the string.<br/>
        /// All parts which is not encoded will not be touched.<br/>
        /// <br/>
        /// From <a href="http://tools.ietf.org/html/rfc2047">RFC 2047</a>:<br/>
        /// <code>
        /// Generally, an "encoded-word" is a sequence of printable ASCII
        /// characters that begins with "=?", ends with "?=", and has two "?"s in
        /// between.  It specifies a character set and an encoding method, and
        /// also includes the original text encoded as graphic ASCII characters,
        /// according to the rules for that encoding method.
        /// </code>
        /// Example:<br/>
        /// <c>=?ISO-8859-1?q?this=20is=20some=20text?= other text here</c>
        /// </summary>
        /// <remarks>See <a href="http://tools.ietf.org/html/rfc2047#section-2">RFC 2047 section 2</a> "Syntax of encoded-words" for more details</remarks>
        /// <param name="encodedWords">Source text. May be content which is not encoded.</param>
        /// <returns>Decoded text</returns>
        /// <exception cref="ArgumentNullException">If <paramref name="encodedWords"/> is <see langword="null"/></exception>
        public static string Decode(string encodedWords)
        {
            if (encodedWords == null)
                throw new ArgumentNullException("encodedWords");
            // Notice that RFC2231 redefines the BNF to
            // encoded-word := "=?" charset ["*" language] "?" encoded-text "?="
            // but no usage of this BNF have been spotted yet. It is here to
            // ease debugging if such a case is discovered.
            // This is the regex that should fit the BNF
            // RFC Says that NO WHITESPACE is allowed in this encoding, but there are examples
            // where whitespace is there, and therefore this regex allows for such.
            const string encodedWordRegex = @"\=\?(?<Charset>\S+?)\?(?<Encoding>\w)\?(?<Content>.+?)\?\=";
            // \w    Matches any word character including underscore. Equivalent to "[A-Za-z0-9_]".
            // \S    Matches any nonwhite space character. Equivalent to "[^ \f\n\r\t\v]".
            // +?   non-gready equivalent to +
            // (?<NAME>REGEX) is a named group with name NAME and regular expression REGEX
            // Any amount of linear-space-white between 'encoded-word's,
            // even if it includes a CRLF followed by one or more SPACEs,
            // is ignored for the purposes of display.
            // http://tools.ietf.org/html/rfc2047#page-12
            // Define a regular expression that captures two encoded words with some whitespace between them
            const string replaceRegex = @"(?<first>" + encodedWordRegex + @")\s+(?<second>" + encodedWordRegex + ")";
            // Then, find an occourance of such an expression, but remove the whitespace inbetween when found
            encodedWords = Regex.Replace(encodedWords, replaceRegex, "${first}${second}");
            string decodedWords = encodedWords;
            MatchCollection matches = Regex.Matches(encodedWords, encodedWordRegex);
            foreach (Match match in matches)
            {
                // If this match was not a success, we should not use it
                if (!match.Success) continue;
                string fullMatchValue = match.Value;
                string encodedText = match.Groups["Content"].Value;
                string encoding = match.Groups["Encoding"].Value;
                string charset = match.Groups["Charset"].Value;
                // Get the encoding which corrosponds to the character set
                Encoding charsetEncoding = EncodingFinder.FindEncoding(charset);
                // Store decoded text here when done
                string decodedText;
                // Encoding may also be written in lowercase
                switch (encoding.ToUpperInvariant())
                {
                    // RFC:
                    // The "B" encoding is identical to the "BASE64" 
                    // encoding defined by RFC 2045.
                    // http://tools.ietf.org/html/rfc2045#section-6.8
                    case "B":
                        decodedText = Base64.Decode(encodedText, charsetEncoding);
                        break;
                    // RFC:
                    // The "Q" encoding is similar to the "Quoted-Printable" content-
                    // transfer-encoding defined in RFC 2045.
                    // There are more details to this. Please check
                    // http://tools.ietf.org/html/rfc2047#section-4.2
                    // 
                    case "Q":
                        decodedText = QuotedPrintable.DecodeEncodedWord(encodedText, charsetEncoding);
                        break;
                    default:
                        throw new ArgumentException("The encoding " + encoding + " was not recognized");
                }
                // Repalce our encoded value with our decoded value
                decodedWords = decodedWords.Replace(fullMatchValue, decodedText);
            }
            return decodedWords;
        }
    }
}
namespace OpenPop.Mime.Decode
{
    using System;
    using System.Text;
    using OpenPop.Common.Logging;
    /// <summary>
    /// Utility class for dealing with Base64 encoded strings
    /// </summary>
    internal static class Base64
    {
        /// <summary>
        /// Decodes a base64 encoded string into the bytes it describes
        /// </summary>
        /// <param name="base64Encoded">The string to decode</param>
        /// <returns>A byte array that the base64 string described</returns>
        public static byte[] Decode(string base64Encoded)
        {
            try
            {
                return Convert.FromBase64String(base64Encoded);
            }
            catch (FormatException e)
            {
                DefaultLogger.Log.LogError("Base64: (FormatException) " + e.Message + "\r\nOn string: " + base64Encoded);
                throw;
            }
        }
        /// <summary>
        /// Decodes a Base64 encoded string using a specified <see cref="System.Text.Encoding"/> 
        /// </summary>
        /// <param name="base64Encoded">Source string to decode</param>
        /// <param name="encoding">The encoding to use for the decoded byte array that <paramref name="base64Encoded"/> describes</param>
        /// <returns>A decoded string</returns>
        /// <exception cref="ArgumentNullException">If <paramref name="base64Encoded"/> or <paramref name="encoding"/> is <see langword="null"/></exception>
        /// <exception cref="FormatException">If <paramref name="base64Encoded"/> is not a valid base64 encoded string</exception>
        public static string Decode(string base64Encoded, Encoding encoding)
        {
            if (base64Encoded == null)
                throw new ArgumentNullException("base64Encoded");
            if (encoding == null)
                throw new ArgumentNullException("encoding");
            return encoding.GetString(Decode(base64Encoded));
        }
    }
}
namespace OpenPop.Common
{
    using System;
    using System.IO;
    using System.Text;
    /// <summary>
    /// Utility to help reading bytes and strings of a <see cref="Stream"/>
    /// </summary>
    internal static class StreamUtility
    {
        /// <summary>
        /// Read a line from the stream.
        /// A line is interpreted as all the bytes read until a CRLF or LF is encountered.<br/>
        /// CRLF pair or LF is not included in the string.
        /// </summary>
        /// <param name="stream">The stream from which the line is to be read</param>
        /// <returns>A line read from the stream returned as a byte array or <see langword="null"/> if no bytes were readable from the stream</returns>
        /// <exception cref="ArgumentNullException">If <paramref name="stream"/> is <see langword="null"/></exception>
        public static byte[] ReadLineAsBytes(Stream stream)
        {
            if (stream == null)
                throw new ArgumentNullException("stream");
            using (MemoryStream memoryStream = new MemoryStream())
            {
                while (true)
                {
                    int justRead = stream.ReadByte();
                    if (justRead == -1 && memoryStream.Length > 0)
                        break;
                    // Check if we started at the end of the stream we read from
                    // and we have not read anything from it yet
                    if (justRead == -1 && memoryStream.Length == 0)
                        return null;
                    char readChar = (char)justRead;
                    // Do not write \r or \n
                    if (readChar != '\r' && readChar != '\n')
                        memoryStream.WriteByte((byte)justRead);
                    // Last point in CRLF pair
                    if (readChar == '\n')
                        break;
                }
                return memoryStream.ToArray();
            }
        }
        /// <summary>
        /// Read a line from the stream. <see cref="ReadLineAsBytes"/> for more documentation.
        /// </summary>
        /// <param name="stream">The stream to read from</param>
        /// <returns>A line read from the stream or <see langword="null"/> if nothing could be read from the stream</returns>
        /// <exception cref="ArgumentNullException">If <paramref name="stream"/> is <see langword="null"/></exception>
        public static string ReadLineAsAscii(Stream stream)
        {
            byte[] readFromStream = ReadLineAsBytes(stream);
            return readFromStream != null ? Encoding.ASCII.GetString(readFromStream) : null;
        }
    }
}
namespace OpenPop.Common.Logging
{
    /// <summary>
    /// Defines a logger for managing system logging output  
    /// </summary>
    public interface ILog
    {
        /// <summary>
        /// Logs an error message to the logs
        /// </summary>
        /// <param name="message">This is the error message to log</param>
        void LogError(string message);
        /// <summary>
        /// Logs a debug message to the logs
        /// </summary>
        /// <param name="message">This is the debug message to log</param>
        void LogDebug(string message);
    }
}
namespace OpenPop.Common.Logging
{
    using System;
    using System.IO;
    /// <summary>
    /// This logging object writes application error and debug output to a text file.
    /// </summary>
    public class FileLogger : ILog
    {
        #region File Logging
        /// <summary>
        /// Lock object to prevent thread interactions
        /// </summary>
        private static readonly object LogLock;
        /// <summary>
        /// Static constructor
        /// </summary>
        static FileLogger()
        {
            // Default log file is defined here
            LogFile = new FileInfo("OpenPOP.log");
            Enabled = true;
            Verbose = false;
            LogLock = new object();
        }
        /// <summary>
        /// Turns the logging on and off.
        /// </summary>
        public static bool Enabled { get; set; }
        /// <summary>
        /// Enables or disables the output of Debug level log messages
        /// </summary>
        public static bool Verbose { get; set; }
        /// <summary>
        /// The file to which log messages will be written
        /// </summary>
        /// <remarks>This property defaults to OpenPOP.log.</remarks>
        public static FileInfo LogFile { get; set; }
        /// <summary>
        /// Write a message to the log file
        /// </summary>
        /// <param name="text">The error text to log</param>
        private static void LogToFile(string text)
        {
            if (text == null)
                throw new ArgumentNullException("text");
            // We want to open the file and append some text to it
            lock (LogLock)
            {
                using (StreamWriter sw = LogFile.AppendText())
                {
                    sw.WriteLine(DateTime.Now + " " + text);
                    sw.Flush();
                }
            }
        }
        #endregion
        #region ILog Implementation
        /// <summary>
        /// Logs an error message to the logs
        /// </summary>
        /// <param name="message">This is the error message to log</param>
        public void LogError(string message)
        {
            if (Enabled)
                LogToFile(message);
        }
        /// <summary>
        /// Logs a debug message to the logs
        /// </summary>
        /// <param name="message">This is the debug message to log</param>
        public void LogDebug(string message)
        {
            if (Enabled && Verbose)
                LogToFile("DEBUG: " + message);
        }
        #endregion
    }
}
namespace OpenPop.Common.Logging
{
    using System;
    /// <summary>
    /// This logging object writes application error and debug output using the
    /// <see cref="System.Diagnostics.Trace"/> facilities.
    /// </summary>
    public class DiagnosticsLogger : ILog
    {
        /// <summary>
        /// Logs an error message to the System Trace facility
        /// </summary>
        /// <param name="message">This is the error message to log</param>
        public void LogError(string message)
        {
            if (message == null)
                throw new ArgumentNullException("message");
            System.Diagnostics.Trace.WriteLine("OpenPOP: " + message);
        }
        /// <summary>
        /// Logs a debug message to the system Trace Facility
        /// </summary>
        /// <param name="message">This is the debug message to log</param>
        public void LogDebug(string message)
        {
            if (message == null)
                throw new ArgumentNullException("message");
            System.Diagnostics.Trace.WriteLine("OpenPOP: (DEBUG) " + message);
        }
    }
}
namespace OpenPop.Common.Logging
{
    using System;
    /// <summary>
    /// This is the log that all logging will go trough.
    /// </summary>
    public static class DefaultLogger
    {
        /// <summary>
        /// This is the logger used by all logging methods in the assembly.<br/>
        /// You can override this if you want, to move logging to one of your own
        /// logging implementations.<br/>
        /// <br/>
        /// By default a <see cref="DiagnosticsLogger"/> is used.
        /// </summary>
        public static ILog Log { get; private set; }
        static DefaultLogger()
        {
            Log = new DiagnosticsLogger();
        }
        /// <summary>
        /// Changes the default logging to log to a new logger
        /// </summary>
        /// <param name="newLogger">The new logger to use to send log messages to</param>
        /// <exception cref="ArgumentNullException">
        /// Never set this to <see langword="null"/>.<br/>
        /// Instead you should implement a NullLogger which just does nothing.
        /// </exception>
        public static void SetLog(ILog newLogger)
        {
            if (newLogger == null)
                throw new ArgumentNullException("newLogger");
            Log = newLogger;
        }
    }
}

posted @ 2012-03-27 01:22  于斯人也  阅读(5423)  评论(0编辑  收藏  举报