import multer from 'multer'; import path from 'path'; import fs from 'fs'; // 업로드 디렉토리 경로 const UPLOAD_DIR = path.join(__dirname, '../../uploads/mail-attachments'); // 디렉토리 생성 (없으면) if (!fs.existsSync(UPLOAD_DIR)) { fs.mkdirSync(UPLOAD_DIR, { recursive: true }); } // 간단한 파일명 정규화 함수 (한글-분석.txt 방식) function normalizeFileName(filename: string): string { if (!filename) return filename; try { // NFC 정규화만 수행 (복잡한 디코딩 제거) return filename.normalize('NFC'); } catch (error) { console.error(`Failed to normalize filename: ${filename}`, error); return filename; } } // 파일 저장 설정 const storage = multer.diskStorage({ destination: (req, file, cb) => { cb(null, UPLOAD_DIR); }, filename: (req, file, cb) => { try { // 파일명 정규화 (한글-분석.txt 방식) file.originalname = file.originalname.normalize('NFC'); console.log('File upload - Processing:', { original: file.originalname, originalHex: Buffer.from(file.originalname).toString('hex'), }); // UUID + 확장자로 유니크한 파일명 생성 const uniqueId = Date.now() + '-' + Math.round(Math.random() * 1e9); const ext = path.extname(file.originalname); const filename = `${uniqueId}${ext}`; console.log('Generated filename:', { original: file.originalname, generated: filename, }); cb(null, filename); } catch (error) { console.error('Filename processing error:', error); const fallbackFilename = `${Date.now()}-${Math.round(Math.random() * 1e9)}_error.tmp`; cb(null, fallbackFilename); } }, }); // 파일 필터 (허용할 파일 타입) const fileFilter = (req: any, file: Express.Multer.File, cb: multer.FileFilterCallback) => { // 파일명 정규화 (fileFilter가 filename보다 먼저 실행되므로 여기서 먼저 처리) try { // NFD를 NFC로 정규화만 수행 file.originalname = file.originalname.normalize('NFC'); } catch (error) { console.warn('Failed to normalize filename in fileFilter:', error); } // 위험한 파일 확장자 차단 const dangerousExtensions = ['.exe', '.bat', '.cmd', '.sh', '.ps1', '.msi']; const ext = path.extname(file.originalname).toLowerCase(); if (dangerousExtensions.includes(ext)) { console.log(`❌ 차단된 파일 타입: ${ext}`); cb(new Error(`보안상의 이유로 ${ext} 파일은 첨부할 수 없습니다.`)); return; } cb(null, true); }; // Multer 설정 export const uploadMailAttachment = multer({ storage, fileFilter, limits: { fileSize: 10 * 1024 * 1024, // 10MB 제한 files: 5, // 최대 5개 파일 }, }); // 첨부파일 정보 추출 헬퍼 export interface AttachmentInfo { filename: string; originalName: string; size: number; path: string; mimetype: string; } export const extractAttachmentInfo = (files: Express.Multer.File[]): AttachmentInfo[] => { return files.map((file) => ({ filename: file.filename, originalName: file.originalname, size: file.size, path: file.path, mimetype: file.mimetype, })); };