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) => ``).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 ✓`); });