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