dcrouter/test/suite/smtpserver_email-processing/test.ep-07.special-character-handling.ts

462 lines
15 KiB
TypeScript
Raw Permalink Normal View History

2025-05-23 19:09:30 +00:00
import { tap, expect } from '@git.zone/tstest/tapbundle';
2025-05-23 19:03:44 +00:00
import * as net from 'net';
2025-05-24 00:23:35 +00:00
import { startTestServer, stopTestServer, type ITestServer } from '../../helpers/server.loader.js';
2025-05-23 19:03:44 +00:00
2025-05-24 00:23:35 +00:00
const TEST_PORT = 30050;
let testServer: ITestServer;
2025-05-23 19:03:44 +00:00
tap.test('setup - start test server', async () => {
2025-05-24 00:23:35 +00:00
testServer = await startTestServer({ port: TEST_PORT, hostname: 'localhost' });
expect(testServer).toBeDefined();
2025-05-23 19:03:44 +00:00
});
tap.test('Special Character Handling - Comprehensive Unicode test', async (tools) => {
const done = tools.defer();
const socket = net.createConnection({
host: 'localhost',
port: TEST_PORT,
timeout: 30000
});
let dataBuffer = '';
let step = 'greeting';
let completed = false;
socket.on('data', (data) => {
dataBuffer += data.toString();
console.log('Server response:', data.toString());
if (step === 'greeting' && dataBuffer.includes('220 ')) {
step = 'ehlo';
socket.write('EHLO testclient\r\n');
dataBuffer = '';
} else if (step === 'ehlo' && dataBuffer.includes('250')) {
step = 'mail';
socket.write('MAIL FROM:<sender@example.com>\r\n');
dataBuffer = '';
} else if (step === 'mail' && dataBuffer.includes('250')) {
step = 'rcpt';
socket.write('RCPT TO:<recipient@example.com>\r\n');
dataBuffer = '';
} else if (step === 'rcpt' && dataBuffer.includes('250')) {
step = 'data';
socket.write('DATA\r\n');
dataBuffer = '';
} else if (step === 'data' && dataBuffer.includes('354')) {
const email = [
`From: sender@example.com`,
`To: recipient@example.com`,
`Subject: Special Character Test - Unicode & Symbols ñáéíóú`,
`Date: ${new Date().toUTCString()}`,
`Message-ID: <special-chars-${Date.now()}@example.com>`,
`MIME-Version: 1.0`,
`Content-Type: text/plain; charset=utf-8`,
`Content-Transfer-Encoding: 8bit`,
'',
'This email tests special character handling:',
'',
'=== UNICODE CHARACTERS ===',
'Accented letters: àáâãäåæçèéêëìíîïñòóôõöøùúûüý',
'German umlauts: äöüÄÖÜß',
'Scandinavian: åäöÅÄÖ',
'French: àâéèêëïîôœùûüÿç',
'Spanish: ñáéíóúü¿¡',
'Polish: ąćęłńóśźż',
'Russian: абвгдеёжзийклмнопрстуфхцчшщъыьэюя',
'Greek: αβγδεζηθικλμνξοπρστυφχψω',
'Arabic: العربية',
'Hebrew: עברית',
'Chinese: 中文测试',
'Japanese: 日本語テスト',
'Korean: 한국어 테스트',
'Thai: ภาษาไทย',
'',
'=== MATHEMATICAL SYMBOLS ===',
'Math: ∑∏∫∆∇∂∞±×÷≠≤≥≈∝∪∩⊂⊃∈∀∃',
'Greek letters: αβγδεζηθικλμνξοπρστυφχψω',
'Arrows: ←→↑↓↔↕⇐⇒⇑⇓⇔⇕',
'',
'=== CURRENCY & SYMBOLS ===',
'Currency: $€£¥¢₹₽₩₪₫₨₦₡₵₴₸₼₲₱',
'Symbols: ©®™§¶†‡•…‰‱°℃℉№',
2025-05-24 01:00:30 +00:00
`Punctuation: «»""''‚„‹›–—―‖‗''""‚„…‰′″‴‵‶‷‸‹›※‼‽⁇⁈⁉⁏⁐⁑⁒⁓⁔⁕⁖⁗⁘⁙⁚⁛⁜⁝⁞`,
2025-05-23 19:03:44 +00:00
'',
'=== EMOJI & SYMBOLS ===',
'Common: ☀☁☂☃☄★☆☇☈☉☊☋☌☍☎☏☐☑☒☓☔☕☖☗☘☙☚☛☜☝☞☟☠☡☢☣☤☥☦☧☨☩☪☫☬☭☮☯☰☱☲☳☴☵☶☷',
'Smileys: ☺☻☹☿♀♁♂♃♄♅♆♇',
'Hearts: ♡♢♣♤♥♦♧♨♩♪♫♬♭♮♯',
'',
'=== SPECIAL FORMATTING ===',
'Zero-width chars: ',
'Combining: e̊åa̋o̧ç',
'Ligatures: fffiflffifflſtst',
'Fractions: ½⅓⅔¼¾⅛⅜⅝⅞',
'Superscript: ⁰¹²³⁴⁵⁶⁷⁸⁹',
'Subscript: ₀₁₂₃₄₅₆₇₈₉',
'',
'End of special character test.',
'.',
''
].join('\r\n');
console.log('Sending email with comprehensive Unicode characters');
socket.write(email);
step = 'sent';
dataBuffer = '';
} else if (step === 'sent' && dataBuffer.includes('250 ') && dataBuffer.includes('message queued')) {
if (!completed) {
completed = true;
console.log('Email with special characters accepted successfully');
2025-05-23 21:20:39 +00:00
expect(true).toEqual(true);
2025-05-23 19:03:44 +00:00
socket.write('QUIT\r\n');
socket.end();
done.resolve();
}
}
});
socket.on('error', (err) => {
console.error('Socket error:', err);
done.reject(err);
});
await done.promise;
});
tap.test('Special Character Handling - Control characters', async (tools) => {
const done = tools.defer();
const socket = net.createConnection({
host: 'localhost',
port: TEST_PORT,
timeout: 30000
});
let dataBuffer = '';
let step = 'greeting';
let completed = false;
socket.on('data', (data) => {
dataBuffer += data.toString();
console.log('Server response:', data.toString());
if (step === 'greeting' && dataBuffer.includes('220 ')) {
step = 'ehlo';
socket.write('EHLO testclient\r\n');
dataBuffer = '';
} else if (step === 'ehlo' && dataBuffer.includes('250')) {
step = 'mail';
socket.write('MAIL FROM:<sender@example.com>\r\n');
dataBuffer = '';
} else if (step === 'mail' && dataBuffer.includes('250')) {
step = 'rcpt';
socket.write('RCPT TO:<recipient@example.com>\r\n');
dataBuffer = '';
} else if (step === 'rcpt' && dataBuffer.includes('250')) {
step = 'data';
socket.write('DATA\r\n');
dataBuffer = '';
} else if (step === 'data' && dataBuffer.includes('354')) {
const email = [
`From: sender@example.com`,
`To: recipient@example.com`,
`Subject: Control Character Test`,
`Date: ${new Date().toUTCString()}`,
`Message-ID: <control-chars-${Date.now()}@example.com>`,
`MIME-Version: 1.0`,
`Content-Type: text/plain; charset=utf-8`,
'',
'=== CONTROL CHARACTERS TEST ===',
'Tab character: (between words)',
'Non-breaking space: word word',
'Soft hyphen: super­cali­fragi­listic­expi­ali­docious',
'Vertical tab: word\x0Bword',
'Form feed: word\x0Cword',
'Backspace: word\x08word',
'',
'=== LINE ENDING TESTS ===',
'Unix LF: Line1\nLine2',
'Windows CRLF: Line3\r\nLine4',
'Mac CR: Line5\rLine6',
'',
'=== BOUNDARY CHARACTERS ===',
'SMTP boundary test: . (dot at start)',
'Double dots: .. (escaped in SMTP)',
'CRLF.CRLF sequence test',
'.',
''
].join('\r\n');
socket.write(email);
step = 'sent';
dataBuffer = '';
} else if (step === 'sent' && dataBuffer.includes('250 ') && dataBuffer.includes('message queued')) {
if (!completed) {
completed = true;
console.log('Email with control characters accepted');
2025-05-23 21:20:39 +00:00
expect(true).toEqual(true);
2025-05-23 19:03:44 +00:00
socket.write('QUIT\r\n');
socket.end();
done.resolve();
}
}
});
socket.on('error', (err) => {
console.error('Socket error:', err);
done.reject(err);
});
await done.promise;
});
tap.test('Special Character Handling - Subject header encoding', async (tools) => {
const done = tools.defer();
const socket = net.createConnection({
host: 'localhost',
port: TEST_PORT,
timeout: 30000
});
let dataBuffer = '';
let step = 'greeting';
let completed = false;
socket.on('data', (data) => {
dataBuffer += data.toString();
console.log('Server response:', data.toString());
if (step === 'greeting' && dataBuffer.includes('220 ')) {
step = 'ehlo';
socket.write('EHLO testclient\r\n');
dataBuffer = '';
} else if (step === 'ehlo' && dataBuffer.includes('250')) {
step = 'mail';
socket.write('MAIL FROM:<sender@example.com>\r\n');
dataBuffer = '';
} else if (step === 'mail' && dataBuffer.includes('250')) {
step = 'rcpt';
socket.write('RCPT TO:<recipient@example.com>\r\n');
dataBuffer = '';
} else if (step === 'rcpt' && dataBuffer.includes('250')) {
step = 'data';
socket.write('DATA\r\n');
dataBuffer = '';
} else if (step === 'data' && dataBuffer.includes('354')) {
const email = [
`From: sender@example.com`,
`To: recipient@example.com`,
`Subject: =?UTF-8?B?8J+YgCBFbW9qaSBpbiBTdWJqZWN0IOKcqCDwn4yI?=`,
`Subject: =?UTF-8?Q?Quoted=2DPrintable=20Subject=20=C3=A1=C3=A9=C3=AD=C3=B3=C3=BA?=`,
`Date: ${new Date().toUTCString()}`,
`Message-ID: <encoded-subject-${Date.now()}@example.com>`,
'',
'Testing encoded subject headers with special characters.',
'.',
''
].join('\r\n');
socket.write(email);
step = 'sent';
dataBuffer = '';
} else if (step === 'sent' && dataBuffer.includes('250 ') && dataBuffer.includes('message queued')) {
if (!completed) {
completed = true;
console.log('Email with encoded subject headers accepted');
2025-05-23 21:20:39 +00:00
expect(true).toEqual(true);
2025-05-23 19:03:44 +00:00
socket.write('QUIT\r\n');
socket.end();
done.resolve();
}
}
});
socket.on('error', (err) => {
console.error('Socket error:', err);
done.reject(err);
});
await done.promise;
});
tap.test('Special Character Handling - Address headers with special chars', async (tools) => {
const done = tools.defer();
const socket = net.createConnection({
host: 'localhost',
port: TEST_PORT,
timeout: 30000
});
let dataBuffer = '';
let step = 'greeting';
let completed = false;
socket.on('data', (data) => {
dataBuffer += data.toString();
console.log('Server response:', data.toString());
if (step === 'greeting' && dataBuffer.includes('220 ')) {
step = 'ehlo';
socket.write('EHLO testclient\r\n');
dataBuffer = '';
} else if (step === 'ehlo' && dataBuffer.includes('250')) {
step = 'mail';
socket.write('MAIL FROM:<sender@example.com>\r\n');
dataBuffer = '';
} else if (step === 'mail' && dataBuffer.includes('250')) {
step = 'rcpt';
socket.write('RCPT TO:<recipient@example.com>\r\n');
dataBuffer = '';
} else if (step === 'rcpt' && dataBuffer.includes('250')) {
step = 'data';
socket.write('DATA\r\n');
dataBuffer = '';
} else if (step === 'data' && dataBuffer.includes('354')) {
const email = [
`From: "José García" <jose@example.com>`,
`To: "François Müller" <francois@example.com>, "北京用户" <beijing@example.com>`,
`Cc: =?UTF-8?B?IkFubmEgw4XDpMO2Ig==?= <anna@example.com>`,
`Reply-To: "Søren Ñoño" <soren@example.com>`,
`Subject: Special names in address headers`,
`Date: ${new Date().toUTCString()}`,
`Message-ID: <special-addrs-${Date.now()}@example.com>`,
'',
'Testing special characters in email addresses and display names.',
'.',
''
].join('\r\n');
socket.write(email);
step = 'sent';
dataBuffer = '';
} else if (step === 'sent' && dataBuffer.includes('250 ') && dataBuffer.includes('message queued')) {
if (!completed) {
completed = true;
console.log('Email with special characters in addresses accepted');
2025-05-23 21:20:39 +00:00
expect(true).toEqual(true);
2025-05-23 19:03:44 +00:00
socket.write('QUIT\r\n');
socket.end();
done.resolve();
}
}
});
socket.on('error', (err) => {
console.error('Socket error:', err);
done.reject(err);
});
await done.promise;
});
tap.test('Special Character Handling - Mixed encodings', async (tools) => {
const done = tools.defer();
const socket = net.createConnection({
host: 'localhost',
port: TEST_PORT,
timeout: 30000
});
let dataBuffer = '';
let step = 'greeting';
let completed = false;
socket.on('data', (data) => {
dataBuffer += data.toString();
console.log('Server response:', data.toString());
if (step === 'greeting' && dataBuffer.includes('220 ')) {
step = 'ehlo';
socket.write('EHLO testclient\r\n');
dataBuffer = '';
} else if (step === 'ehlo' && dataBuffer.includes('250')) {
step = 'mail';
socket.write('MAIL FROM:<sender@example.com>\r\n');
dataBuffer = '';
} else if (step === 'mail' && dataBuffer.includes('250')) {
step = 'rcpt';
socket.write('RCPT TO:<recipient@example.com>\r\n');
dataBuffer = '';
} else if (step === 'rcpt' && dataBuffer.includes('250')) {
step = 'data';
socket.write('DATA\r\n');
dataBuffer = '';
} else if (step === 'data' && dataBuffer.includes('354')) {
const boundary = 'mixed-encoding-boundary';
const email = [
`From: sender@example.com`,
`To: recipient@example.com`,
`Subject: Mixed Encoding Test`,
`Date: ${new Date().toUTCString()}`,
`Message-ID: <mixed-enc-${Date.now()}@example.com>`,
`MIME-Version: 1.0`,
`Content-Type: multipart/mixed; boundary="${boundary}"`,
'',
`--${boundary}`,
`Content-Type: text/plain; charset=utf-8`,
`Content-Transfer-Encoding: 8bit`,
'',
'UTF-8 part: ñáéíóú 中文 日本語',
'',
`--${boundary}`,
`Content-Type: text/plain; charset=iso-8859-1`,
`Content-Transfer-Encoding: quoted-printable`,
'',
'ISO-8859-1 part: =F1=E1=E9=ED=F3=FA',
'',
`--${boundary}`,
`Content-Type: text/plain; charset=windows-1252`,
'',
'Windows-1252 part: €‚ƒ„…†‡',
'',
`--${boundary}`,
`Content-Type: text/plain; charset=utf-16`,
`Content-Transfer-Encoding: base64`,
'',
Buffer.from('UTF-16 text: ñoño', 'utf16le').toString('base64'),
'',
`--${boundary}--`,
'.',
''
].join('\r\n');
socket.write(email);
step = 'sent';
dataBuffer = '';
} else if (step === 'sent' && dataBuffer.includes('250 ') && dataBuffer.includes('message queued')) {
if (!completed) {
completed = true;
console.log('Email with mixed character encodings accepted');
2025-05-23 21:20:39 +00:00
expect(true).toEqual(true);
2025-05-23 19:03:44 +00:00
socket.write('QUIT\r\n');
socket.end();
done.resolve();
}
}
});
socket.on('error', (err) => {
console.error('Socket error:', err);
done.reject(err);
});
await done.promise;
});
tap.test('cleanup - stop test server', async () => {
2025-05-24 00:23:35 +00:00
await stopTestServer(testServer);
expect(true).toEqual(true);
2025-05-23 19:03:44 +00:00
});
2025-05-25 19:05:43 +00:00
export default tap.start();