update
This commit is contained in:
@ -0,0 +1,425 @@
|
||||
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';
|
||||
|
||||
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'
|
||||
});
|
||||
expect(testServer).toBeDefined();
|
||||
});
|
||||
|
||||
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');
|
||||
|
||||
expect(accepted || rejected).toEqual(true);
|
||||
|
||||
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);
|
||||
expect(true).toEqual(true);
|
||||
});
|
||||
|
||||
tap.start();
|
Reference in New Issue
Block a user