dcrouter/test/suite/edge-cases/test.nested-mime-structures.ts
2025-05-23 21:20:39 +00:00

358 lines
12 KiB
TypeScript

import { tap, expect } from '@git.zone/tstest/tapbundle';
import * as plugins from '../plugins.js';
import * as net from 'net';
import { startTestServer, stopTestServer, TEST_PORT, sendEmailWithRawSocket } from '../../helpers/server.loader.js';
import type { SmtpServer } from '../../../ts/mail/delivery/smtpserver/index.js';
let testServer: SmtpServer;
tap.test('setup - start test server', async () => {
testServer = await startTestServer();
await plugins.smartdelay.delayFor(1000);
});
tap.test('Nested MIME Structures - should handle deeply nested multipart structure', async (tools) => {
const done = tools.defer();
const socket = net.createConnection({
host: 'localhost',
port: TEST_PORT,
timeout: 30000
});
let dataBuffer = '';
socket.on('data', (data) => {
dataBuffer += data.toString();
console.log('Server response:', data.toString());
if (dataBuffer.includes('220 ')) {
// Send EHLO
socket.write('EHLO testclient\r\n');
} else if (dataBuffer.includes('250 ') && dataBuffer.includes('EHLO')) {
// Send MAIL FROM
socket.write('MAIL FROM:<sender@example.com>\r\n');
dataBuffer = '';
} else if (dataBuffer.includes('250 ') && dataBuffer.includes('sender accepted')) {
// Send RCPT TO
socket.write('RCPT TO:<recipient@example.com>\r\n');
dataBuffer = '';
} else if (dataBuffer.includes('250 ') && dataBuffer.includes('recipient accepted')) {
// Send DATA
socket.write('DATA\r\n');
dataBuffer = '';
} else if (dataBuffer.includes('354 ')) {
// Create deeply nested MIME structure (4 levels)
const outerBoundary = '----=_Outer_Boundary_' + Date.now();
const middleBoundary = '----=_Middle_Boundary_' + Date.now();
const innerBoundary = '----=_Inner_Boundary_' + Date.now();
const deepBoundary = '----=_Deep_Boundary_' + Date.now();
let emailContent = [
'Subject: Deeply Nested MIME Structure Test',
'From: sender@example.com',
'To: recipient@example.com',
'MIME-Version: 1.0',
`Content-Type: multipart/mixed; boundary="${outerBoundary}"`,
'',
'This is a multipart message with deeply nested structure.',
'',
// Level 1: Outer boundary
`--${outerBoundary}`,
'Content-Type: text/plain',
'',
'This is the first part at the outer level.',
'',
`--${outerBoundary}`,
`Content-Type: multipart/alternative; boundary="${middleBoundary}"`,
'',
// Level 2: Middle boundary
`--${middleBoundary}`,
'Content-Type: text/plain',
'',
'Alternative plain text version.',
'',
`--${middleBoundary}`,
`Content-Type: multipart/related; boundary="${innerBoundary}"`,
'',
// Level 3: Inner boundary
`--${innerBoundary}`,
'Content-Type: text/html',
'',
'<html><body><h1>HTML with related content</h1><img src="cid:image1"></body></html>',
'',
`--${innerBoundary}`,
'Content-Type: image/png',
'Content-ID: <image1>',
'Content-Transfer-Encoding: base64',
'',
'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
'',
`--${innerBoundary}`,
`Content-Type: multipart/mixed; boundary="${deepBoundary}"`,
'',
// Level 4: Deep boundary
`--${deepBoundary}`,
'Content-Type: application/octet-stream',
'Content-Disposition: attachment; filename="data.bin"',
'',
'Binary data simulation',
'',
`--${deepBoundary}`,
'Content-Type: message/rfc822',
'',
'Subject: Embedded Message',
'From: embedded@example.com',
'To: recipient@example.com',
'',
'This is an embedded email message.',
'',
`--${deepBoundary}--`,
'',
`--${innerBoundary}--`,
'',
`--${middleBoundary}--`,
'',
`--${outerBoundary}`,
'Content-Type: application/pdf',
'Content-Disposition: attachment; filename="document.pdf"',
'',
'PDF document data simulation',
'',
`--${outerBoundary}--`,
'.',
''
].join('\r\n');
console.log('Sending email with 4-level nested MIME structure');
socket.write(emailContent);
dataBuffer = '';
} else if (dataBuffer.includes('250 ') && dataBuffer.includes('Message accepted') ||
dataBuffer.includes('552 ') ||
dataBuffer.includes('554 ') ||
dataBuffer.includes('500 ')) {
// Either accepted or gracefully rejected
const accepted = dataBuffer.includes('250 ');
console.log(`Nested MIME structure test ${accepted ? 'accepted' : 'rejected'}`);
socket.write('QUIT\r\n');
socket.end();
done.resolve();
}
});
socket.on('error', (err) => {
console.error('Socket error:', err);
done.reject(err);
});
socket.on('timeout', () => {
console.error('Socket timeout');
socket.destroy();
done.reject(new Error('Socket timeout'));
});
await done.promise;
});
tap.test('Nested MIME Structures - should handle circular references in multipart structure', async (tools) => {
const done = tools.defer();
const socket = net.createConnection({
host: 'localhost',
port: TEST_PORT,
timeout: 30000
});
let dataBuffer = '';
socket.on('data', (data) => {
dataBuffer += data.toString();
console.log('Server response:', data.toString());
if (dataBuffer.includes('220 ')) {
socket.write('EHLO testclient\r\n');
} else if (dataBuffer.includes('250 ') && dataBuffer.includes('EHLO')) {
socket.write('MAIL FROM:<sender@example.com>\r\n');
dataBuffer = '';
} else if (dataBuffer.includes('250 ') && dataBuffer.includes('sender accepted')) {
socket.write('RCPT TO:<recipient@example.com>\r\n');
dataBuffer = '';
} else if (dataBuffer.includes('250 ') && dataBuffer.includes('recipient accepted')) {
socket.write('DATA\r\n');
dataBuffer = '';
} else if (dataBuffer.includes('354 ')) {
// Create structure with references between parts
const boundary1 = '----=_Boundary1_' + Date.now();
const boundary2 = '----=_Boundary2_' + Date.now();
let emailContent = [
'Subject: Multipart with Cross-References',
'From: sender@example.com',
'To: recipient@example.com',
'MIME-Version: 1.0',
`Content-Type: multipart/related; boundary="${boundary1}"`,
'',
`--${boundary1}`,
`Content-Type: multipart/alternative; boundary="${boundary2}"`,
'Content-ID: <part1>',
'',
`--${boundary2}`,
'Content-Type: text/html',
'',
'<html><body>See related part: <a href="cid:part2">Link</a></body></html>',
'',
`--${boundary2}`,
'Content-Type: text/plain',
'',
'Plain text with reference to part2',
'',
`--${boundary2}--`,
'',
`--${boundary1}`,
'Content-Type: application/xml',
'Content-ID: <part2>',
'',
'<?xml version="1.0"?><root><reference href="cid:part1"/></root>',
'',
`--${boundary1}--`,
'.',
''
].join('\r\n');
socket.write(emailContent);
dataBuffer = '';
} else if (dataBuffer.includes('250 ') && dataBuffer.includes('Message accepted') ||
dataBuffer.includes('552 ') ||
dataBuffer.includes('554 ') ||
dataBuffer.includes('500 ')) {
const accepted = dataBuffer.includes('250 ');
console.log(`Cross-reference test ${accepted ? 'accepted' : 'rejected'}`);
socket.write('QUIT\r\n');
socket.end();
done.resolve();
}
});
socket.on('error', (err) => {
console.error('Socket error:', err);
done.reject(err);
});
socket.on('timeout', () => {
console.error('Socket timeout');
socket.destroy();
done.reject(new Error('Socket timeout'));
});
await done.promise;
});
tap.test('Nested MIME Structures - should handle mixed nesting with various encodings', async (tools) => {
const done = tools.defer();
const socket = net.createConnection({
host: 'localhost',
port: TEST_PORT,
timeout: 30000
});
let dataBuffer = '';
socket.on('data', (data) => {
dataBuffer += data.toString();
console.log('Server response:', data.toString());
if (dataBuffer.includes('220 ')) {
socket.write('EHLO testclient\r\n');
} else if (dataBuffer.includes('250 ') && dataBuffer.includes('EHLO')) {
socket.write('MAIL FROM:<sender@example.com>\r\n');
dataBuffer = '';
} else if (dataBuffer.includes('250 ') && dataBuffer.includes('sender accepted')) {
socket.write('RCPT TO:<recipient@example.com>\r\n');
dataBuffer = '';
} else if (dataBuffer.includes('250 ') && dataBuffer.includes('recipient accepted')) {
socket.write('DATA\r\n');
dataBuffer = '';
} else if (dataBuffer.includes('354 ')) {
// Create structure with various encodings
const boundary1 = '----=_Encoding_Outer_' + Date.now();
const boundary2 = '----=_Encoding_Inner_' + Date.now();
let emailContent = [
'Subject: Mixed Encodings in Nested Structure',
'From: sender@example.com',
'To: recipient@example.com',
'MIME-Version: 1.0',
`Content-Type: multipart/mixed; boundary="${boundary1}"`,
'',
`--${boundary1}`,
'Content-Type: text/plain; charset="utf-8"',
'Content-Transfer-Encoding: quoted-printable',
'',
'This is quoted-printable encoded: =C3=A9=C3=A8=C3=AA',
'',
`--${boundary1}`,
`Content-Type: multipart/alternative; boundary="${boundary2}"`,
'',
`--${boundary2}`,
'Content-Type: text/plain; charset="iso-8859-1"',
'Content-Transfer-Encoding: 8bit',
'',
'Text with 8-bit characters: ñáéíóú',
'',
`--${boundary2}`,
'Content-Type: text/html; charset="utf-16"',
'Content-Transfer-Encoding: base64',
'',
'//48AGgAdABtAGwAPgA8AGIAbwBkAHkAPgBVAFQARgAtADEANgAgAHQAZQB4AHQAPAAvAGIAbwBkAHkAPgA8AC8AaAB0AG0AbAA+',
'',
`--${boundary2}--`,
'',
`--${boundary1}`,
'Content-Type: application/octet-stream',
'Content-Transfer-Encoding: base64',
'Content-Disposition: attachment; filename="binary.dat"',
'',
'VGhpcyBpcyBiaW5hcnkgZGF0YQ==',
'',
`--${boundary1}--`,
'.',
''
].join('\r\n');
socket.write(emailContent);
dataBuffer = '';
} else if (dataBuffer.includes('250 ') && dataBuffer.includes('Message accepted') ||
dataBuffer.includes('552 ') ||
dataBuffer.includes('554 ') ||
dataBuffer.includes('500 ')) {
const accepted = dataBuffer.includes('250 ');
console.log(`Mixed encodings test ${accepted ? 'accepted' : 'rejected'}`);
socket.write('QUIT\r\n');
socket.end();
done.resolve();
}
});
socket.on('error', (err) => {
console.error('Socket error:', err);
done.reject(err);
});
socket.on('timeout', () => {
console.error('Socket timeout');
socket.destroy();
done.reject(new Error('Socket timeout'));
});
await done.promise;
});
tap.test('cleanup - stop test server', async () => {
await stopTestServer(testServer);
});
tap.start();