update
This commit is contained in:
@ -0,0 +1,654 @@
|
||||
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('CRFC-08: should handle SMTP extensions correctly (Various RFCs)', async (tools) => {
|
||||
const testId = 'CRFC-08-smtp-extensions';
|
||||
console.log(`\n${testId}: Testing SMTP extensions compliance...`);
|
||||
|
||||
let scenarioCount = 0;
|
||||
|
||||
// Scenario 1: CHUNKING extension (RFC 3030)
|
||||
await (async () => {
|
||||
scenarioCount++;
|
||||
console.log(`\nScenario ${scenarioCount}: Testing CHUNKING extension (RFC 3030)`);
|
||||
|
||||
const testServer = await createTestServer({
|
||||
onConnection: async (socket) => {
|
||||
console.log(' [Server] Client connected');
|
||||
socket.write('220 chunking.example.com ESMTP\r\n');
|
||||
|
||||
let chunkingMode = false;
|
||||
let totalChunks = 0;
|
||||
let totalBytes = 0;
|
||||
|
||||
socket.on('data', (data) => {
|
||||
const text = data.toString();
|
||||
|
||||
if (chunkingMode) {
|
||||
// In chunking mode, all data is message content
|
||||
totalBytes += data.length;
|
||||
console.log(` [Server] Received chunk: ${data.length} bytes`);
|
||||
return;
|
||||
}
|
||||
|
||||
const command = text.trim();
|
||||
console.log(` [Server] Received: ${command}`);
|
||||
|
||||
if (command.startsWith('EHLO')) {
|
||||
socket.write('250-chunking.example.com\r\n');
|
||||
socket.write('250-CHUNKING\r\n');
|
||||
socket.write('250-8BITMIME\r\n');
|
||||
socket.write('250-BINARYMIME\r\n');
|
||||
socket.write('250 OK\r\n');
|
||||
} else if (command.startsWith('MAIL FROM:')) {
|
||||
if (command.includes('BODY=BINARYMIME')) {
|
||||
console.log(' [Server] Binary MIME body declared');
|
||||
}
|
||||
socket.write('250 OK\r\n');
|
||||
} else if (command.startsWith('RCPT TO:')) {
|
||||
socket.write('250 OK\r\n');
|
||||
} else if (command.startsWith('BDAT ')) {
|
||||
// BDAT command format: BDAT <size> [LAST]
|
||||
const parts = command.split(' ');
|
||||
const chunkSize = parseInt(parts[1]);
|
||||
const isLast = parts.includes('LAST');
|
||||
|
||||
totalChunks++;
|
||||
console.log(` [Server] BDAT chunk ${totalChunks}: ${chunkSize} bytes${isLast ? ' (LAST)' : ''}`);
|
||||
|
||||
if (isLast) {
|
||||
socket.write(`250 OK: Message accepted, ${totalChunks} chunks, ${totalBytes} total bytes\r\n`);
|
||||
chunkingMode = false;
|
||||
totalChunks = 0;
|
||||
totalBytes = 0;
|
||||
} else {
|
||||
socket.write('250 OK: Chunk accepted\r\n');
|
||||
chunkingMode = true;
|
||||
}
|
||||
} else if (command === 'DATA') {
|
||||
// DATA not allowed when CHUNKING is available
|
||||
socket.write('503 5.5.1 Use BDAT instead of DATA\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 with binary content that would benefit from chunking
|
||||
const binaryContent = Buffer.alloc(1024);
|
||||
for (let i = 0; i < binaryContent.length; i++) {
|
||||
binaryContent[i] = i % 256;
|
||||
}
|
||||
|
||||
const email = new plugins.smartmail.Email({
|
||||
from: 'sender@example.com',
|
||||
to: ['recipient@example.com'],
|
||||
subject: 'CHUNKING test',
|
||||
text: 'Testing CHUNKING extension with binary data',
|
||||
attachments: [{
|
||||
filename: 'binary-data.bin',
|
||||
content: binaryContent
|
||||
}]
|
||||
});
|
||||
|
||||
const result = await smtpClient.sendMail(email);
|
||||
console.log(' CHUNKING extension handled (if supported by client)');
|
||||
expect(result).toBeDefined();
|
||||
expect(result.messageId).toBeDefined();
|
||||
|
||||
await testServer.server.close();
|
||||
})();
|
||||
|
||||
// Scenario 2: DELIVERBY extension (RFC 2852)
|
||||
await (async () => {
|
||||
scenarioCount++;
|
||||
console.log(`\nScenario ${scenarioCount}: Testing DELIVERBY extension (RFC 2852)`);
|
||||
|
||||
const testServer = await createTestServer({
|
||||
onConnection: async (socket) => {
|
||||
console.log(' [Server] Client connected');
|
||||
socket.write('220 deliverby.example.com ESMTP\r\n');
|
||||
|
||||
socket.on('data', (data) => {
|
||||
const command = data.toString().trim();
|
||||
console.log(` [Server] Received: ${command}`);
|
||||
|
||||
if (command.startsWith('EHLO')) {
|
||||
socket.write('250-deliverby.example.com\r\n');
|
||||
socket.write('250-DELIVERBY 86400\r\n'); // 24 hours max
|
||||
socket.write('250 OK\r\n');
|
||||
} else if (command.startsWith('MAIL FROM:')) {
|
||||
// Check for DELIVERBY parameter
|
||||
const deliverByMatch = command.match(/DELIVERBY=(\d+)([RN]?)/i);
|
||||
if (deliverByMatch) {
|
||||
const seconds = parseInt(deliverByMatch[1]);
|
||||
const mode = deliverByMatch[2] || 'R'; // R=return, N=notify
|
||||
|
||||
console.log(` [Server] DELIVERBY: ${seconds} seconds, mode: ${mode}`);
|
||||
|
||||
if (seconds > 86400) {
|
||||
socket.write('501 5.5.4 DELIVERBY time exceeds maximum\r\n');
|
||||
} else if (seconds < 0) {
|
||||
socket.write('501 5.5.4 Invalid DELIVERBY time\r\n');
|
||||
} else {
|
||||
socket.write('250 OK: Delivery deadline accepted\r\n');
|
||||
}
|
||||
} else {
|
||||
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: Message queued with delivery deadline\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 with delivery deadline
|
||||
const email = new plugins.smartmail.Email({
|
||||
from: 'sender@example.com',
|
||||
to: ['urgent@example.com'],
|
||||
subject: 'Urgent delivery test',
|
||||
text: 'This message has a delivery deadline',
|
||||
// Note: Most SMTP clients don't expose DELIVERBY directly
|
||||
// but we can test server handling
|
||||
});
|
||||
|
||||
const result = await smtpClient.sendMail(email);
|
||||
console.log(' DELIVERBY extension supported by server');
|
||||
expect(result).toBeDefined();
|
||||
|
||||
await testServer.server.close();
|
||||
})();
|
||||
|
||||
// Scenario 3: ETRN extension (RFC 1985)
|
||||
await (async () => {
|
||||
scenarioCount++;
|
||||
console.log(`\nScenario ${scenarioCount}: Testing ETRN extension (RFC 1985)`);
|
||||
|
||||
const testServer = await createTestServer({
|
||||
onConnection: async (socket) => {
|
||||
console.log(' [Server] Client connected');
|
||||
socket.write('220 etrn.example.com ESMTP\r\n');
|
||||
|
||||
socket.on('data', (data) => {
|
||||
const command = data.toString().trim();
|
||||
console.log(` [Server] Received: ${command}`);
|
||||
|
||||
if (command.startsWith('EHLO')) {
|
||||
socket.write('250-etrn.example.com\r\n');
|
||||
socket.write('250-ETRN\r\n');
|
||||
socket.write('250 OK\r\n');
|
||||
} else if (command.startsWith('ETRN ')) {
|
||||
const domain = command.substring(5);
|
||||
console.log(` [Server] ETRN request for domain: ${domain}`);
|
||||
|
||||
if (domain === '@example.com') {
|
||||
socket.write('250 OK: Queue processing started for example.com\r\n');
|
||||
} else if (domain === '#urgent') {
|
||||
socket.write('250 OK: Urgent queue processing started\r\n');
|
||||
} else if (domain.includes('unknown')) {
|
||||
socket.write('458 Unable to queue messages for node\r\n');
|
||||
} else {
|
||||
socket.write('250 OK: Queue processing started\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') {
|
||||
socket.write('221 Bye\r\n');
|
||||
socket.end();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// ETRN is typically used by mail servers, not clients
|
||||
// We'll test the server's ETRN capability manually
|
||||
const net = await import('net');
|
||||
const client = net.createConnection(testServer.port, testServer.hostname);
|
||||
|
||||
const commands = [
|
||||
'EHLO client.example.com',
|
||||
'ETRN @example.com', // Request queue processing for domain
|
||||
'ETRN #urgent', // Request urgent queue processing
|
||||
'ETRN unknown.domain.com', // Test error handling
|
||||
'QUIT'
|
||||
];
|
||||
|
||||
let commandIndex = 0;
|
||||
|
||||
client.on('data', (data) => {
|
||||
const response = data.toString().trim();
|
||||
console.log(` [Client] Response: ${response}`);
|
||||
|
||||
if (commandIndex < commands.length) {
|
||||
setTimeout(() => {
|
||||
const command = commands[commandIndex];
|
||||
console.log(` [Client] Sending: ${command}`);
|
||||
client.write(command + '\r\n');
|
||||
commandIndex++;
|
||||
}, 100);
|
||||
} else {
|
||||
client.end();
|
||||
}
|
||||
});
|
||||
|
||||
await new Promise((resolve, reject) => {
|
||||
client.on('end', () => {
|
||||
console.log(' ETRN extension testing completed');
|
||||
resolve(void 0);
|
||||
});
|
||||
client.on('error', reject);
|
||||
});
|
||||
|
||||
await testServer.server.close();
|
||||
})();
|
||||
|
||||
// Scenario 4: VRFY and EXPN extensions (RFC 5321)
|
||||
await (async () => {
|
||||
scenarioCount++;
|
||||
console.log(`\nScenario ${scenarioCount}: Testing VRFY and EXPN extensions`);
|
||||
|
||||
const testServer = await createTestServer({
|
||||
onConnection: async (socket) => {
|
||||
console.log(' [Server] Client connected');
|
||||
socket.write('220 verify.example.com ESMTP\r\n');
|
||||
|
||||
// Simulated user database
|
||||
const users = new Map([
|
||||
['admin', { email: 'admin@example.com', fullName: 'Administrator' }],
|
||||
['john', { email: 'john.doe@example.com', fullName: 'John Doe' }],
|
||||
['support', { email: 'support@example.com', fullName: 'Support Team' }]
|
||||
]);
|
||||
|
||||
const mailingLists = new Map([
|
||||
['staff', ['admin@example.com', 'john.doe@example.com']],
|
||||
['support-team', ['support@example.com', 'admin@example.com']]
|
||||
]);
|
||||
|
||||
socket.on('data', (data) => {
|
||||
const command = data.toString().trim();
|
||||
console.log(` [Server] Received: ${command}`);
|
||||
|
||||
if (command.startsWith('EHLO')) {
|
||||
socket.write('250-verify.example.com\r\n');
|
||||
socket.write('250-VRFY\r\n');
|
||||
socket.write('250-EXPN\r\n');
|
||||
socket.write('250 OK\r\n');
|
||||
} else if (command.startsWith('VRFY ')) {
|
||||
const query = command.substring(5);
|
||||
console.log(` [Server] VRFY query: ${query}`);
|
||||
|
||||
// Look up user
|
||||
const user = users.get(query.toLowerCase());
|
||||
if (user) {
|
||||
socket.write(`250 ${user.fullName} <${user.email}>\r\n`);
|
||||
} else {
|
||||
// Check if it's an email address
|
||||
const emailMatch = Array.from(users.values()).find(u =>
|
||||
u.email.toLowerCase() === query.toLowerCase()
|
||||
);
|
||||
if (emailMatch) {
|
||||
socket.write(`250 ${emailMatch.fullName} <${emailMatch.email}>\r\n`);
|
||||
} else {
|
||||
socket.write('550 5.1.1 User unknown\r\n');
|
||||
}
|
||||
}
|
||||
} else if (command.startsWith('EXPN ')) {
|
||||
const listName = command.substring(5);
|
||||
console.log(` [Server] EXPN query: ${listName}`);
|
||||
|
||||
const list = mailingLists.get(listName.toLowerCase());
|
||||
if (list) {
|
||||
socket.write(`250-Mailing list ${listName}:\r\n`);
|
||||
list.forEach((email, index) => {
|
||||
const prefix = index < list.length - 1 ? '250-' : '250 ';
|
||||
socket.write(`${prefix}${email}\r\n`);
|
||||
});
|
||||
} else {
|
||||
socket.write('550 5.1.1 Mailing list not found\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') {
|
||||
socket.write('221 Bye\r\n');
|
||||
socket.end();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Test VRFY and EXPN commands
|
||||
const net = await import('net');
|
||||
const client = net.createConnection(testServer.port, testServer.hostname);
|
||||
|
||||
const commands = [
|
||||
'EHLO client.example.com',
|
||||
'VRFY admin', // Verify user by username
|
||||
'VRFY john.doe@example.com', // Verify user by email
|
||||
'VRFY nonexistent', // Test unknown user
|
||||
'EXPN staff', // Expand mailing list
|
||||
'EXPN nonexistent-list', // Test unknown list
|
||||
'QUIT'
|
||||
];
|
||||
|
||||
let commandIndex = 0;
|
||||
|
||||
client.on('data', (data) => {
|
||||
const response = data.toString().trim();
|
||||
console.log(` [Client] Response: ${response}`);
|
||||
|
||||
if (commandIndex < commands.length) {
|
||||
setTimeout(() => {
|
||||
const command = commands[commandIndex];
|
||||
console.log(` [Client] Sending: ${command}`);
|
||||
client.write(command + '\r\n');
|
||||
commandIndex++;
|
||||
}, 200);
|
||||
} else {
|
||||
client.end();
|
||||
}
|
||||
});
|
||||
|
||||
await new Promise((resolve, reject) => {
|
||||
client.on('end', () => {
|
||||
console.log(' VRFY and EXPN testing completed');
|
||||
resolve(void 0);
|
||||
});
|
||||
client.on('error', reject);
|
||||
});
|
||||
|
||||
await testServer.server.close();
|
||||
})();
|
||||
|
||||
// Scenario 5: HELP extension
|
||||
await (async () => {
|
||||
scenarioCount++;
|
||||
console.log(`\nScenario ${scenarioCount}: Testing HELP extension`);
|
||||
|
||||
const testServer = await createTestServer({
|
||||
onConnection: async (socket) => {
|
||||
console.log(' [Server] Client connected');
|
||||
socket.write('220 help.example.com ESMTP\r\n');
|
||||
|
||||
const helpTopics = new Map([
|
||||
['commands', [
|
||||
'Available commands:',
|
||||
'EHLO <domain> - Extended HELLO',
|
||||
'MAIL FROM:<addr> - Specify sender',
|
||||
'RCPT TO:<addr> - Specify recipient',
|
||||
'DATA - Start message text',
|
||||
'QUIT - Close connection'
|
||||
]],
|
||||
['extensions', [
|
||||
'Supported extensions:',
|
||||
'SIZE - Message size declaration',
|
||||
'8BITMIME - 8-bit MIME transport',
|
||||
'STARTTLS - Start TLS negotiation',
|
||||
'AUTH - SMTP Authentication',
|
||||
'DSN - Delivery Status Notifications'
|
||||
]],
|
||||
['syntax', [
|
||||
'Command syntax:',
|
||||
'Commands are case-insensitive',
|
||||
'Lines end with CRLF',
|
||||
'Email addresses must be in <> brackets',
|
||||
'Parameters are space-separated'
|
||||
]]
|
||||
]);
|
||||
|
||||
socket.on('data', (data) => {
|
||||
const command = data.toString().trim();
|
||||
console.log(` [Server] Received: ${command}`);
|
||||
|
||||
if (command.startsWith('EHLO')) {
|
||||
socket.write('250-help.example.com\r\n');
|
||||
socket.write('250-HELP\r\n');
|
||||
socket.write('250 OK\r\n');
|
||||
} else if (command === 'HELP' || command === 'HELP HELP') {
|
||||
socket.write('214-This server provides HELP for the following topics:\r\n');
|
||||
socket.write('214-COMMANDS - List of available commands\r\n');
|
||||
socket.write('214-EXTENSIONS - List of supported extensions\r\n');
|
||||
socket.write('214-SYNTAX - Command syntax rules\r\n');
|
||||
socket.write('214 Use HELP <topic> for specific information\r\n');
|
||||
} else if (command.startsWith('HELP ')) {
|
||||
const topic = command.substring(5).toLowerCase();
|
||||
const helpText = helpTopics.get(topic);
|
||||
|
||||
if (helpText) {
|
||||
helpText.forEach((line, index) => {
|
||||
const prefix = index < helpText.length - 1 ? '214-' : '214 ';
|
||||
socket.write(`${prefix}${line}\r\n`);
|
||||
});
|
||||
} else {
|
||||
socket.write('504 5.3.0 HELP topic not available\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') {
|
||||
socket.write('221 Bye\r\n');
|
||||
socket.end();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Test HELP command
|
||||
const net = await import('net');
|
||||
const client = net.createConnection(testServer.port, testServer.hostname);
|
||||
|
||||
const commands = [
|
||||
'EHLO client.example.com',
|
||||
'HELP', // General help
|
||||
'HELP COMMANDS', // Specific topic
|
||||
'HELP EXTENSIONS', // Another topic
|
||||
'HELP NONEXISTENT', // Unknown topic
|
||||
'QUIT'
|
||||
];
|
||||
|
||||
let commandIndex = 0;
|
||||
|
||||
client.on('data', (data) => {
|
||||
const response = data.toString().trim();
|
||||
console.log(` [Client] Response: ${response}`);
|
||||
|
||||
if (commandIndex < commands.length) {
|
||||
setTimeout(() => {
|
||||
const command = commands[commandIndex];
|
||||
console.log(` [Client] Sending: ${command}`);
|
||||
client.write(command + '\r\n');
|
||||
commandIndex++;
|
||||
}, 200);
|
||||
} else {
|
||||
client.end();
|
||||
}
|
||||
});
|
||||
|
||||
await new Promise((resolve, reject) => {
|
||||
client.on('end', () => {
|
||||
console.log(' HELP extension testing completed');
|
||||
resolve(void 0);
|
||||
});
|
||||
client.on('error', reject);
|
||||
});
|
||||
|
||||
await testServer.server.close();
|
||||
})();
|
||||
|
||||
// Scenario 6: Extension combination and interaction
|
||||
await (async () => {
|
||||
scenarioCount++;
|
||||
console.log(`\nScenario ${scenarioCount}: Testing extension combinations`);
|
||||
|
||||
const testServer = await createTestServer({
|
||||
onConnection: async (socket) => {
|
||||
console.log(' [Server] Client connected');
|
||||
socket.write('220 combined.example.com ESMTP\r\n');
|
||||
|
||||
let activeExtensions: string[] = [];
|
||||
|
||||
socket.on('data', (data) => {
|
||||
const command = data.toString().trim();
|
||||
console.log(` [Server] Received: ${command}`);
|
||||
|
||||
if (command.startsWith('EHLO')) {
|
||||
socket.write('250-combined.example.com\r\n');
|
||||
|
||||
// Announce multiple extensions
|
||||
const extensions = [
|
||||
'SIZE 52428800',
|
||||
'8BITMIME',
|
||||
'SMTPUTF8',
|
||||
'ENHANCEDSTATUSCODES',
|
||||
'PIPELINING',
|
||||
'DSN',
|
||||
'DELIVERBY 86400',
|
||||
'CHUNKING',
|
||||
'BINARYMIME',
|
||||
'HELP'
|
||||
];
|
||||
|
||||
extensions.forEach(ext => {
|
||||
socket.write(`250-${ext}\r\n`);
|
||||
activeExtensions.push(ext.split(' ')[0]);
|
||||
});
|
||||
|
||||
socket.write('250 OK\r\n');
|
||||
console.log(` [Server] Active extensions: ${activeExtensions.join(', ')}`);
|
||||
} else if (command.startsWith('MAIL FROM:')) {
|
||||
// Check for multiple extension parameters
|
||||
const params = [];
|
||||
|
||||
if (command.includes('SIZE=')) {
|
||||
const sizeMatch = command.match(/SIZE=(\d+)/);
|
||||
if (sizeMatch) params.push(`SIZE=${sizeMatch[1]}`);
|
||||
}
|
||||
|
||||
if (command.includes('BODY=')) {
|
||||
const bodyMatch = command.match(/BODY=(\w+)/);
|
||||
if (bodyMatch) params.push(`BODY=${bodyMatch[1]}`);
|
||||
}
|
||||
|
||||
if (command.includes('SMTPUTF8')) {
|
||||
params.push('SMTPUTF8');
|
||||
}
|
||||
|
||||
if (command.includes('DELIVERBY=')) {
|
||||
const deliverByMatch = command.match(/DELIVERBY=(\d+)/);
|
||||
if (deliverByMatch) params.push(`DELIVERBY=${deliverByMatch[1]}`);
|
||||
}
|
||||
|
||||
if (params.length > 0) {
|
||||
console.log(` [Server] Extension parameters: ${params.join(', ')}`);
|
||||
}
|
||||
|
||||
socket.write('250 2.1.0 Sender OK\r\n');
|
||||
} else if (command.startsWith('RCPT TO:')) {
|
||||
// Check for DSN parameters
|
||||
if (command.includes('NOTIFY=')) {
|
||||
const notifyMatch = command.match(/NOTIFY=([^,\s]+)/);
|
||||
if (notifyMatch) {
|
||||
console.log(` [Server] DSN NOTIFY: ${notifyMatch[1]}`);
|
||||
}
|
||||
}
|
||||
|
||||
socket.write('250 2.1.5 Recipient OK\r\n');
|
||||
} else if (command === 'DATA') {
|
||||
if (activeExtensions.includes('CHUNKING')) {
|
||||
socket.write('503 5.5.1 Use BDAT when CHUNKING is available\r\n');
|
||||
} else {
|
||||
socket.write('354 Start mail input\r\n');
|
||||
}
|
||||
} else if (command.startsWith('BDAT ')) {
|
||||
if (activeExtensions.includes('CHUNKING')) {
|
||||
const parts = command.split(' ');
|
||||
const size = parts[1];
|
||||
const isLast = parts.includes('LAST');
|
||||
console.log(` [Server] BDAT chunk: ${size} bytes${isLast ? ' (LAST)' : ''}`);
|
||||
|
||||
if (isLast) {
|
||||
socket.write('250 2.0.0 Message accepted\r\n');
|
||||
} else {
|
||||
socket.write('250 2.0.0 Chunk accepted\r\n');
|
||||
}
|
||||
} else {
|
||||
socket.write('500 5.5.1 CHUNKING not available\r\n');
|
||||
}
|
||||
} else if (command === '.') {
|
||||
socket.write('250 2.0.0 Message accepted\r\n');
|
||||
} else if (command === 'QUIT') {
|
||||
socket.write('221 2.0.0 Bye\r\n');
|
||||
socket.end();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
const smtpClient = createSmtpClient({
|
||||
host: testServer.hostname,
|
||||
port: testServer.port,
|
||||
secure: false
|
||||
});
|
||||
|
||||
// Test email that could use multiple extensions
|
||||
const email = new plugins.smartmail.Email({
|
||||
from: 'sender@example.com',
|
||||
to: ['recipient@example.com'],
|
||||
subject: 'Extension combination test with UTF-8: 测试',
|
||||
text: 'Testing multiple SMTP extensions together',
|
||||
dsn: {
|
||||
notify: ['SUCCESS', 'FAILURE'],
|
||||
envid: 'multi-ext-test-123'
|
||||
}
|
||||
});
|
||||
|
||||
const result = await smtpClient.sendMail(email);
|
||||
console.log(' Multiple extension combination handled');
|
||||
expect(result).toBeDefined();
|
||||
expect(result.messageId).toBeDefined();
|
||||
|
||||
await testServer.server.close();
|
||||
})();
|
||||
|
||||
console.log(`\n${testId}: All ${scenarioCount} SMTP extension scenarios tested ✓`);
|
||||
});
|
Reference in New Issue
Block a user