Example #1
0
        /// <summary>
        /// Default constructor.
        /// </summary>
        /// <param name="mimeEntry"></param>
        /// <param name="mime"></param>
        public MimeEntry(byte[] mimeEntry, MimeParser mime)
        {
            MemoryStream entryStrm = new MemoryStream(mimeEntry);

            m_Headers     = MimeParser.ParseHeaders(entryStrm);
            m_ContentType = mime.ParseContentType(m_Headers);

            m_Entries = new ArrayList();

            // != multipart content
            if (m_ContentType.ToLower().IndexOf("multipart") == -1)
            {
                m_CharSet         = ParseCharSet(m_Headers);
                m_ContentEncoding = ParseEncoding(m_Headers);

                m_Data = new byte[entryStrm.Length - entryStrm.Position];
                entryStrm.Read(m_Data, 0, m_Data.Length);
            }
            // multipart content, get nested entries
            else
            {
                long   s          = (int)entryStrm.Position;
                string boundaryID = MimeParser.ParseHeaderFiledSubField("Content-Type:", "boundary", m_Headers);
                m_Entries = mime.ParseEntries(entryStrm, (int)entryStrm.Position, boundaryID);

                entryStrm.Position = s;
                m_Data             = new byte[entryStrm.Length - s];
                entryStrm.Read(m_Data, 0, m_Data.Length);
            }
        }
Example #2
0
		/// <summary>
		/// Default constructor.
		/// </summary>
		/// <param name="mimeEntry"></param>
		/// <param name="mime"></param>
		public MimeEntry(byte[] mimeEntry,MimeParser mime)
		{
			MemoryStream entryStrm = new MemoryStream(mimeEntry);

			m_Headers     = MimeParser.ParseHeaders(entryStrm);
			m_ContentType = mime.ParseContentType(m_Headers);

			m_Entries = new ArrayList();

			// != multipart content
			if(m_ContentType.ToLower().IndexOf("multipart") == -1){
				m_CharSet         = ParseCharSet(m_Headers);
				m_ContentEncoding = ParseEncoding(m_Headers);

				m_Data = new byte[entryStrm.Length - entryStrm.Position];
				entryStrm.Read(m_Data,0,m_Data.Length);
			}
			// multipart content, get nested entries
			else{
				long s = (int)entryStrm.Position;
				string boundaryID = MimeParser.ParseHeaderFiledSubField("Content-Type:","boundary",m_Headers);
				m_Entries = mime.ParseEntries(entryStrm,(int)entryStrm.Position,boundaryID);

				entryStrm.Position = s;
				m_Data = new byte[entryStrm.Length - s];
				entryStrm.Read(m_Data,0,m_Data.Length);
			}
		}
Example #3
0
        /// <summary>
        /// Parse charset.
        /// </summary>
        /// <param name="headers"></param>
        /// <returns></returns>
        private string ParseCharSet(string headers)
        {
            string charset = MimeParser.ParseHeaderFiledSubField("Content-Type:", "charset", headers);

            // charset ends with ; remove it. Is it right place to do or can it be done MimeParser.ParseHeaderFiledSubField
            if (charset.EndsWith(";"))
            {
                charset = charset.Substring(0, charset.Length - 1);
            }
            if (charset.Length > 0)
            {
                try{
                    Encoding.GetEncoding(charset);

                    return(charset);
                }
                catch {
                    return("ascii");
                }
            }
            // If no charset, consider it as ascii
            else
            {
                return("ascii");
            }
        }
Example #4
0
        /// <summary>
        /// Parse encoding. quoted-printable,7bit,8bit,base64 is supported.
        /// </summary>
        /// <param name="headers"></param>
        /// <returns></returns>
        private string ParseEncoding(string headers)
        {
            string encoding = MimeParser.ParseHeaderField("CONTENT-TRANSFER-ENCODING:", headers);

            if (encoding.Length > 0)
            {
                return(encoding);
            }
            // If no encoding, consider it as ascii
            else
            {
                return("7bit");
            }
        }
Example #5
0
        private Disposition ParseContentDisposition(string headers)
        {
            string disposition = MimeParser.ParseHeaderField("CONTENT-DISPOSITION:", headers);

            if (disposition.ToUpper().IndexOf("ATTACHMENT") > -1)
            {
                return(Disposition.Attachment);
            }

            if (disposition.ToUpper().IndexOf("INLINE") > -1)
            {
                return(Disposition.Inline);
            }

            return(Disposition.Unknown);
        }
		/// <summary>
		/// Checks if message matches for specified search key.
		/// </summary>
		/// <param name="searchKey"></param>
		/// <param name="searchKeyValue"></param>
		/// <param name="messageInfo"></param>
		/// <param name="msg"></param>
		/// <returns></returns>
		public static bool MatchSearchKey(string searchKey,object searchKeyValue,IMAP_Message messageInfo,MimeParser msg)
		{						
			// BEFORE <date>
			//	Messages whose internal date (disregarding time and timezone)
			//	is earlier than the specified date.
			if(searchKey == "BEFORE"){
				if(messageInfo.Date.Date < (DateTime)searchKeyValue){
					return true;
				}
			}
			// BODY <string>
			//	Messages that contain the specified string in the body of the message.
			else if(searchKey == "BODY"){
				if(msg.BodyText.IndexOf((string)searchKeyValue) > -1){
					return true;
				}
			}
			// 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.
			else if(searchKey == "HEADER"){				
				string[] headerField_value = (string[])searchKeyValue;
				string headerFileDValue = Core.CanonicalDecode(MimeParser.ParseHeaderField(headerField_value[0],msg.Headers));
				if(headerField_value[1].Length == 0){
					return true;					
				}
				else if(headerFileDValue.IndexOf(headerField_value[1]) > -1){
					return true;
				}
			}
			// KEYWORD <flag>
			//	Messages with the specified keyword flag set.
			else if(searchKey == "KEYWORD"){
				if((messageInfo.Flags & IMAP_Utils.ParseMessageFalgs((string)searchKeyValue)) != 0){
					return true;
				}
			}					
			// LARGER <n>
			//	Messages with an [RFC-2822] size larger than the specified number of octets.
			else if(searchKey == "LARGER"){
				if(messageInfo.Size > Convert.ToInt64(searchKeyValue)){
					return true;
				}
			}
			// ON <date>
			//	Messages whose internal date (disregarding time and timezone)
			//	is within the specified date.
			else if(searchKey == "ON"){
				if(messageInfo.Date.Date == (DateTime)searchKeyValue){
					return true;
				}
			}
			// SENTBEFORE <date>
			//	Messages whose [RFC-2822] Date: header (disregarding time and
			//	timezone) is earlier than the specified date.
			else if(searchKey == "SENTBEFORE"){				
				if(msg.MessageDate.Date < (DateTime)searchKeyValue){
					return true;
				}
			}
			// SENTON <date>
			//	Messages whose [RFC-2822] Date: header (disregarding time and
			//	timezone) is within the specified date.
			else if(searchKey == "SENTON"){				
				if(msg.MessageDate.Date == (DateTime)searchKeyValue){
					return true;
				}
			}
			// SENTSINCE <date>
			//	Messages whose [RFC-2822] Date: header (disregarding time and
			//	timezone) is within or later than the specified date.
			else if(searchKey == "SENTSINCE"){
				if(msg.MessageDate.Date >= (DateTime)searchKeyValue){
					return true;
				}
			}
			// SINCE <date>
			//	Messages whose internal date (disregarding time and timezone)
			//	is within or later than the specified date.	
			else if(searchKey == "SINCE"){
				if(messageInfo.Date.Date >= (DateTime)searchKeyValue){
					return true;
				}
			}
			// SMALLER <n>
			//	Messages with an [RFC-2822] size smaller than the specified	number of octets.
			else if(searchKey == "SMALLER"){
				if(messageInfo.Size < Convert.ToInt64(searchKeyValue)){
					return true;
				}
			}
			// TEXT <string>
			//	Messages that contain the specified string in the header or	body of the message.				
			else if(searchKey == "TEXT"){
				// TODO:
			}
			// UID <sequence set>
			//	Messages with unique identifiers corresponding to the specified
			//	unique identifier set.  Sequence set ranges are permitted.
			else if(searchKey == "UID"){				
				if(((string)searchKeyValue).IndexOf(":") > -1){
					string[] start_end = ((string)searchKeyValue).Split(':');
					if(messageInfo.MessageUID >= Convert.ToInt64(start_end[0]) && messageInfo.MessageUID <= Convert.ToInt64(start_end[1])){
						return true;
					}
				}
				else{
					if(messageInfo.MessageUID == Convert.ToInt64(searchKeyValue)){
						return true;
					}
				}
			}
			// UNKEYWORD <flag>
			//	Messages that do not have the specified keyword flag set.
			else if(searchKey == "UNKEYWORD"){
				if((messageInfo.Flags & IMAP_Utils.ParseMessageFalgs((string)searchKeyValue)) == 0){
					return true;
				}
			}

			return false;
		}
		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(!m_Authenticated){
				m_pSocket.SendLine(cmdTag + " NO Authenticate first !");
				return;
			}
			if(m_SelectedMailbox.Length == 0){
				m_pSocket.SendLine(cmdTag + " NO Select mailbox first !");
				return;
			}

			#region Parse parameters

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

			ArrayList seq_set = ParseMsgNumbersFromSequenceSet(args[0].Trim(),uidFetch);
		
			// 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";
			}

			// ToDo: ??? start parm parsing from left to end in while loop while params parsed or bad param found
			
			bool headersNeeded = false;
			bool fullMsgNeeded = false;

			// Parse,validate requested fetch items
			Hashtable items = new Hashtable();
			if(fetchItems.IndexOf("UID") > -1){
				items.Add("UID","");
				fetchItems = fetchItems.Replace("UID","");
			}
			if(fetchItems.IndexOf("RFC822.TEXT") > -1){
				fullMsgNeeded = true;
				items.Add("RFC822.TEXT","");
				fetchItems = fetchItems.Replace("RFC822.TEXT","");
			}
			if(fetchItems.IndexOf("RFC822.SIZE") > -1){
				items.Add("RFC822.SIZE","");
				fetchItems = fetchItems.Replace("RFC822.SIZE","");
			}
			if(fetchItems.IndexOf("RFC822.HEADER") > -1){
				headersNeeded = true;
				items.Add("RFC822.HEADER","");
				fetchItems = fetchItems.Replace("RFC822.HEADER","");
			}
			if(fetchItems.IndexOf("RFC822") > -1){
				fullMsgNeeded = true;
				items.Add("RFC822","");
				fetchItems = fetchItems.Replace("RFC822","");
			}
			if(fetchItems.IndexOf("INTERNALDATE") > -1){
				items.Add("INTERNALDATE","");
				fetchItems = fetchItems.Replace("INTERNALDATE","");
			}
			if(fetchItems.IndexOf("FLAGS") > -1){
				items.Add("FLAGS","");
				fetchItems = fetchItems.Replace("FLAGS","");
			}
			if(fetchItems.IndexOf("ENVELOPE") > -1){
				headersNeeded = true;
				items.Add("ENVELOPE","");
				fetchItems = fetchItems.Replace("ENVELOPE","");
			}
			if(fetchItems.IndexOf("BODYSTRUCTURE") > -1){
				fullMsgNeeded = true;
				items.Add("BODYSTRUCTURE","");
				fetchItems = fetchItems.Replace("BODYSTRUCTURE","");
			}
			if(fetchItems.IndexOf("BODY.PEEK[") > -1){
				int start = fetchItems.IndexOf("BODY.PEEK[") + 10;
				string val = fetchItems.Substring(start,fetchItems.IndexOf("]",start) - start).ToUpper().Trim();

				// Remove BODY.PEEK[...] from remaining args string.
				fetchItems = fetchItems.Substring(fetchItems.IndexOf("]") + 1);
				
				//--- See if partial fetch. For example: BODY.PEEK[]<0.100>
				long startPosition = 0;
				long maxLength     = long.MaxValue;
				bool partial       = false;
				if(fetchItems.StartsWith("<")){
					// We got partial fetch, need to get data between <>
					string partialArgs = fetchItems.Substring(1,fetchItems.IndexOf(">") - 1);
					string[] pArgs = partialArgs.Split('.');
					startPosition = Convert.ToInt64(pArgs[0]);
					maxLength     = Convert.ToInt64(pArgs[1]);

					// Remove partial fetch args(<...>) from remaining args string.
					fetchItems = fetchItems.Substring(fetchItems.IndexOf(">") + 1);

					partial = true;
				}
				//----------------------------------------------------------------

				// We must support only:
				// ""                   - full message
				// TEXT                 - message text
				// HEADER               - message header
				// HEADER.FIELDS        - requested message header fields
				// HEADER.FIELDS.NOT    - message header fields except requested
				// number of mime entry - Example: BODY[1];BODY[1.1];BODY[1.1.x. ...]

				if(val.Length > 0){
					string[] fArgs = ParseParams(val);
					
					// Specified number of mime entry requested
			//		if(fArgs.Length == 1 && Core.IsNumber(fArgs[0])){
			//			fullMsgNeeded = true;
			//			items.Add("BODY.PEEK[NUMBER]",new object[]{partial,startPosition,maxLength,Convert.ToInt32(fArgs[0])});
			//		}
			//		else{
						switch(fArgs[0].ToUpper())
						{
							case "TEXT":
								fullMsgNeeded = true;
								items.Add("BODY.PEEK[TEXT]",new object[]{partial,startPosition,maxLength});
								break;

							case "HEADER":
								headersNeeded = true;
								items.Add("BODY.PEEK[HEADER]",new object[]{partial,startPosition,maxLength});
								break;

							case "HEADER.FIELDS":
								if(fArgs.Length == 2){
									headersNeeded = true;
									items.Add("BODY.PEEK[HEADER.FIELDS]",new object[]{partial,startPosition,maxLength,fArgs[1]});
								}
								
								break;

							case "HEADER.FIELDS.NOT":
								if(fArgs.Length == 2){
									headersNeeded = true;
									items.Add("BODY.PEEK[HEADER.FIELDS.NOT]",new object[]{partial,startPosition,maxLength,fArgs[1]});
								}
								break;

							default:
								// This must be number of mime entry
								fullMsgNeeded = true;
								items.Add("BODY.PEEK[NUMBER]",new object[]{partial,startPosition,maxLength,fArgs[0]});
								break;

							//	m_pSocket.SendLine(cmdTag + " BAD Invalid fetch-items argument");
							//	return;
						}
			//		}
				}
				else{
					fullMsgNeeded = true;
					items.Add("BODY.PEEK[]",new object[]{partial,startPosition,maxLength});
				}				
			}
			// BODY[<section>]<<partial>>
			if(fetchItems.IndexOf("BODY[") > -1){
				int start = fetchItems.IndexOf("BODY[") + 5;
				string val = fetchItems.Substring(start,fetchItems.IndexOf("]",start) - start).ToUpper().Trim();
				
				// Remove BODY[...] from remaining args string.
				fetchItems = fetchItems.Substring(fetchItems.IndexOf("]") + 1);
				
				//--- See if partial fetch. For example: BODY[]<0.100>
				long startPosition = 0;
				long maxLength     = long.MaxValue;
				bool partial       = false;
				if(fetchItems.StartsWith("<")){
					// We got partial fetch, need to get data between <>
					string partialArgs = fetchItems.Substring(1,fetchItems.IndexOf(">") - 1);
					string[] pArgs = partialArgs.Split('.');
					startPosition = Convert.ToInt64(pArgs[0]);
					maxLength     = Convert.ToInt64(pArgs[1]);

					// Remove partial fetch args(<...>) from remaining args string.
					fetchItems = fetchItems.Substring(fetchItems.IndexOf(">") + 1);

					partial = true;
				}
				//----------------------------------------------------------------

				// We must support only:
				// ""                   - full message
				// TEXT                 - message text
				// HEADER               - message header
				// HEADER.FIELDS        - requested message header fields
				// HEADER.FIELDS.NOT    - message header fields except requested
				// number of mime entry - Example: BODY[1];BODY[1.1];BODY[1.1.x. ...]

				if(val.Length > 0){
					string[] fArgs = ParseParams(val);
				
					// Specified number of mime entry requested
			//		if(fArgs.Length == 1 && Core.IsNumber(fArgs[0])){
			//			fullMsgNeeded = true;
			//			items.Add("BODY[NUMBER]",new object[]{partial,startPosition,maxLength,Convert.ToInt32(fArgs[0])});
			//		}
			//		else{
						switch(fArgs[0].ToUpper())
						{
							case "TEXT":
								fullMsgNeeded = true;
								items.Add("BODY[TEXT]",new object[]{partial,startPosition,maxLength});
								break;

							case "HEADER":
								headersNeeded = true;
								items.Add("BODY[HEADER]",new object[]{partial,startPosition,maxLength});
								break;

							case "HEADER.FIELDS":
								if(fArgs.Length == 2){
									headersNeeded = true;
									items.Add("BODY[HEADER.FIELDS]",new object[]{partial,startPosition,maxLength,fArgs[1]});
								}								
								break;

							case "HEADER.FIELDS.NOT":
								if(fArgs.Length == 2){
									headersNeeded = true;
									items.Add("BODY[HEADER.FIELDS.NOT]",new object[]{partial,startPosition,maxLength,fArgs[1]});
								}
								break;

							default:
								// This must be number of mime entry
								fullMsgNeeded = true;
								items.Add("BODY[NUMBER]",new object[]{partial,startPosition,maxLength,fArgs[0]});
								break;

							//	m_pSocket.SendLine(cmdTag + " BAD Invalid fetch-items argument");
							//	return;
						}
			//		}
				}
				else{
					fullMsgNeeded = true;
					items.Add("BODY[]",new object[]{partial,startPosition,maxLength});
				}
			}
			if(fetchItems.IndexOf("BODY") > -1){
				fullMsgNeeded = true;
				items.Add("BODY","");
				fetchItems = fetchItems.Replace("BODY","");
			}

			// If length != 0, then contains invalid fetch items
			if(fetchItems.Trim().Length > 0){
				m_pSocket.SendLine(cmdTag + " BAD Invalid fetch-items argument");
				return;
			}

			#endregion

			// ToDo: 
			// 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;
		//	}

			// ToDo: Move to all parts to MimeParse where possible, this avoid multiple decodings

			for(int i=0;i<m_Messages.Count;i++){
				//
				if(seq_set.Contains(i + 1)){
					IMAP_Message msg = m_Messages[i];
				
					byte[] buf = null;
					MemoryStream reply = new MemoryStream();
					// Write fetch start data "* msgNo FETCH ("
					buf = Encoding.ASCII.GetBytes("* " + msg.MessageNo + " FETCH (");
					reply.Write(buf,0,buf.Length);

					byte[]     msgData     = null;
					byte[]     msgHeadData = null;
					MimeParser parser      = null;
					// Check if header or data is neccessary. Eg. if only UID wanted, don't get message at all.
					if(fullMsgNeeded || headersNeeded){
						Message_EventArgs eArgs = m_pServer.OnGetMessage(this,msg,!fullMsgNeeded);
						msgData = eArgs.MessageData;

						// Headers needed parse headers from msgData
						if(headersNeeded){							
							string headers = InfoControl.Net.Mail.Mime.MimeParser.ParseHeaders(new MemoryStream(msgData));
							msgHeadData = Encoding.Default.GetBytes(headers + "\r\n"); // blank line is included in all header fetches
						}

						parser = new MimeParser(msgData);
					}
					
					IMAP_MessageFlags msgFlagsOr = msg.Flags;
					// Construct reply here, based on requested fetch items
					int nCount = 0;
					foreach(string fetchItem in items.Keys){
						object[] partArgs      = null;
						bool     partial       = false;
						long     startPosition = 0;
						long     maxLength     = 0;
						long     lengthToSend  = 0;
						string   partNumber    = "";
						string   headerFields  = "";

						switch(fetchItem)
						{
							case "UID":
								buf = Encoding.ASCII.GetBytes("UID " + msg.MessageUID);
								reply.Write(buf,0,buf.Length);
								break;

							case "RFC822.TEXT":
								// Sets \seen flag
								msg.SetFlags(msg.Flags | IMAP_MessageFlags.Seen);
								
								// RFC822.TEXT {size}
								// msg text
								byte[] f11Data = System.Text.Encoding.ASCII.GetBytes(parser.BodyText);
									
								buf = Encoding.ASCII.GetBytes("RFC822.TEXT {" + f11Data.Length + "}\r\n");
								reply.Write(buf,0,buf.Length);

								reply.Write(f11Data,0,f11Data.Length);
								break;

							case "RFC822.SIZE":
								// "RFC822.SIZE size
								buf = Encoding.ASCII.GetBytes("RFC822.SIZE " + msg.Size);
								reply.Write(buf,0,buf.Length);
								break;

							case "RFC822.HEADER":
								// RFC822.HEADER {size}
								// msg header data							
								buf = Encoding.ASCII.GetBytes("RFC822.HEADER {" + msgHeadData.Length + "}\r\n");
								reply.Write(buf,0,buf.Length);
								reply.Write(msgHeadData,0,msgHeadData.Length);								
								break;

							case "RFC822":
								// Sets \seen flag
								msg.SetFlags(msg.Flags | IMAP_MessageFlags.Seen);

								// RFC822 {size}
								// msg data
								buf = Encoding.ASCII.GetBytes("RFC822 {" + msgData.Length + "}\r\n");
								reply.Write(buf,0,buf.Length);
								reply.Write(msgData,0,msgData.Length);
								break;

							case "INTERNALDATE":
								// INTERNALDATE "date"
								buf = Encoding.ASCII.GetBytes("INTERNALDATE \"" + msg.Date.ToString("r",System.Globalization.DateTimeFormatInfo.InvariantInfo) + "\"");
								reply.Write(buf,0,buf.Length);														
								break;

							case "FLAGS":
								buf = Encoding.ASCII.GetBytes("FLAGS (" + msg.FlagsToString() + ")");
								reply.Write(buf,0,buf.Length);
								break;

							case "ENVELOPE":
								buf = Encoding.ASCII.GetBytes(FetchHelper.ConstructEnvelope(parser));
								reply.Write(buf,0,buf.Length);
								break;

							case "BODYSTRUCTURE":
								// BODYSTRUCTURE ()
								buf = Encoding.ASCII.GetBytes(FetchHelper.ConstructBodyStructure(parser,true));
								reply.Write(buf,0,buf.Length);
								break;

							case "BODY.PEEK[]":
								// BODY[] {size}
								// msg header data
								partArgs = (object[])items[fetchItem];
								partial       = (bool)partArgs[0];
								startPosition = (long)partArgs[1];
								maxLength     = (long)partArgs[2];
								
								lengthToSend = msgData.Length - startPosition;
								if(lengthToSend > maxLength){
									lengthToSend = maxLength;
								}
								if(lengthToSend < 0){
									lengthToSend = 0;
								}

								if(partial){
									buf = Encoding.ASCII.GetBytes("BODY[]<" + startPosition + "> {" + lengthToSend + "}\r\n");
								}
								else{
									buf = Encoding.ASCII.GetBytes("BODY[] {" + lengthToSend + "}\r\n");
								}
								reply.Write(buf,0,buf.Length);
								reply.Write(msgData,(int)startPosition,(int)lengthToSend);
								break;

							case "BODY.PEEK[HEADER]":
								// BODY[HEADER] {size}
								// msg header data
								partArgs = (object[])items[fetchItem];
								partial       = (bool)partArgs[0];
								startPosition = (long)partArgs[1];
								maxLength     = (long)partArgs[2];
								
								lengthToSend = msgHeadData.Length - startPosition;
								if(lengthToSend > maxLength){
									lengthToSend = maxLength;
								}
								if(lengthToSend < 0){
									lengthToSend = 0;
								}

								if(partial){
									buf = Encoding.ASCII.GetBytes("BODY[HEADER]<" + startPosition + "> {" + lengthToSend + "}\r\n");
								}
								else{
									buf = Encoding.ASCII.GetBytes("BODY[HEADER] {" + lengthToSend + "}\r\n");
								}
								reply.Write(buf,0,buf.Length);
								reply.Write(msgHeadData,(int)startPosition,(int)lengthToSend);
								break;

							case "BODY.PEEK[HEADER.FIELDS]":
								// BODY[HEADER.FIELDS ()] {size}
								// msg header data
								partArgs = (object[])items[fetchItem];
								partial       = (bool)partArgs[0];
								startPosition = (long)partArgs[1];
								maxLength     = (long)partArgs[2];
								headerFields  = (string)partArgs[3];

								byte[] fData = Encoding.ASCII.GetBytes(FetchHelper.ParseHeaderFields(headerFields,msgHeadData));

								lengthToSend = fData.Length - startPosition;
								if(lengthToSend > maxLength){
									lengthToSend = maxLength;
								}
								if(lengthToSend < 0){
									lengthToSend = 0;
								}
								
								if(partial){
									buf = Encoding.ASCII.GetBytes("BODY[HEADER.FIELDS (" + headerFields + ")]<" + startPosition + "> {" + lengthToSend + "}\r\n");
								}
								else{
									buf = Encoding.ASCII.GetBytes("BODY[HEADER.FIELDS (" + headerFields + ")] {" + lengthToSend + "}\r\n");
								}
								reply.Write(buf,0,buf.Length);
								reply.Write(fData,(int)startPosition,(int)lengthToSend);														
								break;

							case "BODY.PEEK[HEADER.FIELDS.NOT]":
								// BODY[HEADER.FIELDS.NOT ()] {size}
								// msg header data
								partArgs = (object[])items[fetchItem];
								partial       = (bool)partArgs[0];
								startPosition = (long)partArgs[1];
								maxLength     = (long)partArgs[2];
								headerFields  = (string)partArgs[3];

								byte[] f1Data = Encoding.ASCII.GetBytes(FetchHelper.ParseHeaderFieldsNot(headerFields,msgHeadData));

								lengthToSend = f1Data.Length - startPosition;
								if(lengthToSend > maxLength){
									lengthToSend = maxLength;
								}
								if(lengthToSend < 0){
									lengthToSend = 0;
								}
								
								if(partial){
									buf = Encoding.ASCII.GetBytes("BODY[HEADER.FIELDS.NOT (" + headerFields + ")]<" + startPosition + "> {" + lengthToSend + "}\r\n");
								}
								else{
									buf = Encoding.ASCII.GetBytes("BODY[HEADER.FIELDS.NOT (" + headerFields + ")] {" + lengthToSend + "}\r\n");
								}
								reply.Write(buf,0,buf.Length);
								reply.Write(f1Data,(int)startPosition,(int)lengthToSend);
								break;

							case "BODY.PEEK[TEXT]":
								// BODY[TEXT] {size}
								// msg text
								partArgs = (object[])items[fetchItem];
								partial       = (bool)partArgs[0];
								startPosition = (long)partArgs[1];
								maxLength     = (long)partArgs[2];

								byte[] f111Data = Encoding.ASCII.GetBytes(parser.BodyText);

								lengthToSend = f111Data.Length - startPosition;
								if(lengthToSend > maxLength){
									lengthToSend = maxLength;
								}
								if(lengthToSend < 0){
									lengthToSend = 0;
								}
								
								if(partial){	
									buf = Encoding.ASCII.GetBytes("BODY[TEXT]<" + startPosition + "> {" + lengthToSend + "}\r\n");
								}
								else{
									buf = Encoding.ASCII.GetBytes("BODY[TEXT] {" + lengthToSend + "}\r\n");
								}
								reply.Write(buf,0,buf.Length);
								reply.Write(f111Data,(int)startPosition,(int)lengthToSend);
								break;

							case "BODY.PEEK[NUMBER]":
								// BODY[no.] {size}
								// mime part
								partArgs = (object[])items[fetchItem];
								partial       = (bool)partArgs[0];
								startPosition = (long)partArgs[1];
								maxLength     = (long)partArgs[2];
								partNumber    = partArgs[3].ToString();

								byte[] b1113Data = FetchHelper.ParseMimeEntry(parser,partNumber);
																									
								if(b1113Data != null){
									lengthToSend = b1113Data.Length - startPosition;
									if(lengthToSend > maxLength){
										lengthToSend = maxLength;
									}
									if(lengthToSend < 0){
										lengthToSend = 0;
									}
									
									if(partial){
										buf = Encoding.ASCII.GetBytes("BODY[" + partNumber + "]<" + startPosition + "> {" + lengthToSend + "}\r\n");
									}
									else{
										buf = Encoding.ASCII.GetBytes("BODY[" + partNumber + "] {" + lengthToSend + "}\r\n");
									}
									reply.Write(buf,0,buf.Length);
									reply.Write(b1113Data,(int)startPosition,(int)lengthToSend);
								}
								else{
									// BODY[no.] NIL
									buf = Encoding.ASCII.GetBytes("BODY[" + partNumber + "] NIL");
									reply.Write(buf,0,buf.Length);
								}
								break;

							case "BODY[]":
								// Sets \seen flag
								msg.SetFlags(msg.Flags | IMAP_MessageFlags.Seen);

								// BODY[] {size}
								// msg header data
								partArgs = (object[])items[fetchItem];
								partial       = (bool)partArgs[0];
								startPosition = (long)partArgs[1];
								maxLength     = (long)partArgs[2];
								
								lengthToSend = msgData.Length - startPosition;
								if(lengthToSend > maxLength){
									lengthToSend = maxLength;
								}
								if(lengthToSend < 0){
									lengthToSend = 0;
								}

								if(partial){
									buf = Encoding.ASCII.GetBytes("BODY[]<" + startPosition + "> {" + lengthToSend + "}\r\n");
								}
								else{
									buf = Encoding.ASCII.GetBytes("BODY[] {" + lengthToSend + "}\r\n");
								}
								reply.Write(buf,0,buf.Length);
								reply.Write(msgData,(int)startPosition,(int)lengthToSend);							
								break;

							case "BODY[HEADER]":
								// Sets \seen flag
								msg.SetFlags(msg.Flags | IMAP_MessageFlags.Seen);

								// BODY[HEADER] {size}
								// msg header data
								partArgs = (object[])items[fetchItem];
								partial       = (bool)partArgs[0];
								startPosition = (long)partArgs[1];
								maxLength     = (long)partArgs[2];
								
								lengthToSend = msgHeadData.Length - startPosition;
								if(lengthToSend > maxLength){
									lengthToSend = maxLength;
								}
								if(lengthToSend < 0){
									lengthToSend = 0;
								}

								if(partial){
									buf = Encoding.ASCII.GetBytes("BODY[HEADER]<" + startPosition + "> {" + lengthToSend + "}\r\n");
								}
								else{
									buf = Encoding.ASCII.GetBytes("BODY[HEADER] {" + lengthToSend + "}\r\n");
								}
								reply.Write(buf,0,buf.Length);
								reply.Write(msgHeadData,(int)startPosition,(int)lengthToSend);
								break;

							case "BODY[HEADER.FIELDS]":
								// Sets \seen flag
								msg.SetFlags(msg.Flags | IMAP_MessageFlags.Seen);

								// BODY[HEADER.FIELDS ()] {size}
								// msg header data
								partArgs = (object[])items[fetchItem];
								partial       = (bool)partArgs[0];
								startPosition = (long)partArgs[1];
								maxLength     = (long)partArgs[2];
								headerFields  = (string)partArgs[3];

								byte[] bData = Encoding.ASCII.GetBytes(FetchHelper.ParseHeaderFields(headerFields,msgHeadData));

								lengthToSend = bData.Length - startPosition;
								if(lengthToSend > maxLength){
									lengthToSend = maxLength;
								}
								if(lengthToSend < 0){
									lengthToSend = 0;
								}
								
								if(partial){
									buf = Encoding.ASCII.GetBytes("BODY[HEADER.FIELDS (" + headerFields + ")]<" + startPosition + "> {" + lengthToSend + "}\r\n");
								}
								else{
									buf = Encoding.ASCII.GetBytes("BODY[HEADER.FIELDS (" + headerFields + ")] {" + lengthToSend + "}\r\n");
								}
								reply.Write(buf,0,buf.Length);
								reply.Write(bData,(int)startPosition,(int)lengthToSend);
								break;

							case "BODY[HEADER.FIELDS.NOT]":
								// Sets \seen flag
								msg.SetFlags(msg.Flags | IMAP_MessageFlags.Seen);

								// BODY[HEADER.FIELDS.NOT ()] {size}
								// msg header data
								partArgs = (object[])items[fetchItem];
								partial       = (bool)partArgs[0];
								startPosition = (long)partArgs[1];
								maxLength     = (long)partArgs[2];
								headerFields  = (string)partArgs[3];

								byte[] f2Data = Encoding.ASCII.GetBytes(FetchHelper.ParseHeaderFieldsNot(headerFields,msgHeadData));

								lengthToSend = f2Data.Length - startPosition;
								if(lengthToSend > maxLength){
									lengthToSend = maxLength;
								}
								if(lengthToSend < 0){
									lengthToSend = 0;
								}
									
								if(partial){
									buf = Encoding.ASCII.GetBytes("BODY[HEADER.FIELDS.NOT (" + headerFields + ")]<" + startPosition + "> {" + lengthToSend + "}\r\n");
								}
								else{
									buf = Encoding.ASCII.GetBytes("BODY[HEADER.FIELDS.NOT (" + headerFields + ")] {" + lengthToSend + "}\r\n");
								}
								reply.Write(buf,0,buf.Length);
								reply.Write(f2Data,(int)startPosition,(int)lengthToSend);
								break;

							case "BODY[TEXT]":
								// Sets \seen flag
								msg.SetFlags(msg.Flags | IMAP_MessageFlags.Seen);

								// BODY[TEXT] {size}
								// msg text									
								partArgs = (object[])items[fetchItem];
								partial       = (bool)partArgs[0];
								startPosition = (long)partArgs[1];
								maxLength     = (long)partArgs[2];

								byte[] f1111Data = Encoding.ASCII.GetBytes(parser.BodyText);

								lengthToSend = f1111Data.Length - startPosition;
								if(lengthToSend > maxLength){
									lengthToSend = maxLength;
								}

								if(partial){
									buf = Encoding.ASCII.GetBytes("BODY[TEXT]<" + startPosition + "> {" + lengthToSend + "}\r\n");
								}
								else{
									buf = Encoding.ASCII.GetBytes("BODY[TEXT] {" + lengthToSend + "}\r\n");
								}
								reply.Write(buf,0,buf.Length);
								reply.Write(f1111Data,(int)startPosition,(int)lengthToSend);
								break;

							case "BODY[NUMBER]":
								// Sets \seen flag
								msg.SetFlags(msg.Flags | IMAP_MessageFlags.Seen);

								// BODY[no.] {size}
								// mime part
								partArgs = (object[])items[fetchItem];
								partial       = (bool)partArgs[0];
								startPosition = (long)partArgs[1];
								maxLength     = (long)partArgs[2];
								partNumber    = partArgs[3].ToString();

								byte[] b113Data = FetchHelper.ParseMimeEntry(parser,partNumber);
																									
								if(b113Data != null){
									lengthToSend = b113Data.Length - startPosition;
									if(lengthToSend > maxLength){
										lengthToSend = maxLength;
									}
									if(lengthToSend < 0){
										lengthToSend = 0;
									}

									if(partial){
										buf = Encoding.ASCII.GetBytes("BODY[" + partNumber + "]<" + startPosition + "> {" + lengthToSend + "}\r\n");
									}
									else{
										buf = Encoding.ASCII.GetBytes("BODY[" + partNumber + "] {" + lengthToSend + "}\r\n");
									}
									reply.Write(buf,0,buf.Length);
									reply.Write(b113Data,(int)startPosition,(int)lengthToSend);
								}
								else{									
									// BODY[no.] NIL
									buf = Encoding.ASCII.GetBytes("BODY[" + partNumber + "] NIL");
									reply.Write(buf,0,buf.Length);
								}
								break;

							case "BODY":
								// Sets \seen flag

								// BODY ()
								buf = Encoding.ASCII.GetBytes(FetchHelper.ConstructBodyStructure(parser,false));
								reply.Write(buf,0,buf.Length);
								break;
						}

						nCount++;

						// Write fetch item separator data " "
						// We don't write it for last item
						if(nCount < items.Count){						
							buf = Encoding.ASCII.GetBytes(" ");
							reply.Write(buf,0,buf.Length);
						}
					}

					// Write fetch end data ")"
					buf = Encoding.ASCII.GetBytes(")\r\n");
					reply.Write(buf,0,buf.Length);

					// Send fetch reply to client
					reply.Position = 0;
					m_pSocket.SendData(reply);


					// 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);
					}
				}
				
			}

			m_pSocket.SendLine(cmdTag + " OK FETCH completed");
		}
//
		#region function 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 UNDUCUMENTED !!!
						C: XXXXXX
						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(!m_Authenticated){
				m_pSocket.SendLine(cmdTag + " NO Authenticate first !");
				return;
			}
			if(m_SelectedMailbox.Length == 0){
				m_pSocket.SendLine(cmdTag + " NO Select mailbox first !");
				return;
			}

			// 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);//argsText.Split(' ')[0];

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

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

				// Remove charset value from args text
			//	argsText = argsText.Substring(charset.Length).Trim();
			}

			// Parse search keys
			Hashtable searchKeys = new Hashtable();
			while(argsText.Length > 0){
				// ALL
				if(argsText.ToUpper().StartsWith("ALL")){
					// Remove ALL from argsText
					argsText = argsText.Substring(3).Trim();

					// Just eat this keyword
				}
				// ANSWERED
				else if(argsText.ToUpper().StartsWith("ANSWERED")){
					// Remove ANSWERED from argsText
					argsText = argsText.Substring(8).Trim();

					// Move to KEYWORD <flag>
					searchKeys.Add("KEYWORD","ANSWERED");
				}
				// BCC <string> or {<string>.Length} if charset specified
				else if(argsText.ToUpper().StartsWith("BCC")){
					// Remove BCC from argsText
					argsText = argsText.Substring(3).Trim();

					string paramValue = "";
					if(!argsText.StartsWith("{")){
						try{
							paramValue = IMAP_Utils.ParseQuotedParam(ref argsText);
						}
						catch(Exception x){
							m_pSocket.SendLine(cmdTag + " NO Invalid BCC <string> value: " + x.Message);
							return;
						}
					}
					else{
						// get string length, it must be between {}
						long count = 0;
						if(argsText.StartsWith("{") && argsText.IndexOf('}',1) > -1){
							try{
								count = Convert.ToInt64(argsText.Substring(1,argsText.IndexOf('}',1) - 1));
							}
							catch{
								m_pSocket.SendLine(cmdTag + " NO Invalid BCC {x} value");
								return;
							}
						}
						else{
							m_pSocket.SendLine(cmdTag + " NO Invalid BCC value");
							return;
						}

						// Notify connected client that it can continue sending cmdline
						m_pSocket.SendLine("+ Continue");
		
						// Read string from socket
						MemoryStream ms = new MemoryStream();
						ReadReplyCode result = m_pSocket.ReadData(ms,count,true);
						if(result != ReadReplyCode.Ok){
							throw new Exception(result.ToString());
						}

						paramValue = System.Text.Encoding.GetEncoding(charset).GetString(ms.ToArray());

						// Get next args text
						argsText = m_pSocket.ReadLine();
					}
					
					// Move to HEADER <field-name> <string>
					searchKeys.Add("HEADER",new string[]{"BCC",paramValue});
				}
				// BEFORE <date>
				else if(argsText.ToUpper().StartsWith("BEFORE")){
					// Remove BEFORE from argsText
					argsText = argsText.Substring(6).Trim();

					string dateValueString = argsText.Split(' ')[0];
					try{
						DateTime dateValue = _SearchHelper.ParseDate(dateValueString);

						searchKeys.Add("BEFORE",dateValue);
					}
					catch{
						m_pSocket.SendLine(cmdTag + " NO Invalid BEFORE <date> value");
						return;
					}
					
					// Remove date value from argsText
					argsText = argsText.Substring(dateValueString.Length).Trim();
				}
				// BODY <string> or {<string>.Length} if charset specified
				else if(argsText.ToUpper().StartsWith("BODY")){
					// Remove BODY from argsText
					argsText = argsText.Substring(4).Trim();

					string paramValue = "";
					if(!argsText.StartsWith("{")){
						try{
							paramValue = IMAP_Utils.ParseQuotedParam(ref argsText);
						}
						catch(Exception x){
							m_pSocket.SendLine(cmdTag + " NO Invalid BODY <string> value: " + x.Message);
							return;
						}
					}
					else{
						// get string length, it must be between {}
						long count = 0;
						if(argsText.StartsWith("{") && argsText.IndexOf('}',1) > -1){
							try{
								count = Convert.ToInt64(argsText.Substring(1,argsText.IndexOf('}',1) - 1));
							}
							catch{
								m_pSocket.SendLine(cmdTag + " NO Invalid BODY {x} value");
								return;
							}
						}
						else{
							m_pSocket.SendLine(cmdTag + " NO Invalid BODY value");
							return;
						}

						// Notify connected client that it can continue sending cmdline
						m_pSocket.SendLine("+ Continue");
		
						// Read string from socket
						MemoryStream ms = new MemoryStream();
						ReadReplyCode result = m_pSocket.ReadData(ms,count,true);
						if(result != ReadReplyCode.Ok){
							throw new Exception(result.ToString());
						}

						paramValue = System.Text.Encoding.GetEncoding(charset).GetString(ms.ToArray());

						// Get next args text
						argsText = m_pSocket.ReadLine();
					}

					searchKeys.Add("BODY",paramValue);
				}
				// CC <string> or {<string>.Length} if charset specified
				else if(argsText.ToUpper().StartsWith("CC")){
					// Remove CC from argsText
					argsText = argsText.Substring(2).Trim();

					string paramValue = "";
					if(!argsText.StartsWith("{")){
						try{
							paramValue = IMAP_Utils.ParseQuotedParam(ref argsText);
						}
						catch(Exception x){
							m_pSocket.SendLine(cmdTag + " NO Invalid CC <string> value: " + x.Message);
							return;
						}
					}
					else{
						// get string length, it must be between {}
						long count = 0;
						if(argsText.StartsWith("{") && argsText.IndexOf('}',1) > -1){
							try{
								count = Convert.ToInt64(argsText.Substring(1,argsText.IndexOf('}',1) - 1));
							}
							catch{
								m_pSocket.SendLine(cmdTag + " NO Invalid CC {x} value");
								return;
							}
						}
						else{
							m_pSocket.SendLine(cmdTag + " NO Invalid CC value");
							return;
						}

						// Notify connected client that it can continue sending cmdline
						m_pSocket.SendLine("+ Continue");
		
						// Read string from socket
						MemoryStream ms = new MemoryStream();
						ReadReplyCode result = m_pSocket.ReadData(ms,count,true);
						if(result != ReadReplyCode.Ok){
							throw new Exception(result.ToString());
						}

						paramValue = System.Text.Encoding.GetEncoding(charset).GetString(ms.ToArray());

						// Get next args text
						argsText = m_pSocket.ReadLine();
					}

					// Move to HEADER <field-name> <string>
					searchKeys.Add("HEADER",new string[]{"CC",paramValue});
				}
				// DELETED
				else if(argsText.ToUpper().StartsWith("DELETED")){
					// Remove DELETED from argsText
					argsText = argsText.Substring(7).Trim();

					// Move to KEYWORD <flag>
					searchKeys.Add("KEYWORD","DELETED");
				}
				// DRAFT
				else if(argsText.ToUpper().StartsWith("DRAFT")){
					// Remove DRAFT from argsText
					argsText = argsText.Substring(5).Trim();

					// Move to KEYWORD <flag>
					searchKeys.Add("KEYWORD","DRAFT");
				}
				// FLAGGED
				else if(argsText.ToUpper().StartsWith("FLAGGED")){
					// Remove FLAGGED from argsText
					argsText = argsText.Substring(7).Trim();

					// Move to KEYWORD <flag>
					searchKeys.Add("KEYWORD","FLAGGED");
				}
				// FROM <string> or {<string>.Length} if charset specified
				else if(argsText.ToUpper().StartsWith("FROM")){
					// Remove FROM from argsText
					argsText = argsText.Substring(4).Trim();

					string paramValue = "";
					if(!argsText.StartsWith("{")){
						try{
							paramValue = IMAP_Utils.ParseQuotedParam(ref argsText);
						}
						catch(Exception x){
							m_pSocket.SendLine(cmdTag + " NO Invalid FROM <string> value: " + x.Message);
							return;
						}
					}
					else{
						// get string length, it must be between {}
						long count = 0;
						if(argsText.StartsWith("{") && argsText.IndexOf('}',1) > -1){
							try{
								count = Convert.ToInt64(argsText.Substring(1,argsText.IndexOf('}',1) - 1));
							}
							catch{
								m_pSocket.SendLine(cmdTag + " NO Invalid FROM {x} value");
								return;
							}
						}
						else{
							m_pSocket.SendLine(cmdTag + " NO Invalid FROM value");
							return;
						}

						// Notify connected client that it can continue sending cmdline
						m_pSocket.SendLine("+ Continue");
		
						// Read string from socket
						MemoryStream ms = new MemoryStream();
						ReadReplyCode result = m_pSocket.ReadData(ms,count,true);
						if(result != ReadReplyCode.Ok){
							throw new Exception(result.ToString());
						}

						paramValue = System.Text.Encoding.GetEncoding(charset).GetString(ms.ToArray());

						// Get next args text
						argsText = m_pSocket.ReadLine();
					}

					// Move to HEADER <field-name> <string>
					searchKeys.Add("HEADER",new string[]{"FROM",paramValue});
				}
				// HEADER <field-name> <string> or {<string>.Length} if charset specified
				else if(argsText.ToUpper().StartsWith("HEADER")){
					// Remove HEADER from argsText
					argsText = argsText.Substring(6).Trim();

					// Get field name
					string fieldName = IMAP_Utils.ParseQuotedParam(ref argsText);

					string paramValue = "";
					if(!argsText.StartsWith("{")){
						try{
							paramValue = IMAP_Utils.ParseQuotedParam(ref argsText);
						}
						catch(Exception x){
							m_pSocket.SendLine(cmdTag + " NO Invalid HEADER <string> value: " + x.Message);
							return;
						}
					}
					else{
						// get string length, it must be between {}						
						long count = 0;
						if(argsText.StartsWith("{") && argsText.IndexOf('}') > -1){
							try{
								count = Convert.ToInt64(argsText.Substring(1,argsText.IndexOf('}') - 1));
							}
							catch{
								m_pSocket.SendLine(cmdTag + " NO Invalid HEADER {x} value");
								return;
							}
						}
						else{
							m_pSocket.SendLine(cmdTag + " NO Invalid HEADER value");
							return;
						}
						
						// Notify connected client that it can continue sending cmdline
						m_pSocket.SendLine("+ Continue");
		
						// Read string from socket
						MemoryStream ms = new MemoryStream();
						ReadReplyCode result = m_pSocket.ReadData(ms,count,true);
						if(result != ReadReplyCode.Ok){
							throw new Exception(result.ToString());
						}

						paramValue = System.Text.Encoding.GetEncoding(charset).GetString(ms.ToArray());

						// Get next args text
						argsText = m_pSocket.ReadLine();
					}

					searchKeys.Add("HEADER",new string[]{fieldName,paramValue});
				}
				// KEYWORD <flag>
				else if(argsText.ToUpper().StartsWith("KEYWORD")){
					// Remove KEYWORD from argsText
					argsText = argsText.Substring(7).Trim();

					// Get flag value
					string flagValue = argsText.Split(' ')[0];

					searchKeys.Add("KEYWORD",flagValue);

					// Remove flag value from argsText
					argsText = argsText.Substring(flagValue.Length).Trim();
				}
				// LARGER <n>
				else if(argsText.ToUpper().StartsWith("LARGER")){
					// Remove LARGER from argsText
					argsText = argsText.Substring(6).Trim();

					long lagerValue = 0;
					try{
						lagerValue = Convert.ToInt64(argsText.Split(' ')[0]);
					}
					catch{
					}

					searchKeys.Add("LARGER",lagerValue);

					// Remove flag value from argsText
					argsText = argsText.Substring(lagerValue.ToString().Length).Trim();
				}
				// NEW
				else if(argsText.ToUpper().StartsWith("NEW")){
					// Remove NEW from argsText
					argsText = argsText.Substring(3).Trim();

					// Move to KEYWORD <flag>
					searchKeys.Add("KEYWORD","RECENT");
				}
				// NOT <search-key>
				else if(argsText.ToUpper().StartsWith("NOT")){
					// Remove NOT from argsText
					argsText = argsText.Substring(3).Trim();

					m_pSocket.SendLine(cmdTag + " NO NOT search key isn't supported at moment");
					return;
				}
				// OLD
				else if(argsText.ToUpper().StartsWith("OLD")){
					// Remove OLD from argsText
					argsText = argsText.Substring(3).Trim();

					// Move to KEYWORD <flag>
					searchKeys.Add("UNKEYWORD","RECENT");
				}
				// ON <date>
				else if(argsText.ToUpper().StartsWith("ON")){
					// Remove ON from argsText
					argsText = argsText.Substring(2).Trim();

					string dateValueString = argsText.Split(' ')[0];
					try{
						DateTime dateValue = _SearchHelper.ParseDate(dateValueString);

						searchKeys.Add("ON",dateValue);
					}
					catch{
						m_pSocket.SendLine(cmdTag + " NO Invalid ON <date> value");
						return;
					}
					
					// Remove date value from argsText
					argsText = argsText.Substring(dateValueString.Length).Trim();
				}
				// OR <search-key1> <search-key2>
				else if(argsText.ToUpper().StartsWith("OR")){
					// Remove OR from argsText
					argsText = argsText.Substring(2).Trim();

					m_pSocket.SendLine(cmdTag + " NO OR search key isn't supported at moment");
					return;
				}
				// RECENT
				else if(argsText.ToUpper().StartsWith("RECENT")){
					// Remove RECENT from argsText
					argsText = argsText.Substring(6).Trim();

					// Move to KEYWORD <flag>
					searchKeys.Add("KEYWORD","RECENT");
				}
				// SEEN
				else if(argsText.ToUpper().StartsWith("SEEN")){
					// Remove SEEN from argsText
					argsText = argsText.Substring(4).Trim();

					// Move to KEYWORD <flag>
					searchKeys.Add("KEYWORD","SEEN");
				}
				// SENTBEFORE <date>
				else if(argsText.ToUpper().StartsWith("SENTBEFORE")){
					// Remove SENTBEFORE from argsText
					argsText = argsText.Substring(10).Trim();

					string dateValueString = argsText.Split(' ')[0];
					try{
						DateTime dateValue = _SearchHelper.ParseDate(dateValueString);

						searchKeys.Add("SENTBEFORE",dateValue);
					}
					catch{
						m_pSocket.SendLine(cmdTag + " NO Invalid SENTBEFORE <date> value");
						return;
					}
					
					// Remove date value from argsText
					argsText = argsText.Substring(dateValueString.Length).Trim();
				}
				// SENTON <date>
				else if(argsText.ToUpper().StartsWith("SENTON")){
					// Remove SENTON from argsText
					argsText = argsText.Substring(6).Trim();

					string dateValueString = argsText.Split(' ')[0];
					try{
						DateTime dateValue = _SearchHelper.ParseDate(dateValueString);

						searchKeys.Add("SENTON",dateValue);
					}
					catch{
						m_pSocket.SendLine(cmdTag + " NO Invalid SENTON <date> value");
						return;
					}
					
					// Remove date value from argsText
					argsText = argsText.Substring(dateValueString.Length).Trim();
				}
				// SENTSINCE <date>
				else if(argsText.ToUpper().StartsWith("SENTSINCE")){
					// Remove SENTSINCE from argsText
					argsText = argsText.Substring(9).Trim();

					string dateValueString = argsText.Split(' ')[0];
					try{
						DateTime dateValue = _SearchHelper.ParseDate(dateValueString);

						searchKeys.Add("SENTSINCE",dateValue);
					}
					catch{
						m_pSocket.SendLine(cmdTag + " NO Invalid SENTSINCE <date> value");
						return;
					}
					
					// Remove date value from argsText
					argsText = argsText.Substring(dateValueString.Length).Trim();
				}
				// SINCE <date>
				else if(argsText.ToUpper().StartsWith("SINCE")){
					// Remove SINCE from argsText
					argsText = argsText.Substring(5).Trim();

					string dateValueString = argsText.Split(' ')[0];
					try{
						DateTime dateValue = _SearchHelper.ParseDate(dateValueString);

						searchKeys.Add("SINCE",dateValue);
					}
					catch{
						m_pSocket.SendLine(cmdTag + " NO Invalid SINCE <date> value");
						return;
					}
					
					// Remove date value from argsText
					argsText = argsText.Substring(dateValueString.Length).Trim();
				}
				// SMALLER <n>
				else if(argsText.ToUpper().StartsWith("SMALLER")){
					// Remove SMALLER from argsText
					argsText = argsText.Substring(7).Trim();

					long smallerValue = 0;
					try{
						smallerValue = Convert.ToInt64(argsText.Split(' ')[0]);
					}
					catch{
					}

					searchKeys.Add("SMALLER",smallerValue);

					// Remove flag value from argsText
					argsText = argsText.Substring(smallerValue.ToString().Length).Trim();
				}
				// SUBJECT <string> or {<string>.Length} if charset specified
				else if(argsText.ToUpper().StartsWith("SUBJECT")){
					// Remove SUBJECT from argsText
					argsText = argsText.Substring(7).Trim();

					string paramValue = "";
					if(!argsText.StartsWith("{")){
						try{
							paramValue = IMAP_Utils.ParseQuotedParam(ref argsText);
						}
						catch(Exception x){
							m_pSocket.SendLine(cmdTag + " NO Invalid SUBJECT <string> value: " + x.Message);
							return;
						}
					}
					else{
						// get string length, it must be between {}
						long count = 0;
						if(argsText.StartsWith("{") && argsText.IndexOf('}',1) > -1){
							try{
								count = Convert.ToInt64(argsText.Substring(1,argsText.IndexOf('}',1) - 1));
							}
							catch{
								m_pSocket.SendLine(cmdTag + " NO Invalid SUBJECT {x} value");
								return;
							}
						}
						else{
							m_pSocket.SendLine(cmdTag + " NO Invalid SUBJECT value");
							return;
						}

						// Notify connected client that it can continue sending cmdline
						m_pSocket.SendLine("+ Continue");
		
						// Read string from socket
						MemoryStream ms = new MemoryStream();
						ReadReplyCode result = m_pSocket.ReadData(ms,count,true);
						if(result != ReadReplyCode.Ok){
							throw new Exception(result.ToString());
						}

						paramValue = System.Text.Encoding.GetEncoding(charset).GetString(ms.ToArray());

						// Get next args text
						argsText = m_pSocket.ReadLine();
					}
				
					// Move to HEADER <field-name> <string>
					searchKeys.Add("HEADER",new string[]{"SUBJECT",paramValue});
				}
				// TEXT <string> or {<string>.Length} if charset specified
				else if(argsText.ToUpper().StartsWith("TEXT")){
					// Remove TEXT from argsText
					argsText = argsText.Substring(4).Trim();

					string paramValue = "";
					if(!argsText.StartsWith("{")){
						try{
							paramValue = IMAP_Utils.ParseQuotedParam(ref argsText);
						}
						catch(Exception x){
							m_pSocket.SendLine(cmdTag + " NO Invalid TEXT <string> value: " + x.Message);
							return;
						}
					}
					else{
						// get string length, it must be between {}
						long count = 0;
						if(argsText.StartsWith("{") && argsText.IndexOf('}',1) > -1){
							try{
								count = Convert.ToInt64(argsText.Substring(1,argsText.IndexOf('}',1) - 1));
							}
							catch{
								m_pSocket.SendLine(cmdTag + " NO Invalid TEXT {x} value");
								return;
							}
						}
						else{
							m_pSocket.SendLine(cmdTag + " NO Invalid TEXT value");
							return;
						}

						// Notify connected client that it can continue sending cmdline
						m_pSocket.SendLine("+ Continue");
		
						// Read string from socket
						MemoryStream ms = new MemoryStream();
						ReadReplyCode result = m_pSocket.ReadData(ms,count,true);
						if(result != ReadReplyCode.Ok){
							throw new Exception(result.ToString());
						}

						paramValue = System.Text.Encoding.GetEncoding(charset).GetString(ms.ToArray());

						// Get next args text
						argsText = m_pSocket.ReadLine();
					}

					searchKeys.Add("TEXT",paramValue);
				}
				// TO <string> or {<string>.Length} if charset specified
				else if(argsText.ToUpper().StartsWith("TO")){
					// Remove TO from argsText
					argsText = argsText.Substring(2).Trim();

					string paramValue = "";
					if(!argsText.StartsWith("{")){
						try{
							paramValue = IMAP_Utils.ParseQuotedParam(ref argsText);
						}
						catch(Exception x){
							m_pSocket.SendLine(cmdTag + " NO Invalid TO <string> value: " + x.Message);
							return;
						}
					}
					else{
						// get string lenght, it must be between {}
						long count = 0;
						if(argsText.StartsWith("{") && argsText.IndexOf('}',1) > -1){
							try{
								count = Convert.ToInt64(argsText.Substring(1,argsText.IndexOf('}',1) - 1));
							}
							catch{
								m_pSocket.SendLine(cmdTag + " NO Invalid TO {x} value");
								return;
							}
						}
						else{
							m_pSocket.SendLine(cmdTag + " NO Invalid TO value");
							return;
						}

						// Notify connected client that it can continue sending cmdline
						m_pSocket.SendLine("+ Continue");
		
						// Read string from socket
						MemoryStream ms = new MemoryStream();
						ReadReplyCode result = m_pSocket.ReadData(ms,count,true);
						if(result != ReadReplyCode.Ok){
							throw new Exception(result.ToString());
						}

						paramValue = System.Text.Encoding.GetEncoding(charset).GetString(ms.ToArray());

						// Get next args text
						argsText = m_pSocket.ReadLine();
					}

					// Move to HEADER <field-name> <string>
					searchKeys.Add("HEADER",new string[]{"TO",paramValue});
				}
				// UID <sequence set>
				else if(argsText.ToUpper().StartsWith("UID")){
					// Remove UID from argsText
					argsText = argsText.Substring(3).Trim();

					string uidValue = argsText.Split(' ')[0];

					searchKeys.Add("UID",uidValue);

					// Remove uid value from argsText
					argsText = argsText.Substring(uidValue.Length).Trim();
				}
				// UNANSWERED
				else if(argsText.ToUpper().StartsWith("UNANSWERED")){
					// Remove UNANSWERED from argsText
					argsText = argsText.Substring(10).Trim();

					// Move to UNKEYWORD <flag>
					searchKeys.Add("UNKEYWORD","ANSWERED");
				}
				// UNDELETED
				else if(argsText.ToUpper().StartsWith("UNDELETED")){
					// Remove UNDELETED from argsText
					argsText = argsText.Substring(9).Trim();

					// Move to UNKEYWORD <flag>
					searchKeys.Add("UNKEYWORD","DELETED");
				}
				// UNDRAFT
				else if(argsText.ToUpper().StartsWith("UNDRAFT")){
					// Remove UNDRAFT from argsText
					argsText = argsText.Substring(7).Trim();

					// Move to UNKEYWORD <flag>
					searchKeys.Add("UNKEYWORD","DRAFT");
				}
				// UNFLAGGED
				else if(argsText.ToUpper().StartsWith("UNFLAGGED")){
					// Remove UNFLAGGED from argsText
					argsText = argsText.Substring(9).Trim();

					// Move to UNKEYWORD <flag>
					searchKeys.Add("UNKEYWORD","FLAGGED");
				}
				// UNKEYWORD <flag>
				else if(argsText.ToUpper().StartsWith("UNKEYWORD")){
				}
				// UNSEEN
				else if(argsText.ToUpper().StartsWith("UNSEEN")){
					// Remove UNSEEN from argsText
					argsText = argsText.Substring(6).Trim();

					// Move to UNKEYWORD <flag>
					searchKeys.Add("UNKEYWORD","SEEN");
				}
				// This is is unkown search key
				else{
					m_pSocket.SendLine(cmdTag + " NO Invalid search key " + argsText.Split(' ')[0]);
					return;
				}
			}

			// Just loop messages headers or full messages (depends on search type)
			string searchResponse = "* SEARCH";
			for(int i=0;i<m_Messages.Count;i++){
				IMAP_Message msg = m_Messages[i];

				byte[]     msgData     = null;
				
				Message_EventArgs eArgs = m_pServer.OnGetMessage(this,msg,false);
				msgData = eArgs.MessageData;

				MimeParser parser = new MimeParser(msgData);
				
				bool matches = true;
				foreach(DictionaryEntry ent in searchKeys){
					matches = _SearchHelper.MatchSearchKey(ent.Key.ToString(),ent.Value,msg,parser);
					if(!matches){
						matches = false;
						break;
					}
				}
				if(!matches){
					continue;
				}

				// If we reached so far, then message matches search criteria
				if(uidSearch){
					searchResponse += " " + msg.MessageUID.ToString();
				}
				else{
					searchResponse += " " + msg.MessageNo.ToString();
				}
			}
			
			searchResponse += "\r\n";
			searchResponse += cmdTag + " OK SEARCH completed\r\n";

			m_pSocket.SendData(searchResponse);
		}
        /// <summary>
        /// Gets top lines of message.
        /// </summary>
        /// <param name="nr">Message number which top lines to get.</param>
        /// <param name="nLines">Number of lines to get.</param>
        public System.Net.Mail.MailMessage GetTopOfMessage(int nr, int nLines)
        {
            if (!m_Connected)
            {
                throw new Exception("You must connect first !");
            }

            if (!m_Authenticated)
            {
                throw new Exception("You must authenticate first !");
            }


            streamHelper.Write("TOP " + nr.ToString() + " " + nLines.ToString() + "\r\n");

            // Read first line of reply, check if it's ok
            string line = streamHelper.ReadToEnd();
            if (line.StartsWith("+OK"))
            {
                MimeParser parser = new MimeParser(Core.DoPeriodHandling(socketStream, false, false).ToArray());

                return parser.ToMailMessage();
            }
            else
            {
                throw new Exception("Server returned:" + line);
            }
        }
		/// <summary>
		/// Constructs FETCH BODY and BODYSTRUCTURE response.
		/// </summary>
		/// <param name="parser"></param>
		/// <param name="bodystructure"></param>
		/// <returns></returns>
		public static string ConstructBodyStructure(MimeParser parser,bool bodystructure)
		{
			/* Rfc 3501 7.4.2 BODYSTRUCTURE

				For example, a simple text message of 48 lines and 2279 octets
				can have a body structure of: ("TEXT" "PLAIN" ("CHARSET"
				"US-ASCII") NIL NIL "7BIT" 2279 48)
				
				For example, a two part message consisting of a text and a
				BASE64-encoded text attachment can have a body structure of:
				(("TEXT" "PLAIN" ("CHARSET" "US-ASCII") NIL NIL "7BIT" 1152
				23)("TEXT" "PLAIN" ("CHARSET" "US-ASCII" "NAME" "cc.diff")
				"<*****@*****.**>" "Compiler diff"
				"BASE64" 4554 73) "MIXED")


				// Basic fields for multipart
				(nestedMimeEntries) conentSubType
			
				// Extention data for multipart
				(conentTypeSubFields) contentDisposition contentLanguage [contentLocation]
				
				contentDisposition  - ("disposition" {(subFileds) or NIL}) or NIL
							
				contentType         - 'TEXT'
				conentSubType       - 'PLAIN'
				conentTypeSubFields - '("CHARSET" "iso-8859-1" ...)'
				contentID           - Content-ID field
				contentDescription  - Content-Description field
				contentEncoding     - 'quoted-printable'
				contentSize         - mimeEntry NOT ENCODED data size
				[envelope]          - NOTE: included only if contentType = "message" !!!
				[contentLines]      - number of content lines. NOTE: included only if contentType = "text" !!!
				
				// Basic fields for non-multipart
				contentType conentSubType (conentTypeSubFields) contentID contentDescription contentEncoding contentSize contentLines

				// Extention data for non-multipart
				contentDataMd5 contentDisposition contentLanguage [conentLocation]
			
			
				body language
					A string or parenthesized list giving the body language
					value as defined in [LANGUAGE-TAGS].

				body location
					A string list giving the body content URI as defined in	[LOCATION].
				
			*/
						
			string str = "";

			if(bodystructure){
				str += "BODYSTRUCTURE ";
			}
			else{
				str += "BODY ";
			}
            
			if(parser.ContentType.ToLower().IndexOf("multipart") > -1){
				str += "(";
			}

			str += ConstructPart(parser.MimeEntries,bodystructure);
			
			if(parser.ContentType.ToLower().IndexOf("multipart") > -1){
				// conentSubType
				if(parser.ContentType.Split('/').Length == 2){
					str += " \"" + parser.ContentType.Split('/')[1].Replace(";","") + "\"";					
				}
				else{
					str += " NIL";
				}

				// Need to add extended fields
				if(bodystructure){
					str += " ";

					// conentTypeSubFields
					string longContentType = MimeParser.ParseHeaderField("Content-Type:",parser.Headers);
					if(longContentType.IndexOf(";") > -1){
						str += "(";
						string[] fields = longContentType.Split(';');
						for(int i=1;i<fields.Length;i++){
							string[] nameValue = fields[i].Replace("\"","").Trim().Split(new char[]{'='},2);

							str += "\"" + nameValue[0] + "\" \"" + nameValue[1] + "\"";

							if(i < fields.Length - 1){
								str += " ";
							}
						}
						str += ") ";
					}

					// contentDisposition
					str += "NIL ";

					// contentLanguage
					str += "NIL";
				}
				
				str += ")";
			}	

			return str;
		}
		/// <summary>
		/// Construct FETCH ENVELOPE response.
		/// </summary>
		/// <param name="parser"></param>
		/// <returns></returns>
		public static string ConstructEnvelope(MimeParser parser)
		{
			/* Rfc 3501 7.4.2
				ENVELOPE
				A parenthesized list that describes the envelope structure of a
				message.  This is computed by the server by parsing the
				[RFC-2822] header into the component parts, defaulting various
				fields as necessary.

				The fields of the envelope structure are in the following
				order: date, subject, from, sender, reply-to, to, cc, bcc,
				in-reply-to, and message-id.  The date, subject, in-reply-to,
				and message-id fields are strings.  The from, sender, reply-to,
				to, cc, and bcc fields are parenthesized lists of address
				structures.

				An address structure is a parenthesized list that describes an
				electronic mail address.  The fields of an address structure
				are in the following order: personal name, [SMTP]
				at-domain-list (source route), mailbox name, and host name.

				[RFC-2822] group syntax is indicated by a special form of
				address structure in which the host name field is NIL.  If the
				mailbox name field is also NIL, this is an end of group marker
				(semi-colon in RFC 822 syntax).  If the mailbox name field is
				non-NIL, this is a start of group marker, and the mailbox name
				field holds the group name phrase.

				If the Date, Subject, In-Reply-To, and Message-ID header lines
				are absent in the [RFC-2822] header, the corresponding member
				of the envelope is NIL; if these header lines are present but
				empty the corresponding member of the envelope is the empty
				string.
			*/
			// ((sender))
			// ENVELOPE ("date" "subject" from sender reply-to to cc bcc in-reply-to "messageID")
			
			string envelope = "ENVELOPE (";
			
			// date
			envelope += "\"" + parser.MessageDate.ToString("r",System.Globalization.DateTimeFormatInfo.InvariantInfo) + "\" ";
			
			// subject
			envelope += "\"" + Escape(parser.Subject) + "\" ";

			// from
			// ToDo: May be multiple senders
			InfoControl.Net.Mail.Mime.eAddress adr = new InfoControl.Net.Mail.Mime.eAddress(parser.From);
			envelope += "((\"" + Escape(adr.Name) + "\" NIL \"" + Escape(adr.Mailbox) + "\" \"" + Escape(adr.Domain) + "\")) ";

			// sender
			// ToDo: May be multiple senders
			envelope += "((\"" + Escape(adr.Name) + "\" NIL \"" + Escape(adr.Mailbox) + "\" \"" + Escape(adr.Domain) + "\")) ";

			// reply-to
			string replyTo = MimeParser.ParseHeaderField("reply-to:",parser.Headers);
			if(replyTo.Length > 0){
				envelope += "(";
				foreach(string recipient in replyTo.Split(';')){
					InfoControl.Net.Mail.Mime.eAddress adrTo = new InfoControl.Net.Mail.Mime.eAddress(recipient);
					envelope += "(\"" + Escape(adrTo.Name) + "\" NIL \"" + Escape(adrTo.Mailbox) + "\" \"" + Escape(adrTo.Domain) + "\") ";
				}
				envelope = envelope.TrimEnd();
				envelope += ") ";
			}
			else{
				envelope += "NIL ";				
			}

			// to
			string[] to = parser.To;
			envelope += "(";
			foreach(string recipient in to){
				InfoControl.Net.Mail.Mime.eAddress adrTo = new InfoControl.Net.Mail.Mime.eAddress(recipient);
				envelope += "(\"" + Escape(adrTo.Name) + "\" NIL \"" + Escape(adrTo.Mailbox) + "\" \"" + Escape(adrTo.Domain) + "\") ";
			}
			envelope = envelope.TrimEnd();
			envelope += ") ";

			// cc
			string cc = MimeParser.ParseHeaderField("CC:",parser.Headers);
			if(cc.Length > 0){
				envelope += "(";
				foreach(string recipient in cc.Split(';')){
					InfoControl.Net.Mail.Mime.eAddress adrTo = new InfoControl.Net.Mail.Mime.eAddress(recipient);
					envelope += "(\"" + Escape(adrTo.Name) + "\" NIL \"" + Escape(adrTo.Mailbox) + "\" \"" + Escape(adrTo.Domain) + "\") ";
				}
				envelope = envelope.TrimEnd();
				envelope += ") ";
			}
			else{
				envelope += "NIL ";				
			}

			// bcc
			string bcc = MimeParser.ParseHeaderField("BCC:",parser.Headers);
			if(bcc.Length > 0){
				envelope += "(";
				foreach(string recipient in bcc.Split(';')){
					InfoControl.Net.Mail.Mime.eAddress adrTo = new InfoControl.Net.Mail.Mime.eAddress(recipient);
					envelope += "(\"" + Escape(adrTo.Name) + "\" NIL \"" + Escape(adrTo.Mailbox) + "\" \"" + Escape(adrTo.Domain) + "\") ";
				}
				envelope = envelope.TrimEnd();
				envelope += ") ";
			}
			else{
				envelope += "NIL ";				
			}

			// in-reply-to
			string inReplyTo = MimeParser.ParseHeaderField("in-reply-to:",parser.Headers);
			if(inReplyTo.Length > 0){
				envelope += "\"" + Escape(inReplyTo) + "\" ";
			}
			else{
				envelope += "NIL ";
			}

			// message-id
			if(parser.MessageID.Length > 0){
				envelope += "\"" + Escape(parser.MessageID) + "\"";
			}
			else{
				envelope += "NIL";
			}

			envelope += ")";

			return envelope;
		}
		/// <summary>
		/// Returns requested mime entry data.
		/// </summary>
		/// <param name="parser"></param>
		/// <param name="mimeEntryNo"></param>
		/// <returns>Returns requested mime entry data or NULL if requested entri doesn't exist.</returns>
		public static byte[] ParseMimeEntry(MimeParser parser,string mimeEntryNo)
		{
			MimeEntry mEntry = null;
			string[] parts = mimeEntryNo.Split('.');
			foreach(string part in parts){
				int mEntryNo = Convert.ToInt32(part);				
				if(mEntry == null){					
					if(mEntryNo > 0 && mEntryNo <= parser.MimeEntries.Count){
						mEntry = ((MimeEntry)parser.MimeEntries[mEntryNo - 1]);						
					}
					else{
						return null;
					}
				}
				else{				
					if(mEntryNo > 0 && mEntryNo <= mEntry.MimeEntries.Count){
						mEntry = ((MimeEntry)mEntry.MimeEntries[mEntryNo - 1]);
					}
					else{
						return null;
					}
				}
			}

			if(mEntry != null){
				return mEntry.DataNonDecoded;
			}
			else{
				return null;
			}
		}