Skip to content

[Breaking change]: Adding a ZipArchiveEntry with a specified CompressionLevel sets ZIP central directory header general-purpose bitflags #40299

@edwardneal

Description

@edwardneal

Description

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.

The .NET Framework would set these bits when generating the ZIP files underpinning the ZipPackage API. During the migration to .NET Core, this functionality was lost and both of these bits were always set to 0 when creating new file records within the ZIP file. This breaking change restores that capability, but means that existing .NET Core clients which specify a CompressionOption when calling ZipArchive.CreateEntry will see the general-purpose bit flag values change.

Version

.NET 9 Preview 5

Previous behavior

.NET Core would preserve the general-purpose bits for every ZipArchiveEntry already in a ZipArchive when this was loaded and new entries were being added. However, calling ZipArchive.CreateEntry(string, CompressionLevel) would always result in bits 1 & 2 being left at a default value of zero, even if a CompressionLevel other than CompressionLevel.Optimal was used.

This had a downstream effect: calling Package.CreatePart(Uri, string, CompressionOption) with any CompressionOption would already result in bits 1 & 2 being left unset (and thus the CompressionOption would always be persisted to the ZIP file as CompressionOption.Normal.)

New behavior

The CompressionLevel parameter is mapped to the general-purpose bit flags, as below.

CompressionLevel Bit1 Bit2
Optimal 0 0
SmallestSize 1 0
Fastest 1 1
NoCompression 0 0

Specifying a CompressionLevel of CompressionLevel.NoCompression to ZipArchive.CreateEntry(string, CompressionLevel) will result in the nested file record's compression method being set to Stored (rather than the default value of Deflate.)

The general-purpose bits for ZipArchiveEntry records which already exist in a ZipArchive continue to be preserved if a new ZipArchiveEntry is added.

The CompressionOption enumeration values in Package.CreatePart(Uri, string, CompressionOption) are mapped to CompressionLevel (and will result in the corresponding bits being set) as below.

CompressionOption CompressionLevel
NotCompressed NoCompression
Normal Optimal
Maximum SmallestSize (.NET Framework) / Optimal (.NET Core)
Fast Fastest
SuperFast Fastest

Type of breaking change

  • Binary incompatible: Existing binaries may encounter a breaking change in behavior, such as failure to load or execute, and if so, require recompilation.
  • Source incompatible: When recompiled using the new SDK or component or to target the new runtime, existing source code may require source changes to compile successfully.
  • Behavioral change: Existing binaries may behave differently at run time.

Reason for change

This breaking change was introduced to restore the existing .NET Framework behaviour which was left out of .NET Core at the point of porting, and to grant downstream clients (such as System.IO.Packaging) control over the value of these bits.

Recommended action

Users who want to ensure that new ZipArchiveEntry records added to the ZipArchive have general-purpose bit flags of zero should specify a CompressionLevel of CompressionLevel.Optimal or CompressionLevel.NoCompression when calling ZipArchive.CreateEntry(string, CompressionLevel).

If users are using Package.CreatePart(Uri, string, CompressionOption) to add part to an OPC package, they should specify a CompressionOption of CompressionOption.NotCompressed or CompressionOption.Normal.

Feature area

Core .NET libraries

Affected APIs

ZipArchive.CreateEntry(string, CompressionLevel)

Package.CreatePart(Uri, string, CompressionOption)


Associated WorkItem - 240288

Metadata

Metadata

Assignees

Labels

📌 seQUESTeredIdentifies that an issue has been imported into Quest.breaking-changeIndicates a .NET Core breaking change

Type

No type

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions