dcrouter/test/suite/smtpserver_edge-cases/test.edge-05.extremely-long-lines.ts

425 lines
12 KiB
TypeScript
Raw Permalink Normal View History

2025-05-24 00:23:35 +00:00
import { tap, expect } from '@git.zone/tstest/tapbundle';
2025-05-23 19:03:44 +00:00
import * as net from 'net';
import { startTestServer, stopTestServer } from '../../helpers/server.loader.js';
import type { ITestServer } from '../../helpers/server.loader.js';
const TEST_PORT = 30037;
const TEST_TIMEOUT = 30000;
let testServer: ITestServer;
tap.test('setup - start SMTP server for extremely long lines tests', async () => {
testServer = await startTestServer({
port: TEST_PORT,
hostname: 'localhost'
});
2025-05-24 00:23:35 +00:00
expect(testServer).toBeDefined();
2025-05-23 19:03:44 +00:00
});
tap.test('Extremely Long Lines - should handle lines exceeding RFC 5321 limit', async (tools) => {
const done = tools.defer();
try {
const socket = net.createConnection({
host: 'localhost',
port: TEST_PORT,
timeout: TEST_TIMEOUT
});
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 testhost\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 envelope
socket.write('MAIL FROM:<sender@example.com>\r\n');
await new Promise<string>((resolve) => {
socket.once('data', (chunk) => resolve(chunk.toString()));
});
socket.write('RCPT TO:<recipient@example.com>\r\n');
await new Promise<string>((resolve) => {
socket.once('data', (chunk) => resolve(chunk.toString()));
});
socket.write('DATA\r\n');
const dataResponse = await new Promise<string>((resolve) => {
socket.once('data', (chunk) => resolve(chunk.toString()));
});
expect(dataResponse).toInclude('354');
// Create line exceeding RFC 5321 limit (1000 chars including CRLF)
const longLine = 'X'.repeat(2000); // 2000 character line
const emailWithLongLine =
'From: sender@example.com\r\n' +
'To: recipient@example.com\r\n' +
'Subject: Long Line Test\r\n' +
'\r\n' +
'This email contains an extremely long line:\r\n' +
longLine + '\r\n' +
'End of test.\r\n' +
'.\r\n';
socket.write(emailWithLongLine);
const finalResponse = await new Promise<string>((resolve) => {
socket.once('data', (chunk) => resolve(chunk.toString()));
});
console.log(`Response to ${longLine.length} character line:`, finalResponse);
// Server should handle gracefully (accept, wrap, or reject)
const accepted = finalResponse.includes('250');
const rejected = finalResponse.includes('552') || finalResponse.includes('500') || finalResponse.includes('554');
2025-05-23 21:20:39 +00:00
expect(accepted || rejected).toEqual(true);
2025-05-23 19:03:44 +00:00
if (accepted) {
console.log('Server accepted long line (may wrap internally)');
} else {
console.log('Server rejected long line');
}
// Clean up
socket.write('QUIT\r\n');
socket.end();
} finally {
done.resolve();
}
});
tap.test('Extremely Long Lines - should handle extremely long subject header', async (tools) => {
const done = tools.defer();
try {
const socket = net.createConnection({
host: 'localhost',
port: TEST_PORT,
timeout: TEST_TIMEOUT
});
await new Promise<void>((resolve, reject) => {
socket.once('connect', () => resolve());
socket.once('error', reject);
});
// Setup connection
await new Promise<string>((resolve) => {
socket.once('data', (chunk) => resolve(chunk.toString()));
});
socket.write('EHLO testhost\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 envelope
socket.write('MAIL FROM:<sender@example.com>\r\n');
await new Promise<string>((resolve) => {
socket.once('data', (chunk) => resolve(chunk.toString()));
});
socket.write('RCPT TO:<recipient@example.com>\r\n');
await new Promise<string>((resolve) => {
socket.once('data', (chunk) => resolve(chunk.toString()));
});
socket.write('DATA\r\n');
await new Promise<string>((resolve) => {
socket.once('data', (chunk) => resolve(chunk.toString()));
});
// Create extremely long subject (3000 characters)
const longSubject = 'A'.repeat(3000);
const emailWithLongSubject =
'From: sender@example.com\r\n' +
'To: recipient@example.com\r\n' +
`Subject: ${longSubject}\r\n` +
'\r\n' +
'Body of email with extremely long subject.\r\n' +
'.\r\n';
socket.write(emailWithLongSubject);
const finalResponse = await new Promise<string>((resolve) => {
socket.once('data', (chunk) => resolve(chunk.toString()));
});
console.log(`Response to ${longSubject.length} character subject:`, finalResponse);
// Server should handle this
expect(finalResponse).toMatch(/^[2-5]\d{2}/);
// Clean up
socket.write('QUIT\r\n');
socket.end();
} finally {
done.resolve();
}
});
tap.test('Extremely Long Lines - should handle multiple consecutive long lines', async (tools) => {
const done = tools.defer();
try {
const socket = net.createConnection({
host: 'localhost',
port: TEST_PORT,
timeout: TEST_TIMEOUT
});
await new Promise<void>((resolve, reject) => {
socket.once('connect', () => resolve());
socket.once('error', reject);
});
// Setup connection
await new Promise<string>((resolve) => {
socket.once('data', (chunk) => resolve(chunk.toString()));
});
socket.write('EHLO testhost\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 envelope
socket.write('MAIL FROM:<sender@example.com>\r\n');
await new Promise<string>((resolve) => {
socket.once('data', (chunk) => resolve(chunk.toString()));
});
socket.write('RCPT TO:<recipient@example.com>\r\n');
await new Promise<string>((resolve) => {
socket.once('data', (chunk) => resolve(chunk.toString()));
});
socket.write('DATA\r\n');
await new Promise<string>((resolve) => {
socket.once('data', (chunk) => resolve(chunk.toString()));
});
// Create multiple long lines
const longLine1 = 'A'.repeat(1500);
const longLine2 = 'B'.repeat(1800);
const longLine3 = 'C'.repeat(2000);
const emailWithMultipleLongLines =
'From: sender@example.com\r\n' +
'To: recipient@example.com\r\n' +
'Subject: Multiple Long Lines Test\r\n' +
'\r\n' +
'First long line:\r\n' +
longLine1 + '\r\n' +
'Second long line:\r\n' +
longLine2 + '\r\n' +
'Third long line:\r\n' +
longLine3 + '\r\n' +
'.\r\n';
socket.write(emailWithMultipleLongLines);
const finalResponse = await new Promise<string>((resolve) => {
socket.once('data', (chunk) => resolve(chunk.toString()));
});
console.log('Response to multiple long lines:', finalResponse);
// Server should handle this
expect(finalResponse).toMatch(/^[2-5]\d{2}/);
// Clean up
socket.write('QUIT\r\n');
socket.end();
} finally {
done.resolve();
}
});
tap.test('Extremely Long Lines - should handle extremely long MAIL FROM parameter', async (tools) => {
const done = tools.defer();
try {
const socket = net.createConnection({
host: 'localhost',
port: TEST_PORT,
timeout: TEST_TIMEOUT
});
await new Promise<void>((resolve, reject) => {
socket.once('connect', () => resolve());
socket.once('error', reject);
});
// Setup connection
await new Promise<string>((resolve) => {
socket.once('data', (chunk) => resolve(chunk.toString()));
});
socket.write('EHLO testhost\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);
});
// Create extremely long email address (technically invalid but testing limits)
const longLocalPart = 'a'.repeat(500);
const longDomain = 'b'.repeat(500) + '.com';
const longEmail = `${longLocalPart}@${longDomain}`;
socket.write(`MAIL FROM:<${longEmail}>\r\n`);
const response = await new Promise<string>((resolve) => {
socket.once('data', (chunk) => resolve(chunk.toString()));
});
console.log(`Response to ${longEmail.length} character email address:`, response);
// Should get error response
expect(response).toMatch(/^5\d{2}/);
// Clean up
socket.write('QUIT\r\n');
socket.end();
} finally {
done.resolve();
}
});
tap.test('Extremely Long Lines - should handle line exactly at RFC limit', async (tools) => {
const done = tools.defer();
try {
const socket = net.createConnection({
host: 'localhost',
port: TEST_PORT,
timeout: TEST_TIMEOUT
});
await new Promise<void>((resolve, reject) => {
socket.once('connect', () => resolve());
socket.once('error', reject);
});
// Setup connection
await new Promise<string>((resolve) => {
socket.once('data', (chunk) => resolve(chunk.toString()));
});
socket.write('EHLO testhost\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 envelope
socket.write('MAIL FROM:<sender@example.com>\r\n');
await new Promise<string>((resolve) => {
socket.once('data', (chunk) => resolve(chunk.toString()));
});
socket.write('RCPT TO:<recipient@example.com>\r\n');
await new Promise<string>((resolve) => {
socket.once('data', (chunk) => resolve(chunk.toString()));
});
socket.write('DATA\r\n');
await new Promise<string>((resolve) => {
socket.once('data', (chunk) => resolve(chunk.toString()));
});
// Create line exactly at RFC 5321 limit (998 chars + CRLF = 1000)
const rfcLimitLine = 'X'.repeat(998);
const emailWithRfcLimitLine =
'From: sender@example.com\r\n' +
'To: recipient@example.com\r\n' +
'Subject: RFC Limit Test\r\n' +
'\r\n' +
'Line at RFC 5321 limit:\r\n' +
rfcLimitLine + '\r\n' +
'This should be accepted.\r\n' +
'.\r\n';
socket.write(emailWithRfcLimitLine);
const finalResponse = await new Promise<string>((resolve) => {
socket.once('data', (chunk) => resolve(chunk.toString()));
});
console.log(`Response to ${rfcLimitLine.length} character line (RFC limit):`, finalResponse);
// This should be accepted
expect(finalResponse).toInclude('250');
// Clean up
socket.write('QUIT\r\n');
socket.end();
} finally {
done.resolve();
}
});
tap.test('cleanup - stop SMTP server', async () => {
await stopTestServer(testServer);
2025-05-23 21:20:39 +00:00
expect(true).toEqual(true);
2025-05-23 19:03:44 +00:00
});
2025-05-25 19:05:43 +00:00
export default tap.start();