using System.IO.Compression; using Ludarium.Application; using SharpCompress.Common; using SharpCompress.Writers; namespace Ludarium.UnitTests; public sealed class ArchiveAnalysisTests { [Fact] public void EnumeratesCentralDirectoryWithoutExtractingMembers() { using var stream = CreateZip(("games/demo.nes", new byte[] { 1, 2, 3 }), ("manual.txt", "offline"u8.ToArray())); var result = ArchiveAnalysis.InspectZip(Guid.NewGuid(), stream); Assert.True(result.Complete); Assert.Equal(2, result.Members.Count); Assert.Contains(result.Members, x => x.Path == "games/demo.nes" && x.UncompressedSize == 3 && x.ContentBlobId is null); Assert.Equal([0, 1], result.Members.Select(x => x.Sequence)); } [Fact] public void RecordsTraversalAsUnsafeAndNeverExtractsIt() { using var stream = CreateZip(("../escape.exe", new byte[] { 1 })); var result = ArchiveAnalysis.InspectZip(Guid.NewGuid(), stream); Assert.False(result.Complete); Assert.True(Assert.Single(result.Members).UnsafePath); Assert.Contains(result.Findings, x => x.Contains("never extracted", StringComparison.Ordinal)); } [Fact] public void StopsAtConfiguredMemberLimit() { using var stream = CreateZip(("one.bin", new byte[] { 1 }), ("two.bin", new byte[] { 2 })); var result = ArchiveAnalysis.InspectZip(Guid.NewGuid(), stream, new ArchiveLimits(MaxMembers: 1)); Assert.Single(result.Members); Assert.Contains(result.Findings, x => x.Contains("member limit", StringComparison.Ordinal)); } [Fact] public void HonorsCancellationBeforeEnumeratingMembers() { using var stream = CreateZip(("one.bin", new byte[] { 1 })); using var cancellation = new CancellationTokenSource(); cancellation.Cancel(); Assert.Throws(() => ArchiveAnalysis.InspectZip(Guid.NewGuid(), stream, cancellationToken: cancellation.Token)); } [Fact] public void EnumeratesSevenZipHeadersWithoutExtractingMembers() { var path = Path.Combine(Path.GetTempPath(), $"ludarium-{Guid.NewGuid():N}.7z"); try { using (var writer = WriterFactory.OpenWriter(path, ArchiveType.SevenZip, new WriterOptions(CompressionType.LZMA))) { using var first = new MemoryStream(new byte[] { 1, 2, 3 }); writer.Write("games/demo.nes", first, null); using var second = new MemoryStream("offline"u8.ToArray()); writer.Write("manual.txt", second, null); } using var stream = File.OpenRead(path); var result = ArchiveAnalysis.InspectSevenZip(Guid.NewGuid(), stream); Assert.True(result.Complete); Assert.Equal(2, result.Members.Count); Assert.Contains(result.Members, x => x.Path == "games/demo.nes" && x.UncompressedSize == 3); } finally { File.Delete(path); } } private static MemoryStream CreateZip(params (string Name, byte[] Data)[] entries) { var stream = new MemoryStream(); using (var archive = new ZipArchive(stream, ZipArchiveMode.Create, leaveOpen: true)) { foreach (var (name, data) in entries) { var entry = archive.CreateEntry(name, CompressionLevel.SmallestSize); using var output = entry.Open(); output.Write(data); } } stream.Position = 0; return stream; } }