From 819044d1cc649c31ad5d1ac8c3f17aba123cc689 Mon Sep 17 00:00:00 2001 From: iremyux Date: Fri, 28 Nov 2025 20:36:59 +0100 Subject: [PATCH 1/7] Make ZipArchiveEntry.CompressionMethod public and add tests --- .../ref/System.IO.Compression.cs | 7 + .../src/System.IO.Compression.csproj | 1 + .../DeflateManaged/DeflateManagedStream.cs | 6 +- .../IO/Compression/ZipArchiveEntry.Async.cs | 4 +- .../System/IO/Compression/ZipArchiveEntry.cs | 133 ++++++++++++------ .../IO/Compression/ZipCompressionMethod.cs | 29 ++++ .../tests/ZipArchive/zip_ReadTests.cs | 59 ++++++++ 7 files changed, 188 insertions(+), 51 deletions(-) create mode 100644 src/libraries/System.IO.Compression/src/System/IO/Compression/ZipCompressionMethod.cs diff --git a/src/libraries/System.IO.Compression/ref/System.IO.Compression.cs b/src/libraries/System.IO.Compression/ref/System.IO.Compression.cs index e0e0bd0eda52f2..be5dd78db5c1c9 100644 --- a/src/libraries/System.IO.Compression/ref/System.IO.Compression.cs +++ b/src/libraries/System.IO.Compression/ref/System.IO.Compression.cs @@ -116,6 +116,7 @@ internal ZipArchiveEntry() { } [System.Diagnostics.CodeAnalysis.AllowNullAttribute] public string Comment { get { throw null; } set { } } public long CompressedLength { get { throw null; } } + public System.IO.Compression.ZipCompressionMethod CompressionMethod { get { throw null; } } [System.CLSCompliantAttribute(false)] public uint Crc32 { get { throw null; } } public int ExternalAttributes { get { throw null; } set { } } @@ -135,6 +136,12 @@ public enum ZipArchiveMode Create = 1, Update = 2, } + public enum ZipCompressionMethod : short + { + Stored = 0, + Deflate = 8, + Deflate64 = 9, + } public sealed partial class ZLibCompressionOptions { public ZLibCompressionOptions() { } diff --git a/src/libraries/System.IO.Compression/src/System.IO.Compression.csproj b/src/libraries/System.IO.Compression/src/System.IO.Compression.csproj index 7510c1c1052274..91ad2914646cd3 100644 --- a/src/libraries/System.IO.Compression/src/System.IO.Compression.csproj +++ b/src/libraries/System.IO.Compression/src/System.IO.Compression.csproj @@ -54,6 +54,7 @@ + diff --git a/src/libraries/System.IO.Compression/src/System/IO/Compression/DeflateManaged/DeflateManagedStream.cs b/src/libraries/System.IO.Compression/src/System/IO/Compression/DeflateManaged/DeflateManagedStream.cs index d161f746677724..dc3c9b5eeffb2a 100644 --- a/src/libraries/System.IO.Compression/src/System/IO/Compression/DeflateManaged/DeflateManagedStream.cs +++ b/src/libraries/System.IO.Compression/src/System/IO/Compression/DeflateManaged/DeflateManagedStream.cs @@ -20,16 +20,16 @@ internal sealed partial class DeflateManagedStream : Stream private int _asyncOperations; // A specific constructor to allow decompression of Deflate64 - internal DeflateManagedStream(Stream stream, ZipArchiveEntry.CompressionMethodValues method, long uncompressedSize = -1) + internal DeflateManagedStream(Stream stream, ZipCompressionMethod method, long uncompressedSize = -1) { ArgumentNullException.ThrowIfNull(stream); if (!stream.CanRead) throw new ArgumentException(SR.NotSupported_UnreadableStream, nameof(stream)); - Debug.Assert(method == ZipArchiveEntry.CompressionMethodValues.Deflate64); + Debug.Assert(method == ZipCompressionMethod.Deflate64); - _inflater = new InflaterManaged(method == ZipArchiveEntry.CompressionMethodValues.Deflate64, uncompressedSize); + _inflater = new InflaterManaged(method == ZipCompressionMethod.Deflate64, uncompressedSize); _stream = stream; _buffer = new byte[DefaultBufferSize]; diff --git a/src/libraries/System.IO.Compression/src/System/IO/Compression/ZipArchiveEntry.Async.cs b/src/libraries/System.IO.Compression/src/System/IO/Compression/ZipArchiveEntry.Async.cs index 030f2d1e25f635..2427e35a549277 100644 --- a/src/libraries/System.IO.Compression/src/System/IO/Compression/ZipArchiveEntry.Async.cs +++ b/src/libraries/System.IO.Compression/src/System/IO/Compression/ZipArchiveEntry.Async.cs @@ -89,9 +89,9 @@ private async Task GetUncompressedDataAsync(CancellationToken canc } // if they start modifying it and the compression method is not "store", we should make sure it will get deflated - if (CompressionMethod != CompressionMethodValues.Stored) + if (StoredCompressionMethodInternal != ZipCompressionMethod.Stored) { - CompressionMethod = CompressionMethodValues.Deflate; + StoredCompressionMethodInternal = ZipCompressionMethod.Deflate; } } diff --git a/src/libraries/System.IO.Compression/src/System/IO/Compression/ZipArchiveEntry.cs b/src/libraries/System.IO.Compression/src/System/IO/Compression/ZipArchiveEntry.cs index fb5733e8ebd28c..f98734da6c3da7 100644 --- a/src/libraries/System.IO.Compression/src/System/IO/Compression/ZipArchiveEntry.cs +++ b/src/libraries/System.IO.Compression/src/System/IO/Compression/ZipArchiveEntry.cs @@ -24,7 +24,7 @@ public partial class ZipArchiveEntry private ZipVersionNeededValues _versionToExtract; private BitFlagValues _generalPurposeBitFlag; private readonly bool _isEncrypted; - private CompressionMethodValues _storedCompressionMethod; + private ZipCompressionMethod _storedCompressionMethod; private DateTimeOffset _lastModified; private long _compressedSize; private long _uncompressedSize; @@ -66,7 +66,7 @@ internal ZipArchiveEntry(ZipArchive archive, ZipCentralDirectoryFileHeader cd) _versionToExtract = (ZipVersionNeededValues)cd.VersionNeededToExtract; _generalPurposeBitFlag = (BitFlagValues)cd.GeneralPurposeBitFlag; _isEncrypted = (_generalPurposeBitFlag & BitFlagValues.IsEncrypted) != 0; - CompressionMethod = (CompressionMethodValues)cd.CompressionMethod; + StoredCompressionMethodInternal = (ZipCompressionMethod)cd.CompressionMethod; _lastModified = new DateTimeOffset(ZipHelper.DosTimeToDateTime(cd.LastModified)); _compressedSize = cd.CompressedSize; _uncompressedSize = cd.UncompressedSize; @@ -94,7 +94,7 @@ internal ZipArchiveEntry(ZipArchive archive, ZipCentralDirectoryFileHeader cd) _fileComment = cd.FileComment; - _compressionLevel = MapCompressionLevel(_generalPurposeBitFlag, CompressionMethod); + _compressionLevel = MapCompressionLevel(_generalPurposeBitFlag, StoredCompressionMethodInternal); } // Initializes a ZipArchiveEntry instance for a new archive entry with a specified compression level. @@ -104,9 +104,9 @@ internal ZipArchiveEntry(ZipArchive archive, string entryName, CompressionLevel _compressionLevel = compressionLevel; if (_compressionLevel == CompressionLevel.NoCompression) { - CompressionMethod = CompressionMethodValues.Stored; + StoredCompressionMethodInternal = ZipCompressionMethod.Stored; } - _generalPurposeBitFlag = MapDeflateCompressionOption(_generalPurposeBitFlag, _compressionLevel, CompressionMethod); + _generalPurposeBitFlag = MapDeflateCompressionOption(_generalPurposeBitFlag, _compressionLevel, StoredCompressionMethodInternal); } // Initializes a ZipArchiveEntry instance for a new archive entry. @@ -121,8 +121,8 @@ internal ZipArchiveEntry(ZipArchive archive, string entryName) _versionMadeBySpecification = ZipVersionNeededValues.Default; _versionToExtract = ZipVersionNeededValues.Default; // this must happen before following two assignment _compressionLevel = CompressionLevel.Optimal; - CompressionMethod = CompressionMethodValues.Deflate; - _generalPurposeBitFlag = MapDeflateCompressionOption(0, _compressionLevel, CompressionMethod); + StoredCompressionMethodInternal = ZipCompressionMethod.Deflate; + _generalPurposeBitFlag = MapDeflateCompressionOption(0, _compressionLevel, StoredCompressionMethodInternal); _lastModified = DateTimeOffset.Now; _compressedSize = 0; // we don't know these yet @@ -173,6 +173,15 @@ internal ZipArchiveEntry(ZipArchive archive, string entryName) /// public bool IsEncrypted => _isEncrypted; + /// + /// Gets the compression method used to compress the entry. + /// + /// + /// Returns the compression method as stored in the zip archive header. The value corresponds to the + /// compression method values described in the ZIP File Format Specification (APPNOTE.TXT section 4.4.5). + /// + public ZipCompressionMethod CompressionMethod => _storedCompressionMethod; + /// /// The compressed size of the entry. If the archive that the entry belongs to is in Create mode, attempts to get this property will always throw an exception. If the archive that the entry belongs to is in update mode, this property will only be valid if the entry has not been opened. /// @@ -362,6 +371,51 @@ public Stream Open() } } + /// + /// Opens the entry with the specified access mode. This allows for more granular control over the returned stream's capabilities. + /// + /// The desired file access mode for the returned stream. + /// A Stream that represents the contents of the entry with the specified access capabilities. + /// The requested access is not compatible with the archive's open mode. + /// The entry is already currently open for writing. -or- The entry has been deleted from the archive. -or- The archive that this entry belongs to was opened in ZipArchiveMode.Create, and this entry has already been written to once. + /// The entry is missing from the archive or is corrupt and cannot be read. -or- The entry has been compressed using a compression method that is not supported. + /// The ZipArchive that this entry belongs to has been disposed. + public Stream Open(FileAccess access) + { + ThrowIfInvalidArchive(); + + // Validate that the requested access is compatible with the archive's mode + switch (_archive.Mode) + { + case ZipArchiveMode.Read: + if (access != FileAccess.Read) + throw new ArgumentException(SR.CannotBeWrittenInReadMode, nameof(access)); + return OpenInReadMode(checkOpenable: true); + + case ZipArchiveMode.Create: + if (access == FileAccess.Read) + throw new ArgumentException(SR.CannotBeReadInCreateMode, nameof(access)); + return OpenInWriteMode(); + + case ZipArchiveMode.Update: + switch (access) + { + case FileAccess.Read: + return OpenInReadMode(checkOpenable: true); + case FileAccess.Write: + return OpenInWriteMode(); + case FileAccess.ReadWrite: + return OpenInUpdateMode(); + default: + throw new ArgumentException(SR.InvalidFileAccess, nameof(access)); + } + + default: + Debug.Assert(_archive.Mode == ZipArchiveMode.Update); + return OpenInUpdateMode(); + } + } + /// /// Returns the FullName of the entry. /// @@ -439,23 +493,23 @@ private MemoryStream GetUncompressedData() } // if they start modifying it and the compression method is not "store", we should make sure it will get deflated - if (CompressionMethod != CompressionMethodValues.Stored) + if (StoredCompressionMethodInternal != ZipCompressionMethod.Stored) { - CompressionMethod = CompressionMethodValues.Deflate; + StoredCompressionMethodInternal = ZipCompressionMethod.Deflate; } } return _storedUncompressedData; } - private CompressionMethodValues CompressionMethod + private ZipCompressionMethod StoredCompressionMethodInternal { get { return _storedCompressionMethod; } set { - if (value == CompressionMethodValues.Deflate) + if (value == ZipCompressionMethod.Deflate) VersionToExtractAtLeast(ZipVersionNeededValues.Deflate); - else if (value == CompressionMethodValues.Deflate64) + else if (value == ZipCompressionMethod.Deflate64) VersionToExtractAtLeast(ZipVersionNeededValues.Deflate64); _storedCompressionMethod = value; } @@ -714,20 +768,20 @@ private CheckSumAndSizeWriteStream GetDataCompressor(Stream backingStream, bool // changed to Stored. // // Note: Deflate64 is not supported on all platforms - Debug.Assert(CompressionMethod == CompressionMethodValues.Deflate - || CompressionMethod == CompressionMethodValues.Stored); + Debug.Assert(StoredCompressionMethodInternal == ZipCompressionMethod.Deflate + || StoredCompressionMethodInternal == ZipCompressionMethod.Stored); Func compressorStreamFactory; bool isIntermediateStream = true; - switch (CompressionMethod) + switch (StoredCompressionMethodInternal) { - case CompressionMethodValues.Stored: + case ZipCompressionMethod.Stored: compressorStreamFactory = () => backingStream; isIntermediateStream = false; break; - case CompressionMethodValues.Deflate: - case CompressionMethodValues.Deflate64: + case ZipCompressionMethod.Deflate: + case ZipCompressionMethod.Deflate64: default: compressorStreamFactory = () => new DeflateStream(backingStream, _compressionLevel, leaveBackingStreamOpen); break; @@ -754,19 +808,19 @@ private CheckSumAndSizeWriteStream GetDataCompressor(Stream backingStream, bool private Stream GetDataDecompressor(Stream compressedStreamToRead) { Stream? uncompressedStream; - switch (CompressionMethod) + switch (StoredCompressionMethodInternal) { - case CompressionMethodValues.Deflate: + case ZipCompressionMethod.Deflate: uncompressedStream = new DeflateStream(compressedStreamToRead, CompressionMode.Decompress, _uncompressedSize); break; - case CompressionMethodValues.Deflate64: - uncompressedStream = new DeflateManagedStream(compressedStreamToRead, CompressionMethodValues.Deflate64, _uncompressedSize); + case ZipCompressionMethod.Deflate64: + uncompressedStream = new DeflateManagedStream(compressedStreamToRead, ZipCompressionMethod.Deflate64, _uncompressedSize); break; - case CompressionMethodValues.Stored: + case ZipCompressionMethod.Stored: default: // we can assume that only deflate/deflate64/stored are allowed because we assume that // IsOpenable is checked before this function is called - Debug.Assert(CompressionMethod == CompressionMethodValues.Stored); + Debug.Assert(StoredCompressionMethodInternal == ZipCompressionMethod.Stored); uncompressedStream = compressedStreamToRead; break; @@ -867,15 +921,11 @@ private bool IsOpenableInitialVerifications(bool needToUncompress, out string? m message = null; if (needToUncompress) { - if (CompressionMethod != CompressionMethodValues.Stored && - CompressionMethod != CompressionMethodValues.Deflate && - CompressionMethod != CompressionMethodValues.Deflate64) + if (StoredCompressionMethodInternal != ZipCompressionMethod.Stored && + StoredCompressionMethodInternal != ZipCompressionMethod.Deflate && + StoredCompressionMethodInternal != ZipCompressionMethod.Deflate64) { - message = CompressionMethod switch - { - CompressionMethodValues.BZip2 or CompressionMethodValues.LZMA => SR.Format(SR.UnsupportedCompressionMethod, CompressionMethod.ToString()), - _ => SR.UnsupportedCompression, - }; + message = SR.UnsupportedCompression; return false; } } @@ -923,11 +973,11 @@ private bool IsOpenableFinalVerifications(bool needToLoadIntoMemory, long offset private bool AreSizesTooLarge => _compressedSize > uint.MaxValue || _uncompressedSize > uint.MaxValue; - private static CompressionLevel MapCompressionLevel(BitFlagValues generalPurposeBitFlag, CompressionMethodValues compressionMethod) + private static CompressionLevel MapCompressionLevel(BitFlagValues generalPurposeBitFlag, ZipCompressionMethod compressionMethod) { // Information about the Deflate compression option is stored in bits 1 and 2 of the general purpose bit flags. // If the compression method is not Deflate, the Deflate compression option is invalid - default to NoCompression. - if (compressionMethod == CompressionMethodValues.Deflate || compressionMethod == CompressionMethodValues.Deflate64) + if (compressionMethod == ZipCompressionMethod.Deflate || compressionMethod == ZipCompressionMethod.Deflate64) { return ((int)generalPurposeBitFlag & 0x6) switch { @@ -944,12 +994,12 @@ private static CompressionLevel MapCompressionLevel(BitFlagValues generalPurpose } } - private static BitFlagValues MapDeflateCompressionOption(BitFlagValues generalPurposeBitFlag, CompressionLevel compressionLevel, CompressionMethodValues compressionMethod) + private static BitFlagValues MapDeflateCompressionOption(BitFlagValues generalPurposeBitFlag, CompressionLevel compressionLevel, ZipCompressionMethod compressionMethod) { ushort deflateCompressionOptions = (ushort)( // The Deflate compression level is only valid if the compression method is actually Deflate (or Deflate64). If it's not, the // value of the two bits is undefined and they should be zeroed out. - compressionMethod == CompressionMethodValues.Deflate || compressionMethod == CompressionMethodValues.Deflate64 + compressionMethod == ZipCompressionMethod.Deflate || compressionMethod == ZipCompressionMethod.Deflate64 ? compressionLevel switch { CompressionLevel.Optimal => 0, @@ -982,7 +1032,7 @@ private bool WriteLocalFileHeaderInitialize(bool isEmptyFile, bool forceWrite, o // if we already know that we have an empty file don't worry about anything, just do a straight shot of the header if (isEmptyFile) { - CompressionMethod = CompressionMethodValues.Stored; + StoredCompressionMethodInternal = ZipCompressionMethod.Stored; compressedSizeTruncated = 0; uncompressedSizeTruncated = 0; Debug.Assert(_uncompressedSize == 0); @@ -1627,15 +1677,6 @@ internal enum BitFlagValues : ushort UnicodeFileNameAndComment = 0x800 } - internal enum CompressionMethodValues : ushort - { - Stored = 0x0, - Deflate = 0x8, - Deflate64 = 0x9, - BZip2 = 0xC, - LZMA = 0xE - } - internal sealed class LocalHeaderOffsetComparer : Comparer { private static readonly LocalHeaderOffsetComparer s_instance = new LocalHeaderOffsetComparer(); diff --git a/src/libraries/System.IO.Compression/src/System/IO/Compression/ZipCompressionMethod.cs b/src/libraries/System.IO.Compression/src/System/IO/Compression/ZipCompressionMethod.cs new file mode 100644 index 00000000000000..ca11d5b631930b --- /dev/null +++ b/src/libraries/System.IO.Compression/src/System/IO/Compression/ZipCompressionMethod.cs @@ -0,0 +1,29 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace System.IO.Compression +{ + /// + /// Specifies the compression method used to compress an entry in a zip archive. + /// + /// + /// The values correspond to the compression method values described in the ZIP File Format Specification (APPNOTE.TXT section 4.4.5). + /// + public enum ZipCompressionMethod : short + { + /// + /// The entry is stored (no compression). + /// + Stored = 0x0, + + /// + /// The entry is compressed using the Deflate algorithm. + /// + Deflate = 0x8, + + /// + /// The entry is compressed using the Deflate64 algorithm. + /// + Deflate64 = 0x9, + } +} diff --git a/src/libraries/System.IO.Compression/tests/ZipArchive/zip_ReadTests.cs b/src/libraries/System.IO.Compression/tests/ZipArchive/zip_ReadTests.cs index 24e96ba0dd594b..daa93ed92fa8f3 100644 --- a/src/libraries/System.IO.Compression/tests/ZipArchive/zip_ReadTests.cs +++ b/src/libraries/System.IO.Compression/tests/ZipArchive/zip_ReadTests.cs @@ -820,5 +820,64 @@ protected override void Dispose(bool disposing) base.Dispose(disposing); } } + + [Theory] + [MemberData(nameof(Get_Booleans_Data))] + public static async Task CompressionMethod_Deflate_ReturnsDeflate(bool async) + { + using var ms = new MemoryStream(); + using (var archive = new ZipArchive(ms, ZipArchiveMode.Create, leaveOpen: true)) + { + var entry = archive.CreateEntry("test.txt", CompressionLevel.Optimal); + using (var stream = entry.Open()) + { + stream.Write("test data"u8); + } + } + + ms.Position = 0; + ZipArchive readArchive = await CreateZipArchive(async, ms, ZipArchiveMode.Read); + ZipArchiveEntry readEntry = readArchive.Entries[0]; + Assert.Equal(ZipCompressionMethod.Deflate, readEntry.CompressionMethod); + await DisposeZipArchive(async, readArchive); + } + + [Theory] + [MemberData(nameof(Get_Booleans_Data))] + public static async Task CompressionMethod_Stored_ReturnsStored(bool async) + { + using var ms = new MemoryStream(); + using (var archive = new ZipArchive(ms, ZipArchiveMode.Create, leaveOpen: true)) + { + var entry = archive.CreateEntry("test.txt", CompressionLevel.NoCompression); + using (var stream = entry.Open()) + { + stream.Write("test data"u8); + } + } + + ms.Position = 0; + ZipArchive readArchive = await CreateZipArchive(async, ms, ZipArchiveMode.Read); + ZipArchiveEntry readEntry = readArchive.Entries[0]; + Assert.Equal(ZipCompressionMethod.Stored, readEntry.CompressionMethod); + await DisposeZipArchive(async, readArchive); + } + + [Theory] + [MemberData(nameof(Get_Booleans_Data))] + public static async Task CompressionMethod_EmptyFile_ReturnsStored(bool async) + { + using var ms = new MemoryStream(); + using (var archive = new ZipArchive(ms, ZipArchiveMode.Create, leaveOpen: true)) + { + var entry = archive.CreateEntry("empty.txt"); + } + + ms.Position = 0; + ZipArchive readArchive = await CreateZipArchive(async, ms, ZipArchiveMode.Read); + ZipArchiveEntry readEntry = readArchive.Entries[0]; + Assert.Equal(ZipCompressionMethod.Stored, readEntry.CompressionMethod); + await DisposeZipArchive(async, readArchive); + } } } From 0eb893e3948cc5fa8d6b083514fe9fd805d05882 Mon Sep 17 00:00:00 2001 From: iremyux Date: Fri, 28 Nov 2025 20:44:17 +0100 Subject: [PATCH 2/7] Remove code from another branch --- .../System/IO/Compression/ZipArchiveEntry.cs | 45 ------------------- 1 file changed, 45 deletions(-) diff --git a/src/libraries/System.IO.Compression/src/System/IO/Compression/ZipArchiveEntry.cs b/src/libraries/System.IO.Compression/src/System/IO/Compression/ZipArchiveEntry.cs index f98734da6c3da7..c88625c763c9de 100644 --- a/src/libraries/System.IO.Compression/src/System/IO/Compression/ZipArchiveEntry.cs +++ b/src/libraries/System.IO.Compression/src/System/IO/Compression/ZipArchiveEntry.cs @@ -371,51 +371,6 @@ public Stream Open() } } - /// - /// Opens the entry with the specified access mode. This allows for more granular control over the returned stream's capabilities. - /// - /// The desired file access mode for the returned stream. - /// A Stream that represents the contents of the entry with the specified access capabilities. - /// The requested access is not compatible with the archive's open mode. - /// The entry is already currently open for writing. -or- The entry has been deleted from the archive. -or- The archive that this entry belongs to was opened in ZipArchiveMode.Create, and this entry has already been written to once. - /// The entry is missing from the archive or is corrupt and cannot be read. -or- The entry has been compressed using a compression method that is not supported. - /// The ZipArchive that this entry belongs to has been disposed. - public Stream Open(FileAccess access) - { - ThrowIfInvalidArchive(); - - // Validate that the requested access is compatible with the archive's mode - switch (_archive.Mode) - { - case ZipArchiveMode.Read: - if (access != FileAccess.Read) - throw new ArgumentException(SR.CannotBeWrittenInReadMode, nameof(access)); - return OpenInReadMode(checkOpenable: true); - - case ZipArchiveMode.Create: - if (access == FileAccess.Read) - throw new ArgumentException(SR.CannotBeReadInCreateMode, nameof(access)); - return OpenInWriteMode(); - - case ZipArchiveMode.Update: - switch (access) - { - case FileAccess.Read: - return OpenInReadMode(checkOpenable: true); - case FileAccess.Write: - return OpenInWriteMode(); - case FileAccess.ReadWrite: - return OpenInUpdateMode(); - default: - throw new ArgumentException(SR.InvalidFileAccess, nameof(access)); - } - - default: - Debug.Assert(_archive.Mode == ZipArchiveMode.Update); - return OpenInUpdateMode(); - } - } - /// /// Returns the FullName of the entry. /// From f43686687f501a051ae9735b0bbf0653f51f5277 Mon Sep 17 00:00:00 2001 From: iremyux Date: Fri, 28 Nov 2025 20:47:22 +0100 Subject: [PATCH 3/7] Fix the test that still uses ushort --- .../System.IO.Compression/tests/ZipArchive/zip_ReadTests.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/libraries/System.IO.Compression/tests/ZipArchive/zip_ReadTests.cs b/src/libraries/System.IO.Compression/tests/ZipArchive/zip_ReadTests.cs index daa93ed92fa8f3..a6b038c8a2ce1b 100644 --- a/src/libraries/System.IO.Compression/tests/ZipArchive/zip_ReadTests.cs +++ b/src/libraries/System.IO.Compression/tests/ZipArchive/zip_ReadTests.cs @@ -595,10 +595,9 @@ public static async Task ReadStreamOps(bool async) // Check the entry's compression method to determine seekability // SubReadStream should be seekable when the underlying stream is seekable and the entry is stored (uncompressed) // If the entry is compressed (Deflate, Deflate64, etc.), it will be wrapped in a compression stream which is not seekable - ushort compressionMethod = (ushort)compressionMethodField.GetValue(e); - const ushort StoredCompressionMethod = 0x0; // CompressionMethodValues.Stored + ZipCompressionMethod compressionMethod = (ZipCompressionMethod)compressionMethodField.GetValue(e); - if (compressionMethod == StoredCompressionMethod) + if (compressionMethod == ZipCompressionMethod.Stored) { // Entry is stored (uncompressed), should be seekable Assert.True(s.CanSeek, $"SubReadStream should be seekable for stored (uncompressed) entry '{e.FullName}' with compression method {compressionMethod} when underlying stream is seekable"); From 10c52d64f41bab1490f97f3da8483d5ae8835bc1 Mon Sep 17 00:00:00 2001 From: iremyux Date: Tue, 2 Dec 2025 17:55:27 +0100 Subject: [PATCH 4/7] Make the enum int --- .../System.IO.Compression/ref/System.IO.Compression.cs | 2 +- .../src/System/IO/Compression/ZipCompressionMethod.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libraries/System.IO.Compression/ref/System.IO.Compression.cs b/src/libraries/System.IO.Compression/ref/System.IO.Compression.cs index be5dd78db5c1c9..6f4c376de6c241 100644 --- a/src/libraries/System.IO.Compression/ref/System.IO.Compression.cs +++ b/src/libraries/System.IO.Compression/ref/System.IO.Compression.cs @@ -136,7 +136,7 @@ public enum ZipArchiveMode Create = 1, Update = 2, } - public enum ZipCompressionMethod : short + public enum ZipCompressionMethod { Stored = 0, Deflate = 8, diff --git a/src/libraries/System.IO.Compression/src/System/IO/Compression/ZipCompressionMethod.cs b/src/libraries/System.IO.Compression/src/System/IO/Compression/ZipCompressionMethod.cs index ca11d5b631930b..7ca56e277bbd2f 100644 --- a/src/libraries/System.IO.Compression/src/System/IO/Compression/ZipCompressionMethod.cs +++ b/src/libraries/System.IO.Compression/src/System/IO/Compression/ZipCompressionMethod.cs @@ -9,7 +9,7 @@ namespace System.IO.Compression /// /// The values correspond to the compression method values described in the ZIP File Format Specification (APPNOTE.TXT section 4.4.5). /// - public enum ZipCompressionMethod : short + public enum ZipCompressionMethod { /// /// The entry is stored (no compression). From 8efd970f551e6f383f487aa29ae50fbd0407de02 Mon Sep 17 00:00:00 2001 From: iremyux Date: Tue, 2 Dec 2025 17:55:38 +0100 Subject: [PATCH 5/7] Update the comment --- .../src/System/IO/Compression/ZipArchiveEntry.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/libraries/System.IO.Compression/src/System/IO/Compression/ZipArchiveEntry.cs b/src/libraries/System.IO.Compression/src/System/IO/Compression/ZipArchiveEntry.cs index c88625c763c9de..a26a213dbfb80f 100644 --- a/src/libraries/System.IO.Compression/src/System/IO/Compression/ZipArchiveEntry.cs +++ b/src/libraries/System.IO.Compression/src/System/IO/Compression/ZipArchiveEntry.cs @@ -176,10 +176,6 @@ internal ZipArchiveEntry(ZipArchive archive, string entryName) /// /// Gets the compression method used to compress the entry. /// - /// - /// Returns the compression method as stored in the zip archive header. The value corresponds to the - /// compression method values described in the ZIP File Format Specification (APPNOTE.TXT section 4.4.5). - /// public ZipCompressionMethod CompressionMethod => _storedCompressionMethod; /// From 7e0548eddfc4276590acb71262c75086de57df0b Mon Sep 17 00:00:00 2001 From: iremyux Date: Wed, 3 Dec 2025 22:54:37 +0100 Subject: [PATCH 6/7] Get rid of StoredCompressionMethodInternal --- .../IO/Compression/ZipArchiveEntry.Async.cs | 4 +- .../System/IO/Compression/ZipArchiveEntry.cs | 59 +++++++++---------- 2 files changed, 31 insertions(+), 32 deletions(-) diff --git a/src/libraries/System.IO.Compression/src/System/IO/Compression/ZipArchiveEntry.Async.cs b/src/libraries/System.IO.Compression/src/System/IO/Compression/ZipArchiveEntry.Async.cs index 2427e35a549277..19bdbfff093f20 100644 --- a/src/libraries/System.IO.Compression/src/System/IO/Compression/ZipArchiveEntry.Async.cs +++ b/src/libraries/System.IO.Compression/src/System/IO/Compression/ZipArchiveEntry.Async.cs @@ -89,9 +89,9 @@ private async Task GetUncompressedDataAsync(CancellationToken canc } // if they start modifying it and the compression method is not "store", we should make sure it will get deflated - if (StoredCompressionMethodInternal != ZipCompressionMethod.Stored) + if (CompressionMethod != ZipCompressionMethod.Stored) { - StoredCompressionMethodInternal = ZipCompressionMethod.Deflate; + CompressionMethod = ZipCompressionMethod.Deflate; } } diff --git a/src/libraries/System.IO.Compression/src/System/IO/Compression/ZipArchiveEntry.cs b/src/libraries/System.IO.Compression/src/System/IO/Compression/ZipArchiveEntry.cs index a26a213dbfb80f..c382c5e2c840c5 100644 --- a/src/libraries/System.IO.Compression/src/System/IO/Compression/ZipArchiveEntry.cs +++ b/src/libraries/System.IO.Compression/src/System/IO/Compression/ZipArchiveEntry.cs @@ -66,7 +66,7 @@ internal ZipArchiveEntry(ZipArchive archive, ZipCentralDirectoryFileHeader cd) _versionToExtract = (ZipVersionNeededValues)cd.VersionNeededToExtract; _generalPurposeBitFlag = (BitFlagValues)cd.GeneralPurposeBitFlag; _isEncrypted = (_generalPurposeBitFlag & BitFlagValues.IsEncrypted) != 0; - StoredCompressionMethodInternal = (ZipCompressionMethod)cd.CompressionMethod; + CompressionMethod = (ZipCompressionMethod)cd.CompressionMethod; _lastModified = new DateTimeOffset(ZipHelper.DosTimeToDateTime(cd.LastModified)); _compressedSize = cd.CompressedSize; _uncompressedSize = cd.UncompressedSize; @@ -94,7 +94,7 @@ internal ZipArchiveEntry(ZipArchive archive, ZipCentralDirectoryFileHeader cd) _fileComment = cd.FileComment; - _compressionLevel = MapCompressionLevel(_generalPurposeBitFlag, StoredCompressionMethodInternal); + _compressionLevel = MapCompressionLevel(_generalPurposeBitFlag, CompressionMethod); } // Initializes a ZipArchiveEntry instance for a new archive entry with a specified compression level. @@ -104,9 +104,9 @@ internal ZipArchiveEntry(ZipArchive archive, string entryName, CompressionLevel _compressionLevel = compressionLevel; if (_compressionLevel == CompressionLevel.NoCompression) { - StoredCompressionMethodInternal = ZipCompressionMethod.Stored; + CompressionMethod = ZipCompressionMethod.Stored; } - _generalPurposeBitFlag = MapDeflateCompressionOption(_generalPurposeBitFlag, _compressionLevel, StoredCompressionMethodInternal); + _generalPurposeBitFlag = MapDeflateCompressionOption(_generalPurposeBitFlag, _compressionLevel, CompressionMethod); } // Initializes a ZipArchiveEntry instance for a new archive entry. @@ -121,8 +121,8 @@ internal ZipArchiveEntry(ZipArchive archive, string entryName) _versionMadeBySpecification = ZipVersionNeededValues.Default; _versionToExtract = ZipVersionNeededValues.Default; // this must happen before following two assignment _compressionLevel = CompressionLevel.Optimal; - StoredCompressionMethodInternal = ZipCompressionMethod.Deflate; - _generalPurposeBitFlag = MapDeflateCompressionOption(0, _compressionLevel, StoredCompressionMethodInternal); + CompressionMethod = ZipCompressionMethod.Deflate; + _generalPurposeBitFlag = MapDeflateCompressionOption(0, _compressionLevel, CompressionMethod); _lastModified = DateTimeOffset.Now; _compressedSize = 0; // we don't know these yet @@ -176,7 +176,18 @@ internal ZipArchiveEntry(ZipArchive archive, string entryName) /// /// Gets the compression method used to compress the entry. /// - public ZipCompressionMethod CompressionMethod => _storedCompressionMethod; + public ZipCompressionMethod CompressionMethod + { + get => _storedCompressionMethod; + private set + { + if (value == ZipCompressionMethod.Deflate) + VersionToExtractAtLeast(ZipVersionNeededValues.Deflate); + else if (value == ZipCompressionMethod.Deflate64) + VersionToExtractAtLeast(ZipVersionNeededValues.Deflate64); + _storedCompressionMethod = value; + } + } /// /// The compressed size of the entry. If the archive that the entry belongs to is in Create mode, attempts to get this property will always throw an exception. If the archive that the entry belongs to is in update mode, this property will only be valid if the entry has not been opened. @@ -444,27 +455,15 @@ private MemoryStream GetUncompressedData() } // if they start modifying it and the compression method is not "store", we should make sure it will get deflated - if (StoredCompressionMethodInternal != ZipCompressionMethod.Stored) + if (CompressionMethod != ZipCompressionMethod.Stored) { - StoredCompressionMethodInternal = ZipCompressionMethod.Deflate; + CompressionMethod = ZipCompressionMethod.Deflate; } } return _storedUncompressedData; } - private ZipCompressionMethod StoredCompressionMethodInternal - { - get { return _storedCompressionMethod; } - set - { - if (value == ZipCompressionMethod.Deflate) - VersionToExtractAtLeast(ZipVersionNeededValues.Deflate); - else if (value == ZipCompressionMethod.Deflate64) - VersionToExtractAtLeast(ZipVersionNeededValues.Deflate64); - _storedCompressionMethod = value; - } - } // does almost everything you need to do to forget about this entry // writes the local header/data, gets rid of all the data, // closes all of the streams except for the very outermost one that @@ -719,13 +718,13 @@ private CheckSumAndSizeWriteStream GetDataCompressor(Stream backingStream, bool // changed to Stored. // // Note: Deflate64 is not supported on all platforms - Debug.Assert(StoredCompressionMethodInternal == ZipCompressionMethod.Deflate - || StoredCompressionMethodInternal == ZipCompressionMethod.Stored); + Debug.Assert(CompressionMethod == ZipCompressionMethod.Deflate + || CompressionMethod == ZipCompressionMethod.Stored); Func compressorStreamFactory; bool isIntermediateStream = true; - switch (StoredCompressionMethodInternal) + switch (CompressionMethod) { case ZipCompressionMethod.Stored: compressorStreamFactory = () => backingStream; @@ -759,7 +758,7 @@ private CheckSumAndSizeWriteStream GetDataCompressor(Stream backingStream, bool private Stream GetDataDecompressor(Stream compressedStreamToRead) { Stream? uncompressedStream; - switch (StoredCompressionMethodInternal) + switch (CompressionMethod) { case ZipCompressionMethod.Deflate: uncompressedStream = new DeflateStream(compressedStreamToRead, CompressionMode.Decompress, _uncompressedSize); @@ -771,7 +770,7 @@ private Stream GetDataDecompressor(Stream compressedStreamToRead) default: // we can assume that only deflate/deflate64/stored are allowed because we assume that // IsOpenable is checked before this function is called - Debug.Assert(StoredCompressionMethodInternal == ZipCompressionMethod.Stored); + Debug.Assert(CompressionMethod == ZipCompressionMethod.Stored); uncompressedStream = compressedStreamToRead; break; @@ -872,9 +871,9 @@ private bool IsOpenableInitialVerifications(bool needToUncompress, out string? m message = null; if (needToUncompress) { - if (StoredCompressionMethodInternal != ZipCompressionMethod.Stored && - StoredCompressionMethodInternal != ZipCompressionMethod.Deflate && - StoredCompressionMethodInternal != ZipCompressionMethod.Deflate64) + if (CompressionMethod != ZipCompressionMethod.Stored && + CompressionMethod != ZipCompressionMethod.Deflate && + CompressionMethod != ZipCompressionMethod.Deflate64) { message = SR.UnsupportedCompression; return false; @@ -983,7 +982,7 @@ private bool WriteLocalFileHeaderInitialize(bool isEmptyFile, bool forceWrite, o // if we already know that we have an empty file don't worry about anything, just do a straight shot of the header if (isEmptyFile) { - StoredCompressionMethodInternal = ZipCompressionMethod.Stored; + CompressionMethod = ZipCompressionMethod.Stored; compressedSizeTruncated = 0; uncompressedSizeTruncated = 0; Debug.Assert(_uncompressedSize == 0); From 3cd1f9cea8b7e50abe89c78a70ee740356ec92e0 Mon Sep 17 00:00:00 2001 From: iremyux Date: Thu, 4 Dec 2025 14:46:25 +0100 Subject: [PATCH 7/7] Add test for ZipCompressionMethod.Deflate64 as well --- .../tests/ZipArchive/zip_ReadTests.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/libraries/System.IO.Compression/tests/ZipArchive/zip_ReadTests.cs b/src/libraries/System.IO.Compression/tests/ZipArchive/zip_ReadTests.cs index a6b038c8a2ce1b..5218765645a3a7 100644 --- a/src/libraries/System.IO.Compression/tests/ZipArchive/zip_ReadTests.cs +++ b/src/libraries/System.IO.Compression/tests/ZipArchive/zip_ReadTests.cs @@ -878,5 +878,16 @@ public static async Task CompressionMethod_EmptyFile_ReturnsStored(bool async) Assert.Equal(ZipCompressionMethod.Stored, readEntry.CompressionMethod); await DisposeZipArchive(async, readArchive); } + + [Theory] + [MemberData(nameof(Get_Booleans_Data))] + public static async Task CompressionMethod_Deflate64_ReturnsDeflate64(bool async) + { + MemoryStream ms = await StreamHelpers.CreateTempCopyStream(compat("deflate64.zip")); + ZipArchive readArchive = await CreateZipArchive(async, ms, ZipArchiveMode.Read); + ZipArchiveEntry readEntry = readArchive.Entries[0]; + Assert.Equal(ZipCompressionMethod.Deflate64, readEntry.CompressionMethod); + await DisposeZipArchive(async, readArchive); + } } }