You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
459 lines
17 KiB
459 lines
17 KiB
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
|
|
{
|
|
/// <summary>
|
|
/// Class that holds all headers for a message<br/>
|
|
/// Headers which are unknown the the parser will be held in the <see cref="UnknownHeaders"/> collection.<br/>
|
|
/// <br/>
|
|
/// This class cannot be instantiated from outside the library.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// See <a href="http://tools.ietf.org/html/rfc4021">RFC 4021</a> for a large list of headers.<br/>
|
|
/// </remarks>
|
|
public sealed class MessageHeader
|
|
{
|
|
#region Properties
|
|
/// <summary>
|
|
/// All headers which were not recognized and explicitly dealt with.<br/>
|
|
/// This should mostly be custom headers, which are marked as X-[name].<br/>
|
|
/// <br/>
|
|
/// This list will be empty if all headers were recognized and parsed.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// If you as a user, feels that a header in this collection should
|
|
/// be parsed, feel free to notify the developers.
|
|
/// </remarks>
|
|
public NameValueCollection UnknownHeaders { get; }
|
|
|
|
/// <summary>
|
|
/// A human readable description of the body<br/>
|
|
/// <br/>
|
|
/// <see langword="null"/> if no Content-Description header was present in the message.
|
|
/// </summary>
|
|
public string ContentDescription { get; private set; }
|
|
|
|
/// <summary>
|
|
/// ID of the content part (like an attached image). Used with MultiPart messages.<br/>
|
|
/// <br/>
|
|
/// <see langword="null"/> if no Content-ID header field was present in the message.
|
|
/// </summary>
|
|
/// <see cref="MessageId">For an ID of the message</see>
|
|
public string ContentId { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Message keywords<br/>
|
|
/// <br/>
|
|
/// The list will be empty if no Keywords header was present in the message
|
|
/// </summary>
|
|
public List<string> Keywords { get; }
|
|
|
|
/// <summary>
|
|
/// A List of emails to people who wishes to be notified when some event happens.<br/>
|
|
/// These events could be email:
|
|
/// <list type="bullet">
|
|
/// <item>deletion</item>
|
|
/// <item>printing</item>
|
|
/// <item>received</item>
|
|
/// <item>...</item>
|
|
/// </list>
|
|
/// The list will be empty if no Disposition-Notification-To header was present in the message
|
|
/// </summary>
|
|
/// <remarks>See <a href="http://tools.ietf.org/html/rfc3798">RFC 3798</a> for details</remarks>
|
|
public List<RfcMailAddress> DispositionNotificationTo { get; private set; }
|
|
|
|
/// <summary>
|
|
/// This is the Received headers. This tells the path that the email went.<br/>
|
|
/// <br/>
|
|
/// The list will be empty if no Received header was present in the message
|
|
/// </summary>
|
|
public List<Received> Received { get; }
|
|
|
|
/// <summary>
|
|
/// Importance of this email.<br/>
|
|
/// <br/>
|
|
/// 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.
|
|
/// </summary>
|
|
public MailPriority Importance { get; private set; }
|
|
|
|
/// <summary>
|
|
/// This header describes the Content encoding during transfer.<br/>
|
|
/// <br/>
|
|
/// If no Content-Transfer-Encoding header was present in the message, it is set
|
|
/// to the default of <see cref="Header.ContentTransferEncoding.SevenBit">SevenBit</see> in accordance to the RFC.
|
|
/// </summary>
|
|
/// <remarks>See <a href="http://tools.ietf.org/html/rfc2045#section-6">RFC 2045 section 6</a> for details</remarks>
|
|
public ContentTransferEncoding ContentTransferEncoding { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Carbon Copy. This specifies who got a copy of the message.<br/>
|
|
/// <br/>
|
|
/// The list will be empty if no Cc header was present in the message
|
|
/// </summary>
|
|
public List<RfcMailAddress> Cc { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Blind Carbon Copy. This specifies who got a copy of the message, but others
|
|
/// cannot see who these persons are.<br/>
|
|
/// <br/>
|
|
/// The list will be empty if no Received Bcc was present in the message
|
|
/// </summary>
|
|
public List<RfcMailAddress> Bcc { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Specifies who this mail was for<br/>
|
|
/// <br/>
|
|
/// The list will be empty if no To header was present in the message
|
|
/// </summary>
|
|
public List<RfcMailAddress> To { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Specifies who sent the email<br/>
|
|
/// <br/>
|
|
/// <see langword="null"/> if no From header field was present in the message
|
|
/// </summary>
|
|
public RfcMailAddress From { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Specifies who a reply to the message should be sent to<br/>
|
|
/// <br/>
|
|
/// <see langword="null"/> if no Reply-To header field was present in the message
|
|
/// </summary>
|
|
public RfcMailAddress ReplyTo { get; private set; }
|
|
|
|
/// <summary>
|
|
/// The message identifier(s) of the original message(s) to which the
|
|
/// current message is a reply.<br/>
|
|
/// <br/>
|
|
/// The list will be empty if no In-Reply-To header was present in the message
|
|
/// </summary>
|
|
public List<string> InReplyTo { get; private set; }
|
|
|
|
|
|
/// <summary>
|
|
/// The message identifier(s) of other message(s) to which the current
|
|
/// message is related to.<br/>
|
|
/// <br/>
|
|
/// The list will be empty if no References header was present in the message
|
|
/// </summary>
|
|
public List<string> References { get; private set; }
|
|
|
|
/// <summary>
|
|
/// This is the sender of the email address.<br/>
|
|
/// <br/>
|
|
/// <see langword="null"/> if no Sender header field was present in the message
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// 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.<br/>
|
|
/// RFC states that if the Sender is the same as the From field,
|
|
/// sender should not be included in the message.
|
|
/// </remarks>
|
|
public RfcMailAddress Sender { get; private set; }
|
|
|
|
/// <summary>
|
|
/// The Content-Type header field.<br/>
|
|
/// <br/>
|
|
/// If not set, the ContentType is created by the default "text/plain; charset=us-ascii" which is
|
|
/// defined in <a href="http://tools.ietf.org/html/rfc2045#section-5.2">RFC 2045 section 5.2</a>.<br/>
|
|
/// If set, the default is overridden.
|
|
/// </summary>
|
|
public ContentType ContentType { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Used to describe if a <see cref="MessagePart"/> is to be displayed or to be though of as an attachment.<br/>
|
|
/// Also contains information about filename if such was sent.<br/>
|
|
/// <br/>
|
|
/// <see langword="null"/> if no Content-Disposition header field was present in the message
|
|
/// </summary>
|
|
public ContentDisposition ContentDisposition { get; private set; }
|
|
|
|
/// <summary>
|
|
/// The Date when the email was sent.<br/>
|
|
/// This is the raw value. <see cref="DateSent"/> for a parsed up <see cref="DateTime"/> value of this field.<br/>
|
|
/// <br/>
|
|
/// <see langword="DateTime.MinValue"/> if no Date header field was present in the message or if the date could not be parsed.
|
|
/// </summary>
|
|
/// <remarks>See <a href="http://tools.ietf.org/html/rfc5322#section-3.6.1">RFC 5322 section 3.6.1</a> for more details</remarks>
|
|
public string Date { get; private set; }
|
|
|
|
/// <summary>
|
|
/// The Date when the email was sent.<br/>
|
|
/// This is the parsed equivalent of <see cref="Date"/>.<br/>
|
|
/// Notice that the <see cref="TimeZone"/> of the <see cref="DateTime"/> object is in UTC and has NOT been converted
|
|
/// to local <see cref="TimeZone"/>.
|
|
/// </summary>
|
|
/// <remarks>See <a href="http://tools.ietf.org/html/rfc5322#section-3.6.1">RFC 5322 section 3.6.1</a> for more details</remarks>
|
|
public DateTime DateSent { get; private set; }
|
|
|
|
/// <summary>
|
|
/// An ID of the message that is SUPPOSED to be in every message according to the RFC.<br/>
|
|
/// The ID is unique.<br/>
|
|
/// <br/>
|
|
/// <see langword="null"/> if no Message-ID header field was present in the message
|
|
/// </summary>
|
|
public string MessageId { get; private set; }
|
|
|
|
/// <summary>
|
|
/// The Mime Version.<br/>
|
|
/// This field will almost always show 1.0<br/>
|
|
/// <br/>
|
|
/// <see langword="null"/> if no Mime-Version header field was present in the message
|
|
/// </summary>
|
|
public string MimeVersion { get; private set; }
|
|
|
|
/// <summary>
|
|
/// A single <see cref="RfcMailAddress"/> with no username inside.<br/>
|
|
/// This is a trace header field, that should be in all messages.<br/>
|
|
/// Replies should be sent to this address.<br/>
|
|
/// <br/>
|
|
/// <see langword="null"/> if no Return-Path header field was present in the message
|
|
/// </summary>
|
|
public RfcMailAddress ReturnPath { get; private set; }
|
|
|
|
/// <summary>
|
|
/// The subject line of the message in decoded, one line state.<br/>
|
|
/// This should be in all messages.<br/>
|
|
/// <br/>
|
|
/// <see langword="null"/> if no Subject header field was present in the message
|
|
/// </summary>
|
|
public string Subject { get; private set; }
|
|
#endregion
|
|
|
|
#region Constructor
|
|
/// <summary>
|
|
/// Parses a <see cref="NameValueCollection"/> to a MessageHeader
|
|
/// </summary>
|
|
/// <param name="headers">The collection that should be traversed and parsed</param>
|
|
/// <returns>A valid MessageHeader object</returns>
|
|
/// <exception cref="ArgumentNullException">If <paramref name="headers"/> is <see langword="null"/></exception>
|
|
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<RfcMailAddress>(0);
|
|
Cc = new List<RfcMailAddress>(0);
|
|
Bcc = new List<RfcMailAddress>(0);
|
|
Received = new List<Received>();
|
|
Keywords = new List<string>();
|
|
InReplyTo = new List<string>(0);
|
|
References = new List<string>(0);
|
|
DispositionNotificationTo = new List<RfcMailAddress>();
|
|
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
|
|
/// <summary>
|
|
/// Parses a <see cref="NameValueCollection"/> to a <see cref="MessageHeader"/>
|
|
/// </summary>
|
|
/// <param name="headers">The collection that should be traversed and parsed</param>
|
|
/// <returns>A valid <see cref="MessageHeader"/> object</returns>
|
|
/// <exception cref="ArgumentNullException">If <paramref name="headers"/> is <see langword="null"/></exception>
|
|
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
|
|
/// <summary>
|
|
/// Parses a single header and sets member variables according to it.
|
|
/// </summary>
|
|
/// <param name="headerName">The name of the header</param>
|
|
/// <param name="headerValue">The value of the header in unfolded state (only one line)</param>
|
|
/// <exception cref="ArgumentNullException">If <paramref name="headerName"/> or <paramref name="headerValue"/> is <see langword="null"/></exception>
|
|
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: <foo4*foo1@bar.net>
|
|
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
|
|
}
|
|
} |