462 lines
15 KiB
TypeScript
462 lines
15 KiB
TypeScript
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: supercalifragilisticexpialidocious',
|
||
'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(); |