using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Mail;
using MsgReader.Mime.Decode;
namespace MsgReader.Mime.Header
{
///
/// This class is used for RFC compliant email addresses.
///
/// The class cannot be instantiated from outside the library.
///
///
/// The does not cover all the possible formats
/// for RFC 5322 section 3.4 compliant email addresses.
/// This class is used as an address wrapper to account for that deficiency.
///
public class RfcMailAddress
{
#region Properties
///
/// The email address of this
/// It is possibly string.Empty since RFC mail addresses does not require an email address specified.
///
///
/// Example header with email address:
/// To: Test test@mail.com
/// Address will be test@mail.com
///
///
/// Example header without email address:
/// To: Test
/// Address will be .
///
public string Address { get; }
///
/// The display name of this
/// It is possibly since RFC mail addresses does not require a display name to be specified.
///
///
/// Example header with display name:
/// To: Test test@mail.com
/// DisplayName will be Test
///
///
/// Example header without display name:
/// To: test@test.com
/// DisplayName will be
///
public string DisplayName { get; }
///
/// This is the Raw string used to describe the .
///
public string Raw { get; }
///
/// The associated with the .
///
///
/// The value of this property can be in instances where the cannot represent the address properly.
/// Use property to see if this property is valid.
///
public MailAddress MailAddress { get; }
///
/// Specifies if the object contains a valid reference.
///
public bool HasValidMailAddress => MailAddress != null;
#endregion
#region Constructors
///
/// Constructs an object from a object.
/// This constructor is used when we were able to construct a from a string.
///
/// The address that was parsed into
/// The raw unparsed input which was parsed into the
/// If or is
private RfcMailAddress(MailAddress mailAddress, string raw)
{
if (mailAddress == null)
throw new ArgumentNullException(nameof(mailAddress));
if (raw == null)
throw new ArgumentNullException(nameof(raw));
MailAddress = mailAddress;
Address = mailAddress.Address;
DisplayName = mailAddress.DisplayName;
Raw = raw;
}
///
/// When we were unable to parse a string into a , this constructor can be
/// used. The Raw string is then used as the .
///
/// The raw unparsed input which could not be parsed
/// If is
private RfcMailAddress(string raw)
{
if (raw == null)
throw new ArgumentNullException(nameof(raw));
MailAddress = null;
Address = string.Empty;
DisplayName = raw;
Raw = raw;
}
#endregion
#region ToString
///
/// A string representation of the object
///
/// Returns the string representation for the object
public override string ToString()
{
return HasValidMailAddress ? MailAddress.ToString() : Raw;
}
#endregion
#region Parsing
///
/// Parses an email address from a MIME header
///
/// Examples of input:
/// Eksperten mailrobot <noreply@mail.eksperten.dk>
/// "Eksperten mailrobot" <noreply@mail.eksperten.dk>
/// <noreply@mail.eksperten.dk>
/// noreply@mail.eksperten.dk
///
/// It might also contain encoded text, which will then be decoded.
///
/// The value to parse out and email and/or a username
/// A
/// If is
///
/// RFC 5322 section 3.4 for more details on email syntax.
/// For more information about encoded text.
///
internal static RfcMailAddress ParseMailAddress(string input)
{
if (input == null)
throw new ArgumentNullException(nameof(input));
// Decode the value, if it was encoded
input = EncodedWord.Decode(input.Trim());
//Remove any redundant sets of angle brackets around the email address
var lastOpenAngleBracketIdx = input.LastIndexOf('<');
var lastCloseAngleBracketIdx = input.LastIndexOf('>');
//Find the index of the first angle bracket in this series of angle brackets, e.g "a>b" <> wouldn't find the angle bracket in the display name
var firstOpenAngleBracketIdx = lastOpenAngleBracketIdx;
var firstCloseAngleBracketIdx = lastCloseAngleBracketIdx;
while (firstOpenAngleBracketIdx > 0 && //There is a character before the last open angle bracket
input[firstOpenAngleBracketIdx - 1] == '<' && //The character before the last open angle bracket is another open angle bracket
input[firstCloseAngleBracketIdx - 1] == '>') //The character before the last close angle bracket is another close angle bracket
{
//Update the first angle bracket indices
firstOpenAngleBracketIdx--;
firstCloseAngleBracketIdx--;
}
//If the email address in the input string is enclosed in multiple angle brackets
if (firstOpenAngleBracketIdx != lastOpenAngleBracketIdx)
{
//Remove the multiple angle brackets surrounding the email address from the input string leaving just a single set
input = input.Substring(0, firstOpenAngleBracketIdx) + //Part before any angle brackets (display name if there is one)
input.Substring(lastOpenAngleBracketIdx, firstCloseAngleBracketIdx - lastOpenAngleBracketIdx + 1); //actual email address, including one angle bracket either side
}
// Find the location of the email address
var indexStartEmail = input.LastIndexOf('<');
var indexEndEmail = input.LastIndexOf('>');
try
{
if (indexStartEmail >= 0 && indexEndEmail >= 0)
{
// Check if there is a username in front of the email address
var username = indexStartEmail > 0 ? input.Substring(0, indexStartEmail).Trim() : string.Empty;
// Parse out the email address without the "<" and ">"
indexStartEmail = indexStartEmail + 1;
var emailLength = indexEndEmail - indexStartEmail;
var emailAddress = input.Substring(indexStartEmail, emailLength).Trim();
// There has been cases where there was no emailaddress between the < and >
if (!string.IsNullOrEmpty(emailAddress))
{
// If the username is quoted, MailAddress' constructor will remove them for us
return new RfcMailAddress(new MailAddress(emailAddress, username), input);
}
}
// This might be on the form noreply@mail.eksperten.dk
// Check if there is an email, if notm there is no need to try
if (input.Contains("@"))
return new RfcMailAddress(new MailAddress(input), input);
}
catch (FormatException)
{
// Sometimes invalid emails are sent, like sqlmap-user@sourceforge.net. (last period is illigal)
}
// It could be that the format used was simply a name
// which is indeed valid according to the RFC
// Example:
// Eksperten mailrobot
return new RfcMailAddress(input);
}
///
/// Parses input of the form
/// Eksperten mailrobot <noreply@mail.eksperten.dk>, ...
/// to a list of RFCMailAddresses
///
/// The input that is a comma-separated list of EmailAddresses to parse
/// A List of objects extracted from the parameter.
/// If is
internal static List ParseMailAddresses(string input)
{
if (input == null)
throw new ArgumentNullException(nameof(input));
// MailAddresses are split by commas
IEnumerable mailAddresses = Utility.SplitStringWithCharNotInsideQuotes(input, ',');
// Parse each of these
return mailAddresses.Select(ParseMailAddress).ToList();
}
#endregion
}
}