using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Net.Sockets;
using System.Security.Principal;

using LumiSoft.Net.IO;
using LumiSoft.Net.TCP;
using LumiSoft.Net.AUTH;
using LumiSoft.Net.MIME;
using LumiSoft.Net.Mail;

namespace LumiSoft.Net.IMAP.Server
{
    /// <summary>
    /// This class implements IMAP server session. Defined RFC 3501.
    /// </summary>
    public class IMAP_Session : TCP_ServerSession
    {
        #region class _SelectedFolder

        /// <summary>
        /// This class holds selected folder data.
        /// </summary>
        private class _SelectedFolder
        {
            private string                 m_Folder        = null;
            private bool                   m_IsReadOnly    = false;
            private List<IMAP_MessageInfo> m_pMessagesInfo = null;

            /// <summary>
            /// Default constructor.
            /// </summary>
            /// <param name="folder">Folder name with optional path.</param>
            /// <param name="isReadOnly">Specifies if folder is read only.</param>
            /// <param name="messagesInfo">Messages info.</param>
            /// <exception cref="ArgumentNullException">Is raised when <b>folder</b> or <b>messagesInfo</b> is null reference.</exception>
            public _SelectedFolder(string folder,bool isReadOnly,List<IMAP_MessageInfo> messagesInfo)
            {
                if(folder == null){
                    throw new ArgumentNullException("folder");
                }
                if(messagesInfo == null){
                    throw new ArgumentNullException("messagesInfo");
                }

                m_Folder        = folder;
                m_IsReadOnly    = isReadOnly;
                m_pMessagesInfo = messagesInfo;

                Reindex();
            }
                        

            #region method Filter

            /// <summary>
            /// Gets messages which match to the specified sequence set.
            /// </summary>
            /// <param name="uid">Specifies if sequence set contains UID or sequence numbers.</param>
            /// <param name="seqSet">Sequence set.</param>
            /// <returns>Returns messages which match to the specified sequence set.</returns>
            /// <exception cref="ArgumentNullException">Is raised when <b>seqSet</b> is null reference.</exception>
            internal IMAP_MessageInfo[] Filter(bool uid,IMAP_t_SeqSet seqSet)
            {
                if(seqSet == null){
                    throw new ArgumentNullException("seqSet");
                }

                List<IMAP_MessageInfo> retVal = new List<IMAP_MessageInfo>();
                for(int i=0;i<m_pMessagesInfo.Count;i++){
                    IMAP_MessageInfo msgInfo = m_pMessagesInfo[i];                    
                    if(uid){
                        if(seqSet.Contains(msgInfo.UID)){
                            retVal.Add(msgInfo);
                        }
                    }
                    else{
                        if(seqSet.Contains(i + 1)){
                            retVal.Add(msgInfo);
                        }
                    }
                }

                return retVal.ToArray();
            }

            #endregion

            #region method RemoveMessage

            /// <summary>
            /// Removes specified message from messages info.
            /// </summary>
            /// <param name="message">Message info.</param>
            /// <exception cref="ArgumentNullException">Is raised when <b>message</b> is null reference.</exception>
            internal void RemoveMessage(IMAP_MessageInfo message)
            {
                if(message == null){
                    throw new ArgumentNullException("message");
                }

                m_pMessagesInfo.Remove(message);
            }

            #endregion

            #region method GetSeqNo

            /// <summary>
            /// Gets specified message info IMAP 1-based sequence number.
            /// </summary>
            /// <param name="msgInfo">Message info.</param>
            /// <returns>Returns specified message info IMAP 1-based sequence number.</returns>
            /// <exception cref="ArgumentNullException">Is raised when <b>msgInfo</b> is null reference.</exception>
            internal int GetSeqNo(IMAP_MessageInfo msgInfo)
            {
                if(msgInfo == null){
                    throw new ArgumentNullException("msgInfo");
                }

                return m_pMessagesInfo.IndexOf(msgInfo) + 1;
            }

            /// <summary>
            /// Gets specified message IMAP 1-based sequence number.
            /// </summary>
            /// <param name="uid">Message UID.</param>
            /// <returns>Returns specified message info IMAP 1-based sequence number or -1 if no such message.</returns>
            internal int GetSeqNo(long uid)
            {
                foreach(IMAP_MessageInfo msgInfo in m_pMessagesInfo){
                    if(msgInfo.UID == uid){
                        return msgInfo.SeqNo;
                    }
                }
                
                return -1;
            }

            #endregion

            #region method Reindex

            /// <summary>
            /// Reindexes messages sequence numbers.
            /// </summary>
            internal void Reindex()
            {
                for(int i=0;i<m_pMessagesInfo.Count;i++){
                    m_pMessagesInfo[i].SeqNo = i + 1;
                }
            }

            #endregion


            #region Properties implementation

            /// <summary>
            /// Gets folder name with optional path.
            /// </summary>
            public string Folder
            {
                get{ return m_Folder; }
            }

            /// <summary>
            /// Gets if folder is read-only.
            /// </summary>
            public bool IsReadOnly
            {
                get{ return m_IsReadOnly; }
            }

            /// <summary>
            /// Gets messages info.
            /// </summary>
            internal IMAP_MessageInfo[] MessagesInfo
            {
                get{ return m_pMessagesInfo.ToArray(); }
            }

            #endregion
        }

        #endregion

        #region class _CmdReader

        /// <summary>
        /// This class implements IMAP client command reader.
        /// </summary>
        /// <remarks>Because IMAP command can contain literal strings, then command text can be multiline.</remarks>
        private class _CmdReader
        {
            private IMAP_Session m_pSession        = null;
            private string       m_InitialCmdLine  = null;
            private Encoding     m_pCharset        = null;
            private int          m_MaxLiteralCount = 99;
            private int          m_LiteralCount    = 0;
            private string       m_CmdLine         = null;
            private int          m_MaxLiteralSize  = 32000;

            /// <summary>
            /// Default constructor.
            /// </summary>
            /// <param name="session">Owner IMAP session.</param>
            /// <param name="initialCmdLine">IMAP client initial command line.</param>
            /// <param name="charset">IMAP literal strings charset encoding.</param>
            /// <param name="maxLiteralCount">Maximum count string literal to read.</param>
            /// <exception cref="ArgumentNullException">Is raised when <b>session</b>,<b>initialCmdLine</b> or <b>charset</b> is null reference.</exception>
            public _CmdReader(IMAP_Session session,string initialCmdLine,Encoding charset,int maxLiteralCount)
            {    
                if(session == null){
                    throw new ArgumentNullException("session");
                }
                if(initialCmdLine == null){
                    throw new ArgumentNullException("initialCmdLine");
                }
                if(charset == null){
                    throw new ArgumentNullException("charset");
                }                
                if(maxLiteralCount < 1){
                    throw new ArgumentException("Tere must be at least 1 string literal !","maxLiteralCount");
                }

                m_pSession        = session;
                m_InitialCmdLine  = initialCmdLine;
                m_pCharset        = charset;
                m_MaxLiteralCount = maxLiteralCount;
            }


            #region method Start

            /// <summary>
            /// Start operation processing.
            /// </summary>
            public void Start()
            {
                /* RFC 3501.
                    literal = "{" number "}" CRLF *CHAR8
                              ; Number represents the number of CHAR8s
                */

                // TODO: Async
                // TODO: Limit total command size. 64k ? 

                
                // If initial command line ends with literal string, read literal string and remaining command text.
                if(EndsWithLiteralString(m_InitialCmdLine)){
                    StringBuilder cmdText     = new StringBuilder();
                    int           literalSize = GetLiteralSize(m_InitialCmdLine);

                    // Add initial command line part to command text.
                    cmdText.Append(RemoveLiteralSpecifier(m_InitialCmdLine));

                    SmartStream.ReadLineAsyncOP readLineOP = new SmartStream.ReadLineAsyncOP(new byte[32000],SizeExceededAction.JunkAndThrowException);
                    while(m_MaxLiteralCount > m_LiteralCount){
                        #region Read literal string

                        if(literalSize > m_MaxLiteralSize){
                            throw new DataSizeExceededException();
                        }

                        // Send "+ Continue".
                        m_pSession.WriteLine("+ Continue.");

                        // Read literal string.
                        MemoryStream msLiteral = new MemoryStream();
                        m_pSession.TcpStream.ReadFixedCount(msLiteral,literalSize);
                        
                        // Log
                        m_pSession.LogAddRead(literalSize,m_pCharset.GetString(msLiteral.ToArray()));

                        // Add to command text as quoted string.
                        cmdText.Append(TextUtils.QuoteString(m_pCharset.GetString(msLiteral.ToArray())));

                        #endregion
                        
                        #region Read continuing command text

                        // Read continuing command text.
                        m_pSession.TcpStream.ReadLine(readLineOP,false);

                        // We have error.
                        if(readLineOP.Error != null){
                            throw readLineOP.Error;
                        }
                        else{
                            string line = readLineOP.LineUtf8;

                            // Log
                            m_pSession.LogAddRead(readLineOP.BytesInBuffer,line);

                            // Add command line part to command text.
                            if(EndsWithLiteralString(line)){
                                cmdText.Append(RemoveLiteralSpecifier(line));
                            }
                            else{
                                cmdText.Append(line);
                            }

                            // No more literal string, we are done.
                            if(!EndsWithLiteralString(line)){
                                break;
                            }
                            else{
                                literalSize = GetLiteralSize(line);
                            }
                        }

                        #endregion
                    }

                    m_CmdLine = cmdText.ToString();
                    m_LiteralCount++;
                }
                // We have no literal string, so initial cmd line is final.
                else{
                    m_CmdLine = m_InitialCmdLine;
                }                
            }

            #endregion


            #region static method EndsWithLiteralString

            /// <summary>
            /// Cheks if specified value ends with IMAP literal string.
            /// </summary>
            /// <param name="value">Data value.</param>
            /// <returns>Returns true if value ends with IMAP literal string, otherwise false.</returns>
            /// <exception cref="ArgumentNullException">Is raised when <b>value</b> is null reference.</exception>
            public static bool EndsWithLiteralString(string value)
            {
                if(value == null){
                    throw new ArgumentNullException("value");
                }

                if(value.EndsWith("}")){
                    int    digitCount = 0;
                    char[] chars      = value.ToCharArray();
                    for(int i=chars.Length-2;i>=0;i--){
                        // We have literal string start tag.
                        if(chars[i] == '{'){
                            break;
                        }
                        // Literal string length specifier digit.
                        else if(char.IsDigit(chars[i])){
                            digitCount++;
                        }
                        // Not IMAP literal string char, so we don't have literal string.
                        else{
                            return false;
                        }
                    }

                    // We must have at least single digit literal string length specifier.
                    if(digitCount > 0){
                        return true;
                    }
                }

                return false;
            }

            #endregion


            #region method GetLiteralSize

            /// <summary>
            /// Gets literal string bytes count.
            /// </summary>
            /// <param name="cmdLine">Command line with ending literal string.</param>
            /// <returns>Returns literal string byte count.</returns>
            /// <exception cref="ArgumentNullException">Is raised when <b>cmdLine</b> is null reference.</exception>
            private int GetLiteralSize(string cmdLine)
            {
                if(cmdLine == null){
                    throw new ArgumentNullException("cmdLine");
                }

                return Convert.ToInt32(cmdLine.Substring(cmdLine.LastIndexOf('{') + 1,cmdLine.Length - cmdLine.LastIndexOf('{') - 2));
            }

            #endregion

            #region method RemoveLiteralSpecifier

            /// <summary>
            /// Removes literal string specifier({no_bytes}) from the specified string.
            /// </summary>
            /// <param name="value">Command line with ending literal string specifier.</param>
            /// <returns>Returns command line without literal string specifier.</returns>
            /// <exception cref="ArgumentNullException">Is raised when <b>value</b> is null reference.</exception>
            private string RemoveLiteralSpecifier(string value)
            {
                if(value == null){
                    throw new ArgumentNullException("value");
                }

                return value.Substring(0,value.LastIndexOf('{'));
            }

            #endregion


            #region Properties implementation

            /// <summary>
            /// Gets command line text.
            /// </summary>
            public string CmdLine
            {
                get{ return m_CmdLine;}
            }

            #endregion
        }

        #endregion

        #region class ResponseSender

        /// <summary>
        /// This class implements IMAP response sender.
        /// </summary>
        private class ResponseSender
        {
            #region class QueueItem

            /// <summary>
            /// This class represents queued IMAP response and it's status.
            /// </summary>
            private class QueueItem
            {
                private bool                               m_IsSent                  = false;
                private IMAP_r                             m_pResponse               = null;
                private EventHandler<EventArgs<Exception>> m_pCompletedAsyncCallback = null;

                /// <summary>
                /// Default constructor.
                /// </summary>
                /// <param name="response">IMAP response.</param>
                /// <param name="completedAsyncCallback">Callback to be called when response sending completes asynchronously.</param>
                /// <exception cref="ArgumentNullException">Is raised when <b>response</b> is null reference.</exception>
                public QueueItem(IMAP_r response,EventHandler<EventArgs<Exception>> completedAsyncCallback)
                {
                    if(response == null){
                        throw new ArgumentNullException("response");
                    }

                    m_pResponse               = response;
                    m_pCompletedAsyncCallback = completedAsyncCallback;
                }


                #region Properties implementation

                /// <summary>
                /// Gets or sets if IMAP response is sent.
                /// </summary>
                public bool IsSent
                {
                    get{ return m_IsSent; }

                    set{ m_IsSent = value; }
                }

                /// <summary>
                /// Gets IMAP response.
                /// </summary>
                public IMAP_r Response
                {
                    get{ return m_pResponse; }
                }

                /// <summary>
                /// Gets callback to be called when response sending completes asynchronously.
                /// </summary>
                public EventHandler<EventArgs<Exception>> CompletedAsyncCallback
                {
                    get{ return m_pCompletedAsyncCallback; }
                }

                #endregion
            }

            #endregion

            private object           m_pLock      = new object();
            private IMAP_Session     m_pImap      = null;
            private bool             m_IsSending  = false;
            private Queue<QueueItem> m_pResponses = null;

            /// <summary>
            /// Default constructor.
            /// </summary>
            /// <param name="session">Owner IMAP session.</param>
            /// <exception cref="ArgumentNullException">Is raised when <b>session</b> is null reference.</exception>
            public ResponseSender(IMAP_Session session)
            {
                if(session == null){
                    throw new ArgumentNullException("session");
                }

                m_pImap = session;

                m_pResponses = new Queue<QueueItem>();
            }

            #region method Dispose

            /// <summary>
            /// Cleans up any resources being used.
            /// </summary>
            public void Dispose()
            {
            }

            #endregion


            #region method SendResponseAsync

            /// <summary>
            /// Starts sending response.
            /// </summary>
            /// <param name="response">IMAP response.</param>
            /// <exception cref="ArgumentNullException">Is raised when <b>response</b> is null reference.</exception>
            public void SendResponseAsync(IMAP_r response)
            {
                if(response == null){
                    throw new ArgumentNullException("response");
                }

                SendResponseAsync(response,null);
            }

            /// <summary>
            /// Starts sending response.
            /// </summary>
            /// <param name="response">IMAP response.</param>
            /// <param name="completedAsyncCallback">Callback to be called when this method completes asynchronously.</param>
            /// <returns>Returns true is method completed asynchronously(the completedAsyncCallback is raised upon completion of the operation).
            /// Returns false if operation completed synchronously.</returns>
            /// <exception cref="ArgumentNullException">Is raised when <b>response</b> is null reference.</exception>
            public bool SendResponseAsync(IMAP_r response,EventHandler<EventArgs<Exception>> completedAsyncCallback)
            {
                if(response == null){
                    throw new ArgumentNullException("response");
                }

                QueueItem responseItem = new QueueItem(response,completedAsyncCallback);
                m_pResponses.Enqueue(responseItem);

                // Start sending response(s).
                SendResponsesAsync(false);

                return !responseItem.IsSent;
            }

            #endregion


            #region method SendResponsesAsync

            /// <summary>
            /// Starts sending queued responses.
            /// </summary>
            /// <param name="calledFromAsync">Specifies if this methd is called from asynchronous operation.</param>
            private void SendResponsesAsync(bool calledFromAsync)
            {
                lock(m_pLock){
                    if(m_IsSending || m_pResponses.Count == 0){
                        return;
                    }
                    m_IsSending = true;
                }

                QueueItem responseItem = null;

                // Create callback which is called when SendAsync completes asynchronously.
                EventHandler<EventArgs<Exception>> completedAsyncCallback = delegate(object s,EventArgs<Exception> e){
                    try{
                        // Call callback.
                        if(responseItem.CompletedAsyncCallback != null){
                            responseItem.CompletedAsyncCallback(this,e);
                        }

                        // We don't need lock here, we don't care if some Thread or our call to SendResponsesAsync continues responses sending.
                        m_IsSending = false;
                                                                        
                        // Continue sending queued responses, if any.
                        SendResponsesAsync(true);
                    }
                    catch(Exception x){                     
                        m_pImap.OnError(x);
                        m_IsSending = false;
                    }
                };

                try{
                    // Send responses.
                    while(m_pResponses.Count > 0){
                        responseItem = m_pResponses.Dequeue();

                        // Response sending completed asynchronously, completedAsyncCallback will be called when operation completes.
                        if(responseItem.Response.SendAsync(m_pImap,completedAsyncCallback)){
                            return;
                        }
                        // Response sending completed synchronously.
                        else{
                            responseItem.IsSent = true;

                            // This method(SendResponsesAsync) is called from completedAsyncCallback.
                            // Response sending has completed asynchronously, call callback.
                            if(calledFromAsync && responseItem.CompletedAsyncCallback != null){
                                responseItem.CompletedAsyncCallback(this,new EventArgs<Exception>(null));
                            }
                        }

                        lock(m_pLock){
                            if(m_pResponses.Count == 0){                    
                                m_IsSending = false;

                                return;
                            }
                        }
                    }
                }
                catch(Exception x){
                    m_pImap.OnError(x);
                    m_IsSending = false;
                }                
            }

            #endregion
        }

        #endregion

        private Dictionary<string,AUTH_SASL_ServerMechanism> m_pAuthentications = null;
        private bool                                         m_SessionRejected  = false;
        private int                                          m_BadCommands      = 0;
        private List<string>                                 m_pCapabilities    = null;
        private char                                         m_FolderSeparator  = '/';
        private GenericIdentity                              m_pUser            = null;
        private _SelectedFolder                              m_pSelectedFolder  = null;
        private IMAP_Mailbox_Encoding                        m_MailboxEncoding  = IMAP_Mailbox_Encoding.ImapUtf7;
        private ResponseSender                               m_pResponseSender  = null;

        /// <summary>
        /// Default constructor.
        /// </summary>
        public IMAP_Session()
        {
            m_pAuthentications = new Dictionary<string,AUTH_SASL_ServerMechanism>(StringComparer.CurrentCultureIgnoreCase);

            m_pCapabilities = new List<string>();
            m_pCapabilities.AddRange(new string[]{"IMAP4rev1","NAMESPACE","QUOTA","ACL","IDLE","ENABLE",/*"UTF8=ACCEPT",*/"SASL-IR"});

            m_pResponseSender = new ResponseSender(this);
        }

        #region override method Dispose

        /// <summary>
        /// Cleans up any resource being used.
        /// </summary>
        public override void Dispose()
        {
            if(this.IsDisposed){
                return;
            }
            base.Dispose();

            m_pAuthentications = null;
            m_pCapabilities = null;
            m_pUser = null;
            m_pSelectedFolder = null;
            if(m_pResponseSender != null){
                m_pResponseSender.Dispose();
            }

            // Release events
            this.Started         = null;
            this.Login           = null;
            this.Namespace       = null;
            this.List            = null;
            this.Create          = null;
            this.Delete          = null;
            this.Rename          = null;
            this.LSub            = null;
            this.Subscribe       = null;
            this.Unsubscribe     = null;
            this.Select          = null;
            this.GetMessagesInfo = null;
            this.Append          = null;
            this.GetQuotaRoot    = null;
            this.GetQuota        = null;
            this.GetAcl          = null;
            this.SetAcl          = null;
            this.DeleteAcl       = null;
            this.ListRights      = null;
            this.MyRights        = null;
            this.Fetch           = null;
            this.Search          = null;
            this.Store           = null;
            this.Copy            = null;
            this.Expunge         = null;
        }

        #endregion

        #region override method Start

        /// <summary>
        /// Starts session processing.
        /// </summary>
        protected override void Start()
        {
            base.Start();

            /* RFC 3501 7.1.1. Greeting text.
                The untagged form is also used as one of three possible greetings
                at connection startup.  It indicates that the connection is not
                yet authenticated and that a LOGIN command is needed.

                Example:    S: * OK IMAP4rev1 server ready
                            C: A001 LOGIN fred blurdybloop
                            S: * OK [ALERT] System shutdown in 10 minutes
                            S: A001 OK LOGIN Completed
            */
      
            try{
                IMAP_r_u_ServerStatus response = null;
                if(string.IsNullOrEmpty(this.Server.GreetingText)){
                    response = new IMAP_r_u_ServerStatus("OK","<" + Net_Utils.GetLocalHostName(this.LocalHostName) + "> IMAP4rev1 server ready.");
                }
                else{
                    response = new IMAP_r_u_ServerStatus("OK",this.Server.GreetingText);
                }
                
                IMAP_e_Started e = OnStarted(response);

                if(e.Response != null){
                    m_pResponseSender.SendResponseAsync(e.Response);
                }

                // Setup rejected flag, so we respond "* NO Session rejected." any command except LOGOUT.
                if(e.Response == null || e.Response.ResponseCode.Equals("NO",StringComparison.InvariantCultureIgnoreCase)){
                    m_SessionRejected = true;
                }
                               
                BeginReadCmd();
            }
            catch(Exception x){
                OnError(x);
            }
        }

        #endregion

        #region override method OnError

        /// <summary>
        /// Is called when session has processing error.
        /// </summary>
        /// <param name="x">Exception happened.</param>
        protected override void OnError(Exception x)
        {
            if(this.IsDisposed){
                return;
            }
            if(x == null){
                return;
            }

            /* Error handling:
                IO and Socket exceptions are permanent, so we must end session.
            */

            try{
                LogAddText("Exception: " + x.Message);

                // Permanent error.
                if(x is IOException || x is SocketException){
                    Dispose();
                }
                // xxx error, may be temporary.
                else{
                    base.OnError(x);

                    // Try to send "* BAD Internal server error."
                    try{
                        m_pResponseSender.SendResponseAsync(new IMAP_r_u_ServerStatus("BAD","Internal server error: " + x.Message));
                    }
                    catch{
                        // Error is permanent.
                        Dispose();
                    }
                }
            }
            catch{
            }
        }

        #endregion

        #region override method OnTimeout

        /// <summary>
        /// This method is called when specified session times out.
        /// </summary>
        /// <remarks>
        /// This method allows inhereted classes to report error message to connected client.
        /// Session will be disconnected after this method completes.
        /// </remarks>
        protected override void OnTimeout()
        {
            try{                
                WriteLine("* BYE Idle timeout, closing connection.");
            }
            catch{
                // Skip errors.
            }
        }

        #endregion


        #region method BeginReadCmd

        /// <summary>
        /// Starts reading incoming command from the connected client.
        /// </summary>
        private void BeginReadCmd()
        {
            if(this.IsDisposed){
                return;
            }

            try{
                SmartStream.ReadLineAsyncOP readLineOP = new SmartStream.ReadLineAsyncOP(new byte[32000],SizeExceededAction.JunkAndThrowException);
                // This event is raised only when read next command completes asynchronously.
                readLineOP.CompletedAsync += new EventHandler<EventArgs<SmartStream.ReadLineAsyncOP>>(delegate(object sender,EventArgs<SmartStream.ReadLineAsyncOP> e){                
                    if(ProcessCmd(readLineOP)){
                        BeginReadCmd();
                    }
                });
                // Process incoming commands while, command reading completes synchronously.
                while(this.TcpStream.ReadLine(readLineOP,true)){
                    if(!ProcessCmd(readLineOP)){
                        break;
                    }
                }
            }
            catch(Exception x){
                OnError(x);
            }
        }

        #endregion

        #region method ProcessCmd

        /// <summary>
        /// Completes command reading operation.
        /// </summary>
        /// <param name="op">Operation.</param>
        /// <returns>Returns true if server should start reading next command.</returns>
        private bool ProcessCmd(SmartStream.ReadLineAsyncOP op)
        {
            bool readNextCommand = true;
                        
            try{
                // We are disposed already.
                if(this.IsDisposed){
                    return false;
                }
                // Check errors.
                if(op.Error != null){
                    OnError(op.Error);
                }
                // Remote host shut-down(Socket.ShutDown) socket.
                if(op.BytesInBuffer == 0){
                    LogAddText("The remote host '" + this.RemoteEndPoint.ToString() + "' shut down socket.");
                    Dispose();
                
                    return false;
                }
                                                                
                string[] cmd_args = Encoding.UTF8.GetString(op.Buffer,0,op.LineBytesInBuffer).Split(new char[]{' '},3);
                if(cmd_args.Length < 2){
                    m_pResponseSender.SendResponseAsync(new IMAP_r_u_ServerStatus("BAD","Error: Command '" + op.LineUtf8 + "' not recognized."));

                    return true;
                }
                string   cmdTag   = cmd_args[0];
                string   cmd      = cmd_args[1].ToUpperInvariant();
                string   args     = cmd_args.Length == 3 ? cmd_args[2] : "";

                // Log.
                if(this.Server.Logger != null){
                    // Hide password from log.
                    if(cmd == "LOGIN"){                        
                        this.Server.Logger.AddRead(this.ID,this.AuthenticatedUserIdentity,op.BytesInBuffer,op.LineUtf8.Substring(0,op.LineUtf8.LastIndexOf(' ')) + " <***REMOVED***>",this.LocalEndPoint,this.RemoteEndPoint);
                    }
                    else{
                        this.Server.Logger.AddRead(this.ID,this.AuthenticatedUserIdentity,op.BytesInBuffer,op.LineUtf8,this.LocalEndPoint,this.RemoteEndPoint);
                    }
                }

                // Command line continues(ends with IMAP literal-string), read while we have full command line.
                // Skip APPEND command, we handle it specially.
                if(_CmdReader.EndsWithLiteralString(args) && !string.Equals(cmd,"APPEND",StringComparison.InvariantCultureIgnoreCase)){
                    _CmdReader cmdReader = new _CmdReader(this,args,Encoding.UTF8,99);            
                    cmdReader.Start();

                    args = cmdReader.CmdLine;
                }

                if(cmd == "STARTTLS"){                    
                    STARTTLS(cmdTag,args);
                    readNextCommand = false;
                }
                else if(cmd == "LOGIN"){
                    LOGIN(cmdTag,args);
                }
                else if(cmd == "AUTHENTICATE"){
                    AUTHENTICATE(cmdTag,args);
                }
                else if(cmd == "NAMESPACE"){
                    NAMESPACE(cmdTag,args);
                }
                else if(cmd == "LIST"){
                    LIST(cmdTag,args);
                }
                else if(cmd == "CREATE"){
                    CREATE(cmdTag,args);
                }
                else if(cmd == "DELETE"){
                    DELETE(cmdTag,args);
                }
                else if(cmd == "RENAME"){
                    RENAME(cmdTag,args);
                }
                else if(cmd == "LSUB"){
                    LSUB(cmdTag,args);
                }
                else if(cmd == "SUBSCRIBE"){
                    SUBSCRIBE(cmdTag,args);
                }
                else if(cmd == "UNSUBSCRIBE"){
                    UNSUBSCRIBE(cmdTag,args);
                }
                else if(cmd == "STATUS"){
                    STATUS(cmdTag,args);
                }
                else if(cmd == "SELECT"){
                    SELECT(cmdTag,args);
                }
                else if(cmd == "EXAMINE"){
                    EXAMINE(cmdTag,args);
                }
                else if(cmd == "APPEND"){
                    APPEND(cmdTag,args);

                    return false;
                }
                else if(cmd == "GETQUOTAROOT"){
                    GETQUOTAROOT(cmdTag,args);
                }
                else if(cmd == "GETQUOTA"){
                    GETQUOTA(cmdTag,args);
                }
                else if(cmd == "GETACL"){
                    GETACL(cmdTag,args);
                }
                else if(cmd == "SETACL"){
                    SETACL(cmdTag,args);
                }
                else if(cmd == "DELETEACL"){
                    DELETEACL(cmdTag,args);
                }
                else if(cmd == "LISTRIGHTS"){
                    LISTRIGHTS(cmdTag,args);
                }
                else if(cmd == "MYRIGHTS"){
                    MYRIGHTS(cmdTag,args);
                }
                else if(cmd == "ENABLE"){
                    ENABLE(cmdTag,args);
                }
                else if(cmd == "CHECK"){
                    CHECK(cmdTag,args);
                }
                else if(cmd == "CLOSE"){
                    CLOSE(cmdTag,args);
                }
                else if(cmd == "FETCH"){
                    FETCH(false,cmdTag,args);
                }
                else if(cmd == "SEARCH"){
                    SEARCH(false,cmdTag,args);
                }
                else if(cmd == "STORE"){
                    STORE(false,cmdTag,args);
                }
                else if(cmd == "COPY"){
                    COPY(false,cmdTag,args);
                }
                else if(cmd == "UID"){
                    UID(cmdTag,args);
                }
                else if(cmd == "EXPUNGE"){
                    EXPUNGE(cmdTag,args);
                }
                else if(cmd == "IDLE"){
                    readNextCommand = IDLE(cmdTag,args);
                }
                else if(cmd == "CAPABILITY"){
                    CAPABILITY(cmdTag,args);
                }
                else if(cmd == "NOOP"){
                    NOOP(cmdTag,args);
                }
                else if(cmd == "LOGOUT"){
                    LOGOUT(cmdTag,args);
                    readNextCommand = false;
                }
                else{
                    m_BadCommands++;

                    // Maximum allowed bad commands exceeded.
                    if(this.Server.MaxBadCommands != 0 && m_BadCommands > this.Server.MaxBadCommands){
                        WriteLine("* BYE Too many bad commands, closing transmission channel.");
                        Disconnect();

                        return false;
                    }
                   
                    m_pResponseSender.SendResponseAsync(new IMAP_r_ServerStatus(cmdTag,"BAD","Error: Command '" + cmd + "' not recognized."));
                }
             }
             catch(Exception x){
                 OnError(x);
             }

             return readNextCommand;
        }

        #endregion


        #region method STARTTLS

        private void STARTTLS(string cmdTag,string cmdText)
        {
            /* RFC 3501 6.2.1. STARTTLS Command.
                Arguments:  none

                Responses:  no specific response for this command

                Result:     OK - starttls completed, begin TLS negotiation
                            BAD - command unknown or arguments invalid

                A [TLS] negotiation begins immediately after the CRLF at the end
                of the tagged OK response from the server.  Once a client issues a
                STARTTLS command, it MUST NOT issue further commands until a
                server response is seen and the [TLS] negotiation is complete.

                The server remains in the non-authenticated state, even if client
                credentials are supplied during the [TLS] negotiation.  This does
                not preclude an authentication mechanism such as EXTERNAL (defined
                in [SASL]) from using client identity determined by the [TLS]
                negotiation.

                Once [TLS] has been started, the client MUST discard cached
                information about server capabilities and SHOULD re-issue the
                CAPABILITY command.  This is necessary to protect against man-in-
                the-middle attacks which alter the capabilities list prior to
                STARTTLS.  The server MAY advertise different capabilities after
                STARTTLS.

                Example:    C: a001 CAPABILITY
                            S: * CAPABILITY IMAP4rev1 STARTTLS LOGINDISABLED
                            S: a001 OK CAPABILITY completed
                            C: a002 STARTTLS
                            S: a002 OK Begin TLS negotiation now
                            <TLS negotiation, further commands are under [TLS] layer>
                            C: a003 CAPABILITY
                            S: * CAPABILITY IMAP4rev1 AUTH=PLAIN
                            S: a003 OK CAPABILITY completed
                            C: a004 LOGIN joe password
                            S: a004 OK LOGIN completed
            */

            if(m_SessionRejected){
                m_pResponseSender.SendResponseAsync(new IMAP_r_ServerStatus(cmdTag,"NO","Bad sequence of commands: Session rejected."));

                return;
            }
            if(this.IsAuthenticated){
                m_pResponseSender.SendResponseAsync(new IMAP_r_ServerStatus(cmdTag,"NO","This ommand is only valid in not-authenticated state."));

                return;
            }
            if(this.IsSecureConnection){
                m_pResponseSender.SendResponseAsync(new IMAP_r_ServerStatus(cmdTag,"NO","Bad sequence of commands: Connection is already secure."));

                return;
            }
            if(this.Certificate == null){
                m_pResponseSender.SendResponseAsync(new IMAP_r_ServerStatus(cmdTag,"NO","TLS not available: Server has no SSL certificate."));

                return;
            }

            m_pResponseSender.SendResponseAsync(new IMAP_r_ServerStatus(cmdTag,"OK","Begin TLS negotiation now."));
            
            try{
                DateTime startTime = DateTime.Now;

                // Create delegate which is called when SwitchToSecureAsync has completed.
                Action<SwitchToSecureAsyncOP> switchSecureCompleted = delegate(SwitchToSecureAsyncOP e){
                    try{
                        // Operation failed.
                        if(e.Error != null){
                            LogAddException(e.Error);
                            Disconnect();
                        }
                        // Operation suceeded.
                        else{
                            // Log
                            LogAddText("SSL negotiation completed successfully in " + (DateTime.Now - startTime).TotalSeconds.ToString("f2") + " seconds.");

                            BeginReadCmd();
                        }
                    }
                    catch(Exception x){
                        LogAddException(x);
                        Disconnect();
                    }
                };

                SwitchToSecureAsyncOP op = new SwitchToSecureAsyncOP();
                op.CompletedAsync += delegate(object sender,EventArgs<TCP_ServerSession.SwitchToSecureAsyncOP> e){
                    switchSecureCompleted(op);
                };
                // Switch to secure completed synchronously.
                if(!SwitchToSecureAsync(op)){
                    switchSecureCompleted(op);
                }
            }
            catch(Exception x){
                LogAddException(x);
                Disconnect();
            }
        }

        #endregion

        #region method LOGIN

        private void LOGIN(string cmdTag,string cmdText)
        {
            /* RFC 3501 6.2.3. LOGIN Command.
                Arguments:  user name
                            password

                Responses:  no specific responses for this command

                Result:     OK - login completed, now in authenticated state
                            NO - login failure: user name or password rejected
                            BAD - command unknown or arguments invalid

                The LOGIN command identifies the client to the server and carries
                the plaintext password authenticating this user.

                A server MAY include a CAPABILITY response code in the tagged OK
                response to a successful LOGIN command in order to send
                capabilities automatically.  It is unnecessary for a client to
                send a separate CAPABILITY command if it recognizes these
                automatic capabilities.

                Example:    C: a001 LOGIN SMITH SESAME
                            S: a001 OK LOGIN completed

                    Note: Use of the LOGIN command over an insecure network
                    (such as the Internet) is a security risk, because anyone
                    monitoring network traffic can obtain plaintext passwords.
                    The LOGIN command SHOULD NOT be used except as a last
                    resort, and it is recommended that client implementations
                    have a means to disable any automatic use of the LOGIN
                    command.

                Unless either the STARTTLS command has been negotiated or
                some other mechanism that protects the session from
                password snooping has been provided, a server
                implementation MUST implement a configuration in which it
                advertises the LOGINDISABLED capability and does NOT permit
                the LOGIN command.  Server sites SHOULD NOT use any
                configuration which permits the LOGIN command without such
                a protection mechanism against password snooping.  A client
                implementation MUST NOT send a LOGIN command if the
                LOGINDISABLED capability is advertised.
            */

            if(m_SessionRejected){
                m_pResponseSender.SendResponseAsync(new IMAP_r_ServerStatus(cmdTag,"NO","Bad sequence of commands: Session rejected."));

                return;
            }
            if(this.IsAuthenticated){
                m_pResponseSender.SendResponseAsync(new IMAP_r_ServerStatus(cmdTag,"NO","Re-authentication error."));

                return;
            }
            if(SupportsCap("LOGINDISABLED")){
                m_pResponseSender.SendResponseAsync(new IMAP_r_ServerStatus(cmdTag,"NO","Command 'LOGIN' is disabled, use AUTHENTICATE instead."));

                return;
            }
            if(string.IsNullOrEmpty(cmdText)){
                m_pResponseSender.SendResponseAsync(new IMAP_r_ServerStatus(cmdTag,"BAD","Error in arguments."));

                return;
            }

            string[] user_pass = TextUtils.SplitQuotedString(cmdText,' ',true);
            if(user_pass.Length != 2){
                m_pResponseSender.SendResponseAsync(new IMAP_r_ServerStatus(cmdTag,"BAD","Error in arguments."));

                return;
            }

            IMAP_e_Login e = OnLogin(user_pass[0],user_pass[1]);
            if(e.IsAuthenticated){
                m_pUser = new GenericIdentity(user_pass[0],"IMAP-LOGIN");

                m_pResponseSender.SendResponseAsync(new IMAP_r_ServerStatus(cmdTag,"OK","LOGIN completed."));
            }
            else{
                m_pResponseSender.SendResponseAsync(new IMAP_r_ServerStatus(cmdTag,"NO","LOGIN failed."));
            }
        }

        #endregion

        #region method AUTHENTICATE

        private void AUTHENTICATE(string cmdTag,string cmdText)
        {
            /* RFC 3501 6.2.2. AUTHENTICATE Command.
                Arguments:  authentication mechanism name

                Responses:  continuation data can be requested

                Result:     OK - authenticate completed, now in authenticated state
                            NO - authenticate failure: unsupported authentication
                                 mechanism, credentials rejected
                            BAD - command unknown or arguments invalid,
                                  authentication exchange cancelled

                The AUTHENTICATE command indicates a [SASL] authentication
                mechanism to the server.  If the server supports the requested
                authentication mechanism, it performs an authentication protocol
                exchange to authenticate and identify the client.  It MAY also
                negotiate an OPTIONAL security layer for subsequent protocol
                interactions.  If the requested authentication mechanism is not
                supported, the server SHOULD reject the AUTHENTICATE command by
                sending a tagged NO response.

                The AUTHENTICATE command does not support the optional "initial
                response" feature of [SASL].  Section 5.1 of [SASL] specifies how
                to handle an authentication mechanism which uses an initial
                response.

                The service name specified by this protocol's profile of [SASL] is
                "imap".

                The authentication protocol exchange consists of a series of
                server challenges and client responses that are specific to the
                authentication mechanism.  A server challenge consists of a
                command continuation request response with the "+" token followed
                by a BASE64 encoded string.  The client response consists of a
                single line consisting of a BASE64 encoded string.  If the client
                wishes to cancel an authentication exchange, it issues a line
                consisting of a single "*".  If the server receives such a
                response, it MUST reject the AUTHENTICATE command by sending a
                tagged BAD response.

                If a security layer is negotiated through the [SASL]
                authentication exchange, it takes effect immediately following the
                CRLF that concludes the authentication exchange for the client,
                and the CRLF of the tagged OK response for the server.

                While client and server implementations MUST implement the
                AUTHENTICATE command itself, it is not required to implement any
                authentication mechanisms other than the PLAIN mechanism described
                in [IMAP-TLS].  Also, an authentication mechanism is not required
                to support any security layers.

                    Note: a server implementation MUST implement a
                    configuration in which it does NOT permit any plaintext
                    password mechanisms, unless either the STARTTLS command
                    has been negotiated or some other mechanism that
                    protects the session from password snooping has been
                    provided.  Server sites SHOULD NOT use any configuration
                    which permits a plaintext password mechanism without
                    such a protection mechanism against password snooping.
                    Client and server implementations SHOULD implement
                    additional [SASL] mechanisms that do not use plaintext
                    passwords, such the GSSAPI mechanism described in [SASL]
                    and/or the [DIGEST-MD5] mechanism.

                Servers and clients can support multiple authentication
                mechanisms.  The server SHOULD list its supported authentication
                mechanisms in the response to the CAPABILITY command so that the
                client knows which authentication mechanisms to use.

                A server MAY include a CAPABILITY response code in the tagged OK
                response of a successful AUTHENTICATE command in order to send
                capabilities automatically.  It is unnecessary for a client to
                send a separate CAPABILITY command if it recognizes these
                automatic capabilities.  This should only be done if a security
                layer was not negotiated by the AUTHENTICATE command, because the
                tagged OK response as part of an AUTHENTICATE command is not
                protected by encryption/integrity checking.  [SASL] requires the
                client to re-issue a CAPABILITY command in this case.
                            
                The authorization identity passed from the client to the server
                during the authentication exchange is interpreted by the server as
                the user name whose privileges the client is requesting.

                Example:    S: * OK IMAP4rev1 Server
                            C: A001 AUTHENTICATE GSSAPI
                            S: +
                            C: YIIB+wYJKoZIhvcSAQICAQBuggHqMIIB5qADAgEFoQMCAQ6iBw
                               MFACAAAACjggEmYYIBIjCCAR6gAwIBBaESGxB1Lndhc2hpbmd0
                               b24uZWR1oi0wK6ADAgEDoSQwIhsEaW1hcBsac2hpdmFtcy5jYW
                               Mud2FzaGluZ3Rvbi5lZHWjgdMwgdCgAwIBAaEDAgEDooHDBIHA
                               cS1GSa5b+fXnPZNmXB9SjL8Ollj2SKyb+3S0iXMljen/jNkpJX
                               AleKTz6BQPzj8duz8EtoOuNfKgweViyn/9B9bccy1uuAE2HI0y
                               C/PHXNNU9ZrBziJ8Lm0tTNc98kUpjXnHZhsMcz5Mx2GR6dGknb
                               I0iaGcRerMUsWOuBmKKKRmVMMdR9T3EZdpqsBd7jZCNMWotjhi
                               vd5zovQlFqQ2Wjc2+y46vKP/iXxWIuQJuDiisyXF0Y8+5GTpAL
                               pHDc1/pIGmMIGjoAMCAQGigZsEgZg2on5mSuxoDHEA1w9bcW9n
                               FdFxDKpdrQhVGVRDIzcCMCTzvUboqb5KjY1NJKJsfjRQiBYBdE
                               NKfzK+g5DlV8nrw81uOcP8NOQCLR5XkoMHC0Dr/80ziQzbNqhx
                               O6652Npft0LQwJvenwDI13YxpwOdMXzkWZN/XrEqOWp6GCgXTB
                               vCyLWLlWnbaUkZdEYbKHBPjd8t/1x5Yg==
                            S: + YGgGCSqGSIb3EgECAgIAb1kwV6ADAgEFoQMCAQ+iSzBJoAMC
                               AQGiQgRAtHTEuOP2BXb9sBYFR4SJlDZxmg39IxmRBOhXRKdDA0
                                uHTCOT9Bq3OsUTXUlk0CsFLoa8j+gvGDlgHuqzWHPSQg==
                            C:
                            S: + YDMGCSqGSIb3EgECAgIBAAD/////6jcyG4GE3KkTzBeBiVHe
                               ceP2CWY0SR0fAQAgAAQEBAQ=
                            C: YDMGCSqGSIb3EgECAgIBAAD/////3LQBHXTpFfZgrejpLlLImP
                               wkhbfa2QteAQAgAG1yYwE=
                            S: A001 OK GSSAPI authentication successful

                            Note: The line breaks within server challenges and client
                            responses are for editorial clarity and are not in real
                            authenticators.
            */

            /* RFC 4959.l7 SASL-IR 7.
                The following syntax specification uses the Augmented Backus-Naur
                Form [RFC4234] notation.  [RFC3501] defines the non-terminals
                capability, auth-type, and base64.

                capability    =/ "SASL-IR"

                authenticate  = "AUTHENTICATE" SP auth-type [SP (base64 / "=")]
                                *(CRLF base64)
                                ;;redefine AUTHENTICATE from [RFC3501]
            
            */

            if(m_SessionRejected){
                m_pResponseSender.SendResponseAsync(new IMAP_r_ServerStatus(cmdTag,"NO","Bad sequence of commands: Session rejected."));

                return;
            }
            if(this.IsAuthenticated){
                m_pResponseSender.SendResponseAsync(new IMAP_r_ServerStatus(cmdTag,"NO","Re-authentication error."));

                return;
            }

            #region Parse parameters

            string[] arguments = cmdText.Split(' ');
            if(arguments.Length > 2){
                m_pResponseSender.SendResponseAsync(new IMAP_r_ServerStatus(cmdTag,"BAD","Error in arguments."));

                return;
            }
            byte[] initialClientResponse = new byte[0];
            if(arguments.Length == 2){
                if(arguments[1] == "="){
                    // Skip.
                }
                else{
                    try{
                        initialClientResponse = Convert.FromBase64String(arguments[1]);
                    }
                    catch{
                        m_pResponseSender.SendResponseAsync(new IMAP_r_ServerStatus(cmdTag,"BAD","Syntax error: Parameter 'initial-response' value must be BASE64 or contain a single character '='."));

                        return;
                    }
                }
            }
            string mechanism = arguments[0];

            #endregion
                        
            if(!this.Authentications.ContainsKey(mechanism)){
                m_pResponseSender.SendResponseAsync(new IMAP_r_ServerStatus(cmdTag,"NO","Not supported authentication mechanism."));

                return;
            }

            byte[] clientResponse = initialClientResponse;
            AUTH_SASL_ServerMechanism auth = this.Authentications[mechanism];            
            auth.Reset();
            while(true){
                byte[] serverResponse = auth.Continue(clientResponse);
                // Authentication completed.
                if(auth.IsCompleted){
                    if(auth.IsAuthenticated){
                        m_pUser = new GenericIdentity(auth.UserName,"SASL-" + auth.Name);
                              
                        m_pResponseSender.SendResponseAsync(new IMAP_r_ServerStatus(cmdTag,"OK","Authentication succeeded."));
                    }
                    else{
                        m_pResponseSender.SendResponseAsync(new IMAP_r_ServerStatus(cmdTag,"NO","Authentication credentials invalid."));
                    }
                    break;
                }
                // Authentication continues.
                else{
                    // Send server challenge.
                    if(serverResponse.Length == 0){
                        m_pResponseSender.SendResponseAsync(new IMAP_r_ServerStatus("+",""));
                    }
                    else{
                        m_pResponseSender.SendResponseAsync(new IMAP_r_ServerStatus("+",Convert.ToBase64String(serverResponse)));
                    }

                    // Read client response. 
                    SmartStream.ReadLineAsyncOP readLineOP = new SmartStream.ReadLineAsyncOP(new byte[32000],SizeExceededAction.JunkAndThrowException);
                    this.TcpStream.ReadLine(readLineOP,false);
                    if(readLineOP.Error != null){
                        throw readLineOP.Error;
                    }
                    // Log
                    if(this.Server.Logger != null){
                        this.Server.Logger.AddRead(this.ID,this.AuthenticatedUserIdentity,readLineOP.BytesInBuffer,"base64 auth-data",this.LocalEndPoint,this.RemoteEndPoint);
                    }

                    // Client canceled authentication.
                    if(readLineOP.LineUtf8 == "*"){
                        m_pResponseSender.SendResponseAsync(new IMAP_r_ServerStatus(cmdTag,"NO","Authentication canceled."));

                        return;
                    }
                    // We have base64 client response, decode it.
                    else{
                        try{
                            clientResponse = Convert.FromBase64String(readLineOP.LineUtf8);
                        }
                        catch{
                            m_pResponseSender.SendResponseAsync(new IMAP_r_ServerStatus(cmdTag,"NO","Invalid client response '" + clientResponse + "'."));

                            return;
                        }
                    }
                }
            }
        }

        #endregion


        #region method NAMESPACE

        private void NAMESPACE(string cmdTag,string cmdText)
        {
            /* RFC 2342 5. NAMESPACE Command.
                Arguments: none

                Response:  an untagged NAMESPACE response that contains the prefix
                           and hierarchy delimiter to the server's Personal
                           Namespace(s), Other Users' Namespace(s), and Shared
                           Namespace(s) that the server wishes to expose. The
                           response will contain a NIL for any namespace class
                           that is not available. Namespace_Response_Extensions
                           MAY be included in the response.
                           Namespace_Response_Extensions which are not on the IETF
                           standards track, MUST be prefixed with an "X-".

                Result:    OK - Command completed
                           NO - Error: Can't complete command
                           BAD - argument invalid
                
                Example:
                    < A server that contains a Personal Namespace and a single Shared Namespace. >

                    C: A001 NAMESPACE
                    S: * NAMESPACE (("" "/")) NIL (("Public Folders/" "/"))
                    S: A001 OK NAMESPACE command completed
            */

            if(!SupportsCap("NAMESPACE")){
                m_pResponseSender.SendResponseAsync(new IMAP_r_ServerStatus(cmdTag,"NO","Command not supported."));

                return;
            }
            if(!this.IsAuthenticated){
                m_pResponseSender.SendResponseAsync(new IMAP_r_ServerStatus(cmdTag,"NO","Authentication required."));

                return;
            }

            IMAP_e_Namespace e = OnNamespace(new IMAP_r_ServerStatus(cmdTag,"OK","NAMESPACE command completed."));
            if(e.NamespaceResponse != null){
                m_pResponseSender.SendResponseAsync(e.NamespaceResponse);
            }
            m_pResponseSender.SendResponseAsync(e.Response);
        }

        #endregion

        #region method LIST

        private void LIST(string cmdTag,string cmdText)
        {
            /* RFC 3501 6.3.8. LIST Command.
                Arguments:  reference name
                            mailbox name with possible wildcards

                Responses:  untagged responses: LIST

                Result:     OK - list completed
                            NO - list failure: can't list that reference or name
                            BAD - command unknown or arguments invalid

                The LIST command returns a subset of names from the complete set
                of all names available to the client.  Zero or more untagged LIST
                replies are returned, containing the name attributes, hierarchy
                delimiter, and name; see the description of the LIST reply for
                more detail.

                The LIST command SHOULD return its data quickly, without undue
                delay.  For example, it SHOULD NOT go to excess trouble to
                calculate the \Marked or \Unmarked status or perform other
                processing; if each name requires 1 second of processing, then a
                list of 1200 names would take 20 minutes!

                An empty ("" string) reference name argument indicates that the
                mailbox name is interpreted as by SELECT.  The returned mailbox
                names MUST match the supplied mailbox name pattern.  A non-empty
                reference name argument is the name of a mailbox or a level of
                mailbox hierarchy, and indicates the context in which the mailbox
                name is interpreted.

                An empty ("" string) mailbox name argument is a special request to
                return the hierarchy delimiter and the root name of the name given
                in the reference.  The value returned as the root MAY be the empty
                string if the reference is non-rooted or is an empty string.  In
                all cases, a hierarchy delimiter (or NIL if there is no hierarchy)
                is returned.  This permits a client to get the hierarchy delimiter
                (or find out that the mailbox names are flat) even when no
                mailboxes by that name currently exist.

                The reference and mailbox name arguments are interpreted into a
                canonical form that represents an unambiguous left-to-right
                hierarchy.  The returned mailbox names will be in the interpreted
                form.

                    Note: The interpretation of the reference argument is
                    implementation-defined.  It depends upon whether the
                    server implementation has a concept of the "current
                    working directory" and leading "break out characters",
                    which override the current working directory.

                    For example, on a server which exports a UNIX or NT
                    filesystem, the reference argument contains the current
                    working directory, and the mailbox name argument would
                    contain the name as interpreted in the current working
                    directory.

                    If a server implementation has no concept of break out
                    characters, the canonical form is normally the reference
                    name appended with the mailbox name.  Note that if the
                    server implements the namespace convention (section
                    5.1.2), "#" is a break out character and must be treated
                    as such.

                    If the reference argument is not a level of mailbox
                    hierarchy (that is, it is a \NoInferiors name), and/or
                    the reference argument does not end with the hierarchy
                    delimiter, it is implementation-dependent how this is
                    interpreted.  For example, a reference of "foo/bar" and
                    mailbox name of "rag/baz" could be interpreted as
                    "foo/bar/rag/baz", "foo/barrag/baz", or "foo/rag/baz".
                    A client SHOULD NOT use such a reference argument except
                    at the explicit request of the user.  A hierarchical
                    browser MUST NOT make any assumptions about server
                    interpretation of the reference unless the reference is
                    a level of mailbox hierarchy AND ends with the hierarchy
                    delimiter.

                Any part of the reference argument that is included in the
                interpreted form SHOULD prefix the interpreted form.  It SHOULD
                also be in the same form as the reference name argument.  This
                rule permits the client to determine if the returned mailbox name
                is in the context of the reference argument, or if something about
                the mailbox argument overrode the reference argument.  Without
                this rule, the client would have to have knowledge of the server's
                naming semantics including what characters are "breakouts" that
                override a naming context.

                    For example, here are some examples of how references
                    and mailbox names might be interpreted on a UNIX-based
                    server:

                        Reference     Mailbox Name  Interpretation
                        ------------  ------------  --------------
                        ~smith/Mail/  foo.*         ~smith/Mail/foo.*
                        archive/      %             archive/%
                        #news.        comp.mail.*   #news.comp.mail.*
                        ~smith/Mail/  /usr/doc/foo  /usr/doc/foo
                        archive/      ~fred/Mail/*  ~fred/Mail/*

                    The first three examples demonstrate interpretations in
                    the context of the reference argument.  Note that
                    "~smith/Mail" SHOULD NOT be transformed into something
                    like "/u2/users/smith/Mail", or it would be impossible
                    for the client to determine that the interpretation was
                    in the context of the reference.

                The character "*" is a wildcard, and matches zero or more
                characters at this position.  The character "%" is similar to "*",
                but it does not match a hierarchy delimiter.  If the "%" wildcard
                is the last character of a mailbox name argument, matching levels
                of hierarchy are also returned.  If these levels of hierarchy are
                not also selectable mailboxes, they are returned with the
                \Noselect mailbox name attribute (see the description of the LIST
                response for more details).

                Server implementations are permitted to "hide" otherwise
                accessible mailboxes from the wildcard characters, by preventing
                certain characters or names from matching a wildcard in certain
                situations.  For example, a UNIX-based server might restrict the
                interpretation of "*" so that an initial "/" character does not
                match.

                The special name INBOX is included in the output from LIST, if
                INBOX is supported by this server for this user and if the
                uppercase string "INBOX" matches the interpreted reference and
                mailbox name arguments with wildcards as described above.  The
                criteria for omitting INBOX is whether SELECT INBOX will return
                failure; it is not relevant whether the user's real INBOX resides
                on this or some other server.

                Example:    C: A101 LIST "" ""
                            S: * LIST (\Noselect) "/" ""
                            S: A101 OK LIST Completed
                            C: A102 LIST #news.comp.mail.misc ""
                            S: * LIST (\Noselect) "." #news.
                            S: A102 OK LIST Completed
                            C: A103 LIST /usr/staff/jones ""
                            S: * LIST (\Noselect) "/" /
                            S: A103 OK LIST Completed
                            C: A202 LIST ~/Mail/ %
                            S: * LIST (\Noselect) "/" ~/Mail/foo
                            S: * LIST () "/" ~/Mail/meetings
                            S: A202 OK LIST completed
            */

            if(!this.IsAuthenticated){
                m_pResponseSender.SendResponseAsync(new IMAP_r_ServerStatus(cmdTag,"NO","Authentication required."));

                return;
            }

            string[] parts = TextUtils.SplitQuotedString(cmdText,' ',true);
            if(parts.Length != 2){
                m_pResponseSender.SendResponseAsync(new IMAP_r_ServerStatus(cmdTag,"BAD","Error in arguments."));

                return;
            }

            string refName = IMAP_Utils.DecodeMailbox(parts[0]);
            string folder  = IMAP_Utils.DecodeMailbox(parts[1]);

            // Store start time
			long startTime = DateTime.Now.Ticks;

            // Folder separator request.
            if(folder == string.Empty){
                m_pResponseSender.SendResponseAsync(new IMAP_r_u_List(m_FolderSeparator));
            }
            else{
                IMAP_e_List e = OnList(refName,folder);
                foreach(IMAP_r_u_List r in e.Folders){
                    m_pResponseSender.SendResponseAsync(r);
                }
            }

            m_pResponseSender.SendResponseAsync(new IMAP_r_ServerStatus(cmdTag,"OK","LIST Completed in " + ((DateTime.Now.Ticks - startTime) / (decimal)10000000).ToString("f2") + " seconds."));
        }

        #endregion

        #region method CREATE

        private void CREATE(string cmdTag,string cmdText)
        {
            /* RFC 3501 6.3.3. CREATE Command.
                Arguments:  mailbox name

                Responses:  no specific responses for this command

                Result:     OK - create completed
                            NO - create failure: can't create mailbox with that name
                            BAD - command unknown or arguments invalid

                The CREATE command creates a mailbox with the given name.  An OK
                response is returned only if a new mailbox with that name has been
                created.  It is an error to attempt to create INBOX or a mailbox
                with a name that refers to an extant mailbox.  Any error in
                creation will return a tagged NO response.

                If the mailbox name is suffixed with the server's hierarchy
                separator character (as returned from the server by a LIST
                command), this is a declaration that the client intends to create
                mailbox names under this name in the hierarchy.  Server
                implementations that do not require this declaration MUST ignore
                the declaration.  In any case, the name created is without the
                trailing hierarchy delimiter.

                If the server's hierarchy separator character appears elsewhere in
                the name, the server SHOULD create any superior hierarchical names
                that are needed for the CREATE command to be successfully
                completed.  In other words, an attempt to create "foo/bar/zap" on
                a server in which "/" is the hierarchy separator character SHOULD
                create foo/ and foo/bar/ if they do not already exist.

                If a new mailbox is created with the same name as a mailbox which
                was deleted, its unique identifiers MUST be greater than any
                unique identifiers used in the previous incarnation of the mailbox
                UNLESS the new incarnation has a different unique identifier
                validity value.  See the description of the UID command for more
                detail.

                Example:    C: A003 CREATE owatagusiam/
                            S: A003 OK CREATE completed
                            C: A004 CREATE owatagusiam/blurdybloop
                            S: A004 OK CREATE completed

                    Note: The interpretation of this example depends on whether
                    "/" was returned as the hierarchy separator from LIST.  If
                    "/" is the hierarchy separator, a new level of hierarchy
                    named "owatagusiam" with a member called "blurdybloop" is
                    created.  Otherwise, two mailboxes at the same hierarchy
                    level are created.
            */

            if(!this.IsAuthenticated){
                m_pResponseSender.SendResponseAsync(new IMAP_r_ServerStatus(cmdTag,"NO","Authentication required."));

                return;
            }

            string folder = IMAP_Utils.DecodeMailbox(TextUtils.UnQuoteString(cmdText));
            
            IMAP_e_Folder e = OnCreate(cmdTag,folder,new IMAP_r_ServerStatus(cmdTag,"OK","CREATE command completed."));

            m_pResponseSender.SendResponseAsync(e.Response);
        }

        #endregion

        #region method DELETE

        private void DELETE(string cmdTag,string cmdText)
        {
            /* RFC 3501 6.3.4. DELETE Command.
                Arguments:  mailbox name

                Responses:  no specific responses for this command

                Result:     OK - delete completed
                            NO - delete failure: can't delete mailbox with that name
                            BAD - command unknown or arguments invalid

                The DELETE command permanently removes the mailbox with the given
                name.  A tagged OK response is returned only if the mailbox has
                been deleted.  It is an error to attempt to delete INBOX or a
                mailbox name that does not exist.

                The DELETE command MUST NOT remove inferior hierarchical names.
                For example, if a mailbox "foo" has an inferior "foo.bar"
                (assuming "." is the hierarchy delimiter character), removing
                "foo" MUST NOT remove "foo.bar".  It is an error to attempt to
                delete a name that has inferior hierarchical names and also has
                the \Noselect mailbox name attribute (see the description of the
                LIST response for more details).

                It is permitted to delete a name that has inferior hierarchical
                names and does not have the \Noselect mailbox name attribute.  In
                this case, all messages in that mailbox are removed, and the name
                will acquire the \Noselect mailbox name attribute.

                The value of the highest-used unique identifier of the deleted
                mailbox MUST be preserved so that a new mailbox created with the
                same name will not reuse the identifiers of the former
                incarnation, UNLESS the new incarnation has a different unique
                identifier validity value.  See the description of the UID command
                for more detail.

                Examples:   C: A682 LIST "" *
                            S: * LIST () "/" blurdybloop
                            S: * LIST (\Noselect) "/" foo
                            S: * LIST () "/" foo/bar
                            S: A682 OK LIST completed
                            C: A683 DELETE blurdybloop
                            S: A683 OK DELETE completed
                            C: A684 DELETE foo
                            S: A684 NO Name "foo" has inferior hierarchical names
                            C: A685 DELETE foo/bar
                            S: A685 OK DELETE Completed
                            C: A686 LIST "" *
                            S: * LIST (\Noselect) "/" foo
                            S: A686 OK LIST completed
                            C: A687 DELETE foo
                            S: A687 OK DELETE Completed
            */

            if(!this.IsAuthenticated){
                m_pResponseSender.SendResponseAsync(new IMAP_r_ServerStatus(cmdTag,"NO","Authentication required."));

                return;
            }

            string folder = IMAP_Utils.DecodeMailbox(cmdText);
            
            IMAP_e_Folder e = OnDelete(cmdTag,folder,new IMAP_r_ServerStatus(cmdTag,"OK","DELETE command completed."));

            m_pResponseSender.SendResponseAsync(e.Response);
        }

        #endregion

        #region method RENAME

        private void RENAME(string cmdTag,string cmdText)
        {
            /* RFC 3501 6.3.5. RENAME Command.
                Arguments:  existing mailbox name
                            new mailbox name

                Responses:  no specific responses for this command

                Result:     OK - rename completed
                            NO - rename failure: can't rename mailbox with that name,
                                 can't rename to mailbox with that name
                            BAD - command unknown or arguments invalid

                The RENAME command changes the name of a mailbox.  A tagged OK
                response is returned only if the mailbox has been renamed.  It is
                an error to attempt to rename from a mailbox name that does not
                exist or to a mailbox name that already exists.  Any error in
                renaming will return a tagged NO response.

                If the name has inferior hierarchical names, then the inferior
                hierarchical names MUST also be renamed.  For example, a rename of
                "foo" to "zap" will rename "foo/bar" (assuming "/" is the
                hierarchy delimiter character) to "zap/bar".

                If the server's hierarchy separator character appears in the name,
                the server SHOULD create any superior hierarchical names that are
                needed for the RENAME command to complete successfully.  In other
                words, an attempt to rename "foo/bar/zap" to baz/rag/zowie on a
                server in which "/" is the hierarchy separator character SHOULD
                create baz/ and baz/rag/ if they do not already exist.

                The value of the highest-used unique identifier of the old mailbox
                name MUST be preserved so that a new mailbox created with the same
                name will not reuse the identifiers of the former incarnation,
                UNLESS the new incarnation has a different unique identifier
                validity value.  See the description of the UID command for more
                detail.

                Renaming INBOX is permitted, and has special behavior.  It moves
                all messages in INBOX to a new mailbox with the given name,
                leaving INBOX empty.  If the server implementation supports
                inferior hierarchical names of INBOX, these are unaffected by a
                rename of INBOX.

                Examples:   C: A682 LIST "" *
                            S: * LIST () "/" blurdybloop
                            S: * LIST (\Noselect) "/" foo
                            S: * LIST () "/" foo/bar
                            S: A682 OK LIST completed
                            C: A683 RENAME blurdybloop sarasoop
                            S: A683 OK RENAME completed
                            C: A684 RENAME foo zowie
                            S: A684 OK RENAME Completed
                            C: A685 LIST "" *
                            S: * LIST () "/" sarasoop
                            S: * LIST (\Noselect) "/" zowie
                            S: * LIST () "/" zowie/bar
                            S: A685 OK LIST completed
            */

            if(!this.IsAuthenticated){
                m_pResponseSender.SendResponseAsync(new IMAP_r_ServerStatus(cmdTag,"NO","Authentication required."));

                return;
            }
            
            string[] parts = TextUtils.SplitQuotedString(cmdText,' ',true);
            if(parts.Length != 2){
                m_pResponseSender.SendResponseAsync(new IMAP_r_ServerStatus(cmdTag,"BAD","Error in arguments."));

                return;
            }
            
            IMAP_e_Rename e = OnRename(cmdTag,IMAP_Utils.DecodeMailbox(parts[0]),IMAP_Utils.DecodeMailbox(parts[1]));
            if(e.Response == null){
                m_pResponseSender.SendResponseAsync(new IMAP_r_ServerStatus(cmdTag,"NO","Internal server error: IMAP Server application didn't return any resposne."));
            }
            else{
                m_pResponseSender.SendResponseAsync(e.Response);
            }
        }

        #endregion

        #region method LSUB

        private void LSUB(string cmdTag,string cmdText)
        {
            /* RFC 3501 6.3.9. LSUB Command.
                Arguments:  reference name
                            mailbox name with possible wildcards

                Responses:  untagged responses: LSUB

                Result:     OK - lsub completed
                            NO - lsub failure: can't list that reference or name
                            BAD - command unknown or arguments invalid

                The LSUB command returns a subset of names from the set of names
                that the user has declared as being "active" or "subscribed".
                Zero or more untagged LSUB replies are returned.  The arguments to
                LSUB are in the same form as those for LIST.

                The returned untagged LSUB response MAY contain different mailbox
                flags from a LIST untagged response.  If this should happen, the
                flags in the untagged LIST are considered more authoritative.

                A special situation occurs when using LSUB with the % wildcard.
                Consider what happens if "foo/bar" (with a hierarchy delimiter of
                "/") is subscribed but "foo" is not.  A "%" wildcard to LSUB must
                return foo, not foo/bar, in the LSUB response, and it MUST be
                flagged with the \Noselect attribute.

                The server MUST NOT unilaterally remove an existing mailbox name
                from the subscription list even if a mailbox by that name no
                longer exists.

                Example:    C: A002 LSUB "#news." "comp.mail.*"
                            S: * LSUB () "." #news.comp.mail.mime
                            S: * LSUB () "." #news.comp.mail.misc
                            S: A002 OK LSUB completed
                            C: A003 LSUB "#news." "comp.%"
                            S: * LSUB (\NoSelect) "." #news.comp.mail
                            S: A003 OK LSUB completed
            */

            if(!this.IsAuthenticated){
                m_pResponseSender.SendResponseAsync(new IMAP_r_ServerStatus(cmdTag,"NO","Authentication required."));

                return;
            }

            string[] parts = TextUtils.SplitQuotedString(cmdText,' ',true);
            if(parts.Length != 2){
                m_pResponseSender.SendResponseAsync(new IMAP_r_ServerStatus(cmdTag,"BAD","Error in arguments."));

                return;
            }

            string refName = IMAP_Utils.DecodeMailbox(parts[0]);
            string folder  = IMAP_Utils.DecodeMailbox(parts[1]);

            // Store start time
			long startTime = DateTime.Now.Ticks;
            
            IMAP_e_LSub e = OnLSub(refName,folder);
            foreach(IMAP_r_u_LSub r in e.Folders){
                m_pResponseSender.SendResponseAsync(r);
            }
            
            m_pResponseSender.SendResponseAsync(new IMAP_r_ServerStatus(cmdTag,"OK","LSUB Completed in " + ((DateTime.Now.Ticks - startTime) / (decimal)10000000).ToString("f2") + " seconds.")); 
        }

        #endregion

        #region method SUBSCRIBE

        private void SUBSCRIBE(string cmdTag,string cmdText)
        {
            /* RFC 3501 6.3.6. SUBSCRIBE Command.
                Arguments:  mailbox

                Responses:  no specific responses for this command

                Result:     OK - subscribe completed
                            NO - subscribe failure: can't subscribe to that name
                            BAD - command unknown or arguments invalid

                The SUBSCRIBE command adds the specified mailbox name to the
                server's set of "active" or "subscribed" mailboxes as returned by
                the LSUB command.  This command returns a tagged OK response only
                if the subscription is successful.

                A server MAY validate the mailbox argument to SUBSCRIBE to verify
                that it exists.  However, it MUST NOT unilaterally remove an
                existing mailbox name from the subscription list even if a mailbox
                by that name no longer exists.

                    Note: This requirement is because a server site can
                    choose to routinely remove a mailbox with a well-known
                    name (e.g., "system-alerts") after its contents expire,
                    with the intention of recreating it when new contents
                    are appropriate.

                Example:    C: A002 SUBSCRIBE #news.comp.mail.mime
                S: A002 OK SUBSCRIBE completed
            */

            if(!this.IsAuthenticated){
                m_pResponseSender.SendResponseAsync(new IMAP_r_ServerStatus(cmdTag,"NO","Authentication required."));

                return;
            }            

            string folder = IMAP_Utils.DecodeMailbox(TextUtils.UnQuoteString(cmdText));

            if(string.IsNullOrEmpty(folder)){
                m_pResponseSender.SendResponseAsync(new IMAP_r_ServerStatus(cmdTag,"NO","Mailbox name must be specified."));

                return;
            }
            
            IMAP_e_Folder e = OnSubscribe(cmdTag,folder,new IMAP_r_ServerStatus(cmdTag,"OK","SUBSCRIBE command completed."));
            
            m_pResponseSender.SendResponseAsync(e.Response);
        }

        #endregion

        #region method UNSUBSCRIBE

        private void UNSUBSCRIBE(string cmdTag,string cmdText)
        {
            /* RFC 3501 6.3.7. UNSUBSCRIBE Command.
                Arguments:  mailbox name

                Responses:  no specific responses for this command

                Result:     OK - unsubscribe completed
                            NO - unsubscribe failure: can't unsubscribe that name
                            BAD - command unknown or arguments invalid

                The UNSUBSCRIBE command removes the specified mailbox name from
                the server's set of "active" or "subscribed" mailboxes as returned
                by the LSUB command.  This command returns a tagged OK response
                only if the unsubscription is successful.

                Example:    C: A002 UNSUBSCRIBE #news.comp.mail.mime
                            S: A002 OK UNSUBSCRIBE completed
            */

            if(!this.IsAuthenticated){
                m_pResponseSender.SendResponseAsync(new IMAP_r_ServerStatus(cmdTag,"NO","Authentication required."));

                return;
            }

            string folder = IMAP_Utils.DecodeMailbox(TextUtils.UnQuoteString(cmdText));
            
            IMAP_e_Folder e = OnUnsubscribe(cmdTag,folder,new IMAP_r_ServerStatus(cmdTag,"OK","UNSUBSCRIBE command completed."));
            
            m_pResponseSender.SendResponseAsync(e.Response);
        }

        #endregion

        #region method STATUS

        private void STATUS(string cmdTag,string cmdText)
        {
            /* RFC 3501 6.3.10.STATUS Command.
                Arguments:  mailbox name
                            status data item names

                Responses:  untagged responses: STATUS

                Result:     OK - status completed
                            NO - status failure: no status for that name
                            BAD - command unknown or arguments invalid

                The STATUS command requests the status of the indicated mailbox.
                It does not change the currently selected mailbox, nor does it
                affect the state of any messages in the queried mailbox (in
                particular, STATUS MUST NOT cause messages to lose the \Recent
                flag).

                The STATUS command provides an alternative to opening a second
                IMAP4rev1 connection and doing an EXAMINE command on a mailbox to
                query that mailbox's status without deselecting the current
                mailbox in the first IMAP4rev1 connection.

                Unlike the LIST command, the STATUS command is not guaranteed to
                be fast in its response.  Under certain circumstances, it can be
                quite slow.  In some implementations, the server is obliged to
                open the mailbox read-only internally to obtain certain status
                information.  Also unlike the LIST command, the STATUS command
                does not accept wildcards.

                Note: The STATUS command is intended to access the
                status of mailboxes other than the currently selected
                mailbox.  Because the STATUS command can cause the
                mailbox to be opened internally, and because this
                information is available by other means on the selected
                mailbox, the STATUS command SHOULD NOT be used on the
                currently selected mailbox.

                The STATUS command MUST NOT be used as a "check for new
                messages in the selected mailbox" operation (refer to
                sections 7, 7.3.1, and 7.3.2 for more information about
                the proper method for new message checking).

                Because the STATUS command is not guaranteed to be fast
                in its results, clients SHOULD NOT expect to be able to
                issue many consecutive STATUS commands and obtain
                reasonable performance.

                The currently defined status data items that can be requested are:

                MESSAGES
                    The number of messages in the mailbox.

                RECENT
                    The number of messages with the \Recent flag set.

                UIDNEXT
                    The next unique identifier value of the mailbox.  Refer to
                    section 2.3.1.1 for more information.

                UIDVALIDITY
                    The unique identifier validity value of the mailbox.  Refer to
                    section 2.3.1.1 for more information.

                UNSEEN
                    The number of messages which do not have the \Seen flag set.

                Example:    C: A042 STATUS blurdybloop (UIDNEXT MESSAGES)
                            S: * STATUS blurdybloop (MESSAGES 231 UIDNEXT 44292)
                            S: A042 OK STATUS completed
            */

            if(!this.IsAuthenticated){
                m_pResponseSender.SendResponseAsync(new IMAP_r_ServerStatus(cmdTag,"NO","Authentication required."));

                return;
            }

            string[] parts = TextUtils.SplitQuotedString(cmdText,' ',false,2);
            if(parts.Length != 2){
                m_pResponseSender.SendResponseAsync(new IMAP_r_ServerStatus(cmdTag,"BAD","Error in arguments."));

                return;
            }

            // Store start time
			long startTime = DateTime.Now.Ticks;

            try{
                string folder = IMAP_Utils.DecodeMailbox(TextUtils.UnQuoteString(parts[0]));
                if(!(parts[1].StartsWith("(") && parts[1].EndsWith(")"))){
                    m_pResponseSender.SendResponseAsync(new IMAP_r_ServerStatus(cmdTag,"BAD","Error in arguments."));
                }
                else{
                    IMAP_e_Select eSelect = OnSelect(cmdTag,folder);
                    if(eSelect.ErrorResponse != null){
                        m_pResponseSender.SendResponseAsync(eSelect.ErrorResponse);

                        return;
                    }

                    IMAP_e_MessagesInfo eMessagesInfo = OnGetMessagesInfo(folder);
                   
                    int  msgCount    = -1;
                    int  recentCount = -1;
                    long uidNext     = -1;
                    long folderUid   = -1;
                    int  unseenCount = -1;

                    string[] statusItems = parts[1].Substring(1,parts[1].Length - 2).Split(' ');
                    for(int i=0;i<statusItems.Length;i++){
                        string statusItem = statusItems[i];
                        if(string.Equals(statusItem,"MESSAGES",StringComparison.InvariantCultureIgnoreCase)){
                            msgCount = eMessagesInfo.Exists;
                        }
                        else if(string.Equals(statusItem,"RECENT",StringComparison.InvariantCultureIgnoreCase)){
                            recentCount = eMessagesInfo.Recent;
                        }
                        else if(string.Equals(statusItem,"UIDNEXT",StringComparison.InvariantCultureIgnoreCase)){
                            uidNext = eMessagesInfo.UidNext;
                        }
                        else if(string.Equals(statusItem,"UIDVALIDITY",StringComparison.InvariantCultureIgnoreCase)){
                            folderUid = eSelect.FolderUID;
                        }
                        else if(string.Equals(statusItem,"UNSEEN",StringComparison.InvariantCultureIgnoreCase)){
                            unseenCount = eMessagesInfo.Unseen;
                        }
                        // Invalid status item.
                        else{
                            m_pResponseSender.SendResponseAsync(new IMAP_r_ServerStatus(cmdTag,"BAD","Error in arguments."));

                            return;
                        }
                    }
                                
                    m_pResponseSender.SendResponseAsync(new IMAP_r_u_Status(folder,msgCount,recentCount,uidNext,folderUid,unseenCount));
                    m_pResponseSender.SendResponseAsync(new IMAP_r_ServerStatus(cmdTag,"OK","STATUS completed in " + ((DateTime.Now.Ticks - startTime) / (decimal)10000000).ToString("f2") + " seconds."));
                }
            }
            catch(Exception x){
                m_pResponseSender.SendResponseAsync(new IMAP_r_ServerStatus(cmdTag,"NO","Error: " + x.Message));
            }
        }

        #endregion

        #region method SELECT

        private void SELECT(string cmdTag,string cmdText)
        {
            /* RFC 3501 6.3.1. SELECT Command.
                Arguments:  mailbox name

                Responses:  REQUIRED untagged responses: FLAGS, EXISTS, RECENT
                            REQUIRED OK untagged responses:  UNSEEN,  PERMANENTFLAGS,
                            UIDNEXT, UIDVALIDITY

                Result:     OK - select completed, now in selected state
                            NO - select failure, now in authenticated state: no
                                 such mailbox, can't access mailbox
                            BAD - command unknown or arguments invalid

                The SELECT command selects a mailbox so that messages in the
                mailbox can be accessed.  Before returning an OK to the client,
                the server MUST send the following untagged data to the client.
                Note that earlier versions of this protocol only required the
                FLAGS, EXISTS, and RECENT untagged data; consequently, client
                implementations SHOULD implement default behavior for missing data
                as discussed with the individual item.

                    FLAGS       Defined flags in the mailbox.  See the description
                                of the FLAGS response for more detail.

                    <n> EXISTS  The number of messages in the mailbox.  See the
                                description of the EXISTS response for more detail.

                    <n> RECENT  The number of messages with the \Recent flag set.
                                See the description of the RECENT response for more
                                detail.

                    OK [UNSEEN <n>]
                                The message sequence number of the first unseen
                                message in the mailbox.  If this is missing, the
                                client can not make any assumptions about the first
                                unseen message in the mailbox, and needs to issue a
                                SEARCH command if it wants to find it.

                    OK [PERMANENTFLAGS (<list of flags>)]
                                A list of message flags that the client can change
                                permanently.  If this is missing, the client should
                                assume that all flags can be changed permanently.

                    OK [UIDNEXT <n>]
                                The next unique identifier value.  Refer to section
                                2.3.1.1 for more information.  If this is missing,
                                the client can not make any assumptions about the
                                next unique identifier value.

                    OK [UIDVALIDITY <n>]
                                The unique identifier validity value.  Refer to
                                section 2.3.1.1 for more information.  If this is
                                missing, the server does not support unique
                                identifiers.

                Only one mailbox can be selected at a time in a connection;
                simultaneous access to multiple mailboxes requires multiple
                connections.  The SELECT command automatically deselects any
                currently selected mailbox before attempting the new selection.
                Consequently, if a mailbox is selected and a SELECT command that
                fails is attempted, no mailbox is selected.

                If the client is permitted to modify the mailbox, the server
                SHOULD prefix the text of the tagged OK response with the
                "[READ-WRITE]" response code.

                If the client is not permitted to modify the mailbox but is
                permitted read access, the mailbox is selected as read-only, and
                the server MUST prefix the text of the tagged OK response to
                SELECT with the "[READ-ONLY]" response code.  Read-only access
                through SELECT differs from the EXAMINE command in that certain
                read-only mailboxes MAY permit the change of permanent state on a
                per-user (as opposed to global) basis.  Netnews messages marked in
                a server-based .newsrc file are an example of such per-user
                permanent state that can be modified with read-only mailboxes.

                Example:    C: A142 SELECT INBOX
                            S: * 172 EXISTS
                            S: * 1 RECENT
                            S: * OK [UNSEEN 12] Message 12 is first unseen
                            S: * OK [UIDVALIDITY 3857529045] UIDs valid
                            S: * OK [UIDNEXT 4392] Predicted next UID
                            S: * FLAGS (\Answered \Flagged \Deleted \Seen \Draft)
                            S: * OK [PERMANENTFLAGS (\Deleted \Seen \*)] Limited
                            S: A142 OK [READ-WRITE] SELECT completed
            */

            /* 5738 3.2.  UTF8 Parameter to SELECT and EXAMINE
                The "UTF8=ACCEPT" capability also indicates that the server supports
                the "UTF8" parameter to SELECT and EXAMINE.  When a mailbox is
                selected with the "UTF8" parameter, it alters the behavior of all
                IMAP commands related to message sizes, message headers, and MIME
                body headers so they refer to the message with UTF-8 headers.  If the
                mailstore is not UTF-8 header native and the SELECT or EXAMINE
                command with UTF-8 header modifier succeeds, then the server MUST
                return results as if the mailstore were UTF-8 header native with
                upconversion requirements as described in Section 8.  The server MAY
                reject the SELECT or EXAMINE command with the [NOT-UTF-8] response
                code, unless the "UTF8=ALL" or "UTF8=ONLY" capability is advertised.

                Servers MAY include mailboxes that can only be selected or examined
                if the "UTF8" parameter is provided.  However, such mailboxes MUST
                NOT be included in the output of an unextended LIST, LSUB, or
                equivalent command.  If a client attempts to SELECT or EXAMINE such
                mailboxes without the "UTF8" parameter, the server MUST reject the
                command with a [UTF-8-ONLY] response code.  As a result, such
                mailboxes will not be accessible by IMAP clients written prior to
                this specification and are discouraged unless the server advertises
                "UTF8=ONLY" or the server implements IMAP4 LIST Command Extensions
   
                    utf8-select-param = "UTF8"
                        ;; Conforms to <select-param> from RFC 4466

                    C: a SELECT newmailbox (UTF8)
                    S: ...
                    S: a OK SELECT completed
                    C: b FETCH 1 (SIZE ENVELOPE BODY)
                    S: ... < UTF-8 header native results >
                    S: b OK FETCH completed

                    C: c EXAMINE legacymailbox (UTF8)
                    S: c NO [NOT-UTF-8] Mailbox does not support UTF-8 access
            */

            // Store start time
			long startTime = DateTime.Now.Ticks;

            if(!this.IsAuthenticated){
                m_pResponseSender.SendResponseAsync(new IMAP_r_ServerStatus(cmdTag,"NO","Authentication required."));

                return;
            }
                        
            // Unselect folder if any selected.
            if(m_pSelectedFolder != null){
                m_pSelectedFolder = null;
            }

            string[] args = TextUtils.SplitQuotedString(cmdText,' ');
            if(args.Length >= 2){
                // At moment we don't support UTF-8 mailboxes.
                if(string.Equals(args[1],"(UTF8)",StringComparison.InvariantCultureIgnoreCase)){
                    m_pResponseSender.SendResponseAsync(new IMAP_r_ServerStatus(cmdTag,"NO",new IMAP_t_orc_Unknown("NOT-UTF-8"),"Mailbox does not support UTF-8 access."));
                }
                else{
                    m_pResponseSender.SendResponseAsync(new IMAP_r_ServerStatus(cmdTag,"BAD","Error in arguments."));
                }

                return;
            }

            try{
                string folder = TextUtils.UnQuoteString(IMAP_Utils.DecodeMailbox(cmdText));

                IMAP_e_Select e = OnSelect(cmdTag,folder);
                if(e.ErrorResponse == null){
                    IMAP_e_MessagesInfo eMessagesInfo = OnGetMessagesInfo(folder);

                    m_pResponseSender.SendResponseAsync(new IMAP_r_u_Exists(eMessagesInfo.Exists));
                    m_pResponseSender.SendResponseAsync(new IMAP_r_u_Recent(eMessagesInfo.Recent));
                    if(eMessagesInfo.FirstUnseen > -1){
                        m_pResponseSender.SendResponseAsync(new IMAP_r_u_ServerStatus("OK",new IMAP_t_orc_Unseen(eMessagesInfo.FirstUnseen),"Message " + eMessagesInfo.FirstUnseen + " is the first unseen."));
                    }
                    m_pResponseSender.SendResponseAsync(new IMAP_r_u_ServerStatus("OK",new IMAP_t_orc_UidNext((int)eMessagesInfo.UidNext),"Predicted next message UID."));
                    m_pResponseSender.SendResponseAsync(new IMAP_r_u_ServerStatus("OK",new IMAP_t_orc_UidValidity(e.FolderUID),"Folder UID value."));
                    m_pResponseSender.SendResponseAsync(new IMAP_r_u_Flags(e.Flags.ToArray()));
                    m_pResponseSender.SendResponseAsync(new IMAP_r_u_ServerStatus("OK",new IMAP_t_orc_PermanentFlags(e.PermanentFlags.ToArray()),"Avaliable permanent flags."));
                    m_pResponseSender.SendResponseAsync(new IMAP_r_ServerStatus(cmdTag,"OK",new IMAP_t_orc_Unknown(e.IsReadOnly ? "READ-ONLY" : "READ-WRITE"),"SELECT completed in " + ((DateTime.Now.Ticks - startTime) / (decimal)10000000).ToString("f2") + " seconds."));

                    m_pSelectedFolder = new _SelectedFolder(folder,e.IsReadOnly,eMessagesInfo.MessagesInfo);
                    m_pSelectedFolder.Reindex();
                }
                else{
                    m_pResponseSender.SendResponseAsync(e.ErrorResponse);
                }
            }
            catch(Exception x){
                m_pResponseSender.SendResponseAsync(new IMAP_r_ServerStatus(cmdTag,"NO","Error: " + x.Message));
            }
        }

        #endregion

        #region method EXAMINE

        private void EXAMINE(string cmdTag,string cmdText)
        {
            /* RFC 3501 6.3.2. EXAMINE Command.
                Arguments:  mailbox name

                Responses:  REQUIRED untagged responses: FLAGS, EXISTS, RECENT
                            REQUIRED OK untagged responses:  UNSEEN,  PERMANENTFLAGS,
                            UIDNEXT, UIDVALIDITY

                Result:     OK - examine completed, now in selected state
                            NO - examine failure, now in authenticated state: no
                                 such mailbox, can't access mailbox
                            BAD - command unknown or arguments invalid

                The EXAMINE command is identical to SELECT and returns the same
                output; however, the selected mailbox is identified as read-only.
                No changes to the permanent state of the mailbox, including
                per-user state, are permitted; in particular, EXAMINE MUST NOT
                cause messages to lose the \Recent flag.

                The text of the tagged OK response to the EXAMINE command MUST
                begin with the "[READ-ONLY]" response code.

                Example:    C: A932 EXAMINE blurdybloop
                            S: * 17 EXISTS
                            S: * 2 RECENT
                            S: * OK [UNSEEN 8] Message 8 is first unseen
                            S: * OK [UIDVALIDITY 3857529045] UIDs valid
                            S: * OK [UIDNEXT 4392] Predicted next UID
                            S: * FLAGS (\Answered \Flagged \Deleted \Seen \Draft)
                            S: * OK [PERMANENTFLAGS ()] No permanent flags permitted
                            S: A932 OK [READ-ONLY] EXAMINE completed
            */

            /* 5738 3.2.  UTF8 Parameter to SELECT and EXAMINE
                The "UTF8=ACCEPT" capability also indicates that the server supports
                the "UTF8" parameter to SELECT and EXAMINE.  When a mailbox is
                selected with the "UTF8" parameter, it alters the behavior of all
                IMAP commands related to message sizes, message headers, and MIME
                body headers so they refer to the message with UTF-8 headers.  If the
                mailstore is not UTF-8 header native and the SELECT or EXAMINE
                command with UTF-8 header modifier succeeds, then the server MUST
                return results as if the mailstore were UTF-8 header native with
                upconversion requirements as described in Section 8.  The server MAY
                reject the SELECT or EXAMINE command with the [NOT-UTF-8] response
                code, unless the "UTF8=ALL" or "UTF8=ONLY" capability is advertised.

                Servers MAY include mailboxes that can only be selected or examined
                if the "UTF8" parameter is provided.  However, such mailboxes MUST
                NOT be included in the output of an unextended LIST, LSUB, or
                equivalent command.  If a client attempts to SELECT or EXAMINE such
                mailboxes without the "UTF8" parameter, the server MUST reject the
                command with a [UTF-8-ONLY] response code.  As a result, such
                mailboxes will not be accessible by IMAP clients written prior to
                this specification and are discouraged unless the server advertises
                "UTF8=ONLY" or the server implements IMAP4 LIST Command Extensions
   
                    utf8-select-param = "UTF8"
                        ;; Conforms to <select-param> from RFC 4466

                    C: a SELECT newmailbox (UTF8)
                    S: ...
                    S: a OK SELECT completed
                    C: b FETCH 1 (SIZE ENVELOPE BODY)
                    S: ... < UTF-8 header native results >
                    S: b OK FETCH completed

                    C: c EXAMINE legacymailbox (UTF8)
                    S: c NO [NOT-UTF-8] Mailbox does not support UTF-8 access
            */

            if(!this.IsAuthenticated){
                m_pResponseSender.SendResponseAsync(new IMAP_r_ServerStatus(cmdTag,"NO","Authentication required."));

                return;
            }
            
            // Store start time
			long startTime = DateTime.Now.Ticks;

            // Unselect folder if any selected.
            if(m_pSelectedFolder != null){
                m_pSelectedFolder = null;
            }

            string[] args = TextUtils.SplitQuotedString(cmdText,' ');
            if(args.Length >= 2){
                // At moment we don't support UTF-8 mailboxes.
                if(string.Equals(args[1],"(UTF8)",StringComparison.InvariantCultureIgnoreCase)){
                    m_pResponseSender.SendResponseAsync(new IMAP_r_ServerStatus(cmdTag,"NO",new IMAP_t_orc_Unknown("NOT-UTF-8"),"Mailbox does not support UTF-8 access."));
                }
                else{
                    m_pResponseSender.SendResponseAsync(new IMAP_r_ServerStatus(cmdTag,"BAD","Error in arguments."));
                }

                return;
            }

            string folder = TextUtils.UnQuoteString(IMAP_Utils.DecodeMailbox(cmdText));

            IMAP_e_Select e = OnSelect(cmdTag,folder);
            if(e.ErrorResponse == null){                 
                IMAP_e_MessagesInfo eMessagesInfo = OnGetMessagesInfo(folder);

                m_pResponseSender.SendResponseAsync(new IMAP_r_u_Exists(eMessagesInfo.Exists));
                m_pResponseSender.SendResponseAsync(new IMAP_r_u_Recent(eMessagesInfo.Recent));
                if(eMessagesInfo.FirstUnseen > -1){
                    m_pResponseSender.SendResponseAsync(new IMAP_r_u_ServerStatus("OK",new IMAP_t_orc_Unseen(eMessagesInfo.FirstUnseen),"Message " + eMessagesInfo.FirstUnseen + " is the first unseen."));
                }
                m_pResponseSender.SendResponseAsync(new IMAP_r_u_ServerStatus("OK",new IMAP_t_orc_UidNext((int)eMessagesInfo.UidNext),"Predicted next message UID."));
                m_pResponseSender.SendResponseAsync(new IMAP_r_u_ServerStatus("OK",new IMAP_t_orc_UidValidity(e.FolderUID),"Folder UID value."));
                m_pResponseSender.SendResponseAsync(new IMAP_r_u_Flags(e.Flags.ToArray()));
                m_pResponseSender.SendResponseAsync(new IMAP_r_u_ServerStatus("OK",new IMAP_t_orc_PermanentFlags(e.PermanentFlags.ToArray()),"Avaliable permanent flags."));
                m_pResponseSender.SendResponseAsync(new IMAP_r_ServerStatus(cmdTag,"OK",new IMAP_t_orc_ReadOnly(),"EXAMINE completed in " + ((DateTime.Now.Ticks - startTime) / (decimal)10000000).ToString("f2") + " seconds."));

                m_pSelectedFolder = new _SelectedFolder(folder,e.IsReadOnly,eMessagesInfo.MessagesInfo);
                m_pSelectedFolder.Reindex();
            }
            else{
                m_pResponseSender.SendResponseAsync(e.ErrorResponse);
            }
        }

        #endregion

        #region method APPEND

        private void APPEND(string cmdTag,string cmdText)
        {
            /* RFC 3501 6.3.11. APPEND Command.
                Arguments:  mailbox name
                            OPTIONAL flag parenthesized list
                            OPTIONAL date/time string
                            message literal

                Responses:  no specific responses for this command

                Result:     OK - append completed
                            NO - append error: can't append to that mailbox, error
                                 in flags or date/time or message text
                            BAD - command unknown or arguments invalid

                The APPEND command appends the literal argument as a new message
                to the end of the specified destination mailbox.  This argument
                SHOULD be in the format of an [RFC-2822] message.  8-bit
                characters are permitted in the message.  A server implementation
                that is unable to preserve 8-bit data properly MUST be able to
                reversibly convert 8-bit APPEND data to 7-bit using a [MIME-IMB]
                content transfer encoding.

                    Note: There MAY be exceptions, e.g., draft messages, in
                    which required [RFC-2822] header lines are omitted in
                    the message literal argument to APPEND.  The full
                    implications of doing so MUST be understood and
                    carefully weighed.

                If a flag parenthesized list is specified, the flags SHOULD be set
                in the resulting message; otherwise, the flag list of the
                resulting message is set to empty by default.  In either case, the
                Recent flag is also set.

                If a date-time is specified, the internal date SHOULD be set in
                the resulting message; otherwise, the internal date of the
                resulting message is set to the current date and time by default.

                If the append is unsuccessful for any reason, the mailbox MUST be
                restored to its state before the APPEND attempt; no partial
                appending is permitted.

                If the destination mailbox does not exist, a server MUST return an
                error, and MUST NOT automatically create the mailbox.  Unless it
                is certain that the destination mailbox can not be created, the
                server MUST send the response code "[TRYCREATE]" as the prefix of
                the text of the tagged NO response.  This gives a hint to the
                client that it can attempt a CREATE command and retry the APPEND
                if the CREATE is successful.

                If the mailbox is currently selected, the normal new message
                actions SHOULD occur.  Specifically, the server SHOULD notify the
                client immediately via an untagged EXISTS response.  If the server
                does not do so, the client MAY issue a NOOP command (or failing
                that, a CHECK command) after one or more APPEND commands.

                Example:    C: A003 APPEND saved-messages (\Seen) {310}
                            S: + Ready for literal data
                            C: Date: Mon, 7 Feb 1994 21:52:25 -0800 (PST)
                            C: From: Fred Foobar <foobar@Blurdybloop.COM>
                            C: Subject: afternoon meeting
                            C: To: mooch@owatagu.siam.edu
                            C: Message-Id: <B27397-0100000@Blurdybloop.COM>
                            C: MIME-Version: 1.0
                            C: Content-Type: TEXT/PLAIN; CHARSET=US-ASCII
                            C:
                            C: Hello Joe, do you think we can meet at 3:30 tomorrow?
                            C:
                            S: A003 OK APPEND completed

                    Note: The APPEND command is not used for message delivery,
                    because it does not provide a mechanism to transfer [SMTP]
                    envelope information.
            */

            if(!this.IsAuthenticated){
                m_pResponseSender.SendResponseAsync(new IMAP_r_ServerStatus(cmdTag,"NO","Authentication required."));

                return;
            }

            // Store start time
			long startTime = DateTime.Now.Ticks;

            #region Parse arguments

            StringReader r = new StringReader(cmdText);
            r.ReadToFirstChar();
            string folder = null;
            if(r.StartsWith("\"")){
                folder = IMAP_Utils.DecodeMailbox(r.ReadWord());
            }
            // Mailbox name as string-literal.
            else if(r.StartsWith("{")){
                _CmdReader cmdReader = new _CmdReader(this,cmdText,Encoding.UTF8,1);            
                cmdReader.Start();

                r = new StringReader(cmdReader.CmdLine);

                if(r.StartsWith("\"")){
                    folder = IMAP_Utils.DecodeMailbox(r.ReadWord());
                }
                else{
                    folder = IMAP_Utils.DecodeMailbox(r.QuotedReadToDelimiter(' '));
                }
            }
            else{
                folder = IMAP_Utils.DecodeMailbox(r.QuotedReadToDelimiter(' '));
            }
            r.ReadToFirstChar();
            List<string> flags = new List<string>();
            if(r.StartsWith("(")){                
                foreach(string f in r.ReadParenthesized().Split(' ')){
                    if(f.Length > 0 && !flags.Contains(f.Substring(1))){
                        flags.Add(f.Substring(1));
                    }
                }
            }
            r.ReadToFirstChar();
            DateTime date = DateTime.MinValue;
            if(!r.StartsWith("{")){
                try{
                    date = IMAP_Utils.ParseDate(r.ReadWord());
                } 
                catch{
                }
            }
            int size = Convert.ToInt32(r.ReadParenthesized());
            if(r.Available > 0){
                m_pResponseSender.SendResponseAsync(new IMAP_r_ServerStatus(cmdTag,"BAD","Error in arguments."));

                return;
            }

            #endregion

            IMAP_e_Append e = OnAppend(folder,flags.ToArray(),date,size,new IMAP_r_ServerStatus(cmdTag,"OK","APPEND command completed in %exectime seconds."));
            
            if(e.Response.IsError){
                m_pResponseSender.SendResponseAsync(e.Response);
            }
            else if(e.Stream == null){
                m_pResponseSender.SendResponseAsync(new IMAP_r_ServerStatus(cmdTag,"NO","Internal server error: No storage stream available."));
            }
            else{
                m_pResponseSender.SendResponseAsync(new IMAP_r_ServerStatus("+","Ready for literal data."));
                
                // Create callback which is called when BeginReadFixedCount completes.
                AsyncCallback readLiteralCompletedCallback = delegate(IAsyncResult ar){
                    try{
                        this.TcpStream.EndReadFixedCount(ar);
                        // Log.
                        LogAddRead(size,"Readed " + size + " bytes.");

                        // TODO: Async
                        // Read command line terminating CRLF.
                        SmartStream.ReadLineAsyncOP readLineOP = new SmartStream.ReadLineAsyncOP(new byte[32000],SizeExceededAction.JunkAndThrowException);
                        this.TcpStream.ReadLine(readLineOP,false);
                        // Read command line terminating CRLF failed.
                        if(readLineOP.Error != null){
                            OnError(readLineOP.Error);
                        }
                        // Read command line terminating CRLF succeeded.
                        else{
                            LogAddRead(readLineOP.BytesInBuffer,readLineOP.LineUtf8);

                            // Raise Completed event.
                            e.OnCompleted();

                            m_pResponseSender.SendResponseAsync(IMAP_r_ServerStatus.Parse(e.Response.ToString().TrimEnd().Replace("%exectime",((DateTime.Now.Ticks - startTime) / (decimal)10000000).ToString("f2"))));
                            BeginReadCmd();
                        }
                    }
                    catch(Exception x){
                        OnError(x);
                    }
                };

                this.TcpStream.BeginReadFixedCount(e.Stream,size,readLiteralCompletedCallback,null);
            }
        }

        #endregion

        #region method GETQUOTAROOT

        private void GETQUOTAROOT(string cmdTag,string cmdText)
        {
            /* RFC 2087 4.3. GETQUOTAROOT
				Arguments:  mailbox name

                Data:       untagged responses: QUOTAROOT, QUOTA

                Result:     OK - getquota completed
                            NO - getquota error: no such mailbox, permission denied
                            BAD - command unknown or arguments invalid

                The GETQUOTAROOT command takes the name of a mailbox and returns the
                list of quota roots for the mailbox in an untagged QUOTAROOT
                response.  For each listed quota root, it also returns the quota
                root's resource usage and limits in an untagged QUOTA response.

                Example:    C: A003 GETQUOTAROOT INBOX
                            S: * QUOTAROOT INBOX ""
                            S: * QUOTA "" (STORAGE 10 512)
                            S: A003 OK Getquota completed
							
			*/

            if(!SupportsCap("QUOTA")){
                m_pResponseSender.SendResponseAsync(new IMAP_r_ServerStatus(cmdTag,"NO","Command not supported."));

                return;
            }
            if(!this.IsAuthenticated){
                m_pResponseSender.SendResponseAsync(new IMAP_r_ServerStatus(cmdTag,"NO","Authentication required."));

                return;
            }
            if(string.IsNullOrEmpty(cmdText)){
                m_pResponseSender.SendResponseAsync(new IMAP_r_ServerStatus(cmdTag,"BAD","Error in arguments."));

                return;
            }
            
            string folder = IMAP_Utils.DecodeMailbox(TextUtils.UnQuoteString(cmdText));

            IMAP_e_GetQuotaRoot e = OnGetGuotaRoot(folder,new IMAP_r_ServerStatus(cmdTag,"OK","GETQUOTAROOT command completed."));

            if(e.QuotaRootResponses.Count > 0){
                foreach(IMAP_r_u_QuotaRoot r in e.QuotaRootResponses){
                    m_pResponseSender.SendResponseAsync(r);
                }                
            }
            if(e.QuotaResponses.Count > 0){
                foreach(IMAP_r_u_Quota r in e.QuotaResponses){
                    m_pResponseSender.SendResponseAsync(r);
                }                
            }
            m_pResponseSender.SendResponseAsync(e.Response);
        }

        #endregion

        #region method GETQUOTA

        private void GETQUOTA(string cmdTag,string cmdText)
        {
            /* RFC 2087 4.2. GETQUOTA
				Arguments:  quota root

                Data:       untagged responses: QUOTA

                Result:     OK - getquota completed
                            NO - getquota  error:  no  such  quota  root,  permission denied
                            BAD - command unknown or arguments invalid

                The GETQUOTA command takes the name of a quota root and returns the
                quota root's resource usage and limits in an untagged QUOTA response.

                Example:    C: A003 GETQUOTA ""
                            S: * QUOTA "" (STORAGE 10 512)
                            S: A003 OK Getquota completed
							
			*/

            if(!this.IsAuthenticated){
                m_pResponseSender.SendResponseAsync(new IMAP_r_ServerStatus(cmdTag,"NO","Authentication required."));

                return;
            }            

            string quotaRoot = IMAP_Utils.DecodeMailbox(TextUtils.UnQuoteString(cmdText));

            IMAP_e_GetQuota e = OnGetQuota(quotaRoot,new IMAP_r_ServerStatus(cmdTag,"OK","QUOTA command completed."));
            if(e.QuotaResponses.Count > 0){
                foreach(IMAP_r_u_Quota r in e.QuotaResponses){
                    m_pResponseSender.SendResponseAsync(r);
                }                
            }
            m_pResponseSender.SendResponseAsync(e.Response);
        }

        #endregion

        #region method GETACL

        private void GETACL(string cmdTag,string cmdText)
        {
            /* RFC 4314 3.3. GETACL Command.
                Arguments:  mailbox name

                Data:       untagged responses: ACL

                Result:     OK - getacl completed
                            NO - getacl failure: can't get acl
                            BAD - arguments invalid

                The GETACL command returns the access control list for mailbox in an
                untagged ACL response.

                Some implementations MAY permit multiple forms of an identifier to
                reference the same IMAP account.  Usually, such implementations will
                have a canonical form that is stored internally.  An ACL response
                caused by a GETACL command MAY include a canonicalized form of the
                identifier that might be different from the one used in the
                corresponding SETACL command.

                Example:    C: A002 GETACL INBOX
							S: * ACL INBOX Fred rwipslda
							S: A002 OK Getacl complete
							
							.... Multiple users
							S: * ACL INBOX Fred rwipslda test rwipslda
							
							.... No acl flags for Fred
							S: * ACL INBOX Fred "" test rwipslda         
            */

            if(!SupportsCap("ACL")){
                m_pResponseSender.SendResponseAsync(new IMAP_r_ServerStatus(cmdTag,"NO","Command not supported."));

                return;
            }
            if(!this.IsAuthenticated){
                m_pResponseSender.SendResponseAsync(new IMAP_r_ServerStatus(cmdTag,"NO","Authentication required."));

                return;
            }            

            string folder = IMAP_Utils.DecodeMailbox(TextUtils.UnQuoteString(cmdText));

            IMAP_e_GetAcl e = OnGetAcl(folder,new IMAP_r_ServerStatus(cmdTag,"OK","GETACL command completed."));
            if(e.AclResponses.Count > 0){
                foreach(IMAP_r_u_Acl r in e.AclResponses){
                    m_pResponseSender.SendResponseAsync(r);
                }                
            }
            m_pResponseSender.SendResponseAsync(e.Response);
        }

        #endregion

        #region method SETACL

        private void SETACL(string cmdTag,string cmdText)
        {
            /* RFC 4314 3.1. SETACL Command.
                Arguments:  mailbox name
                            identifier
                            access right modification

                Data:       no specific data for this command

                Result:     OK - setacl completed
                            NO - setacl failure: can't set acl
                            BAD - arguments invalid

                The SETACL command changes the access control list on the specified
                mailbox so that the specified identifier is granted permissions as
                specified in the third argument.

                The third argument is a string containing an optional plus ("+") or
                minus ("-") prefix, followed by zero or more rights characters.  If
                the string starts with a plus, the following rights are added to any
                existing rights for the identifier.  If the string starts with a
                minus, the following rights are removed from any existing rights for
                the identifier.  If the string does not start with a plus or minus,
                the rights replace any existing rights for the identifier.

                Note that an unrecognized right MUST cause the command to return the
                BAD response.  In particular, the server MUST NOT silently ignore
                unrecognized rights.

                Example:    C: A035 SETACL INBOX/Drafts John lrQswicda
                            S: A035 BAD Uppercase rights are not allowed
            
                            C: A036 SETACL INBOX/Drafts John lrqswicda
                            S: A036 BAD The q right is not supported
            */

            if(!SupportsCap("ACL")){
                m_pResponseSender.SendResponseAsync(new IMAP_r_ServerStatus(cmdTag,"NO","Command not supported."));

                return;
            }
            if(!this.IsAuthenticated){
                m_pResponseSender.SendResponseAsync(new IMAP_r_ServerStatus(cmdTag,"NO","Authentication required."));

                return;
            }            

            string[] parts = TextUtils.SplitQuotedString(cmdText,' ',true);
            if(parts.Length != 3){
                m_pResponseSender.SendResponseAsync(new IMAP_r_ServerStatus(cmdTag,"BAD","Error in arguments."));

                return;
            }

            string rights = parts[2];
            IMAP_Flags_SetType setType = IMAP_Flags_SetType.Replace;
            if(rights.StartsWith("+")){
                setType = IMAP_Flags_SetType.Add;
                rights = rights.Substring(1);
            }
            else if(rights.StartsWith("-")){
                setType = IMAP_Flags_SetType.Remove;
                rights = rights.Substring(1);
            }

            IMAP_e_SetAcl e = OnSetAcl(
                IMAP_Utils.DecodeMailbox(parts[0]),
                IMAP_Utils.DecodeMailbox(parts[1]),
                setType,
                rights,
                new IMAP_r_ServerStatus(cmdTag,"OK","SETACL command completed.")
            );
            m_pResponseSender.SendResponseAsync(e.Response);
        }

        #endregion

        #region method DELETEACL

        private void DELETEACL(string cmdTag,string cmdText)
        {
            /* RFC 4314 3.2. DELETEACL Command.
                Arguments:  mailbox name
                            identifier

                Data:       no specific data for this command

                Result:     OK - deleteacl completed
                            NO - deleteacl failure: can't delete acl
                            BAD - arguments invalid

                The DELETEACL command removes any <identifier,rights> pair for the
                specified identifier from the access control list for the specified
                mailbox.

                Example:    C: B001 getacl INBOX
                            S: * ACL INBOX Fred rwipslxetad -Fred wetd $team w
                            S: B001 OK Getacl complete
                            C: B002 DeleteAcl INBOX Fred
                            S: B002 OK Deleteacl complete
            */

            if(!SupportsCap("ACL")){
                m_pResponseSender.SendResponseAsync(new IMAP_r_ServerStatus(cmdTag,"NO","Command not supported."));

                return;
            }
            if(!this.IsAuthenticated){
                m_pResponseSender.SendResponseAsync(new IMAP_r_ServerStatus(cmdTag,"NO","Authentication required."));

                return;
            }
            
            string[] parts = TextUtils.SplitQuotedString(cmdText,' ',true);
            if(parts.Length != 2){
                m_pResponseSender.SendResponseAsync(new IMAP_r_ServerStatus(cmdTag,"BAD","Error in arguments."));

                return;
            }

            IMAP_e_DeleteAcl e = OnDeleteAcl(
                IMAP_Utils.DecodeMailbox(parts[0]),
                IMAP_Utils.DecodeMailbox(parts[1]),
                new IMAP_r_ServerStatus(cmdTag,"OK","DELETEACL command completed.")
            );
            m_pResponseSender.SendResponseAsync(e.Response);
        }

        #endregion

        #region method LISTRIGHTS

        private void LISTRIGHTS(string cmdTag,string cmdText)
        {
            /* RFC 4314 3.4. LISTRIGHTS Command.
                Arguments:  mailbox name
                            identifier

                Data:       untagged responses: LISTRIGHTS

                Result:     OK - listrights completed
                            NO - listrights failure: can't get rights list
                            BAD - arguments invalid

                The LISTRIGHTS command takes a mailbox name and an identifier and
                returns information about what rights can be granted to the
                identifier in the ACL for the mailbox.

                Some implementations MAY permit multiple forms of an identifier to
                reference the same IMAP account.  Usually, such implementations will
                have a canonical form that is stored internally.  A LISTRIGHTS
                response caused by a LISTRIGHTS command MUST always return the same
                form of an identifier as specified by the client.  This is to allow
                the client to correlate the response with the command.

                Example:    C: a001 LISTRIGHTS ~/Mail/saved smith
                            S: * LISTRIGHTS ~/Mail/saved smith la r swicdkxte
                            S: a001 OK Listrights completed

                Example:    C: a005 listrights archive/imap anyone
                            S: * LISTRIGHTS archive.imap anyone ""
                               l r s w i p k x t e c d a 0 1 2 3 4 5 6 7 8 9
                            S: a005 Listrights successful
            */

            if(!SupportsCap("ACL")){
                m_pResponseSender.SendResponseAsync(new IMAP_r_ServerStatus(cmdTag,"NO","Command not supported."));

                return;
            }
            if(!this.IsAuthenticated){
                m_pResponseSender.SendResponseAsync(new IMAP_r_ServerStatus(cmdTag,"NO","Authentication required."));

                return;
            }

            string[] parts = TextUtils.SplitQuotedString(cmdText,' ',true);
            if(parts.Length != 2){
                m_pResponseSender.SendResponseAsync(new IMAP_r_ServerStatus(cmdTag,"BAD","Error in arguments."));

                return;
            }
            

            IMAP_e_ListRights e = OnListRights(
                IMAP_Utils.DecodeMailbox(parts[0]),
                IMAP_Utils.DecodeMailbox(parts[1]),
                new IMAP_r_ServerStatus(cmdTag,"OK","LISTRIGHTS command completed.")
            );

            
            if(e.ListRightsResponse != null){
                m_pResponseSender.SendResponseAsync(e.ListRightsResponse);
            }
            m_pResponseSender.SendResponseAsync(e.Response);
        }

        #endregion

        #region method MYRIGHTS

        private void MYRIGHTS(string cmdTag,string cmdText)
        {
            /* RFC 4314 3.5. MYRIGHTS Command.
                Arguments:  mailbox name

                Data:       untagged responses: MYRIGHTS

                Result:     OK - myrights completed
                            NO - myrights failure: can't get rights
                            BAD - arguments invalid

                The MYRIGHTS command returns the set of rights that the user has to
                mailbox in an untagged MYRIGHTS reply.

                Example:    C: A003 MYRIGHTS INBOX
                            S: * MYRIGHTS INBOX rwiptsldaex
                            S: A003 OK Myrights complete
            */

            if(!SupportsCap("ACL")){
                m_pResponseSender.SendResponseAsync(new IMAP_r_ServerStatus(cmdTag,"NO","Command not supported."));

                return;
            }
            if(!this.IsAuthenticated){
                m_pResponseSender.SendResponseAsync(new IMAP_r_ServerStatus(cmdTag,"NO","Authentication required."));

                return;
            }
            
            string folder = IMAP_Utils.DecodeMailbox(TextUtils.UnQuoteString(cmdText));

            IMAP_e_MyRights e = OnMyRights(folder,new IMAP_r_ServerStatus(cmdTag,"OK","MYRIGHTS command completed."));
            if(e.MyRightsResponse != null){
                m_pResponseSender.SendResponseAsync(e.MyRightsResponse);
            }
            m_pResponseSender.SendResponseAsync(e.Response);
        }

        #endregion

        #region method ENABLE

        private void ENABLE(string cmdTag,string cmdText)
        {            
            /* RFC 5161 3.1. The ENABLE Command.
                Arguments: capability names

                Result: OK: Relevant capabilities enabled
                        BAD: No arguments, or syntax error in an argument

                The ENABLE command takes a list of capability names, and requests the
                server to enable the named extensions.  Once enabled using ENABLE,
                each extension remains active until the IMAP connection is closed.
                For each argument, the server does the following:

                    - If the argument is not an extension known to the server, the server
                      MUST ignore the argument.

                    - If the argument is an extension known to the server, and it is not
                      specifically permitted to be enabled using ENABLE, the server MUST
                      ignore the argument.  (Note that knowing about an extension doesn't
                      necessarily imply supporting that extension.)

                    - If the argument is an extension that is supported by the server and
                      that needs to be enabled, the server MUST enable the extension for
                      the duration of the connection.  At present, this applies only to
                      CONDSTORE ([RFC4551]).  Note that once an extension is enabled,
                      there is no way to disable it.

                If the ENABLE command is successful, the server MUST send an untagged
                ENABLED response (see Section 3.2).

                Clients SHOULD only include extensions that need to be enabled by the
                server.  At the time of publication, CONDSTORE is the only such
                extension (i.e., ENABLE CONDSTORE is an additional "CONDSTORE
                enabling command" as defined in [RFC4551]).  Future RFCs may add to
                this list.

                The ENABLE command is only valid in the authenticated state (see
                [RFC3501]), before any mailbox is selected.  Clients MUST NOT issue
                ENABLE once they SELECT/EXAMINE a mailbox; however, server
                implementations don't have to check that no mailbox is selected or
                was previously selected during the duration of a connection.

                The ENABLE command can be issued multiple times in a session.  It is
                additive; i.e., "ENABLE a b", followed by "ENABLE c" is the same as a
                single command "ENABLE a b c".  When multiple ENABLE commands are
                issued, each corresponding ENABLED response SHOULD only contain
                extensions enabled by the corresponding ENABLE command.

                There are no limitations on pipelining ENABLE.  For example, it is
                possible to send ENABLE and then immediately SELECT, or a LOGIN
                immediately followed by ENABLE.

                The server MUST NOT change the CAPABILITY list as a result of
                executing ENABLE; i.e., a CAPABILITY command issued right after an
                ENABLE command MUST list the same capabilities as a CAPABILITY
                command issued before the ENABLE command.  This is demonstrated in
                the following example:
             
                    C: t1 CAPABILITY
                    S: * CAPABILITY IMAP4rev1 ID LITERAL+ ENABLE X-GOOD-IDEA
                    S: t1 OK foo
                    C: t2 ENABLE CONDSTORE X-GOOD-IDEA
                    S: * ENABLED X-GOOD-IDEA
                    S: t2 OK foo
                    C: t3 CAPABILITY
                    S: * CAPABILITY IMAP4rev1 ID LITERAL+ ENABLE X-GOOD-IDEA
                    S: t3 OK foo again

                In the following example, the client enables CONDSTORE:

                    C: a1 ENABLE CONDSTORE
                    S: * ENABLED CONDSTORE
                    S: a1 OK Conditional Store enabled
            */

            // Capability disabled.
            if(!SupportsCap("ENABLE")){
                m_pResponseSender.SendResponseAsync(new IMAP_r_ServerStatus(cmdTag,"NO","Command 'ENABLE' not supported."));

                return;
            }
            if(!this.IsAuthenticated){
                m_pResponseSender.SendResponseAsync(new IMAP_r_ServerStatus(cmdTag,"NO","Authentication required."));

                return;
            }
            if(string.IsNullOrEmpty(cmdText)){
                m_pResponseSender.SendResponseAsync(new IMAP_r_ServerStatus(cmdTag,"BAD","No arguments, or syntax error in an argument."));

                return;
            }

            foreach(string capa in cmdText.Split(' ')){
                if(string.Equals("UTF8=ACCEPT",capa,StringComparison.InvariantCultureIgnoreCase)){
                    //m_MailboxEncoding = IMAP_Mailbox_Encoding.ImapUtf8;
                    //m_pResponseSender.SendResponseAsync(new IMAP_r_u_Enable(new string[]{"UTF8=ACCEPT"}));
                }
                // Ignore as specification says.
                //else{
                //}
            }
            m_pResponseSender.SendResponseAsync(new IMAP_r_ServerStatus(cmdTag,"OK","ENABLE command completed."));
        }

        #endregion


        #region method CHECK

        private void CHECK(string cmdTag,string cmdText)
        {
            /* 6.4.1.  CHECK Command
                Arguments:  none

                Responses:  no specific responses for this command

                Result:     OK - check completed
                            BAD - command unknown or arguments invalid

                The CHECK command requests a checkpoint of the currently selected
                mailbox.  A checkpoint refers to any implementation-dependent
                housekeeping associated with the mailbox (e.g., resolving the
                server's in-memory state of the mailbox with the state on its
                disk) that is not normally executed as part of each command.  A
                checkpoint MAY take a non-instantaneous amount of real time to
                complete.  If a server implementation has no such housekeeping
                considerations, CHECK is equivalent to NOOP.

                There is no guarantee that an EXISTS untagged response will happen
                as a result of CHECK.  NOOP, not CHECK, SHOULD be used for new
                message polling.

                Example:    C: FXXZ CHECK
                            S: FXXZ OK CHECK Completed
            */

            if(!this.IsAuthenticated){
                m_pResponseSender.SendResponseAsync(new IMAP_r_ServerStatus(cmdTag,"NO","Authentication required."));

                return;
            }
            if(m_pSelectedFolder == null){
                m_pResponseSender.SendResponseAsync(new IMAP_r_ServerStatus(cmdTag,"NO","Error: This command is valid only in selected state."));

                return;
            }

            // Store start time
			long startTime = DateTime.Now.Ticks;

           
            UpdateSelectedFolderAndSendChanges();
            
            m_pResponseSender.SendResponseAsync(new IMAP_r_ServerStatus(cmdTag,"OK","CHECK Completed in " + ((DateTime.Now.Ticks - startTime) / (decimal)10000000).ToString("f2") + " seconds."));
        }

        #endregion

        #region method CLOSE

        private void CLOSE(string cmdTag,string cmdText)
        {
            /* RFC 3501 6.4.2. CLOSE Command.
                Arguments:  none

                Responses:  no specific responses for this command

                Result:     OK - close completed, now in authenticated state
                            BAD - command unknown or arguments invalid

                The CLOSE command permanently removes all messages that have the
                \Deleted flag set from the currently selected mailbox, and returns
                to the authenticated state from the selected state.  No untagged
                EXPUNGE responses are sent.

                No messages are removed, and no error is given, if the mailbox is
                selected by an EXAMINE command or is otherwise selected read-only.

                Even if a mailbox is selected, a SELECT, EXAMINE, or LOGOUT
                command MAY be issued without previously issuing a CLOSE command.
                The SELECT, EXAMINE, and LOGOUT commands implicitly close the
                currently selected mailbox without doing an expunge.  However,
                when many messages are deleted, a CLOSE-LOGOUT or CLOSE-SELECT
                sequence is considerably faster than an EXPUNGE-LOGOUT or
                EXPUNGE-SELECT because no untagged EXPUNGE responses (which the
                client would probably ignore) are sent.

                Example:    C: A341 CLOSE
                            S: A341 OK CLOSE completed
            */

            if(!this.IsAuthenticated){
                m_pResponseSender.SendResponseAsync(new IMAP_r_ServerStatus(cmdTag,"NO","Authentication required."));

                return;
            }
            if(m_pSelectedFolder == null){
                m_pResponseSender.SendResponseAsync(new IMAP_r_ServerStatus(cmdTag,"NO","Error: This command is valid only in selected state."));

                return;
            }

            if(m_pSelectedFolder != null && !m_pSelectedFolder.IsReadOnly){
                foreach(IMAP_MessageInfo msgInfo in m_pSelectedFolder.MessagesInfo){
                    if(msgInfo.ContainsFlag("Deleted")){
                        OnExpunge(msgInfo,new IMAP_r_ServerStatus("dummy","OK","This is CLOSE command expunge, so this response is not used."));
                    }
                }
            }
            m_pSelectedFolder = null;
            
            m_pResponseSender.SendResponseAsync(new IMAP_r_ServerStatus(cmdTag,"OK","CLOSE completed."));
        }

        #endregion
//
        #region method FETCH

        private void FETCH(bool uid,string cmdTag,string cmdText)
        {
            /* RFC 3501. 6.4.5. FETCH Command.
                Arguments:  sequence set
                            message data item names or macro

                Responses:  untagged responses: FETCH

                Result:     OK - fetch completed
                            NO - fetch error: can't fetch that data
                            BAD - command unknown or arguments invalid

                The FETCH command retrieves data associated with a message in the
                mailbox.  The data items to be fetched can be either a single atom
                or a parenthesized list.

                Most data items, identified in the formal syntax under the
                msg-att-static rule, are static and MUST NOT change for any
                particular message.  Other data items, identified in the formal
                syntax under the msg-att-dynamic rule, MAY change, either as a
                result of a STORE command or due to external events.

                    For example, if a client receives an ENVELOPE for a
                    message when it already knows the envelope, it can
                    safely ignore the newly transmitted envelope.

                There are three macros which specify commonly-used sets of data
                items, and can be used instead of data items.  A macro must be
                used by itself, and not in conjunction with other macros or data
                items.

                ALL
                    Macro equivalent to: (FLAGS INTERNALDATE RFC822.SIZE ENVELOPE)

                FAST
                    Macro equivalent to: (FLAGS INTERNALDATE RFC822.SIZE)

                FULL
                    Macro equivalent to: (FLAGS INTERNALDATE RFC822.SIZE ENVELOPE BODY)

                The currently defined data items that can be fetched are:

                BODY
                    Non-extensible form of BODYSTRUCTURE.

                BODY[<section>]<<partial>>
                    The text of a particular body section.  The section
                    specification is a set of zero or more part specifiers
                    delimited by periods.  A part specifier is either a part number
                    or one of the following: HEADER, HEADER.FIELDS,
                    HEADER.FIELDS.NOT, MIME, and TEXT.  An empty section
                    specification refers to the entire message, including the
                    header.

                    Every message has at least one part number.  Non-[MIME-IMB]
                    messages, and non-multipart [MIME-IMB] messages with no
                    encapsulated message, only have a part 1.

                    Multipart messages are assigned consecutive part numbers, as
                    they occur in the message.  If a particular part is of type
                    message or multipart, its parts MUST be indicated by a period
                    followed by the part number within that nested multipart part.

                    A part of type MESSAGE/RFC822 also has nested part numbers,
                    referring to parts of the MESSAGE part's body.

                    The HEADER, HEADER.FIELDS, HEADER.FIELDS.NOT, and TEXT part
                    specifiers can be the sole part specifier or can be prefixed by
                    one or more numeric part specifiers, provided that the numeric
                    part specifier refers to a part of type MESSAGE/RFC822.  The
                    MIME part specifier MUST be prefixed by one or more numeric
                    part specifiers.

                    The HEADER, HEADER.FIELDS, and HEADER.FIELDS.NOT part
                    specifiers refer to the [RFC-2822] header of the message or of
                    an encapsulated [MIME-IMT] MESSAGE/RFC822 message.
                    HEADER.FIELDS and HEADER.FIELDS.NOT are followed by a list of
                    field-name (as defined in [RFC-2822]) names, and return a
                    subset of the header.  The subset returned by HEADER.FIELDS
                    contains only those header fields with a field-name that
                    matches one of the names in the list; similarly, the subset
                    returned by HEADER.FIELDS.NOT contains only the header fields
                    with a non-matching field-name.  The field-matching is
                    case-insensitive but otherwise exact.  Subsetting does not
                    exclude the [RFC-2822] delimiting blank line between the header
                    and the body; the blank line is included in all header fetches,
                    except in the case of a message which has no body and no blank
                    line.

                    The MIME part specifier refers to the [MIME-IMB] header for
                    this part.

                    The TEXT part specifier refers to the text body of the message,
                    omitting the [RFC-2822] header.

                        Here is an example of a complex message with some of its
                        part specifiers:

                    HEADER     ([RFC-2822] header of the message)
                    TEXT       ([RFC-2822] text body of the message) MULTIPART/MIXED
                    1          TEXT/PLAIN
                    2          APPLICATION/OCTET-STREAM
                    3          MESSAGE/RFC822
                    3.HEADER   ([RFC-2822] header of the message)
                    3.TEXT     ([RFC-2822] text body of the message) MULTIPART/MIXED
                    3.1        TEXT/PLAIN
                    3.2        APPLICATION/OCTET-STREAM
                    4          MULTIPART/MIXED
                    4.1        IMAGE/GIF
                    4.1.MIME   ([MIME-IMB] header for the IMAGE/GIF)
                    4.2        MESSAGE/RFC822
                    4.2.HEADER ([RFC-2822] header of the message)
                    4.2.TEXT   ([RFC-2822] text body of the message) MULTIPART/MIXED
                    4.2.1      TEXT/PLAIN
                    4.2.2      MULTIPART/ALTERNATIVE
                    4.2.2.1    TEXT/PLAIN
                    4.2.2.2    TEXT/RICHTEXT
            
                    It is possible to fetch a substring of the designated text.
                    This is done by appending an open angle bracket ("<"), the
                    octet position of the first desired octet, a period, the
                    maximum number of octets desired, and a close angle bracket
                    (">") to the part specifier.  If the starting octet is beyond
                    the end of the text, an empty string is returned.
                    Any partial fetch that attempts to read beyond the end of the
                    text is truncated as appropriate.  A partial fetch that starts
                    at octet 0 is returned as a partial fetch, even if this
                    truncation happened.

                        Note: This means that BODY[]<0.2048> of a 1500-octet message
                        will return BODY[]<0> with a literal of size 1500, not
                        BODY[].

                        Note: A substring fetch of a HEADER.FIELDS or
                        HEADER.FIELDS.NOT part specifier is calculated after
                        subsetting the header.

                    The \Seen flag is implicitly set; if this causes the flags to
                    change, they SHOULD be included as part of the FETCH responses.

                BODY.PEEK[<section>]<<partial>>
                    An alternate form of BODY[<section>] that does not implicitly
                    set the \Seen flag.

                BODYSTRUCTURE
                    The [MIME-IMB] body structure of the message.  This is computed
                    by the server by parsing the [MIME-IMB] header fields in the
                    [RFC-2822] header and [MIME-IMB] headers.

                ENVELOPE
                    The envelope structure of the message.  This is computed by the
                    server by parsing the [RFC-2822] header into the component
                    parts, defaulting various fields as necessary.

                FLAGS
                    The flags that are set for this message.

                INTERNALDATE
                    The internal date of the message.

                RFC822
                    Functionally equivalent to BODY[], differing in the syntax of
                    the resulting untagged FETCH data (RFC822 is returned).

                RFC822.HEADER
                    Functionally equivalent to BODY.PEEK[HEADER], differing in the
                    syntax of the resulting untagged FETCH data (RFC822.HEADER is
                    returned).

                RFC822.SIZE
                    The [RFC-2822] size of the message.

                RFC822.TEXT
                    Functionally equivalent to BODY[TEXT], differing in the syntax
                    of the resulting untagged FETCH data (RFC822.TEXT is returned).
                UID
                    The unique identifier for the message.


                Example:    C: A654 FETCH 2:4 (FLAGS BODY[HEADER.FIELDS (DATE FROM)])
                            S: * 2 FETCH ....
                            S: * 3 FETCH ....
                            S: * 4 FETCH ....
                            S: A654 OK FETCH completed
            */

            // Store start time
			long startTime = DateTime.Now.Ticks;

            if(!this.IsAuthenticated){
                m_pResponseSender.SendResponseAsync(new IMAP_r_ServerStatus(cmdTag,"NO","Authentication required."));

                return;
            }
            if(m_pSelectedFolder == null){
                m_pResponseSender.SendResponseAsync(new IMAP_r_ServerStatus(cmdTag,"NO","Error: This command is valid only in selected state."));

                return;
            }
            
            string[] parts = cmdText.Split(new char[]{' '},2);
            if(parts.Length != 2){
                m_pResponseSender.SendResponseAsync(new IMAP_r_ServerStatus(cmdTag,"BAD","Error in arguments."));

                return;
            }

            IMAP_t_SeqSet seqSet = null;
            try{                
                seqSet = IMAP_t_SeqSet.Parse(parts[0]);
            }
            catch{
                m_pResponseSender.SendResponseAsync(new IMAP_r_ServerStatus(cmdTag,"BAD","Error in arguments: Invalid 'sequence-set' value."));

                return;
            }

            #region Parse data-items

            List<IMAP_t_Fetch_i> dataItems     = new List<IMAP_t_Fetch_i>();
            bool                 msgDataNeeded = false;

            // Remove parenthesizes.
            string dataItemsString = parts[1].Trim();
            if(dataItemsString.StartsWith("(") && dataItemsString.EndsWith(")")){
                dataItemsString = dataItemsString.Substring(1,dataItemsString.Length - 2).Trim();
            }

            // Replace macros.
            dataItemsString = dataItemsString.Replace("ALL","FLAGS INTERNALDATE RFC822.SIZE ENVELOPE");
            dataItemsString = dataItemsString.Replace("FAST","FLAGS INTERNALDATE RFC822.SIZE"); 
            dataItemsString = dataItemsString.Replace("FULL","FLAGS INTERNALDATE RFC822.SIZE ENVELOPE BODY");

            StringReader r = new StringReader(dataItemsString);

            IMAP_Fetch_DataType fetchDataType = IMAP_Fetch_DataType.MessageHeader;

            // Parse data-items.
            while(r.Available > 0){
                r.ReadToFirstChar();

                #region BODYSTRUCTURE

                if(r.StartsWith("BODYSTRUCTURE",false)){
                    r.ReadWord();
                    dataItems.Add(new IMAP_t_Fetch_i_BodyStructure());
                    msgDataNeeded = true;
                    if(fetchDataType != IMAP_Fetch_DataType.FullMessage){
                        fetchDataType = IMAP_Fetch_DataType.MessageStructure;
                    }
                }

                #endregion

                #region BODY[<section>]<<partial>> and BODY.PEEK[<section>]<<partial>>

                else if(r.StartsWith("BODY[",false) || r.StartsWith("BODY.PEEK[",false)){
                    bool peek = r.StartsWith("BODY.PEEK[",false);
                    r.ReadWord();

                    #region Parse <section>

                    string section = r.ReadParenthesized();
                                                                            
                    // Full message wanted.
                    if(string.IsNullOrEmpty(section)){
                        fetchDataType = IMAP_Fetch_DataType.FullMessage;
                    }
                    else{
                        // Left-side part-items must be numbers, only last one may be (HEADER,HEADER.FIELDS,HEADER.FIELDS.NOT,MIME,TEXT).
                    
                        StringReader rSection = new StringReader(section);
                        string remainingSection = rSection.ReadWord();
                        while(remainingSection.Length > 0){
                            string[] section_parts = remainingSection.Split(new char[]{'.'},2);
                            // Not part number.
                            if(!Net_Utils.IsInteger(section_parts[0])){
                                // We must have one of the following values here (HEADER,HEADER.FIELDS,HEADER.FIELDS.NOT,MIME,TEXT).
                                if(remainingSection.Equals("HEADER",StringComparison.InvariantCultureIgnoreCase)){
                                    if(fetchDataType != IMAP_Fetch_DataType.FullMessage && fetchDataType != IMAP_Fetch_DataType.MessageStructure){
                                        fetchDataType = IMAP_Fetch_DataType.MessageHeader;
                                    }
                                }
                                else if(remainingSection.Equals("HEADER.FIELDS",StringComparison.InvariantCultureIgnoreCase)){
                                    rSection.ReadToFirstChar();
                                    if(!rSection.StartsWith("(")){
                                        WriteLine(cmdTag + " BAD Error in arguments.");

                                        return;
                                    }
                                    rSection.ReadParenthesized();

                                    if(fetchDataType != IMAP_Fetch_DataType.FullMessage && fetchDataType != IMAP_Fetch_DataType.MessageStructure){
                                        fetchDataType = IMAP_Fetch_DataType.MessageHeader;
                                    }
                                }
                                else if(remainingSection.Equals("HEADER.FIELDS.NOT",StringComparison.InvariantCultureIgnoreCase)){
                                    rSection.ReadToFirstChar();
                                    if(!rSection.StartsWith("(")){
                                        WriteLine(cmdTag + " BAD Error in arguments.");

                                        return;
                                    }
                                    rSection.ReadParenthesized();

                                    if(fetchDataType != IMAP_Fetch_DataType.FullMessage && fetchDataType != IMAP_Fetch_DataType.MessageStructure){
                                        fetchDataType = IMAP_Fetch_DataType.MessageHeader;
                                    }
                                }
                                else if(remainingSection.Equals("MIME",StringComparison.InvariantCultureIgnoreCase)){
                                    fetchDataType = IMAP_Fetch_DataType.FullMessage;
                                }
                                else if(remainingSection.Equals("TEXT",StringComparison.InvariantCultureIgnoreCase)){
                                    fetchDataType = IMAP_Fetch_DataType.FullMessage;
                                }
                                // Unknown parts specifier.
                                else{
                                    WriteLine(cmdTag + " BAD Error in arguments.");

                                    return;
                                }

                                break;
                            }
                            else{
                                // For parts specifier, minimum is message structure.
                                if(fetchDataType != IMAP_Fetch_DataType.FullMessage){
                                    fetchDataType = IMAP_Fetch_DataType.MessageStructure;
                                }
                            }

                            if(section_parts.Length == 2){
                                remainingSection = section_parts[1];
                            }
                            else{
                                remainingSection = "";
                            }
                        }
                    }

                    #endregion

                    #region Parse <partial>

                    int offset   = -1;
                    int maxCount = -1;
                    // Partial data wanted.
                    if(r.StartsWith("<")){
                        string[] origin = r.ReadParenthesized().Split('.');
                        if(origin.Length > 2){
                            WriteLine(cmdTag + " BAD Error in arguments.");

                            return;
                        }

                        if(!int.TryParse(origin[0],out offset)){
                            WriteLine(cmdTag + " BAD Error in arguments.");

                            return;
                        }
                        if(origin.Length == 2){
                            if(!int.TryParse(origin[1],out maxCount)){
                                WriteLine(cmdTag + " BAD Error in arguments.");

                                return;
                            }
                        }
                    }

                    #endregion

                    if(peek){
                        dataItems.Add(new IMAP_t_Fetch_i_BodyPeek(section,offset,maxCount));
                    }
                    else{
                        dataItems.Add(new IMAP_t_Fetch_i_Body(section,offset,maxCount));
                    }
                    msgDataNeeded = true;
                }

                #endregion
                
                #region BODY

                else if(r.StartsWith("BODY",false)){
                    r.ReadWord();
                    dataItems.Add(new IMAP_t_Fetch_i_BodyS());
                    msgDataNeeded = true;
                    if(fetchDataType != IMAP_Fetch_DataType.FullMessage){
                        fetchDataType = IMAP_Fetch_DataType.MessageStructure;
                    }
                }

                #endregion

                #region ENVELOPE

                else if(r.StartsWith("ENVELOPE",false)){
                    r.ReadWord();
                    dataItems.Add(new IMAP_t_Fetch_i_Envelope());
                    msgDataNeeded = true;
                    if(fetchDataType != IMAP_Fetch_DataType.FullMessage && fetchDataType != IMAP_Fetch_DataType.MessageStructure){
                        fetchDataType = IMAP_Fetch_DataType.MessageHeader;
                    }
                }

                #endregion
                
                #region FLAGS

                else if(r.StartsWith("FLAGS",false)){
                    r.ReadWord();
                    dataItems.Add(new IMAP_t_Fetch_i_Flags());
                }

                #endregion

                #region INTERNALDATE

                else if(r.StartsWith("INTERNALDATE",false)){
                    r.ReadWord();
                    dataItems.Add(new IMAP_t_Fetch_i_InternalDate());
                }

                #endregion

                #region RFC822.HEADER

                else if(r.StartsWith("RFC822.HEADER",false)){
                    r.ReadWord();
                    dataItems.Add(new IMAP_t_Fetch_i_Rfc822Header());
                    msgDataNeeded = true;
                    if(fetchDataType != IMAP_Fetch_DataType.FullMessage && fetchDataType != IMAP_Fetch_DataType.MessageStructure){
                        fetchDataType = IMAP_Fetch_DataType.MessageHeader;
                    }
                }

                #endregion

                #region RFC822.SIZE

                else if(r.StartsWith("RFC822.SIZE",false)){
                    r.ReadWord();
                    dataItems.Add(new IMAP_t_Fetch_i_Rfc822Size());
                }

                #endregion

                #region RFC822.TEXT

                else if(r.StartsWith("RFC822.TEXT",false)){
                    r.ReadWord();
                    dataItems.Add(new IMAP_t_Fetch_i_Rfc822Text());
                    msgDataNeeded = true;
                    fetchDataType = IMAP_Fetch_DataType.FullMessage;
                }

                #endregion

                #region RFC822

                else if(r.StartsWith("RFC822",false)){
                    r.ReadWord();
                    dataItems.Add(new IMAP_t_Fetch_i_Rfc822());
                    msgDataNeeded = true;
                    fetchDataType = IMAP_Fetch_DataType.FullMessage;
                }

                #endregion

                #region UID

                else if(r.StartsWith("UID",false)){
                    r.ReadWord();
                    dataItems.Add(new IMAP_t_Fetch_i_Uid());
                }

                #endregion

                #region Unknown data-item.

                else{
                    WriteLine(cmdTag + " BAD Error in arguments: Unknown FETCH data-item.");

                    return;
                }

                #endregion
            }

            #endregion

            // UID FETCH must always return UID data-item, even if user didn't request it.
            if(uid){
                bool add = true;
                foreach(IMAP_t_Fetch_i item in dataItems){                    
                    if(item is IMAP_t_Fetch_i_Uid){
                        add = false;
                        break;
                    }
                }
                if(add){
                    dataItems.Add(new IMAP_t_Fetch_i_Uid());
                }
            }

            UpdateSelectedFolderAndSendChanges();

            IMAP_e_Fetch fetchEArgs = new IMAP_e_Fetch(
                m_pSelectedFolder.Filter(uid,seqSet),
                fetchDataType,
                new IMAP_r_ServerStatus(cmdTag,"OK","FETCH command completed in %exectime seconds.")
            );
            fetchEArgs.NewMessageData += new EventHandler<IMAP_e_Fetch.e_NewMessageData>(delegate(object s,IMAP_e_Fetch.e_NewMessageData e){                
                StringBuilder reponseBuffer = new StringBuilder();
                reponseBuffer.Append("* " + e.MessageInfo.SeqNo + " FETCH (");

                Mail_Message message = e.MessageData;

                // Return requested data-items for the returned message.
                for(int i=0;i<dataItems.Count;i++){
                    IMAP_t_Fetch_i dataItem = dataItems[i];
                                      
                    // Add data-items separator.
                    if(i > 0){
                        reponseBuffer.Append(" ");
                    }
                                       
                    #region BODY

                    if(dataItem is IMAP_t_Fetch_i_BodyS){
                        reponseBuffer.Append(ConstructBodyStructure(message,false));
                    }

                    #endregion

                    #region BODY[<section>]<<partial>> and BODY.PEEK[<section>]<<partial>>

                    else if(dataItem is IMAP_t_Fetch_i_Body || dataItem is IMAP_t_Fetch_i_BodyPeek){
                        string section  = "";
                        int    offset   = -1;
                        int    maxCount = -1;
                        if(dataItem is IMAP_t_Fetch_i_Body){
                            section  = ((IMAP_t_Fetch_i_Body)dataItem).Section;
                            offset   = ((IMAP_t_Fetch_i_Body)dataItem).Offset;
                            maxCount = ((IMAP_t_Fetch_i_Body)dataItem).MaxCount;
                        }
                        else{
                            section  = ((IMAP_t_Fetch_i_BodyPeek)dataItem).Section;
                            offset   = ((IMAP_t_Fetch_i_BodyPeek)dataItem).Offset;
                            maxCount = ((IMAP_t_Fetch_i_BodyPeek)dataItem).MaxCount;
                        }

                        using(MemoryStreamEx tmpFs = new MemoryStreamEx(32000)){
                            // Empty section, full message wanted.
                            if(string.IsNullOrEmpty(section)){
                                message.ToStream(tmpFs,new MIME_Encoding_EncodedWord(MIME_EncodedWordEncoding.B,Encoding.UTF8),Encoding.UTF8);
                                tmpFs.Position = 0;
                            }
                            // Message data part wanted.
                            else{
                                // Get specified MIME part.
                                MIME_Entity entity = GetMimeEntity(message,ParsePartNumberFromSection(section));
                                if(entity != null){
                                    string partSpecifier = ParsePartSpecifierFromSection(section);

                                    #region HEADER

                                    if(string.Equals(partSpecifier,"HEADER",StringComparison.InvariantCultureIgnoreCase)){                                        
                                        entity.Header.ToStream(tmpFs,new MIME_Encoding_EncodedWord(MIME_EncodedWordEncoding.B,Encoding.UTF8),Encoding.UTF8);
                                        // All header fetches must include header terminator(CRLF).
                                        if(tmpFs.Length >0 ){
                                            tmpFs.WriteByte((byte)'\r');
                                            tmpFs.WriteByte((byte)'\n');
                                        }
                                        tmpFs.Position = 0;
                                    }

                                    #endregion

                                    #region HEADER.FIELDS

                                    else if(string.Equals(partSpecifier,"HEADER.FIELDS",StringComparison.InvariantCultureIgnoreCase)){                            
                                        string   fieldsString = section.Split(new char[]{' '},2)[1];
                                        string[] fieldNames   = fieldsString.Substring(1,fieldsString.Length - 2).Split(' ');
                                        foreach(string filedName in fieldNames){
                                            MIME_h[] fields = entity.Header[filedName];
                                            if(fields != null){
                                                foreach(MIME_h field in fields){
                                                    byte[] fieldBytes = Encoding.UTF8.GetBytes(field.ToString(new MIME_Encoding_EncodedWord(MIME_EncodedWordEncoding.B,Encoding.UTF8),Encoding.UTF8));
                                                    tmpFs.Write(fieldBytes,0,fieldBytes.Length);
                                                }
                                            }
                                        }
                                        // All header fetches must include header terminator(CRLF).
                                        if(tmpFs.Length > 0){
                                            tmpFs.WriteByte((byte)'\r');
                                            tmpFs.WriteByte((byte)'\n');
                                        }
                                        tmpFs.Position = 0;
                                    }

                                    #endregion

                                    #region HEADER.FIELDS.NOT

                                    else if(string.Equals(partSpecifier,"HEADER.FIELDS.NOT",StringComparison.InvariantCultureIgnoreCase)){
                                        string   fieldsString = section.Split(new char[]{' '},2)[1];
                                        string[] fieldNames   = fieldsString.Substring(1,fieldsString.Length - 2).Split(' ');
                                        foreach(MIME_h field in entity.Header){
                                            bool contains = false;
                                            foreach(string fieldName in fieldNames){
                                                if(string.Equals(field.Name,fieldName,StringComparison.InvariantCultureIgnoreCase)){
                                                    contains = true;
                                                    break;
                                                }
                                            }

                                            if(!contains){
                                                byte[] fieldBytes = Encoding.UTF8.GetBytes(field.ToString(new MIME_Encoding_EncodedWord(MIME_EncodedWordEncoding.B,Encoding.UTF8),Encoding.UTF8));
                                                tmpFs.Write(fieldBytes,0,fieldBytes.Length);
                                            }
                                        }
                                        // All header fetches must include header terminator(CRLF).
                                        if(tmpFs.Length >0 ){
                                            tmpFs.WriteByte((byte)'\r');
                                            tmpFs.WriteByte((byte)'\n');
                                        }
                                        tmpFs.Position = 0;
                                    }

                                    #endregion

                                    #region MIME

                                    else if(string.Equals(partSpecifier,"MIME",StringComparison.InvariantCultureIgnoreCase)){
                                        entity.Header.ToStream(tmpFs,new MIME_Encoding_EncodedWord(MIME_EncodedWordEncoding.B,Encoding.UTF8),Encoding.UTF8);
                                        // All header fetches must include header terminator(CRLF).
                                        if(tmpFs.Length >0 ){
                                            tmpFs.WriteByte((byte)'\r');
                                            tmpFs.WriteByte((byte)'\n');
                                        }
                                        tmpFs.Position = 0;
                                    }

                                    #endregion

                                    #region TEXT

                                    else if(string.Equals(partSpecifier,"TEXT",StringComparison.InvariantCultureIgnoreCase)){
                                        entity.Body.ToStream(tmpFs,new MIME_Encoding_EncodedWord(MIME_EncodedWordEncoding.B,Encoding.UTF8),Encoding.UTF8,false);
                                        tmpFs.Position = 0;
                                    }

                                    #endregion

                                    #region part-number only

                                    else{
                                        entity.Body.ToStream(tmpFs,new MIME_Encoding_EncodedWord(MIME_EncodedWordEncoding.B,Encoding.UTF8),Encoding.UTF8,false);
                                        tmpFs.Position = 0;
                                    }

                                    #endregion
                                }
                            }

                            #region Send data

                            // All data wanted.
                            if(offset < 0){
                                reponseBuffer.Append("BODY[" + section + "] {" + tmpFs.Length + "}\r\n");
                                WriteLine(reponseBuffer.ToString());
                                reponseBuffer = new StringBuilder();

                                this.TcpStream.WriteStream(tmpFs);
                                LogAddWrite(tmpFs.Length,"Wrote " + tmpFs.Length + " bytes.");
                            }
                            // Partial data wanted.
                            else{                                    
                                // Offet out of range.
                                if(offset >= tmpFs.Length){
                                    reponseBuffer.Append("BODY[" + section + "]<" + offset + "> \"\"");
                                }
                                else{
                                    tmpFs.Position = offset;
                                        
                                    int count = maxCount > -1 ? (int)Math.Min(maxCount,tmpFs.Length - tmpFs.Position) : (int)(tmpFs.Length - tmpFs.Position);
                                    reponseBuffer.Append("BODY[" + section + "]<" + offset + "> {" + count + "}");
                                    WriteLine(reponseBuffer.ToString());
                                    reponseBuffer = new StringBuilder();

                                    this.TcpStream.WriteStream(tmpFs,count);
                                    LogAddWrite(tmpFs.Length,"Wrote " + count + " bytes.");
                                }
                            }

                            #endregion
                        }

                        // Set Seen flag.
                        if(!m_pSelectedFolder.IsReadOnly && dataItem is IMAP_t_Fetch_i_Body){
                            try{
                                OnStore(e.MessageInfo,IMAP_Flags_SetType.Add,new string[]{"Seen"},new IMAP_r_ServerStatus("dummy","OK","This is FETCH set Seen flag, this response not used."));
                            }
                            catch{
                            }
                        }
                    }

                    #endregion

                    #region BODYSTRUCTURE

                    else if(dataItem is IMAP_t_Fetch_i_BodyStructure){
                        reponseBuffer.Append(ConstructBodyStructure(message,true));
                    }

                    #endregion

                    #region ENVELOPE

                    else if(dataItem is IMAP_t_Fetch_i_Envelope){
                        reponseBuffer.Append(IMAP_t_Fetch_r_i_Envelope.ConstructEnvelope(message));
                    }

                    #endregion

                    #region FLAGS

                    else if(dataItem is IMAP_t_Fetch_i_Flags){
                        reponseBuffer.Append("FLAGS " + e.MessageInfo.FlagsToImapString());
                    }

                    #endregion

                    #region INTERNALDATE

                    else if(dataItem is IMAP_t_Fetch_i_InternalDate){
                        reponseBuffer.Append("INTERNALDATE \"" + IMAP_Utils.DateTimeToString(e.MessageInfo.InternalDate) + "\"");
                    }

                    #endregion

                    #region RFC822

                    else if(dataItem is IMAP_t_Fetch_i_Rfc822){
                        using(MemoryStreamEx tmpFs = new MemoryStreamEx(32000)){
                            message.ToStream(tmpFs,new MIME_Encoding_EncodedWord(MIME_EncodedWordEncoding.B,Encoding.UTF8),Encoding.UTF8);
                            tmpFs.Position = 0;

                            reponseBuffer.Append("RFC822 {" + tmpFs.Length + "}\r\n");
                            WriteLine(reponseBuffer.ToString());
                            reponseBuffer = new StringBuilder();

                            this.TcpStream.WriteStream(tmpFs);
                            LogAddWrite(tmpFs.Length,"Wrote " + tmpFs.Length + " bytes.");
                        }
                    }

                    #endregion

                    #region RFC822.HEADER

                    else if(dataItem is IMAP_t_Fetch_i_Rfc822Header){
                        MemoryStream ms = new MemoryStream();
                        message.Header.ToStream(ms,new MIME_Encoding_EncodedWord(MIME_EncodedWordEncoding.B,Encoding.UTF8),Encoding.UTF8);
                        ms.Position = 0;

                        reponseBuffer.Append("RFC822.HEADER {" + ms.Length + "}\r\n");
                        WriteLine(reponseBuffer.ToString());
                        reponseBuffer = new StringBuilder();

                        this.TcpStream.WriteStream(ms);
                        LogAddWrite(ms.Length,"Wrote " + ms.Length + " bytes.");
                    }

                    #endregion

                    #region RFC822.SIZE

                    else if(dataItem is IMAP_t_Fetch_i_Rfc822Size){
                        reponseBuffer.Append("RFC822.SIZE " + e.MessageInfo.Size);
                    }

                    #endregion

                    #region RFC822.TEXT

                    else if(dataItem is IMAP_t_Fetch_i_Rfc822Text){
                        using(MemoryStreamEx tmpFs = new MemoryStreamEx(32000)){
                            message.Body.ToStream(tmpFs,new MIME_Encoding_EncodedWord(MIME_EncodedWordEncoding.B,Encoding.UTF8),Encoding.UTF8,false);
                            tmpFs.Position = 0;

                            reponseBuffer.Append("RFC822.TEXT {" + tmpFs.Length + "}\r\n");
                            WriteLine(reponseBuffer.ToString());
                            reponseBuffer = new StringBuilder();

                            this.TcpStream.WriteStream(tmpFs);
                            LogAddWrite(tmpFs.Length,"Wrote " + tmpFs.Length + " bytes.");
                        }
                    }

                    #endregion

                    #region UID

                    else if(dataItem is IMAP_t_Fetch_i_Uid){ 
                        reponseBuffer.Append("UID " + e.MessageInfo.UID);
                    }

                    #endregion
                }

                reponseBuffer.Append(")\r\n");            
                WriteLine(reponseBuffer.ToString());
            });
                        
            // We have all needed data in message info.
            if(!msgDataNeeded){
                foreach(IMAP_MessageInfo msgInfo in m_pSelectedFolder.Filter(uid,seqSet)){
                    fetchEArgs.AddData(msgInfo);
                }
            }
            // Request messages data.
            else{
                OnFetch(fetchEArgs);
            }
                                    
            WriteLine(fetchEArgs.Response.ToString().Replace("%exectime",((DateTime.Now.Ticks - startTime) / (decimal)10000000).ToString("f2")));
        }

        #endregion

        #region method SEARCH

        private void SEARCH(bool uid,string cmdTag,string cmdText)
        {
            /* RFC 3501 6.4.4. SEARCH Command.
                Arguments:  OPTIONAL [CHARSET] specification
                            searching criteria (one or more)

                Responses:  REQUIRED untagged response: SEARCH

                Result:     OK - search completed
                            NO - search error: can't search that [CHARSET] or criteria
                            BAD - command unknown or arguments invalid

                The SEARCH command searches the mailbox for messages that match
                the given searching criteria.  Searching criteria consist of one
                or more search keys.  The untagged SEARCH response from the server
                contains a listing of message sequence numbers corresponding to
                those messages that match the searching criteria.
            
                When multiple keys are specified, the result is the intersection
                (AND function) of all the messages that match those keys.  For
                example, the criteria DELETED FROM "SMITH" SINCE 1-Feb-1994 refers
                to all deleted messages from Smith that were placed in the mailbox
                since February 1, 1994.  A search key can also be a parenthesized
                list of one or more search keys (e.g., for use with the OR and NOT
                keys).

                Server implementations MAY exclude [MIME-IMB] body parts with
                terminal content media types other than TEXT and MESSAGE from
                consideration in SEARCH matching.

                The OPTIONAL [CHARSET] specification consists of the word
                "CHARSET" followed by a registered [CHARSET].  It indicates the
                [CHARSET] of the strings that appear in the search criteria.
                [MIME-IMB] content transfer encodings, and [MIME-HDRS] strings in
                [RFC-2822]/[MIME-IMB] headers, MUST be decoded before comparing
                text in a [CHARSET] other than US-ASCII.  US-ASCII MUST be
                supported; other [CHARSET]s MAY be supported.

                If the server does not support the specified [CHARSET], it MUST
                return a tagged NO response (not a BAD).  This response SHOULD
                contain the BADCHARSET response code, which MAY list the
                [CHARSET]s supported by the server.

                In all search keys that use strings, a message matches the key if
                the string is a substring of the field.  The matching is
                case-insensitive.

                The defined search keys are as follows.  Refer to the Formal
                Syntax section for the precise syntactic definitions of the
                arguments.

                <sequence set>
                    Messages with message sequence numbers corresponding to the
                    specified message sequence number set.

                ALL
                    All messages in the mailbox; the default initial key for ANDing.

                ANSWERED
                    Messages with the \Answered flag set.

                BCC <string>
                    Messages that contain the specified string in the envelope
                    structure's BCC field.

                BEFORE <date>
                    Messages whose internal date (disregarding time and timezone)
                    is earlier than the specified date.

                BODY <string>
                    Messages that contain the specified string in the body of the
                    message.

                CC <string>
                    Messages that contain the specified string in the envelope
                    structure's CC field.

                DELETED
                    Messages with the \Deleted flag set.

                DRAFT
                    Messages with the \Draft flag set.

                FLAGGED
                    Messages with the \Flagged flag set.

                FROM <string>
                    Messages that contain the specified string in the envelope
                    structure's FROM field.

                HEADER <field-name> <string>
                    Messages that have a header with the specified field-name (as
                    defined in [RFC-2822]) and that contains the specified string
                    in the text of the header (what comes after the colon).  If the
                    string to search is zero-length, this matches all messages that
                    have a header line with the specified field-name regardless of
                    the contents.

                KEYWORD <flag>
                    Messages with the specified keyword flag set.

                LARGER <n>
                    Messages with an [RFC-2822] size larger than the specified
                    number of octets.

                NEW
                    Messages that have the \Recent flag set but not the \Seen flag.
                    This is functionally equivalent to "(RECENT UNSEEN)".

                NOT <search-key>
                    Messages that do not match the specified search key.

                OLD
                    Messages that do not have the \Recent flag set.  This is
                    functionally equivalent to "NOT RECENT" (as opposed to "NOT NEW").

                ON <date>
                    Messages whose internal date (disregarding time and timezone)
                    is within the specified date.

                OR <search-key1> <search-key2>
                    Messages that match either search key.

                RECENT
                    Messages that have the \Recent flag set.

                SEEN
                    Messages that have the \Seen flag set.

                SENTBEFORE <date>
                    Messages whose [RFC-2822] Date: header (disregarding time and
                    timezone) is earlier than the specified date.

                SENTON <date>
                    Messages whose [RFC-2822] Date: header (disregarding time and
                    timezone) is within the specified date.

                SENTSINCE <date>
                    Messages whose [RFC-2822] Date: header (disregarding time and
                    timezone) is within or later than the specified date.

                SINCE <date>
                    Messages whose internal date (disregarding time and timezone)
                    is within or later than the specified date.

                SMALLER <n>
                    Messages with an [RFC-2822] size smaller than the specified
                    number of octets.

                SUBJECT <string>
                    Messages that contain the specified string in the envelope
                    structure's SUBJECT field.

                TEXT <string>
                    Messages that contain the specified string in the header or
                    body of the message.

                TO <string>
                    Messages that contain the specified string in the envelope
                    structure's TO field.

                UID <sequence set>
                    Messages with unique identifiers corresponding to the specified
                    unique identifier set.  Sequence set ranges are permitted.

                UNANSWERED
                    Messages that do not have the \Answered flag set.

                UNDELETED
                    Messages that do not have the \Deleted flag set.

                UNDRAFT
                    Messages that do not have the \Draft flag set.

                UNFLAGGED
                    Messages that do not have the \Flagged flag set.

                UNKEYWORD <flag>
                    Messages that do not have the specified keyword flag set.

                UNSEEN
                    Messages that do not have the \Seen flag set.

                Example:    C: A282 SEARCH FLAGGED SINCE 1-Feb-1994 NOT FROM "Smith"
                            S: * SEARCH 2 84 882
                            S: A282 OK SEARCH completed
                            C: A283 SEARCH TEXT "string not in mailbox"
                            S: * SEARCH
                            S: A283 OK SEARCH completed
                            C: A284 SEARCH CHARSET UTF-8 TEXT {6}
                            C: XXXXXX
                            S: * SEARCH 43
                            S: A284 OK SEARCH completed
            */

            if(!this.IsAuthenticated){
                m_pResponseSender.SendResponseAsync(new IMAP_r_ServerStatus(cmdTag,"NO","Authentication required."));

                return;
            }
            if(m_pSelectedFolder == null){
                m_pResponseSender.SendResponseAsync(new IMAP_r_ServerStatus(cmdTag,"NO","Error: This command is valid only in selected state."));

                return;
            }

            // Store start time
			long startTime = DateTime.Now.Ticks;

            #region Parse arguments

            StringReader r = new StringReader(cmdText);
            
            // See if we have optional CHARSET argument.
            if(r.StartsWith("CHARSET",false)){
                r.ReadWord();

                string charset = r.ReadWord();
                if(!(string.Equals(charset,"US-ASCII",StringComparison.InvariantCultureIgnoreCase) || string.Equals(charset,"UTF-8",StringComparison.InvariantCultureIgnoreCase))){
                    m_pResponseSender.SendResponseAsync(new IMAP_r_ServerStatus(cmdTag,"NO",new IMAP_t_orc_BadCharset(new string[]{"US-ASCII","UTF-8"}),"Not supported charset."));

                    return;
                }
            }

            #endregion

            try{
                IMAP_Search_Key_Group criteria = IMAP_Search_Key_Group.Parse(r);

                UpdateSelectedFolderAndSendChanges();
                
                List<int> matchedValues = new List<int>();

                IMAP_e_Search searchArgs = new IMAP_e_Search(criteria,new IMAP_r_ServerStatus(cmdTag,"OK","SEARCH completed in %exectime seconds."));
                searchArgs.Matched += new EventHandler<EventArgs<long>>(delegate(object s,EventArgs<long> e){
                    if(uid){
                        matchedValues.Add((int)e.Value);
                    }
                    else{
                        // Search sequence-number for that message.
                        int seqNo = m_pSelectedFolder.GetSeqNo(e.Value);
                        if(seqNo != -1){
                            matchedValues.Add(seqNo);
                        }
                    }                    
                });
                OnSearch(searchArgs);

                m_pResponseSender.SendResponseAsync(new IMAP_r_u_Search(matchedValues.ToArray()));
                m_pResponseSender.SendResponseAsync(IMAP_r_ServerStatus.Parse(searchArgs.Response.ToString().TrimEnd().Replace("%exectime",((DateTime.Now.Ticks - startTime) / (decimal)10000000).ToString("f2"))));
            }
            catch{
                m_pResponseSender.SendResponseAsync(new IMAP_r_ServerStatus(cmdTag,"BAD","Error in arguments."));
            }            
        }

        #endregion

        #region method STORE

        private void STORE(bool uid,string cmdTag,string cmdText)
        {
            /* RFC 3501 6.4.6. STORE Command.
                Arguments:  sequence set
                            message data item name
                            value for message data item

                Responses:  untagged responses: FETCH

                Result:     OK - store completed
                            NO - store error: can't store that data
                            BAD - command unknown or arguments invalid

                The STORE command alters data associated with a message in the
                mailbox.  Normally, STORE will return the updated value of the
                data with an untagged FETCH response.  A suffix of ".SILENT" in
                the data item name prevents the untagged FETCH, and the server
                SHOULD assume that the client has determined the updated value
                itself or does not care about the updated value.

                    Note: Regardless of whether or not the ".SILENT" suffix
                    was used, the server SHOULD send an untagged FETCH
                    response if a change to a message's flags from an
                    external source is observed.  The intent is that the
                    status of the flags is determinate without a race
                    condition.

                The currently defined data items that can be stored are:

                FLAGS <flag list>
                    Replace the flags for the message (other than \Recent) with the
                    argument.  The new value of the flags is returned as if a FETCH
                    of those flags was done.

                FLAGS.SILENT <flag list>
                    Equivalent to FLAGS, but without returning a new value.

                +FLAGS <flag list>
                    Add the argument to the flags for the message.  The new value
                    of the flags is returned as if a FETCH of those flags was done.

                +FLAGS.SILENT <flag list>
                    Equivalent to +FLAGS, but without returning a new value.

                -FLAGS <flag list>
                    Remove the argument from the flags for the message.  The new
                    value of the flags is returned as if a FETCH of those flags was
                    done.

                -FLAGS.SILENT <flag list>
                    Equivalent to -FLAGS, but without returning a new value.

                Example:    C: A003 STORE 2:4 +FLAGS (\Deleted)
                            S: * 2 FETCH (FLAGS (\Deleted \Seen))
                            S: * 3 FETCH (FLAGS (\Deleted))
                            S: * 4 FETCH (FLAGS (\Deleted \Flagged \Seen))
                            S: A003 OK STORE completed
            */

            // Store start time
			long startTime = DateTime.Now.Ticks;

            if(!this.IsAuthenticated){
                m_pResponseSender.SendResponseAsync(new IMAP_r_ServerStatus(cmdTag,"NO","Authentication required."));

                return;
            }
            if(m_pSelectedFolder == null){
                m_pResponseSender.SendResponseAsync(new IMAP_r_ServerStatus(cmdTag,"NO","Error: This command is valid only in selected state."));

                return;
            }

            #region Parse arguments

            string[] parts = cmdText.Split(new char[]{' '},3);
            if(parts.Length != 3){
                m_pResponseSender.SendResponseAsync(new IMAP_r_ServerStatus(cmdTag,"BAD","Error in arguments."));

                return;
            }

            IMAP_t_SeqSet seqSet = null;
            try{                
                seqSet = IMAP_t_SeqSet.Parse(parts[0]);
            }
            catch{
                m_pResponseSender.SendResponseAsync(new IMAP_r_ServerStatus(cmdTag,"BAD","Error in arguments."));

                return;
            }
         
            IMAP_Flags_SetType setType;
            bool               silent = false;
            if(string.Equals(parts[1],"FLAGS",StringComparison.InvariantCultureIgnoreCase)){
                setType = IMAP_Flags_SetType.Replace;
            }
            else if(string.Equals(parts[1],"FLAGS.SILENT",StringComparison.InvariantCultureIgnoreCase)){
                setType = IMAP_Flags_SetType.Replace;
                silent = true;
            }
            else if(string.Equals(parts[1],"+FLAGS",StringComparison.InvariantCultureIgnoreCase)){
                setType = IMAP_Flags_SetType.Add;
            }
            else if(string.Equals(parts[1],"+FLAGS.SILENT",StringComparison.InvariantCultureIgnoreCase)){
                setType = IMAP_Flags_SetType.Add;
                silent = true;
            }
            else if(string.Equals(parts[1],"-FLAGS",StringComparison.InvariantCultureIgnoreCase)){
                setType = IMAP_Flags_SetType.Remove;
            }
            else if(string.Equals(parts[1],"-FLAGS.SILENT",StringComparison.InvariantCultureIgnoreCase)){
                setType = IMAP_Flags_SetType.Remove;
                silent = true;
            }
            else{
                m_pResponseSender.SendResponseAsync(new IMAP_r_ServerStatus(cmdTag,"BAD","Error in arguments."));

                return;
            }                       

            if(!(parts[2].StartsWith("(") && parts[2].EndsWith(")"))){
                m_pResponseSender.SendResponseAsync(new IMAP_r_ServerStatus(cmdTag,"BAD","Error in arguments."));

                return;
            }
            List<string> flags = new List<string>();
            foreach(string f in parts[2].Substring(1,parts[2].Length - 2).Split(' ')){
                if(f.Length > 0 && !flags.Contains(f.Substring(1))){
                    flags.Add(f.Substring(1));
                }
            }

            #endregion

            IMAP_r_ServerStatus response = new IMAP_r_ServerStatus(cmdTag,"OK","STORE command completed in %exectime seconds.");
            foreach(IMAP_MessageInfo msgInfo in m_pSelectedFolder.Filter(uid,seqSet)){
                IMAP_e_Store e = OnStore(msgInfo,setType,flags.ToArray(),response);
                response = e.Response;
                if(!string.Equals(e.Response.ResponseCode,"OK",StringComparison.InvariantCultureIgnoreCase)){
                    break;
                }

                // Update local message info flags value.
                msgInfo.UpdateFlags(setType,flags.ToArray());

                if(!silent){
                    // UID STORE must include UID. For more info see RFC 3501 6.4.8. UID Command.
                    if(uid){
                        m_pResponseSender.SendResponseAsync(
                            new IMAP_r_u_Fetch(
                                m_pSelectedFolder.GetSeqNo(msgInfo),
                                new IMAP_t_Fetch_r_i[]{
                                    new IMAP_t_Fetch_r_i_Flags(IMAP_t_MsgFlags.Parse(msgInfo.FlagsToImapString())),
                                    new IMAP_t_Fetch_r_i_Uid(msgInfo.UID)
                                }
                            )
                        );
                    }
                    else{
                        m_pResponseSender.SendResponseAsync(
                            new IMAP_r_u_Fetch(
                                m_pSelectedFolder.GetSeqNo(msgInfo),
                                new IMAP_t_Fetch_r_i[]{
                                    new IMAP_t_Fetch_r_i_Flags(new IMAP_t_MsgFlags(msgInfo.Flags))
                                }
                            )
                        );
                    }
                }
            }

            m_pResponseSender.SendResponseAsync(IMAP_r_ServerStatus.Parse(response.ToString().TrimEnd().Replace("%exectime",((DateTime.Now.Ticks - startTime) / (decimal)10000000).ToString("f2"))));
        }

        #endregion

        #region method COPY

        private void COPY(bool uid,string cmdTag,string cmdText)
        {
            /* RFC 3501 6.4.7. COPY Command.
                Arguments:  sequence set
                            mailbox name

                Responses:  no specific responses for this command

                Result:     OK - copy completed
                            NO - copy error: can't copy those messages or to that
                                 name
                            BAD - command unknown or arguments invalid

                The COPY command copies the specified message(s) to the end of the
                specified destination mailbox.  The flags and internal date of the
                message(s) SHOULD be preserved, and the Recent flag SHOULD be set,
                in the copy.

                If the destination mailbox does not exist, a server SHOULD return
                an error.  It SHOULD NOT automatically create the mailbox.  Unless
                it is certain that the destination mailbox can not be created, the
                server MUST send the response code "[TRYCREATE]" as the prefix of
                the text of the tagged NO response.  This gives a hint to the
                client that it can attempt a CREATE command and retry the COPY if
                the CREATE is successful.

                If the COPY command is unsuccessful for any reason, server
                implementations MUST restore the destination mailbox to its state
                before the COPY attempt.

                Example:    C: A003 COPY 2:4 MEETING
                            S: A003 OK COPY completed
            */

            if(!this.IsAuthenticated){
                m_pResponseSender.SendResponseAsync(new IMAP_r_ServerStatus(cmdTag,"NO","Authentication required."));

                return;
            }
            if(m_pSelectedFolder == null){
                m_pResponseSender.SendResponseAsync(new IMAP_r_ServerStatus(cmdTag,"NO","Error: This command is valid only in selected state."));

                return;
            }

            string[] parts = cmdText.Split(new char[]{' '},2);
            if(parts.Length != 2){
                m_pResponseSender.SendResponseAsync(new IMAP_r_ServerStatus(cmdTag,"BAD","Error in arguments."));

                return;
            }
            IMAP_t_SeqSet seqSet = null;
            try{                
                seqSet = IMAP_t_SeqSet.Parse(parts[0]);
            }
            catch{
                m_pResponseSender.SendResponseAsync(new IMAP_r_ServerStatus(cmdTag,"BAD","Error in arguments."));

                return;
            }
            string targetFolder = IMAP_Utils.DecodeMailbox(TextUtils.UnQuoteString(parts[1]));

            UpdateSelectedFolderAndSendChanges();

            IMAP_e_Copy e = OnCopy(
                targetFolder,
                m_pSelectedFolder.Filter(uid,seqSet),
                new IMAP_r_ServerStatus(cmdTag,"OK","COPY completed.")
            );
            m_pResponseSender.SendResponseAsync(e.Response);
        }

        #endregion

        #region method UID

        private void UID(string cmdTag,string cmdText)
        {
            /* RFC 3501 6.4.8. UID Command.
                Arguments:  command name
                            command arguments

                Responses:  untagged responses: FETCH, SEARCH

                Result:     OK - UID command completed
                            NO - UID command error
                            BAD - command unknown or arguments invalid

                The UID command has two forms.  In the first form, it takes as its
                arguments a COPY, FETCH, or STORE command with arguments
                appropriate for the associated command.  However, the numbers in
                the sequence set argument are unique identifiers instead of
                message sequence numbers.  Sequence set ranges are permitted, but
                there is no guarantee that unique identifiers will be contiguous.

                A non-existent unique identifier is ignored without any error
                message generated.  Thus, it is possible for a UID FETCH command
                to return an OK without any data or a UID COPY or UID STORE to
                return an OK without performing any operations.

                In the second form, the UID command takes a SEARCH command with
                SEARCH command arguments.  The interpretation of the arguments is
                the same as with SEARCH; however, the numbers returned in a SEARCH
                response for a UID SEARCH command are unique identifiers instead
                of message sequence numbers.  For example, the command UID SEARCH
                1:100 UID 443:557 returns the unique identifiers corresponding to
                the intersection of two sequence sets, the message sequence number
                range 1:100 and the UID range 443:557.

                    Note: in the above example, the UID range 443:557
                    appears.  The same comment about a non-existent unique
                    identifier being ignored without any error message also
                    applies here.  Hence, even if neither UID 443 or 557
                    exist, this range is valid and would include an existing
                    UID 495.

                Also note that a UID range of 559:* always includes the
                UID of the last message in the mailbox, even if 559 is
                higher than any assigned UID value.  This is because the
                contents of a range are independent of the order of the
                range endpoints.  Thus, any UID range with * as one of
                the endpoints indicates at least one message (the
                message with the highest numbered UID), unless the
                mailbox is empty.

                The number after the "*" in an untagged FETCH response is always a
                message sequence number, not a unique identifier, even for a UID
                command response.  However, server implementations MUST implicitly
                include the UID message data item as part of any FETCH response
                caused by a UID command, regardless of whether a UID was specified
                as a message data item to the FETCH.

                    Note: The rule about including the UID message data item as part
                    of a FETCH response primarily applies to the UID FETCH and UID
                    STORE commands, including a UID FETCH command that does not
                    include UID as a message data item.  Although it is unlikely that
                    the other UID commands will cause an untagged FETCH, this rule
                    applies to these commands as well.

                Example:    C: A999 UID FETCH 4827313:4828442 FLAGS
                            S: * 23 FETCH (FLAGS (\Seen) UID 4827313)
                            S: * 24 FETCH (FLAGS (\Seen) UID 4827943)
                            S: * 25 FETCH (FLAGS (\Seen) UID 4828442)
                            S: A999 OK UID FETCH completed
            */

            if(!this.IsAuthenticated){
                m_pResponseSender.SendResponseAsync(new IMAP_r_ServerStatus(cmdTag,"NO","Authentication required."));

                return;
            }
            if(m_pSelectedFolder == null){
                m_pResponseSender.SendResponseAsync(new IMAP_r_ServerStatus(cmdTag,"NO","Error: This command is valid only in selected state."));

                return;
            }

            string[] cmd_cmtText = cmdText.Split(new char[]{' '},2);
                        
            if(string.Equals(cmd_cmtText[0],"COPY",StringComparison.InvariantCultureIgnoreCase)){
                COPY(true,cmdTag,cmd_cmtText[1]);
            }
            else if(string.Equals(cmd_cmtText[0],"FETCH",StringComparison.InvariantCultureIgnoreCase)){
                FETCH(true,cmdTag,cmd_cmtText[1]);
            }
            else if(string.Equals(cmd_cmtText[0],"STORE",StringComparison.InvariantCultureIgnoreCase)){
                STORE(true,cmdTag,cmd_cmtText[1]);
            }
            else if(string.Equals(cmd_cmtText[0],"SEARCH",StringComparison.InvariantCultureIgnoreCase)){
                SEARCH(true,cmdTag,cmd_cmtText[1]);
            }
            else{
                m_pResponseSender.SendResponseAsync(new IMAP_r_ServerStatus(cmdTag,"BAD","Error in arguments."));
            }
        }

        #endregion

        #region method EXPUNGE

        private void EXPUNGE(string cmdTag,string cmdText)
        {
            /* RFC 3501 6.4.3. EXPUNGE Command.
                Arguments:  none

                Responses:  untagged responses: EXPUNGE

                Result:     OK - expunge completed
                            NO - expunge failure: can't expunge (e.g., permission
                                 denied)
                            BAD - command unknown or arguments invalid

                The EXPUNGE command permanently removes all messages that have the
                \Deleted flag set from the currently selected mailbox.  Before
                returning an OK to the client, an untagged EXPUNGE response is
                sent for each message that is removed.

                Example:    C: A202 EXPUNGE
                            S: * 3 EXPUNGE
                            S: * 3 EXPUNGE
                            S: * 5 EXPUNGE
                            S: * 8 EXPUNGE
                            S: A202 OK EXPUNGE completed

                    Note: In this example, messages 3, 4, 7, and 11 had the
                    \Deleted flag set.  See the description of the EXPUNGE
                    response for further explanation.
            */

            if(!this.IsAuthenticated){
                m_pResponseSender.SendResponseAsync(new IMAP_r_ServerStatus(cmdTag,"NO","Authentication required."));

                return;
            }
            if(m_pSelectedFolder == null){
                m_pResponseSender.SendResponseAsync(new IMAP_r_ServerStatus(cmdTag,"NO","Error: This command is valid only in selected state."));

                return;
            }

            // Store start time
			long startTime = DateTime.Now.Ticks;
            
            IMAP_r_ServerStatus response = new IMAP_r_ServerStatus(cmdTag,"OK","EXPUNGE completed in " + ((DateTime.Now.Ticks - startTime) / (decimal)10000000).ToString("f2") + " seconds.");
            for(int i=0;i<m_pSelectedFolder.MessagesInfo.Length;i++){
                IMAP_MessageInfo msgInfo = m_pSelectedFolder.MessagesInfo[i];
                if(msgInfo.ContainsFlag("Deleted")){
                    IMAP_e_Expunge e = OnExpunge(msgInfo,response);
                    // Expunge failed.
                    if(!string.Equals(e.Response.ResponseCode,"OK",StringComparison.InvariantCultureIgnoreCase)){
                        m_pResponseSender.SendResponseAsync(e.Response);

                        return;
                    }
                    m_pSelectedFolder.RemoveMessage(msgInfo);

                    m_pResponseSender.SendResponseAsync(new IMAP_r_u_Expunge(i + 1));
                }
            }
            m_pSelectedFolder.Reindex();
            
            m_pResponseSender.SendResponseAsync(response);
        }

        #endregion

        #region method IDLE

        private bool IDLE(string cmdTag,string cmdText)
        {
            /* RFC 2177 3. IDLE Command.
                Arguments:  none

                Responses:  continuation data will be requested; the client sends
                            the continuation data "DONE" to end the command

                Result:     OK - IDLE completed after client sent "DONE"
                            NO - failure: the server will not allow the IDLE
                            command at this time
                BAD - command unknown or arguments invalid

                The IDLE command may be used with any IMAP4 server implementation
                that returns "IDLE" as one of the supported capabilities to the
                CAPABILITY command.  If the server does not advertise the IDLE
                capability, the client MUST NOT use the IDLE command and must poll
                for mailbox updates.  In particular, the client MUST continue to be
                able to accept unsolicited untagged responses to ANY command, as
                specified in the base IMAP specification.

                The IDLE command is sent from the client to the server when the
                client is ready to accept unsolicited mailbox update messages.  The
                server requests a response to the IDLE command using the continuation
                ("+") response.  The IDLE command remains active until the client
                responds to the continuation, and as long as an IDLE command is
                active, the server is now free to send untagged EXISTS, EXPUNGE, and
                other messages at any time.

                The IDLE command is terminated by the receipt of a "DONE"
                continuation from the client; such response satisfies the server's
                continuation request.  At that point, the server MAY send any
                remaining queued untagged responses and then MUST immediately send
                the tagged response to the IDLE command and prepare to process other
                commands. As in the base specification, the processing of any new
                command may cause the sending of unsolicited untagged responses,
                subject to the ambiguity limitations.  The client MUST NOT send a
                command while the server is waiting for the DONE, since the server
                will not be able to distinguish a command from a continuation.

                The server MAY consider a client inactive if it has an IDLE command
                running, and if such a server has an inactivity timeout it MAY log
                the client off implicitly at the end of its timeout period.  Because
                of that, clients using IDLE are advised to terminate the IDLE and
                re-issue it at least every 29 minutes to avoid being logged off.
                This still allows a client to receive immediate mailbox updates even
                though it need only "poll" at half hour intervals.

                Example:    C: A001 SELECT INBOX
                            S: * FLAGS (Deleted Seen)
                            S: * 3 EXISTS
                            S: * 0 RECENT
                            S: * OK [UIDVALIDITY 1]
                            S: A001 OK SELECT completed
                            C: A002 IDLE
                            S: + idling
                            ...time passes; new mail arrives...
                            S: * 4 EXISTS
                            C: DONE
                            S: A002 OK IDLE terminated
                            ...another client expunges message 2 now...
                            C: A003 FETCH 4 ALL
                            S: * 4 FETCH (...)
                            S: A003 OK FETCH completed
                            C: A004 IDLE
                            S: * 2 EXPUNGE
                            S: * 3 EXISTS
                            S: + idling
                            ...time passes; another client expunges message 3...
                            S: * 3 EXPUNGE
                            S: * 2 EXISTS
                            ...time passes; new mail arrives...
                            S: * 3 EXISTS
                            C: DONE
                            S: A004 OK IDLE terminated
                            C: A005 FETCH 3 ALL
                            S: * 3 FETCH (...)
                            S: A005 OK FETCH completed
            */

            if(!this.IsAuthenticated){
                m_pResponseSender.SendResponseAsync(new IMAP_r_ServerStatus(cmdTag,"NO","Authentication required."));

                return true;
            }

            m_pResponseSender.SendResponseAsync(new IMAP_r_ServerStatus("+","idling"));

            TimerEx timer = new TimerEx(30000,true);
            timer.Elapsed += new System.Timers.ElapsedEventHandler(delegate(object sender,System.Timers.ElapsedEventArgs e){
                try{
                    UpdateSelectedFolderAndSendChanges();
                }
                catch{
                }
            });
            timer.Enabled = true;

            // Read client response. 
            SmartStream.ReadLineAsyncOP readLineOP = new SmartStream.ReadLineAsyncOP(new byte[32000],SizeExceededAction.JunkAndThrowException);
            readLineOP.CompletedAsync += new EventHandler<EventArgs<SmartStream.ReadLineAsyncOP>>(delegate(object sender,EventArgs<SmartStream.ReadLineAsyncOP> e){
                try{
                    if(readLineOP.Error != null){
                        LogAddText("Error: " + readLineOP.Error.Message);
                        timer.Dispose();

                        return;
                    }
                    // Remote host closed connection.
                    else if(readLineOP.BytesInBuffer == 0){
                        LogAddText("Remote host(connected client) closed IMAP connection.");
                        timer.Dispose();
                        Dispose();

                        return;
                    }

                    LogAddRead(readLineOP.BytesInBuffer,readLineOP.LineUtf8);

                    if(string.Equals(readLineOP.LineUtf8,"DONE",StringComparison.InvariantCultureIgnoreCase)){
                        timer.Dispose();

                        m_pResponseSender.SendResponseAsync(new IMAP_r_ServerStatus(cmdTag,"OK","IDLE terminated."));
                        BeginReadCmd();
                    }
                    else{
                        while(this.TcpStream.ReadLine(readLineOP,true)){
                            if(readLineOP.Error != null){
                                LogAddText("Error: " + readLineOP.Error.Message);
                                timer.Dispose();

                                return;
                            }
                            LogAddRead(readLineOP.BytesInBuffer,readLineOP.LineUtf8);

                            if(string.Equals(readLineOP.LineUtf8,"DONE",StringComparison.InvariantCultureIgnoreCase)){
                                timer.Dispose();

                                m_pResponseSender.SendResponseAsync(new IMAP_r_ServerStatus(cmdTag,"OK","IDLE terminated."));
                                BeginReadCmd();

                                break;
                            }
                        }
                    }
                }
                catch(Exception x){
                    timer.Dispose();

                    OnError(x);
                }
            });
            while(this.TcpStream.ReadLine(readLineOP,true)){
                if(readLineOP.Error != null){
                    LogAddText("Error: " + readLineOP.Error.Message);
                    timer.Dispose();

                    break;
                }

                LogAddRead(readLineOP.BytesInBuffer,readLineOP.LineUtf8);

                if(string.Equals(readLineOP.LineUtf8,"DONE",StringComparison.InvariantCultureIgnoreCase)){
                    timer.Dispose();

                    m_pResponseSender.SendResponseAsync(new IMAP_r_ServerStatus(cmdTag,"OK","IDLE terminated."));
                    BeginReadCmd();

                    break;
                }                
            }
            
            return false;            
        }

        #endregion


        #region method CAPABILITY

        private void CAPABILITY(string cmdTag,string cmdText)
        {
            /* RFC 3501 6.1.1. CAPABILITY Command.
                Arguments:  none

                Responses:  REQUIRED untagged response: CAPABILITY

                Result:     OK - capability completed
                            BAD - command unknown or arguments invalid

                The CAPABILITY command requests a listing of capabilities that the
                server supports.  The server MUST send a single untagged
                CAPABILITY response with "IMAP4rev1" as one of the listed
                capabilities before the (tagged) OK response.

                A capability name which begins with "AUTH=" indicates that the
                server supports that particular authentication mechanism.  All
                such names are, by definition, part of this specification.  For
                example, the authorization capability for an experimental
                "blurdybloop" authenticator would be "AUTH=XBLURDYBLOOP" and not
                "XAUTH=BLURDYBLOOP" or "XAUTH=XBLURDYBLOOP".

                Other capability names refer to extensions, revisions, or
                amendments to this specification.  See the documentation of the
                CAPABILITY response for additional information.  No capabilities,
                beyond the base IMAP4rev1 set defined in this specification, are
                enabled without explicit client action to invoke the capability.

                Client and server implementations MUST implement the STARTTLS,
                LOGINDISABLED, and AUTH=PLAIN (described in [IMAP-TLS])
                capabilities.  See the Security Considerations section for
                important information.

                See the section entitled "Client Commands -
                Experimental/Expansion" for information about the form of site or
                implementation-specific capabilities.

                Example:    C: abcd CAPABILITY
                            S: * CAPABILITY IMAP4rev1 STARTTLS AUTH=GSSAPI LOGINDISABLED
                            S: abcd OK CAPABILITY completed
                            C: efgh STARTTLS
                            S: efgh OK STARTLS completed
                            <TLS negotiation, further commands are under [TLS] layer>
                            C: ijkl CAPABILITY
                            S: * CAPABILITY IMAP4rev1 AUTH=GSSAPI AUTH=PLAIN
                            S: ijkl OK CAPABILITY completed
            */

            List<string> capabilities = new List<string>();
            if(!this.IsSecureConnection && this.Certificate != null){
                capabilities.Add("STARTTLS");
            }
            foreach(string c in m_pCapabilities){
                capabilities.Add(c);
            }
            foreach(AUTH_SASL_ServerMechanism auth in this.Authentications.Values){
                capabilities.Add("AUTH=" + auth.Name);
            }

            m_pResponseSender.SendResponseAsync(new IMAP_r_u_Capability(capabilities.ToArray()));
            m_pResponseSender.SendResponseAsync(new IMAP_r_ServerStatus(cmdTag,"OK","CAPABILITY completed."));
        }

        #endregion

        #region method NOOP

        private void NOOP(string cmdTag,string cmdText)
        {
            /* RFC 3501 6.1.2. NOOP Command.
                Arguments:  none

                Responses:  no specific responses for this command (but see below)

                Result:     OK - noop completed
                            BAD - command unknown or arguments invalid

                The NOOP command always succeeds.  It does nothing.

                Since any command can return a status update as untagged data, the
                NOOP command can be used as a periodic poll for new messages or
                message status updates during a period of inactivity (this is the
                preferred method to do this).  The NOOP command can also be used
                to reset any inactivity autologout timer on the server.

                Example:    C: a002 NOOP
                            S: a002 OK NOOP completed
                            . . .
                            C: a047 NOOP
                            S: * 22 EXPUNGE
                            S: * 23 EXISTS
                            S: * 3 RECENT
                            S: * 14 FETCH (FLAGS (\Seen \Deleted))
                            S: a047 OK NOOP completed
            */

            // Store start time
			long startTime = DateTime.Now.Ticks;

            if(m_pSelectedFolder != null){
                UpdateSelectedFolderAndSendChanges();
            }

            m_pResponseSender.SendResponseAsync(new IMAP_r_ServerStatus(cmdTag,"OK","NOOP Completed in " + ((DateTime.Now.Ticks - startTime) / (decimal)10000000).ToString("f2") + " seconds."));
        }

        #endregion

        #region method LOGOUT

        private void LOGOUT(string cmdTag,string cmdText)
        {
            /* RFC 3501 6.1.3. LOGOUT Command.
                Arguments:  none

                Responses:  REQUIRED untagged response: BYE

                Result:     OK - logout completed
                            BAD - command unknown or arguments invalid

                The LOGOUT command informs the server that the client is done with
                the connection.  The server MUST send a BYE untagged response
                before the (tagged) OK response, and then close the network
                connection.

                Example:    C: A023 LOGOUT
                            S: * BYE IMAP4rev1 Server logging out
                            S: A023 OK LOGOUT completed
                            (Server and client then close the connection)
            */

            try{
                m_pResponseSender.SendResponseAsync(new IMAP_r_u_Bye("IMAP4rev1 Server logging out."));

                // Create callback which is called when BYE comletes asynchronously.
                EventHandler<EventArgs<Exception>> byeCompletedAsyncCallback = delegate(object s,EventArgs<Exception> e){
                    try{
                        Disconnect();
                        Dispose();
                    }
                    catch{
                    }
                };
                                
                // BYE completed synchronously.
                if(!m_pResponseSender.SendResponseAsync(new IMAP_r_ServerStatus(cmdTag,"OK","LOGOUT completed."),byeCompletedAsyncCallback)){
                    Disconnect();
                    Dispose();
                }
                // BYE completed asynchronously, callback byeCompletedAsyncCallback is called when operation completes.
                //else{
            }
            catch{
                Disconnect();
                Dispose();
            }            
        }

        #endregion
                
        
        #region method WriteLine

        /// <summary>
        /// Sends and logs specified line to connected host.
        /// </summary>
        /// <param name="line">Line to send.</param>
        private void WriteLine(string line)
        {
            if(line == null){
                throw new ArgumentNullException("line");
            }

            byte[] buffer = null;
            if(line.EndsWith("\r\n")){
                buffer = Encoding.UTF8.GetBytes(line);
            }
            else{
                buffer = Encoding.UTF8.GetBytes(line + "\r\n");
            }

            this.TcpStream.Write(buffer,0,buffer.Length);
            
            // Log.
            if(this.Server.Logger != null){
                this.Server.Logger.AddWrite(this.ID,this.AuthenticatedUserIdentity,buffer.Length,line,this.LocalEndPoint,this.RemoteEndPoint);
            }
        }

        #endregion

        #region method LogAddRead

        /// <summary>
        /// Logs read operation.
        /// </summary>
        /// <param name="size">Number of bytes read.</param>
        /// <param name="text">Log text.</param>
        public void LogAddRead(long size,string text)
        {
            try{
                if(this.Server.Logger != null){
                    this.Server.Logger.AddRead(
                        this.ID,
                        this.AuthenticatedUserIdentity,
                        size,
                        text,                        
                        this.LocalEndPoint,
                        this.RemoteEndPoint
                    );
                }
            }
            catch{
                // We skip all logging errors, normally there shouldn't be any.
            }
        }

        #endregion

        #region method LogAddWrite

        /// <summary>
        /// Logs write operation.
        /// </summary>
        /// <param name="size">Number of bytes written.</param>
        /// <param name="text">Log text.</param>
        public void LogAddWrite(long size,string text)
        {
            try{
                if(this.Server.Logger != null){
                    this.Server.Logger.AddWrite(
                        this.ID,
                        this.AuthenticatedUserIdentity,
                        size,
                        text,                        
                        this.LocalEndPoint,
                        this.RemoteEndPoint
                    );
                }
            }
            catch{
                // We skip all logging errors, normally there shouldn't be any.
            }
        }

        #endregion

        #region method LogAddText

        /// <summary>
        /// Logs specified text.
        /// </summary>
        /// <param name="text">text to log.</param>
        /// <exception cref="ArgumentNullException">Is raised when <b>text</b> is null reference.</exception>
        public void LogAddText(string text)
        {
            if(text == null){
                throw new ArgumentNullException("text");
            }
            
            try{
                if(this.Server.Logger != null){
                    this.Server.Logger.AddText(
                        this.ID,
                        this.AuthenticatedUserIdentity,
                        text,                        
                        this.LocalEndPoint,
                        this.RemoteEndPoint
                    );
                }
            }
            catch{
            }
        }

        #endregion

        #region method LogAddException

        /// <summary>
        /// Logs specified exception.
        /// </summary>
        /// <param name="exception">Exception to log.</param>
        /// <exception cref="ArgumentNullException">Is raised when <b>exception</b> is null reference.</exception>
        public void LogAddException(Exception exception)
        {
            if(exception == null){
                throw new ArgumentNullException("exception");
            }
            
            try{
                if(this.Server.Logger != null){
                    this.Server.Logger.AddException(
                        this.ID,
                        this.AuthenticatedUserIdentity,
                        exception.Message,                        
                        this.LocalEndPoint,
                        this.RemoteEndPoint,
                        exception
                    );
                }
            }
            catch{
            }
        }

        #endregion

        #region method UpdateSelectedFolderAndSendChanges

        /// <summary>
        /// Updates current slected folder status and sends currently selected folder changes versus current folder state.
        /// </summary>
        private void UpdateSelectedFolderAndSendChanges()
        {
            if(m_pSelectedFolder == null){
                return;
            }
                        
            IMAP_e_MessagesInfo e = OnGetMessagesInfo(m_pSelectedFolder.Folder);
            
            int currentExists = m_pSelectedFolder.MessagesInfo.Length;
            // Create ID indexed lookup table for new messages.
            Dictionary<string,string> newMessagesLookup = new Dictionary<string,string>();
            foreach(IMAP_MessageInfo msgInfo in e.MessagesInfo){
                newMessagesLookup.Add(msgInfo.ID,null);
            }
            
            StringBuilder retVal = new StringBuilder();
            // Check deleted messages, send "* n EXPUNGE" for each deleted message.
            foreach(IMAP_MessageInfo msgInfo in m_pSelectedFolder.MessagesInfo){
                // Message deleted.
                if(!newMessagesLookup.ContainsKey(msgInfo.ID)){
                    retVal.Append("* " + m_pSelectedFolder.GetSeqNo(msgInfo) + " EXPUNGE\r\n");
                    m_pSelectedFolder.RemoveMessage(msgInfo);
                }
            }

            // Send EXISTS if current count differs from existing.
            if(currentExists != e.MessagesInfo.Count){
                retVal.Append("* " + e.MessagesInfo.Count + " EXISTS\r\n");
            }

            // Send STATUS change responses.
            if(retVal.Length > 0){
                WriteLine(retVal.ToString());                
            }

            // Create new selected folder based on new messages info.
            m_pSelectedFolder = new _SelectedFolder(m_pSelectedFolder.Folder,m_pSelectedFolder.IsReadOnly,e.MessagesInfo);
        }

        #endregion

        #region method SupportsCap

        /// <summary>
        /// Gets if session supports specified capability.
        /// </summary>
        /// <param name="capability">Capability name.</param>
        /// <returns>Returns true if session supports specified capability.</returns>
        private bool SupportsCap(string capability)
        {
            foreach(string c in m_pCapabilities){
                if(string.Equals(c,capability,StringComparison.InvariantCultureIgnoreCase)){
                    return true;
                }
            }

            return false;
        }

        #endregion

        #region method ParsePartNumberFromSection

        /// <summary>
        /// Parses MIME part-number specifier from BODY[] section string.
        /// </summary>
        /// <param name="section">Section string.</param>
        /// <returns>Returns part-number.</returns>
        /// <exception cref="ArgumentNullException">Is raised wehn <b>section</b> is null reference.</exception>
        private string ParsePartNumberFromSection(string section)
        {
            if(section == null){
                throw new ArgumentNullException("section");
            }

            StringBuilder retVal = new StringBuilder();
            string[] parts = section.Split('.');
            foreach(string part in parts){
                if(Net_Utils.IsInteger(part)){
                    if(retVal.Length > 0){
                        retVal.Append(".");
                    }
                    retVal.Append(part);
                }
                else{
                    break;
                }
            }

            return retVal.ToString();
        }

        #endregion

        #region method ParsePartSpecifierFromSection

        /// <summary>
        /// Parses MIME part specifier from BODY[] section string.
        /// </summary>
        /// <param name="section">Section string.</param>
        /// <returns>Returns specifier.</returns>
        /// <exception cref="ArgumentNullException">Is raised wehn <b>section</b> is null reference.</exception>
        private string ParsePartSpecifierFromSection(string section)
        {
            if(section == null){
                throw new ArgumentNullException("section");
            }

            StringBuilder retVal = new StringBuilder();
            string[] parts = section.Split(' ')[0].Split('.');
            foreach(string part in parts){
                if(!Net_Utils.IsInteger(part)){
                    if(retVal.Length > 0){
                        retVal.Append(".");
                    }
                    retVal.Append(part);
                }
            }

            return retVal.ToString();
        }

        #endregion

        #region method GetMimeEntity

        /// <summary>
		/// Gets specified mime entity. Returns null if specified mime entity doesn't exist.
		/// </summary>
		/// <param name="message">Mail message.</param>
		/// <param name="partNumber">MIME part-number specifier. Nested mime entities are pointed by '.'. 
		/// For example: 1,1.1,2.1, ... .</param>
		/// <returns></returns>
        /// <exception cref="ArgumentNullException">Is raised when <b>message</b> is null reference.</exception>
		public MIME_Entity GetMimeEntity(Mail_Message message,string partNumber)
		{
            if(message == null){
                throw new ArgumentNullException("message");
            }
            			
			// For single part message there is only one entity with value 1.
			// Example:
			//		header
			//		entity -> 1
			
			// For multipart message, entity counting starts from MainEntity.ChildEntities
			// Example:
			//		header
			//		multipart/mixed
			//			text/plain  -> 1
			//			application/pdf  -> 2
			//          ...

            // TODO: nested rfc 822 message

            if(partNumber == string.Empty){
                return message;
            }

			// Single part
			if(message.ContentType == null || message.ContentType.Type.ToLower() != "multipart"){
				if(Convert.ToInt32(partNumber) == 1){
					return message;
				}
				else{
					return null;
				}
			}
			// multipart
			else{                
				MIME_Entity entity = message;
				string[] parts = partNumber.Split('.');
				foreach(string part in parts){
					int mEntryNo = Convert.ToInt32(part) - 1; // Enitites are zero base, mimeEntitySpecifier is 1 based.
                    if(entity.Body is MIME_b_Multipart){
                        MIME_b_Multipart multipart = (MIME_b_Multipart)entity.Body;
                        if(mEntryNo > -1 && mEntryNo < multipart.BodyParts.Count){
						    entity = multipart.BodyParts[mEntryNo];
					    }
                        else{
                            return null;
                        }
                    }
			        else{
                        return null;
                    }
				}

				return entity;
			}			
		}

		#endregion

        #region mehtod ConstructBodyStructure

		/// <summary>
		/// Constructs FETCH BODY and BODYSTRUCTURE response.
		/// </summary>
		/// <param name="message">Mail message.</param>
		/// <param name="bodystructure">Specifies if to construct BODY or BODYSTRUCTURE.</param>
		/// <returns></returns>
		public string ConstructBodyStructure(Mail_Message message,bool bodystructure)
		{			
			if(bodystructure){
				return "BODYSTRUCTURE " + ConstructParts(message,bodystructure);
			}
			else{
				return "BODY " + ConstructParts(message,bodystructure);
			}
		}

		/// <summary>
		/// Constructs specified entity and it's childentities bodystructure string.
		/// </summary>
		/// <param name="entity">Mime entity.</param>
		/// <param name="bodystructure">Specifies if to construct BODY or BODYSTRUCTURE.</param>
		/// <returns></returns>
		private string ConstructParts(MIME_Entity entity,bool bodystructure)
		{
			/* RFC 3501 7.4.2 BODYSTRUCTURE
							  BODY A form of BODYSTRUCTURE without extension data.
			  
				A parenthesized list that describes the [MIME-IMB] body
				structure of a message.  This is computed by the server by
				parsing the [MIME-IMB] header fields, defaulting various fields
				as necessary.

				For example, a simple text message of 48 lines and 2279 octets
				can have a body structure of: ("TEXT" "PLAIN" ("CHARSET"
				"US-ASCII") NIL NIL "7BIT" 2279 48)

				Multiple parts are indicated by parenthesis nesting.  Instead
				of a body type as the first element of the parenthesized list,
				there is a sequence of one or more nested body structures.  The
				second element of the parenthesized list is the multipart
				subtype (mixed, digest, parallel, alternative, etc.).
					
				For example, a two part message consisting of a text and a
				BASE64-encoded text attachment can have a body structure of:
				(("TEXT" "PLAIN" ("CHARSET" "US-ASCII") NIL NIL "7BIT" 1152
				23)("TEXT" "PLAIN" ("CHARSET" "US-ASCII" "NAME" "cc.diff")
				"<960723163407.20117h@cac.washington.edu>" "Compiler diff"
				"BASE64" 4554 73) "MIXED")

				Extension data follows the multipart subtype.  Extension data
				is never returned with the BODY fetch, but can be returned with
				a BODYSTRUCTURE fetch.  Extension data, if present, MUST be in
				the defined order.  The extension data of a multipart body part
				are in the following order:

				body parameter parenthesized list
					A parenthesized list of attribute/value pairs [e.g., ("foo"
					"bar" "baz" "rag") where "bar" is the value of "foo", and
					"rag" is the value of "baz"] as defined in [MIME-IMB].

				body disposition
					A parenthesized list, consisting of a disposition type
					string, followed by a parenthesized list of disposition
					attribute/value pairs as defined in [DISPOSITION].

				body language
					A string or parenthesized list giving the body language
					value as defined in [LANGUAGE-TAGS].

				body location
					A string list giving the body content URI as defined in [LOCATION].

				Any following extension data are not yet defined in this
				version of the protocol.  Such extension data can consist of
				zero or more NILs, strings, numbers, or potentially nested
				parenthesized lists of such data.  Client implementations that
				do a BODYSTRUCTURE fetch MUST be prepared to accept such
				extension data.  Server implementations MUST NOT send such
				extension data until it has been defined by a revision of this
				protocol.

				The basic fields of a non-multipart body part are in the
				following order:

				body type
					A string giving the content media type name as defined in [MIME-IMB].
				
				body subtype
					 A string giving the content subtype name as defined in [MIME-IMB].

				body parameter parenthesized list
					A parenthesized list of attribute/value pairs [e.g., ("foo"
					"bar" "baz" "rag") where "bar" is the value of "foo" and
					"rag" is the value of "baz"] as defined in [MIME-IMB].

				body id
					A string giving the content id as defined in [MIME-IMB].

				body description
					A string giving the content description as defined in [MIME-IMB].

				body encoding
					A string giving the content transfer encoding as defined in	[MIME-IMB].

				body size
					A number giving the size of the body in octets.  Note that
					this size is the size in its transfer encoding and not the
					resulting size after any decoding.

				A body type of type MESSAGE and subtype RFC822 contains,
				immediately after the basic fields, the envelope structure,
				body structure, and size in text lines of the encapsulated
				message.

				A body type of type TEXT contains, immediately after the basic
				fields, the size of the body in text lines.  Note that this
				size is the size in its content transfer encoding and not the
				resulting size after any decoding.

				Extension data follows the basic fields and the type-specific
				fields listed above.  Extension data is never returned with the
				BODY fetch, but can be returned with a BODYSTRUCTURE fetch.
				Extension data, if present, MUST be in the defined order.

				The extension data of a non-multipart body part are in the
				following order:

				body MD5
					A string giving the body MD5 value as defined in [MD5].
					
				body disposition
					A parenthesized list with the same content and function as
					the body disposition for a multipart body part.

				body language
					A string or parenthesized list giving the body language
					value as defined in [LANGUAGE-TAGS].

				body location
					A string list giving the body content URI as defined in [LOCATION].

				Any following extension data are not yet defined in this
				version of the protocol, and would be as described above under
				multipart extension data.
			
			
				// We don't construct extention fields like rfc says:
					Server implementations MUST NOT send such
					extension data until it has been defined by a revision of this
					protocol.
			
										
				contentTypeMainMediaType - Example: 'TEXT'
				contentTypeSubMediaType  - Example: 'PLAIN'
				conentTypeParameters     - Example: '("CHARSET" "iso-8859-1" ...)'
				contentID                - Content-ID: header field value.
				contentDescription       - Content-Description: header field value.
				contentEncoding          - Content-Transfer-Encoding: header field value.
				contentSize              - mimeEntity ENCODED data size
				[envelope]               - NOTE: included only if contentType = "message" !!!
				[contentLines]           - number of ENCODED data lines. NOTE: included only if contentType = "text" !!!
									   			
				// Basic fields for multipart
				(nestedMimeEntries) contentTypeSubMediaType
												
				// Basic fields for non-multipart
				contentTypeMainMediaType contentTypeSubMediaType (conentTypeParameters) contentID contentDescription contentEncoding contentSize [envelope] [contentLine]

			*/

            MIME_Encoding_EncodedWord wordEncoder = new MIME_Encoding_EncodedWord(MIME_EncodedWordEncoding.B,Encoding.UTF8);
            wordEncoder.Split = false;

			StringBuilder retVal = new StringBuilder();
			// Multipart message
			if(entity.Body is MIME_b_Multipart){
				retVal.Append("(");

				// Construct child entities.
				foreach(MIME_Entity childEntity in ((MIME_b_Multipart)entity.Body).BodyParts){
					// Construct child entity. This can be multipart or non multipart.
					retVal.Append(ConstructParts(childEntity,bodystructure));
				}
			
				// Add contentTypeSubMediaType
                if(entity.ContentType != null && entity.ContentType.SubType != null){
                    retVal.Append(" \"" + entity.ContentType.SubType + "\"");
                }
                else{
                    retVal.Append(" \"plain\"");
                }

				retVal.Append(")");
			}
			// Single part message
			else{
				retVal.Append("(");

				// NOTE: all header fields and parameters must in ENCODED form !!!

				// Add contentTypeMainMediaType
				if(entity.ContentType != null && entity.ContentType.Type != null){
					retVal.Append("\"" + entity.ContentType.Type + "\"");
				}
				else{
					retVal.Append("\"text\"");
				}

                // Add contentTypeSubMediaType
                if(entity.ContentType != null && entity.ContentType.SubType != null){
                    retVal.Append(" \"" + entity.ContentType.SubType + "\"");
                }
                else{
                    retVal.Append(" \"plain\"");
                }

				// conentTypeParameters - Syntax: {("name" SP "value" *(SP "name" SP "value"))}
				if(entity.ContentType != null){
                    if(entity.ContentType.Parameters.Count > 0){
                        retVal.Append(" (");
                        bool first = true;
                        foreach(MIME_h_Parameter parameter in entity.ContentType.Parameters){
                            // For the first item, don't add SP.
                            if(first){
                                first = false;
                            }
                            else{
                                retVal.Append(" ");
                            }

                            retVal.Append("\"" + parameter.Name + "\" \"" + wordEncoder.Encode(parameter.Value) + "\"");
                        }
                        retVal.Append(")");
                    }
                    else{
                        retVal.Append(" NIL");
                    }
				}
				else{
					retVal.Append(" NIL");
				}

				// contentID
				string contentID = entity.ContentID;
				if(contentID != null){
					retVal.Append(" \"" + wordEncoder.Encode(contentID) + "\""); 
				}
				else{
					retVal.Append(" NIL");
				}

				// contentDescription
				string contentDescription = entity.ContentDescription;
				if(contentDescription != null){
					retVal.Append(" \"" + wordEncoder.Encode(contentDescription) + "\""); 
				}
				else{
					retVal.Append(" NIL");
				}

				// contentEncoding
				if(entity.ContentTransferEncoding != null){
					retVal.Append(" \"" + wordEncoder.Encode(entity.ContentTransferEncoding) + "\""); 
				}
				else{
					// If not specified, then must be 7bit.
					retVal.Append(" \"7bit\"");
				}

				// contentSize
				if(entity.Body is MIME_b_SinglepartBase){                    
					retVal.Append(" " + ((MIME_b_SinglepartBase)entity.Body).EncodedData.Length.ToString());
				}
				else{
					retVal.Append(" 0");
				}

				// envelope ---> FOR ContentType: message/rfc822 ONLY ###
				if(entity.Body is MIME_b_MessageRfc822){                    
					retVal.Append(" " + IMAP_t_Fetch_r_i_Envelope.ConstructEnvelope(((MIME_b_MessageRfc822)entity.Body).Message));

                    // BODYSTRUCTURE
                    retVal.Append(" NIL");

                    // LINES
                    retVal.Append(" NIL");
				}

				// contentLines ---> FOR ContentType: text/xxx ONLY ###
				if(entity.Body is MIME_b_Text){                    
				    long lineCount = 0;
					StreamLineReader r = new StreamLineReader(new MemoryStream(((MIME_b_SinglepartBase)entity.Body).EncodedData));
					byte[] line = r.ReadLine();
					while(line != null){
						lineCount++;

						line = r.ReadLine();
					}
						
					retVal.Append(" " + lineCount.ToString());
                }


                #region BODYSTRUCTURE extention fields

                if(bodystructure){
                    // body MD5
                    retVal.Append(" NIL");

                    // body disposition  Syntax: {(disposition-type [ SP ("name" SP "value" *(SP "name" SP "value"))])}
				    if(entity.ContentDisposition != null && entity.ContentDisposition.Parameters.Count > 0){
                        retVal.Append(" (\"" + entity.ContentDisposition.DispositionType + "\"");

                        if(entity.ContentDisposition.Parameters.Count > 0){
                            retVal.Append(" (");

                            bool first = true;
                            foreach(MIME_h_Parameter parameter in entity.ContentDisposition.Parameters){
                                // For the first item, don't add SP.
                                if(first){
                                    first = false;
                                }
                                else{
                                    retVal.Append(" ");
                                }

                                retVal.Append("\"" + parameter.Name + "\" \"" + wordEncoder.Encode(parameter.Value) + "\"");
                            }
                            retVal.Append(")");
                        }
                        else{
                            retVal.Append(" NIL");
                        }

                        retVal.Append(")");
				    }
				    else{
					    retVal.Append(" NIL");
				    }

                    // body language
                    retVal.Append(" NIL");

                    // body location
                    retVal.Append(" NIL");
                }

                #endregion

                retVal.Append(")");
			}

			return retVal.ToString();
		}

		#endregion


        #region Properties implementation

        /// <summary>
        /// Gets session owner IMAP server.
        /// </summary>
        /// <exception cref="ObjectDisposedException">Is raised when this object is disposed and this property is accessed.</exception>
        public new IMAP_Server Server
        {
            get{
                if(this.IsDisposed){
                    throw new ObjectDisposedException(this.GetType().Name);
                }

                return (IMAP_Server)base.Server;
            }
        }

        /// <summary>
        /// Gets supported SASL authentication methods collection.
        /// </summary>
        /// <exception cref="ObjectDisposedException">Is raised when this object is disposed and this property is accessed.</exception>
        public Dictionary<string,AUTH_SASL_ServerMechanism> Authentications
        {
            get{
                if(this.IsDisposed){
                    throw new ObjectDisposedException(this.GetType().Name);
                }

                return m_pAuthentications; 
            }
        }

        /// <summary>
        /// Gets number of bad commands happened on IMAP session.
        /// </summary>
        /// <exception cref="ObjectDisposedException">Is raised when this object is disposed and this property is accessed.</exception>
        public int BadCommands
        {
            get{ 
                if(this.IsDisposed){
                    throw new ObjectDisposedException(this.GetType().Name);
                }

                return m_BadCommands; 
            }
        }

        /// <summary>
        /// Gets authenticated user identity or null if user has not authenticated.
        /// </summary>
        /// <exception cref="ObjectDisposedException">Is raised when this object is disposed and this property is accessed.</exception>
        public override GenericIdentity AuthenticatedUserIdentity
        {
	        get{
                if(this.IsDisposed){
                    throw new ObjectDisposedException(this.GetType().Name);
                }

		        return m_pUser;
	        }
        }

        /// <summary>
        /// Gets session supported CAPABILITIES.
        /// </summary>
        /// <exception cref="ObjectDisposedException">Is raised when this object is disposed and this property is accessed.</exception>
        public List<string> Capabilities
        {
            get{ 
                if(this.IsDisposed){
                    throw new ObjectDisposedException(this.GetType().Name);
                }

                return m_pCapabilities; 
            }
        }

        /// <summary>
        /// Gets selected folder name with optional path. Value null means no selected folder.
        /// </summary>
        public string SelectedFolderName
        {
            get{ 
                if(m_pSelectedFolder == null){
                    return null;
                }
                else{
                    return m_pSelectedFolder.Folder; 
                }
            }
        }


        /// <summary>
        /// Gets mailbox encoding.
        /// </summary>
        internal IMAP_Mailbox_Encoding MailboxEncoding
        {
            get{ return m_MailboxEncoding; }
        }

        #endregion

        #region Events implementation

        /// <summary>
        /// Is raised when session has started processing and needs to send "* OK ..." greeting or "* NO ..." error resposne to the connected client.
        /// </summary>
        public event EventHandler<IMAP_e_Started> Started = null;

        #region method OnStarted

        /// <summary>
        /// Raises <b>Started</b> event.
        /// </summary>
        /// <param name="response">Default IMAP server response.</param>
        /// <returns>Returns event args.</returns>
        private IMAP_e_Started OnStarted(IMAP_r_u_ServerStatus response)
        {
            IMAP_e_Started eArgs = new IMAP_e_Started(response);            
            if(this.Started != null){                
                this.Started(this,eArgs);
            }

            return eArgs;
        }

        #endregion

        /// <summary>
        /// Is raised when IMAP session needs to handle LOGIN command.
        /// </summary>
        public event EventHandler<IMAP_e_Login> Login = null;

        #region method OnLogin

        /// <summary>
        /// Raises <b>Login</b> event.
        /// </summary>
        /// <param name="user">User name.</param>
        /// <param name="password">Password.</param>
        /// <returns>Returns event args.</returns>
        private IMAP_e_Login OnLogin(string user,string password)
        {
            IMAP_e_Login eArgs = new IMAP_e_Login(user,password);            
            if(this.Login != null){                
                this.Login(this,eArgs);
            }

            return eArgs;
        }

        #endregion


        /// <summary>
        /// Is raised when IMAP session needs to handle NAMESPACE command.
        /// </summary>
        public event EventHandler<IMAP_e_Namespace> Namespace = null;

        #region method OnNamespace

        /// <summary>
        /// Raises <b>Namespace</b> event.
        /// </summary>
        /// <param name="response">Default IMAP server response.</param>
        /// <returns>Returns event args.</returns>
        private IMAP_e_Namespace OnNamespace(IMAP_r_ServerStatus response)
        {
            IMAP_e_Namespace eArgs = new IMAP_e_Namespace(response);            
            if(this.Namespace != null){                
                this.Namespace(this,eArgs);
            }

            return eArgs;
        }

        #endregion

        /// <summary>
        /// Is raised when IMAP session needs to handle LIST command.
        /// </summary>
        public event EventHandler<IMAP_e_List> List = null;

        #region method OnList

        /// <summary>
        /// Raises <b>List</b> event.
        /// </summary>
        /// <param name="refName">Folder reference name.</param>
        /// <param name="folder">Folder filter.</param>
        /// <returns>Returns event args.</returns>
        private IMAP_e_List OnList(string refName,string folder)
        {
            IMAP_e_List eArgs = new IMAP_e_List(refName,folder);
            if(this.List != null){
                this.List(this,eArgs);
            }

            return eArgs;
        }

        #endregion

        /// <summary>
        /// Is raised when IMAP session needs to handle CREATE command.
        /// </summary>
        public event EventHandler<IMAP_e_Folder> Create = null;

        #region method OnCreate

        /// <summary>
        /// Raises <b>Create</b> event.
        /// </summary>
        /// <param name="cmdTag">Command tag.</param>
        /// <param name="folder">Folder name with optional path.</param>
        /// <param name="response">Default IMAP server response.</param>
        /// <returns>Returns event args.</returns>
        private IMAP_e_Folder OnCreate(string cmdTag,string folder,IMAP_r_ServerStatus response)
        {
            IMAP_e_Folder eArgs = new IMAP_e_Folder(cmdTag,folder,response);
            if(this.Create != null){
                this.Create(this,eArgs);
            }

            return eArgs;
        }

        #endregion

        /// <summary>
        /// Is raised when IMAP session needs to handle DELETE command.
        /// </summary>
        public event EventHandler<IMAP_e_Folder> Delete = null;

        #region method OnDelete

        /// <summary>
        /// Raises <b>Delete</b> event.
        /// </summary>
        /// <param name="cmdTag">Command tag.</param>
        /// <param name="folder">Folder name with optional path.</param>
        /// <param name="response">Default IMAP server response.</param>
        /// <returns>Returns event args.</returns>
        private IMAP_e_Folder OnDelete(string cmdTag,string folder,IMAP_r_ServerStatus response)
        {
            IMAP_e_Folder eArgs = new IMAP_e_Folder(cmdTag,folder,response);
            if(this.Delete != null){
                this.Delete(this,eArgs);
            }

            return eArgs;
        }

        #endregion

        /// <summary>
        /// Is raised when IMAP session needs to handle RENAME command.
        /// </summary>
        public event EventHandler<IMAP_e_Rename> Rename = null;

        #region method OnRename

        /// <summary>
        /// Raises <b>Rename</b> event.
        /// </summary>
        /// <param name="cmdTag">Command tag.</param>
        /// <param name="currentFolder">Current folder name with optional path.</param>
        /// <param name="newFolder">New folder name with optional path.</param>
        /// <returns>Returns event args.</returns>
        private IMAP_e_Rename OnRename(string cmdTag,string currentFolder,string newFolder)
        {
            IMAP_e_Rename eArgs = new IMAP_e_Rename(cmdTag,currentFolder,newFolder);
            if(this.Rename != null){
                this.Rename(this,eArgs);
            }

            return eArgs;
        }

        #endregion

        /// <summary>
        /// Is raised when IMAP session needs to handle LSUB command.
        /// </summary>
        public event EventHandler<IMAP_e_LSub> LSub = null;

        #region method OnLSub

        /// <summary>
        /// Raises <b>LSub</b> event.
        /// </summary>
        /// <param name="refName">Folder reference name.</param>
        /// <param name="folder">Folder filter.</param>
        /// <returns>Returns event args.</returns>
        private IMAP_e_LSub OnLSub(string refName,string folder)
        {
            IMAP_e_LSub eArgs = new IMAP_e_LSub(refName,folder);
            if(this.LSub != null){
                this.LSub(this,eArgs);
            }

            return eArgs;
        }

        #endregion

        /// <summary>
        /// Is raised when IMAP session needs to handle SUBSCRIBE command.
        /// </summary>
        public event EventHandler<IMAP_e_Folder> Subscribe = null;

        #region method OnSubscribe

        /// <summary>
        /// Raises <b>Subscribe</b> event.
        /// </summary>
        /// <param name="cmdTag">Command tag.</param>
        /// <param name="folder">Folder name with optional path.</param>
        /// <param name="response">Default IMAP server response.</param>
        /// <returns>Returns event args.</returns>
        private IMAP_e_Folder OnSubscribe(string cmdTag,string folder,IMAP_r_ServerStatus response)
        {
            IMAP_e_Folder eArgs = new IMAP_e_Folder(cmdTag,folder,response);
            if(this.Subscribe != null){
                this.Subscribe(this,eArgs);
            }

            return eArgs;
        }

        #endregion

        /// <summary>
        /// Is raised when IMAP session needs to handle SUBSCRIBE command.
        /// </summary>
        public event EventHandler<IMAP_e_Folder> Unsubscribe = null;

        #region method OnUnsubscribe

        /// <summary>
        /// Raises <b>OnUnsubscribe</b> event.
        /// </summary>
        /// <param name="cmdTag">Command tag.</param>
        /// <param name="folder">Folder name with optional path.</param>
        /// <param name="response">Default IMAP server response.</param>
        /// <returns>Returns event args.</returns>
        private IMAP_e_Folder OnUnsubscribe(string cmdTag,string folder,IMAP_r_ServerStatus response)
        {
            IMAP_e_Folder eArgs = new IMAP_e_Folder(cmdTag,folder,response);
            if(this.Unsubscribe != null){
                this.Unsubscribe(this,eArgs);
            }

            return eArgs;
        }

        #endregion
                
        /// <summary>
        /// Is raised when IMAP session needs to handle SELECT command.
        /// </summary>
        public event EventHandler<IMAP_e_Select> Select = null;

        #region method OnSelect

        /// <summary>
        /// Raises <b>Select</b> event.
        /// </summary>
        /// <param name="cmdTag">Command tag.</param>
        /// <param name="folder">Folder name with optional path.</param>
        /// <returns>Returns event args.</returns>
        private IMAP_e_Select OnSelect(string cmdTag,string folder)
        {
            IMAP_e_Select eArgs = new IMAP_e_Select(cmdTag,folder);
            if(this.Select != null){
                this.Select(this,eArgs);
            }

            return eArgs;
        }

        #endregion

        /// <summary>
        /// Is raised when IMAP session needs to get folder messages info.
        /// </summary>
        public event EventHandler<IMAP_e_MessagesInfo> GetMessagesInfo = null;

        #region method OnGetMessagesInfo

        /// <summary>
        /// Raises <b>GetMessagesInfo</b> event.
        /// </summary>
        /// <param name="folder">Folder name with optional path.</param>
        /// <returns>Returns event args.</returns>
        private IMAP_e_MessagesInfo OnGetMessagesInfo(string folder)
        {
            IMAP_e_MessagesInfo eArgs = new IMAP_e_MessagesInfo(folder);
            if(this.GetMessagesInfo != null){
                this.GetMessagesInfo(this,eArgs);
            }

            return eArgs;
        }

        #endregion

        /// <summary>
        /// Is raised when IMAP session needs to handle APPEND command.
        /// </summary>
        public event EventHandler<IMAP_e_Append> Append = null;

        #region method OnAppend

        /// <summary>
        /// Raises <b>StoreMessage</b> event.
        /// </summary>
        /// <param name="folder">Folder name with optional path.</param>
        /// <param name="flags">Message flags.</param>
        /// <param name="date">Message IMAP internal date.</param>
        /// <param name="size">Message size in bytes.</param>
        /// <param name="response">Default IMAP server response.</param>
        /// <returns>Returns event args.</returns>
        private IMAP_e_Append OnAppend(string folder,string[] flags,DateTime date,int size,IMAP_r_ServerStatus response)
        {
            IMAP_e_Append eArgs = new IMAP_e_Append(folder,flags,date,size,response);
            if(this.Append != null){
                this.Append(this,eArgs);
            }

            return eArgs;
        }

        #endregion

        /// <summary>
        /// Is raised when IMAP session needs to handle GETQUOTAROOT command.
        /// </summary>
        public event EventHandler<IMAP_e_GetQuotaRoot> GetQuotaRoot = null;

        #region method OnGetGuotaRoot

        /// <summary>
        /// Raises <b>GetQuotaRoot</b> event.
        /// </summary>
        /// <param name="folder">Folder name with optional path.</param>
        /// <param name="response">Default IMAP server response.</param>
        /// <returns>Returns event args.</returns>
        private IMAP_e_GetQuotaRoot OnGetGuotaRoot(string folder,IMAP_r_ServerStatus response)
        {
            IMAP_e_GetQuotaRoot eArgs = new IMAP_e_GetQuotaRoot(folder,response);
            if(this.GetQuotaRoot != null){
                this.GetQuotaRoot(this,eArgs);
            }

            return eArgs;
        }

        #endregion

        /// <summary>
        /// Is raised when IMAP session needs to handle GETQUOTA command.
        /// </summary>
        public event EventHandler<IMAP_e_GetQuota> GetQuota = null;

        #region method OnGetQuota

        /// <summary>
        /// Raises <b>GetQuota</b> event.
        /// </summary>
        /// <param name="quotaRoot">Quota root name.</param>
        /// <param name="response">Default IMAP server response.</param>
        /// <returns>Returns event args.</returns>
        private IMAP_e_GetQuota OnGetQuota(string quotaRoot,IMAP_r_ServerStatus response)
        {
            IMAP_e_GetQuota eArgs = new IMAP_e_GetQuota(quotaRoot,response);
            if(this.GetQuota != null){
                this.GetQuota(this,eArgs);
            }

            return eArgs;
        }

        #endregion

        /// <summary>
        /// Is raised when IMAP session needs to handle GETACL command.
        /// </summary>
        public event EventHandler<IMAP_e_GetAcl> GetAcl = null;

        #region method OnGetAcl

        /// <summary>
        /// Raises <b>GetAcl</b> event.
        /// </summary>
        /// <param name="folder">Folder name with optional path.</param>
        /// <param name="response">Default IMAP server response.</param>
        /// <returns>Returns event args.</returns>
        private IMAP_e_GetAcl OnGetAcl(string folder,IMAP_r_ServerStatus response)
        {
            IMAP_e_GetAcl eArgs = new IMAP_e_GetAcl(folder,response);
            if(this.GetAcl != null){
                this.GetAcl(this,eArgs);
            }

            return eArgs;
        }

        #endregion

        /// <summary>
        /// Is raised when IMAP session needs to handle SETACL command.
        /// </summary>
        public event EventHandler<IMAP_e_SetAcl> SetAcl = null;

        #region method OnSetAcl

        /// <summary>
        /// Raises <b>SetAcl</b> event.
        /// </summary>
        /// <param name="folder">Folder name with optional path.</param>
        /// <param name="identifier">ACL identifier (normally user or group name).</param>
        /// <param name="flagsSetType">Flags set type.</param>
        /// <param name="rights">Identifier rights.</param>
        /// <param name="response">Default IMAP server response.</param>
        /// <returns>Returns event args.</returns>
        private IMAP_e_SetAcl OnSetAcl(string folder,string identifier,IMAP_Flags_SetType flagsSetType,string rights,IMAP_r_ServerStatus response)
        {
            IMAP_e_SetAcl eArgs = new IMAP_e_SetAcl(folder,identifier,flagsSetType,rights,response);
            if(this.SetAcl != null){
                this.SetAcl(this,eArgs);
            }

            return eArgs;
        }

        #endregion

        /// <summary>
        /// Is raised when IMAP session needs to handle DELETEACL command.
        /// </summary>
        public event EventHandler<IMAP_e_DeleteAcl> DeleteAcl = null;

        #region method OnDeleteAcl

        /// <summary>
        /// Raises <b>DeleteAcl</b> event.
        /// </summary>
        /// <param name="folder">Folder name with optional path.</param>
        /// <param name="identifier">ACL identifier (normally user or group name).</param>
        /// <param name="response">Default IMAP server response.</param>
        /// <returns>Returns event args.</returns>
        private IMAP_e_DeleteAcl OnDeleteAcl(string folder,string identifier,IMAP_r_ServerStatus response)
        {
            IMAP_e_DeleteAcl eArgs = new IMAP_e_DeleteAcl(folder,identifier,response);
            if(this.DeleteAcl != null){
                this.DeleteAcl(this,eArgs);
            }

            return eArgs;
        }

        #endregion

        /// <summary>
        /// Is raised when IMAP session needs to handle LISTRIGHTS command.
        /// </summary>
        public event EventHandler<IMAP_e_ListRights> ListRights = null;

        #region method OnListRights

        /// <summary>
        /// Raises <b>ListRights</b> event.
        /// </summary>
        /// <param name="folder">Folder name with optional path.</param>
        /// <param name="identifier">ACL identifier (normally user or group name).</param>
        /// <param name="response">Default IMAP server response.</param>
        /// <returns>Returns event args.</returns>
        private IMAP_e_ListRights OnListRights(string folder,string identifier,IMAP_r_ServerStatus response)
        {
            IMAP_e_ListRights eArgs = new IMAP_e_ListRights(folder,identifier,response);
            if(this.ListRights != null){
                this.ListRights(this,eArgs);
            }

            return eArgs;
        }

        #endregion

        /// <summary>
        /// Is raised when IMAP session needs to handle MYRIGHTS command.
        /// </summary>
        public event EventHandler<IMAP_e_MyRights> MyRights = null;

        #region method OnMyRights

        /// <summary>
        /// Raises <b>MyRights</b> event.
        /// </summary>
        /// <param name="folder">Folder name with optional path.</param>
        /// <param name="response">Default IMAP server response.</param>
        /// <returns>Returns event args.</returns>
        private IMAP_e_MyRights OnMyRights(string folder,IMAP_r_ServerStatus response)
        {
            IMAP_e_MyRights eArgs = new IMAP_e_MyRights(folder,response);
            if(this.MyRights != null){
                this.MyRights(this,eArgs);
            }

            return eArgs;
        }

        #endregion

        /// <summary>
        /// Is raised when IMAP session needs to handle FETCH command.
        /// </summary>
        public event EventHandler<IMAP_e_Fetch> Fetch = null;

        #region method OnFetch

        /// <summary>
        /// Raises <b>Fetch</b> event.
        /// </summary>
        /// <param name="e">Event data.</param>
        private void OnFetch(IMAP_e_Fetch e)
        {
            if(this.Fetch != null){
                this.Fetch(this,e);
            }
        }

        #endregion
                
        /// <summary>
        /// Is raised when IMAP session needs to handle SEARCH command.
        /// </summary>
        public event EventHandler<IMAP_e_Search> Search = null;

        #region method OnSearch

        /// <summary>
        /// Raises <b>Search</b> event.
        /// </summary>
        /// <param name="e">Event args.</param>
        private void OnSearch(IMAP_e_Search e)
        {
            if(this.Search != null){
                this.Search(this,e);
            }
        }

        #endregion

        /// <summary>
        /// Is raised when IMAP session needs to handle STORE command.
        /// </summary>
        public event EventHandler<IMAP_e_Store> Store = null;

        #region method OnStore

        /// <summary>
        /// Raises <b>Store</b> event.
        /// </summary>
        /// <param name="msgInfo">Message info.</param>
        /// <param name="setType">Flags set type.</param>
        /// <param name="flags">Flags.</param>
        /// <param name="response">Default IMAP server response.</param>
        /// <returns>Returns event args.</returns>
        private IMAP_e_Store OnStore(IMAP_MessageInfo msgInfo,IMAP_Flags_SetType setType,string[] flags,IMAP_r_ServerStatus response)
        {
            IMAP_e_Store eArgs = new IMAP_e_Store(m_pSelectedFolder.Folder,msgInfo,setType,flags,response);
            if(this.Store != null){
                this.Store(this,eArgs);
            }

            return eArgs;
        }

        #endregion

        /// <summary>
        /// Is raised when IMAP session needs to handle COPY command.
        /// </summary>
        public event EventHandler<IMAP_e_Copy> Copy = null;

        #region method OnCopy

        /// <summary>
        /// Raises <b>Copy</b> event.
        /// </summary>
        /// <param name="targetFolder">Target folder name with optional path.</param>
        /// <param name="messagesInfo">Messages info.</param>
        /// <param name="response">Default IMAP server response.</param>
        /// <returns>Returns event args.</returns>
        private IMAP_e_Copy OnCopy(string targetFolder,IMAP_MessageInfo[] messagesInfo,IMAP_r_ServerStatus response)
        {
            IMAP_e_Copy eArgs = new IMAP_e_Copy(m_pSelectedFolder.Folder,targetFolder,messagesInfo,response);
            if(this.Copy != null){
                this.Copy(this,eArgs);
            }

            return eArgs;
        }

        #endregion

        /// <summary>
        /// Is raised when IMAP session needs to handle EXPUNGE command.
        /// </summary>
        public event EventHandler<IMAP_e_Expunge> Expunge = null;

        #region method OnExpunge

        /// <summary>
        /// Raises <b>Expunge</b> event.
        /// </summary>
        /// <param name="msgInfo">Messgae info.</param>
        /// <param name="response">Default IMAP server response.</param>
        /// <returns>Returns event args.</returns>
        private IMAP_e_Expunge OnExpunge(IMAP_MessageInfo msgInfo,IMAP_r_ServerStatus response)
        {
            IMAP_e_Expunge eArgs = new IMAP_e_Expunge(m_pSelectedFolder.Folder,msgInfo,response);
            if(this.Expunge != null){
                this.Expunge(this,eArgs);
            }

            return eArgs;
        }

        #endregion

        #endregion
    }
}
