update
This commit is contained in:
@@ -1,7 +1,6 @@
|
||||
import { tap, expect } from '@git.zone/tapbundle';
|
||||
import { tap, expect } from '@git.zone/tstest/tapbundle';
|
||||
import * as net from 'net';
|
||||
import { startTestServer, stopTestServer } from '../../helpers/server.loader.js';
|
||||
import type { ITestServer } from '../../helpers/server.loader.js';
|
||||
import { startTestServer, stopTestServer, type ITestServer } from '../../helpers/server.loader.js';
|
||||
|
||||
const TEST_PORT = 30036;
|
||||
const TEST_TIMEOUT = 30000;
|
||||
@@ -13,7 +12,7 @@ tap.test('setup - start SMTP server for empty command tests', async () => {
|
||||
port: TEST_PORT,
|
||||
hostname: 'localhost'
|
||||
});
|
||||
expect(testServer).toBeInstanceOf(Object);
|
||||
expect(testServer).toBeDefined();
|
||||
});
|
||||
|
||||
tap.test('Empty Commands - should reject empty line (just CRLF)', async (tools) => {
|
||||
@@ -204,7 +203,7 @@ tap.test('Empty Commands - should reject MAIL FROM with empty parameter', async
|
||||
|
||||
// Should get syntax error (501 or 550)
|
||||
expect(response).toMatch(/^5\d{2}/);
|
||||
expect(response.toLowerCase()).toMatch(/syntax|parameter|address/);
|
||||
expect(response).toMatch(/syntax|parameter|address/i);
|
||||
|
||||
// Clean up
|
||||
socket.write('QUIT\r\n');
|
||||
@@ -265,7 +264,7 @@ tap.test('Empty Commands - should reject RCPT TO with empty parameter', async (t
|
||||
|
||||
// Should get syntax error
|
||||
expect(response).toMatch(/^5\d{2}/);
|
||||
expect(response.toLowerCase()).toMatch(/syntax|parameter|address/);
|
||||
expect(response).toMatch(/syntax|parameter|address/i);
|
||||
|
||||
// Clean up
|
||||
socket.write('QUIT\r\n');
|
||||
|
||||
@@ -1,317 +1,404 @@
|
||||
import { tap, expect } from '@git.zone/tstest/tapbundle';
|
||||
import * as plugins from '../plugins.js';
|
||||
import * as net from 'net';
|
||||
import { startTestServer, stopTestServer, TEST_PORT, sendEmailWithRawSocket } from '../../helpers/server.loader.js';
|
||||
import type { SmtpServer } from '../../../ts/mail/delivery/smtpserver/index.js';
|
||||
import { startTestServer, stopTestServer, type ITestServer } from '../../helpers/server.loader.js';
|
||||
|
||||
let testServer: SmtpServer;
|
||||
const TEST_PORT = 2525;
|
||||
const TEST_TIMEOUT = 30000;
|
||||
|
||||
let testServer: ITestServer;
|
||||
|
||||
tap.test('setup - start test server', async () => {
|
||||
testServer = await startTestServer();
|
||||
await plugins.smartdelay.delayFor(1000);
|
||||
testServer = await startTestServer({ port: TEST_PORT });
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
expect(testServer).toBeDefined();
|
||||
});
|
||||
|
||||
tap.test('Extremely Long Headers - should handle single extremely long header', async (tools) => {
|
||||
const done = tools.defer();
|
||||
|
||||
const socket = net.createConnection({
|
||||
host: 'localhost',
|
||||
port: TEST_PORT,
|
||||
timeout: 30000
|
||||
});
|
||||
|
||||
let dataBuffer = '';
|
||||
|
||||
socket.on('data', (data) => {
|
||||
dataBuffer += data.toString();
|
||||
console.log('Server response:', data.toString());
|
||||
try {
|
||||
const socket = net.createConnection({
|
||||
host: 'localhost',
|
||||
port: TEST_PORT,
|
||||
timeout: TEST_TIMEOUT
|
||||
});
|
||||
|
||||
if (dataBuffer.includes('220 ')) {
|
||||
// Send EHLO
|
||||
socket.write('EHLO testclient\r\n');
|
||||
} else if (dataBuffer.includes('250 ') && dataBuffer.includes('EHLO')) {
|
||||
// Send MAIL FROM
|
||||
socket.write('MAIL FROM:<sender@example.com>\r\n');
|
||||
dataBuffer = '';
|
||||
} else if (dataBuffer.includes('250 ') && dataBuffer.includes('sender accepted')) {
|
||||
// Send RCPT TO
|
||||
socket.write('RCPT TO:<recipient@example.com>\r\n');
|
||||
dataBuffer = '';
|
||||
} else if (dataBuffer.includes('250 ') && dataBuffer.includes('recipient accepted')) {
|
||||
// Send DATA
|
||||
socket.write('DATA\r\n');
|
||||
dataBuffer = '';
|
||||
} else if (dataBuffer.includes('354 ')) {
|
||||
// Send email with extremely long header
|
||||
const longValue = 'X'.repeat(3000);
|
||||
const emailContent = [
|
||||
`Subject: Test Email`,
|
||||
`From: sender@example.com`,
|
||||
`To: recipient@example.com`,
|
||||
`X-Long-Header: ${longValue}`,
|
||||
'',
|
||||
'This email has an extremely long header.',
|
||||
'.',
|
||||
''
|
||||
].join('\r\n');
|
||||
|
||||
socket.write(emailContent);
|
||||
dataBuffer = '';
|
||||
} else if (dataBuffer.includes('250 ') && dataBuffer.includes('Message accepted') ||
|
||||
dataBuffer.includes('552 ') ||
|
||||
dataBuffer.includes('554 ') ||
|
||||
dataBuffer.includes('500 ')) {
|
||||
// Either accepted or gracefully rejected
|
||||
const accepted = dataBuffer.includes('250 ');
|
||||
console.log(`Long header test ${accepted ? 'accepted' : 'rejected'}`);
|
||||
|
||||
socket.write('QUIT\r\n');
|
||||
socket.end();
|
||||
done.resolve();
|
||||
}
|
||||
});
|
||||
|
||||
socket.on('error', (err) => {
|
||||
console.error('Socket error:', err);
|
||||
done.reject(err);
|
||||
});
|
||||
|
||||
socket.on('timeout', () => {
|
||||
console.error('Socket timeout');
|
||||
socket.destroy();
|
||||
done.reject(new Error('Socket timeout'));
|
||||
});
|
||||
|
||||
await done.promise;
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
socket.once('connect', () => resolve());
|
||||
socket.once('error', reject);
|
||||
});
|
||||
|
||||
// Get banner
|
||||
await new Promise<string>((resolve) => {
|
||||
socket.once('data', (chunk) => resolve(chunk.toString()));
|
||||
});
|
||||
|
||||
// Send EHLO
|
||||
socket.write('EHLO testclient\r\n');
|
||||
await new Promise<string>((resolve) => {
|
||||
let data = '';
|
||||
const handler = (chunk: Buffer) => {
|
||||
data += chunk.toString();
|
||||
if (data.includes('\r\n') && (data.match(/^250 /m) || data.match(/^250-.*\r\n250 /ms))) {
|
||||
socket.removeListener('data', handler);
|
||||
resolve(data);
|
||||
}
|
||||
};
|
||||
socket.on('data', handler);
|
||||
});
|
||||
|
||||
// Send MAIL FROM
|
||||
socket.write('MAIL FROM:<sender@example.com>\r\n');
|
||||
const mailResponse = await new Promise<string>((resolve) => {
|
||||
socket.once('data', (chunk) => resolve(chunk.toString()));
|
||||
});
|
||||
expect(mailResponse).toInclude('250');
|
||||
|
||||
// Send RCPT TO
|
||||
socket.write('RCPT TO:<recipient@example.com>\r\n');
|
||||
const rcptResponse = await new Promise<string>((resolve) => {
|
||||
socket.once('data', (chunk) => resolve(chunk.toString()));
|
||||
});
|
||||
expect(rcptResponse).toInclude('250');
|
||||
|
||||
// Send DATA
|
||||
socket.write('DATA\r\n');
|
||||
const dataResponse = await new Promise<string>((resolve) => {
|
||||
socket.once('data', (chunk) => resolve(chunk.toString()));
|
||||
});
|
||||
expect(dataResponse).toInclude('354');
|
||||
|
||||
// Send email with extremely long header (3000 characters)
|
||||
const longValue = 'X'.repeat(3000);
|
||||
const emailContent = [
|
||||
`Subject: Test Email`,
|
||||
`From: sender@example.com`,
|
||||
`To: recipient@example.com`,
|
||||
`X-Long-Header: ${longValue}`,
|
||||
'',
|
||||
'This email has an extremely long header.',
|
||||
'.',
|
||||
''
|
||||
].join('\r\n');
|
||||
|
||||
socket.write(emailContent);
|
||||
|
||||
const finalResponse = await new Promise<string>((resolve) => {
|
||||
socket.once('data', (chunk) => resolve(chunk.toString()));
|
||||
});
|
||||
|
||||
// Server might accept or reject - both are valid for extremely long headers
|
||||
const accepted = finalResponse.includes('250');
|
||||
const rejected = finalResponse.includes('552') || finalResponse.includes('554') || finalResponse.includes('500');
|
||||
|
||||
console.log(`Long header test ${accepted ? 'accepted' : 'rejected'}: ${finalResponse.trim()}`);
|
||||
expect(accepted || rejected).toEqual(true);
|
||||
|
||||
// Clean up
|
||||
socket.write('QUIT\r\n');
|
||||
socket.end();
|
||||
|
||||
} finally {
|
||||
done.resolve();
|
||||
}
|
||||
});
|
||||
|
||||
tap.test('Extremely Long Headers - should handle multi-line header with many segments', async (tools) => {
|
||||
const done = tools.defer();
|
||||
|
||||
const socket = net.createConnection({
|
||||
host: 'localhost',
|
||||
port: TEST_PORT,
|
||||
timeout: 30000
|
||||
});
|
||||
|
||||
let dataBuffer = '';
|
||||
|
||||
socket.on('data', (data) => {
|
||||
dataBuffer += data.toString();
|
||||
console.log('Server response:', data.toString());
|
||||
try {
|
||||
const socket = net.createConnection({
|
||||
host: 'localhost',
|
||||
port: TEST_PORT,
|
||||
timeout: TEST_TIMEOUT
|
||||
});
|
||||
|
||||
if (dataBuffer.includes('220 ')) {
|
||||
socket.write('EHLO testclient\r\n');
|
||||
} else if (dataBuffer.includes('250 ') && dataBuffer.includes('EHLO')) {
|
||||
socket.write('MAIL FROM:<sender@example.com>\r\n');
|
||||
dataBuffer = '';
|
||||
} else if (dataBuffer.includes('250 ') && dataBuffer.includes('sender accepted')) {
|
||||
socket.write('RCPT TO:<recipient@example.com>\r\n');
|
||||
dataBuffer = '';
|
||||
} else if (dataBuffer.includes('250 ') && dataBuffer.includes('recipient accepted')) {
|
||||
socket.write('DATA\r\n');
|
||||
dataBuffer = '';
|
||||
} else if (dataBuffer.includes('354 ')) {
|
||||
// Create multi-line header with 50 segments
|
||||
const segments = [];
|
||||
for (let i = 0; i < 50; i++) {
|
||||
segments.push(` Segment ${i}: ${' '.repeat(60)}value`);
|
||||
}
|
||||
|
||||
const emailContent = [
|
||||
`Subject: Test Email`,
|
||||
`From: sender@example.com`,
|
||||
`To: recipient@example.com`,
|
||||
`X-Multi-Line: Initial value`,
|
||||
...segments,
|
||||
'',
|
||||
'This email has a multi-line header with many segments.',
|
||||
'.',
|
||||
''
|
||||
].join('\r\n');
|
||||
|
||||
socket.write(emailContent);
|
||||
dataBuffer = '';
|
||||
} else if (dataBuffer.includes('250 ') && dataBuffer.includes('Message accepted') ||
|
||||
dataBuffer.includes('552 ') ||
|
||||
dataBuffer.includes('554 ') ||
|
||||
dataBuffer.includes('500 ')) {
|
||||
const accepted = dataBuffer.includes('250 ');
|
||||
console.log(`Multi-line header test ${accepted ? 'accepted' : 'rejected'}`);
|
||||
|
||||
socket.write('QUIT\r\n');
|
||||
socket.end();
|
||||
done.resolve();
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
socket.once('connect', () => resolve());
|
||||
socket.once('error', reject);
|
||||
});
|
||||
|
||||
// Get banner
|
||||
await new Promise<string>((resolve) => {
|
||||
socket.once('data', (chunk) => resolve(chunk.toString()));
|
||||
});
|
||||
|
||||
// Send EHLO
|
||||
socket.write('EHLO testclient\r\n');
|
||||
await new Promise<string>((resolve) => {
|
||||
let data = '';
|
||||
const handler = (chunk: Buffer) => {
|
||||
data += chunk.toString();
|
||||
if (data.includes('\r\n') && (data.match(/^250 /m) || data.match(/^250-.*\r\n250 /ms))) {
|
||||
socket.removeListener('data', handler);
|
||||
resolve(data);
|
||||
}
|
||||
};
|
||||
socket.on('data', handler);
|
||||
});
|
||||
|
||||
// Send MAIL FROM
|
||||
socket.write('MAIL FROM:<sender@example.com>\r\n');
|
||||
const mailResponse = await new Promise<string>((resolve) => {
|
||||
socket.once('data', (chunk) => resolve(chunk.toString()));
|
||||
});
|
||||
expect(mailResponse).toInclude('250');
|
||||
|
||||
// Send RCPT TO
|
||||
socket.write('RCPT TO:<recipient@example.com>\r\n');
|
||||
const rcptResponse = await new Promise<string>((resolve) => {
|
||||
socket.once('data', (chunk) => resolve(chunk.toString()));
|
||||
});
|
||||
expect(rcptResponse).toInclude('250');
|
||||
|
||||
// Send DATA
|
||||
socket.write('DATA\r\n');
|
||||
const dataResponse = await new Promise<string>((resolve) => {
|
||||
socket.once('data', (chunk) => resolve(chunk.toString()));
|
||||
});
|
||||
expect(dataResponse).toInclude('354');
|
||||
|
||||
// Create multi-line header with 50 segments (RFC 5322 folding)
|
||||
const segments = [];
|
||||
for (let i = 0; i < 50; i++) {
|
||||
segments.push(` Segment ${i}: ${' '.repeat(60)}value`);
|
||||
}
|
||||
});
|
||||
|
||||
socket.on('error', (err) => {
|
||||
console.error('Socket error:', err);
|
||||
done.reject(err);
|
||||
});
|
||||
|
||||
socket.on('timeout', () => {
|
||||
console.error('Socket timeout');
|
||||
socket.destroy();
|
||||
done.reject(new Error('Socket timeout'));
|
||||
});
|
||||
|
||||
await done.promise;
|
||||
|
||||
const emailContent = [
|
||||
`Subject: Test Email`,
|
||||
`From: sender@example.com`,
|
||||
`To: recipient@example.com`,
|
||||
`X-Multi-Line: Initial value`,
|
||||
...segments,
|
||||
'',
|
||||
'This email has a multi-line header with many segments.',
|
||||
'.',
|
||||
''
|
||||
].join('\r\n');
|
||||
|
||||
socket.write(emailContent);
|
||||
|
||||
const finalResponse = await new Promise<string>((resolve) => {
|
||||
socket.once('data', (chunk) => resolve(chunk.toString()));
|
||||
});
|
||||
|
||||
const accepted = finalResponse.includes('250');
|
||||
const rejected = finalResponse.includes('552') || finalResponse.includes('554') || finalResponse.includes('500');
|
||||
|
||||
console.log(`Multi-line header test ${accepted ? 'accepted' : 'rejected'}: ${finalResponse.trim()}`);
|
||||
expect(accepted || rejected).toEqual(true);
|
||||
|
||||
// Clean up
|
||||
socket.write('QUIT\r\n');
|
||||
socket.end();
|
||||
|
||||
} finally {
|
||||
done.resolve();
|
||||
}
|
||||
});
|
||||
|
||||
tap.test('Extremely Long Headers - should handle multiple long headers in one email', async (tools) => {
|
||||
const done = tools.defer();
|
||||
|
||||
const socket = net.createConnection({
|
||||
host: 'localhost',
|
||||
port: TEST_PORT,
|
||||
timeout: 30000
|
||||
});
|
||||
|
||||
let dataBuffer = '';
|
||||
|
||||
socket.on('data', (data) => {
|
||||
dataBuffer += data.toString();
|
||||
console.log('Server response:', data.toString());
|
||||
try {
|
||||
const socket = net.createConnection({
|
||||
host: 'localhost',
|
||||
port: TEST_PORT,
|
||||
timeout: TEST_TIMEOUT
|
||||
});
|
||||
|
||||
if (dataBuffer.includes('220 ')) {
|
||||
socket.write('EHLO testclient\r\n');
|
||||
} else if (dataBuffer.includes('250 ') && dataBuffer.includes('EHLO')) {
|
||||
socket.write('MAIL FROM:<sender@example.com>\r\n');
|
||||
dataBuffer = '';
|
||||
} else if (dataBuffer.includes('250 ') && dataBuffer.includes('sender accepted')) {
|
||||
socket.write('RCPT TO:<recipient@example.com>\r\n');
|
||||
dataBuffer = '';
|
||||
} else if (dataBuffer.includes('250 ') && dataBuffer.includes('recipient accepted')) {
|
||||
socket.write('DATA\r\n');
|
||||
dataBuffer = '';
|
||||
} else if (dataBuffer.includes('354 ')) {
|
||||
// Create multiple long headers
|
||||
const header1 = 'A'.repeat(1000);
|
||||
const header2 = 'B'.repeat(1500);
|
||||
const header3 = 'C'.repeat(2000);
|
||||
|
||||
const emailContent = [
|
||||
`Subject: Test Email with Multiple Long Headers`,
|
||||
`From: sender@example.com`,
|
||||
`To: recipient@example.com`,
|
||||
`X-Long-Header-1: ${header1}`,
|
||||
`X-Long-Header-2: ${header2}`,
|
||||
`X-Long-Header-3: ${header3}`,
|
||||
'',
|
||||
'This email has multiple long headers.',
|
||||
'.',
|
||||
''
|
||||
].join('\r\n');
|
||||
|
||||
const totalHeaderSize = header1.length + header2.length + header3.length;
|
||||
console.log(`Total header size: ${totalHeaderSize} bytes`);
|
||||
|
||||
socket.write(emailContent);
|
||||
dataBuffer = '';
|
||||
} else if (dataBuffer.includes('250 ') && dataBuffer.includes('Message accepted') ||
|
||||
dataBuffer.includes('552 ') ||
|
||||
dataBuffer.includes('554 ') ||
|
||||
dataBuffer.includes('500 ')) {
|
||||
const accepted = dataBuffer.includes('250 ');
|
||||
console.log(`Multiple long headers test ${accepted ? 'accepted' : 'rejected'}`);
|
||||
|
||||
socket.write('QUIT\r\n');
|
||||
socket.end();
|
||||
done.resolve();
|
||||
}
|
||||
});
|
||||
|
||||
socket.on('error', (err) => {
|
||||
console.error('Socket error:', err);
|
||||
done.reject(err);
|
||||
});
|
||||
|
||||
socket.on('timeout', () => {
|
||||
console.error('Socket timeout');
|
||||
socket.destroy();
|
||||
done.reject(new Error('Socket timeout'));
|
||||
});
|
||||
|
||||
await done.promise;
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
socket.once('connect', () => resolve());
|
||||
socket.once('error', reject);
|
||||
});
|
||||
|
||||
// Get banner
|
||||
await new Promise<string>((resolve) => {
|
||||
socket.once('data', (chunk) => resolve(chunk.toString()));
|
||||
});
|
||||
|
||||
// Send EHLO
|
||||
socket.write('EHLO testclient\r\n');
|
||||
await new Promise<string>((resolve) => {
|
||||
let data = '';
|
||||
const handler = (chunk: Buffer) => {
|
||||
data += chunk.toString();
|
||||
if (data.includes('\r\n') && (data.match(/^250 /m) || data.match(/^250-.*\r\n250 /ms))) {
|
||||
socket.removeListener('data', handler);
|
||||
resolve(data);
|
||||
}
|
||||
};
|
||||
socket.on('data', handler);
|
||||
});
|
||||
|
||||
// Send MAIL FROM
|
||||
socket.write('MAIL FROM:<sender@example.com>\r\n');
|
||||
const mailResponse = await new Promise<string>((resolve) => {
|
||||
socket.once('data', (chunk) => resolve(chunk.toString()));
|
||||
});
|
||||
expect(mailResponse).toInclude('250');
|
||||
|
||||
// Send RCPT TO
|
||||
socket.write('RCPT TO:<recipient@example.com>\r\n');
|
||||
const rcptResponse = await new Promise<string>((resolve) => {
|
||||
socket.once('data', (chunk) => resolve(chunk.toString()));
|
||||
});
|
||||
expect(rcptResponse).toInclude('250');
|
||||
|
||||
// Send DATA
|
||||
socket.write('DATA\r\n');
|
||||
const dataResponse = await new Promise<string>((resolve) => {
|
||||
socket.once('data', (chunk) => resolve(chunk.toString()));
|
||||
});
|
||||
expect(dataResponse).toInclude('354');
|
||||
|
||||
// Create multiple long headers
|
||||
const header1 = 'A'.repeat(1000);
|
||||
const header2 = 'B'.repeat(1500);
|
||||
const header3 = 'C'.repeat(2000);
|
||||
|
||||
const emailContent = [
|
||||
`Subject: Test Email with Multiple Long Headers`,
|
||||
`From: sender@example.com`,
|
||||
`To: recipient@example.com`,
|
||||
`X-Long-Header-1: ${header1}`,
|
||||
`X-Long-Header-2: ${header2}`,
|
||||
`X-Long-Header-3: ${header3}`,
|
||||
'',
|
||||
'This email has multiple long headers.',
|
||||
'.',
|
||||
''
|
||||
].join('\r\n');
|
||||
|
||||
const totalHeaderSize = header1.length + header2.length + header3.length;
|
||||
console.log(`Total header size: ${totalHeaderSize} bytes`);
|
||||
|
||||
socket.write(emailContent);
|
||||
|
||||
const finalResponse = await new Promise<string>((resolve) => {
|
||||
socket.once('data', (chunk) => resolve(chunk.toString()));
|
||||
});
|
||||
|
||||
const accepted = finalResponse.includes('250');
|
||||
const rejected = finalResponse.includes('552') || finalResponse.includes('554') || finalResponse.includes('500');
|
||||
|
||||
console.log(`Multiple long headers test ${accepted ? 'accepted' : 'rejected'}: ${finalResponse.trim()}`);
|
||||
expect(accepted || rejected).toEqual(true);
|
||||
|
||||
// Clean up
|
||||
socket.write('QUIT\r\n');
|
||||
socket.end();
|
||||
|
||||
} finally {
|
||||
done.resolve();
|
||||
}
|
||||
});
|
||||
|
||||
tap.test('Extremely Long Headers - should handle header with exactly RFC limit', async (tools) => {
|
||||
const done = tools.defer();
|
||||
|
||||
const socket = net.createConnection({
|
||||
host: 'localhost',
|
||||
port: TEST_PORT,
|
||||
timeout: 30000
|
||||
});
|
||||
|
||||
let dataBuffer = '';
|
||||
|
||||
socket.on('data', (data) => {
|
||||
dataBuffer += data.toString();
|
||||
console.log('Server response:', data.toString());
|
||||
try {
|
||||
const socket = net.createConnection({
|
||||
host: 'localhost',
|
||||
port: TEST_PORT,
|
||||
timeout: TEST_TIMEOUT
|
||||
});
|
||||
|
||||
if (dataBuffer.includes('220 ')) {
|
||||
socket.write('EHLO testclient\r\n');
|
||||
} else if (dataBuffer.includes('250 ') && dataBuffer.includes('EHLO')) {
|
||||
socket.write('MAIL FROM:<sender@example.com>\r\n');
|
||||
dataBuffer = '';
|
||||
} else if (dataBuffer.includes('250 ') && dataBuffer.includes('sender accepted')) {
|
||||
socket.write('RCPT TO:<recipient@example.com>\r\n');
|
||||
dataBuffer = '';
|
||||
} else if (dataBuffer.includes('250 ') && dataBuffer.includes('recipient accepted')) {
|
||||
socket.write('DATA\r\n');
|
||||
dataBuffer = '';
|
||||
} else if (dataBuffer.includes('354 ')) {
|
||||
// Create header line exactly at RFC 5322 limit (998 chars excluding CRLF)
|
||||
// Header name and colon take some space
|
||||
const headerName = 'X-RFC-Limit';
|
||||
const colonSpace = ': ';
|
||||
const remainingSpace = 998 - headerName.length - colonSpace.length;
|
||||
const headerValue = 'X'.repeat(remainingSpace);
|
||||
|
||||
const emailContent = [
|
||||
`Subject: Test Email`,
|
||||
`From: sender@example.com`,
|
||||
`To: recipient@example.com`,
|
||||
`${headerName}${colonSpace}${headerValue}`,
|
||||
'',
|
||||
'This email has a header at exactly the RFC limit.',
|
||||
'.',
|
||||
''
|
||||
].join('\r\n');
|
||||
|
||||
socket.write(emailContent);
|
||||
dataBuffer = '';
|
||||
} else if (dataBuffer.includes('250 ') && dataBuffer.includes('Message accepted') ||
|
||||
dataBuffer.includes('552 ') ||
|
||||
dataBuffer.includes('554 ') ||
|
||||
dataBuffer.includes('500 ')) {
|
||||
const accepted = dataBuffer.includes('250 ');
|
||||
console.log(`RFC limit header test ${accepted ? 'accepted' : 'rejected'}`);
|
||||
|
||||
socket.write('QUIT\r\n');
|
||||
socket.end();
|
||||
done.resolve();
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
socket.once('connect', () => resolve());
|
||||
socket.once('error', reject);
|
||||
});
|
||||
|
||||
// Get banner
|
||||
await new Promise<string>((resolve) => {
|
||||
socket.once('data', (chunk) => resolve(chunk.toString()));
|
||||
});
|
||||
|
||||
// Send EHLO
|
||||
socket.write('EHLO testclient\r\n');
|
||||
await new Promise<string>((resolve) => {
|
||||
let data = '';
|
||||
const handler = (chunk: Buffer) => {
|
||||
data += chunk.toString();
|
||||
if (data.includes('\r\n') && (data.match(/^250 /m) || data.match(/^250-.*\r\n250 /ms))) {
|
||||
socket.removeListener('data', handler);
|
||||
resolve(data);
|
||||
}
|
||||
};
|
||||
socket.on('data', handler);
|
||||
});
|
||||
|
||||
// Send MAIL FROM
|
||||
socket.write('MAIL FROM:<sender@example.com>\r\n');
|
||||
const mailResponse = await new Promise<string>((resolve) => {
|
||||
socket.once('data', (chunk) => resolve(chunk.toString()));
|
||||
});
|
||||
expect(mailResponse).toInclude('250');
|
||||
|
||||
// Send RCPT TO
|
||||
socket.write('RCPT TO:<recipient@example.com>\r\n');
|
||||
const rcptResponse = await new Promise<string>((resolve) => {
|
||||
socket.once('data', (chunk) => resolve(chunk.toString()));
|
||||
});
|
||||
expect(rcptResponse).toInclude('250');
|
||||
|
||||
// Send DATA
|
||||
socket.write('DATA\r\n');
|
||||
const dataResponse = await new Promise<string>((resolve) => {
|
||||
socket.once('data', (chunk) => resolve(chunk.toString()));
|
||||
});
|
||||
expect(dataResponse).toInclude('354');
|
||||
|
||||
// Create header line exactly at RFC 5322 limit (998 chars excluding CRLF)
|
||||
// Header name and colon take some space
|
||||
const headerName = 'X-RFC-Limit';
|
||||
const colonSpace = ': ';
|
||||
const remainingSpace = 998 - headerName.length - colonSpace.length;
|
||||
const headerValue = 'X'.repeat(remainingSpace);
|
||||
|
||||
const emailContent = [
|
||||
`Subject: Test Email`,
|
||||
`From: sender@example.com`,
|
||||
`To: recipient@example.com`,
|
||||
`${headerName}${colonSpace}${headerValue}`,
|
||||
'',
|
||||
'This email has a header at exactly the RFC limit.',
|
||||
'.',
|
||||
''
|
||||
].join('\r\n');
|
||||
|
||||
socket.write(emailContent);
|
||||
|
||||
const finalResponse = await new Promise<string>((resolve) => {
|
||||
socket.once('data', (chunk) => resolve(chunk.toString()));
|
||||
});
|
||||
|
||||
// This should be accepted since it's exactly at the limit
|
||||
const accepted = finalResponse.includes('250');
|
||||
const rejected = finalResponse.includes('552') || finalResponse.includes('554') || finalResponse.includes('500');
|
||||
|
||||
console.log(`RFC limit header test ${accepted ? 'accepted' : 'rejected'}: ${finalResponse.trim()}`);
|
||||
expect(accepted || rejected).toEqual(true);
|
||||
|
||||
// RFC compliant servers should accept headers exactly at the limit
|
||||
if (accepted) {
|
||||
console.log('✓ Server correctly accepts headers at RFC limit');
|
||||
} else {
|
||||
console.log('⚠ Server rejected header at RFC limit (may be overly strict)');
|
||||
}
|
||||
});
|
||||
|
||||
socket.on('error', (err) => {
|
||||
console.error('Socket error:', err);
|
||||
done.reject(err);
|
||||
});
|
||||
|
||||
socket.on('timeout', () => {
|
||||
console.error('Socket timeout');
|
||||
socket.destroy();
|
||||
done.reject(new Error('Socket timeout'));
|
||||
});
|
||||
|
||||
await done.promise;
|
||||
|
||||
// Clean up
|
||||
socket.write('QUIT\r\n');
|
||||
socket.end();
|
||||
|
||||
} finally {
|
||||
done.resolve();
|
||||
}
|
||||
});
|
||||
|
||||
tap.test('cleanup - stop test server', async () => {
|
||||
await stopTestServer(testServer);
|
||||
expect(true).toEqual(true);
|
||||
});
|
||||
|
||||
tap.start();
|
||||
@@ -1,4 +1,4 @@
|
||||
import { tap, expect } from '@git.zone/tapbundle';
|
||||
import { tap, expect } from '@git.zone/tstest/tapbundle';
|
||||
import * as net from 'net';
|
||||
import { startTestServer, stopTestServer } from '../../helpers/server.loader.js';
|
||||
import type { ITestServer } from '../../helpers/server.loader.js';
|
||||
@@ -13,7 +13,7 @@ tap.test('setup - start SMTP server for extremely long lines tests', async () =>
|
||||
port: TEST_PORT,
|
||||
hostname: 'localhost'
|
||||
});
|
||||
expect(testServer).toBeInstanceOf(Object);
|
||||
expect(testServer).toBeDefined();
|
||||
});
|
||||
|
||||
tap.test('Extremely Long Lines - should handle lines exceeding RFC 5321 limit', async (tools) => {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { tap, expect } from '@git.zone/tapbundle';
|
||||
import { tap, expect } from '@git.zone/tstest/tapbundle';
|
||||
import * as net from 'net';
|
||||
import { startTestServer, stopTestServer } from '../../helpers/server.loader.js';
|
||||
import type { ITestServer } from '../../helpers/server.loader.js';
|
||||
@@ -13,7 +13,7 @@ tap.test('setup - start SMTP server for invalid character tests', async () => {
|
||||
port: TEST_PORT,
|
||||
hostname: 'localhost'
|
||||
});
|
||||
expect(testServer).toBeInstanceOf(Object);
|
||||
expect(testServer).toBeDefined();
|
||||
});
|
||||
|
||||
tap.test('Invalid Character Handling - should handle control characters in email', async (tools) => {
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
import { tap, expect } from '@git.zone/tstest/tapbundle';
|
||||
import * as plugins from '../plugins.js';
|
||||
import * as plugins from '../../../ts/plugins.js';
|
||||
import * as net from 'net';
|
||||
import { startTestServer, stopTestServer, TEST_PORT, sendEmailWithRawSocket } from '../../helpers/server.loader.js';
|
||||
import { startTestServer, stopTestServer } from '../../helpers/server.loader.js';
|
||||
import type { SmtpServer } from '../../../ts/mail/delivery/smtpserver/index.js';
|
||||
|
||||
let testServer: SmtpServer;
|
||||
const TEST_PORT = 2525;
|
||||
|
||||
tap.test('setup - start test server', async () => {
|
||||
testServer = await startTestServer();
|
||||
await plugins.smartdelay.delayFor(1000);
|
||||
testServer = await startTestServer({ port: TEST_PORT });
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
});
|
||||
|
||||
tap.test('Nested MIME Structures - should handle deeply nested multipart structure', async (tools) => {
|
||||
@@ -21,27 +22,33 @@ tap.test('Nested MIME Structures - should handle deeply nested multipart structu
|
||||
});
|
||||
|
||||
let dataBuffer = '';
|
||||
let state = 'initial';
|
||||
|
||||
socket.on('data', (data) => {
|
||||
dataBuffer += data.toString();
|
||||
console.log('Server response:', data.toString());
|
||||
|
||||
if (dataBuffer.includes('220 ')) {
|
||||
if (dataBuffer.includes('220 ') && state === 'initial') {
|
||||
// Send EHLO
|
||||
socket.write('EHLO testclient\r\n');
|
||||
} else if (dataBuffer.includes('250 ') && dataBuffer.includes('EHLO')) {
|
||||
state = 'ehlo_sent';
|
||||
dataBuffer = '';
|
||||
} else if (dataBuffer.includes('250 ') && state === 'ehlo_sent') {
|
||||
// Send MAIL FROM
|
||||
socket.write('MAIL FROM:<sender@example.com>\r\n');
|
||||
state = 'mail_from_sent';
|
||||
dataBuffer = '';
|
||||
} else if (dataBuffer.includes('250 ') && dataBuffer.includes('sender accepted')) {
|
||||
} else if (dataBuffer.includes('250 ') && state === 'mail_from_sent') {
|
||||
// Send RCPT TO
|
||||
socket.write('RCPT TO:<recipient@example.com>\r\n');
|
||||
state = 'rcpt_to_sent';
|
||||
dataBuffer = '';
|
||||
} else if (dataBuffer.includes('250 ') && dataBuffer.includes('recipient accepted')) {
|
||||
} else if (dataBuffer.includes('250 ') && state === 'rcpt_to_sent') {
|
||||
// Send DATA
|
||||
socket.write('DATA\r\n');
|
||||
state = 'data_sent';
|
||||
dataBuffer = '';
|
||||
} else if (dataBuffer.includes('354 ')) {
|
||||
} else if (dataBuffer.includes('354 ') && state === 'data_sent') {
|
||||
// Create deeply nested MIME structure (4 levels)
|
||||
const outerBoundary = '----=_Outer_Boundary_' + Date.now();
|
||||
const middleBoundary = '----=_Middle_Boundary_' + Date.now();
|
||||
@@ -126,8 +133,9 @@ tap.test('Nested MIME Structures - should handle deeply nested multipart structu
|
||||
|
||||
console.log('Sending email with 4-level nested MIME structure');
|
||||
socket.write(emailContent);
|
||||
state = 'email_sent';
|
||||
dataBuffer = '';
|
||||
} else if (dataBuffer.includes('250 ') && dataBuffer.includes('Message accepted') ||
|
||||
} else if ((dataBuffer.includes('250 OK') && state === 'email_sent') ||
|
||||
dataBuffer.includes('552 ') ||
|
||||
dataBuffer.includes('554 ') ||
|
||||
dataBuffer.includes('500 ')) {
|
||||
@@ -165,23 +173,29 @@ tap.test('Nested MIME Structures - should handle circular references in multipar
|
||||
});
|
||||
|
||||
let dataBuffer = '';
|
||||
let state = 'initial';
|
||||
|
||||
socket.on('data', (data) => {
|
||||
dataBuffer += data.toString();
|
||||
console.log('Server response:', data.toString());
|
||||
|
||||
if (dataBuffer.includes('220 ')) {
|
||||
if (dataBuffer.includes('220 ') && state === 'initial') {
|
||||
socket.write('EHLO testclient\r\n');
|
||||
} else if (dataBuffer.includes('250 ') && dataBuffer.includes('EHLO')) {
|
||||
state = 'ehlo_sent';
|
||||
dataBuffer = '';
|
||||
} else if (dataBuffer.includes('250 ') && state === 'ehlo_sent') {
|
||||
socket.write('MAIL FROM:<sender@example.com>\r\n');
|
||||
state = 'mail_from_sent';
|
||||
dataBuffer = '';
|
||||
} else if (dataBuffer.includes('250 ') && dataBuffer.includes('sender accepted')) {
|
||||
} else if (dataBuffer.includes('250 ') && state === 'mail_from_sent') {
|
||||
socket.write('RCPT TO:<recipient@example.com>\r\n');
|
||||
state = 'rcpt_to_sent';
|
||||
dataBuffer = '';
|
||||
} else if (dataBuffer.includes('250 ') && dataBuffer.includes('recipient accepted')) {
|
||||
} else if (dataBuffer.includes('250 ') && state === 'rcpt_to_sent') {
|
||||
socket.write('DATA\r\n');
|
||||
state = 'data_sent';
|
||||
dataBuffer = '';
|
||||
} else if (dataBuffer.includes('354 ')) {
|
||||
} else if (dataBuffer.includes('354 ') && state === 'data_sent') {
|
||||
// Create structure with references between parts
|
||||
const boundary1 = '----=_Boundary1_' + Date.now();
|
||||
const boundary2 = '----=_Boundary2_' + Date.now();
|
||||
@@ -221,8 +235,9 @@ tap.test('Nested MIME Structures - should handle circular references in multipar
|
||||
].join('\r\n');
|
||||
|
||||
socket.write(emailContent);
|
||||
state = 'email_sent';
|
||||
dataBuffer = '';
|
||||
} else if (dataBuffer.includes('250 ') && dataBuffer.includes('Message accepted') ||
|
||||
} else if ((dataBuffer.includes('250 OK') && state === 'email_sent') ||
|
||||
dataBuffer.includes('552 ') ||
|
||||
dataBuffer.includes('554 ') ||
|
||||
dataBuffer.includes('500 ')) {
|
||||
@@ -259,23 +274,29 @@ tap.test('Nested MIME Structures - should handle mixed nesting with various enco
|
||||
});
|
||||
|
||||
let dataBuffer = '';
|
||||
let state = 'initial';
|
||||
|
||||
socket.on('data', (data) => {
|
||||
dataBuffer += data.toString();
|
||||
console.log('Server response:', data.toString());
|
||||
|
||||
if (dataBuffer.includes('220 ')) {
|
||||
if (dataBuffer.includes('220 ') && state === 'initial') {
|
||||
socket.write('EHLO testclient\r\n');
|
||||
} else if (dataBuffer.includes('250 ') && dataBuffer.includes('EHLO')) {
|
||||
state = 'ehlo_sent';
|
||||
dataBuffer = '';
|
||||
} else if (dataBuffer.includes('250 ') && state === 'ehlo_sent') {
|
||||
socket.write('MAIL FROM:<sender@example.com>\r\n');
|
||||
state = 'mail_from_sent';
|
||||
dataBuffer = '';
|
||||
} else if (dataBuffer.includes('250 ') && dataBuffer.includes('sender accepted')) {
|
||||
} else if (dataBuffer.includes('250 ') && state === 'mail_from_sent') {
|
||||
socket.write('RCPT TO:<recipient@example.com>\r\n');
|
||||
state = 'rcpt_to_sent';
|
||||
dataBuffer = '';
|
||||
} else if (dataBuffer.includes('250 ') && dataBuffer.includes('recipient accepted')) {
|
||||
} else if (dataBuffer.includes('250 ') && state === 'rcpt_to_sent') {
|
||||
socket.write('DATA\r\n');
|
||||
state = 'data_sent';
|
||||
dataBuffer = '';
|
||||
} else if (dataBuffer.includes('354 ')) {
|
||||
} else if (dataBuffer.includes('354 ') && state === 'data_sent') {
|
||||
// Create structure with various encodings
|
||||
const boundary1 = '----=_Encoding_Outer_' + Date.now();
|
||||
const boundary2 = '----=_Encoding_Inner_' + Date.now();
|
||||
@@ -323,8 +344,9 @@ tap.test('Nested MIME Structures - should handle mixed nesting with various enco
|
||||
].join('\r\n');
|
||||
|
||||
socket.write(emailContent);
|
||||
state = 'email_sent';
|
||||
dataBuffer = '';
|
||||
} else if (dataBuffer.includes('250 ') && dataBuffer.includes('Message accepted') ||
|
||||
} else if ((dataBuffer.includes('250 OK') && state === 'email_sent') ||
|
||||
dataBuffer.includes('552 ') ||
|
||||
dataBuffer.includes('554 ') ||
|
||||
dataBuffer.includes('500 ')) {
|
||||
|
||||
@@ -1,14 +1,18 @@
|
||||
import { tap, expect } from '@git.zone/tstest/tapbundle';
|
||||
import * as plugins from '../plugins.js';
|
||||
import * as net from 'net';
|
||||
import { startTestServer, stopTestServer, TEST_PORT, sendEmailWithRawSocket } from '../../helpers/server.loader.js';
|
||||
import type { SmtpServer } from '../../../ts/mail/delivery/smtpserver/index.js';
|
||||
import { startTestServer, stopTestServer, type ITestServer } from '../../helpers/server.loader.js';
|
||||
|
||||
let testServer: SmtpServer;
|
||||
const TEST_PORT = 30041;
|
||||
const TEST_TIMEOUT = 30000;
|
||||
|
||||
let testServer: ITestServer;
|
||||
|
||||
tap.test('setup - start test server', async () => {
|
||||
testServer = await startTestServer();
|
||||
await plugins.smartdelay.delayFor(1000);
|
||||
testServer = await startTestServer({
|
||||
port: TEST_PORT,
|
||||
hostname: 'localhost'
|
||||
});
|
||||
expect(testServer).toBeDefined();
|
||||
});
|
||||
|
||||
tap.test('Unusual MIME Types - should handle email with various unusual MIME types', async (tools) => {
|
||||
@@ -17,31 +21,33 @@ tap.test('Unusual MIME Types - should handle email with various unusual MIME typ
|
||||
const socket = net.createConnection({
|
||||
host: 'localhost',
|
||||
port: TEST_PORT,
|
||||
timeout: 30000
|
||||
timeout: TEST_TIMEOUT
|
||||
});
|
||||
|
||||
let dataBuffer = '';
|
||||
let receivedData = '';
|
||||
let currentStep = 'connecting';
|
||||
|
||||
socket.on('data', (data) => {
|
||||
dataBuffer += data.toString();
|
||||
receivedData += data.toString();
|
||||
console.log('Server response:', data.toString());
|
||||
|
||||
if (dataBuffer.includes('220 ')) {
|
||||
// Send EHLO
|
||||
if (currentStep === 'connecting' && receivedData.includes('220')) {
|
||||
currentStep = 'ehlo';
|
||||
receivedData = '';
|
||||
socket.write('EHLO testclient\r\n');
|
||||
} else if (dataBuffer.includes('250 ') && dataBuffer.includes('EHLO')) {
|
||||
// Send MAIL FROM
|
||||
} else if (currentStep === 'ehlo' && receivedData.includes('250 ')) {
|
||||
currentStep = 'mail_from';
|
||||
receivedData = '';
|
||||
socket.write('MAIL FROM:<sender@example.com>\r\n');
|
||||
dataBuffer = '';
|
||||
} else if (dataBuffer.includes('250 ') && dataBuffer.includes('sender accepted')) {
|
||||
// Send RCPT TO
|
||||
} else if (currentStep === 'mail_from' && receivedData.includes('250')) {
|
||||
currentStep = 'rcpt_to';
|
||||
receivedData = '';
|
||||
socket.write('RCPT TO:<recipient@example.com>\r\n');
|
||||
dataBuffer = '';
|
||||
} else if (dataBuffer.includes('250 ') && dataBuffer.includes('recipient accepted')) {
|
||||
// Send DATA
|
||||
} else if (currentStep === 'rcpt_to' && receivedData.includes('250')) {
|
||||
currentStep = 'data';
|
||||
receivedData = '';
|
||||
socket.write('DATA\r\n');
|
||||
dataBuffer = '';
|
||||
} else if (dataBuffer.includes('354 ')) {
|
||||
} else if (currentStep === 'data' && receivedData.includes('354')) {
|
||||
// Create multipart email with unusual MIME types
|
||||
const boundary = '----=_Part_1_' + Date.now();
|
||||
const unusualMimeTypes = [
|
||||
@@ -82,13 +88,14 @@ tap.test('Unusual MIME Types - should handle email with various unusual MIME typ
|
||||
console.log(`Sending email with ${unusualMimeTypes.length} unusual MIME types`);
|
||||
|
||||
socket.write(fullEmail);
|
||||
dataBuffer = '';
|
||||
} else if (dataBuffer.includes('250 ') && dataBuffer.includes('Message accepted') ||
|
||||
dataBuffer.includes('552 ') ||
|
||||
dataBuffer.includes('554 ') ||
|
||||
dataBuffer.includes('500 ')) {
|
||||
currentStep = 'waiting_response';
|
||||
receivedData = '';
|
||||
} else if (currentStep === 'waiting_response' && (receivedData.includes('250 ') ||
|
||||
receivedData.includes('552 ') ||
|
||||
receivedData.includes('554 ') ||
|
||||
receivedData.includes('500 '))) {
|
||||
// Either accepted or gracefully rejected
|
||||
const accepted = dataBuffer.includes('250 ');
|
||||
const accepted = receivedData.includes('250 ');
|
||||
console.log(`Unusual MIME types test ${accepted ? 'accepted' : 'rejected'}`);
|
||||
|
||||
socket.write('QUIT\r\n');
|
||||
@@ -117,27 +124,33 @@ tap.test('Unusual MIME Types - should handle email with deeply nested multipart
|
||||
const socket = net.createConnection({
|
||||
host: 'localhost',
|
||||
port: TEST_PORT,
|
||||
timeout: 30000
|
||||
timeout: TEST_TIMEOUT
|
||||
});
|
||||
|
||||
let dataBuffer = '';
|
||||
let receivedData = '';
|
||||
let currentStep = 'connecting';
|
||||
|
||||
socket.on('data', (data) => {
|
||||
dataBuffer += data.toString();
|
||||
receivedData += data.toString();
|
||||
console.log('Server response:', data.toString());
|
||||
|
||||
if (dataBuffer.includes('220 ')) {
|
||||
if (currentStep === 'connecting' && receivedData.includes('220')) {
|
||||
currentStep = 'ehlo';
|
||||
receivedData = '';
|
||||
socket.write('EHLO testclient\r\n');
|
||||
} else if (dataBuffer.includes('250 ') && dataBuffer.includes('EHLO')) {
|
||||
} else if (currentStep === 'ehlo' && receivedData.includes('250 ')) {
|
||||
currentStep = 'mail_from';
|
||||
receivedData = '';
|
||||
socket.write('MAIL FROM:<sender@example.com>\r\n');
|
||||
dataBuffer = '';
|
||||
} else if (dataBuffer.includes('250 ') && dataBuffer.includes('sender accepted')) {
|
||||
} else if (currentStep === 'mail_from' && receivedData.includes('250')) {
|
||||
currentStep = 'rcpt_to';
|
||||
receivedData = '';
|
||||
socket.write('RCPT TO:<recipient@example.com>\r\n');
|
||||
dataBuffer = '';
|
||||
} else if (dataBuffer.includes('250 ') && dataBuffer.includes('recipient accepted')) {
|
||||
} else if (currentStep === 'rcpt_to' && receivedData.includes('250')) {
|
||||
currentStep = 'data';
|
||||
receivedData = '';
|
||||
socket.write('DATA\r\n');
|
||||
dataBuffer = '';
|
||||
} else if (dataBuffer.includes('354 ')) {
|
||||
} else if (currentStep === 'data' && receivedData.includes('354')) {
|
||||
// Create nested multipart structure
|
||||
const boundary1 = '----=_Part_Outer_' + Date.now();
|
||||
const boundary2 = '----=_Part_Inner_' + Date.now();
|
||||
@@ -183,12 +196,13 @@ tap.test('Unusual MIME Types - should handle email with deeply nested multipart
|
||||
].join('\r\n');
|
||||
|
||||
socket.write(emailContent);
|
||||
dataBuffer = '';
|
||||
} else if (dataBuffer.includes('250 ') && dataBuffer.includes('Message accepted') ||
|
||||
dataBuffer.includes('552 ') ||
|
||||
dataBuffer.includes('554 ') ||
|
||||
dataBuffer.includes('500 ')) {
|
||||
const accepted = dataBuffer.includes('250 ');
|
||||
currentStep = 'waiting_response';
|
||||
receivedData = '';
|
||||
} else if (currentStep === 'waiting_response' && (receivedData.includes('250 ') ||
|
||||
receivedData.includes('552 ') ||
|
||||
receivedData.includes('554 ') ||
|
||||
receivedData.includes('500 '))) {
|
||||
const accepted = receivedData.includes('250 ');
|
||||
console.log(`Nested multipart test ${accepted ? 'accepted' : 'rejected'}`);
|
||||
|
||||
socket.write('QUIT\r\n');
|
||||
@@ -217,27 +231,33 @@ tap.test('Unusual MIME Types - should handle email with non-standard charset enc
|
||||
const socket = net.createConnection({
|
||||
host: 'localhost',
|
||||
port: TEST_PORT,
|
||||
timeout: 30000
|
||||
timeout: TEST_TIMEOUT
|
||||
});
|
||||
|
||||
let dataBuffer = '';
|
||||
let receivedData = '';
|
||||
let currentStep = 'connecting';
|
||||
|
||||
socket.on('data', (data) => {
|
||||
dataBuffer += data.toString();
|
||||
receivedData += data.toString();
|
||||
console.log('Server response:', data.toString());
|
||||
|
||||
if (dataBuffer.includes('220 ')) {
|
||||
if (currentStep === 'connecting' && receivedData.includes('220')) {
|
||||
currentStep = 'ehlo';
|
||||
receivedData = '';
|
||||
socket.write('EHLO testclient\r\n');
|
||||
} else if (dataBuffer.includes('250 ') && dataBuffer.includes('EHLO')) {
|
||||
} else if (currentStep === 'ehlo' && receivedData.includes('250 ')) {
|
||||
currentStep = 'mail_from';
|
||||
receivedData = '';
|
||||
socket.write('MAIL FROM:<sender@example.com>\r\n');
|
||||
dataBuffer = '';
|
||||
} else if (dataBuffer.includes('250 ') && dataBuffer.includes('sender accepted')) {
|
||||
} else if (currentStep === 'mail_from' && receivedData.includes('250')) {
|
||||
currentStep = 'rcpt_to';
|
||||
receivedData = '';
|
||||
socket.write('RCPT TO:<recipient@example.com>\r\n');
|
||||
dataBuffer = '';
|
||||
} else if (dataBuffer.includes('250 ') && dataBuffer.includes('recipient accepted')) {
|
||||
} else if (currentStep === 'rcpt_to' && receivedData.includes('250')) {
|
||||
currentStep = 'data';
|
||||
receivedData = '';
|
||||
socket.write('DATA\r\n');
|
||||
dataBuffer = '';
|
||||
} else if (dataBuffer.includes('354 ')) {
|
||||
} else if (currentStep === 'data' && receivedData.includes('354')) {
|
||||
// Create email with various charset encodings
|
||||
const boundary = '----=_Part_Charset_' + Date.now();
|
||||
|
||||
@@ -276,12 +296,13 @@ tap.test('Unusual MIME Types - should handle email with non-standard charset enc
|
||||
].join('\r\n');
|
||||
|
||||
socket.write(emailContent);
|
||||
dataBuffer = '';
|
||||
} else if (dataBuffer.includes('250 ') && dataBuffer.includes('Message accepted') ||
|
||||
dataBuffer.includes('552 ') ||
|
||||
dataBuffer.includes('554 ') ||
|
||||
dataBuffer.includes('500 ')) {
|
||||
const accepted = dataBuffer.includes('250 ');
|
||||
currentStep = 'waiting_response';
|
||||
receivedData = '';
|
||||
} else if (currentStep === 'waiting_response' && (receivedData.includes('250 ') ||
|
||||
receivedData.includes('552 ') ||
|
||||
receivedData.includes('554 ') ||
|
||||
receivedData.includes('500 '))) {
|
||||
const accepted = receivedData.includes('250 ');
|
||||
console.log(`Various charset test ${accepted ? 'accepted' : 'rejected'}`);
|
||||
|
||||
socket.write('QUIT\r\n');
|
||||
@@ -306,6 +327,7 @@ tap.test('Unusual MIME Types - should handle email with non-standard charset enc
|
||||
|
||||
tap.test('cleanup - stop test server', async () => {
|
||||
await stopTestServer(testServer);
|
||||
expect(true).toEqual(true);
|
||||
});
|
||||
|
||||
tap.start();
|
||||
@@ -1,4 +1,4 @@
|
||||
import { tap, expect } from '@git.zone/tapbundle';
|
||||
import { tap, expect } from '@git.zone/tstest/tapbundle';
|
||||
import * as net from 'net';
|
||||
import { startTestServer, stopTestServer, type ITestServer } from '../../helpers/server.loader.js';
|
||||
|
||||
|
||||
Reference in New Issue
Block a user