Click here to Skip to main content
Click here to Skip to main content
Articles » Languages » C# » General » Downloads
 
Add your own
alternative version

SIP Stack with SIP Proxy - (VOIP)

, 11 Jun 2007 CPOL
C# implementation of SIP
SIP_Proxy_demo.zip
SIP_Proxy_demo
SIP_Proxy_demo.suo
SIP_Proxy_demo
bin
Debug
LumiSoft.Net.dll
SIP_Proxy_demo.exe
SIP_Proxy_demo.vshost.exe
dep
LumiSoft.Net.dll
LumiSoft.Net.pdb
Properties
Settings.settings
Resources
add.ico
app.ico
delete.ico
edit.ico
error.ico
info.ico
refresh.ico
rule.ico
server_running.ico
server_stopped.ico
viewmessages.ico
Stack.zip
Net
docs
dns
dns_records.jpg
dns_records.vsd
Net
_junk
_Obsolete
AUTH
bin
Release
LumiSoft.Net.dll
Data
Dns
Client
FTP
Client
Server
HTTP
Server
ICMP
IMAP
Client
Server
IO
Log
LumiSoft.Net
Mime
vCard
Net.csproj.user
Net.suo
NNTP
Client
POP3
Client
Server
RTP
SDP
ServersCore
SIP
Client
Message
Proxy
Stack
SMTP
Client
Server
STUN
Client
Message
URI
using System;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Collections;
using System.Text;
using LumiSoft.Net;
using LumiSoft.Net.IMAP;
using LumiSoft.Net.Mime;
using LumiSoft.Net.AUTH;

namespace LumiSoft.Net.IMAP.Server
{	
	/// <summary>
	/// IMAP session.
	/// </summary>
	public class IMAP_Session : SocketServerSession
	{
		private IMAP_Server         m_pServer         = null;
		private string              m_SelectedMailbox = "";
        private IMAP_SelectedFolder m_pSelectedFolder = null;
		private int                 m_BadCmdCount     = 0;
        private bool                m_Disposed        = false;

		/// <summary>
        /// Default constructor.
        /// </summary>
        /// <param name="sessionID">Session ID.</param>
        /// <param name="socket">Server connected socket.</param>
        /// <param name="bindInfo">BindInfo what accepted socket.</param>
        /// <param name="server">Reference to server.</param>
        internal IMAP_Session(string sessionID,SocketEx socket,BindInfo bindInfo,IMAP_Server server) : base(sessionID,socket,bindInfo,server)
        {
            m_pServer = server;

			// Start session proccessing
			StartSession();
		}


		#region method StartSession

		/// <summary>
		/// Starts session.
		/// </summary>
		private void StartSession()
		{
			// Add session to session list
			m_pServer.AddSession(this);
	
			try{
				// Check if ip is allowed to connect this computer
				if(m_pServer.OnValidate_IpAddress(this.LocalEndPoint,this.RemoteEndPoint)){
                    //--- Dedicated SSL connection, switch to SSL -----------------------------------//
                    if(this.BindInfo.SSL){
                        try{
                            this.Socket.SwitchToSSL(this.BindInfo.SSL_Certificate);

                            if(this.Socket.Logger != null){
                                this.Socket.Logger.AddTextEntry("SSL negotiation completed successfully.");
                            }
                        }
                        catch(Exception x){
                            if(this.Socket.Logger != null){
                                this.Socket.Logger.AddTextEntry("SSL handshake failed ! " + x.Message);

                                EndSession();
                                return;
                            }
                        }
                    }
                    //-------------------------------------------------------------------------------//

					// Notify that server is ready
                    if(m_pServer.GreetingText == ""){
					    this.Socket.WriteLine("* OK " + m_pServer.HostName + " IMAP Server ready");
                    }
                    else{
                        this.Socket.WriteLine("* OK " + m_pServer.GreetingText);
                    }

					BeginRecieveCmd();
				}
				else{
					EndSession();
				}
			}
			catch(Exception x){
				OnError(x);
			}
		}

		#endregion

		#region method EndSession

		/// <summary>
		/// Ends session, closes socket.
		/// </summary>
		private void EndSession()
		{	
            lock(this){
                // We have ended this sessions already, probably some async opration ended and called this, skip it.
		        if(m_Disposed){
                    return;
                }

			    try{
                    m_Disposed = true;

				    // Write logs to log file, if needed
				    if(m_pServer.LogCommands){
					    this.Socket.Logger.Flush();
				    }

				    if(this.Socket != null){
					    this.Socket.Shutdown(SocketShutdown.Both);
					    this.Socket.Disconnect();
				    }
			    }
			    catch{ // We don't need to check errors here, because they only may be Socket closing errors.
			    }
			    finally{
				    m_pServer.RemoveSession(this);
			    }
            }
		}

		#endregion


        #region method Kill

        /// <summary>
        /// Kill this session.
        /// </summary>
        public override void Kill()
        {
            EndSession();
        }

        #endregion

		#region method OnSessionTimeout

		/// <summary>
		/// Is called by server when session has timed out.
		/// </summary>
		internal protected override void OnSessionTimeout()
		{
			try{
				this.Socket.WriteLine("* BYE Session timeout, closing transmission channel");
			}
			catch{
			}

			EndSession();
		}

		#endregion

		#region method OnError

		/// <summary>
		/// Is called when error occures.
		/// </summary>
		/// <param name="x"></param>
		private void OnError(Exception x)
		{
            // Session disposed, so we don't care about terminating errors any more.
            if(m_Disposed){
                return;
            }

			try{
                // We must see InnerException too, SocketException may be as inner exception.
                SocketException socketException = null;
                if(x is SocketException){
                    socketException = (SocketException)x;
                }
                else if(x.InnerException != null && x.InnerException is SocketException){
                    socketException = (SocketException)x.InnerException;
                }

				if(socketException != null){
					// Client disconnected without shutting down
					if(socketException.ErrorCode == 10054 || socketException.ErrorCode == 10053){
						if(m_pServer.LogCommands){
							this.Socket.Logger.AddTextEntry("Client aborted/disconnected");
						}

						EndSession();

						// Exception handled, return
						return;
					}
                    // Read/Write timed-out
                    else if(socketException.ErrorCode == 10060){
                        if(m_pServer.LogCommands){
                            this.Socket.Logger.AddTextEntry("Read or write to destination host timed-out !");
                        }

                        // Exception handled, return
						return;
                    }
				}

				m_pServer.OnSysError("",x);
			}
			catch(Exception ex){
				m_pServer.OnSysError("",ex);
			}
		}

		#endregion


		#region method BeginRecieveCmd
		
		/// <summary>
		/// Starts recieveing command.
		/// </summary>
		private void BeginRecieveCmd()
		{
			MemoryStream strm = new MemoryStream();
			this.Socket.BeginReadLine(strm,1024,strm,new SocketCallBack(this.EndRecieveCmd));
		}

		#endregion

		#region method EndRecieveCmd

		/// <summary>
		/// Is called if command is recieved.
		/// </summary>
		/// <param name="result"></param>
		/// <param name="count"></param>
		/// <param name="exception"></param>
		/// <param name="tag"></param>
		private void EndRecieveCmd(SocketCallBackResult result,long count,Exception exception,object tag)
		{
			try{
				switch(result)
				{
					case SocketCallBackResult.Ok:
						MemoryStream strm = (MemoryStream)tag;

						string cmdLine = System.Text.Encoding.Default.GetString(strm.ToArray());

						// Exceute command
						if(SwitchCommand(cmdLine)){
							// Session end, close session
							EndSession();
						}
						break;

					case SocketCallBackResult.LengthExceeded:
						this.Socket.WriteLine("* BAD Line too long.");

						BeginRecieveCmd();
						break;

					case SocketCallBackResult.SocketClosed:
						EndSession();
						break;

					case SocketCallBackResult.Exception:
						OnError(exception);
						break;
				}
			}
			catch(Exception x){
				 OnError(x);
			}
		}

		#endregion

		
		#region method SwitchCommand

		/// <summary>
		/// Executes IMAP command.
		/// </summary>
		/// <param name="IMAP_commandTxt">Original command text.</param>
		/// <returns>Returns true if must end session(command loop).</returns>
		private bool SwitchCommand(string IMAP_commandTxt)
		{
			// Parse commandTag + comand + args
			// eg. C: a100 SELECT INBOX<CRLF>
			// a100   - commandTag
			// SELECT - command
			// INBOX  - arg

			//---- Parse command --------------------------------------------------//
			string[] cmdParts = IMAP_commandTxt.TrimStart().Split(new char[]{' '});
			// For bad command, just return empty cmdTag and command name
			if(cmdParts.Length < 2){
				cmdParts = new string[]{"",""};
			}
			string commandTag = cmdParts[0].Trim().Trim();
			string command    = cmdParts[1].ToUpper().Trim();
			string argsText   = Core.GetArgsText(IMAP_commandTxt,cmdParts[0] + " " + cmdParts[1]);
			//---------------------------------------------------------------------//

			bool getNextCmd = true;

			switch(command){
				//--- Non-Authenticated State
                case "STARTTLS":
				    STARTTLS(commandTag,argsText);
					break;

				case "AUTHENTICATE":
					Authenticate(commandTag,argsText);
					break;

				case "LOGIN":
					LogIn(commandTag,argsText);
					break;
				//--- End of non-Authenticated

				//--- Authenticated State
				case "SELECT":
					Select(commandTag,argsText);
					break;

				case "EXAMINE":
					Examine(commandTag,argsText);
					break;

				case "CREATE":
					Create(commandTag,argsText);
					break;

				case "DELETE":
					Delete(commandTag,argsText);
					break;

				case "RENAME":
					Rename(commandTag,argsText);
					break;

				case "SUBSCRIBE":
					Suscribe(commandTag,argsText);
					break;

				case "UNSUBSCRIBE":
					UnSuscribe(commandTag,argsText);
					break;

				case "LIST":
					List(commandTag,argsText);
					break;

				case "LSUB":
					LSub(commandTag,argsText);
					break;

				case "STATUS":
					Status(commandTag,argsText);
					break;

				case "APPEND":
					getNextCmd = BeginAppendCmd(commandTag,argsText);
					break;

				case "NAMESPACE":
					Namespace(commandTag,argsText);
					break;

				case "GETACL":
					GETACL(commandTag,argsText);
					break;

				case "SETACL":
					SETACL(commandTag,argsText);
					break;

				case "DELETEACL":
					DELETEACL(commandTag,argsText);
					break;

				case "LISTRIGHTS":
					LISTRIGHTS(commandTag,argsText);
					break;

				case "MYRIGHTS":
					MYRIGHTS(commandTag,argsText);
					break;

                case "GETQUOTA":
					GETQUOTA(commandTag,argsText);
					break;

                case "GETQUOTAROOT":
					GETQUOTAROOT(commandTag,argsText);
					break;
				//--- End of Authenticated

				//--- Selected State
				case "CHECK":
					Check(commandTag);
					break;

				case "CLOSE":
					Close(commandTag);
					break;

				case "EXPUNGE":	
					Expunge(commandTag);
					break;

				case "SEARCH":
					Search(commandTag,argsText,false);
					break;

				case "FETCH":
					Fetch(commandTag,argsText,false);
					break;

				case "STORE":
					Store(commandTag,argsText,false);
					break;

				case "COPY":
					Copy(commandTag,argsText,false);
					break;

				case "UID":	
					Uid(commandTag,argsText);
					break;
				//--- End of Selected 

				//--- Any State
				case "CAPABILITY":
					Capability(commandTag);
					break;

				case "NOOP":
					Noop(commandTag);
					break;

				case "LOGOUT":
					LogOut(commandTag);
					return true;
				//--- End of Any
										
				default:					
					this.Socket.WriteLine(commandTag + " BAD command unrecognized");

					//---- Check that maximum bad commands count isn't exceeded ---------------//
					if(m_BadCmdCount > m_pServer.MaxBadCommands-1){
						this.Socket.WriteLine("* BAD Too many bad commands, closing transmission channel");
						return true;
					}
					m_BadCmdCount++;
					//-------------------------------------------------------------------------//
					break;				
			}

			if(getNextCmd){
				BeginRecieveCmd();
			}
			
			return false;
		}

		#endregion


        //--- Non-Authenticated State ------

        #region method STARTTLS

        private void STARTTLS(string cmdTag,string argsText)
        {
            /* RFC 2595 3. IMAP STARTTLS extension.
                3. IMAP STARTTLS extension

                   When the TLS extension is present in IMAP, "STARTTLS" is listed as a
                   capability in response to the CAPABILITY command.  This extension
                   adds a single command, "STARTTLS" to the IMAP protocol which is used
                   to begin a TLS negotiation.

                3.1. STARTTLS Command

                   Arguments:  none

                   Responses:  no specific responses for this command

                   Result:     OK - 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 STARTTLS command is only valid in non-authenticated state.
                      The server remains in non-authenticated state, even if client
                      credentials are supplied during the TLS negotiation.  The SASL
                      [SASL] EXTERNAL mechanism MAY be used to authenticate once TLS
                      client credentials are successfully exchanged, but servers
                      supporting the STARTTLS command are not required to support the
                      EXTERNAL mechanism.

                      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.

                      The formal syntax for IMAP is amended as follows:

                        command_any   =/  "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=EXTERNAL
                               S: a003 OK CAPABILITY completed
                               C: a004 LOGIN joe password
                               S: a004 OK LOGIN completed
            */

            if(this.Authenticated){
                this.Socket.WriteLine(cmdTag + " NO The STARTTLS command is only valid in non-authenticated state !");
                return;
            }
            if(this.Socket.SSL){
                this.Socket.WriteLine(cmdTag + " NO The STARTTLS already started !");
                return;
            }
            if(this.BindInfo.SSL_Certificate == null){
                this.Socket.WriteLine(cmdTag + " NO TLS not available, SSL certificate isn't specified !");
				return;
            }


            this.Socket.WriteLine(cmdTag + " OK Ready to start TLS");

            try{          
                this.Socket.SwitchToSSL(this.BindInfo.SSL_Certificate);

                if(m_pServer.LogCommands){
                    this.Socket.Logger.AddTextEntry("TLS negotiation completed successfully.");
                }
            }
            catch(Exception x){
                this.Socket.WriteLine(cmdTag + " NO TLS handshake failed ! " + x.Message);
            }
        }

        #endregion

        #region method Authenticate

        private void Authenticate(string cmdTag,string argsText)
		{
			/* 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
			*/
			if(this.Authenticated){
				this.Socket.WriteLine(cmdTag + " NO AUTH you are already logged in");
                return;
			}

			string userName = "";
		//	string password = "";
			AuthUser_EventArgs aArgs = null;

			switch(argsText.ToUpper())
			{
				case "CRAM-MD5":

					#region CRAM-MDD5 authentication

					/* Cram-M5
					C: A0001 AUTH CRAM-MD5
					S: + <md5_calculation_hash_in_base64>
					C: base64(decoded:username password_hash)
					S: A0001 OK CRAM authentication successful
					*/
					
					string md5Hash = "<" + Guid.NewGuid().ToString().ToLower() + ">";
					this.Socket.WriteLine("+ " + Convert.ToBase64String(System.Text.Encoding.ASCII.GetBytes(md5Hash)));

					string reply = this.Socket.ReadLine();								
					reply = System.Text.Encoding.Default.GetString(Convert.FromBase64String(reply));
					string[] replyArgs = reply.Split(' ');
					userName = replyArgs[0];
					
					aArgs = m_pServer.OnAuthUser(this,userName,replyArgs[1],md5Hash,AuthType.CRAM_MD5);

                    // There is custom error, return it
                    if(aArgs.ErrorText != null){
                        this.Socket.WriteLine(cmdTag + " NO " + aArgs.ErrorText);
                        return;
                    }

					if(aArgs.Validated){
						this.Socket.WriteLine(cmdTag + " OK Authentication successful.");
						
						this.SetUserName(userName);
					}
					else{
						this.Socket.WriteLine(cmdTag + " NO Authentication failed");
					}

					#endregion

					break;

				case "DIGEST-MD5":
					
					#region DIGEST-MD5 authentication

					/* RFC 2831 AUTH DIGEST-MD5
					 * 
					 * Example:
					 * 
					 * C: AUTH DIGEST-MD5
					 * S: + base64(realm="elwood.innosoft.com",nonce="OA6MG9tEQGm2hh",qop="auth",algorithm=md5-sess)
					 * C: base64(username="chris",realm="elwood.innosoft.com",nonce="OA6MG9tEQGm2hh",
					 *    nc=00000001,cnonce="OA6MHXh6VqTrRk",digest-uri="imap/elwood.innosoft.com",
                     *    response=d388dad90d4bbd760a152321f2143af7,qop=auth)
					 * S: + base64(rspauth=ea40f60335c427b5527b84dbabcdfffd)
					 * C:
					 * S: A0001 OK Authentication successful.
					*/

					string realm = m_pServer.HostName;
					string nonce = AuthHelper.GenerateNonce();

					this.Socket.WriteLine("+ " + AuthHelper.Base64en(AuthHelper.Create_Digest_Md5_ServerResponse(realm,nonce)));

					string clientResponse = AuthHelper.Base64de(this.Socket.ReadLine());					
					// Check that realm and nonce in client response are same as we specified
					if(clientResponse.IndexOf("realm=\"" + realm + "\"") > - 1 && clientResponse.IndexOf("nonce=\"" + nonce + "\"") > - 1){
						// Parse user name and password compare value
				//		string userName  = "";
						string passwData = "";
						string cnonce = ""; 
						foreach(string clntRespParam in clientResponse.Split(',')){
							if(clntRespParam.StartsWith("username=")){
								userName = clntRespParam.Split(new char[]{'='},2)[1].Replace("\"","");
							}
							else if(clntRespParam.StartsWith("response=")){
								passwData = clntRespParam.Split(new char[]{'='},2)[1];
							}							
							else if(clntRespParam.StartsWith("cnonce=")){
								cnonce = clntRespParam.Split(new char[]{'='},2)[1].Replace("\"","");
							}
						}

						aArgs = m_pServer.OnAuthUser(this,userName,passwData,clientResponse,AuthType.DIGEST_MD5);

                        // There is custom error, return it
                        if(aArgs.ErrorText != null){
                            this.Socket.WriteLine(cmdTag + " NO " + aArgs.ErrorText);
                            return;
                        }

						if(aArgs.Validated){
							// Send server computed password hash
							this.Socket.WriteLine("+ " + AuthHelper.Base64en("rspauth=" + aArgs.ReturnData));
					
							// We must got empty line here
							clientResponse = this.Socket.ReadLine();
							if(clientResponse == ""){
								this.Socket.WriteLine(cmdTag + " OK Authentication successful.");

                                this.SetUserName(userName);
							}
							else{
								this.Socket.WriteLine(cmdTag + " NO Authentication failed");
							}
						}
						else{
							this.Socket.WriteLine(cmdTag + " NO Authentication failed");
						}
					}
					else{
						this.Socket.WriteLine(cmdTag + " NO Authentication failed");
					}
				
					#endregion

					break;

				default:
					this.Socket.WriteLine(cmdTag + " NO unsupported authentication mechanism");
					break;
			}
			
		}

		#endregion

		#region method LogIn

		private void LogIn(string cmdTag,string argsText)
		{
			/* 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.
			
				Example: C: a001 LOGIN SMITH SESAME
					     S: a001 OK LOGIN completed
						 
						 //----
						 C: a001 LOGIN "SMITH" "SESAME"
						 S: a001 OK LOGIN completed
			   
			*/
			if(this.Authenticated){
				this.Socket.WriteLine(cmdTag + " NO Reauthentication error, you are already logged in");
                return;
			}

			string[] args = TextUtils.SplitQuotedString(argsText,' ',true);
			if(args.Length != 2){
				this.Socket.WriteLine(cmdTag + " BAD Invalid arguments, syntax: {<command-tag> LOGIN \"<user-name>\" \"<password>\"}");
				return;
			}

			string userName = args[0];
			string password = args[1];

            // Store start time
			long startTime = DateTime.Now.Ticks;
            			
            AuthUser_EventArgs aArgs = m_pServer.OnAuthUser(this,userName,password,"",AuthType.Plain);
            // There is custom error, return it
            if(aArgs.ErrorText != null){
                this.Socket.WriteLine(cmdTag + " NO " + aArgs.ErrorText);
                return;
            }

			if(aArgs.Validated){
				this.Socket.WriteLine(cmdTag + " OK LOGIN Completed in " + ((DateTime.Now.Ticks - startTime) / (decimal)10000000).ToString("f2") + " seconds");

				this.SetUserName(userName);			
			}
			else{
				this.Socket.WriteLine(cmdTag + " NO LOGIN failed");
			}
		}

		#endregion

		//--- End of non-Authenticated State 


		//--- Authenticated State ------

		#region method Select

		private void Select(string cmdTag,string argsText)
		{
			/* 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
			   
			*/
			if(!this.Authenticated){
				this.Socket.WriteLine(cmdTag + " NO Authenticate first !");
				return;
			}

			string[] args = TextUtils.SplitQuotedString(argsText,' ',true);
			if(args.Length != 1){
				this.Socket.WriteLine(cmdTag + " BAD SELECT invalid arguments. Syntax: {<command-tag> SELECT \"mailboxName\"}");
				return;
			}

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

            IMAP_SelectedFolder selectedFolder = new IMAP_SelectedFolder(Core.Decode_IMAP_UTF7_String(args[0]));
			IMAP_eArgs_GetMessagesInfo eArgs = m_pServer.OnGetMessagesInfo(this,selectedFolder);
			if(eArgs.ErrorText == null){
				m_pSelectedFolder = selectedFolder;
				m_SelectedMailbox = Core.Decode_IMAP_UTF7_String(args[0]);

				string response = "";
				response += "* FLAGS (\\Answered \\Flagged \\Deleted \\Seen \\Draft)\r\n";
				response += "* " + m_pSelectedFolder.Messages.Count + " EXISTS\r\n";
				response += "* " + m_pSelectedFolder.RecentCount + " RECENT\r\n";
				response += "* OK [UNSEEN " + m_pSelectedFolder.FirstUnseen + "] Message " + m_pSelectedFolder.FirstUnseen + " is first unseen\r\n";
				response += "* OK [UIDVALIDITY " + m_pSelectedFolder.FolderUID + "] Folder UID\r\n";
				response += "* OK [UIDNEXT " + m_pSelectedFolder.MessageUidNext + "] Predicted next message UID\r\n";
				response += "* OK [PERMANENTFLAGS (\\Answered \\Flagged \\Deleted \\Seen \\Draft)] Available permanent flags\r\n";
				response += cmdTag + " OK [" + (m_pSelectedFolder.ReadOnly ? "READ-ONLY" : "READ-WRITE") + "] SELECT Completed in " + ((DateTime.Now.Ticks - startTime) / (decimal)10000000).ToString("f2") + " seconds\r\n";

				this.Socket.Write(response);
			}
			else{
				this.Socket.WriteLine(cmdTag + " NO " + eArgs.ErrorText);
			}
		}

		#endregion

		#region method Examine

		private void Examine(string cmdTag,string argsText)
		{
			/* 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
			*/
			if(!this.Authenticated){
				this.Socket.WriteLine(cmdTag + " NO Authenticate first !");
				return;
			}

			string[] args = TextUtils.SplitQuotedString(argsText,' ',true);
			if(args.Length != 1){
				this.Socket.WriteLine(cmdTag + " BAD EXAMINE invalid arguments. Syntax: {<command-tag> EXAMINE \"mailboxName\"}");
				return;
			}

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

            IMAP_SelectedFolder selectedFolder = new IMAP_SelectedFolder(Core.Decode_IMAP_UTF7_String(args[0]));
			IMAP_eArgs_GetMessagesInfo eArgs = m_pServer.OnGetMessagesInfo(this,selectedFolder);
			if(eArgs.ErrorText == null){
                m_pSelectedFolder = selectedFolder;
				m_pSelectedFolder.ReadOnly = true;				
				m_SelectedMailbox = Core.Decode_IMAP_UTF7_String(args[0]);

				string response = "";
				response += "* FLAGS (\\Answered \\Flagged \\Deleted \\Seen \\Draft)\r\n";
				response += "* " + m_pSelectedFolder.Messages.Count + " EXISTS\r\n";
				response += "* " + m_pSelectedFolder.RecentCount + " RECENT\r\n";
				response += "* OK [UNSEEN " + m_pSelectedFolder.FirstUnseen + "] Message " + m_pSelectedFolder.FirstUnseen + " is first unseen\r\n";
				response += "* OK [UIDVALIDITY " + m_pSelectedFolder.FolderUID + "] UIDs valid\r\n";
				response += "* OK [UIDNEXT " + m_pSelectedFolder.MessageUidNext + "] Predicted next UID\r\n";
				response += "* OK [PERMANENTFLAGS (\\Answered \\Flagged \\Deleted \\Seen \\Draft)] Available permanent falgs\r\n";
				response += cmdTag + " OK [READ-ONLY] EXAMINE  Completed in " + ((DateTime.Now.Ticks - startTime) / (decimal)10000000).ToString("f2") + " seconds\r\n";

				this.Socket.Write(response);
			}
			else{
				this.Socket.WriteLine(cmdTag + " NO " + eArgs.ErrorText);
			}
		}

		#endregion

		#region method Create

		private void Create(string cmdTag,string argsText)
		{
			/* RFC 3501 6.3.3
				
				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
				it.

				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 complete successfully.
				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
			   
			*/
			if(!this.Authenticated){
				this.Socket.WriteLine(cmdTag + " NO Authenticate first !");
				return;
			}

			string[] args = TextUtils.SplitQuotedString(argsText,' ',true);
			if(args.Length != 1){
				this.Socket.WriteLine(cmdTag + " BAD CREATE invalid arguments. Syntax: {<command-tag> CREATE \"mailboxName\"}");
				return;
			}

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

			string errorText = m_pServer.OnCreateMailbox(this,Core.Decode_IMAP_UTF7_String(args[0]));
			if(errorText == null){
				this.Socket.WriteLine(cmdTag + " OK CREATE Completed in " + ((DateTime.Now.Ticks - startTime) / (decimal)10000000).ToString("f2") + " seconds" );
			}
			else{
				this.Socket.WriteLine(cmdTag + " NO " + errorText);
			}
		}

		#endregion

		#region method Delete

		private void Delete(string cmdTag,string argsText)
		{
			/* RFC 3501 6.3.4 DELETE 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 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.Authenticated){
				this.Socket.WriteLine(cmdTag + " NO Authenticate first !");
				return;
			}

			string[] args = TextUtils.SplitQuotedString(argsText,' ',true);
			if(args.Length != 1){
				this.Socket.WriteLine(cmdTag + " BAD DELETE invalid arguments. Syntax: {<command-tag> DELETE \"mailboxName\"}");
				return;
			}
			
			string errorText = m_pServer.OnDeleteMailbox(this,Core.Decode_IMAP_UTF7_String(args[0]));
			if(errorText == null){
				this.Socket.WriteLine(cmdTag + " OK DELETE Completed");
			}
			else{
				this.Socket.WriteLine(cmdTag + " NO " + errorText);
			}			
		}

		#endregion

		#region method Rename

		private void Rename(string cmdTag,string argsText)
		{
			/* 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".

				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.Authenticated){
				this.Socket.WriteLine(cmdTag + " NO Authenticate first !");
				return;
			}

			string[] args = TextUtils.SplitQuotedString(argsText,' ',true);
			if(args.Length != 2){
				this.Socket.WriteLine(cmdTag + " BAD RENAME invalid arguments. Syntax: {<command-tag> RENAME \"mailboxName\" \"newMailboxName\"}");
				return;
			}

			string mailbox    = Core.Decode_IMAP_UTF7_String(args[0]);
			string newMailbox = Core.Decode_IMAP_UTF7_String(args[1]);
			
			string errorText = m_pServer.OnRenameMailbox(this,mailbox,newMailbox);
			if(errorText == null){
				this.Socket.WriteLine(cmdTag + " OK RENAME Completed");
			}
			else{
				this.Socket.WriteLine(cmdTag + " NO " + errorText);
			}		
		}

		#endregion

		#region method Suscribe

		private void Suscribe(string cmdTag,string argsText)
		{
			/* RFC 3501 6.3.6 SUBSCRIBE Commmand
				
				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 some server sites may 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.Authenticated){
				this.Socket.WriteLine(cmdTag + " NO Authenticate first !");
				return;
			}

			string[] args = TextUtils.SplitQuotedString(argsText,' ',true);
			if(args.Length != 1){
				this.Socket.WriteLine(cmdTag + " BAD SUBSCRIBE invalid arguments. Syntax: {<command-tag> SUBSCRIBE \"mailboxName\"}");
				return;
			}

			string errorText = m_pServer.OnSubscribeMailbox(this,Core.Decode_IMAP_UTF7_String(args[0]));
			if(errorText == null){
				this.Socket.WriteLine(cmdTag + " OK SUBSCRIBE completed");
			}
			else{
				this.Socket.WriteLine(cmdTag + " NO " + errorText);
			}			
		}

		#endregion

		#region methd UnSuscribe

		private void UnSuscribe(string cmdTag,string argsText)
		{
			/* RFC 3501 6.3.7 UNSUBSCRIBE 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 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.Authenticated){
				this.Socket.WriteLine(cmdTag + " NO Authenticate first !");
				return;
			}

			string[] args = TextUtils.SplitQuotedString(argsText,' ',true);
			if(args.Length != 1){
				this.Socket.WriteLine(cmdTag + " BAD UNSUBSCRIBE invalid arguments. Syntax: {<command-tag> UNSUBSCRIBE \"mailboxName\"}");
				return;
			}

			string errorText = m_pServer.OnUnSubscribeMailbox(this,Core.Decode_IMAP_UTF7_String(args[0]));
			if(errorText == null){
				this.Socket.WriteLine(cmdTag + " OK UNSUBSCRIBE completed");
			}
			else{
				this.Socket.WriteLine(cmdTag + " NO " + errorText);
			}			
		}

		#endregion

		#region method List

		private void List(string cmdTag,string argsText)
		{
			/* Rc 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.
				
				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 a context in which the mailbox
				name is interpreted in an implementation-defined manner.
				
				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 null if
				the reference is non-rooted or is null.  In all cases, the
				hierarchy delimiter is returned.  This permits a client to get the
				hierarchy delimiter even when no mailboxes by that name currently
				exist.
				
				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(!this.Authenticated){
				this.Socket.WriteLine(cmdTag + " NO Authenticate first !");
				return;
			}

			string[] args  = TextUtils.SplitQuotedString(argsText,' ',true);
			if(args.Length != 2){
				this.Socket.WriteLine(cmdTag + " BAD Invalid LIST arguments. Syntax: {<command-tag> LIST \"<reference-name>\" \"<mailbox-name>\"}");
				return;
			}

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

			string refName = Core.Decode_IMAP_UTF7_String(args[0]);
			string mailbox = Core.Decode_IMAP_UTF7_String(args[1]);

			string reply = "";

			// Folder separator wanted
			if(mailbox.Length == 0){
				reply += "* LIST (\\Noselect) \"/\" \"\"\r\n";
			}
			// List mailboxes
			else{				
				IMAP_Folders mailboxes = m_pServer.OnGetMailboxes(this,refName,mailbox);
				foreach(IMAP_Folder mBox in mailboxes.Folders){
					if(mBox.Selectable){
						reply += "* LIST () \"/\" \"" + Core.Encode_IMAP_UTF7_String(mBox.Folder) + "\" \r\n";
					}
					else{
						reply += "* LIST (\\Noselect) \"/\" \"" + Core.Encode_IMAP_UTF7_String(mBox.Folder) + "\" \r\n";
					}
				}
			}

			reply += cmdTag + " OK LIST Completed in " + ((DateTime.Now.Ticks - startTime) / (decimal)10000000).ToString("f2") + " seconds\r\n";
			this.Socket.Write(reply);
		}

		#endregion

		#region method LSub

		private void LSub(string cmdTag,string argsText)
		{
			/* 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
			   
			*/
			if(!this.Authenticated){
				this.Socket.WriteLine(cmdTag + " NO Authenticate first !");
				return;
			}

			string[] args  = TextUtils.SplitQuotedString(argsText,' ',true);
			if(args.Length != 2){
				this.Socket.WriteLine(cmdTag + " BAD LSUB invalid arguments");
				return;
			}

			string refName = Core.Decode_IMAP_UTF7_String(args[0]);
			string mailbox = Core.Decode_IMAP_UTF7_String(args[1]);
			
			string reply = "";

			IMAP_Folders mailboxes = m_pServer.OnGetSubscribedMailboxes(this,refName,mailbox);
			foreach(IMAP_Folder mBox in mailboxes.Folders){
				reply += "* LSUB () \"/\" \"" + Core.Encode_IMAP_UTF7_String(mBox.Folder) + "\" \r\n";
			}

			reply += cmdTag + " OK LSUB Completed\r\n";
			this.Socket.Write(reply);
		}

		#endregion

		#region method Status

		private void Status(string cmdTag,string argsText)
		{
			/* 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.  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.

				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.Authenticated){
				this.Socket.WriteLine(cmdTag + " NO Authenticate first !");
				return;
			}

			string[] args = ParseParams(argsText);
			if(args.Length != 2){
				this.Socket.WriteLine(cmdTag + " BAD Invalid STATUS arguments. Syntax: {<command-tag> STATUS \"<mailbox-name>\" \"(status-data-items)\"}");
				return;
			}

			string folder      = Core.Decode_IMAP_UTF7_String(args[0]);
			string wantedItems = args[1].ToUpper();

			// See wanted items are valid.
			if(wantedItems.Replace("MESSAGES","").Replace("RECENT","").Replace("UIDNEXT","").Replace("UIDVALIDITY","").Replace("UNSEEN","").Trim().Length > 0){
				this.Socket.WriteLine(cmdTag + " BAD STATUS invalid arguments");
				return;
			}

            IMAP_SelectedFolder selectedFolder = new IMAP_SelectedFolder(folder);
			IMAP_eArgs_GetMessagesInfo eArgs = m_pServer.OnGetMessagesInfo(this,selectedFolder);
			if(eArgs.ErrorText == null){
				string itemsReply = "";
				if(wantedItems.IndexOf("MESSAGES") > -1){
					itemsReply += " MESSAGES " + selectedFolder.Messages.Count;
				}
				if(wantedItems.IndexOf("RECENT") > -1){
					itemsReply += " RECENT " + selectedFolder.RecentCount;
				}
				if(wantedItems.IndexOf("UNSEEN") > -1){
					itemsReply += " UNSEEN " + selectedFolder.UnSeenCount;
				}			
				if(wantedItems.IndexOf("UIDVALIDITY") > -1){
					itemsReply += " UIDVALIDITY " + selectedFolder.FolderUID;
				}
				if(wantedItems.IndexOf("UIDNEXT") > -1){
					itemsReply += " UIDNEXT " + selectedFolder.MessageUidNext;
				}
				itemsReply = itemsReply.Trim();

				this.Socket.WriteLine("* STATUS " + args[0] + " (" + itemsReply + ")");
				this.Socket.WriteLine(cmdTag + " OK STATUS completed");
			}
			else{
				this.Socket.WriteLine(cmdTag + " NO " + eArgs.ErrorText);
			}
		}

		#endregion

		#region command Append

		#region method BeginAppendCmd

		/// <summary>
		/// Returns true if command ended syncronously.
		/// </summary>
		private bool BeginAppendCmd(string cmdTag,string argsText)
		{
			/* 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.
					
				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.
					
				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
					
			*/
			if(!this.Authenticated){
				this.Socket.WriteLine(cmdTag + " NO Authenticate first !");
				return true;
			}

			string[] args = ParseParams(argsText);
			if(args.Length < 2 || args.Length > 4){
				this.Socket.WriteLine(cmdTag + " BAD APPEND Invalid arguments");
				return true;
			}

			string            mailbox = Core.Decode_IMAP_UTF7_String(args[0]);			
			IMAP_MessageFlags mFlags  = 0;
			DateTime          date    = DateTime.Now;
			long              msgLen  = Convert.ToInt64(args[args.Length - 1].Replace("{","").Replace("}",""));

			if(args.Length == 4){
				//--- Parse flags, see if valid ----------------
				string flags = args[1].ToUpper();
				if(flags.Replace("\\ANSWERED","").Replace("\\FLAGGED","").Replace("\\DELETED","").Replace("\\SEEN","").Replace("\\DRAFT","").Trim().Length > 0){
					this.Socket.WriteLine(cmdTag + " BAD arguments invalid");
					return false;
				}
				
				mFlags = IMAP_Utils.ParseMessageFlags(flags);				
				date = MimeUtils.ParseDate(args[2]);
			}
			else if(args.Length == 3){
				// See if date or flags specified, try date first
				try{
					date = MimeUtils.ParseDate(args[1]);
				}
				catch{
					//--- Parse flags, see if valid ----------------
					string flags = args[1].ToUpper();
					if(flags.Replace("\\ANSWERED","").Replace("\\FLAGGED","").Replace("\\DELETED","").Replace("\\SEEN","").Replace("\\DRAFT","").Trim().Length > 0){
						this.Socket.WriteLine(cmdTag + " BAD arguments invalid");
						return false;
					}
			
					mFlags = IMAP_Utils.ParseMessageFlags(flags);
				}
			}

			// Request data
			this.Socket.WriteLine("+ Ready for literal data");

			MemoryStream strm = new MemoryStream();
			Hashtable param = new Hashtable();
			param.Add("cmdTag",cmdTag);
			param.Add("mailbox",mailbox);
			param.Add("mFlags",mFlags);
			param.Add("date",date);
			param.Add("strm",strm);

			// Begin recieving data                      Why needed msgLen+2 ??? 
			this.Socket.BeginReadSpecifiedLength(strm,(int)msgLen + 2,param,new SocketCallBack(this.EndAppendCmd));

			return false;
		}

		#endregion

		#region method EndAppendCmd

		/// <summary>
		/// Is called when DATA command is finnished.
		/// </summary>
		/// <param name="result"></param>
		/// <param name="count"></param>
		/// <param name="exception"></param>
		/// <param name="tag"></param>
		private void EndAppendCmd(SocketCallBackResult result,long count,Exception exception,object tag)
		{
			try{
				switch(result)
				{
					case SocketCallBackResult.Ok:
						Hashtable         param   = (Hashtable)tag;
						string            cmdTag  = (string)param["cmdTag"];
						string            mailbox = (string)param["mailbox"];
						IMAP_MessageFlags mFlags  = (IMAP_MessageFlags)param["mFlags"];
						DateTime          date    = (DateTime)param["date"];
						MemoryStream      strm    = (MemoryStream)param["strm"];
						
						IMAP_Message msg = new IMAP_Message(null,"",0,date,0,mFlags);
						string errotText = m_pServer.OnStoreMessage(this,mailbox,msg,strm.ToArray());
						if(errotText == null){
							this.Socket.WriteLine(cmdTag + " OK APPEND completed, recieved " + strm.Length + " bytes");
						}
						else{
							this.Socket.WriteLine(cmdTag + " NO " + errotText);
						}												
						break;

					case SocketCallBackResult.LengthExceeded:
					//	SendData("552 Requested mail action aborted: exceeded storage allocation\r\n");

					//	BeginRecieveCmd();
						break;

					case SocketCallBackResult.SocketClosed:
						EndSession();
						return;

					case SocketCallBackResult.Exception:
						OnError(exception);
						return;
				}

				// Command completed ok, get next command
				BeginRecieveCmd();
			}
			catch(Exception x){
				OnError(x);
			}
		}

		#endregion

		#endregion

		#region method Namespace

		private void Namespace(string cmdTag,string argsText)
		{
			/* 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(!this.Authenticated){
				this.Socket.WriteLine(cmdTag + " NO Authenticate first !");
				return;
			}

			SharedRootFolders_EventArgs eArgs = m_pServer.OnGetSharedRootFolders(this);
			string publicRootFolders = "NIL";
			if(eArgs.PublicRootFolders != null && eArgs.PublicRootFolders.Length > 0){
				publicRootFolders = "(";
				foreach(string publicRootFolder in eArgs.PublicRootFolders){
					publicRootFolders += "(\"" + publicRootFolder + "/\" \"/\")";
				}
				publicRootFolders += ")";
			}
			string sharedRootFolders = "NIL";
			if(eArgs.SharedRootFolders != null && eArgs.SharedRootFolders.Length > 0){
				sharedRootFolders = "(";
				foreach(string sharedRootFolder in eArgs.SharedRootFolders){
					sharedRootFolders += "(\"" + sharedRootFolder + "/\" \"/\")";
				}
				sharedRootFolders += ")";
			}
			
			string response  = "* NAMESPACE ((\"\" \"/\")) " + sharedRootFolders + " " + publicRootFolders + "\r\n";
			       response += cmdTag + " OK NAMESPACE completed";

			this.Socket.WriteLine(response);
		}

		#endregion


		#region method GETACL

		private void GETACL(string cmdTag,string argsText)
		{
			/* RFC 2086 4.3. GETACL
				Arguments:  mailbox name

				Data:       untagged responses: ACL

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

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

				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(!this.Authenticated){
				this.Socket.WriteLine(cmdTag + " NO Authenticate first !");
				return;
			}

			string[] args = TextUtils.SplitQuotedString(argsText,' ',true);
			if(args.Length != 1){
				this.Socket.WriteLine(cmdTag + " BAD GETACL invalid arguments. Syntax: GETACL<SP>FolderName<CRLF>");
				return;
			}

			IMAP_GETACL_eArgs eArgs = m_pServer.OnGetFolderACL(this,Core.Decode_IMAP_UTF7_String(IMAP_Utils.NormalizeFolder(args[0])));
			if(eArgs.ErrorText.Length > 0){
				this.Socket.WriteLine(cmdTag + " NO GETACL " + eArgs.ErrorText);
			}
			else{
				string response = "";
				if(eArgs.ACL.Count > 0){
					response += "* ACL \"" + args[0] + "\"";
					foreach(DictionaryEntry ent in eArgs.ACL){
						string aclFalgs = IMAP_Utils.ACL_to_String((IMAP_ACL_Flags)ent.Value);
						if(aclFalgs.Length == 0){
							aclFalgs = "\"\"";
						}
						response += " \"" + ent.Key + "\" " + aclFalgs;
					}
					response += "\r\n";
				}
				response += cmdTag + " OK Getacl complete\r\n";

				this.Socket.Write(response);
			}
		}

		#endregion

		#region method SETACL

		private void SETACL(string cmdTag,string argsText)
		{
			/* RFC 2086 4.1. SETACL
				Arguments:  mailbox name
							authentication identifier
							access right modification

				Data:       no specific data for this command

				Result:     OK - setacl completed
							NO - setacl failure: can't set acl
							BAD - command unknown or 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.
			*/

			if(!this.Authenticated){
				this.Socket.WriteLine(cmdTag + " NO Authenticate first !");
				return;
			}

			string[] args = ParseParams(argsText);
			if(args.Length != 3){
				this.Socket.WriteLine(cmdTag + " BAD GETACL invalid arguments. Syntax: SETACL<SP>FolderName<SP>UserName<SP>ACL_Flags<CRLF>");
				return;
			}

			string aclFlags = args[2];
			IMAP_Flags_SetType setType = IMAP_Flags_SetType.Replace;
			if(aclFlags.StartsWith("+")){
				setType = IMAP_Flags_SetType.Add;
			}
			else if(aclFlags.StartsWith("-")){
				setType = IMAP_Flags_SetType.Remove;
			}

			IMAP_SETACL_eArgs eArgs = m_pServer.OnSetFolderACL(this,IMAP_Utils.NormalizeFolder(args[0]),args[1],setType,IMAP_Utils.ACL_From_String(aclFlags));
			if(eArgs.ErrorText.Length > 0){
				this.Socket.WriteLine(cmdTag + " NO SETACL " + eArgs.ErrorText);
			}
			else{
				this.Socket.WriteLine(cmdTag + " OK SETACL completed");
			}
		}

		#endregion

		#region method DELETEACL

		private void DELETEACL(string cmdTag,string argsText)
		{
			/* RFC 2086 4.2. DELETEACL
				Arguments:  mailbox name
							authentication identifier

				Data:       no specific data for this command

				Result:     OK - deleteacl completed
							NO - deleteacl failure: can't delete acl
							BAD - command unknown or 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: A002 DELETEACL INBOX test
							S: A002 OK DELETEACL completed
			*/

			if(!this.Authenticated){
				this.Socket.WriteLine(cmdTag + " NO Authenticate first !");
				return;
			}

			string[] args = TextUtils.SplitQuotedString(argsText,' ',true);
			if(args.Length != 2){
				this.Socket.WriteLine(cmdTag + " BAD GETACL invalid arguments. Syntax: DELETEACL<SP>FolderName<SP>UserName<CRLF>");
				return;
			}

			IMAP_DELETEACL_eArgs eArgs = m_pServer.OnDeleteFolderACL(this,IMAP_Utils.NormalizeFolder(args[0]),args[1]);
			if(eArgs.ErrorText.Length > 0){
				this.Socket.WriteLine(cmdTag + " NO DELETEACL " + eArgs.ErrorText);
			}
			else{
				this.Socket.WriteLine(cmdTag + " OK DELETEACL completed");
			}
		}

		#endregion

		#region method LISTRIGHTS

		private void LISTRIGHTS(string cmdTag,string argsText)
		{
			/* RFC 2086 4.4. LISTRIGHTS
				Arguments:  mailbox name
				authentication identifier

				Data:       untagged responses: LISTRIGHTS

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

					The LISTRIGHTS command takes a mailbox name and an identifier and
					returns information about what rights may be granted to the identifier
					in the ACL for the mailbox.
					
				Example:    C: a001 LISTRIGHTS ~/Mail/saved smith
							S: * LISTRIGHTS ~/Mail/saved "smith" la r swicd
							S: a001 OK Listrights completed


							C: a005 LISTRIGHTS archive.imap anyone
							S: * LISTRIGHTS archive.imap "anyone" "" l r s w i p c d a
							0 1 2 3 4 5 6 7 8 9
			   
			*/

			if(!this.Authenticated){
				this.Socket.WriteLine(cmdTag + " NO Authenticate first !");
				return;
			}

			string[] args = TextUtils.SplitQuotedString(argsText,' ',true);
			if(args.Length != 2){
				this.Socket.WriteLine(cmdTag + " BAD GETACL invalid arguments. Syntax: LISTRIGHTS<SP>FolderName<SP>UserName<CRLF>");
				return;
			}
			
			string response = "* LISTRIGHTS " + "\"" + args[0] + "\" \"" + args[1] + "\" l r s w i p c d a\r\n";
			response += cmdTag + " OK MYRIGHTS Completed\r\n";

			this.Socket.Write(response);
		}

		#endregion

		#region method MYRIGHTS

		private void MYRIGHTS(string cmdTag,string argsText)
		{
			/* RFC 2086 4.5. MYRIGHTS
				Arguments:  mailbox name

				Data:       untagged responses: MYRIGHTS

				Result:     OK - myrights completed
							NO - myrights failure: can't get rights
							BAD - command unknown or 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 rwipslda
							S: A003 OK Myrights complete
							
			*/

			if(!this.Authenticated){
				this.Socket.WriteLine(cmdTag + " NO Authenticate first !");
				return;
			}

			string[] args = TextUtils.SplitQuotedString(argsText,' ',true);
			if(args.Length != 1){
				this.Socket.WriteLine(cmdTag + " BAD GETACL invalid arguments. Syntax: MYRIGHTS<SP>FolderName<CRLF>");
				return;
			}

			IMAP_GetUserACL_eArgs eArgs = m_pServer.OnGetUserACL(this,IMAP_Utils.NormalizeFolder(args[0]),this.UserName);
			if(eArgs.ErrorText.Length > 0){
				this.Socket.WriteLine(cmdTag + " NO MYRIGHTS " + eArgs.ErrorText);
			}
			else{
				string aclFlags = IMAP_Utils.ACL_to_String(eArgs.ACL);
				if(aclFlags.Length == 0){
					aclFlags = "\"\"";
				}
				string response = "* MYRIGHTS " + "\"" + args[0] + "\" " + aclFlags + "\r\n";
				response += cmdTag + " OK MYRIGHTS Completed\r\n";

				this.Socket.Write(response);
			}
		}

		#endregion


        #region method GETQUOTA

		private void GETQUOTA(string cmdTag,string argsText)
		{
			/* 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.Authenticated){
				this.Socket.WriteLine(cmdTag + " NO Authenticate first !");
				return;
			}

			string[] args = TextUtils.SplitQuotedString(argsText,' ',true);
			if(args.Length != 1){
				this.Socket.WriteLine(cmdTag + " BAD GETQUOTA invalid arguments. Syntax: GETQUOTA \"quota_root\"<CRLF>");
				return;
			}

            IMAP_eArgs_GetQuota eArgs = m_pServer.OnGetUserQuota(this);
            string reply  = "* QUOTA \"\" (STORAGE " + eArgs.MailboxSize + " " + eArgs.MaxMailboxSize + ")\r\n";
                   reply += cmdTag + " OK GETQUOTA completed\r\n";
            this.Socket.Write(reply);
		}

		#endregion

        #region method GETQUOTAROOT

		private void GETQUOTAROOT(string cmdTag,string argsText)
		{
			/* 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(!this.Authenticated){
				this.Socket.WriteLine(cmdTag + " NO Authenticate first !");
				return;
			}

			string[] args = TextUtils.SplitQuotedString(argsText,' ',true);
			if(args.Length != 1){
				this.Socket.WriteLine(cmdTag + " BAD GETQUOTAROOT invalid arguments. Syntax: GETQUOTAROOT \"folder\"<CRLF>");
				return;
			}

            IMAP_eArgs_GetQuota eArgs = m_pServer.OnGetUserQuota(this);
            string reply  = "* QUOTAROOT \"" + args[0] + "\" \"\"\r\n";
                   reply += "* QUOTA \"\" (STORAGE " + eArgs.MailboxSize + " " + eArgs.MaxMailboxSize + ")\r\n";
                   reply += cmdTag + " OK GETQUOTAROOT completed\r\n";
            this.Socket.Write(reply);
		}

		#endregion

		//--- End of Authenticated State 


		//--- Selected State ------

		#region method Check

		private void Check(string cmdTag)
		{
			/* RFC 3501 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
				mail polling.
				
				Example:    C: FXXZ CHECK
							S: FXXZ OK CHECK Completed
			   
			*/
			if(!this.Authenticated){
				this.Socket.WriteLine(cmdTag + " NO Authenticate first !");
				return;
			}
			if(m_SelectedMailbox.Length == 0){
				this.Socket.WriteLine(cmdTag + " NO Select mailbox first !");
				return;
			}
			
			this.Socket.WriteLine(cmdTag + " OK CHECK completed");
		}

		#endregion

		#region method Close

		private void Close(string cmdTag)
		{
			/* 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 from the currently selected
				mailbox all messages that have the \Deleted flag set, and returns
				to authenticated state from 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.Authenticated){
				this.Socket.WriteLine(cmdTag + " NO Authenticate first !");
				return;
			}			
			if(m_SelectedMailbox.Length == 0){
				this.Socket.WriteLine(cmdTag + " NO Select mailbox first !");
				return;
			}

			if(!m_pSelectedFolder.ReadOnly){
				IMAP_Message[] messages = m_pSelectedFolder.Messages.GetWithFlags(IMAP_MessageFlags.Deleted);
				foreach(IMAP_Message msg in messages){
					m_pServer.OnDeleteMessage(this,msg);
				}
			}
			
			m_SelectedMailbox = "";
			m_pSelectedFolder = null;

			this.Socket.WriteLine(cmdTag + " OK CLOSE completed");
		}

		#endregion

		#region method Expunge

		private void Expunge(string cmdTag)
		{
			/* 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.
					
				The EXPUNGE response reports that the specified message sequence
			    number has been permanently removed from the mailbox.  The message
				sequence number for each successive message in the mailbox is
				IMMEDIATELY DECREMENTED BY 1, and this decrement is reflected in
				message sequence numbers in subsequent responses (including other
				untagged EXPUNGE responses).


				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.Authenticated){
				this.Socket.WriteLine(cmdTag + " NO Authenticate first !");
				return;
			}
			if(m_SelectedMailbox.Length == 0){
				this.Socket.WriteLine(cmdTag + " NO Select mailbox first !");
				return;
			}
			
			IMAP_Message[] messages = m_pSelectedFolder.Messages.GetWithFlags(IMAP_MessageFlags.Deleted);
			for(int i=0;i<messages.Length;i++){
				IMAP_Message msg = messages[i]; 

				string errorText = m_pServer.OnDeleteMessage(this,msg);
				if(errorText == null){
					this.Socket.WriteLine("* " + msg.SequenceNo + " EXPUNGE");
					m_pSelectedFolder.Messages.Remove(msg);
				}
				else{
					this.Socket.WriteLine(cmdTag + " NO " + errorText);
					return;
				}
			}

			this.Socket.WriteLine(cmdTag + " OK EXPUNGE completed");
		}

		#endregion

		#region method Search

		private void Search(string cmdTag,string argsText,bool uidSearch)
		{
			/* 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}
						S: + Continue                          ### THIS IS UNDOCUMENTED !!!
						C: XXXXXX[arg conitnue]<CRLF>
						S: * SEARCH 43
						S: A284 OK SEARCH completed

				Note: Since this document is restricted to 7-bit ASCII
				text, it is not possible to show actual UTF-8 data.  The
				"XXXXXX" is a placeholder for what would be 6 octets of
				8-bit data in an actual transaction.
			*/
			if(!this.Authenticated){
				this.Socket.WriteLine(cmdTag + " NO Authenticate first !");
				return;
			}
			if(m_SelectedMailbox.Length == 0){
				this.Socket.WriteLine(cmdTag + " NO Select mailbox first !");
				return;
			}

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

			//--- Get Optional charset, if specified -----------------------------------------------------------------//
			string charset = "ASCII";
			// CHARSET charset
			if(argsText.ToUpper().StartsWith("CHARSET")){
				// Remove CHARSET from argsText
				argsText = argsText.Substring(7).Trim();

				string charsetValueString = IMAP_Utils.ParseQuotedParam(ref argsText);

				try{
					System.Text.Encoding.GetEncoding(charsetValueString);

					charset = charsetValueString;
				}
				catch{
					this.Socket.WriteLine(cmdTag + " NO [BADCHARSET UTF-8] " + charsetValueString + " is not supported");
					return;
				}
			}
			//---------------------------------------------------------------------------------------------------------//

			/* If multiline command, read all lines
				C: A284 SEARCH CHARSET UTF-8 TEXT {6}
				S: + Continue                             ### THIS IS UNDOCUMENTED !!!
				C: XXXXXX[arg conitnue]<CRLF>
			*/			
			argsText = argsText.Trim();
			while(argsText.EndsWith("}") && argsText.IndexOf("{") > -1){
				long dataLength = 0;
				try{
					// Get data length from {xxx}
					dataLength = Convert.ToInt64(argsText.Substring(argsText.LastIndexOf("{") + 1 ,argsText.Length - argsText.LastIndexOf("{") - 2));					
				}
				// There is no valid numeric value between {}, just skip and allow SearchGroup parser to handle this value
				catch{
					break;
				}
		
				MemoryStream dataStream = new MemoryStream();

				this.Socket.WriteLine("+ Continue");
				this.Socket.ReadSpecifiedLength((int)dataLength,dataStream);
				string argsContinueLine = this.Socket.ReadLine().TrimEnd();
					
				// Append readed data + [args conitnue] line
				argsText += System.Text.Encoding.GetEncoding(charset).GetString(dataStream.ToArray()) + argsContinueLine;

				// There is no more argumets, stop getting. 
				// We must check this because if length = 0 and no args returned, last line ends with {0},
				// leaves this into endless loop.
				if(argsContinueLine == ""){
					break;
				}
			}

			//--- Parse search criteria ------------------------//
			SearchGroup searchCriteria = new SearchGroup();
			try{				
				searchCriteria.Parse(new StringReader(argsText));
			}
			catch(Exception x){
				this.Socket.WriteLine(cmdTag + " NO " + x.Message);
				return;
			}
			//--------------------------------------------------//
/*
			string searchResponse = "* SEARCH";

			// No header and body text needed, can do search on internal messages info data
			if(!searchCriteria.IsHeaderNeeded() && !searchCriteria.IsBodyTextNeeded()){
				// Loop internal messages info, see what messages match
				for(int i=0;i<m_Messages.Count;i++){
					IMAP_Message messageInfo = m_Messages[i];

					// See if message matches
					if(searchCriteria.Match(i,messageInfo.MessageUID,(int)messageInfo.Size,messageInfo.Date,messageInfo.Flags,null,"")){
						// For UID search we must return message UID's
						if(uidSearch){
							searchResponse += " " + messageInfo.MessageUID.ToString();
						}
						// For normal search we must return message index numbers.
						else{
							searchResponse += " " + messageInfo.MessageNo.ToString();							
						}
					}
				}
			}
			// Can't search on iternal messages info, need to do header or body text search, call Search event
			else{
				// Call 'Search' event, get search criteria matching messages
				IMAP_eArgs_Search eArgs = m_pServer.OnSearch(this,Core.Decode_IMAP_UTF7_String(this.SelectedMailbox),new IMAP_SearchMatcher(searchCriteria));
				
				// Constuct matching messages search response
				for(int i=0;i<eArgs.MatchingMessages.Count;i++){
					IMAP_Message messageInfo = eArgs.MatchingMessages[i];

					// For UID search we must return message UID's
					if(uidSearch){
						searchResponse += " " + messageInfo.MessageUID.ToString();
					}
					// For normal search we must return message index numbers.
					else{
						// Map search returnded message to internal message number
						int no = m_Messages.IndexFromUID(messageInfo.MessageUID);
						if(no > -1){
							searchResponse += " " + no.ToString();
						}
					}
				}
			}

			searchResponse += "\r\n";
			searchResponse += cmdTag + " OK SEARCH completed in " + ((DateTime.Now.Ticks - startTime) / (decimal)10000000).ToString("f2") + " seconds\r\n";
*/
			// Just loop messages headers or full messages (depends on search type)
		//	string searchResponse = "* SEARCH";
			this.Socket.Write("* SEARCH");
			string searchResponse = "";

            IMAP_MessageItems_enum messageItems = IMAP_MessageItems_enum.None;
            if(searchCriteria.IsBodyTextNeeded()){
			    messageItems |= IMAP_MessageItems_enum.Message;
            }
            else if(searchCriteria.IsHeaderNeeded()){
                messageItems |= IMAP_MessageItems_enum.Header;
            }
			for(int i=0;i<m_pSelectedFolder.Messages.Count;i++){
				IMAP_Message msg = m_pSelectedFolder.Messages[i];
				
				//-- Get message only if matching needs it ------------------------//
				LumiSoft.Net.Mime.Mime parser = null;
				if((messageItems & IMAP_MessageItems_enum.Message) != 0 || (messageItems & IMAP_MessageItems_enum.Header) != 0){
                    // Raise event GetMessageItems, get requested message items.
                    IMAP_eArgs_MessageItems eArgs = m_pServer.OnGetMessageItems(this,msg,messageItems);
					
					// Message data is null if no such message available, just skip that message
					if(!eArgs.MessageExists){
						continue;
					}
                    try{
                        // Ensure that all requested items were provided.
                        eArgs.Validate();
                    }
                    catch(Exception x){
                        m_pServer.OnSysError(x.Message,x);
                        this.Socket.WriteLine(cmdTag + " NO Internal IMAP server component error: " + x.Message);
                        return;
                    }

                    try{
                        if(eArgs.MessageStream != null){
					        parser = LumiSoft.Net.Mime.Mime.Parse(eArgs.MessageStream);
                        }
                        else{
                            parser = LumiSoft.Net.Mime.Mime.Parse(eArgs.Header);
                        }
                    }
					// Message parsing failed, bad message. Just make new warning message.
					catch(Exception x){
						parser = LumiSoft.Net.Mime.Mime.CreateSimple(new AddressList(),new AddressList(),"[BAD MESSAGE] Bad message, message parsing failed !","NOTE: Bad message, message parsing failed !\r\n\r\n" + x.Message,"");
					}
				}
				//-----------------------------------------------------------------//

				string bodyText = "";
				if(searchCriteria.IsBodyTextNeeded()){
					bodyText = parser.BodyText;
				}

				// See if message matches to search criteria
				if(searchCriteria.Match(i,msg.UID,(int)msg.Size,msg.InternalDate,msg.Flags,parser,bodyText)){
					if(uidSearch){
						this.Socket.Write(" " + msg.UID.ToString());

				//		searchResponse += " " + msg.MessageUID.ToString();
					}
					else{
						this.Socket.Write(" " + (i + 1).ToString());

				//		searchResponse += " " + i.ToString();
					}
				}
			}
			
			searchResponse += "\r\n";
			searchResponse += cmdTag + " OK SEARCH completed in " + ((DateTime.Now.Ticks - startTime) / (decimal)10000000).ToString("f2") + " seconds\r\n";

			// Send search server response
			this.Socket.Write(searchResponse);
		}

		#endregion

		#region method Fetch

		private void Fetch(string cmdTag,string argsText,bool uidFetch)
		{
			/* Rfc 3501 6.4.5 FETCH Command
			
				Arguments:  message set
							message data item names

				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
	  
			*/
			if(!this.Authenticated){
				this.Socket.WriteLine(cmdTag + " NO Authenticate first !");
				return;
			}
			if(m_SelectedMailbox.Length == 0){
				this.Socket.WriteLine(cmdTag + " NO Select mailbox first !");
				return;
			}

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

            IMAP_MessageItems_enum messageItems = IMAP_MessageItems_enum.None;

			#region Parse parameters

			string[] args = ParseParams(argsText);		
			if(args.Length != 2){
				this.Socket.WriteLine(cmdTag + " BAD Invalid arguments");
				return;
			}

			IMAP_SequenceSet sequenceSet = new IMAP_SequenceSet();
			// Just try if it can be parsed as sequence-set
			try{	
			    if(uidFetch){
                    if(m_pSelectedFolder.Messages.Count > 0){
                        sequenceSet.Parse(args[0],m_pSelectedFolder.Messages[m_pSelectedFolder.Messages.Count - 1].UID);
                    }
				}
				else{
                    sequenceSet.Parse(args[0],m_pSelectedFolder.Messages.Count);
				}
			}
			// This isn't valid sequnce-set value
			catch{
				this.Socket.WriteLine(cmdTag + " BAD Invalid <sequnce-set> value '" + args[0] + "' Syntax: {<command-tag> FETCH <sequnce-set> (<fetch-keys>)}!");
				return;
			}
		
			// Replace macros
			string fetchItems = args[1].ToUpper();
			       fetchItems = fetchItems.Replace("ALL" ,"FLAGS INTERNALDATE RFC822.SIZE ENVELOPE");
				   fetchItems = fetchItems.Replace("FAST","FLAGS INTERNALDATE RFC822.SIZE");
				   fetchItems = fetchItems.Replace("FULL","FLAGS INTERNALDATE RFC822.SIZE ENVELOPE BODY");
		
			// If UID FETCH and no UID, we must implicity add it, it's required 
			if(uidFetch && fetchItems.ToUpper().IndexOf("UID") == -1){
				fetchItems += " UID";
			}

			// Start parm parsing from left to end in while loop while params parsed or bad param found
			ArrayList fetchFlags = new ArrayList();
			StringReader argsReader = new StringReader(fetchItems.Trim());
			while(argsReader.Available > 0){
				argsReader.ReadToFirstChar();
				
				#region BODYSTRUCTURE

				// BODYSTRUCTURE
				if(argsReader.StartsWith("BODYSTRUCTURE")){
					argsReader.ReadSpecifiedLength("BODYSTRUCTURE".Length);

					fetchFlags.Add(new object[]{"BODYSTRUCTURE"});
                    messageItems |= IMAP_MessageItems_enum.BodyStructure;
				}

				#endregion

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

				// BODY, BODY[<section>]<<partial>>, BODY.PEEK[<section>]<<partial>>
				else if(argsReader.StartsWith("BODY")){
					// Remove BODY
					argsReader.ReadSpecifiedLength("BODY".Length);

					bool peek = false;
					// BODY.PEEK
					if(argsReader.StartsWith(".PEEK")){
						// Remove .PEEK
						argsReader.ReadSpecifiedLength(".PEEK".Length);

						peek = true;
					}

					// [<section>]<<partial>>
					if(argsReader.StartsWith("[")){
                        // Read value between []
                        string section = "";
                        try{
                            section = argsReader.ReadParenthesized();                            
                        }
                        catch{
                            this.Socket.WriteLine(cmdTag + " BAD Invalid BODY[], closing ] parenthesize is missing !");
                            return;
                        }

						string originalSectionValue = section;
						string mimePartsSpecifier = "";
						string sectionType = "";
						string sectionArgs = "";
						/* Validate <section>
							 Section can be:
								""                                                    - entire message								
								[MimePartsSepcifier.]HEADER                           - message header
								[MimePartsSepcifier.]HEADER.FIELDS (headerFields)     - message header fields
								[MimePartsSepcifier.]HEADER.FIELDS.NOT (headerFields) - message header fields except requested
								[MimePartsSepcifier.]TEXT                             - message text
								[MimePartsSepcifier.]MIME                             - same as header, different response
						*/
						if(section.Length > 0){
							string[] section_args = section.Split(new char[]{' '},2);
							section = section_args[0];
							if(section_args.Length == 2){
								sectionArgs = section_args[1];
							}

							if(section.EndsWith("HEADER")){
								// Remove HEADER from end
								section = section.Substring(0,section.Length - "HEADER".Length);

								sectionType = "HEADER";
								messageItems |= IMAP_MessageItems_enum.Header;
							}
							else if(section.EndsWith("HEADER.FIELDS")){
								// Remove HEADER.FIELDS from end
								section = section.Substring(0,section.Length - "HEADER.FIELDS".Length);

								sectionType = "HEADER.FIELDS";
								messageItems |= IMAP_MessageItems_enum.Header;
							}
							else if(section.EndsWith("HEADER.FIELDS.NOT")){
								// Remove HEADER.FIELDS.NOT from end
								section = section.Substring(0,section.Length - "HEADER.FIELDS.NOT".Length);

								sectionType = "HEADER.FIELDS.NOT";
								messageItems |= IMAP_MessageItems_enum.Header;
							}
							else if(section.EndsWith("TEXT")){
								// Remove TEXT from end
								section = section.Substring(0,section.Length - "TEXT".Length);

								sectionType = "TEXT";
								messageItems |= IMAP_MessageItems_enum.Message;
							}							
							else if(section.EndsWith("MIME")){
								// Remove MIME from end
								section = section.Substring(0,section.Length - "MIME".Length);

								sectionType = "MIME";
								messageItems = IMAP_MessageItems_enum.Header;
							}

							// Remove last ., if there is any
							if(section.EndsWith(".")){
								section = section.Substring(0,section.Length - 1);
							}

							// MimePartsSepcifier is specified, validate it. It can contain numbers only.
							if(section.Length > 0){
								// Now we certainly need full message, because nested mime parts wanted
								messageItems |= IMAP_MessageItems_enum.Message;
								
								string[] sectionParts = section.Split('.');
								foreach(string sectionPart in sectionParts){
									if(!Core.IsNumber(sectionPart)){
										this.Socket.WriteLine(cmdTag + " BAD Invalid BODY[<section>] argument. Invalid <section>: " + section);
										return;
									}
								}

								mimePartsSpecifier = section;
							}
						}
						else{
							messageItems |= IMAP_MessageItems_enum.Message;
						}

						long startPosition = -1;
						long length = -1;
						// See if partial fetch
						if(argsReader.StartsWith("<")){
                            /* <partial> syntax:
									startPosition[.endPosition]							  
							*/

                            // Read partial value between <>
                            string partial = "";
                            try{
                                partial = argsReader.ReadParenthesized();                            
                            }
                            catch{
                                this.Socket.WriteLine(cmdTag + " BAD Invalid BODY[]<start[.length]>, closing > parenthesize is missing !");
                                return;
                            }

							string[] start_length = partial.Split('.');

							// Validate <partial>
							if(start_length.Length == 0 || start_length.Length > 2 || !Core.IsNumber(start_length[0]) || (start_length.Length == 2 && !Core.IsNumber(start_length[1]))){
								this.Socket.WriteLine(cmdTag + " BAD Invalid BODY[]<partial> argument. Invalid <partial>: " + partial);
								return;
							}
					
							startPosition = Convert.ToInt64(start_length[0]);
							if(start_length.Length == 2){
								length = Convert.ToInt64(start_length[1]);
							}
						}
                        
						// object[] structure for BODY[]
						//	fetchFlagName
						//	isPeek
						//	mimePartsSpecifier
						//  originalSectionValue
						//	sectionType
						//	sectionArgs
						//	startPosition
						//	length
						fetchFlags.Add(new object[]{"BODY[]",peek,mimePartsSpecifier,originalSectionValue,sectionType,sectionArgs,startPosition,length});
					}
					// BODY
					else{						
						fetchFlags.Add(new object[]{"BODY"});
                        messageItems |= IMAP_MessageItems_enum.BodyStructure;
					}
				}

				#endregion

				#region ENVELOPE

				// ENVELOPE
				else if(argsReader.StartsWith("ENVELOPE")){
					argsReader.ReadSpecifiedLength("ENVELOPE".Length);

					fetchFlags.Add(new object[]{"ENVELOPE"});
                    messageItems |= IMAP_MessageItems_enum.Envelope;
				}

				#endregion

				#region FLAGS

				// FLAGS
				//	The flags that are set for this message.
				else if(argsReader.StartsWith("FLAGS")){
					argsReader.ReadSpecifiedLength("FLAGS".Length);

					fetchFlags.Add(new object[]{"FLAGS"});
				}

				#endregion

				#region INTERNALDATE

				// INTERNALDATE
				else if(argsReader.StartsWith("INTERNALDATE")){
					argsReader.ReadSpecifiedLength("INTERNALDATE".Length);

					fetchFlags.Add(new object[]{"INTERNALDATE"});
				}

				#endregion

				#region RFC822.HEADER

				// RFC822.HEADER
				else if(argsReader.StartsWith("RFC822.HEADER")){
					argsReader.ReadSpecifiedLength("RFC822.HEADER".Length);

					fetchFlags.Add(new object[]{"RFC822.HEADER"});
                    messageItems |= IMAP_MessageItems_enum.Header;
				}

				#endregion

				#region RFC822.SIZE

				// RFC822.SIZE
				//	The [RFC-2822] size of the message.
				else if(argsReader.StartsWith("RFC822.SIZE")){
					argsReader.ReadSpecifiedLength("RFC822.SIZE".Length);

					fetchFlags.Add(new object[]{"RFC822.SIZE"});
				}

				#endregion

				#region RFC822.TEXT

				// RFC822.TEXT
				else if(argsReader.StartsWith("RFC822.TEXT")){
					argsReader.ReadSpecifiedLength("RFC822.TEXT".Length);

					fetchFlags.Add(new object[]{"RFC822.TEXT"});
                    messageItems |= IMAP_MessageItems_enum.Message;
				}

				#endregion
	
				#region RFC822
				
				// RFC822 NOTE: RFC822 must be below RFC822.xxx or is parsed wrong !
				else if(argsReader.StartsWith("RFC822")){
					argsReader.ReadSpecifiedLength("RFC822".Length);

					fetchFlags.Add(new object[]{"RFC822"});
                    messageItems |= IMAP_MessageItems_enum.Message;
				}

				#endregion

				#region UID

				// UID
				//	The unique identifier for the message.
				else if(argsReader.StartsWith("UID")){
					argsReader.ReadSpecifiedLength("UID".Length);

					fetchFlags.Add(new object[]{"UID"});
				}

				#endregion

				// This must be unknown fetch flag
				else{
					this.Socket.WriteLine(cmdTag + " BAD Invalid fetch-items argument. Unkown part starts from: " + argsReader.SourceString);
					return;
				}
			}

			#endregion

			// ToDo: ??? But non of the servers do it ?
			// The server should respond with a tagged BAD response to a command that uses a message
            // sequence number greater than the number of messages in the selected mailbox.  This
            // includes "*" if the selected mailbox is empty.
		//	if(m_Messages.Count == 0 || ){
		//		SendData(cmdTag + " BAD Sequence number greater than the number of messages in the selected mailbox !\r\n");
		//		return;
		//	}

            // Create buffered writer, so we make less network calls.
            SocketBufferedWriter bufferedWriter = new SocketBufferedWriter(this.Socket);
            
			for(int i=0;i<m_pSelectedFolder.Messages.Count;i++){
				IMAP_Message msg = m_pSelectedFolder.Messages[i];

				// For UID FETCH we must compare UIDs and for normal FETCH message numbers. 
				bool sequenceSetContains = false;
				if(uidFetch){
					sequenceSetContains = sequenceSet.Contains(msg.UID);
				}
				else{
					sequenceSetContains = sequenceSet.Contains(i + 1);
				}

				if(sequenceSetContains){            
                    IMAP_eArgs_MessageItems eArgs = null;
                    // Get message items only if they are needed.
                    if(messageItems != IMAP_MessageItems_enum.None){
                        // Raise event GetMessageItems to get all neccesary message itmes
                        eArgs = m_pServer.OnGetMessageItems(this,msg,messageItems);

                        // Message doesn't exist any more, notify email client.
                        if(!eArgs.MessageExists){
                            bufferedWriter.Write("* " + msg.SequenceNo + " EXPUNGE");
                            m_pSelectedFolder.Messages.Remove(msg);
                            i--;
                            continue;
                        }
                        try{
                            // Ensure that all requested items were provided.
                            eArgs.Validate();
                        }
                        catch(Exception x){
                            m_pServer.OnSysError(x.Message,x);
                            this.Socket.WriteLine(cmdTag + " NO Internal IMAP server component error: " + x.Message);
                            return;
                        }
                    }
                                                          
                    // Write fetch start data "* msgNo FETCH ("
                    bufferedWriter.Write("* " + (i + 1) + " FETCH (");
					
					IMAP_MessageFlags msgFlagsOr = msg.Flags;
					// Construct reply here, based on requested fetch items
					int nCount = 0;
					foreach(object[] fetchFlag in fetchFlags){
						string fetchFlagName = (string)fetchFlag[0];

						#region BODY

						// BODY
						if(fetchFlagName == "BODY"){
							// Sets \seen flag
							msg.SetFlags(msg.Flags | IMAP_MessageFlags.Seen);

							// BODY ()
                            bufferedWriter.Write("BODY " + eArgs.BodyStructure);
						}

						#endregion

						#region BODY[], BODY.PEEK[]

						// BODY[<section>]<<partial>>, BODY.PEEK[<section>]<<partial>>
						else if(fetchFlagName == "BODY[]"){
                            // Force to write all buffered data.
                            bufferedWriter.Flush();

							// object[] structure for BODY[]
							//	fetchFlagName
							//	isPeek
							//	mimePartsSpecifier
							//  originalSectionValue
							//	sectionType
							//	sectionArgs
							//	startPosition
							//	length
							bool   isPeek               = (bool)fetchFlag[1];
							string mimePartsSpecifier   = (string)fetchFlag[2];
							string originalSectionValue = (string)fetchFlag[3];
							string sectionType          = (string)fetchFlag[4];
							string sectionArgs          = (string)fetchFlag[5];
							long   startPosition        = (long)fetchFlag[6];
							long   length               = (long)fetchFlag[7];

							// Difference between BODY[] and BODY.PEEK[] is that .PEEK won't set seen flag
							if(!isPeek){
								// Sets \seen flag
								msg.SetFlags(msg.Flags | IMAP_MessageFlags.Seen);
							}
							
							/* Section value:
								""                - entire message								
								HEADER            - message header
								HEADER.FIELDS     - message header fields
								HEADER.FIELDS.NOT - message header fields except requested
								TEXT              - message text
								MIME              - same as header, different response
							*/
							Stream dataStream = null;
							if(sectionType == "" && mimePartsSpecifier == ""){
								dataStream = eArgs.MessageStream;
							}
							else{
                                LumiSoft.Net.Mime.Mime parser = null;
                                try{
                                    if(eArgs.MessageStream == null){
                                        parser = LumiSoft.Net.Mime.Mime.Parse(eArgs.Header);
                                    }
                                    else{
                                        parser = LumiSoft.Net.Mime.Mime.Parse(eArgs.MessageStream);
                                    }
                                }                                
                                // Invalid message, parsing failed
                                catch{
                                    parser = LumiSoft.Net.Mime.Mime.CreateSimple(new AddressList(),new AddressList(),"BAD Message","This is BAD message, mail server failed to parse it !","");
                                }
								MimeEntity currentEntity = parser.MainEntity;
								// Specific mime entity requested, get it
								if(mimePartsSpecifier != ""){
									currentEntity = FetchHelper.GetMimeEntity(parser,mimePartsSpecifier);
								}

								if(currentEntity != null){
									if(sectionType == "HEADER"){
										dataStream = new MemoryStream(FetchHelper.GetMimeEntityHeader(currentEntity));
									}
									else if(sectionType == "HEADER.FIELDS"){
										dataStream = new MemoryStream(FetchHelper.ParseHeaderFields(sectionArgs,currentEntity));
									}
									else if(sectionType == "HEADER.FIELDS.NOT"){
										dataStream = new MemoryStream(FetchHelper.ParseHeaderFieldsNot(sectionArgs,currentEntity));
									}
									else if(sectionType == "TEXT"){
										try{
											dataStream = new MemoryStream(currentEntity.DataEncoded);
										}
										catch{ // This probably multipart entity, data isn't available
										}
									}
									else if(sectionType == "MIME"){
										dataStream = new MemoryStream(FetchHelper.GetMimeEntityHeader(currentEntity));
									}
									else if(sectionType == ""){
										try{
											dataStream = new MemoryStream(currentEntity.DataEncoded);
										}
										catch{ // This probably multipart entity, data isn't available
										}
									}
								}
							}
							
							// Partial fetch. Reports <origin position> in fetch reply.
							if(startPosition > -1){
								if(dataStream == null){
                                    this.Socket.Write("BODY[" + originalSectionValue + "]<" + startPosition.ToString() + "> \"\"\r\n");
								}
								else{
									long lengthToSend = length;
									if(lengthToSend == -1){
										lengthToSend = (dataStream.Length - dataStream.Position) - startPosition;
									}
									if((lengthToSend + startPosition) > (dataStream.Length - dataStream.Position)){
										lengthToSend = (dataStream.Length - dataStream.Position) - startPosition;
									}

									if(startPosition >= (dataStream.Length - dataStream.Position)){
                                        this.Socket.Write("BODY[" + originalSectionValue + "]<" + startPosition.ToString() + "> \"\"\r\n");
									}
									else{	
						                this.Socket.Write("BODY[" + originalSectionValue + "]<" + startPosition.ToString() + "> {" + lengthToSend + "}\r\n");										
                                        dataStream.Position += startPosition;
										this.Socket.Write(dataStream,lengthToSend);
									}
								}
							}
							// Normal fetch
							else{
								if(dataStream == null){
                                    this.Socket.Write("BODY[" + originalSectionValue + "] \"\"\r\n");
								}
								else{
                                    this.Socket.Write("BODY[" + originalSectionValue + "] {" + (dataStream.Length - dataStream.Position) + "}\r\n");
									this.Socket.Write(dataStream);
								}
							}
						}

						#endregion

						#region BODYSTRUCTURE

						// BODYSTRUCTURE
						else if(fetchFlagName == "BODYSTRUCTURE"){
                            bufferedWriter.Write("BODYSTRUCTURE " + eArgs.BodyStructure);
						}

						#endregion

						#region ENVELOPE

						// ENVELOPE
						else if(fetchFlagName == "ENVELOPE"){
                            bufferedWriter.Write("ENVELOPE " + eArgs.Envelope);
						}

						#endregion

						#region FLAGS

						// FLAGS
						else if(fetchFlagName == "FLAGS"){
                            bufferedWriter.Write("FLAGS (" + msg.FlagsString + ")");
						}

						#endregion

						#region INTERNALDATE

						// INTERNALDATE
						else if(fetchFlagName == "INTERNALDATE"){
							// INTERNALDATE "date"
                            bufferedWriter.Write("INTERNALDATE \"" + IMAP_Utils.DateTimeToString(msg.InternalDate) + "\"");
						}

						#endregion

						#region RFC822

						// RFC822
						else if(fetchFlagName == "RFC822"){
                            // Force to write all buffered data.
                            bufferedWriter.Flush();

							// Sets \seen flag
							msg.SetFlags(msg.Flags | IMAP_MessageFlags.Seen);

							// RFC822 {size}
							// msg data
                            this.Socket.Write("RFC822 {" + eArgs.MessageSize.ToString() + "}\r\n");
							this.Socket.Write(eArgs.MessageStream);
						}

						#endregion

						#region RFC822.HEADER

						// RFC822.HEADER
						else if(fetchFlagName == "RFC822.HEADER"){
                            // Force to write all buffered data.
                            bufferedWriter.Flush();

							// RFC822.HEADER {size}
							// msg header data
						    this.Socket.Write("RFC822.HEADER {" + eArgs.Header.Length + "}\r\n");
							this.Socket.Write(eArgs.Header);	
						}

						#endregion

						#region RFC822.SIZE
						
						// RFC822.SIZE
						else if(fetchFlagName == "RFC822.SIZE"){
							// RFC822.SIZE size
                            bufferedWriter.Write("RFC822.SIZE " + msg.Size);
						}

						#endregion
						
						#region RFC822.TEXT

						// RFC822.TEXT
						else if(fetchFlagName == "RFC822.TEXT"){
                            // Force to write all buffered data.
                            bufferedWriter.Flush();

							// Sets \seen flag
							msg.SetFlags(msg.Flags | IMAP_MessageFlags.Seen);
							
							//--- Find body text entity ------------------------------------//
                            LumiSoft.Net.Mime.Mime parser = LumiSoft.Net.Mime.Mime.Parse(eArgs.MessageStream);
							MimeEntity bodyTextEntity = null;
							if(parser.MainEntity.ContentType == MediaType_enum.NotSpecified){
								if(parser.MainEntity.DataEncoded != null){
									bodyTextEntity = parser.MainEntity;
								}
							}
							else{
								MimeEntity[] entities = parser.MimeEntities;
								foreach(MimeEntity entity in entities){
									if(entity.ContentType == MediaType_enum.Text_plain){
										bodyTextEntity = entity;
										break;
									}
								}
							}
							//----------------------------------------------------------------//

							// RFC822.TEXT {size}
							// msg text	
							byte[] data = null;
							if(bodyTextEntity != null){
								data = bodyTextEntity.DataEncoded;
							}
							else{
								data = System.Text.Encoding.ASCII.GetBytes("");
							}
									
                            this.Socket.Write("RFC822.TEXT {" + data.Length + "}\r\n");
							this.Socket.Write(data);
						}

						#endregion

						#region UID

						// UID
						else if(fetchFlagName == "UID"){
                            bufferedWriter.Write("UID " + msg.UID);
						}

						#endregion

						nCount++;

						// Write fetch item separator data " "
						// We don't write it for last item
						if(nCount < fetchFlags.Count){
                            bufferedWriter.Write(" ");
						}                       
					}
                                        
					// Write fetch end data ")"
                    bufferedWriter.Write(")\r\n");

                    // Free event args, close message stream, ... .
                    if(eArgs != null){
                        eArgs.Dispose();
                    }

					// Set message flags here if required or changed
					if(((int)IMAP_MessageFlags.Recent & (int)msg.Flags) != 0 || msgFlagsOr != msg.Flags){
						msg.SetFlags(msg.Flags & ~IMAP_MessageFlags.Recent);

						m_pServer.OnStoreMessageFlags(this,msg);
					}
				}               			
			}

            // Force to write all buffered data.
            bufferedWriter.Flush();

			this.Socket.WriteLine(cmdTag + " OK FETCH completed in " + ((DateTime.Now.Ticks - startTime) / (decimal)10000000).ToString("f2") + " seconds");
		}

		#endregion

		#region method Store

		private void Store(string cmdTag,string argsText,bool uidStore)
		{
			/* Rfc 3501 6.4.6 STORE Command
				
				Arguments:  message 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
			   
			*/
			if(!this.Authenticated){
				this.Socket.WriteLine(cmdTag + " NO Authenticate first !");
				return;
			}
			if(m_SelectedMailbox.Length == 0){
				this.Socket.WriteLine(cmdTag + " NO Select mailbox first !");
				return;
			}
			if(m_pSelectedFolder.ReadOnly){
				this.Socket.WriteLine(cmdTag + " NO Mailbox is read-only");
				return;
			}

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

			string[] args = ParseParams(argsText);
			if(args.Length != 3){
				this.Socket.WriteLine(cmdTag + " BAD STORE invalid arguments. Syntax: {<command-tag> STORE <sequnce-set> <data-item> (<message-flags>)}");
				return;
			}

			IMAP_SequenceSet sequenceSet = new IMAP_SequenceSet();
			// Just try if it can be parsed as sequence-set
			try{				
				if(uidStore){
                    if(m_pSelectedFolder.Messages.Count > 0){
                        sequenceSet.Parse(args[0],m_pSelectedFolder.Messages[m_pSelectedFolder.Messages.Count - 1].UID);
                    }
				}
				else{
                    sequenceSet.Parse(args[0],m_pSelectedFolder.Messages.Count);
				}
			}
			// This isn't vaild sequnce-set value
			catch{
				this.Socket.WriteLine(cmdTag + "BAD Invalid <sequnce-set> value '" + args[0] + "' Syntax: {<command-tag> STORE <sequnce-set> <data-item> (<message-flags>)}!");
				return;
			}

			//--- Parse Flags behaviour ---------------//
			string flagsAction = "";
			bool   silent      = false;
			string flagsType = args[1].ToUpper();
			switch(flagsType)
			{
				case "FLAGS":
					flagsAction = "REPLACE";
					break;

				case "FLAGS.SILENT":
					flagsAction = "REPLACE";
					silent = true;
					break;

				case "+FLAGS":
					flagsAction = "ADD";
					break;

				case "+FLAGS.SILENT":
					flagsAction = "ADD";
					silent = true;
					break;

				case "-FLAGS":
					flagsAction = "REMOVE";
					break;

				case "-FLAGS.SILENT":
					flagsAction = "REMOVE";	
					silent = true;
					break;

				default:
					this.Socket.WriteLine(cmdTag + " BAD arguments invalid");
					return;
			}
			//-------------------------------------------//

			//--- Parse flags, see if valid ----------------
			string flags = args[2].ToUpper();
			if(flags.Replace("\\ANSWERED","").Replace("\\FLAGGED","").Replace("\\DELETED","").Replace("\\SEEN","").Replace("\\DRAFT","").Trim().Length > 0){
				this.Socket.WriteLine(cmdTag + " BAD arguments invalid");
				return;
			}

			IMAP_MessageFlags mFlags = IMAP_Utils.ParseMessageFlags(flags);

			// Call OnStoreMessageFlags for each message in sequence set
			// Calulate new flags(using old message flags + new flags) for message 
			// and request to store all flags to message, don't specify if add, remove or replace falgs.
			
			for(int i=0;i<m_pSelectedFolder.Messages.Count;i++){
				IMAP_Message msg = m_pSelectedFolder.Messages[i];

				// For UID STORE we must compare UIDs and for normal STORE message numbers. 
				bool sequenceSetContains = false;
				if(uidStore){
					sequenceSetContains = sequenceSet.Contains(msg.UID);
				}
				else{
					sequenceSetContains = sequenceSet.Contains(i + 1);
				}
    
				if(sequenceSetContains){					
					// Calculate new flags and set to msg
					switch(flagsAction)
					{
						case "REPLACE":
							msg.SetFlags(mFlags);
							break;

						case "ADD":
							msg.SetFlags(msg.Flags | mFlags);
							break;

						case "REMOVE":
							msg.SetFlags(msg.Flags & ~mFlags);
							break;
					}

					// ToDo: see if flags changed, if not don't call OnStoreMessageFlags

					string errorText = m_pServer.OnStoreMessageFlags(this,msg);
					if(errorText == null){
						if(!silent){ // Silent doesn't reply untagged lines
							if(!uidStore){
								this.Socket.WriteLine("* " + (i + 1) + " FETCH FLAGS (" + msg.FlagsString + ")");
							}
							// Called from UID command, need to add UID response
							else{
								this.Socket.WriteLine("* " + (i + 1) + " FETCH (FLAGS (" + msg.FlagsString + ") UID " + msg.UID + "))");
							}
						}
					}
					else{
						this.Socket.WriteLine(cmdTag + " NO " + errorText);
						return;
					}
				}
			}
                         
            this.Socket.WriteLine(cmdTag + " OK STORE completed in " + ((DateTime.Now.Ticks - startTime) / (decimal)10000000).ToString("f2") + " seconds");
		}

		#endregion

		#region method Copy

		private void Copy(string cmdTag,string argsText,bool uidCopy)
		{
			/* RFC 3501 6.4.7 COPY Command
			
				Arguments:  message 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 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
				
				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.Authenticated){
				this.Socket.WriteLine(cmdTag + " NO Authenticate first !");
				return;
			}
			if(m_SelectedMailbox.Length == 0){
				this.Socket.WriteLine(cmdTag + " NO Select mailbox first !");
				return;
			}

			string[] args = ParseParams(argsText);
			if(args.Length != 2){
				this.Socket.WriteLine(cmdTag + " BAD Invalid arguments");
				return;
			}

			IMAP_SequenceSet sequenceSet = new IMAP_SequenceSet();
			// Just try if it can be parsed as sequence-set
			try{				
				if(uidCopy){
                    if(m_pSelectedFolder.Messages.Count > 0){
                        sequenceSet.Parse(args[0],m_pSelectedFolder.Messages[m_pSelectedFolder.Messages.Count - 1].UID);
                    }
				}
				else{
                    sequenceSet.Parse(args[0],m_pSelectedFolder.Messages.Count);
				}
			}
			// This isn't vaild sequnce-set value
			catch{
				this.Socket.WriteLine(cmdTag + "BAD Invalid <sequnce-set> value '" + args[0] + "' Syntax: {<command-tag> COPY <sequnce-set> \"<mailbox-name>\"}!");
				return;
			}

			string errorText = "";
			for(int i=0;i<m_pSelectedFolder.Messages.Count;i++){
				IMAP_Message msg = m_pSelectedFolder.Messages[i];

				// For UID COPY we must compare UIDs and for normal COPY message numbers. 
				bool sequenceSetContains = false;
				if(uidCopy){
					sequenceSetContains = sequenceSet.Contains(msg.UID);
				}
				else{
					sequenceSetContains = sequenceSet.Contains(i + 1);
				}

				if(sequenceSetContains){
					errorText = m_pServer.OnCopyMessage(this,msg,Core.Decode_IMAP_UTF7_String(args[1]));
					if(errorText != null){
						break; // Errors return error text, don't try to copy other messages
					}
				}
			}

			if(errorText == null){
				this.Socket.WriteLine(cmdTag + " OK COPY completed");
			}
			else{
				this.Socket.WriteLine(cmdTag + " NO " + errorText);
			}
		}

		#endregion

		#region method Uid

		private void Uid(string cmdTag,string argsText)
		{
			/* 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 message set argument are unique identifiers instead of message
				sequence numbers.

				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 the message sequence number set 1:100 and the
				UID set 443:557.

				Message set ranges are permitted; however, there is no guarantee
				that unique identifiers be contiguous.  A non-existent unique
				identifier within a message set range is ignored without any error
				message generated.

				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.
				
				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 UID FETCH completed
			*/
			if(!this.Authenticated){
				this.Socket.WriteLine(cmdTag + " NO Authenticate first !");
				return;
			}
			if(m_SelectedMailbox.Length == 0){
				this.Socket.WriteLine(cmdTag + " NO Select mailbox first !");
				return;
			}

			string[] args = ParseParams(argsText);
			if(args.Length < 2){ // We must have at least command and message-set or cmd args
				this.Socket.WriteLine(cmdTag + " BAD Invalid arguments");
				return;
			}

			// Get commands args text, we just remove COMMAND
			string cmdArgs = Core.GetArgsText(argsText,args[0]);

			// See if valid command specified with UID command
			switch(args[0].ToUpper())
			{
				case "COPY":
					Copy(cmdTag,cmdArgs,true);
					break;

				case "FETCH":
					Fetch(cmdTag,cmdArgs,true);
					break;

				case "STORE":
					Store(cmdTag,cmdArgs,true);
					break;

				case "SEARCH":
					Search(cmdTag,cmdArgs,true);
					break;

				default:
					this.Socket.WriteLine(cmdTag + " BAD Invalid arguments");
					return;
			}
		}

		#endregion

		//--- End of Selected State


		//--- Any State ------

		#region method Capability

		private void Capability(string cmdTag)
		{
			/* RFC 3501 6.1.1
			
				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.
				
				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
			*/

			string reply = "* CAPABILITY IMAP4rev1";
            if((m_pServer.SupportedAuthentications & SaslAuthTypes.Digest_md5) != 0){
                reply += " AUTH=DIGEST-MD5";
            }
            if((m_pServer.SupportedAuthentications & SaslAuthTypes.Cram_md5) != 0){
                reply += " AUTH=CRAM-MD5";
            }
            if(!this.Socket.SSL && this.BindInfo.SSL_Certificate != null){
                reply += " STARTTLS";
            }
            reply += " NAMESPACE ACL QUOTA\r\n";
			reply += cmdTag + " OK CAPABILITY completed\r\n";

			this.Socket.Write(reply);
		}

		#endregion

		#region method Noop

		private void Noop(string cmdTag)
		{
			/* 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.  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
			*/

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

			// If there is selected mailbox, see if messages status has changed
			if(m_SelectedMailbox.Length > 0){
				// Get status
                IMAP_SelectedFolder        folderInfo = new IMAP_SelectedFolder(m_SelectedMailbox);
				IMAP_eArgs_GetMessagesInfo eArgs      = m_pServer.OnGetMessagesInfo(this,folderInfo);
                                
                // Join new info with exisiting
				string statusResponse = m_pSelectedFolder.Update(folderInfo);
                
				this.Socket.WriteLine(statusResponse + cmdTag + " OK NOOP Completed in " + ((DateTime.Now.Ticks - startTime) / (decimal)10000000).ToString("f2") + " seconds\r\n");
			}
            else{
			    this.Socket.WriteLine(cmdTag + " OK NOOP Completed in " + ((DateTime.Now.Ticks - startTime) / (decimal)10000000).ToString("f2") + " seconds\r\n");
            }
		}

		#endregion

		#region method LogOut

		private void LogOut(string cmdTag)
		{
			/* RFC 3501 6.1.3
			
				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)
			*/

			string reply  = "* BYE IMAP4rev1 Server logging out\r\n";
			       reply += cmdTag + " OK LOGOUT completed\r\n";

			this.Socket.Write(reply);
		}

		#endregion

		//--- End of Any State
	

		#region method ParseParams

		private string[] ParseParams(string argsText)
		{
			ArrayList p = new ArrayList();

			try{
				while(argsText.Length > 0){
					// Parameter is between ""
					if(argsText.StartsWith("\"")){
						p.Add(argsText.Substring(1,argsText.IndexOf("\"",1) - 1));
						// Remove parsed param
						argsText = argsText.Substring(argsText.IndexOf("\"",1) + 1).Trim();			
					}
					else{
						// Parameter is between ()
						if(argsText.StartsWith("(")){
							p.Add(argsText.Substring(1,argsText.LastIndexOf(")") - 1));
							// Remove parsed param
							argsText = argsText.Substring(argsText.LastIndexOf(")") + 1).Trim();
						}
						else{						
							// Read parameter till " ", probably there is more params
							// Note: If there is ({ before SP, cosider that it's last parameter.
							//       For example body[header.fields (from to)]
							if(argsText.IndexOf(" ") > -1 && argsText.IndexOfAny(new char[]{'(','['},0,argsText.IndexOf(" ")) == -1){
								p.Add(argsText.Substring(0,argsText.IndexOf(" ")));
								// Remove parsed param
								argsText = argsText.Substring(argsText.IndexOf(" ") + 1).Trim();
							}
							// This is last param
							else{
								p.Add(argsText);
								argsText = "";
							}
						}
					}
				}
			}
			catch{
			}

			string[] retVal = new string[p.Count];
			p.CopyTo(retVal);

			return retVal;
		}

		#endregion


		#region Properties Implementation
		
		/// <summary>
		/// Gets selected mailbox.
		/// </summary>
		public string SelectedMailbox
		{
			get{ return m_SelectedMailbox; }
		}

		#endregion

	}
}

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

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

License

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

Share

About the Author

Ivar Lumi

Estonia Estonia
No Biography provided

| Advertise | Privacy | Terms of Use | Mobile
Web03 | 2.8.141223.1 | Last Updated 11 Jun 2007
Article Copyright 2007 by Ivar Lumi
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid