dcrouter/test/suite/smtpserver_email-processing/test.ep-07.special-character-handling.ts
2025-05-25 19:05:43 +00:00

462 lines
15 KiB
TypeScript
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { tap, expect } from '@git.zone/tstest/tapbundle';
import * as net from 'net';
import { startTestServer, stopTestServer, type ITestServer } from '../../helpers/server.loader.js';
const TEST_PORT = 30050;
let testServer: ITestServer;
tap.test('setup - start test server', async () => {
testServer = await startTestServer({ port: TEST_PORT, hostname: 'localhost' });
expect(testServer).toBeDefined();
});
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: ©®™§¶†‡•…‰‱°℃℉№',
`Punctuation: «»""''‚„‹›–—―‖‗''""‚„…‰′″‴‵‶‷‸‹›※‼‽⁇⁈⁉⁏⁐⁑⁒⁓⁔⁕⁖⁗⁘⁙⁚⁛⁜⁝⁞`,
'',
'=== 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');
expect(true).toEqual(true);
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');
expect(true).toEqual(true);
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');
expect(true).toEqual(true);
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');
expect(true).toEqual(true);
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');
expect(true).toEqual(true);
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 () => {
await stopTestServer(testServer);
expect(true).toEqual(true);
});
export default tap.start();