import * as rfc2822Parser from "address-rfc2822" import type {Address} from "address-rfc2822" import {splitAtUnquoted} from "../../utils/utils.js" import {decode as decodeRfc2047} from "./rfc2047.js" // This class is used for RFC compliant email addresses. // The class cannot be instantiated from outside the library. // The MailAddress 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. export class RfcMailAddress { // A string representation of the object // Returns the string representation for the object toString(): string { if (!!this.mailAddress && this.hasValidMailAddress()) return this.mailAddress.format() else return this.raw } /// 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 . readonly address: string /// 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 "" readonly displayName: string // This is the Raw string used to describe the RfcMailAddress readonly raw: string /// The mailAddress associated with the rfcMailAddress /// The value of this property can be null in instances where the mailAddress /// represent the address properly. /// Use hasValidMailAddress property to see if this property is valid. readonly mailAddress: Address | null /// /// Specifies if the object contains a valid reference. /// hasValidMailAddress(): boolean { return this.mailAddress != null } /// /// 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 /// /// constructor(mailAddress: Address | null, raw: string) { if (!raw) throw new Error("raw is null!") this.mailAddress = mailAddress this.raw = raw if (!mailAddress) { this.address = "" this.displayName = raw } else { this.address = mailAddress.address this.displayName = mailAddress.name() } } /// 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. static parseMailAddress(input: string): RfcMailAddress { if (input == null) throw new Error("input was null!") input = decodeRfc2047(input.trim()) //Remove any redundant sets of angle brackets around the email address const lastOpenAngleBracketIdx = input.lastIndexOf("<") const 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 let firstOpenAngleBracketIdx = lastOpenAngleBracketIdx let 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) { //Part before any angle brackets (display name if there is one) const dpn = input.substring(0, firstOpenAngleBracketIdx) //actual email address, including one angle bracket either side const ma = input.substring(lastOpenAngleBracketIdx, firstCloseAngleBracketIdx + 1) input = dpn + ma } try { const result = rfc2822Parser.parse(input)[0] return new RfcMailAddress(result, input) } catch (e) { // 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(null, 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 RfcMailAddress objects extracted from the input parameter. // If is static parseMailAddresses(input: string): Array { if (input == null) throw new Error("input is null!") return splitAtUnquoted(input, ",").map(RfcMailAddress.parseMailAddress) } }