384 lines
13 KiB
TypeScript
384 lines
13 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-03: should handle protocol violations gracefully', async (tools) => {
|
||
|
const testId = 'CEDGE-03-protocol-violations';
|
||
|
console.log(`\n${testId}: Testing protocol violation handling...`);
|
||
|
|
||
|
let scenarioCount = 0;
|
||
|
|
||
|
// Scenario 1: Server closes connection unexpectedly
|
||
|
await (async () => {
|
||
|
scenarioCount++;
|
||
|
console.log(`\nScenario ${scenarioCount}: Testing unexpected connection closure`);
|
||
|
|
||
|
const testServer = await createTestServer({
|
||
|
onConnection: async (socket) => {
|
||
|
console.log(' [Server] Client connected');
|
||
|
socket.write('220 mail.example.com ESMTP\r\n');
|
||
|
|
||
|
let commandCount = 0;
|
||
|
socket.on('data', (data) => {
|
||
|
commandCount++;
|
||
|
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');
|
||
|
} else if (command.startsWith('MAIL FROM:')) {
|
||
|
// Abruptly close connection
|
||
|
console.log(' [Server] Closing connection unexpectedly');
|
||
|
socket.destroy();
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
});
|
||
|
|
||
|
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: 'Connection closure test',
|
||
|
text: 'Testing unexpected disconnection'
|
||
|
});
|
||
|
|
||
|
try {
|
||
|
await smtpClient.sendMail(email);
|
||
|
console.log(' Unexpected success');
|
||
|
} catch (error) {
|
||
|
console.log(` Expected error: ${error.message}`);
|
||
|
expect(error).toBeDefined();
|
||
|
}
|
||
|
|
||
|
await testServer.server.close();
|
||
|
})();
|
||
|
|
||
|
// Scenario 2: Server sends data without CRLF
|
||
|
await (async () => {
|
||
|
scenarioCount++;
|
||
|
console.log(`\nScenario ${scenarioCount}: Testing responses without proper CRLF`);
|
||
|
|
||
|
const testServer = await createTestServer({
|
||
|
onConnection: async (socket) => {
|
||
|
console.log(' [Server] Client connected');
|
||
|
// Send greeting without CRLF
|
||
|
socket.write('220 mail.example.com ESMTP');
|
||
|
// Then send proper CRLF
|
||
|
setTimeout(() => socket.write('\r\n'), 100);
|
||
|
|
||
|
socket.on('data', (data) => {
|
||
|
const command = data.toString().trim();
|
||
|
console.log(` [Server] Received: ${command}`);
|
||
|
|
||
|
if (command.startsWith('EHLO')) {
|
||
|
// Mix responses with and without CRLF
|
||
|
socket.write('250-mail.example.com\n'); // Just LF
|
||
|
socket.write('250-SIZE 10485760\r'); // Just CR
|
||
|
socket.write('250 OK\r\n'); // Proper CRLF
|
||
|
} 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');
|
||
|
} 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
|
||
|
});
|
||
|
|
||
|
const email = new plugins.smartmail.Email({
|
||
|
from: 'sender@example.com',
|
||
|
to: ['recipient@example.com'],
|
||
|
subject: 'Line ending test',
|
||
|
text: 'Testing non-standard line endings'
|
||
|
});
|
||
|
|
||
|
const result = await smtpClient.sendMail(email);
|
||
|
console.log(` Result: ${result.messageId ? 'Success (handled gracefully)' : 'Failed'}`);
|
||
|
expect(result).toBeDefined();
|
||
|
|
||
|
await testServer.server.close();
|
||
|
})();
|
||
|
|
||
|
// Scenario 3: Server sends responses in wrong order
|
||
|
await (async () => {
|
||
|
scenarioCount++;
|
||
|
console.log(`\nScenario ${scenarioCount}: Testing out-of-order responses`);
|
||
|
|
||
|
const testServer = await createTestServer({
|
||
|
onConnection: async (socket) => {
|
||
|
console.log(' [Server] Client connected');
|
||
|
socket.write('220 mail.example.com ESMTP\r\n');
|
||
|
|
||
|
const pendingResponses: Array<() => void> = [];
|
||
|
|
||
|
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');
|
||
|
} else if (command.startsWith('MAIL FROM:')) {
|
||
|
// Delay response
|
||
|
pendingResponses.push(() => {
|
||
|
socket.write('250 OK\r\n');
|
||
|
});
|
||
|
} else if (command.startsWith('RCPT TO:')) {
|
||
|
// Send this response first, then the MAIL response
|
||
|
socket.write('250 OK\r\n');
|
||
|
if (pendingResponses.length > 0) {
|
||
|
pendingResponses.forEach(fn => fn());
|
||
|
pendingResponses.length = 0;
|
||
|
}
|
||
|
} 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
|
||
|
});
|
||
|
|
||
|
const email = new plugins.smartmail.Email({
|
||
|
from: 'sender@example.com',
|
||
|
to: ['recipient@example.com'],
|
||
|
subject: 'Response order test',
|
||
|
text: 'Testing out-of-order responses'
|
||
|
});
|
||
|
|
||
|
try {
|
||
|
const result = await smtpClient.sendMail(email);
|
||
|
console.log(` Result: ${result.messageId ? 'Success' : 'Failed'}`);
|
||
|
} catch (error) {
|
||
|
console.log(` Expected possible error: ${error.message}`);
|
||
|
expect(error).toBeDefined();
|
||
|
}
|
||
|
|
||
|
await testServer.server.close();
|
||
|
})();
|
||
|
|
||
|
// Scenario 4: Server sends unsolicited responses
|
||
|
await (async () => {
|
||
|
scenarioCount++;
|
||
|
console.log(`\nScenario ${scenarioCount}: Testing unsolicited server responses`);
|
||
|
|
||
|
const testServer = await createTestServer({
|
||
|
onConnection: async (socket) => {
|
||
|
console.log(' [Server] Client connected');
|
||
|
socket.write('220 mail.example.com ESMTP\r\n');
|
||
|
|
||
|
// Send unsolicited responses periodically
|
||
|
const interval = setInterval(() => {
|
||
|
if (!socket.destroyed) {
|
||
|
console.log(' [Server] Sending unsolicited response');
|
||
|
socket.write('250-NOTICE: Server status update\r\n');
|
||
|
}
|
||
|
}, 500);
|
||
|
|
||
|
socket.on('close', () => clearInterval(interval));
|
||
|
|
||
|
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');
|
||
|
} 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');
|
||
|
} else if (command === '.') {
|
||
|
socket.write('250 OK\r\n');
|
||
|
} else if (command === 'QUIT') {
|
||
|
clearInterval(interval);
|
||
|
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: 'Unsolicited response test',
|
||
|
text: 'Testing unsolicited server messages'
|
||
|
});
|
||
|
|
||
|
const result = await smtpClient.sendMail(email);
|
||
|
console.log(` Result: ${result.messageId ? 'Success' : 'Failed'}`);
|
||
|
expect(result).toBeDefined();
|
||
|
|
||
|
await testServer.server.close();
|
||
|
})();
|
||
|
|
||
|
// Scenario 5: Server violates response code format
|
||
|
await (async () => {
|
||
|
scenarioCount++;
|
||
|
console.log(`\nScenario ${scenarioCount}: Testing invalid response codes`);
|
||
|
|
||
|
const testServer = await createTestServer({
|
||
|
onConnection: async (socket) => {
|
||
|
console.log(' [Server] Client connected');
|
||
|
// Invalid response code (should be 3 digits)
|
||
|
socket.write('22 mail.example.com ESMTP\r\n');
|
||
|
setTimeout(() => {
|
||
|
// Send correct response
|
||
|
socket.write('220 mail.example.com ESMTP\r\n');
|
||
|
}, 100);
|
||
|
|
||
|
socket.on('data', (data) => {
|
||
|
const command = data.toString().trim();
|
||
|
console.log(` [Server] Received: ${command}`);
|
||
|
|
||
|
if (command.startsWith('EHLO')) {
|
||
|
// Mix valid and invalid response codes
|
||
|
socket.write('250-mail.example.com\r\n');
|
||
|
socket.write('25O-TYPO IN CODE\r\n'); // Letter O instead of zero
|
||
|
socket.write('250 OK\r\n');
|
||
|
} else if (command.startsWith('MAIL FROM:')) {
|
||
|
socket.write('2.5.0 OK\r\n'); // Wrong format
|
||
|
setTimeout(() => {
|
||
|
socket.write('250 OK\r\n');
|
||
|
}, 50);
|
||
|
} 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();
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
});
|
||
|
|
||
|
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: 'Response code test',
|
||
|
text: 'Testing invalid response codes'
|
||
|
});
|
||
|
|
||
|
try {
|
||
|
const result = await smtpClient.sendMail(email);
|
||
|
console.log(` Result: ${result.messageId ? 'Success (handled invalid codes)' : 'Failed'}`);
|
||
|
} catch (error) {
|
||
|
console.log(` Expected possible error: ${error.message}`);
|
||
|
expect(error).toBeDefined();
|
||
|
}
|
||
|
|
||
|
await testServer.server.close();
|
||
|
})();
|
||
|
|
||
|
// Scenario 6: Server sends binary data
|
||
|
await (async () => {
|
||
|
scenarioCount++;
|
||
|
console.log(`\nScenario ${scenarioCount}: Testing binary data in responses`);
|
||
|
|
||
|
const testServer = await createTestServer({
|
||
|
onConnection: async (socket) => {
|
||
|
console.log(' [Server] Client connected');
|
||
|
// Send greeting with some binary data
|
||
|
socket.write('220 mail.example.com ESMTP\r\n');
|
||
|
socket.write(Buffer.from([0x00, 0x01, 0x02, 0x03])); // Binary data
|
||
|
|
||
|
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');
|
||
|
} 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');
|
||
|
} else if (command === '.') {
|
||
|
// Include binary in response
|
||
|
socket.write('250 OK ');
|
||
|
socket.write(Buffer.from([0xFF, 0xFE]));
|
||
|
socket.write('\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
|
||
|
});
|
||
|
|
||
|
const email = new plugins.smartmail.Email({
|
||
|
from: 'sender@example.com',
|
||
|
to: ['recipient@example.com'],
|
||
|
subject: 'Binary data test',
|
||
|
text: 'Testing binary data handling'
|
||
|
});
|
||
|
|
||
|
try {
|
||
|
const result = await smtpClient.sendMail(email);
|
||
|
console.log(` Result: ${result.messageId ? 'Success' : 'Failed'}`);
|
||
|
expect(result).toBeDefined();
|
||
|
} catch (error) {
|
||
|
console.log(` Error handling binary data: ${error.message}`);
|
||
|
expect(error).toBeDefined();
|
||
|
}
|
||
|
|
||
|
await testServer.server.close();
|
||
|
})();
|
||
|
|
||
|
console.log(`\n${testId}: All ${scenarioCount} protocol violation scenarios tested ✓`);
|
||
|
});
|