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-02: should handle malformed commands gracefully', async (tools) => { const testId = 'CEDGE-02-malformed-commands'; console.log(`\n${testId}: Testing malformed command handling...`); let scenarioCount = 0; // Scenario 1: Commands with extra spaces await (async () => { scenarioCount++; console.log(`\nScenario ${scenarioCount}: Testing commands with extra spaces`); const testServer = await createTestServer({ onConnection: async (socket) => { console.log(' [Server] Client connected'); socket.write('220 mail.example.com ESMTP\r\n'); socket.on('data', (data) => { const command = data.toString().trim(); console.log(` [Server] Received: ${command}`); // Accept commands with extra spaces if (command.match(/^EHLO\s+/i)) { socket.write('250-mail.example.com\r\n'); socket.write('250 STARTTLS\r\n'); } else if (command.match(/^MAIL\s+FROM:/i)) { // Even with multiple spaces socket.write('250 OK\r\n'); } else if (command.match(/^RCPT\s+TO:/i)) { socket.write('250 OK\r\n'); } else if (command === 'DATA') { 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 }); // Test sending with commands that might have extra spaces const email = new plugins.smartmail.Email({ from: 'sender@example.com', to: ['recipient@example.com'], subject: 'Test with extra spaces', text: 'Testing command formatting' }); 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 2: Commands with incorrect case mixing await (async () => { scenarioCount++; console.log(`\nScenario ${scenarioCount}: Testing mixed case commands`); const testServer = await createTestServer({ onConnection: async (socket) => { console.log(' [Server] Client connected'); socket.write('220 mail.example.com ESMTP\r\n'); socket.on('data', (data) => { const command = data.toString().trim(); console.log(` [Server] Received: ${command}`); // RFC says commands should be case-insensitive const upperCommand = command.toUpperCase(); if (upperCommand.startsWith('EHLO')) { socket.write('250-mail.example.com\r\n'); socket.write('250 8BITMIME\r\n'); } else if (upperCommand.startsWith('MAIL FROM:')) { socket.write('250 OK\r\n'); } else if (upperCommand.startsWith('RCPT TO:')) { socket.write('250 OK\r\n'); } else if (upperCommand === 'DATA') { socket.write('354 Start mail input\r\n'); } else if (command === '.') { socket.write('250 OK\r\n'); } else if (upperCommand === 'QUIT') { socket.write('221 Bye\r\n'); socket.end(); } }); } }); const smtpClient = createSmtpClient({ host: testServer.hostname, port: testServer.port, secure: false }); const email = new plugins.smartmail.Email({ from: 'sender@example.com', to: ['recipient@example.com'], subject: 'Mixed case test', text: 'Testing case sensitivity' }); 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 3: Commands with missing parameters await (async () => { scenarioCount++; console.log(`\nScenario ${scenarioCount}: Testing commands with missing parameters`); const testServer = await createTestServer({ onConnection: async (socket) => { console.log(' [Server] Client connected'); socket.write('220 mail.example.com ESMTP\r\n'); socket.on('data', (data) => { const command = data.toString().trim(); console.log(` [Server] Received: ${command}`); if (command.startsWith('EHLO')) { if (command === 'EHLO' || command === 'EHLO ') { // Missing hostname socket.write('501 Syntax error in parameters or arguments\r\n'); } else { socket.write('250-mail.example.com\r\n'); socket.write('250 OK\r\n'); } } else if (command === 'MAIL FROM:' || command === 'MAIL') { // Missing address socket.write('501 Syntax error in parameters or arguments\r\n'); } else if (command.startsWith('MAIL FROM:')) { socket.write('250 OK\r\n'); } else if (command === 'RCPT TO:' || command === 'RCPT') { // Missing address socket.write('501 Syntax error in parameters or arguments\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'); } else if (command === '.') { socket.write('250 OK\r\n'); } else if (command === 'QUIT') { socket.write('221 Bye\r\n'); socket.end(); } }); } }); // Test with a forgiving client that handles syntax errors const smtpClient = createSmtpClient({ host: testServer.hostname, port: testServer.port, secure: false }); const email = new plugins.smartmail.Email({ from: 'sender@example.com', to: ['recipient@example.com'], subject: 'Syntax error recovery test', text: 'Testing error recovery' }); const result = await smtpClient.sendMail(email); console.log(` Result: ${result.messageId ? 'Success' : 'Failed'}`); expect(result).toBeDefined(); await testServer.server.close(); })(); // Scenario 4: Commands with invalid syntax await (async () => { scenarioCount++; console.log(`\nScenario ${scenarioCount}: Testing invalid command syntax`); const testServer = await createTestServer({ onConnection: async (socket) => { console.log(' [Server] Client connected'); socket.write('220 mail.example.com ESMTP\r\n'); let state = 'connected'; socket.on('data', (data) => { const command = data.toString().trim(); console.log(` [Server] Received: ${command}`); if (command.startsWith('EHLO')) { socket.write('250-mail.example.com\r\n'); socket.write('250 OK\r\n'); state = 'ready'; } else if (command.includes('FROM') && !command.startsWith('MAIL FROM:')) { // Invalid MAIL command format socket.write('500 Syntax error, command unrecognized\r\n'); } else if (command.startsWith('MAIL FROM:')) { socket.write('250 OK\r\n'); state = 'mail'; } else if (command.includes('TO') && !command.startsWith('RCPT TO:')) { // Invalid RCPT command format socket.write('500 Syntax error, command unrecognized\r\n'); } else if (command.startsWith('RCPT TO:')) { socket.write('250 OK\r\n'); state = 'rcpt'; } else if (command === 'DATA') { socket.write('354 Start mail input\r\n'); state = 'data'; } else if (command === '.' && state === 'data') { socket.write('250 OK\r\n'); state = 'done'; } else if (command === 'QUIT') { socket.write('221 Bye\r\n'); socket.end(); } else if (state !== 'data') { // Unknown command socket.write('502 Command not implemented\r\n'); } }); } }); const smtpClient = createSmtpClient({ host: testServer.hostname, port: testServer.port, secure: false }); const email = new plugins.smartmail.Email({ from: 'sender@example.com', to: ['recipient@example.com'], subject: 'Invalid syntax test', text: 'Testing invalid command handling' }); const result = await smtpClient.sendMail(email); console.log(` Result: ${result.messageId ? 'Success' : 'Failed'}`); expect(result).toBeDefined(); await testServer.server.close(); })(); // Scenario 5: Commands sent out of order await (async () => { scenarioCount++; console.log(`\nScenario ${scenarioCount}: Testing out-of-order commands`); const testServer = await createTestServer({ onConnection: async (socket) => { console.log(' [Server] Client connected'); socket.write('220 mail.example.com ESMTP\r\n'); let state = 'connected'; socket.on('data', (data) => { const command = data.toString().trim(); console.log(` [Server] Received: ${command} (state: ${state})`); if (command.startsWith('EHLO')) { socket.write('250-mail.example.com\r\n'); socket.write('250 OK\r\n'); state = 'ready'; } else if (command.startsWith('RCPT TO:') && state !== 'mail' && state !== 'rcpt') { // RCPT before MAIL socket.write('503 Bad sequence of commands\r\n'); } else if (command.startsWith('DATA') && state !== 'rcpt') { // DATA before RCPT socket.write('503 Bad sequence of commands\r\n'); } else if (command.startsWith('MAIL FROM:')) { if (state === 'ready' || state === 'done') { socket.write('250 OK\r\n'); state = 'mail'; } else { socket.write('503 Bad sequence of commands\r\n'); } } else if (command.startsWith('RCPT TO:')) { socket.write('250 OK\r\n'); state = 'rcpt'; } else if (command === 'DATA') { socket.write('354 Start mail input\r\n'); state = 'data'; } else if (command === '.' && state === 'data') { socket.write('250 OK\r\n'); state = 'done'; } else if (command === 'QUIT') { socket.write('221 Bye\r\n'); socket.end(); } }); } }); const smtpClient = createSmtpClient({ host: testServer.hostname, port: testServer.port, secure: false }); const email = new plugins.smartmail.Email({ from: 'sender@example.com', to: ['recipient@example.com'], subject: 'Command sequence test', text: 'Testing command ordering' }); const result = await smtpClient.sendMail(email); console.log(` Result: ${result.messageId ? 'Success' : 'Failed'}`); expect(result).toBeDefined(); await testServer.server.close(); })(); // Scenario 6: Commands with invalid characters await (async () => { scenarioCount++; console.log(`\nScenario ${scenarioCount}: Testing commands with invalid characters`); const testServer = await createTestServer({ onConnection: async (socket) => { console.log(' [Server] Client connected'); socket.write('220 mail.example.com ESMTP\r\n'); socket.on('data', (data) => { const command = data.toString(); const printable = command.replace(/[\r\n]/g, '\\r\\n').replace(/[^\x20-\x7E]/g, '?'); console.log(` [Server] Received: ${printable}`); // Check for non-ASCII characters if (/[^\x00-\x7F]/.test(command)) { socket.write('500 Syntax error, non-ASCII characters not allowed\r\n'); return; } const cleanCommand = command.trim(); if (cleanCommand.startsWith('EHLO')) { socket.write('250-mail.example.com\r\n'); socket.write('250 OK\r\n'); } else if (cleanCommand.startsWith('MAIL FROM:')) { socket.write('250 OK\r\n'); } else if (cleanCommand.startsWith('RCPT TO:')) { socket.write('250 OK\r\n'); } else if (cleanCommand === 'DATA') { socket.write('354 Start mail input\r\n'); } else if (cleanCommand === '.') { socket.write('250 OK\r\n'); } else if (cleanCommand === 'QUIT') { socket.write('221 Bye\r\n'); socket.end(); } }); } }); const smtpClient = createSmtpClient({ host: testServer.hostname, port: testServer.port, secure: false }); const email = new plugins.smartmail.Email({ from: 'sender@example.com', to: ['recipient@example.com'], subject: 'Character encoding test', text: 'Testing character validation' }); const result = await smtpClient.sendMail(email); console.log(` Result: ${result.messageId ? 'Success' : 'Failed'}`); expect(result).toBeDefined(); await testServer.server.close(); })(); console.log(`\n${testId}: All ${scenarioCount} malformed command scenarios tested ✓`); });