dcrouter/test/suite/smtpclient_edge-cases/test.cedge-06.large-headers.ts
2025-05-24 17:00:59 +00:00

564 lines
19 KiB
TypeScript

import { expect, tap } from '@git.zone/tstest/tapbundle';
import * as plugins from './plugins.js';
import { createTestServer } from '../../helpers/server.loader.js';
import { createSmtpClient } from '../../helpers/smtp.client.js';
tap.test('CEDGE-06: should handle large headers gracefully', async (tools) => {
const testId = 'CEDGE-06-large-headers';
console.log(`\n${testId}: Testing large header handling...`);
let scenarioCount = 0;
// Scenario 1: Very long subject lines
await (async () => {
scenarioCount++;
console.log(`\nScenario ${scenarioCount}: Testing very long subject lines`);
const testServer = await createTestServer({
onConnection: async (socket) => {
console.log(' [Server] Client connected');
socket.write('220 mail.example.com ESMTP\r\n');
let inData = false;
let subjectLength = 0;
socket.on('data', (data) => {
const text = data.toString();
if (inData) {
// Look for Subject header
const subjectMatch = text.match(/^Subject:\s*(.+?)(?:\r\n(?:\s+.+)?)*\r\n/m);
if (subjectMatch) {
subjectLength = subjectMatch[0].length;
console.log(` [Server] Subject header length: ${subjectLength} chars`);
}
if (text.includes('\r\n.\r\n')) {
inData = false;
socket.write('250 OK\r\n');
}
return;
}
const command = text.trim();
console.log(` [Server] Received: ${command}`);
if (command.startsWith('EHLO')) {
socket.write('250-mail.example.com\r\n');
socket.write('250 OK\r\n');
} else if (command.startsWith('MAIL FROM:')) {
socket.write('250 OK\r\n');
} else if (command.startsWith('RCPT TO:')) {
socket.write('250 OK\r\n');
} else if (command === 'DATA') {
socket.write('354 Start mail input\r\n');
inData = true;
} else if (command === 'QUIT') {
socket.write('221 Bye\r\n');
socket.end();
}
});
}
});
const smtpClient = createSmtpClient({
host: testServer.hostname,
port: testServer.port,
secure: false
});
// Test various subject lengths
const subjectLengths = [100, 500, 1000, 2000, 5000];
for (const length of subjectLengths) {
const subject = 'A'.repeat(length);
console.log(` Testing subject with ${length} characters...`);
const email = new plugins.smartmail.Email({
from: 'sender@example.com',
to: ['recipient@example.com'],
subject: subject,
text: 'Testing long subject header folding'
});
const result = await smtpClient.sendMail(email);
expect(result).toBeDefined();
expect(result.messageId).toBeDefined();
}
await testServer.server.close();
})();
// Scenario 2: Many recipients (large To/Cc/Bcc headers)
await (async () => {
scenarioCount++;
console.log(`\nScenario ${scenarioCount}: Testing many recipients`);
const testServer = await createTestServer({
onConnection: async (socket) => {
console.log(' [Server] Client connected');
socket.write('220 mail.example.com ESMTP\r\n');
let recipientCount = 0;
socket.on('data', (data) => {
const command = data.toString().trim();
if (command.startsWith('RCPT TO:')) {
recipientCount++;
console.log(` [Server] Recipient ${recipientCount}`);
socket.write('250 OK\r\n');
} else if (command.startsWith('EHLO')) {
socket.write('250-mail.example.com\r\n');
socket.write('250 OK\r\n');
} else if (command.startsWith('MAIL FROM:')) {
recipientCount = 0;
socket.write('250 OK\r\n');
} else if (command === 'DATA') {
console.log(` [Server] Total recipients: ${recipientCount}`);
socket.write('354 Start mail input\r\n');
} else if (command === '.') {
socket.write('250 OK\r\n');
} else if (command === 'QUIT') {
socket.write('221 Bye\r\n');
socket.end();
}
});
}
});
const smtpClient = createSmtpClient({
host: testServer.hostname,
port: testServer.port,
secure: false
});
// Create email with many recipients
const recipientCounts = [10, 50, 100];
for (const count of recipientCounts) {
console.log(` Testing with ${count} recipients...`);
const toAddresses = Array(Math.floor(count / 3))
.fill(null)
.map((_, i) => `recipient${i + 1}@example.com`);
const ccAddresses = Array(Math.floor(count / 3))
.fill(null)
.map((_, i) => `cc${i + 1}@example.com`);
const bccAddresses = Array(count - toAddresses.length - ccAddresses.length)
.fill(null)
.map((_, i) => `bcc${i + 1}@example.com`);
const email = new plugins.smartmail.Email({
from: 'sender@example.com',
to: toAddresses,
cc: ccAddresses,
bcc: bccAddresses,
subject: `Test with ${count} total recipients`,
text: 'Testing large recipient lists'
});
const result = await smtpClient.sendMail(email);
expect(result).toBeDefined();
expect(result.messageId).toBeDefined();
}
await testServer.server.close();
})();
// Scenario 3: Many custom headers
await (async () => {
scenarioCount++;
console.log(`\nScenario ${scenarioCount}: Testing many custom headers`);
const testServer = await createTestServer({
onConnection: async (socket) => {
console.log(' [Server] Client connected');
socket.write('220 mail.example.com ESMTP\r\n');
let inData = false;
let headerCount = 0;
socket.on('data', (data) => {
const text = data.toString();
if (inData) {
// Count headers
const headerLines = text.split('\r\n');
headerLines.forEach(line => {
if (line.match(/^[A-Za-z0-9-]+:\s*.+$/)) {
headerCount++;
}
});
if (text.includes('\r\n.\r\n')) {
inData = false;
console.log(` [Server] Total headers: ${headerCount}`);
socket.write('250 OK\r\n');
headerCount = 0;
}
return;
}
const command = text.trim();
console.log(` [Server] Received: ${command}`);
if (command.startsWith('EHLO')) {
socket.write('250-mail.example.com\r\n');
socket.write('250 OK\r\n');
} else if (command.startsWith('MAIL FROM:')) {
socket.write('250 OK\r\n');
} else if (command.startsWith('RCPT TO:')) {
socket.write('250 OK\r\n');
} else if (command === 'DATA') {
socket.write('354 Start mail input\r\n');
inData = true;
} else if (command === 'QUIT') {
socket.write('221 Bye\r\n');
socket.end();
}
});
}
});
const smtpClient = createSmtpClient({
host: testServer.hostname,
port: testServer.port,
secure: false
});
// Create email with many headers
const headerCounts = [10, 50, 100];
for (const count of headerCounts) {
console.log(` Testing with ${count} custom headers...`);
const headers: { [key: string]: string } = {};
for (let i = 0; i < count; i++) {
headers[`X-Custom-Header-${i}`] = `This is custom header value number ${i} with some additional text to make it longer`;
}
const email = new plugins.smartmail.Email({
from: 'sender@example.com',
to: ['recipient@example.com'],
subject: `Test with ${count} headers`,
text: 'Testing many custom headers',
headers
});
const result = await smtpClient.sendMail(email);
expect(result).toBeDefined();
expect(result.messageId).toBeDefined();
}
await testServer.server.close();
})();
// Scenario 4: Very long header values
await (async () => {
scenarioCount++;
console.log(`\nScenario ${scenarioCount}: Testing very long header values`);
const testServer = await createTestServer({
onConnection: async (socket) => {
console.log(' [Server] Client connected');
socket.write('220 mail.example.com ESMTP\r\n');
let inData = false;
let maxHeaderLength = 0;
socket.on('data', (data) => {
const text = data.toString();
if (inData) {
// Find longest header
const headers = text.match(/^[A-Za-z0-9-]+:\s*.+?(?=\r\n(?:[A-Za-z0-9-]+:|$))/gms);
if (headers) {
headers.forEach(header => {
if (header.length > maxHeaderLength) {
maxHeaderLength = header.length;
console.log(` [Server] New longest header: ${header.substring(0, 50)}... (${header.length} chars)`);
}
});
}
if (text.includes('\r\n.\r\n')) {
inData = false;
socket.write('250 OK\r\n');
}
return;
}
const command = text.trim();
console.log(` [Server] Received: ${command}`);
if (command.startsWith('EHLO')) {
socket.write('250-mail.example.com\r\n');
socket.write('250 OK\r\n');
} else if (command.startsWith('MAIL FROM:')) {
socket.write('250 OK\r\n');
} else if (command.startsWith('RCPT TO:')) {
socket.write('250 OK\r\n');
} else if (command === 'DATA') {
socket.write('354 Start mail input\r\n');
inData = true;
} else if (command === 'QUIT') {
socket.write('221 Bye\r\n');
socket.end();
}
});
}
});
const smtpClient = createSmtpClient({
host: testServer.hostname,
port: testServer.port,
secure: false
});
// Test with very long header values
const longValues = [
'A'.repeat(500),
'Word '.repeat(200), // Many words
Array(100).fill('item').join(', '), // Comma-separated list
'This is a very long header value that contains multiple sentences. ' +
'Each sentence adds to the overall length of the header. ' +
'The header should be properly folded according to RFC 5322. ' +
'This ensures compatibility with various email servers and clients. '.repeat(5)
];
for (let i = 0; i < longValues.length; i++) {
console.log(` Testing long header value ${i + 1} (${longValues[i].length} chars)...`);
const email = new plugins.smartmail.Email({
from: 'sender@example.com',
to: ['recipient@example.com'],
subject: `Test ${i + 1}`,
text: 'Testing long header values',
headers: {
'X-Long-Header': longValues[i],
'X-Another-Long': longValues[i].split('').reverse().join('')
}
});
const result = await smtpClient.sendMail(email);
expect(result).toBeDefined();
expect(result.messageId).toBeDefined();
}
await testServer.server.close();
})();
// Scenario 5: Headers with special folding requirements
await (async () => {
scenarioCount++;
console.log(`\nScenario ${scenarioCount}: Testing header folding edge cases`);
const testServer = await createTestServer({
onConnection: async (socket) => {
console.log(' [Server] Client connected');
socket.write('220 mail.example.com ESMTP\r\n');
let inData = false;
socket.on('data', (data) => {
const text = data.toString();
if (inData) {
// Check for proper folding (continuation lines start with whitespace)
const lines = text.split('\r\n');
let foldedHeaders = 0;
lines.forEach((line, index) => {
if (index > 0 && (line.startsWith(' ') || line.startsWith('\t'))) {
foldedHeaders++;
}
});
if (foldedHeaders > 0) {
console.log(` [Server] Found ${foldedHeaders} folded header lines`);
}
if (text.includes('\r\n.\r\n')) {
inData = false;
socket.write('250 OK\r\n');
}
return;
}
const command = text.trim();
console.log(` [Server] Received: ${command}`);
if (command.startsWith('EHLO')) {
socket.write('250-mail.example.com\r\n');
socket.write('250 OK\r\n');
} else if (command.startsWith('MAIL FROM:')) {
socket.write('250 OK\r\n');
} else if (command.startsWith('RCPT TO:')) {
socket.write('250 OK\r\n');
} else if (command === 'DATA') {
socket.write('354 Start mail input\r\n');
inData = true;
} else if (command === 'QUIT') {
socket.write('221 Bye\r\n');
socket.end();
}
});
}
});
const smtpClient = createSmtpClient({
host: testServer.hostname,
port: testServer.port,
secure: false
});
// Test headers that require special folding
const email = new plugins.smartmail.Email({
from: 'sender@example.com',
to: ['recipient@example.com'],
subject: 'Testing header folding',
text: 'Testing special folding cases',
headers: {
// Long header with no natural break points
'X-No-Spaces': 'A'.repeat(100),
// Header with URLs that shouldn't be broken
'X-URLs': 'Visit https://example.com/very/long/path/that/should/not/be/broken/in/the/middle and https://another-example.com/another/very/long/path',
// Header with quoted strings
'X-Quoted': '"This is a very long quoted string that should be kept together if possible when folding the header" and some more text',
// Header with structured data
'X-Structured': 'key1=value1; key2="a very long value that might need folding"; key3=value3; key4="another long value"',
// References header (common to have many message IDs)
'References': Array(20).fill(null).map((_, i) => `<message-id-${i}@example.com>`).join(' ')
}
});
const result = await smtpClient.sendMail(email);
console.log(` Result: ${result.messageId ? 'Success' : 'Failed'}`);
expect(result).toBeDefined();
expect(result.messageId).toBeDefined();
await testServer.server.close();
})();
// Scenario 6: Headers at server limits
await (async () => {
scenarioCount++;
console.log(`\nScenario ${scenarioCount}: Testing headers at server limits`);
const maxHeaderSize = 8192; // Common limit
const testServer = await createTestServer({
onConnection: async (socket) => {
console.log(' [Server] Client connected');
socket.write('220 mail.example.com ESMTP\r\n');
let inData = false;
let headerSection = '';
socket.on('data', (data) => {
const text = data.toString();
if (inData) {
if (!headerSection && text.includes('\r\n\r\n')) {
// Extract header section
headerSection = text.substring(0, text.indexOf('\r\n\r\n'));
console.log(` [Server] Header section size: ${headerSection.length} bytes`);
if (headerSection.length > maxHeaderSize) {
socket.write('552 5.3.4 Header size exceeds maximum allowed\r\n');
socket.end();
return;
}
}
if (text.includes('\r\n.\r\n')) {
inData = false;
socket.write('250 OK\r\n');
headerSection = '';
}
return;
}
const command = text.trim();
console.log(` [Server] Received: ${command}`);
if (command.startsWith('EHLO')) {
socket.write('250-mail.example.com\r\n');
socket.write('250 OK\r\n');
} else if (command.startsWith('MAIL FROM:')) {
socket.write('250 OK\r\n');
} else if (command.startsWith('RCPT TO:')) {
socket.write('250 OK\r\n');
} else if (command === 'DATA') {
socket.write('354 Start mail input\r\n');
inData = true;
} else if (command === 'QUIT') {
socket.write('221 Bye\r\n');
socket.end();
}
});
}
});
const smtpClient = createSmtpClient({
host: testServer.hostname,
port: testServer.port,
secure: false
});
// Test with headers near the limit
const testSizes = [
{ size: 1000, desc: 'well below limit' },
{ size: 7000, desc: 'near limit' },
{ size: 8000, desc: 'very close to limit' }
];
for (const test of testSizes) {
console.log(` Testing with header size ${test.desc} (${test.size} bytes)...`);
// Create headers that total approximately the target size
const headers: { [key: string]: string } = {};
let currentSize = 0;
let headerIndex = 0;
while (currentSize < test.size) {
const headerName = `X-Test-Header-${headerIndex}`;
const remainingSize = test.size - currentSize;
const headerValue = 'A'.repeat(Math.min(remainingSize - headerName.length - 4, 200)); // -4 for ": \r\n"
headers[headerName] = headerValue;
currentSize += headerName.length + headerValue.length + 4;
headerIndex++;
}
const email = new plugins.smartmail.Email({
from: 'sender@example.com',
to: ['recipient@example.com'],
subject: `Testing ${test.desc}`,
text: 'Testing header size limits',
headers
});
try {
const result = await smtpClient.sendMail(email);
console.log(` Result: Success`);
expect(result).toBeDefined();
expect(result.messageId).toBeDefined();
} catch (error) {
console.log(` Result: Failed (${error.message})`);
// This is expected for very large headers
}
}
await testServer.server.close();
})();
console.log(`\n${testId}: All ${scenarioCount} large header scenarios tested ✓`);
});