/** * SMTP Client Validation Utilities * Input validation functions for SMTP client operations */ import { REGEX_PATTERNS } from '../constants.js'; import type { ISmtpClientOptions, ISmtpAuthOptions } from '../interfaces.js'; /** * Validate email address format */ export function validateEmailAddress(email: string): boolean { if (!email || typeof email !== 'string') { return false; } return REGEX_PATTERNS.EMAIL_ADDRESS.test(email.trim()); } /** * Validate SMTP client options */ export function validateClientOptions(options: ISmtpClientOptions): string[] { const errors: string[] = []; // Required fields if (!options.host || typeof options.host !== 'string') { errors.push('Host is required and must be a string'); } if (!options.port || typeof options.port !== 'number' || options.port < 1 || options.port > 65535) { errors.push('Port must be a number between 1 and 65535'); } // Optional field validation if (options.connectionTimeout !== undefined) { if (typeof options.connectionTimeout !== 'number' || options.connectionTimeout < 1000) { errors.push('Connection timeout must be a number >= 1000ms'); } } if (options.socketTimeout !== undefined) { if (typeof options.socketTimeout !== 'number' || options.socketTimeout < 1000) { errors.push('Socket timeout must be a number >= 1000ms'); } } if (options.maxConnections !== undefined) { if (typeof options.maxConnections !== 'number' || options.maxConnections < 1) { errors.push('Max connections must be a positive number'); } } if (options.maxMessages !== undefined) { if (typeof options.maxMessages !== 'number' || options.maxMessages < 1) { errors.push('Max messages must be a positive number'); } } // Validate authentication options if (options.auth) { errors.push(...validateAuthOptions(options.auth)); } return errors; } /** * Validate authentication options */ export function validateAuthOptions(auth: ISmtpAuthOptions): string[] { const errors: string[] = []; if (auth.method && !['PLAIN', 'LOGIN', 'OAUTH2', 'AUTO'].includes(auth.method)) { errors.push('Invalid authentication method'); } // For basic auth, require user and pass if ((auth.user || auth.pass) && (!auth.user || !auth.pass)) { errors.push('Both user and pass are required for basic authentication'); } // For OAuth2, validate required fields if (auth.oauth2) { const oauth = auth.oauth2; if (!oauth.user || !oauth.clientId || !oauth.clientSecret || !oauth.refreshToken) { errors.push('OAuth2 requires user, clientId, clientSecret, and refreshToken'); } if (oauth.user && !validateEmailAddress(oauth.user)) { errors.push('OAuth2 user must be a valid email address'); } } return errors; } /** * Validate hostname format */ export function validateHostname(hostname: string): boolean { if (!hostname || typeof hostname !== 'string') { return false; } // Basic hostname validation (allow IP addresses and domain names) const hostnameRegex = /^(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)*[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?$|^(?:\d{1,3}\.){3}\d{1,3}$|^\[(?:[0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}\]$/; return hostnameRegex.test(hostname); } /** * Validate port number */ export function validatePort(port: number): boolean { return typeof port === 'number' && port >= 1 && port <= 65535; } /** * Sanitize and validate domain name for EHLO */ export function validateAndSanitizeDomain(domain: string): string { if (!domain || typeof domain !== 'string') { return 'localhost'; } const sanitized = domain.trim().toLowerCase(); if (validateHostname(sanitized)) { return sanitized; } return 'localhost'; } /** * Validate recipient list */ export function validateRecipients(recipients: string | string[]): string[] { const errors: string[] = []; const recipientList = Array.isArray(recipients) ? recipients : [recipients]; for (const recipient of recipientList) { if (!validateEmailAddress(recipient)) { errors.push(`Invalid email address: ${recipient}`); } } return errors; } /** * Validate sender address */ export function validateSender(sender: string): boolean { return validateEmailAddress(sender); }