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.
239 lines
11 KiB
239 lines
11 KiB
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Net.Mail;
|
|
using MsgReader.Mime.Decode;
|
|
|
|
namespace MsgReader.Mime.Header
|
|
{
|
|
/// <summary>
|
|
/// This class is used for RFC compliant email addresses.<br/>
|
|
/// <br/>
|
|
/// The class cannot be instantiated from outside the library.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// The <seealso cref="MailAddress"/> does not cover all the possible formats
|
|
/// for <a href="http://tools.ietf.org/html/rfc5322#section-3.4">RFC 5322 section 3.4</a> compliant email addresses.
|
|
/// This class is used as an address wrapper to account for that deficiency.
|
|
/// </remarks>
|
|
public class RfcMailAddress
|
|
{
|
|
#region Properties
|
|
///<summary>
|
|
/// The email address of this <see cref="RfcMailAddress"/><br/>
|
|
/// It is possibly string.Empty since RFC mail addresses does not require an email address specified.
|
|
///</summary>
|
|
///<example>
|
|
/// Example header with email address:<br/>
|
|
/// To: <c>Test test@mail.com</c><br/>
|
|
/// Address will be <c>test@mail.com</c><br/>
|
|
///</example>
|
|
///<example>
|
|
/// Example header without email address:<br/>
|
|
/// To: <c>Test</c><br/>
|
|
/// Address will be <see cref="string.Empty"/>.
|
|
///</example>
|
|
public string Address { get; }
|
|
|
|
///<summary>
|
|
/// The display name of this <see cref="RfcMailAddress"/><br/>
|
|
/// It is possibly <see cref="string.Empty"/> since RFC mail addresses does not require a display name to be specified.
|
|
///</summary>
|
|
///<example>
|
|
/// Example header with display name:<br/>
|
|
/// To: <c>Test test@mail.com</c><br/>
|
|
/// DisplayName will be <c>Test</c>
|
|
///</example>
|
|
///<example>
|
|
/// Example header without display name:<br/>
|
|
/// To: <c>test@test.com</c><br/>
|
|
/// DisplayName will be <see cref="string.Empty"/>
|
|
///</example>
|
|
public string DisplayName { get; }
|
|
|
|
/// <summary>
|
|
/// This is the Raw string used to describe the <see cref="RfcMailAddress"/>.
|
|
/// </summary>
|
|
public string Raw { get; }
|
|
|
|
/// <summary>
|
|
/// The <see cref="MailAddress"/> associated with the <see cref="RfcMailAddress"/>.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// The value of this property can be <see lanword="null"/> in instances where the <see cref="MailAddress"/> cannot represent the address properly.<br/>
|
|
/// Use <see cref="HasValidMailAddress"/> property to see if this property is valid.
|
|
/// </remarks>
|
|
public MailAddress MailAddress { get; }
|
|
|
|
/// <summary>
|
|
/// Specifies if the object contains a valid <see cref="MailAddress"/> reference.
|
|
/// </summary>
|
|
public bool HasValidMailAddress => MailAddress != null;
|
|
#endregion
|
|
|
|
#region Constructors
|
|
/// <summary>
|
|
/// Constructs an <see cref="RfcMailAddress"/> object from a <see cref="MailAddress"/> object.<br/>
|
|
/// This constructor is used when we were able to construct a <see cref="MailAddress"/> from a string.
|
|
/// </summary>
|
|
/// <param name="mailAddress">The address that <paramref name="raw"/> was parsed into</param>
|
|
/// <param name="raw">The raw unparsed input which was parsed into the <paramref name="mailAddress"/></param>
|
|
/// <exception cref="ArgumentNullException">If <paramref name="mailAddress"/> or <paramref name="raw"/> is <see langword="null"/></exception>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// When we were unable to parse a string into a <see cref="MailAddress"/>, this constructor can be
|
|
/// used. The Raw string is then used as the <see cref="DisplayName"/>.
|
|
/// </summary>
|
|
/// <param name="raw">The raw unparsed input which could not be parsed</param>
|
|
/// <exception cref="ArgumentNullException">If <paramref name="raw"/> is <see langword="null"/></exception>
|
|
private RfcMailAddress(string raw)
|
|
{
|
|
if (raw == null)
|
|
throw new ArgumentNullException(nameof(raw));
|
|
|
|
MailAddress = null;
|
|
Address = string.Empty;
|
|
DisplayName = raw;
|
|
Raw = raw;
|
|
}
|
|
#endregion
|
|
|
|
#region ToString
|
|
/// <summary>
|
|
/// A string representation of the <see cref="RfcMailAddress"/> object
|
|
/// </summary>
|
|
/// <returns>Returns the string representation for the object</returns>
|
|
public override string ToString()
|
|
{
|
|
return HasValidMailAddress ? MailAddress.ToString() : Raw;
|
|
}
|
|
#endregion
|
|
|
|
#region Parsing
|
|
/// <summary>
|
|
/// Parses an email address from a MIME header<br/>
|
|
/// <br/>
|
|
/// Examples of input:
|
|
/// <c>Eksperten mailrobot <noreply@mail.eksperten.dk></c><br/>
|
|
/// <c>"Eksperten mailrobot" <noreply@mail.eksperten.dk></c><br/>
|
|
/// <c><noreply@mail.eksperten.dk></c><br/>
|
|
/// <c>noreply@mail.eksperten.dk</c><br/>
|
|
/// <br/>
|
|
/// It might also contain encoded text, which will then be decoded.
|
|
/// </summary>
|
|
/// <param name="input">The value to parse out and email and/or a username</param>
|
|
/// <returns>A <see cref="RfcMailAddress"/></returns>
|
|
/// <exception cref="ArgumentNullException">If <paramref name="input"/> is <see langword="null"/></exception>
|
|
/// <remarks>
|
|
/// <see href="http://tools.ietf.org/html/rfc5322#section-3.4">RFC 5322 section 3.4</see> for more details on email syntax.<br/>
|
|
/// <see cref="EncodedWord.Decode">For more information about encoded text</see>.
|
|
/// </remarks>
|
|
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" <<blah@email.com>> 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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parses input of the form<br/>
|
|
/// <c>Eksperten mailrobot <noreply@mail.eksperten.dk>, ...</c><br/>
|
|
/// to a list of RFCMailAddresses
|
|
/// </summary>
|
|
/// <param name="input">The input that is a comma-separated list of EmailAddresses to parse</param>
|
|
/// <returns>A List of <seealso cref="RfcMailAddress"/> objects extracted from the <paramref name="input"/> parameter.</returns>
|
|
/// <exception cref="ArgumentNullException">If <paramref name="input"/> is <see langword="null"/></exception>
|
|
internal static List<RfcMailAddress> ParseMailAddresses(string input)
|
|
{
|
|
if (input == null)
|
|
throw new ArgumentNullException(nameof(input));
|
|
|
|
// MailAddresses are split by commas
|
|
IEnumerable<string> mailAddresses = Utility.SplitStringWithCharNotInsideQuotes(input, ',');
|
|
|
|
// Parse each of these
|
|
return mailAddresses.Select(ParseMailAddress).ToList();
|
|
}
|
|
#endregion
|
|
}
|
|
} |