/// <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"); } }
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); } }
/// <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); } }
/// <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"); } }
/// <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); } }
/// <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; }
/// <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 += "\"" + 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; }
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; }