Adding a ZipArchiveEntry with CompressionLevel sets ZIP central directory header general-purpose bit flags
The ZIP file specification defines that bits 1 & 2 of the general-purpose bit flag in a nested file record's central directory header should be used to indicate the compression level of the nested file.
.NET Framework sets these bits when generating the ZIP files underpinning the ZipPackage API. During the migration of .NET Framework code to .NET, this functionality was lost and in .NET, bits 1 & 2 were always set to 0
when new file records were created within the ZIP file. This breaking change restores that capability. However, existing .NET clients that specify a CompressionOption when calling ZipArchive.CreateEntry will see the general-purpose bit flag values change.
Previous behavior
Previously, .NET preserved the general-purpose bits for every ZipArchiveEntry already in a ZipArchive when it was loaded and new entries were added. However, calling ZipArchive.CreateEntry(String, CompressionLevel) always resulted in bits 1 & 2 being left at a default value of 0
, even if a CompressionLevel other than CompressionLevel.Optimal was used.
This behavior had a downstream effect: calling Package.CreatePart(Uri, String, CompressionOption) with any CompressionOption resulted in bits 1 & 2 being left unset (and thus the CompressionOption was always persisted to the ZIP file as CompressionOption.Normal).
New behavior
Starting in .NET 9, the CompressionLevel parameter is mapped to the general-purpose bit flags as indicated in the following table.
CompressionLevel |
Bit 1 | Bit 2 |
---|---|---|
NoCompression |
0 | 0 |
Optimal |
0 | 0 |
SmallestSize |
1 | 0 |
Fastest |
1 | 1 |
If you call ZipArchive.CreateEntry(String, CompressionLevel) and specify CompressionLevel.NoCompression, the nested file record's compression method is set to Stored
(rather than the default value of Deflate
.)
The general-purpose bits for ZipArchiveEntry
records that already exist in a ZipArchive
are still preserved if a new ZipArchiveEntry
is added.
The CompressionOption
enumeration values in Package.CreatePart(Uri, String, CompressionOption) are mapped to CompressionLevel
(and result in the corresponding bits being set) as shown in the following table.
CompressionOption |
CompressionLevel |
---|---|
NotCompressed |
NoCompression |
Normal |
Optimal |
Maximum |
SmallestSize (.NET Framework)Optimal (.NET) |
Fast |
Fastest |
SuperFast |
Fastest |
Version introduced
.NET 9 Preview 5
Type of breaking change
This change is a behavioral change.
Reason for change
This breaking change was introduced to restore the existing .NET Framework behavior, which was omitted from .NET at the point of porting. This change also gives downstream clients (such as System.IO.Packaging) control over the value of these bits.
Recommended action
If you want to ensure that new ZipArchiveEntry
records added to the ZipArchive
have general-purpose bit flags of 0
, specify a CompressionLevel
of CompressionLevel.Optimal
or CompressionLevel.NoCompression
when you call ZipArchive.CreateEntry(String, CompressionLevel).
If you use Package.CreatePart(Uri, String, CompressionOption) to add parts to an OPC package, specify a CompressionOption
of CompressionOption.NotCompressed
or CompressionOption.Normal
.