dcrouter/test/suite/smtpclient_edge-cases/test.cedge-05.encoding-issues.ts
2025-05-24 17:00:59 +00:00

535 lines
18 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-05: should handle encoding issues gracefully', async (tools) => {
const testId = 'CEDGE-05-encoding-issues';
console.log(`\n${testId}: Testing encoding issue handling...`);
let scenarioCount = 0;
// Scenario 1: Mixed character encodings in email content
await (async () => {
scenarioCount++;
console.log(`\nScenario ${scenarioCount}: Testing mixed character encodings`);
const testServer = await createTestServer({
onConnection: async (socket) => {
console.log(' [Server] Client connected');
socket.write('220 mail.example.com ESMTP\r\n');
let inData = false;
let messageData = '';
socket.on('data', (data) => {
const text = data.toString();
if (inData) {
messageData += text;
if (text.includes('\r\n.\r\n')) {
inData = false;
console.log(` [Server] Received message data (${messageData.length} bytes)`);
// Check for various encodings
const hasUtf8 = /[\u0080-\uFFFF]/.test(messageData);
const hasBase64 = /Content-Transfer-Encoding:\s*base64/i.test(messageData);
const hasQuotedPrintable = /Content-Transfer-Encoding:\s*quoted-printable/i.test(messageData);
console.log(` [Server] Encodings detected: UTF-8=${hasUtf8}, Base64=${hasBase64}, QP=${hasQuotedPrintable}`);
socket.write('250 OK\r\n');
messageData = '';
}
return;
}
const command = text.trim();
console.log(` [Server] Received: ${command}`);
if (command.startsWith('EHLO')) {
socket.write('250-mail.example.com\r\n');
socket.write('250-8BITMIME\r\n');
socket.write('250-SMTPUTF8\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');
inData = true;
} else if (command === 'QUIT') {
socket.write('221 Bye\r\n');
socket.end();
}
});
}
});
const smtpClient = createSmtpClient({
host: testServer.hostname,
port: testServer.port,
secure: false
});
// Email with mixed encodings
const email = new plugins.smartmail.Email({
from: 'sender@example.com',
to: ['recipient@example.com'],
subject: 'Test with émojis 🎉 and spéçiål characters',
text: 'Plain text with Unicode: café, naïve, 你好, مرحبا',
html: '<p>HTML with entities: caf&eacute;, na&iuml;ve, and emoji 🌟</p>',
attachments: [{
filename: 'tëst-filé.txt',
content: 'Attachment content with special chars: ñ, ü, ß'
}]
});
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: Invalid UTF-8 sequences
await (async () => {
scenarioCount++;
console.log(`\nScenario ${scenarioCount}: Testing invalid UTF-8 sequences`);
const testServer = await createTestServer({
onConnection: async (socket) => {
console.log(' [Server] Client connected');
socket.write('220 mail.example.com ESMTP\r\n');
let inData = false;
socket.on('data', (data) => {
if (inData) {
if (data.toString().includes('\r\n.\r\n')) {
inData = false;
socket.write('250 OK\r\n');
}
return;
}
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-8BITMIME\r\n');
socket.write('250 OK\r\n');
} else if (command.startsWith('MAIL FROM:')) {
// Check for invalid UTF-8 in email address
const hasInvalidUtf8 = Buffer.from(command).some((byte, i, arr) => {
if (byte >= 0x80) {
// Check if it's valid UTF-8
if ((byte & 0xE0) === 0xC0) {
return i + 1 >= arr.length || (arr[i + 1] & 0xC0) !== 0x80;
}
// Add more UTF-8 validation as needed
}
return false;
});
if (hasInvalidUtf8) {
socket.write('501 5.5.4 Invalid UTF-8 in address\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');
inData = true;
} else if (command === 'QUIT') {
socket.write('221 Bye\r\n');
socket.end();
}
});
}
});
const smtpClient = createSmtpClient({
host: testServer.hostname,
port: testServer.port,
secure: false
});
// Create email with potentially problematic content
const email = new plugins.smartmail.Email({
from: 'sender@example.com',
to: ['recipient@example.com'],
subject: 'Test with various encodings',
text: 'Testing text with special chars',
headers: {
'X-Custom-Header': 'Test value with special chars'
}
});
const result = await smtpClient.sendMail(email);
console.log(` Result: ${result.messageId ? 'Success' : 'Failed'}`);
expect(result).toBeDefined();
await testServer.server.close();
})();
// Scenario 3: Base64 encoding edge cases
await (async () => {
scenarioCount++;
console.log(`\nScenario ${scenarioCount}: Testing Base64 encoding edge cases`);
const testServer = await createTestServer({
onConnection: async (socket) => {
console.log(' [Server] Client connected');
socket.write('220 mail.example.com ESMTP\r\n');
let inData = false;
let messageData = '';
socket.on('data', (data) => {
if (inData) {
messageData += data.toString();
if (messageData.includes('\r\n.\r\n')) {
inData = false;
// Check for base64 content
const base64Match = messageData.match(/Content-Transfer-Encoding:\s*base64\r?\n\r?\n([^\r\n]+)/i);
if (base64Match) {
const base64Content = base64Match[1];
console.log(` [Server] Found base64 content: ${base64Content.substring(0, 50)}...`);
// Verify it's valid base64
const isValidBase64 = /^[A-Za-z0-9+/]*={0,2}$/.test(base64Content.replace(/\s/g, ''));
console.log(` [Server] Base64 valid: ${isValidBase64}`);
}
socket.write('250 OK\r\n');
messageData = '';
}
return;
}
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');
inData = true;
} else if (command === 'QUIT') {
socket.write('221 Bye\r\n');
socket.end();
}
});
}
});
const smtpClient = createSmtpClient({
host: testServer.hostname,
port: testServer.port,
secure: false
});
// Create various sizes of binary content
const sizes = [0, 1, 2, 3, 57, 58, 59, 76, 77]; // Edge cases for base64 line wrapping
for (const size of sizes) {
const binaryContent = Buffer.alloc(size);
for (let i = 0; i < size; i++) {
binaryContent[i] = i % 256;
}
const email = new plugins.smartmail.Email({
from: 'sender@example.com',
to: ['recipient@example.com'],
subject: `Base64 test with ${size} bytes`,
text: 'Testing base64 encoding',
attachments: [{
filename: `test-${size}.bin`,
content: binaryContent
}]
});
console.log(` Testing with ${size} byte attachment...`);
const result = await smtpClient.sendMail(email);
expect(result).toBeDefined();
expect(result.messageId).toBeDefined();
}
await testServer.server.close();
})();
// Scenario 4: Quoted-printable encoding edge cases
await (async () => {
scenarioCount++;
console.log(`\nScenario ${scenarioCount}: Testing quoted-printable encoding`);
const testServer = await createTestServer({
onConnection: async (socket) => {
console.log(' [Server] Client connected');
socket.write('220 mail.example.com ESMTP\r\n');
let inData = false;
let messageData = '';
socket.on('data', (data) => {
if (inData) {
messageData += data.toString();
if (messageData.includes('\r\n.\r\n')) {
inData = false;
// Check for quoted-printable content
if (/Content-Transfer-Encoding:\s*quoted-printable/i.test(messageData)) {
console.log(' [Server] Found quoted-printable content');
// Check for proper QP encoding
const qpLines = messageData.split('\r\n');
const longLines = qpLines.filter(line => line.length > 76);
if (longLines.length > 0) {
console.log(` [Server] Warning: ${longLines.length} lines exceed 76 characters`);
}
}
socket.write('250 OK\r\n');
messageData = '';
}
return;
}
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');
inData = true;
} 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 content that requires quoted-printable encoding
const email = new plugins.smartmail.Email({
from: 'sender@example.com',
to: ['recipient@example.com'],
subject: 'Quoted-printable test',
text: [
'Line with special chars: café, naïve',
'Very long line that exceeds the 76 character limit and should be properly wrapped when encoded with quoted-printable encoding',
'Line with = sign and trailing spaces ',
'Line ending with =',
'Tést with various spëcial characters: ñ, ü, ß, ø, å'
].join('\n')
});
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 5: Header encoding (RFC 2047)
await (async () => {
scenarioCount++;
console.log(`\nScenario ${scenarioCount}: Testing header encoding (RFC 2047)`);
const testServer = await createTestServer({
onConnection: async (socket) => {
console.log(' [Server] Client connected');
socket.write('220 mail.example.com ESMTP\r\n');
let inData = false;
let headers: string[] = [];
socket.on('data', (data) => {
const text = data.toString();
if (inData) {
if (!text.startsWith('\r\n') && text.includes(':')) {
headers.push(text.split('\r\n')[0]);
}
if (text.includes('\r\n.\r\n')) {
inData = false;
// Check encoded headers
const encodedHeaders = headers.filter(h => h.includes('=?'));
console.log(` [Server] Found ${encodedHeaders.length} encoded headers`);
encodedHeaders.forEach(h => {
const match = h.match(/=\?([^?]+)\?([BQ])\?([^?]+)\?=/);
if (match) {
console.log(` [Server] Encoded header: charset=${match[1]}, encoding=${match[2]}`);
}
});
socket.write('250 OK\r\n');
headers = [];
}
return;
}
const command = text.trim();
console.log(` [Server] Received: ${command}`);
if (command.startsWith('EHLO')) {
socket.write('250-mail.example.com\r\n');
socket.write('250-SMTPUTF8\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');
inData = true;
} else if (command === 'QUIT') {
socket.write('221 Bye\r\n');
socket.end();
}
});
}
});
const smtpClient = createSmtpClient({
host: testServer.hostname,
port: testServer.port,
secure: false
});
// Test various header encodings
const testCases = [
{
subject: 'Simple ASCII subject',
from: { name: 'John Doe', address: 'john@example.com' }
},
{
subject: 'Subject with émojis 🎉 and spéçiål çhåracters',
from: { name: 'Jöhn Døe', address: 'john@example.com' }
},
{
subject: 'Japanese: こんにちは, Chinese: 你好, Arabic: مرحبا',
from: { name: '山田太郎', address: 'yamada@example.com' }
},
{
subject: 'Very long subject that contains special characters and should be encoded and folded properly: café, naïve, résumé, piñata',
from: { name: 'Sender with a véry løng nåme that éxceeds normal limits', address: 'sender@example.com' }
}
];
for (const testCase of testCases) {
console.log(` Testing: "${testCase.subject.substring(0, 50)}..."`);
const email = new plugins.smartmail.Email({
from: testCase.from,
to: ['recipient@example.com'],
subject: testCase.subject,
text: 'Testing header encoding',
headers: {
'X-Custom': `Custom header with special chars: ${testCase.subject.substring(0, 20)}`
}
});
const result = await smtpClient.sendMail(email);
expect(result).toBeDefined();
expect(result.messageId).toBeDefined();
}
await testServer.server.close();
})();
// Scenario 6: Content-Type charset mismatches
await (async () => {
scenarioCount++;
console.log(`\nScenario ${scenarioCount}: Testing Content-Type charset handling`);
const testServer = await createTestServer({
onConnection: async (socket) => {
console.log(' [Server] Client connected');
socket.write('220 mail.example.com ESMTP\r\n');
let inData = false;
socket.on('data', (data) => {
if (inData) {
if (data.toString().includes('\r\n.\r\n')) {
inData = false;
socket.write('250 OK\r\n');
}
return;
}
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');
inData = true;
} 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 different charset declarations
const email = new plugins.smartmail.Email({
from: 'sender@example.com',
to: ['recipient@example.com'],
subject: 'Charset test',
text: 'Text with special chars: é, ñ, ü',
html: '<p>HTML with different chars: café, naïve</p>',
headers: {
'Content-Type': 'text/plain; charset=iso-8859-1' // Mismatch with actual UTF-8 content
}
});
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} encoding scenarios tested ✓`);
});