using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.IO; using System.Net; using System.Net.Mime; using MsgReader.Mime.Header; using MsgReader.Mime.Traverse; namespace MsgReader.Mime { /// /// This is the root of the email tree structure.
/// for a description about the structure.
///
/// A Message (this class) contains the headers of an email message such as: /// /// - To /// - From /// - Subject /// - Content-Type /// - Message-ID /// /// which are located in the property.
///
/// Use the property to find the actual content of the email message. ///
public class Message { #region Properties /// /// Returns the ID of the message when this is available in the /// (as specified in [RFC2822]). Null when not available /// public string Id { get { if (Headers == null) return null; return !string.IsNullOrEmpty(Headers.MessageId) ? Headers.MessageId : null; } } /// /// Headers of the Message. /// public MessageHeader Headers { get; } /// /// This is the body of the email Message.
///
/// If the body was parsed for this Message, this property will never be . ///
public MessagePart MessagePart { get; } /// /// This will return the first where the /// is set to "html/text". This will return when there is no "html/text" /// found. /// public MessagePart HtmlBody { get; } /// /// This will return the first where the /// is set to "text/plain". This will be when there is no "text/plain" /// found. /// public MessagePart TextBody { get; } /// /// This will return all the message parts that are flagged as /// . /// This will be when there are no message parts /// that are flagged as . /// public ReadOnlyCollection Attachments { get; } /// /// The raw content from which this message has been constructed.
/// These bytes can be persisted and later used to recreate the Message. ///
public byte[] RawMessage { get; } #endregion #region Constructors /// /// Convenience constructor for .
///
/// Creates a message from a byte array. The full message including its body is parsed. ///
/// The byte array which is the message contents to parse public Message(byte[] rawMessageContent) : this(rawMessageContent, true) {} /// /// Constructs a message from a byte array.
///
/// The headers are always parsed, but if is , the body is not parsed. ///
/// The byte array which is the message contents to parse /// /// if the body should be parsed, /// if only headers should be parsed out of the byte array /// public Message(byte[] rawMessageContent, bool parseBody) { RawMessage = rawMessageContent; // Find the headers and the body parts of the byte array HeaderExtractor.ExtractHeadersAndBody(rawMessageContent, out var headersTemp, out var body); // Set the Headers property Headers = headersTemp; // Should we also parse the body? if (parseBody) { // Parse the body into a MessagePart MessagePart = new MessagePart(body, Headers); var findBodyMessagePartWithMediaType = new FindBodyMessagePartWithMediaType(); // Searches for the first HTML body and mark this one as the HTML body of the E-mail HtmlBody = findBodyMessagePartWithMediaType.VisitMessage(this, "text/html"); if (HtmlBody != null) HtmlBody.IsHtmlBody = true; // Searches for the first TEXT body and mark this one as the TEXT body of the E-mail TextBody = findBodyMessagePartWithMediaType.VisitMessage(this, "text/plain"); if (TextBody != null) TextBody.IsTextBody = true; var attachments = new AttachmentFinder().VisitMessage(this); if (HtmlBody != null) { foreach (var attachment in attachments) { if (attachment.IsInline) continue; var htmlBody = HtmlBody.BodyEncoding.GetString(HtmlBody.Body); attachment.IsInline = htmlBody.Contains($"cid:{attachment.ContentId}"); } } if (attachments != null) Attachments = attachments.AsReadOnly(); } } #endregion #region GetEmailAddresses /// /// Returns the list of as a normal or html string /// /// A list with one or more objects /// When true the E-mail addresses are converted to hyperlinks /// Set this to true when the E-mail body format is html /// public string GetEmailAddresses(IEnumerable rfcMailAddresses, bool convertToHref, bool html) { var result = string.Empty; if (rfcMailAddresses == null) return result; foreach (var rfcMailAddress in rfcMailAddresses) { if (result != string.Empty) result += "; "; var emailAddress = string.Empty; var displayName = rfcMailAddress.DisplayName; if (rfcMailAddress.HasValidMailAddress) emailAddress = rfcMailAddress.Address; if (string.Equals(emailAddress, displayName, StringComparison.InvariantCultureIgnoreCase)) displayName = string.Empty; if (html) { emailAddress = WebUtility.HtmlEncode(emailAddress); displayName = WebUtility.HtmlEncode(displayName); } if (convertToHref && html && !string.IsNullOrEmpty(emailAddress)) result += "" + (!string.IsNullOrEmpty(displayName) ? displayName : emailAddress) + ""; else { if (!string.IsNullOrEmpty(displayName)) result += displayName; var beginTag = string.Empty; var endTag = string.Empty; if (!string.IsNullOrEmpty(displayName)) { if (html) { beginTag = " <"; endTag = ">"; } else { beginTag = " <"; endTag = ">"; } } if (!string.IsNullOrEmpty(emailAddress)) result += beginTag + emailAddress + endTag; } } return result; } #endregion #region Save /// /// Save this to a file.
///
/// Can be loaded at a later time using the method. ///
/// The File location to save the to. Existent files will be overwritten. /// If is /// Other exceptions relevant to using a might be thrown as well public void Save(FileInfo file) { if (file == null) throw new ArgumentNullException(nameof(file)); using (var fileStream = new FileStream(file.FullName, FileMode.Create)) Save(fileStream); } /// /// Save this to a stream.
///
/// The stream to write to /// If is /// Other exceptions relevant to might be thrown as well public void Save(Stream messageStream) { if (messageStream == null) throw new ArgumentNullException(nameof(messageStream)); messageStream.Write(RawMessage, 0, RawMessage.Length); } #endregion #region Load /// /// Loads a from a file containing a raw email. /// /// The File location to load the from. The file must exist. /// If is /// If does not exist /// Other exceptions relevant to a might be thrown as well /// A with the content loaded from the public static Message Load(FileInfo file) { if (file == null) throw new ArgumentNullException(nameof(file)); if (!file.Exists) throw new FileNotFoundException("Cannot load message from non-existent file", file.FullName); using (var fileStream = new FileStream(file.FullName, FileMode.Open)) return Load(fileStream); } /// /// Loads a from a containing a raw email. /// /// The from which to load the raw /// If is /// Other exceptions relevant to might be thrown as well /// A with the content loaded from the public static Message Load(Stream messageStream) { if (messageStream == null) throw new ArgumentNullException(nameof(messageStream)); using (var memoryStream = new MemoryStream()) { messageStream.CopyTo(memoryStream); var content = memoryStream.ToArray(); return new Message(content); } } #endregion } }