docs icon indicating copy to clipboard operation
docs copied to clipboard

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

Open edwardneal opened this issue 1 year ago • 4 comments

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.
  • [X] 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

edwardneal avatar Apr 02 '24 22:04 edwardneal

@edwardneal Is this really going to make it into Preview 3 that releases next Tuesday?

gewarren avatar Apr 02 '24 23:04 gewarren

@gewarren I'm not sure, but I think it's unlikely - I've created this issue towards the conclusion of the PR merge process, and chose Preview 3 because it was the next available preview release. I think it'll probably be in Preview 4 or later, given the timing; I've corrected my first post.

edwardneal avatar Apr 03 '24 06:04 edwardneal

The snap for Preview 4 happened already. Maybe dotnet/runtime#98278 will make it in for Preview 5.

gewarren avatar May 04 '24 00:05 gewarren

The PR referenced has now been merged in commit 6cc6c66.

edwardneal avatar May 10 '24 17:05 edwardneal