update
This commit is contained in:
@@ -0,0 +1,393 @@
|
||||
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 ✓`);
|
||||
});
|
Reference in New Issue
Block a user