const BLOCKED_DOCUMENT_TYPES = new Set([ 'application/javascript', 'application/x-javascript', 'text/javascript', 'text/html', 'application/xhtml+xml', 'application/x-httpd-php', 'application/x-sh' ]); const EXTENSION_TO_MIME: Record = { jpg: 'image/jpeg', jpeg: 'image/jpeg', png: 'image/png', webp: 'image/webp', gif: 'image/gif', pdf: 'application/pdf', doc: 'application/msword', docx: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', xls: 'application/vnd.ms-excel', xlsx: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', ppt: 'application/vnd.ms-powerpoint', pptx: 'application/vnd.openxmlformats-officedocument.presentationml.presentation', txt: 'text/plain', rtf: 'application/rtf', odt: 'application/vnd.oasis.opendocument.text' }; const ALLOWED_DOCUMENT_MIME = new Set([ 'image/jpeg', 'image/png', 'image/webp', 'image/gif', 'application/pdf', 'application/msword', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'application/vnd.ms-excel', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 'application/vnd.ms-powerpoint', 'application/vnd.openxmlformats-officedocument.presentationml.presentation', 'text/plain', 'application/rtf', 'application/vnd.oasis.opendocument.text' ]); const MIME_ALIASES: Record = { 'image/jpg': 'image/jpeg', 'image/pjpeg': 'image/jpeg', 'application/x-pdf': 'application/pdf' }; function extractFileExtension(fileName?: string | null) { const trimmed = (fileName ?? '').trim().toLowerCase(); const dotIndex = trimmed.lastIndexOf('.'); if (dotIndex <= 0) return ''; return trimmed.slice(dotIndex + 1); } export function normalizeDocumentContentType(contentType?: string | null, fileName?: string | null) { const raw = (contentType ?? '').trim().toLowerCase(); const base = raw.split(';')[0]?.trim() ?? ''; const aliased = MIME_ALIASES[base] ?? base; if (aliased && aliased !== 'application/octet-stream' && ALLOWED_DOCUMENT_MIME.has(aliased)) { return aliased; } const extension = extractFileExtension(fileName); if (extension && EXTENSION_TO_MIME[extension]) { return EXTENSION_TO_MIME[extension]; } return aliased || 'application/octet-stream'; } export function assertDocumentAttachmentContentType(contentType: string, fileName?: string) { const normalized = normalizeDocumentContentType(contentType, fileName); if (BLOCKED_DOCUMENT_TYPES.has(normalized)) { throw new Error('Тип файла не поддерживается'); } if (!ALLOWED_DOCUMENT_MIME.has(normalized)) { throw new Error('Допустимы изображения, PDF, Word, Excel, PowerPoint, TXT и ODT'); } return normalized; } export function extensionForDocumentContentType(contentType: string, fileName?: string) { const normalized = normalizeDocumentContentType(contentType, fileName); switch (normalized) { case 'image/jpeg': return 'jpg'; case 'image/png': return 'png'; case 'image/webp': return 'webp'; case 'image/gif': return 'gif'; case 'application/pdf': return 'pdf'; case 'application/msword': return 'doc'; case 'application/vnd.openxmlformats-officedocument.wordprocessingml.document': return 'docx'; case 'application/vnd.ms-excel': return 'xls'; case 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': return 'xlsx'; case 'application/vnd.ms-powerpoint': return 'ppt'; case 'application/vnd.openxmlformats-officedocument.presentationml.presentation': return 'pptx'; case 'text/plain': return 'txt'; case 'application/rtf': return 'rtf'; case 'application/vnd.oasis.opendocument.text': return 'odt'; default: { const extension = extractFileExtension(fileName); return extension || 'bin'; } } } export function contentTypeForDocumentKey(storageKey: string) { const lower = storageKey.toLowerCase(); if (lower.endsWith('.png')) return 'image/png'; if (lower.endsWith('.webp')) return 'image/webp'; if (lower.endsWith('.gif')) return 'image/gif'; if (lower.endsWith('.pdf')) return 'application/pdf'; if (lower.endsWith('.doc')) return 'application/msword'; if (lower.endsWith('.docx')) return 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'; if (lower.endsWith('.xls')) return 'application/vnd.ms-excel'; if (lower.endsWith('.xlsx')) return 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'; if (lower.endsWith('.ppt')) return 'application/vnd.ms-powerpoint'; if (lower.endsWith('.pptx')) return 'application/vnd.openxmlformats-officedocument.presentationml.presentation'; if (lower.endsWith('.txt')) return 'text/plain'; if (lower.endsWith('.rtf')) return 'application/rtf'; if (lower.endsWith('.odt')) return 'application/vnd.oasis.opendocument.text'; return 'image/jpeg'; }