diff --git a/docs/API.md b/docs/API.md index 57c618dd..e3026917 100644 --- a/docs/API.md +++ b/docs/API.md @@ -831,3 +831,7 @@ using (var reader = ReaderFactory.OpenReader(stream)) - [USAGE.md](USAGE.md) - Complete code examples - [FORMATS.md](FORMATS.md) - Supported formats - [PERFORMANCE.md](PERFORMANCE.md) - API selection guide +## 7z Encryption Detection + +For 7z archives, `IsEncrypted` is true when any entry has an AES-encrypted data stream. +Streamless empty files and directories are not encrypted. Archives without files return false. diff --git a/src/SharpCompress/Archives/SevenZip/SevenZipArchive.cs b/src/SharpCompress/Archives/SevenZip/SevenZipArchive.cs index 27822313..f90c7e92 100644 --- a/src/SharpCompress/Archives/SevenZip/SevenZipArchive.cs +++ b/src/SharpCompress/Archives/SevenZip/SevenZipArchive.cs @@ -98,7 +98,7 @@ public partial class SevenZipArchive : AbstractArchive x.FilePart.Folder) .Any(folder => folder.Skip(1).Any()); - public override bool IsEncrypted => Entries.First(x => !x.IsDirectory).IsEncrypted; + public override bool IsEncrypted => Entries.Any(x => x.IsEncrypted); public override long TotalSize => _database?._packSizes.Aggregate(0L, (total, packSize) => total + packSize) ?? 0; diff --git a/src/SharpCompress/Common/SevenZip/SevenZipFilePart.cs b/src/SharpCompress/Common/SevenZip/SevenZipFilePart.cs index 65bb7503..a1715fdf 100644 --- a/src/SharpCompress/Common/SevenZip/SevenZipFilePart.cs +++ b/src/SharpCompress/Common/SevenZip/SevenZipFilePart.cs @@ -119,7 +119,5 @@ internal class SevenZipFilePart : FilePart }; } - internal bool IsEncrypted => - !Header.IsDir - && Folder?._coders.FindIndex(c => c._methodId._id == CMethodId.K_AES_ID) != -1; + internal bool IsEncrypted => !Header.IsDir && Folder?.IsEncrypted() == true; } diff --git a/tests/SharpCompress.Test/SevenZip/SevenZipEncryptionDetectionTests.cs b/tests/SharpCompress.Test/SevenZip/SevenZipEncryptionDetectionTests.cs new file mode 100644 index 00000000..2bd2a0aa --- /dev/null +++ b/tests/SharpCompress.Test/SevenZip/SevenZipEncryptionDetectionTests.cs @@ -0,0 +1,82 @@ +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using SharpCompress.Archives.SevenZip; +using SharpCompress.Common.SevenZip; +using SharpCompress.Crypto; +using SharpCompress.Writers.SevenZip; +using Xunit; + +namespace SharpCompress.Test.SevenZip; + +public class SevenZipEncryptionDetectionTests +{ + [Theory] + [InlineData(false)] + [InlineData(true)] + public async Task SevenZipArchive_EmptyFirstFile_EncryptionDetection(bool encrypted) + { + using var output = new MemoryStream(); + using (var writer = new SevenZipWriter(output, new SevenZipWriterOptions())) + { + using var empty = new MemoryStream(); + using var data = new MemoryStream(new byte[] { 1, 2, 3 }); + writer.Write("empty.txt", empty, null); + writer.Write("data.txt", data, null); + } + output.Position = 0; + using var archive = (SevenZipArchive)SevenZipArchive.OpenArchive(output); + var entries = archive.Entries.Cast().ToList(); + if (encrypted) + { + // Only metadata is under test; do not decode after adding the AES marker. + entries[1] + .FilePart.Folder!._coders.Add(new CCoderInfo { _methodId = CMethodId.K_AES }); + } + Assert.False(entries[0].IsEncrypted); + Assert.Same(Stream.Null, entries[0].OpenEntryStream()); + Assert.Equal(encrypted, entries[1].IsEncrypted); + Assert.Equal(encrypted, archive.IsEncrypted); + Assert.Equal(encrypted, await archive.IsEncryptedAsync()); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void SevenZipArchive_NoFiles_IsNotEncrypted(bool directory) + { + using var stream = new MemoryStream(); + // Build reader fixtures directly so this PR does not depend on the writer fix in #1440. + using var header = new MemoryStream(); + ArchiveHeaderWriter.WriteRawHeader( + header, + null, + new SevenZipFilesInfoWriter + { + Entries = directory + ? new[] + { + new SevenZipWriteEntry + { + Name = "dir", + IsDirectory = true, + IsEmpty = true, + Attributes = 0x10, + }, + } + : System.Array.Empty(), + } + ); + var bytes = header.ToArray(); + var crc = Crc32Stream.Compute( + Crc32Stream.DEFAULT_POLYNOMIAL, + Crc32Stream.DEFAULT_SEED, + bytes + ); + SevenZipSignatureHeaderWriter.WriteFinal(stream, 0, (ulong)bytes.Length, crc); + stream.Write(bytes, 0, bytes.Length); + stream.Position = 0; + using var archive = SevenZipArchive.OpenArchive(stream); + Assert.False(archive.IsEncrypted); + } +}