Compare commits

...

6 Commits

Author SHA1 Message Date
Adam Hathcock
165239c971 Merge pull request #1213 from adamhathcock/copilot/fix-compression-type-entry
Fix CompressionType for WinZip AES encrypted ZIP entries
2026-02-12 16:33:28 +00:00
copilot-swe-agent[bot]
d68b9d6a86 Final validation - all tests pass, no security issues
Co-authored-by: adamhathcock <527620+adamhathcock@users.noreply.github.com>
2026-02-12 15:51:01 +00:00
copilot-swe-agent[bot]
359b1093bc Add named constants for WinZip AES extra data magic numbers
Co-authored-by: adamhathcock <527620+adamhathcock@users.noreply.github.com>
2026-02-12 15:47:30 +00:00
copilot-swe-agent[bot]
ebb8f16e44 Fix CompressionType for WinZip AES encrypted entries
Co-authored-by: adamhathcock <527620+adamhathcock@users.noreply.github.com>
2026-02-12 15:45:52 +00:00
copilot-swe-agent[bot]
c7dac12cd9 Initial analysis - WinZip AES encrypted entries show CompressionType: Unknown
Co-authored-by: adamhathcock <527620+adamhathcock@users.noreply.github.com>
2026-02-12 15:40:59 +00:00
copilot-swe-agent[bot]
a505808549 Initial plan 2026-02-12 15:37:05 +00:00
4 changed files with 105 additions and 21 deletions

View File

@@ -10,6 +10,10 @@ public class ZipEntry : Entry
{
private readonly ZipFilePart? _filePart;
// WinZip AES extra data constants
private const int MinimumWinZipAesExtraDataLength = 7;
private const int WinZipAesCompressionMethodOffset = 5;
internal ZipEntry(ZipFilePart? filePart, IReaderOptions readerOptions)
: base(readerOptions)
{
@@ -33,24 +37,54 @@ public class ZipEntry : Entry
CreatedTime = times?.UnicodeTimes.Item3;
}
public override CompressionType CompressionType =>
_filePart?.Header.CompressionMethod switch
public override CompressionType CompressionType
{
get
{
ZipCompressionMethod.BZip2 => CompressionType.BZip2,
ZipCompressionMethod.Deflate => CompressionType.Deflate,
ZipCompressionMethod.Deflate64 => CompressionType.Deflate64,
ZipCompressionMethod.LZMA => CompressionType.LZMA,
ZipCompressionMethod.PPMd => CompressionType.PPMd,
ZipCompressionMethod.None => CompressionType.None,
ZipCompressionMethod.Shrink => CompressionType.Shrink,
ZipCompressionMethod.Reduce1 => CompressionType.Reduce1,
ZipCompressionMethod.Reduce2 => CompressionType.Reduce2,
ZipCompressionMethod.Reduce3 => CompressionType.Reduce3,
ZipCompressionMethod.Reduce4 => CompressionType.Reduce4,
ZipCompressionMethod.Explode => CompressionType.Explode,
ZipCompressionMethod.ZStandard => CompressionType.ZStandard,
_ => CompressionType.Unknown,
};
var compressionMethod = GetActualCompressionMethod();
return compressionMethod switch
{
ZipCompressionMethod.BZip2 => CompressionType.BZip2,
ZipCompressionMethod.Deflate => CompressionType.Deflate,
ZipCompressionMethod.Deflate64 => CompressionType.Deflate64,
ZipCompressionMethod.LZMA => CompressionType.LZMA,
ZipCompressionMethod.PPMd => CompressionType.PPMd,
ZipCompressionMethod.None => CompressionType.None,
ZipCompressionMethod.Shrink => CompressionType.Shrink,
ZipCompressionMethod.Reduce1 => CompressionType.Reduce1,
ZipCompressionMethod.Reduce2 => CompressionType.Reduce2,
ZipCompressionMethod.Reduce3 => CompressionType.Reduce3,
ZipCompressionMethod.Reduce4 => CompressionType.Reduce4,
ZipCompressionMethod.Explode => CompressionType.Explode,
ZipCompressionMethod.ZStandard => CompressionType.ZStandard,
_ => CompressionType.Unknown,
};
}
}
private ZipCompressionMethod GetActualCompressionMethod()
{
if (_filePart?.Header.CompressionMethod != ZipCompressionMethod.WinzipAes)
{
return _filePart?.Header.CompressionMethod ?? ZipCompressionMethod.None;
}
// For WinZip AES, the actual compression method is stored in the extra data
var aesExtraData = _filePart.Header.Extra.FirstOrDefault(x =>
x.Type == ExtraDataType.WinZipAes
);
if (aesExtraData is null || aesExtraData.DataBytes.Length < MinimumWinZipAesExtraDataLength)
{
return ZipCompressionMethod.WinzipAes;
}
// The compression method is at offset 5 in the extra data
return (ZipCompressionMethod)
System.Buffers.Binary.BinaryPrimitives.ReadUInt16LittleEndian(
aesExtraData.DataBytes.AsSpan(WinZipAesCompressionMethodOffset)
);
}
public override long Crc => _filePart?.Header.Crc ?? 0;

View File

@@ -491,6 +491,56 @@ public class ZipArchiveTests : ArchiveTests
}
}
[Fact]
public void Zip_WinzipAES_CompressionType()
{
// Test that WinZip AES encrypted entries correctly report their compression type
using var deflateArchive = ZipArchive.OpenArchive(
Path.Combine(TEST_ARCHIVES_PATH, "Zip.deflate.WinzipAES.zip"),
new ReaderOptions { Password = "test" }
);
foreach (var entry in deflateArchive.Entries.Where(x => !x.IsDirectory))
{
Assert.True(entry.IsEncrypted);
Assert.Equal(CompressionType.Deflate, entry.CompressionType);
}
using var lzmaArchive = ZipArchive.OpenArchive(
Path.Combine(TEST_ARCHIVES_PATH, "Zip.lzma.WinzipAES.zip"),
new ReaderOptions { Password = "test" }
);
foreach (var entry in lzmaArchive.Entries.Where(x => !x.IsDirectory))
{
Assert.True(entry.IsEncrypted);
Assert.Equal(CompressionType.LZMA, entry.CompressionType);
}
}
[Fact]
public void Zip_Pkware_CompressionType()
{
// Test that Pkware encrypted entries correctly report their compression type
using var deflateArchive = ZipArchive.OpenArchive(
Path.Combine(TEST_ARCHIVES_PATH, "Zip.deflate.pkware.zip"),
new ReaderOptions { Password = "test" }
);
foreach (var entry in deflateArchive.Entries.Where(x => !x.IsDirectory))
{
Assert.True(entry.IsEncrypted);
Assert.Equal(CompressionType.Deflate, entry.CompressionType);
}
using var bzip2Archive = ZipArchive.OpenArchive(
Path.Combine(TEST_ARCHIVES_PATH, "Zip.bzip2.pkware.zip"),
new ReaderOptions { Password = "test" }
);
foreach (var entry in bzip2Archive.Entries.Where(x => !x.IsDirectory))
{
Assert.True(entry.IsEncrypted);
Assert.Equal(CompressionType.BZip2, entry.CompressionType);
}
}
[Fact]
public void Zip_Read_Volume_Comment()
{

View File

@@ -224,7 +224,7 @@ public class ZipReaderAsyncTests : ReaderTests
{
if (!reader.Entry.IsDirectory)
{
Assert.Equal(CompressionType.Unknown, reader.Entry.CompressionType);
Assert.Equal(CompressionType.LZMA, reader.Entry.CompressionType);
await reader.WriteEntryToDirectoryAsync(SCRATCH_FILES_PATH);
}
}
@@ -252,7 +252,7 @@ public class ZipReaderAsyncTests : ReaderTests
{
if (!reader.Entry.IsDirectory)
{
Assert.Equal(CompressionType.Unknown, reader.Entry.CompressionType);
Assert.Equal(CompressionType.Deflate, reader.Entry.CompressionType);
await reader.WriteEntryToDirectoryAsync(SCRATCH_FILES_PATH);
}
}

View File

@@ -201,7 +201,7 @@ public class ZipReaderTests : ReaderTests
{
if (!reader.Entry.IsDirectory)
{
Assert.Equal(CompressionType.Unknown, reader.Entry.CompressionType);
Assert.Equal(CompressionType.LZMA, reader.Entry.CompressionType);
reader.WriteEntryToDirectory(SCRATCH_FILES_PATH);
}
}
@@ -223,7 +223,7 @@ public class ZipReaderTests : ReaderTests
{
if (!reader.Entry.IsDirectory)
{
Assert.Equal(CompressionType.Unknown, reader.Entry.CompressionType);
Assert.Equal(CompressionType.Deflate, reader.Entry.CompressionType);
reader.WriteEntryToDirectory(SCRATCH_FILES_PATH);
}
}