/// <summary> /// Creates a new empty instance of the MailMessage class from a string /// containing a raw mail message header. /// </summary> /// <param name="text">A string containing the mail header to create /// the MailMessage instance from.</param> /// <returns>A MailMessage instance with initialized Header fields but /// no content</returns> internal static IMailMessage FromHeader(string text) { NameValueCollection header = ParseMailHeader(text); IMailMessage m = new IMailMessage(); foreach (string key in header) { string value = header.GetValues(key)[0]; try { m.Headers.Add(key, value); } catch { // HeaderCollection throws an exception if adding an empty string as // value, which can happen, if reading a mail message with an empty // subject. // Also spammers often forge headers, so just fall through and ignore. } } Match ma = Regex.Match(header["Subject"] ?? "", @"=\?([A-Za-z0-9\-]+)"); if (ma.Success) { /* encoded-word subject */ m.SubjectEncoding = Util.GetEncoding(ma.Groups[1].Value); string decoded = Util.DecodeWords(header["Subject"]); if(!string.IsNullOrEmpty(decoded)) decoded = Regex.Replace(decoded, @"\r", String.Empty); if(!string.IsNullOrEmpty(decoded)) decoded = Regex.Replace(decoded, @"\n", String.Empty); m.Subject = decoded; } else { m.SubjectEncoding = Encoding.ASCII; string subject = header["Subject"]; if (!string.IsNullOrEmpty(subject)) subject = Regex.Replace(subject, @"\r", String.Empty); if (!string.IsNullOrEmpty(subject)) subject = Regex.Replace(subject, @"\n", String.Empty); m.Subject = subject; } m.Priority = ParsePriority(header["Priority"]); SetAddressFields(m, header); return m; }
/// <summary> /// Sets the address fields (From, To, CC, etc.) of a MailMessage /// object using the specified mail message header information. /// </summary> /// <param name="m">The MailMessage instance to operate on</param> /// <param name="header">A collection of mail and MIME headers</param> private static void SetAddressFields(IMailMessage m, NameValueCollection header) { string[] addr; if (header["To"] != null) { addr = ParseAddressList(header["To"]); foreach (string s in addr) { try { m.To.Add(Util.DecodeWords(s)); } catch (Exception e) { } } } if (header["Cc"] != null) { addr = ParseAddressList(header["Cc"]); foreach (string s in addr) { try { m.CC.Add(Util.DecodeWords(s)); } catch { } } } if (header["Bcc"] != null) { addr = ParseAddressList(header["Bcc"]); foreach (string s in addr) { try { m.Bcc.Add(Util.DecodeWords(s)); } catch { } } } if (header["From"] != null) { addr = ParseAddressList(header["From"]); if (addr.Length > 0) { try { m.From = new MailAddress(Util.DecodeWords(addr[0])); } catch { } } } if (header["Sender"] != null) { addr = ParseAddressList(header["Sender"]); if(addr.Length > 0) { try { m.Sender = new MailAddress(Util.DecodeWords(addr[0])); } catch { } } } if (header["Reply-to"] != null) { addr = ParseAddressList(header["Reply-to"]); foreach (string s in addr) { try { m.ReplyToList.Add(Util.DecodeWords(s)); } catch { } } } }
/// <summary> /// Stores the specified mail messages on the IMAP server. /// </summary> /// <param name="messages">An array of mail messages to store on the server.</param> /// <param name="seen">Set this to true to set the \Seen flag for each message /// on the server.</param> /// <param name="mailbox">The mailbox the messages will be stored in. If this /// parameter is omitted, the value of the DefaultMailbox property is used to /// determine the mailbox to store the messages in.</param> /// <exception cref="NotAuthenticatedException">Thrown if the method was called /// in a non-authenticated state, i.e. before logging into the server with /// valid credentials.</exception> /// <exception cref="BadServerResponseException">Thrown if the mail messages could /// not be stored. The message property of the exception contains the error message /// returned by the server.</exception> /// <returns>An array containing the unique identifiers (UID) of the stored /// messages.</returns> /// <remarks>A unique identifier (UID) is a 32-bit value assigned to each /// message which uniquely identifies the message within a mailbox. No two /// messages in a mailbox share the the same UID.</remarks> /// <seealso cref="StoreMessage"/> public List<long> StoreMessages(IMailMessage[] messages, bool seen = false, string mailbox = null) { List<long> list = new List<long>(); foreach (IMailMessage m in messages) list.Add(StoreMessage(m, seen, mailbox)); return list; }
/// <summary> /// Returns only the plain TEXT data of the messages. All HTML is stripped out. All replies stripped out according to the > google indents for replies. All forwarded areas stripped out also based on google. /// </summary> /// <param name="uids">The list of uids to get the messages text for</param> /// <param name="seen">If false then the \Seen flag is not set</param> /// <param name="mailbox">The mailbox to get the messages from</param> /// <returns>Returns a dictionary where the UIDs are the keys and the text data is the value</returns> public Dictionary<long, string> GetPreviewText(long[] uids) { if (!Authed) throw new NotAuthenticatedException(); lock (sequenceLock) { if (this.selectedMailbox == null) throw new InvalidOperationException("No mailbox or folder currently selected."); string uidRange = Util.BuildUIDRange(uids); Dictionary<long, string> results = new Dictionary<long, string>(); /* Retrieve and parse the body structure of the mail message */ Dictionary<long, string> structures = GetBodystructure(uids); try { foreach (long uid in structures.Keys) { Bodypart[] parts = Bodystructure.Parse(structures[uid]); IMailMessage message = new IMailMessage(); string htmlContent = ""; foreach (Bodypart part in parts) { if (part.Subtype.ToLower() == "plain") { string content = GetBodypart(uid, part.PartNumber, false); content = Util.StripReplies(Util.DecodeWords(part, content)); htmlContent = Util.StripSpecialCharacters(Util.StripCSSDeclerations(Regex.Replace(System.Net.WebUtility.HtmlDecode(Regex.Replace(Util.StripTagsCharArray(content), " ", " ")), @"\s{2,}", " "))).Trim(); break; } else if (part.Subtype.ToLower() == "html") { string content = GetBodypart(uid, part.PartNumber, false); HtmlAgilityPack.HtmlDocument document = new HtmlDocument(); content = Util.DecodeWords(part, content); document.LoadHtml(content); if (document.DocumentNode.SelectSingleNode("//body") != null) content = document.DocumentNode.SelectSingleNode("//body").InnerText; else content = document.DocumentNode.InnerText; htmlContent = Util.StripSpecialCharacters(Util.StripCSSDeclerations(Regex.Replace(System.Net.WebUtility.HtmlDecode(Regex.Replace(Util.StripTagsCharArray(content), " ", " ")), @"\s{2,}", " "))).Trim(); break; } } results.Add(uid, htmlContent); } } catch (FormatException) { throw new BadServerResponseException("Server returned erroneous " + "body structure."); } catch (Exception e) { return results; } return results; } }
/// <summary> /// Stores the specified mail message on the IMAP server. /// </summary> /// <param name="message">The mail message to store on the server.</param> /// <param name="seen">Set this to true to set the \Seen flag for the message /// on the server.</param> /// <param name="mailbox">The mailbox the message will be stored in. If this /// parameter is omitted, the value of the DefaultMailbox property is used to /// determine the mailbox to store the message in.</param> /// <exception cref="NotAuthenticatedException">Thrown if the method was called /// in a non-authenticated state, i.e. before logging into the server with /// valid credentials.</exception> /// <exception cref="BadServerResponseException">Thrown if the mail message could /// not be stored. The message property of the exception contains the error message /// returned by the server.</exception> /// <returns>The unique identifier (UID) of the stored message.</returns> /// <remarks>A unique identifier (UID) is a 32-bit value assigned to each /// message which uniquely identifies the message within a mailbox. No two /// messages in a mailbox share the the same UID.</remarks> /// <seealso cref="StoreMessages"/> /// <include file='Examples.xml' path='S22/Imap/ImapClient[@name="StoreMessage"]/*'/> public long StoreMessage(IMailMessage message, bool seen = false, string mailbox = null) { if (!Authed) throw new NotAuthenticatedException(); if (mailbox == null) mailbox = defaultMailbox; string mime822 = message.ToMIME822(); lock (sequenceLock) { string tag = GetTag(); string response = SendCommandGetResponse(tag + "APPEND " + mailbox.QuoteString() + (seen ? @" (\Seen)" : "") + " {" + mime822.Length + "}"); /* Server is required to send a continuation response to signal * we can go ahead with the actual message data */ if (!response.StartsWith("+")) throw new BadServerResponseException(response); response = SendCommandGetResponse(mime822); if (!IsResponseOK(response, tag)) throw new BadServerResponseException(response); return GetHighestUID(); } }
/// <summary> /// Gets just the message HTML or TEXT of the messages /// </summary> /// <param name="uids">The list of uids to get the html for</param> /// <param name="seen">If false then the \Seen flag is not set</param> /// <param name="mailbox">The mailbox to get messages from</param> /// <returns>Returns a dictionary where the UIDs are the keys and the message data is the value</returns> public Dictionary<long, string> GetMessageHTML(long[] uids) { if (!Authed) throw new NotAuthenticatedException(); lock (sequenceLock) { if (this.selectedMailbox == null) throw new InvalidOperationException("No mailbox or folder currently selected."); Dictionary<long, string> results = new Dictionary<long, string>(); /* Retrieve and parse the body structure of the mail message */ Dictionary<long, string> structures = GetBodystructure(uids); try { foreach (long uid in structures.Keys) { Bodypart[] parts = Bodystructure.Parse(structures[uid]); string htmlContent = ""; IMailMessage message = new IMailMessage(); foreach (Bodypart part in parts) { if (part.Subtype.ToLower() == "html") { /* fetch the content */ htmlContent = GetBodypart(uid, part.PartNumber, false); message.AddBodypart(part, Util.DecodeWords(part,htmlContent)); break; } } if (htmlContent != string.Empty) { results.Add(uid, message.Body); continue; } //Well we didn't find any html blocks so lets find just the plain text foreach (Bodypart part in parts) { if (part.Subtype.ToLower() == "plain") { /* fetch the content */ htmlContent = GetBodypart(uid, part.PartNumber, false); message.AddBodypart(part, Util.DecodeWords(part,htmlContent)); break; } } results.Add(uid, message.Body); } } catch (FormatException) { throw new BadServerResponseException("Server returned erroneous " + "body structure."); } return results; } }
/// <summary> /// Creates a nested multipart/alternative part which contains all entries /// from the AlternateViews collection of the specified MailMessage instance /// as well as the body part for the Body and BodyEncoding properties of the /// specified MailMessage instance. /// </summary> /// <param name="builder">The StringBuilder instance to append to.</param> /// <param name="m">The MailMessage instance whose AlternateView collection /// will be added to the nested multipart/alternative part.</param> /// <param name="header">The RFC822/MIME mail header to use for /// constructing the mail body.</param> /// <remarks>This is used if the MailMessage instance contains both alternative /// views and attachments. In this case the created RFC822/MIME mail message will /// contain nested body parts.</remarks> internal void AddNestedAlternative(StringBuilder builder, IMailMessage m, NameValueCollection header) { string boundary = GenerateContentBoundary(); builder.AppendLine("Content-Type: multipart/alternative; boundary=" + boundary); builder.AppendLine(); // Add the body parts to the nested multipart/alternative part builder.AppendLine("--" + boundary); AddBody(builder, m, header, true); foreach (AlternateView v in m.AlternateViews) { builder.AppendLine("--" + boundary); AddAttachment(builder, v); } builder.AppendLine("--" + boundary + "--"); }
/// <summary> /// Adds a body part to the specified Stringbuilder object composed from /// the Body and BodyEncoding properties of the MailMessage class. /// </summary> /// <param name="builder">The Stringbuilder to append the body part to.</param> /// <param name="m">The MailMessage instance to build the body part from.</param> /// <param name="header">The RFC822/MIME mail header to use for /// constructing the mail body.</param> /// <param name="addHeaders">Set to true to append body headers before /// adding the actual body part content.</param> internal void AddBody(StringBuilder builder, IMailMessage m, NameValueCollection header, bool addHeaders = false) { bool base64 = header["Content-Transfer-Encoding"] == "base64"; if (addHeaders) { string contentType = m.IsBodyHtml ? "text/html" : "text/plain"; if (m.BodyEncoding != null) contentType = contentType + "; charset=" + m.BodyEncoding.WebName; builder.AppendLine("Content-Type: " + contentType); if (m.Body != null && !m.Body.IsASCII()) { builder.AppendLine("Content-Transfer-Encoding: base64"); base64 = true; } builder.AppendLine(); } string body = m.Body; if (base64) { byte[] bytes = m.BodyEncoding.GetBytes(m.Body); body = Convert.ToBase64String(bytes); } StringReader reader = new StringReader(body); char[] line = new char[76]; int read; while ((read = reader.Read(line, 0, line.Length)) > 0) builder.AppendLine(new string(line, 0, read)); }
/// <summary> /// Builds an RFC822/MIME-compliant mail body from the specified /// MailMessage instance and returns it as a formatted string. /// </summary> /// <param name="m">The MailMessage instance to build the mail body /// from.</param> /// <param name="header">The RFC822/MIME mail header to use for /// constructing the mail body.</param> /// <returns>An RFC822/MIME-compliant mail body as a string. /// </returns> /// <remarks>According to RFC2822 each line of a mail message should /// at max be 78 characters in length excluding carriage return and /// newline characters. This method accounts for that and ensures /// line breaks are inserted to meet this requirement.</remarks> internal string BuildBody(IMailMessage m, NameValueCollection header) { StringBuilder builder = new StringBuilder(); bool multipart = header["Content-Type"].Contains("boundary"); // Just a regular RFC822 mail w/o any MIME parts if (!multipart) { AddBody(builder, m, header); return builder.ToString(); } Match match = Regex.Match(header["Content-Type"], @"boundary=(\w+)"); string boundary = match.Groups[1].Value; // Start boundary builder.AppendLine("--" + boundary); bool nestParts = m.AlternateViews.Count > 0 && m.Attachments.Count > 0; if (nestParts) { AddNestedAlternative(builder, m, header); builder.AppendLine("--" + boundary); AddNestedMixed(builder, m); } else { AddBody(builder, m, header, true); foreach (AlternateView v in m.AlternateViews) { builder.AppendLine("--" + boundary); AddAttachment(builder, v); } foreach (Attachment a in m.Attachments) { builder.AppendLine("--" + boundary); AddAttachment(builder, a); } } // End boundary builder.AppendLine("--" + boundary + "--"); return builder.ToString(); }
/// <summary> /// Builds a RFC822/MIME-compliant mail header from the specified /// MailMessage instance and returns it as a NameValueCollection. /// </summary> /// <param name="m">The MailMessage instance to build the header /// from.</param> /// <returns>A NameValueCollection representing the RFC822/MIME /// mail header fields.</returns> internal NameValueCollection BuildHeader(IMailMessage m) { string[] ignore = new string[] { "MIME-Version", "Date", "Subject", "From", "To", "Cc", "Bcc", "Content-Type", "Content-Transfer-Encoding", "Priority", "Reply-To", "X-Priority", "Importance", "Sender", "Message-Id" }; NameValueCollection header = new NameValueCollection() { { "MIME-Version", "1.0" }, { "Date", DateTime.Now.ToString("R") }, { "Priority", PriorityMap[m.Priority] }, { "Importance", ImportanceMap[m.Priority] } }; if (m.From == null) throw new InvalidOperationException("The From property must not be null"); header.Add("From", m.From.To822Address()); if (m.Subject != null) header.Add("Subject", m.Subject.IsASCII() ? m.Subject : QEncode(m.Subject)); foreach (MailAddress a in m.To) header.Add("To", a.To822Address()); foreach (MailAddress a in m.CC) header.Add("Cc", a.To822Address()); foreach (MailAddress a in m.Bcc) header.Add("Bcc", a.To822Address()); bool multipart = m.AlternateViews.Count > 0 || m.Attachments.Count > 0; if (!multipart) { string contentType = m.IsBodyHtml ? "text/html" : "text/plain"; if (m.BodyEncoding != null) contentType = contentType + "; charset=" + m.BodyEncoding.WebName; header.Add("Content-Type", contentType); if (m.Body != null && !m.Body.IsASCII()) header.Add("Content-Transfer-Encoding", "base64"); } else { string contentType = (m.Attachments.Count == 0 ? "multipart/alternative" : "multipart/mixed") + "; boundary=" + GenerateContentBoundary(); header.Add("Content-Type", contentType); } // Add any custom headers added by user foreach (string key in m.Headers) { if (ignore.Contains(key, StringComparer.OrdinalIgnoreCase)) continue; header.Add(key, m.Headers.GetValues(key)[0]); } return header; }