using System.IO.Compression; namespace Shared.Excel; public sealed class XlsxSafetyInspector { public const long DefaultMaxUncompressedBytes = 250L * 1024 * 1024; public const int DefaultMaxEntries = 5_000; public void EnsureSafe(byte[] bytes, long maxUncompressedBytes = DefaultMaxUncompressedBytes, int maxEntries = DefaultMaxEntries) { using var stream = new MemoryStream(bytes, writable: false); using var archive = new ZipArchive(stream, ZipArchiveMode.Read, leaveOpen: false); if (archive.Entries.Count == 0 || archive.Entries.Count > maxEntries) throw new InvalidDataException("Excel 파일 내부 구조가 비정상적입니다."); if (!archive.Entries.Any(x => x.FullName.Equals("[Content_Types].xml", StringComparison.OrdinalIgnoreCase)) || !archive.Entries.Any(x => x.FullName.Equals("xl/workbook.xml", StringComparison.OrdinalIgnoreCase))) throw new InvalidDataException("유효한 .xlsx 통합문서가 아닙니다."); long total = 0; foreach (var entry in archive.Entries) { total = checked(total + entry.Length); if (total > maxUncompressedBytes) throw new InvalidDataException("압축 해제된 Excel 데이터가 허용 크기를 초과합니다."); // A very small compressed entry expanding to an extreme size is suspicious. if (entry.CompressedLength > 0 && entry.Length > 10L * 1024 * 1024 && entry.Length / entry.CompressedLength > 200) throw new InvalidDataException("비정상적인 압축률의 Excel 항목이 감지되었습니다."); } } }