This commit is contained in:
2025-05-23 00:06:07 +00:00
parent f058b2d1e7
commit 4905595cbb
7 changed files with 351 additions and 99 deletions

View File

@@ -189,46 +189,79 @@ export class DataHandler implements IDataHandler {
/**
* Process a complete email
* @param rawData - Raw email data
* @param session - SMTP session
* @returns Promise that resolves with the Email object
*/
public async processEmail(rawData: string, session: ISmtpSession): Promise<Email> {
// Clean up the raw email data
let cleanedData = rawData;
// Remove trailing end-of-data marker: various formats
cleanedData = cleanedData
.replace(/\r\n\.\r\n$/, '')
.replace(/\n\.\r\n$/, '')
.replace(/\r\n\.\n$/, '')
.replace(/\n\.\n$/, '')
.replace(/^\.$/, ''); // Handle ONLY a lone dot as the entire content (not trailing dots)
// Remove dot-stuffing (RFC 5321, section 4.5.2)
cleanedData = cleanedData.replace(/\r\n\.\./g, '\r\n.');
try {
// Parse email into Email object using cleaned data
const email = await this.parseEmailFromData(cleanedData, session);
// Return the parsed email
return email;
} catch (error) {
SmtpLogger.error(`Failed to parse email: ${error instanceof Error ? error.message : String(error)}`, {
sessionId: session.id,
error: error instanceof Error ? error : new Error(String(error))
});
// Create a minimal email object on error
const fallbackEmail = new Email();
fallbackEmail.setFromRawData(cleanedData);
return fallbackEmail;
}
}
/**
* Parse email from raw data
* @param rawData - Raw email data
* @param session - SMTP session
* @returns Email object
*/
private async parseEmailFromData(rawData: string, session: ISmtpSession): Promise<Email> {
const email = new Email();
// Set raw data
email.setFromRawData(rawData);
// Set envelope information from session
if (session.mailFrom) {
email.setFrom(session.mailFrom);
}
if (session.rcptTo && session.rcptTo.length > 0) {
for (const recipient of session.rcptTo) {
email.addTo(recipient);
}
}
return email;
}
/**
* Process a complete email (legacy method)
* @param session - SMTP session
* @returns Promise that resolves with the result of the transaction
*/
public async processEmail(session: ISmtpSession): Promise<ISmtpTransactionResult> {
const isLargeMessage = (session.emailDataSize || 0) > 100 * 1024; // 100KB threshold
// For large messages, process chunks efficiently to avoid memory issues
if (isLargeMessage) {
session.emailData = this.processEmailDataStreaming(session.emailDataChunks || []);
// Clear chunks immediately after processing to free memory
session.emailDataChunks = [];
session.emailDataSize = 0;
// Force garbage collection for large messages
if (global.gc) {
global.gc();
}
} else {
// For smaller messages, use the simpler approach
session.emailData = (session.emailDataChunks || []).join('');
// Remove trailing end-of-data marker: various formats
session.emailData = session.emailData
.replace(/\r\n\.\r\n$/, '')
.replace(/\n\.\r\n$/, '')
.replace(/\r\n\.\n$/, '')
.replace(/\n\.\n$/, '')
.replace(/^\.$/, ''); // Handle ONLY a lone dot as the entire content (not trailing dots)
// Remove dot-stuffing (RFC 5321, section 4.5.2)
session.emailData = session.emailData.replace(/\r\n\.\./g, '\r\n.');
// Clear chunks after processing
session.emailDataChunks = [];
}
public async processEmailLegacy(session: ISmtpSession): Promise<ISmtpTransactionResult> {
try {
// Parse email into Email object
const email = await this.parseEmail(session);
// Use the email data from session
const email = await this.parseEmailFromData(session.emailData || '', session);
// Process the email based on the processing mode
const processingMode = session.processingMode || 'mta';
@@ -1195,6 +1228,18 @@ SmtpLogger.debug(`Parsed email subject: ${subject}`, { subject });
}, 100); // Short delay before retry
}
/**
* Handle email data (interface requirement)
*/
public async handleData(
socket: plugins.net.Socket | plugins.tls.TLSSocket,
data: string,
session: ISmtpSession
): Promise<void> {
// Delegate to existing method
await this.handleDataReceived(socket, data);
}
/**
* Clean up resources
*/