import { tap, expect } from '@git.zone/tstest/tapbundle'; import { startTestSmtpServer } from '../../helpers/server.loader.js'; import { createSmtpClient } from '../../helpers/smtp.client.js'; import { Email } from '../../../ts/mail/core/classes.email.js'; let testServer: any; tap.test('setup test SMTP server', async () => { testServer = await startTestSmtpServer({ features: ['8BITMIME', 'SMTPUTF8'] // Enable UTF-8 support }); expect(testServer).toBeTruthy(); expect(testServer.port).toBeGreaterThan(0); }); tap.test('CEP-06: Basic UTF-8 content', async () => { const smtpClient = createSmtpClient({ host: testServer.hostname, port: testServer.port, secure: false, connectionTimeout: 5000, debug: true }); await smtpClient.connect(); // Create email with UTF-8 content const email = new Email({ from: 'sender@example.com', to: ['recipient@example.com'], subject: 'UTF-8 Test: こんにちは 🌍', text: 'Hello in multiple languages:\n' + 'English: Hello World\n' + 'Japanese: こんにちは世界\n' + 'Chinese: 你好世界\n' + 'Arabic: مرحبا بالعالم\n' + 'Russian: Привет мир\n' + 'Emoji: 🌍🌎🌏✉️📧' }); // Check content encoding let contentType = ''; let charset = ''; const originalSendCommand = smtpClient.sendCommand.bind(smtpClient); smtpClient.sendCommand = async (command: string) => { if (command.toLowerCase().includes('content-type:')) { contentType = command; const charsetMatch = command.match(/charset=([^;\s]+)/i); if (charsetMatch) { charset = charsetMatch[1]; } } return originalSendCommand(command); }; const result = await smtpClient.sendMail(email); expect(result).toBeTruthy(); console.log('Content-Type:', contentType.trim()); console.log('Charset:', charset || 'not specified'); // Should use UTF-8 charset expect(charset.toLowerCase()).toMatch(/utf-?8/); await smtpClient.close(); }); tap.test('CEP-06: International email addresses', async () => { const smtpClient = createSmtpClient({ host: testServer.hostname, port: testServer.port, secure: false, connectionTimeout: 5000, debug: true }); await smtpClient.connect(); // Check if server supports SMTPUTF8 const ehloResponse = await smtpClient.sendCommand('EHLO testclient.example.com'); const supportsSmtpUtf8 = ehloResponse.includes('SMTPUTF8'); console.log('Server supports SMTPUTF8:', supportsSmtpUtf8); // Test international email addresses const internationalAddresses = [ 'user@例え.jp', 'utilisateur@exemple.fr', 'benutzer@beispiel.de', 'пользователь@пример.рф', '用户@例子.中国' ]; for (const address of internationalAddresses) { console.log(`\nTesting international address: ${address}`); const email = new Email({ from: 'sender@example.com', to: [address], subject: 'International Address Test', text: `Testing delivery to: ${address}` }); try { // Monitor MAIL FROM with SMTPUTF8 let smtpUtf8Used = false; const originalSendCommand = smtpClient.sendCommand.bind(smtpClient); smtpClient.sendCommand = async (command: string) => { if (command.includes('SMTPUTF8')) { smtpUtf8Used = true; } return originalSendCommand(command); }; const result = await smtpClient.sendMail(email); console.log(` Result: ${result ? 'Success' : 'Failed'}`); console.log(` SMTPUTF8 used: ${smtpUtf8Used}`); if (!supportsSmtpUtf8 && !result) { console.log(' Expected failure - server does not support SMTPUTF8'); } } catch (error) { console.log(` Error: ${error.message}`); if (!supportsSmtpUtf8) { console.log(' Expected - server does not support international addresses'); } } } await smtpClient.close(); }); tap.test('CEP-06: UTF-8 in headers', async () => { const smtpClient = createSmtpClient({ host: testServer.hostname, port: testServer.port, secure: false, connectionTimeout: 5000, debug: true }); await smtpClient.connect(); // Create email with UTF-8 in various headers const email = new Email({ from: '"发件人" ', to: ['"收件人" '], subject: 'Meeting: Café ☕ at 3pm 🕒', headers: { 'X-Custom-Header': 'Custom UTF-8: αβγδε', 'X-Language': '日本語' }, text: 'Meeting at the café to discuss the project.' }); // Capture encoded headers const capturedHeaders: string[] = []; const originalSendCommand = smtpClient.sendCommand.bind(smtpClient); smtpClient.sendCommand = async (command: string) => { if (command.includes(':') && !command.startsWith('MAIL') && !command.startsWith('RCPT')) { capturedHeaders.push(command); } return originalSendCommand(command); }; await smtpClient.sendMail(email); console.log('\nCaptured headers with UTF-8:'); capturedHeaders.forEach(header => { // Check for encoded-word syntax (RFC 2047) if (header.includes('=?')) { const encodedMatch = header.match(/=\?([^?]+)\?([BQ])\?([^?]+)\?=/); if (encodedMatch) { console.log(` Encoded header: ${header.substring(0, 50)}...`); console.log(` Charset: ${encodedMatch[1]}, Encoding: ${encodedMatch[2]}`); } } else if (/[\u0080-\uFFFF]/.test(header)) { console.log(` Raw UTF-8 header: ${header.substring(0, 50)}...`); } }); await smtpClient.close(); }); tap.test('CEP-06: Different character encodings', async () => { const smtpClient = createSmtpClient({ host: testServer.hostname, port: testServer.port, secure: false, connectionTimeout: 5000, debug: true }); await smtpClient.connect(); // Test different encoding scenarios const encodingTests = [ { name: 'Plain ASCII', subject: 'Simple ASCII Subject', text: 'This is plain ASCII text.', expectedEncoding: 'none' }, { name: 'Latin-1 characters', subject: 'Café, naïve, résumé', text: 'Text with Latin-1: àáâãäåæçèéêë', expectedEncoding: 'quoted-printable or base64' }, { name: 'CJK characters', subject: '会議の予定:明日', text: '明日の会議は午後3時からです。', expectedEncoding: 'base64' }, { name: 'Mixed scripts', subject: 'Hello 你好 مرحبا', text: 'Mixed: English, 中文, العربية, Русский', expectedEncoding: 'base64' }, { name: 'Emoji heavy', subject: '🎉 Party Time 🎊', text: '🌟✨🎈🎁🎂🍰🎵🎶💃🕺', expectedEncoding: 'base64' } ]; for (const test of encodingTests) { console.log(`\nTesting: ${test.name}`); const email = new Email({ from: 'sender@example.com', to: ['recipient@example.com'], subject: test.subject, text: test.text }); let transferEncoding = ''; let subjectEncoding = ''; const originalSendCommand = smtpClient.sendCommand.bind(smtpClient); smtpClient.sendCommand = async (command: string) => { if (command.toLowerCase().includes('content-transfer-encoding:')) { transferEncoding = command.split(':')[1].trim(); } if (command.toLowerCase().startsWith('subject:')) { if (command.includes('=?')) { subjectEncoding = 'encoded-word'; } else { subjectEncoding = 'raw'; } } return originalSendCommand(command); }; await smtpClient.sendMail(email); console.log(` Subject encoding: ${subjectEncoding}`); console.log(` Body transfer encoding: ${transferEncoding}`); console.log(` Expected: ${test.expectedEncoding}`); } await smtpClient.close(); }); tap.test('CEP-06: Line length handling for UTF-8', async () => { const smtpClient = createSmtpClient({ host: testServer.hostname, port: testServer.port, secure: false, connectionTimeout: 5000, debug: true }); await smtpClient.connect(); // Create long lines with UTF-8 characters const longJapanese = '日本語のテキスト'.repeat(20); // ~300 bytes const longEmoji = '😀😃😄😁😆😅😂🤣'.repeat(25); // ~800 bytes const email = new Email({ from: 'sender@example.com', to: ['recipient@example.com'], subject: 'Long UTF-8 Lines Test', text: `Short line\n${longJapanese}\nAnother short line\n${longEmoji}\nEnd` }); // Monitor line lengths let maxLineLength = 0; let longLines = 0; const originalSendCommand = smtpClient.sendCommand.bind(smtpClient); let inData = false; smtpClient.sendCommand = async (command: string) => { if (command === 'DATA') { inData = true; } else if (command === '.') { inData = false; } else if (inData) { const lines = command.split('\r\n'); lines.forEach(line => { const byteLength = Buffer.byteLength(line, 'utf8'); maxLineLength = Math.max(maxLineLength, byteLength); if (byteLength > 78) { // RFC recommended line length longLines++; } }); } return originalSendCommand(command); }; await smtpClient.sendMail(email); console.log(`\nLine length analysis:`); console.log(` Maximum line length: ${maxLineLength} bytes`); console.log(` Lines over 78 bytes: ${longLines}`); // Lines should be properly wrapped or encoded if (maxLineLength > 998) { // RFC hard limit console.log(' WARNING: Lines exceed RFC 5321 limit of 998 bytes'); } await smtpClient.close(); }); tap.test('CEP-06: Bidirectional text handling', async () => { const smtpClient = createSmtpClient({ host: testServer.hostname, port: testServer.port, secure: false, connectionTimeout: 5000, debug: true }); await smtpClient.connect(); // Test bidirectional text (RTL and LTR mixed) const email = new Email({ from: 'sender@example.com', to: ['recipient@example.com'], subject: 'مرحبا Hello שלום', text: 'Mixed direction text:\n' + 'English text followed by عربي ثم עברית\n' + 'מספרים: 123 أرقام: ٤٥٦\n' + 'LTR: Hello → RTL: مرحبا ← LTR: World' }); const result = await smtpClient.sendMail(email); expect(result).toBeTruthy(); console.log('Successfully sent email with bidirectional text'); await smtpClient.close(); }); tap.test('CEP-06: Special UTF-8 cases', async () => { const smtpClient = createSmtpClient({ host: testServer.hostname, port: testServer.port, secure: false, connectionTimeout: 5000, debug: true }); await smtpClient.connect(); // Test special UTF-8 cases const specialCases = [ { name: 'Zero-width characters', text: 'Visible‌Zero‌Width‌Non‌Joiner‌Between‌Words' }, { name: 'Combining characters', text: 'a\u0300 e\u0301 i\u0302 o\u0303 u\u0308' // à é î õ ü }, { name: 'Surrogate pairs', text: '𝐇𝐞𝐥𝐥𝐨 𝕎𝕠𝕣𝕝𝕕 🏴󠁧󠁢󠁳󠁣󠁴󠁿' // Mathematical bold, flags }, { name: 'Right-to-left marks', text: '\u202Edetrevni si txet sihT\u202C' // RTL override }, { name: 'Non-standard spaces', text: 'Different spaces: \u2000\u2001\u2002\u2003\u2004' } ]; for (const testCase of specialCases) { console.log(`\nTesting ${testCase.name}`); const email = new Email({ from: 'sender@example.com', to: ['recipient@example.com'], subject: `UTF-8 Special: ${testCase.name}`, text: testCase.text }); try { const result = await smtpClient.sendMail(email); console.log(` Result: ${result ? 'Success' : 'Failed'}`); console.log(` Text bytes: ${Buffer.byteLength(testCase.text, 'utf8')}`); } catch (error) { console.log(` Error: ${error.message}`); } } await smtpClient.close(); }); tap.test('CEP-06: Fallback encoding for non-UTF8 servers', async () => { const smtpClient = createSmtpClient({ host: testServer.hostname, port: testServer.port, secure: false, connectionTimeout: 5000, preferredEncoding: 'quoted-printable', // Force specific encoding debug: true }); await smtpClient.connect(); // Send UTF-8 content that needs encoding const email = new Email({ from: 'sender@example.com', to: ['recipient@example.com'], subject: 'Fallback Encoding: Café français', text: 'Testing encoding: àèìòù ÀÈÌÒÙ äëïöü ñç' }); let encodingUsed = ''; const originalSendCommand = smtpClient.sendCommand.bind(smtpClient); smtpClient.sendCommand = async (command: string) => { if (command.toLowerCase().includes('content-transfer-encoding:')) { encodingUsed = command.split(':')[1].trim(); } return originalSendCommand(command); }; await smtpClient.sendMail(email); console.log('\nFallback encoding test:'); console.log('Preferred encoding:', 'quoted-printable'); console.log('Actual encoding used:', encodingUsed); await smtpClient.close(); }); tap.test('cleanup test SMTP server', async () => { if (testServer) { await testServer.stop(); } }); export default tap.start();