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