update
This commit is contained in:
@ -102,6 +102,7 @@ export class Email {
|
||||
/**
|
||||
* Validates an email address using smartmail's EmailAddressValidator
|
||||
* For constructor validation, we only check syntax to avoid delays
|
||||
* Supports RFC-compliant addresses including display names and bounce addresses.
|
||||
*
|
||||
* @param email The email address to validate
|
||||
* @returns boolean indicating if the email is valid
|
||||
@ -109,8 +110,69 @@ export class Email {
|
||||
private isValidEmail(email: string): boolean {
|
||||
if (!email || typeof email !== 'string') return false;
|
||||
|
||||
// Use smartmail's validation for better accuracy
|
||||
return Email.emailValidator.isValidFormat(email);
|
||||
// Handle empty return path (bounce address)
|
||||
if (email === '<>' || email === '') {
|
||||
return true; // Empty return path is valid for bounces per RFC 5321
|
||||
}
|
||||
|
||||
// Extract email from display name format
|
||||
const extractedEmail = this.extractEmailAddress(email);
|
||||
if (!extractedEmail) return false;
|
||||
|
||||
// Convert IDN (International Domain Names) to ASCII for validation
|
||||
let emailToValidate = extractedEmail;
|
||||
const atIndex = extractedEmail.indexOf('@');
|
||||
if (atIndex > 0) {
|
||||
const localPart = extractedEmail.substring(0, atIndex);
|
||||
const domainPart = extractedEmail.substring(atIndex + 1);
|
||||
|
||||
// Check if domain contains non-ASCII characters
|
||||
if (/[^\x00-\x7F]/.test(domainPart)) {
|
||||
try {
|
||||
// Convert IDN to ASCII using the URL API (built-in punycode support)
|
||||
const url = new URL(`http://${domainPart}`);
|
||||
emailToValidate = `${localPart}@${url.hostname}`;
|
||||
} catch (e) {
|
||||
// If conversion fails, allow the original domain
|
||||
// This supports testing and edge cases
|
||||
emailToValidate = extractedEmail;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Use smartmail's validation for the ASCII-converted email address
|
||||
return Email.emailValidator.isValidFormat(emailToValidate);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the email address from a string that may contain a display name.
|
||||
* Handles formats like:
|
||||
* - simple@example.com
|
||||
* - "John Doe" <john@example.com>
|
||||
* - John Doe <john@example.com>
|
||||
*
|
||||
* @param emailString The email string to parse
|
||||
* @returns The extracted email address or null
|
||||
*/
|
||||
private extractEmailAddress(emailString: string): string | null {
|
||||
if (!emailString || typeof emailString !== 'string') return null;
|
||||
|
||||
emailString = emailString.trim();
|
||||
|
||||
// Handle empty return path first
|
||||
if (emailString === '<>' || emailString === '') {
|
||||
return '';
|
||||
}
|
||||
|
||||
// Check for angle brackets format - updated regex to handle empty content
|
||||
const angleMatch = emailString.match(/<([^>]*)>/);
|
||||
if (angleMatch) {
|
||||
// If matched but content is empty (e.g., <>), return empty string
|
||||
return angleMatch[1].trim() || '';
|
||||
}
|
||||
|
||||
// If no angle brackets, assume it's a plain email
|
||||
return emailString.trim();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -161,7 +223,11 @@ export class Email {
|
||||
*/
|
||||
public getFromDomain(): string | null {
|
||||
try {
|
||||
const parts = this.from.split('@');
|
||||
const emailAddress = this.extractEmailAddress(this.from);
|
||||
if (!emailAddress || emailAddress === '') {
|
||||
return null;
|
||||
}
|
||||
const parts = emailAddress.split('@');
|
||||
if (parts.length !== 2 || !parts[1]) {
|
||||
return null;
|
||||
}
|
||||
@ -171,6 +237,84 @@ export class Email {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the clean from email address without display name
|
||||
* @returns The email address without display name
|
||||
*/
|
||||
public getFromAddress(): string {
|
||||
const extracted = this.extractEmailAddress(this.from);
|
||||
// Return extracted value if not null (including empty string for bounce messages)
|
||||
const address = extracted !== null ? extracted : this.from;
|
||||
|
||||
// Convert IDN to ASCII for SMTP protocol
|
||||
return this.convertIDNToASCII(address);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts IDN (International Domain Names) to ASCII
|
||||
* @param email The email address to convert
|
||||
* @returns The email with ASCII-converted domain
|
||||
*/
|
||||
private convertIDNToASCII(email: string): string {
|
||||
if (!email || email === '') return email;
|
||||
|
||||
const atIndex = email.indexOf('@');
|
||||
if (atIndex <= 0) return email;
|
||||
|
||||
const localPart = email.substring(0, atIndex);
|
||||
const domainPart = email.substring(atIndex + 1);
|
||||
|
||||
// Check if domain contains non-ASCII characters
|
||||
if (/[^\x00-\x7F]/.test(domainPart)) {
|
||||
try {
|
||||
// Convert IDN to ASCII using the URL API (built-in punycode support)
|
||||
const url = new URL(`http://${domainPart}`);
|
||||
return `${localPart}@${url.hostname}`;
|
||||
} catch (e) {
|
||||
// If conversion fails, return original
|
||||
return email;
|
||||
}
|
||||
}
|
||||
|
||||
return email;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets clean to email addresses without display names
|
||||
* @returns Array of email addresses without display names
|
||||
*/
|
||||
public getToAddresses(): string[] {
|
||||
return this.to.map(email => {
|
||||
const extracted = this.extractEmailAddress(email);
|
||||
const address = extracted !== null ? extracted : email;
|
||||
return this.convertIDNToASCII(address);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets clean cc email addresses without display names
|
||||
* @returns Array of email addresses without display names
|
||||
*/
|
||||
public getCcAddresses(): string[] {
|
||||
return this.cc.map(email => {
|
||||
const extracted = this.extractEmailAddress(email);
|
||||
const address = extracted !== null ? extracted : email;
|
||||
return this.convertIDNToASCII(address);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets clean bcc email addresses without display names
|
||||
* @returns Array of email addresses without display names
|
||||
*/
|
||||
public getBccAddresses(): string[] {
|
||||
return this.bcc.map(email => {
|
||||
const extracted = this.extractEmailAddress(email);
|
||||
const address = extracted !== null ? extracted : email;
|
||||
return this.convertIDNToASCII(address);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all recipients (to, cc, bcc) as a unique array
|
||||
|
Reference in New Issue
Block a user