update
This commit is contained in:
@ -132,9 +132,20 @@ export class DataHandler implements IDataHandler {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check for end of data marker
|
||||
const lastChunk = session.emailDataChunks[session.emailDataChunks.length - 1] || '';
|
||||
if (SMTP_PATTERNS.END_DATA.test(lastChunk)) {
|
||||
// Check for end of data marker - combine all chunks to ensure we don't miss it if split across chunks
|
||||
const combinedData = session.emailDataChunks.join('');
|
||||
|
||||
// More permissive check for the end-of-data marker
|
||||
// Check for various formats: \r\n.\r\n, \n.\r\n, \r\n.\n, \n.\n, or just . or .\r\n at the end
|
||||
if (combinedData.endsWith('\r\n.\r\n') ||
|
||||
combinedData.endsWith('\n.\r\n') ||
|
||||
combinedData.endsWith('\r\n.\n') ||
|
||||
combinedData.endsWith('\n.\n') ||
|
||||
data === '.\r\n' ||
|
||||
data === '.') {
|
||||
|
||||
SmtpLogger.debug(`End of data marker found for session ${session.id}`, { sessionId: session.id });
|
||||
|
||||
// End of data marker found
|
||||
await this.handleEndOfData(socket, session);
|
||||
}
|
||||
@ -149,8 +160,13 @@ export class DataHandler implements IDataHandler {
|
||||
// Combine all chunks and remove end of data marker
|
||||
session.emailData = (session.emailDataChunks || []).join('');
|
||||
|
||||
// Remove trailing end-of-data marker: \r\n.\r\n
|
||||
session.emailData = session.emailData.replace(/\r\n\.\r\n$/, '');
|
||||
// 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 a lone dot at the end
|
||||
|
||||
// Remove dot-stuffing (RFC 5321, section 4.5.2)
|
||||
session.emailData = session.emailData.replace(/\r\n\.\./g, '\r\n.');
|
||||
@ -286,16 +302,77 @@ export class DataHandler implements IDataHandler {
|
||||
* @returns Promise that resolves with the parsed Email object
|
||||
*/
|
||||
public async parseEmail(session: ISmtpSession): Promise<Email> {
|
||||
// Create an email with minimal required options
|
||||
// Parse raw email text to extract headers
|
||||
const rawData = session.emailData;
|
||||
const headerEndIndex = rawData.indexOf('\r\n\r\n');
|
||||
|
||||
if (headerEndIndex === -1) {
|
||||
// No headers/body separation, create basic email
|
||||
return new Email({
|
||||
from: session.envelope.mailFrom.address,
|
||||
to: session.envelope.rcptTo.map(r => r.address),
|
||||
subject: 'Received via SMTP',
|
||||
text: rawData
|
||||
});
|
||||
}
|
||||
|
||||
// Extract headers and body
|
||||
const headersText = rawData.substring(0, headerEndIndex);
|
||||
const bodyText = rawData.substring(headerEndIndex + 4); // Skip the \r\n\r\n separator
|
||||
|
||||
// Parse headers
|
||||
const headers: Record<string, string> = {};
|
||||
const headerLines = headersText.split('\r\n');
|
||||
let currentHeader = '';
|
||||
|
||||
for (const line of headerLines) {
|
||||
// Check if this is a continuation of a previous header
|
||||
if (line.startsWith(' ') || line.startsWith('\t')) {
|
||||
if (currentHeader) {
|
||||
headers[currentHeader] += ' ' + line.trim();
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// This is a new header
|
||||
const separatorIndex = line.indexOf(':');
|
||||
if (separatorIndex !== -1) {
|
||||
const name = line.substring(0, separatorIndex).trim().toLowerCase();
|
||||
const value = line.substring(separatorIndex + 1).trim();
|
||||
headers[name] = value;
|
||||
currentHeader = name;
|
||||
}
|
||||
}
|
||||
|
||||
// Extract common headers
|
||||
const subject = headers['subject'] || 'No Subject';
|
||||
const from = headers['from'] || session.envelope.mailFrom.address;
|
||||
const to = headers['to'] || session.envelope.rcptTo.map(r => r.address).join(', ');
|
||||
const messageId = headers['message-id'] || `<${Date.now()}.${Math.random().toString(36).substring(2)}@${this.options.hostname}>`;
|
||||
|
||||
// Create email object
|
||||
const email = new Email({
|
||||
from: session.envelope.mailFrom.address,
|
||||
to: session.envelope.rcptTo.map(r => r.address),
|
||||
subject: 'Received via SMTP',
|
||||
text: session.emailData
|
||||
from: from,
|
||||
to: to.split(',').map(addr => addr.trim()),
|
||||
subject: subject,
|
||||
text: bodyText,
|
||||
messageId: messageId,
|
||||
// Add original session envelope data for accurate routing
|
||||
originalMailFrom: session.envelope.mailFrom.address,
|
||||
originalRcptTo: session.envelope.rcptTo.map(r => r.address)
|
||||
});
|
||||
|
||||
// Note: In a real implementation, we would parse the raw email data
|
||||
// to extract headers, content, etc., but that's beyond the scope of this refactoring
|
||||
// Add received header
|
||||
const timestamp = new Date().toUTCString();
|
||||
const receivedHeader = `from ${session.clientHostname || 'unknown'} (${session.remoteAddress}) by ${this.options.hostname} with ESMTP id ${session.id}; ${timestamp}`;
|
||||
email.addHeader('Received', receivedHeader);
|
||||
|
||||
// Add all original headers
|
||||
for (const [name, value] of Object.entries(headers)) {
|
||||
if (!['from', 'to', 'subject', 'message-id'].includes(name)) {
|
||||
email.addHeader(name, value);
|
||||
}
|
||||
}
|
||||
|
||||
return email;
|
||||
}
|
||||
|
Reference in New Issue
Block a user