Esempio n. 1
0
        /// <summary>
        /// Parse encoding.
        /// </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");
            }
        }
Esempio n. 2
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>
        /// Default constructor.
        /// </summary>
        /// <param name="mimeEntry"></param>
        /// <param name="mime"></param>
        public MimeEntry(byte[] mimeEntry,MimeParser mime)
        {
            m_Headers         = ParseHeaders(mimeEntry);
            m_ConentType      = ParseContentType(m_Headers);

            // != multipart content (must be nested)
            if(m_ConentType.ToLower().IndexOf("multipart") == -1){
                m_CharSet         = ParseCharSet(m_Headers);
                m_ContentEncoding = ParseEncoding(m_Headers);
                m_FileName        = ParseFileName(m_Headers);
                m_Disposition     = ParseContentDisposition(m_Headers);

                m_Data = ParseData(System.Text.Encoding.Default.GetString(mimeEntry).Substring(m_Headers.Length + 2)); // 2-<CRLF>
            }
            else{ // Get nested entries
                string boundaryID = mime.ParseBoundaryID(m_Headers);
                m_Entries = mime.ParseEntries(new MemoryStream(mimeEntry),m_Headers.Length,boundaryID);
            }
        }
Esempio n. 4
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);

            // != 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);
            }
            else{ // multipart content, get nested entries
                string boundaryID = MimeParser.ParseHeaderFiledSubField("Content-Type:","boundary",m_Headers);
                m_Entries = mime.ParseEntries(entryStrm,(int)entryStrm.Position,boundaryID);
            }
        }
Esempio n. 5
0
        /// <summary>
        /// Parse charset.
        /// </summary>
        /// <param name="headers"></param>
        /// <returns></returns>
        private string ParseCharSet(string headers)
        {
            string charset = MimeParser.ParseHeaderFiledSubField("Content-Type:", "charset", headers);

            if (charset.Length > 0)
            {
                try{
                    Encoding.GetEncoding(charset);

                    return(charset);
                }
                catch {
                    return("ascii");
                }
            }
            // If no charset, consider it as ascii
            else
            {
                return("ascii");
            }
        }
Esempio n. 6
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);

            // != 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);
            }
            else              // multipart content, get nested entries
            {
                string boundaryID = MimeParser.ParseHeaderFiledSubField("Content-Type:", "boundary", m_Headers);
                m_Entries = mime.ParseEntries(entryStrm, (int)entryStrm.Position, boundaryID);
            }
        }
Esempio n. 7
0
        /// <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,int mimeEntryNo)
        {
            if(mimeEntryNo > 0 && mimeEntryNo <= parser.MimeEntries.Count){
                return ((MimeEntry)parser.MimeEntries[mimeEntryNo - 1]).Data;
            }

            return null;
        }
Esempio n. 8
0
        /// <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;
        }
Esempio n. 9
0
        /// <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 += "\"" + parser.Subject + "\" ";

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

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

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

            // to
            string[] to = parser.To;
            envelope += "(";
            foreach(string recipient in to){
                LumiSoft.Net.Mime.Parser.eAddress adrTo = new LumiSoft.Net.Mime.Parser.eAddress(recipient);
                envelope += "(\"" + adrTo.Name + "\" NIL \"" + adrTo.Mailbox + "\" \"" + 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(';')){
                    LumiSoft.Net.Mime.Parser.eAddress adrTo = new LumiSoft.Net.Mime.Parser.eAddress(recipient);
                    envelope += "(\"" + adrTo.Name + "\" NIL \"" + adrTo.Mailbox + "\" \"" + 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(';')){
                    LumiSoft.Net.Mime.Parser.eAddress adrTo = new LumiSoft.Net.Mime.Parser.eAddress(recipient);
                    envelope += "(\"" + adrTo.Name + "\" NIL \"" + adrTo.Mailbox + "\" \"" + 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 += "\"" + inReplyTo + "\"";
            }
            else{
                envelope += "NIL ";
            }

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

            envelope += ")";

            return envelope;
        }
Esempio n. 10
0
        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){
                SendData(cmdTag + " NO Authenticate first !\r\n");
                return;
            }
            if(m_SelectedMailbox.Length == 0){
                SendData(cmdTag + " NO Select mailbox first !\r\n");
                return;
            }

            #region Parse parameters

            string[] args = ParseParams(argsText);
            if(args.Length != 2){
                SendData(cmdTag + " BAD Invalid arguments\r\n");
                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();

                // 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

                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]",Convert.ToInt32(fArgs[0]));
                    }
                    else{
                        switch(fArgs[0].ToUpper())
                        {
                            case "TEXT":
                                fullMsgNeeded = true;
                                items.Add("BODY.PEEK[TEXT]","");
                                break;

                            case "HEADER":
                                headersNeeded = true;
                                items.Add("BODY.PEEK[HEADER]","");
                                break;

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

                                break;

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

                            default:
                                SendData(cmdTag + " BAD Invalid fetch-items argument\r\n");
                                return;
                        }
                    }
                }
                else{
                    fullMsgNeeded = true;
                    items.Add("BODY.PEEK[]","");
                }

                fetchItems = fetchItems.Replace(fetchItems.Substring(fetchItems.IndexOf("BODY.PEEK["),fetchItems.IndexOf("]",fetchItems.IndexOf("BODY.PEEK[")) - fetchItems.IndexOf("BODY.PEEK[") + 1),"");
            }
            if(fetchItems.IndexOf("BODY[") > -1){
                int start = fetchItems.IndexOf("BODY[") + 5;
                string val = fetchItems.Substring(start,fetchItems.IndexOf("]",start) - start).ToUpper().Trim();

                // 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

                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]",Convert.ToInt32(fArgs[0]));
                    }
                    else{
                        switch(fArgs[0].ToUpper())
                        {
                            case "TEXT":
                                fullMsgNeeded = true;
                                items.Add("BODY[TEXT]","");
                                break;

                            case "HEADER":
                                headersNeeded = true;
                                items.Add("BODY[HEADER]","");
                                break;

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

                                break;

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

                            default:
                                SendData(cmdTag + " BAD Invalid fetch-items argument\r\n");
                                return;
                        }
                    }
                }
                else{
                    fullMsgNeeded = true;
                    items.Add("BODY[]","");
                }

                fetchItems = fetchItems.Replace(fetchItems.Substring(fetchItems.IndexOf("BODY["),fetchItems.IndexOf("]",fetchItems.IndexOf("BODY[")) - fetchItems.IndexOf("BODY[") + 1),"");
            }
            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){
                SendData(cmdTag + " BAD Invalid fetch-items argument\r\n");
                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){
                            msgHeadData = Encoding.Default.GetBytes(LumiSoft.Net.Mime.MimeParser.ParseHeaders(new MemoryStream(msgData)));
                        }

                        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){
                        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
                                buf = Encoding.ASCII.GetBytes("BODY[] {" + msgData.Length + "}\r\n");
                                reply.Write(buf,0,buf.Length);
                                reply.Write(msgData,0,msgData.Length);
                                break;

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

                            case "BODY.PEEK[HEADER.FIELDS]":
                                // BODY[HEADER.FIELDS ()] {size}
                                // msg header data
                                byte[] fData = Encoding.ASCII.GetBytes(FetchHelper.ParseHeaderFields(items[fetchItem].ToString(),msgHeadData));

                                buf = Encoding.ASCII.GetBytes("BODY[HEADER.FIELDS (" + items[fetchItem].ToString() + ")] {" + fData.Length + "}\r\n");
                                reply.Write(buf,0,buf.Length);

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

                            case "BODY.PEEK[HEADER.FIELDS.NOT]":
                                // BODY[HEADER.FIELDS.NOT ()] {size}
                                // msg header data
                                byte[] f1Data = Encoding.ASCII.GetBytes(FetchHelper.ParseHeaderFieldsNot(items[fetchItem].ToString(),msgHeadData));

                                buf = Encoding.ASCII.GetBytes("BODY[HEADER.FIELDS.NOT (" + items[fetchItem].ToString() + ")] {" + f1Data.Length + "}\r\n");
                                reply.Write(buf,0,buf.Length);

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

                            case "BODY.PEEK[TEXT]":
                                // BODY[TEXT] {size}
                                // msg text
                                byte[] f111Data = Encoding.ASCII.GetBytes(parser.BodyText);

                                buf = Encoding.ASCII.GetBytes("BODY[TEXT] {" + f111Data.Length + "}\r\n");
                                reply.Write(buf,0,buf.Length);

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

                            case "BODY.PEEK[NUMBER]":
                                // BODY[no.] {size}
                                // mime part
                                byte[] b1113Data = FetchHelper.ParseMimeEntry(parser,(int)items[fetchItem]);

                                if(b1113Data != null){
                                    buf = Encoding.ASCII.GetBytes("BODY[" + items[fetchItem].ToString() + "] {" + b1113Data.Length + "}\r\n");
                                    reply.Write(buf,0,buf.Length);

                                    reply.Write(b1113Data,0,b1113Data.Length);
                                }
                                else{
                                    // BODY[no.] NIL
                                    buf = Encoding.ASCII.GetBytes("BODY[" + items[fetchItem].ToString() + "] 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
                                buf = Encoding.ASCII.GetBytes("BODY[] {" + msgData.Length + "}\r\n");
                                reply.Write(buf,0,buf.Length);
                                reply.Write(msgData,0,msgData.Length);
                                break;

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

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

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

                                // BODY[HEADER.FIELDS ()] {size}
                                // msg header data
                                byte[] bData = Encoding.ASCII.GetBytes(FetchHelper.ParseHeaderFields(items[fetchItem].ToString(),msgHeadData));

                                buf = Encoding.ASCII.GetBytes("BODY[HEADER.FIELDS (" + items[fetchItem].ToString() + ")] {" + bData.Length + "}\r\n");
                                reply.Write(buf,0,buf.Length);

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

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

                                // BODY[HEADER.FIELDS.NOT ()] {size}
                                // msg header data
                                byte[] f2Data = Encoding.ASCII.GetBytes(FetchHelper.ParseHeaderFieldsNot(items[fetchItem].ToString(),msgHeadData));

                                buf = Encoding.ASCII.GetBytes("BODY[HEADER.FIELDS.NOT (" + items[fetchItem].ToString() + ")] {" + f2Data.Length + "}\r\n");
                                reply.Write(buf,0,buf.Length);

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

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

                                // BODY[TEXT] {size}
                                // msg text
                                byte[] f1111Data = Encoding.ASCII.GetBytes(parser.BodyText);

                                buf = Encoding.ASCII.GetBytes("BODY[TEXT] {" + f1111Data.Length + "}\r\n");
                                reply.Write(buf,0,buf.Length);

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

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

                                // BODY[no.] {size}
                                // mime part
                                byte[] b113Data = FetchHelper.ParseMimeEntry(parser,(int)items[fetchItem]);

                                if(b113Data != null){
                                    buf = Encoding.ASCII.GetBytes("BODY[" + items[fetchItem].ToString() + "] {" + b113Data.Length + "}\r\n");
                                    reply.Write(buf,0,buf.Length);

                                    reply.Write(b113Data,0,b113Data.Length);
                                }
                                else{
                                    // BODY[no.] NIL
                                    buf = Encoding.ASCII.GetBytes("BODY[" + items[fetchItem].ToString() + "] 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;
                    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);
                    }
                }

            }

            SendData(cmdTag + " OK FETCH completed\r\n");
        }
        /// <summary>
        /// Parses body text from message
        /// </summary>
        /// <param name="data"></param>
        /// <returns></returns>
        public static string ParseText(byte[] data)
        {
            MimeParser p = new MimeParser(data);

            return p.BodyText;
        }
        /// <summary>
        /// Returns requested mime entry data.
        /// </summary>
        /// <param name="message"></param>
        /// <param name="mimeEntryNo"></param>
        /// <returns>Returns requested mime entry data or NULL if requested entri doesn't exist.</returns>
        public static byte[] ParseMimeEntry(byte[] message,int mimeEntryNo)
        {
            MimeParser p = new MimeParser(message);

            if(mimeEntryNo > 0 && mimeEntryNo <= p.MimeEntries.Count){
                return ((MimeEntry)p.MimeEntries[mimeEntryNo - 1]).Data;
            }

            return null;
        }
        /// <summary>
        /// Filters message.
        /// </summary>
        /// <param name="messageStream">Message stream which to filter.</param>
        /// <param name="filteredStream">Filtered stream.</param>
        /// <param name="sender">Senders email address.</param>
        /// <param name="recipients">Recipients email addresses.</param>
        /// <param name="api">Access to server API.</param>
        public FilterResult Filter(MemoryStream messageStream,out MemoryStream filteredStream,string sender,string[] recipients,ServerAPI api)
        {
            messageStream.Position = 0;
            filteredStream = messageStream; // we don't change message content, just return same stream

            //--- Load data -----------------------
            DataSet ds = new DataSet();
            DataTable dt = ds.Tables.Add("KewWords");
            dt.Columns.Add("Cost",typeof(int));
            dt.Columns.Add("KeyWord");

            dt = ds.Tables.Add("ContentMd5");
            dt.Columns.Add("Description");
            dt.Columns.Add("EntryMd5Value");
            ds.ReadXml(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) + "\\lsSpam_db.xml");

            //--- Do mime parts data md5 hash compare ----------------
            ArrayList entries = new ArrayList();
            MimeParser parser = new MimeParser(messageStream.ToArray());
            GetEntries(parser.MimeEntries,entries);

            System.Security.Cryptography.MD5 md5 = System.Security.Cryptography.MD5.Create();
            foreach(MimeEntry ent in entries){
                if(ent.Data != null){
                    string md5Hash = Convert.ToBase64String(md5.ComputeHash(ent.Data));

                    foreach(DataRow dr in ds.Tables["ContentMd5"].Rows){
                        // Message contains blocked content(attachment,...)
                        if(dr["EntryMd5Value"].ToString() == md5Hash){
                            WriteFilterLog(DateTime.Now.ToString() + " From:" + sender + " Subject:\"" + parser.Subject + "\"  Contained blocked content\r\n");
                            return FilterResult.DontStore;
                        }
                    }
                }
            }

            byte[] topLines = new byte[2000];
            if(messageStream.Length < 2000){
                topLines = new byte[messageStream.Length];
            }
            messageStream.Read(topLines,0,topLines.Length);

            string lines = System.Text.Encoding.ASCII.GetString(topLines).ToLower();

            //--- Try spam keywords -----------
            int totalCost = 0;
            string keyWords = "";
            DataView dv = ds.Tables["KewWords"].DefaultView;
            dv.Sort = "Cost DESC";
            foreach(DataRowView drV in dv){
                if(lines.IndexOf(drV.Row["KeyWord"].ToString().ToLower()) > -1){
                    totalCost += Convert.ToInt32(drV.Row["Cost"]);

                    keyWords += drV.Row["KeyWord"].ToString() + " cost:" + drV.Row["Cost"].ToString() + " ";

                    // Check that total cost isn't exceeded
                    if(totalCost > 99){
                        //--- Send blocked note to sender
                        MimeConstructor m = new MimeConstructor();
                        m.Body = "Message was blocked by server and considered as SPAM !!!\n\nCaused by keywords: " + keyWords + "\n\nMaximum total cost is 100 !";
                        m.From = "postmaster";
                        m.To   = new string[]{sender};
                        m.Attachments.Add(new Attachment("data.eml",messageStream.ToArray()));

                        using(MemoryStream msg = new MemoryStream(System.Text.Encoding.ASCII.GetBytes(m.ConstructMime()))){
                            api.StoreMessage("","",msg,sender,"",true,DateTime.Now,0);
                        }

                        WriteFilterLog(DateTime.Now.ToString() + " From:" + sender + " Blocked KeyWords: " + keyWords + "\r\n");

                        return FilterResult.DontStore;
                    }
                }
            }
            //---------------------------------
            return FilterResult.Store;
        }