|
| 1 | +--- |
| 2 | +title: "Breaking change: Adding a ZipArchiveEntry with CompressionLevel sets ZIP central directory header general-purpose bit flags" |
| 3 | +description: Learn about the .NET 9 breaking change in core .NET libraries where adding a ZipArchiveEntry with a specified CompressionLevel sets the ZIP central directory header general-purpose bit flags. |
| 4 | +ms.date: 06/03/2024 |
| 5 | +--- |
| 6 | +# Adding a ZipArchiveEntry with CompressionLevel sets ZIP central directory header general-purpose bit flags |
| 7 | + |
| 8 | +The [ZIP file specification](https://pkware.cachefly.net/webdocs/APPNOTE/APPNOTE_6.2.0.txt) 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. |
| 9 | + |
| 10 | +.NET Framework sets these bits when generating the ZIP files underpinning the <xref:System.IO.Packaging.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 <xref:System.IO.Packaging.CompressionOption> when calling <xref:System.IO.Compression.ZipArchive.CreateEntry%2A?displayProperty=nameWithType> will see the general-purpose bit flag values change. |
| 11 | + |
| 12 | +## Previous behavior |
| 13 | + |
| 14 | +Previously, .NET preserved the general-purpose bits for every <xref:System.IO.Compression.ZipArchiveEntry> already in a <xref:System.IO.Compression.ZipArchive> when it was loaded and new entries were added. However, calling <xref:System.IO.Compression.ZipArchive.CreateEntry(System.String,System.IO.Compression.CompressionLevel)?displayProperty=nameWithType> always resulted in bits 1 & 2 being left at a default value of `0`, even if a <xref:System.IO.Compression.CompressionLevel> other than <xref:System.IO.Compression.CompressionLevel.Optimal?displayProperty=nameWithType> was used. |
| 15 | + |
| 16 | +This behavior had a downstream effect: calling <xref:System.IO.Packaging.Package.CreatePart(System.Uri,System.String,System.IO.Packaging.CompressionOption)?displayProperty=nameWithType> with any <xref:System.IO.Packaging.CompressionOption> resulted in bits 1 & 2 being left unset (and thus the <xref:System.IO.Packaging.CompressionOption> was always persisted to the ZIP file as <xref:System.IO.Packaging.CompressionOption.Normal?displayProperty=nameWithType>). |
| 17 | + |
| 18 | +## New behavior |
| 19 | + |
| 20 | +Starting in .NET 9, the <xref:System.IO.Compression.CompressionLevel> parameter is mapped to the general-purpose bit flags as indicated in the following table. |
| 21 | + |
| 22 | +| `CompressionLevel` | Bit 1 | Bit 2 | |
| 23 | +|--------------------|-------|-------| |
| 24 | +| `NoCompression` | 0 | 0 | |
| 25 | +| `Optimal` | 0 | 0 | |
| 26 | +| `SmallestSize` | 1 | 0 | |
| 27 | +| `Fastest` | 1 | 1 | |
| 28 | + |
| 29 | +If you call <xref:System.IO.Compression.ZipArchive.CreateEntry(System.String,System.IO.Compression.CompressionLevel)?displayProperty=nameWithType> and specify <xref:System.IO.Compression.CompressionLevel.NoCompression?displayProperty=nameWithType>, the nested file record's compression method is set to `Stored` (rather than the default value of `Deflate`.) |
| 30 | + |
| 31 | +The general-purpose bits for `ZipArchiveEntry` records that already exist in a `ZipArchive` are still preserved if a new `ZipArchiveEntry` is added. |
| 32 | + |
| 33 | +The `CompressionOption` enumeration values in <xref:System.IO.Packaging.Package.CreatePart(System.Uri,System.String,System.IO.Packaging.CompressionOption)?displayProperty=nameWithType> are mapped to `CompressionLevel` (and result in the corresponding bits being set) as shown in the following table. |
| 34 | + |
| 35 | +| `CompressionOption` | `CompressionLevel` | |
| 36 | +|---------------------|------------------------------------------------------| |
| 37 | +| `NotCompressed` | `NoCompression` | |
| 38 | +| `Normal` | `Optimal` | |
| 39 | +| `Maximum` | `SmallestSize` (.NET Framework)<br/>`Optimal` (.NET) | |
| 40 | +| `Fast` | `Fastest` | |
| 41 | +| `SuperFast` | `Fastest` | |
| 42 | + |
| 43 | +## Version introduced |
| 44 | + |
| 45 | +.NET 9 Preview 5 |
| 46 | + |
| 47 | +## Type of breaking change |
| 48 | + |
| 49 | +This change is a [behavioral change](../../categories.md#behavioral-change). |
| 50 | + |
| 51 | +## Reason for change |
| 52 | + |
| 53 | +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 <xref:System.IO.Packaging>) control over the value of these bits. |
| 54 | + |
| 55 | +## Recommended action |
| 56 | + |
| 57 | +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 <xref:System.IO.Compression.ZipArchive.CreateEntry(System.String,System.IO.Compression.CompressionLevel)?displayProperty=nameWithType>. |
| 58 | + |
| 59 | +If you use <xref:System.IO.Packaging.Package.CreatePart(System.Uri,System.String,System.IO.Packaging.CompressionOption)?displayProperty=nameWithType> to add parts to an OPC package, specify a `CompressionOption` of `CompressionOption.NotCompressed` or `CompressionOption.Normal`. |
| 60 | + |
| 61 | +## Affected APIs |
| 62 | + |
| 63 | +- <xref:System.IO.Compression.ZipArchive.CreateEntry(System.String,System.IO.Compression.CompressionLevel)?displayProperty=fullName> |
| 64 | +- <xref:System.IO.Packaging.Package.CreatePart(System.Uri,System.String,System.IO.Packaging.CompressionOption)?displayProperty=fullName> |
0 commit comments