dcrouter/test/suite/smtpserver_edge-cases/test.edge-08.nested-mime-structures.ts

379 lines
12 KiB
TypeScript
Raw Normal View History

2025-05-23 19:09:30 +00:00
import { tap, expect } from '@git.zone/tstest/tapbundle';
2025-05-24 00:23:35 +00:00
import * as plugins from '../../../ts/plugins.js';
2025-05-23 19:03:44 +00:00
import * as net from 'net';
2025-05-24 01:00:30 +00:00
import { startTestServer, stopTestServer } from '../../helpers/server.loader.js'
import type { ITestServer } from '../../helpers/server.loader.js';
let testServer: ITestServer;
2025-05-24 00:23:35 +00:00
const TEST_PORT = 2525;
2025-05-23 19:03:44 +00:00
tap.test('setup - start test server', async () => {
2025-05-24 00:23:35 +00:00
testServer = await startTestServer({ port: TEST_PORT });
await new Promise(resolve => setTimeout(resolve, 1000));
2025-05-23 19:03:44 +00:00
});
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 = '';
2025-05-24 00:23:35 +00:00
let state = 'initial';
2025-05-23 19:03:44 +00:00
socket.on('data', (data) => {
dataBuffer += data.toString();
console.log('Server response:', data.toString());
2025-05-24 00:23:35 +00:00
if (dataBuffer.includes('220 ') && state === 'initial') {
2025-05-23 19:03:44 +00:00
// Send EHLO
socket.write('EHLO testclient\r\n');
2025-05-24 00:23:35 +00:00
state = 'ehlo_sent';
dataBuffer = '';
} else if (dataBuffer.includes('250 ') && state === 'ehlo_sent') {
2025-05-23 19:03:44 +00:00
// Send MAIL FROM
socket.write('MAIL FROM:<sender@example.com>\r\n');
2025-05-24 00:23:35 +00:00
state = 'mail_from_sent';
2025-05-23 19:03:44 +00:00
dataBuffer = '';
2025-05-24 00:23:35 +00:00
} else if (dataBuffer.includes('250 ') && state === 'mail_from_sent') {
2025-05-23 19:03:44 +00:00
// Send RCPT TO
socket.write('RCPT TO:<recipient@example.com>\r\n');
2025-05-24 00:23:35 +00:00
state = 'rcpt_to_sent';
2025-05-23 19:03:44 +00:00
dataBuffer = '';
2025-05-24 00:23:35 +00:00
} else if (dataBuffer.includes('250 ') && state === 'rcpt_to_sent') {
2025-05-23 19:03:44 +00:00
// Send DATA
socket.write('DATA\r\n');
2025-05-24 00:23:35 +00:00
state = 'data_sent';
2025-05-23 19:03:44 +00:00
dataBuffer = '';
2025-05-24 00:23:35 +00:00
} else if (dataBuffer.includes('354 ') && state === 'data_sent') {
2025-05-23 19:03:44 +00:00
// 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);
2025-05-24 00:23:35 +00:00
state = 'email_sent';
2025-05-23 19:03:44 +00:00
dataBuffer = '';
2025-05-24 00:23:35 +00:00
} else if ((dataBuffer.includes('250 OK') && state === 'email_sent') ||
2025-05-23 19:03:44 +00:00
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 = '';
2025-05-24 00:23:35 +00:00
let state = 'initial';
2025-05-23 19:03:44 +00:00
socket.on('data', (data) => {
dataBuffer += data.toString();
console.log('Server response:', data.toString());
2025-05-24 00:23:35 +00:00
if (dataBuffer.includes('220 ') && state === 'initial') {
2025-05-23 19:03:44 +00:00
socket.write('EHLO testclient\r\n');
2025-05-24 00:23:35 +00:00
state = 'ehlo_sent';
dataBuffer = '';
} else if (dataBuffer.includes('250 ') && state === 'ehlo_sent') {
2025-05-23 19:03:44 +00:00
socket.write('MAIL FROM:<sender@example.com>\r\n');
2025-05-24 00:23:35 +00:00
state = 'mail_from_sent';
2025-05-23 19:03:44 +00:00
dataBuffer = '';
2025-05-24 00:23:35 +00:00
} else if (dataBuffer.includes('250 ') && state === 'mail_from_sent') {
2025-05-23 19:03:44 +00:00
socket.write('RCPT TO:<recipient@example.com>\r\n');
2025-05-24 00:23:35 +00:00
state = 'rcpt_to_sent';
2025-05-23 19:03:44 +00:00
dataBuffer = '';
2025-05-24 00:23:35 +00:00
} else if (dataBuffer.includes('250 ') && state === 'rcpt_to_sent') {
2025-05-23 19:03:44 +00:00
socket.write('DATA\r\n');
2025-05-24 00:23:35 +00:00
state = 'data_sent';
2025-05-23 19:03:44 +00:00
dataBuffer = '';
2025-05-24 00:23:35 +00:00
} else if (dataBuffer.includes('354 ') && state === 'data_sent') {
2025-05-23 19:03:44 +00:00
// 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);
2025-05-24 00:23:35 +00:00
state = 'email_sent';
2025-05-23 19:03:44 +00:00
dataBuffer = '';
2025-05-24 00:23:35 +00:00
} else if ((dataBuffer.includes('250 OK') && state === 'email_sent') ||
2025-05-23 19:03:44 +00:00
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 = '';
2025-05-24 00:23:35 +00:00
let state = 'initial';
2025-05-23 19:03:44 +00:00
socket.on('data', (data) => {
dataBuffer += data.toString();
console.log('Server response:', data.toString());
2025-05-24 00:23:35 +00:00
if (dataBuffer.includes('220 ') && state === 'initial') {
2025-05-23 19:03:44 +00:00
socket.write('EHLO testclient\r\n');
2025-05-24 00:23:35 +00:00
state = 'ehlo_sent';
dataBuffer = '';
} else if (dataBuffer.includes('250 ') && state === 'ehlo_sent') {
2025-05-23 19:03:44 +00:00
socket.write('MAIL FROM:<sender@example.com>\r\n');
2025-05-24 00:23:35 +00:00
state = 'mail_from_sent';
2025-05-23 19:03:44 +00:00
dataBuffer = '';
2025-05-24 00:23:35 +00:00
} else if (dataBuffer.includes('250 ') && state === 'mail_from_sent') {
2025-05-23 19:03:44 +00:00
socket.write('RCPT TO:<recipient@example.com>\r\n');
2025-05-24 00:23:35 +00:00
state = 'rcpt_to_sent';
2025-05-23 19:03:44 +00:00
dataBuffer = '';
2025-05-24 00:23:35 +00:00
} else if (dataBuffer.includes('250 ') && state === 'rcpt_to_sent') {
2025-05-23 19:03:44 +00:00
socket.write('DATA\r\n');
2025-05-24 00:23:35 +00:00
state = 'data_sent';
2025-05-23 19:03:44 +00:00
dataBuffer = '';
2025-05-24 00:23:35 +00:00
} else if (dataBuffer.includes('354 ') && state === 'data_sent') {
2025-05-23 19:03:44 +00:00
// 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);
2025-05-24 00:23:35 +00:00
state = 'email_sent';
2025-05-23 19:03:44 +00:00
dataBuffer = '';
2025-05-24 00:23:35 +00:00
} else if ((dataBuffer.includes('250 OK') && state === 'email_sent') ||
2025-05-23 19:03:44 +00:00
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);
});
2025-05-25 19:05:43 +00:00
export default tap.start();