using System; using System.Collections.Generic; using System.Collections.Specialized; using System.Net.Mail; using System.Net.Mime; using MsgReader.Mime.Decode; namespace MsgReader.Mime.Header { /// /// Class that holds all headers for a message
/// Headers which are unknown the the parser will be held in the collection.
///
/// This class cannot be instantiated from outside the library. ///
/// /// See RFC 4021 for a large list of headers.
///
public sealed class MessageHeader { #region Properties /// /// All headers which were not recognized and explicitly dealt with.
/// This should mostly be custom headers, which are marked as X-[name].
///
/// This list will be empty if all headers were recognized and parsed. ///
/// /// If you as a user, feels that a header in this collection should /// be parsed, feel free to notify the developers. /// public NameValueCollection UnknownHeaders { get; } /// /// A human readable description of the body
///
/// if no Content-Description header was present in the message. ///
public string ContentDescription { get; private set; } /// /// ID of the content part (like an attached image). Used with MultiPart messages.
///
/// if no Content-ID header field was present in the message. ///
/// For an ID of the message public string ContentId { get; private set; } /// /// Message keywords
///
/// The list will be empty if no Keywords header was present in the message ///
public List Keywords { get; } /// /// A List of emails to people who wishes to be notified when some event happens.
/// These events could be email: /// /// deletion /// printing /// received /// ... /// /// The list will be empty if no Disposition-Notification-To header was present in the message ///
/// See RFC 3798 for details public List DispositionNotificationTo { get; private set; } /// /// This is the Received headers. This tells the path that the email went.
///
/// The list will be empty if no Received header was present in the message ///
public List Received { get; } /// /// Importance of this email.
///
/// The importance level is set to normal, if no Importance header field was mentioned or it contained /// unknown information. This is the expected behavior according to the RFC. ///
public MailPriority Importance { get; private set; } /// /// This header describes the Content encoding during transfer.
///
/// If no Content-Transfer-Encoding header was present in the message, it is set /// to the default of SevenBit in accordance to the RFC. ///
/// See RFC 2045 section 6 for details public ContentTransferEncoding ContentTransferEncoding { get; private set; } /// /// Carbon Copy. This specifies who got a copy of the message.
///
/// The list will be empty if no Cc header was present in the message ///
public List Cc { get; private set; } /// /// Blind Carbon Copy. This specifies who got a copy of the message, but others /// cannot see who these persons are.
///
/// The list will be empty if no Received Bcc was present in the message ///
public List Bcc { get; private set; } /// /// Specifies who this mail was for
///
/// The list will be empty if no To header was present in the message ///
public List To { get; private set; } /// /// Specifies who sent the email
///
/// if no From header field was present in the message ///
public RfcMailAddress From { get; private set; } /// /// Specifies who a reply to the message should be sent to
///
/// if no Reply-To header field was present in the message ///
public RfcMailAddress ReplyTo { get; private set; } /// /// The message identifier(s) of the original message(s) to which the /// current message is a reply.
///
/// The list will be empty if no In-Reply-To header was present in the message ///
public List InReplyTo { get; private set; } /// /// The message identifier(s) of other message(s) to which the current /// message is related to.
///
/// The list will be empty if no References header was present in the message ///
public List References { get; private set; } /// /// This is the sender of the email address.
///
/// if no Sender header field was present in the message ///
/// /// The RFC states that this field can be used if a secretary /// is sending an email for someone she is working for. /// The email here will then be the secretary's email, and /// the Reply-To field would hold the address of the person she works for.
/// RFC states that if the Sender is the same as the From field, /// sender should not be included in the message. ///
public RfcMailAddress Sender { get; private set; } /// /// The Content-Type header field.
///
/// If not set, the ContentType is created by the default "text/plain; charset=us-ascii" which is /// defined in RFC 2045 section 5.2.
/// If set, the default is overridden. ///
public ContentType ContentType { get; private set; } /// /// Used to describe if a is to be displayed or to be though of as an attachment.
/// Also contains information about filename if such was sent.
///
/// if no Content-Disposition header field was present in the message ///
public ContentDisposition ContentDisposition { get; private set; } /// /// The Date when the email was sent.
/// This is the raw value. for a parsed up value of this field.
///
/// if no Date header field was present in the message or if the date could not be parsed. ///
/// See RFC 5322 section 3.6.1 for more details public string Date { get; private set; } /// /// The Date when the email was sent.
/// This is the parsed equivalent of .
/// Notice that the of the object is in UTC and has NOT been converted /// to local . ///
/// See RFC 5322 section 3.6.1 for more details public DateTime DateSent { get; private set; } /// /// An ID of the message that is SUPPOSED to be in every message according to the RFC.
/// The ID is unique.
///
/// if no Message-ID header field was present in the message ///
public string MessageId { get; private set; } /// /// The Mime Version.
/// This field will almost always show 1.0
///
/// if no Mime-Version header field was present in the message ///
public string MimeVersion { get; private set; } /// /// A single with no username inside.
/// This is a trace header field, that should be in all messages.
/// Replies should be sent to this address.
///
/// if no Return-Path header field was present in the message ///
public RfcMailAddress ReturnPath { get; private set; } /// /// The subject line of the message in decoded, one line state.
/// This should be in all messages.
///
/// if no Subject header field was present in the message ///
public string Subject { get; private set; } #endregion #region Constructor /// /// Parses a to a MessageHeader /// /// The collection that should be traversed and parsed /// A valid MessageHeader object /// If is internal MessageHeader(NameValueCollection headers) { if (headers == null) throw new ArgumentNullException(nameof(headers)); // Create empty lists as defaults. We do not like null values // List with an initial capacity set to zero will be replaced // when a corrosponding header is found To = new List(0); Cc = new List(0); Bcc = new List(0); Received = new List(); Keywords = new List(); InReplyTo = new List(0); References = new List(0); DispositionNotificationTo = new List(); UnknownHeaders = new NameValueCollection(); // Default importancetype is Normal (assumed if not set) Importance = MailPriority.Normal; // 7BIT is the default ContentTransferEncoding (assumed if not set) ContentTransferEncoding = ContentTransferEncoding.SevenBit; // text/plain; charset=us-ascii is the default ContentType ContentType = new ContentType("text/plain; charset=us-ascii"); // Now parse the actual headers ParseHeaders(headers); } #endregion #region ParseHeaders /// /// Parses a to a /// /// The collection that should be traversed and parsed /// A valid object /// If is private void ParseHeaders(NameValueCollection headers) { if (headers == null) throw new ArgumentNullException(nameof(headers)); // Now begin to parse the header values foreach (string headerName in headers.Keys) { var headerValues = headers.GetValues(headerName); if (headerValues == null) continue; foreach (var headerValue in headerValues) ParseHeader(headerName, headerValue); } } #endregion #region ParseHeader /// /// Parses a single header and sets member variables according to it. /// /// The name of the header /// The value of the header in unfolded state (only one line) /// If or is private void ParseHeader(string headerName, string headerValue) { if(headerName == null) throw new ArgumentNullException(nameof(headerName)); if (headerValue == null) throw new ArgumentNullException(nameof(headerValue)); switch (headerName.ToUpperInvariant()) { // See http://tools.ietf.org/html/rfc5322#section-3.6.3 case "TO": To = RfcMailAddress.ParseMailAddresses(headerValue); break; // See http://tools.ietf.org/html/rfc5322#section-3.6.3 case "CC": Cc = RfcMailAddress.ParseMailAddresses(headerValue); break; // See http://tools.ietf.org/html/rfc5322#section-3.6.3 case "BCC": Bcc = RfcMailAddress.ParseMailAddresses(headerValue); break; // See http://tools.ietf.org/html/rfc5322#section-3.6.2 case "FROM": // There is only one MailAddress in the from field From = RfcMailAddress.ParseMailAddress(headerValue); break; // http://tools.ietf.org/html/rfc5322#section-3.6.2 // The implementation here might be wrong case "REPLY-TO": // This field may actually be a list of addresses, but no // such case has been encountered ReplyTo = RfcMailAddress.ParseMailAddress(headerValue); break; // http://tools.ietf.org/html/rfc5322#section-3.6.2 case "SENDER": Sender = RfcMailAddress.ParseMailAddress(headerValue); break; // See http://tools.ietf.org/html/rfc5322#section-3.6.5 // RFC 5322: // The "Keywords:" field contains a comma-separated list of one or more // words or quoted-strings. // The field are intended to have only human-readable content // with information about the message case "KEYWORDS": var keywordsTemp = headerValue.Split(','); foreach (var keyword in keywordsTemp) { // Remove the quotes if there is any Keywords.Add(Utility.RemoveQuotesIfAny(keyword.Trim())); } break; // See http://tools.ietf.org/html/rfc5322#section-3.6.7 case "RECEIVED": // Simply add the value to the list Received.Add(new Received(headerValue.Trim())); break; case "IMPORTANCE": Importance = HeaderFieldParser.ParseImportance(headerValue.Trim()); break; // See http://tools.ietf.org/html/rfc3798#section-2.1 case "DISPOSITION-NOTIFICATION-TO": DispositionNotificationTo = RfcMailAddress.ParseMailAddresses(headerValue); break; case "MIME-VERSION": MimeVersion = headerValue.Trim(); break; // See http://tools.ietf.org/html/rfc5322#section-3.6.5 case "SUBJECT": Subject = EncodedWord.Decode(headerValue); break; // See http://tools.ietf.org/html/rfc5322#section-3.6.7 case "RETURN-PATH": // Return-paths does not include a username, but we // may still use the address parser ReturnPath = RfcMailAddress.ParseMailAddress(headerValue); break; // See http://tools.ietf.org/html/rfc5322#section-3.6.4 // Example Message-ID // <33cdd74d6b89ab2250ecd75b40a41405@nfs.eksperten.dk> case "MESSAGE-ID": MessageId = HeaderFieldParser.ParseId(headerValue); break; // See http://tools.ietf.org/html/rfc5322#section-3.6.4 case "IN-REPLY-TO": InReplyTo = HeaderFieldParser.ParseMultipleIDs(headerValue); break; // See http://tools.ietf.org/html/rfc5322#section-3.6.4 case "REFERENCES": References = HeaderFieldParser.ParseMultipleIDs(headerValue); break; // See http://tools.ietf.org/html/rfc5322#section-3.6.1 case "DATE": // See https://tools.ietf.org/html/rfc4021#section-2.1.48 case "DELIVERY-DATE": Date = headerValue.Trim(); DateSent = Rfc2822DateTime.StringToDate(headerValue); break; // See http://tools.ietf.org/html/rfc2045#section-6 // See ContentTransferEncoding class for more details case "CONTENT-TRANSFER-ENCODING": ContentTransferEncoding = HeaderFieldParser.ParseContentTransferEncoding(headerValue.Trim()); break; // See http://tools.ietf.org/html/rfc2045#section-8 case "CONTENT-DESCRIPTION": // Human description of for example a file. Can be encoded ContentDescription = EncodedWord.Decode(headerValue.Trim()); break; // See http://tools.ietf.org/html/rfc2045#section-5.1 // Example: Content-type: text/plain; charset="us-ascii" case "CONTENT-TYPE": ContentType = HeaderFieldParser.ParseContentType(headerValue); break; // See http://tools.ietf.org/html/rfc2183 case "CONTENT-DISPOSITION": ContentDisposition = HeaderFieldParser.ParseContentDisposition(headerValue); break; // See http://tools.ietf.org/html/rfc2045#section-7 // Example: case "CONTENT-ID": ContentId = HeaderFieldParser.ParseId(headerValue); break; default: // This is an unknown header // Custom headers are allowed. That means headers // that are not mentionen in the RFC. // Such headers start with the letter "X" // We do not have any special parsing of such // Add it to unknown headers UnknownHeaders.Add(headerName, headerValue); break; } } #endregion } }