import * as plugins from '../plugins.js'; export interface ISmartMtaTextNamespace { prefix: string; maxBytes: number; } export const smartMtaTextNamespaces: readonly ISmartMtaTextNamespace[] = Object.freeze([ { prefix: '/email/dkim/', maxBytes: 128 * 1024 }, { prefix: '/email/dns/', maxBytes: 1024 * 1024 }, { prefix: '/email/bounces/', maxBytes: 8 * 1024 * 1024 }, { prefix: '/email/templates/', maxBytes: 4 * 1024 * 1024 }, { prefix: '/email/routes/config.json', maxBytes: 4 * 1024 * 1024 }, // SmartMTA 8.1+ accepted-envelope dispatch bookkeeping (idempotent replay records). { prefix: '/email/accepted-envelope-dispatch/', maxBytes: 64 * 1024 }, { prefix: '/security/ip-reputation-cache.json', maxBytes: 8 * 1024 * 1024 }, { prefix: '/workhosters/mail-identities.json', maxBytes: 4 * 1024 * 1024 }, ]); export function normalizeSmartMtaStorageKey(key: string): string { if (typeof key !== 'string' || key.length === 0) { throw new Error('SmartMTA storage key must be a non-empty string'); } if (key.includes('\\') || key.includes('\0')) { throw new Error(`Invalid SmartMTA storage key: ${key}`); } const withLeadingSlash = key.startsWith('/') ? key : `/${key}`; const segments = withLeadingSlash.split('/').slice(1); if ( segments.length === 0 || segments.some((segment, index) => segment === '.' || segment === '..' || (segment === '' && index < segments.length - 1)) ) { throw new Error(`Invalid SmartMTA storage key: ${key}`); } const normalized = `/${segments.filter(Boolean).join('/')}`; if (normalized.length > 1024) { throw new Error('SmartMTA storage key exceeds 1024 characters'); } return normalized; } export function getSmartMtaTextNamespace(key: string): ISmartMtaTextNamespace { const normalizedKey = normalizeSmartMtaStorageKey(key); const namespace = smartMtaTextNamespaces.find((candidate) => { return candidate.prefix.endsWith('/') ? normalizedKey.startsWith(candidate.prefix) : normalizedKey === candidate.prefix; }); if (!namespace) { throw new Error(`Unsupported SmartMTA text storage namespace: ${normalizedKey}`); } return namespace; } export function validateSmartMtaTextValue(key: string, value: string): void { if (typeof value !== 'string') { throw new Error('SmartMTA text storage only accepts string values'); } const namespace = getSmartMtaTextNamespace(key); const size = Buffer.byteLength(value, 'utf8'); if (size > namespace.maxBytes) { throw new Error( `SmartMTA storage value for ${normalizeSmartMtaStorageKey(key)} exceeds ${namespace.maxBytes} bytes`, ); } } export function hashSmartMtaStorageKey(key: string): string { return plugins.crypto .createHash('sha256') .update(normalizeSmartMtaStorageKey(key)) .digest('hex'); }