import { tap, expect } from '@git.zone/tstest/tapbundle'; import * as net from 'net'; import { startTestServer, stopTestServer, type ITestServer } from '../../helpers/server.loader.js'; const TEST_PORT = 2525; const TEST_TIMEOUT = 30000; let testServer: ITestServer; tap.test('setup - start test server', async () => { testServer = await startTestServer({ port: TEST_PORT }); await new Promise(resolve => setTimeout(resolve, 1000)); expect(testServer).toBeDefined(); }); tap.test('Extremely Long Headers - should handle single extremely long header', async (tools) => { const done = tools.defer(); try { const socket = net.createConnection({ host: 'localhost', port: TEST_PORT, timeout: TEST_TIMEOUT }); await new Promise((resolve, reject) => { socket.once('connect', () => resolve()); socket.once('error', reject); }); // Get banner await new Promise((resolve) => { socket.once('data', (chunk) => resolve(chunk.toString())); }); // Send EHLO socket.write('EHLO testclient\r\n'); await new Promise((resolve) => { let data = ''; const handler = (chunk: Buffer) => { data += chunk.toString(); if (data.includes('\r\n') && (data.match(/^250 /m) || data.match(/^250-.*\r\n250 /ms))) { socket.removeListener('data', handler); resolve(data); } }; socket.on('data', handler); }); // Send MAIL FROM socket.write('MAIL FROM:\r\n'); const mailResponse = await new Promise((resolve) => { socket.once('data', (chunk) => resolve(chunk.toString())); }); expect(mailResponse).toInclude('250'); // Send RCPT TO socket.write('RCPT TO:\r\n'); const rcptResponse = await new Promise((resolve) => { socket.once('data', (chunk) => resolve(chunk.toString())); }); expect(rcptResponse).toInclude('250'); // Send DATA socket.write('DATA\r\n'); const dataResponse = await new Promise((resolve) => { socket.once('data', (chunk) => resolve(chunk.toString())); }); expect(dataResponse).toInclude('354'); // Send email with extremely long header (3000 characters) const longValue = 'X'.repeat(3000); const emailContent = [ `Subject: Test Email`, `From: sender@example.com`, `To: recipient@example.com`, `X-Long-Header: ${longValue}`, '', 'This email has an extremely long header.', '.', '' ].join('\r\n'); socket.write(emailContent); const finalResponse = await new Promise((resolve) => { socket.once('data', (chunk) => resolve(chunk.toString())); }); // Server might accept or reject - both are valid for extremely long headers const accepted = finalResponse.includes('250'); const rejected = finalResponse.includes('552') || finalResponse.includes('554') || finalResponse.includes('500'); console.log(`Long header test ${accepted ? 'accepted' : 'rejected'}: ${finalResponse.trim()}`); expect(accepted || rejected).toEqual(true); // Clean up socket.write('QUIT\r\n'); socket.end(); } finally { done.resolve(); } }); tap.test('Extremely Long Headers - should handle multi-line header with many segments', async (tools) => { const done = tools.defer(); try { const socket = net.createConnection({ host: 'localhost', port: TEST_PORT, timeout: TEST_TIMEOUT }); await new Promise((resolve, reject) => { socket.once('connect', () => resolve()); socket.once('error', reject); }); // Get banner await new Promise((resolve) => { socket.once('data', (chunk) => resolve(chunk.toString())); }); // Send EHLO socket.write('EHLO testclient\r\n'); await new Promise((resolve) => { let data = ''; const handler = (chunk: Buffer) => { data += chunk.toString(); if (data.includes('\r\n') && (data.match(/^250 /m) || data.match(/^250-.*\r\n250 /ms))) { socket.removeListener('data', handler); resolve(data); } }; socket.on('data', handler); }); // Send MAIL FROM socket.write('MAIL FROM:\r\n'); const mailResponse = await new Promise((resolve) => { socket.once('data', (chunk) => resolve(chunk.toString())); }); expect(mailResponse).toInclude('250'); // Send RCPT TO socket.write('RCPT TO:\r\n'); const rcptResponse = await new Promise((resolve) => { socket.once('data', (chunk) => resolve(chunk.toString())); }); expect(rcptResponse).toInclude('250'); // Send DATA socket.write('DATA\r\n'); const dataResponse = await new Promise((resolve) => { socket.once('data', (chunk) => resolve(chunk.toString())); }); expect(dataResponse).toInclude('354'); // Create multi-line header with 50 segments (RFC 5322 folding) const segments = []; for (let i = 0; i < 50; i++) { segments.push(` Segment ${i}: ${' '.repeat(60)}value`); } const emailContent = [ `Subject: Test Email`, `From: sender@example.com`, `To: recipient@example.com`, `X-Multi-Line: Initial value`, ...segments, '', 'This email has a multi-line header with many segments.', '.', '' ].join('\r\n'); socket.write(emailContent); const finalResponse = await new Promise((resolve) => { socket.once('data', (chunk) => resolve(chunk.toString())); }); const accepted = finalResponse.includes('250'); const rejected = finalResponse.includes('552') || finalResponse.includes('554') || finalResponse.includes('500'); console.log(`Multi-line header test ${accepted ? 'accepted' : 'rejected'}: ${finalResponse.trim()}`); expect(accepted || rejected).toEqual(true); // Clean up socket.write('QUIT\r\n'); socket.end(); } finally { done.resolve(); } }); tap.test('Extremely Long Headers - should handle multiple long headers in one email', async (tools) => { const done = tools.defer(); try { const socket = net.createConnection({ host: 'localhost', port: TEST_PORT, timeout: TEST_TIMEOUT }); await new Promise((resolve, reject) => { socket.once('connect', () => resolve()); socket.once('error', reject); }); // Get banner await new Promise((resolve) => { socket.once('data', (chunk) => resolve(chunk.toString())); }); // Send EHLO socket.write('EHLO testclient\r\n'); await new Promise((resolve) => { let data = ''; const handler = (chunk: Buffer) => { data += chunk.toString(); if (data.includes('\r\n') && (data.match(/^250 /m) || data.match(/^250-.*\r\n250 /ms))) { socket.removeListener('data', handler); resolve(data); } }; socket.on('data', handler); }); // Send MAIL FROM socket.write('MAIL FROM:\r\n'); const mailResponse = await new Promise((resolve) => { socket.once('data', (chunk) => resolve(chunk.toString())); }); expect(mailResponse).toInclude('250'); // Send RCPT TO socket.write('RCPT TO:\r\n'); const rcptResponse = await new Promise((resolve) => { socket.once('data', (chunk) => resolve(chunk.toString())); }); expect(rcptResponse).toInclude('250'); // Send DATA socket.write('DATA\r\n'); const dataResponse = await new Promise((resolve) => { socket.once('data', (chunk) => resolve(chunk.toString())); }); expect(dataResponse).toInclude('354'); // Create multiple long headers const header1 = 'A'.repeat(1000); const header2 = 'B'.repeat(1500); const header3 = 'C'.repeat(2000); const emailContent = [ `Subject: Test Email with Multiple Long Headers`, `From: sender@example.com`, `To: recipient@example.com`, `X-Long-Header-1: ${header1}`, `X-Long-Header-2: ${header2}`, `X-Long-Header-3: ${header3}`, '', 'This email has multiple long headers.', '.', '' ].join('\r\n'); const totalHeaderSize = header1.length + header2.length + header3.length; console.log(`Total header size: ${totalHeaderSize} bytes`); socket.write(emailContent); const finalResponse = await new Promise((resolve) => { socket.once('data', (chunk) => resolve(chunk.toString())); }); const accepted = finalResponse.includes('250'); const rejected = finalResponse.includes('552') || finalResponse.includes('554') || finalResponse.includes('500'); console.log(`Multiple long headers test ${accepted ? 'accepted' : 'rejected'}: ${finalResponse.trim()}`); expect(accepted || rejected).toEqual(true); // Clean up socket.write('QUIT\r\n'); socket.end(); } finally { done.resolve(); } }); tap.test('Extremely Long Headers - should handle header with exactly RFC limit', async (tools) => { const done = tools.defer(); try { const socket = net.createConnection({ host: 'localhost', port: TEST_PORT, timeout: TEST_TIMEOUT }); await new Promise((resolve, reject) => { socket.once('connect', () => resolve()); socket.once('error', reject); }); // Get banner await new Promise((resolve) => { socket.once('data', (chunk) => resolve(chunk.toString())); }); // Send EHLO socket.write('EHLO testclient\r\n'); await new Promise((resolve) => { let data = ''; const handler = (chunk: Buffer) => { data += chunk.toString(); if (data.includes('\r\n') && (data.match(/^250 /m) || data.match(/^250-.*\r\n250 /ms))) { socket.removeListener('data', handler); resolve(data); } }; socket.on('data', handler); }); // Send MAIL FROM socket.write('MAIL FROM:\r\n'); const mailResponse = await new Promise((resolve) => { socket.once('data', (chunk) => resolve(chunk.toString())); }); expect(mailResponse).toInclude('250'); // Send RCPT TO socket.write('RCPT TO:\r\n'); const rcptResponse = await new Promise((resolve) => { socket.once('data', (chunk) => resolve(chunk.toString())); }); expect(rcptResponse).toInclude('250'); // Send DATA socket.write('DATA\r\n'); const dataResponse = await new Promise((resolve) => { socket.once('data', (chunk) => resolve(chunk.toString())); }); expect(dataResponse).toInclude('354'); // Create header line exactly at RFC 5322 limit (998 chars excluding CRLF) // Header name and colon take some space const headerName = 'X-RFC-Limit'; const colonSpace = ': '; const remainingSpace = 998 - headerName.length - colonSpace.length; const headerValue = 'X'.repeat(remainingSpace); const emailContent = [ `Subject: Test Email`, `From: sender@example.com`, `To: recipient@example.com`, `${headerName}${colonSpace}${headerValue}`, '', 'This email has a header at exactly the RFC limit.', '.', '' ].join('\r\n'); socket.write(emailContent); const finalResponse = await new Promise((resolve) => { socket.once('data', (chunk) => resolve(chunk.toString())); }); // This should be accepted since it's exactly at the limit const accepted = finalResponse.includes('250'); const rejected = finalResponse.includes('552') || finalResponse.includes('554') || finalResponse.includes('500'); console.log(`RFC limit header test ${accepted ? 'accepted' : 'rejected'}: ${finalResponse.trim()}`); expect(accepted || rejected).toEqual(true); // RFC compliant servers should accept headers exactly at the limit if (accepted) { console.log('✓ Server correctly accepts headers at RFC limit'); } else { console.log('⚠ Server rejected header at RFC limit (may be overly strict)'); } // Clean up socket.write('QUIT\r\n'); socket.end(); } finally { done.resolve(); } }); tap.test('cleanup - stop test server', async () => { await stopTestServer(testServer); expect(true).toEqual(true); }); export default tap.start();