• 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;
            }
        }
    }
    
    
  • 相关阅读:
    linux rename命令批量修改文件名
    深度学习在推断阶段(inference)的硬件实现方法概述
    pkg-config原理及用法
    可测性分析
    CMD常用命令
    CMD命令:不是内部或者外部命令也不是可运行的程序或批处理文件
    main函数的参数argc和argv
    Eclipse中的特殊注释:TODO、XXX、FIXME
    whl文件(python)安装方法
    linux软链接和硬链接
  • 原文地址:https://www.cnblogs.com/Microshaoft/p/2418882.html
Copyright © 2020-2023  润新知