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