Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/core/compatibility/9.0.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ If you're migrating an app to .NET 9, the breaking changes listed here might aff

| Title | Type of change | Introduced version |
|------------------------------------------------------------------------------------------|---------------------|--------------------|
| [Adding a ZipArchiveEntry with CompressionLevel sets ZIP central directory header general-purpose bit flags](core-libraries/9.0/compressionlevel-bits.md) | Behavioral change | Preview 5 |
| [API obsoletions with custom diagnostic IDs](core-libraries/9.0/obsolete-apis-with-custom-diagnostics.md) | Source incompatible | Preview 1 |
| [Creating type of array of System.Void not allowed](core-libraries/9.0/type-instance.md) | Behavioral change | Preview 1 |
| [Inline array struct size limit is enforced](core-libraries/9.0/inlinearray-size.md) | Behavioral change | Preview 1 |
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
---
title: "Breaking change: Adding a ZipArchiveEntry with CompressionLevel sets ZIP central directory header general-purpose bit flags"
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.
ms.date: 06/03/2024
---
# Adding a ZipArchiveEntry with CompressionLevel sets ZIP central directory header general-purpose bit flags

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.

.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.

## Previous behavior

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.

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>).

## New behavior

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.

| `CompressionLevel` | Bit 1 | Bit 2 |
|--------------------|-------|-------|
| `NoCompression` | 0 | 0 |
| `Optimal` | 0 | 0 |
| `SmallestSize` | 1 | 0 |
| `Fastest` | 1 | 1 |

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`.)

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 <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.

| `CompressionOption` | `CompressionLevel` |
|---------------------|------------------------------------------------------|
| `NotCompressed` | `NoCompression` |
| `Normal` | `Optimal` |
| `Maximum` | `SmallestSize` (.NET Framework)<br/>`Optimal` (.NET) |
| `Fast` | `Fastest` |
| `SuperFast` | `Fastest` |

## Version introduced

.NET 9 Preview 5

## Type of breaking change

This change is a [behavioral change](../../categories.md#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 <xref: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 <xref:System.IO.Compression.ZipArchive.CreateEntry(System.String,System.IO.Compression.CompressionLevel)?displayProperty=nameWithType>.

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`.

## Affected APIs

- <xref:System.IO.Compression.ZipArchive.CreateEntry(System.String,System.IO.Compression.CompressionLevel)?displayProperty=fullName>
- <xref:System.IO.Packaging.Package.CreatePart(System.Uri,System.String,System.IO.Packaging.CompressionOption)?displayProperty=fullName>
8 changes: 7 additions & 1 deletion docs/core/compatibility/toc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ items:
items:
- name: API obsoletions with custom diagnostic IDs
href: core-libraries/9.0/obsolete-apis-with-custom-diagnostics.md
- name: Adding a ZipArchiveEntry sets header general-purpose bit flags
href: core-libraries/9.0/compressionlevel-bits.md
- name: Creating type of array of System.Void not allowed
href: core-libraries/9.0/type-instance.md
- name: Inline array struct size limit is enforced
Expand Down Expand Up @@ -1159,7 +1161,7 @@ items:
- name: Default ASP.NET Core port changed to 8080
href: containers/8.0/aspnet-port.md
- name: Kerberos package removed from Alpine and Debian images
href: containers/8.0/krb5-libs-package.md
href: containers/8.0/krb5-libs-package.md
- name: libintl package removed from Alpine images
href: containers/8.0/libintl-package.md
- name: Multi-platform container tags are Linux-only
Expand All @@ -1170,6 +1172,10 @@ items:
items:
- name: .NET 9
items:
- name: API obsoletions with custom diagnostic IDs
href: core-libraries/9.0/obsolete-apis-with-custom-diagnostics.md
- name: Adding a ZipArchiveEntry sets header general-purpose bit flags
href: core-libraries/9.0/compressionlevel-bits.md
- name: Creating type of array of System.Void not allowed
href: core-libraries/9.0/type-instance.md
- name: Inline array struct size limit is enforced
Expand Down
12 changes: 6 additions & 6 deletions docs/fundamentals/code-analysis/code-quality-rule-options.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ Each refining option can be configured for all rules, for a category of rules (f

The syntax for configuring an option for *all* rules is as follows:

|Syntax|Example|
|-|-|
| Syntax | Example |
|----------------------------------------------------|--------------------------------------------|
| dotnet_code_quality.\<OptionName> = \<OptionValue> | `dotnet_code_quality.api_surface = public` |

The values for `<OptionName>` are listed under [Options](#options).
Expand All @@ -35,8 +35,8 @@ The values for `<OptionName>` are listed under [Options](#options).

The syntax for configuring an option for a [*category* of rules](categories.md) is as follows:

|Syntax|Example|
|-|-|
| Syntax | Example |
|-----------------------------------------------------------------|---------------------------------------------------|
| dotnet_code_quality.\<RuleCategory>.\<OptionName> = OptionValue | `dotnet_code_quality.Naming.api_surface = public` |

The following table lists the available values for `<RuleCategory>`.
Expand Down Expand Up @@ -65,8 +65,8 @@ The following table lists the available values for `<RuleCategory>`.

The syntax for configuring an option for a *specific* rule is as follows:

|Syntax|Example|
|-|-|
| Syntax | Example |
|--------------------------------------------------------------|---------------------------------------------------|
| dotnet_code_quality.\<RuleId>.\<OptionName> = \<OptionValue> | `dotnet_code_quality.CA1040.api_surface = public` |

## Options
Expand Down