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