using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Mail;
using System.Net.Mime;
using MsgReader.Mime.Decode;
namespace MsgReader.Mime.Header
{
///
/// Class that can parse different fields in the header sections of a MIME message.
///
internal static class HeaderFieldParser
{
#region ParseContentTransferEncoding
///
/// Parses the Content-Transfer-Encoding header.
///
/// The value for the header to be parsed
/// A
/// If is
/// If the could not be parsed to a
public static ContentTransferEncoding ParseContentTransferEncoding(string headerValue)
{
if (headerValue == null)
throw new ArgumentNullException(nameof(headerValue));
switch (headerValue.Trim().ToUpperInvariant())
{
case "7BIT":
return ContentTransferEncoding.SevenBit;
case "8BIT":
return ContentTransferEncoding.EightBit;
case "QUOTED-PRINTABLE":
return ContentTransferEncoding.QuotedPrintable;
case "BASE64":
return ContentTransferEncoding.Base64;
case "BINARY":
return ContentTransferEncoding.Binary;
// If a wrong argument is passed to this parser method, then we assume
// default encoding, which is SevenBit.
// This is to ensure that we do not throw exceptions, even if the email not MIME valid.
default:
return ContentTransferEncoding.SevenBit;
}
}
#endregion
#region ParseImportance
///
/// Parses an ImportanceType from a given Importance header value.
///
/// The value to be parsed
/// A . If the is not recognized, Normal is returned.
/// If is
public static MailPriority ParseImportance(string headerValue)
{
if (headerValue == null)
throw new ArgumentNullException(nameof(headerValue));
switch (headerValue.ToUpperInvariant())
{
case "5":
case "HIGH":
return MailPriority.High;
case "3":
case "NORMAL":
return MailPriority.Normal;
case "1":
case "LOW":
return MailPriority.Low;
default:
return MailPriority.Normal;
}
}
#endregion
#region ParseContentType
///
/// Parses a the value for the header Content-Type to
/// a object.
///
/// The value to be parsed
/// A object
/// If is
public static ContentType ParseContentType(string headerValue)
{
if (headerValue == null)
throw new ArgumentNullException(nameof(headerValue));
// We create an empty Content-Type which we will fill in when we see the values
var contentType = new ContentType();
// Now decode the parameters
var parameters = Rfc2231Decoder.Decode(headerValue);
foreach (var keyValuePair in parameters)
{
var key = keyValuePair.Key.ToUpperInvariant().Trim();
var value = Utility.RemoveQuotesIfAny(keyValuePair.Value.Trim());
switch (key)
{
case "":
// This is the MediaType - it has no key since it is the first one mentioned in the
// headerValue and has no = in it.
// Check for illegal content-type
var v = value.ToUpperInvariant().Trim('\0');
if (v.Equals("TEXT") || v.Equals("TEXT/"))
value = "text/plain";
contentType.MediaType = value;
break;
case "BOUNDARY":
contentType.Boundary = value;
break;
case "CHARSET":
contentType.CharSet = value;
break;
case "NAME":
contentType.Name = EncodedWord.Decode(value);
break;
default:
// This is to shut up the code help that is saying that contentType.Parameters
// can be null - which it cant!
if (contentType.Parameters == null)
throw new Exception("The ContentType parameters property is null. This will never be thrown.");
// We add the unknown value to our parameters list
// "Known" unknown values are:
// - title
// - report-type
contentType.Parameters.Add(key, value);
break;
}
}
return contentType;
}
#endregion
#region ParseContentDisposition
///
/// Parses a the value for the header Content-Disposition to a object.
///
/// The value to be parsed
/// A object
/// If is
public static ContentDisposition ParseContentDisposition(string headerValue)
{
if (headerValue == null)
throw new ArgumentNullException(nameof(headerValue));
// See http://www.ietf.org/rfc/rfc2183.txt for RFC definition
// Create empty ContentDisposition - we will fill in details as we read them
var contentDisposition = new ContentDisposition();
// Now decode the parameters
var parameters = Rfc2231Decoder.Decode(headerValue);
foreach (var keyValuePair in parameters)
{
var key = keyValuePair.Key.ToUpperInvariant().Trim();
var value = Utility.RemoveQuotesIfAny(keyValuePair.Value.Trim());
switch (key)
{
case "":
// This is the DispisitionType - it has no key since it is the first one
// and has no = in it.
contentDisposition.DispositionType = value;
break;
// The correct name of the parameter is filename, but some emails also contains the parameter
// name, which also holds the name of the file. Therefore we use both names for the same field.
case "NAME":
case "FILENAME":
// The filename might be in qoutes, and it might be encoded-word encoded
contentDisposition.FileName = EncodedWord.Decode(value);
break;
case "CREATION-DATE":
// Notice that we need to create a new DateTime because of a failure in .NET 2.0.
// The failure is: you cannot give contentDisposition a DateTime with a Kind of UTC
// It will set the CreationDate correctly, but when trying to read it out it will throw an exception.
// It is the same with ModificationDate and ReadDate.
// This is fixed in 4.0 - maybe in 3.0 too.
// Therefore we create a new DateTime which have a DateTimeKind set to unspecified
var creationDate = new DateTime(Rfc2822DateTime.StringToDate(value).Ticks);
contentDisposition.CreationDate = creationDate;
break;
case "MODIFICATION-DATE":
var midificationDate = new DateTime(Rfc2822DateTime.StringToDate(value).Ticks);
contentDisposition.ModificationDate = midificationDate;
break;
case "READ-DATE":
var readDate = new DateTime(Rfc2822DateTime.StringToDate(value).Ticks);
contentDisposition.ReadDate = readDate;
break;
case "SIZE":
contentDisposition.Size = SizeParser.Parse(value);
break;
case "CHARSET": // ignoring invalid parameter in Content-Disposition
case "VOICE":
break;
default:
if (!key.StartsWith("X-"))
throw new ArgumentException(
"Unknown parameter in Content-Disposition. Ask developer to fix! Parameter: " + key);
contentDisposition.Parameters.Add(key, value);
break;
}
}
return contentDisposition;
}
#endregion
#region ParseId
///
/// Parses an ID like Message-Id and Content-Id.
/// Example:
/// <test@test.com>
/// into
/// test@test.com
///
/// The id to parse
/// A parsed ID
public static string ParseId(string headerValue)
{
// Remove whitespace in front and behind since
// whitespace is allowed there
// Remove the last > and the first <
return headerValue.Trim().TrimEnd('>').TrimStart('<');
}
#endregion
#region ParseMultipleIDs
///
/// Parses multiple IDs from a single string like In-Reply-To.
///
/// The value to parse
/// A list of IDs
public static List ParseMultipleIDs(string headerValue)
{
// Split the string by >
// We cannot use ' ' (space) here since this is a possible value:
//
var ids = headerValue.Trim().Split(new[] {'>'}, StringSplitOptions.RemoveEmptyEntries);
return ids.Select(ParseId).ToList();
}
#endregion
}
}