[PR #1023] Fix version mismatch between Local File Header and Central Directory File Header in Zip archives #1446

Closed
opened 2026-01-29 22:20:36 +00:00 by claunia · 0 comments
Owner

Original Pull Request: https://github.com/adamhathcock/sharpcompress/pull/1023

State: closed
Merged: Yes


Plan: Fix Zip64 Version Mismatch Between LFH and CDFH

Problem

When UseZip64=true but files are small (< 4GB), there's a version mismatch:

  • Local File Header (LFH) writes version 45 (Zip64) or 63 (LZMA/PPMd)
  • Central Directory File Header (CDFH) writes version 20 (because actual sizes don't require Zip64)
  • This causes validation failures in System.IO.Packaging and potentially other readers

Root Cause

  • In ZipWriter.WriteHeader(): Version is set based on compression method and Zip64 usage
  • In ZipCentralDirectoryEntry.Write(): Version was set to 45 only when actual sizes/offsets exceed uint.MaxValue, ignoring compression method requirements

Solution

  • Understand the issue and existing code
  • Create tests to validate version consistency (5 comprehensive tests)
  • Fix ZipCentralDirectoryEntry.Write() to properly handle:
    • Version 63 for LZMA/PPMd/BZip2/ZStandard compression
    • Version 45 when Zip64HeaderOffset != 0 or actual sizes require it
    • Version 20 for standard Deflate/None compression
  • Run tests to ensure no regression (all 374 Zip tests pass)
  • Format code with CSharpier
  • Remove ZipCompressionMethod.None from version 63 check (per review feedback)

Changes Made

  1. Modified ZipCentralDirectoryEntry.cs:

    • Updated version calculation to check compression method first
    • Then check if Zip64 was used in LFH (Zip64HeaderOffset != 0)
    • Finally fallback to version 20 for standard compression
  2. Added Zip64VersionConsistencyTests.cs: Created 5 tests

    • Test with small file and UseZip64=true
    • Test with small file and UseZip64=false
    • Test with multiple small files and UseZip64=true
    • Test with LZMA compression (version 63)
    • Test with PPMd compression (version 63)

Test Results

  • All 5 new tests pass
  • All 374 Zip tests pass (including LZMA/PPMd)

This ensures archives created with UseZip64=true or advanced compression methods can be opened by System.IO.Packaging and other strict ZIP readers.

Original prompt

This section details on the original issue you should resolve

<issue_title>Failed validation when using Zip64 with System.IO.Packaging</issue_title>
<issue_description>Hi there, ran into this little problem when using SharpCompress.

A zip archive created with UseZip64 set to true will fail to open using ZipPackage (from System.IO.Packaging) with .NET Framework 4.8.

The exception occurs in ZipIOLocalFileBlock.Validate() here:

if (VersionNeededToExtract != centralDirFileHeader.VersionNeededToExtract || GeneralPurposeBitFlag != centralDirFileHeader.GeneralPurposeBitFlag || CompressedSize != centralDirFileHeader.CompressedSize || UncompressedSize != centralDirFileHeader.UncompressedSize || CompressionMethod != centralDirFileHeader.CompressionMethod || Crc32 != centralDirFileHeader.Crc32)
{
	throw new FileFormatException(SR.Get("CorruptedData"));
}

VersionNeededToExtract value is 45 while centralDirFileHeader.VersionNeededToExtract is 20.
This mismatch seems to come from the fact that in SharpCompress the Local File Header will always have its version set to 45 when UseZip64 is true, but the version in the Central Directory File Header will be 45 only if size/offset is over uint.MaxValue (which makes sense).

Not sure what's the best way to handle this, an easy one would probably be to set both versions to 45 as soon as the flag is true ? (but this may not be optimal/trivial).

In SharpCompress these parts seem to be in ZipWriter.WriteToStream() / ZipWriter.WriteHeader() for the LFH and in ZipCentralDirectoryEntry.Write() for the CDFH.

Small code to reproduce the issue:

using System.IO.Packaging;

using SharpCompress.Archives;
using SharpCompress.Archives.Zip;
using SharpCompress.Common;
using SharpCompress.Writers;
using SharpCompress.Writers.Zip;

WriterOptions writerOptions = new ZipWriterOptions(CompressionType.Deflate)
{
    LeaveStreamOpen = false,
    UseZip64 = true
};
string file = "test_zip64.zip";

ZipArchive zipArchive = ZipArchive.Create();
zipArchive.AddEntry("empty", new MemoryStream());
zipArchive.SaveTo(file, writerOptions);

using var package = ZipPackage.Open(file, FileMode.Open, FileAccess.Read);

Kind of related to adamhathcock/sharpcompress#164</issue_description>

Comments on the Issue (you are @copilot in this section)


💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.

**Original Pull Request:** https://github.com/adamhathcock/sharpcompress/pull/1023 **State:** closed **Merged:** Yes --- ## Plan: Fix Zip64 Version Mismatch Between LFH and CDFH ### Problem When `UseZip64=true` but files are small (< 4GB), there's a version mismatch: - Local File Header (LFH) writes version 45 (Zip64) or 63 (LZMA/PPMd) - Central Directory File Header (CDFH) writes version 20 (because actual sizes don't require Zip64) - This causes validation failures in System.IO.Packaging and potentially other readers ### Root Cause - In `ZipWriter.WriteHeader()`: Version is set based on compression method and Zip64 usage - In `ZipCentralDirectoryEntry.Write()`: Version was set to 45 only when actual sizes/offsets exceed uint.MaxValue, ignoring compression method requirements ### Solution - [x] Understand the issue and existing code - [x] Create tests to validate version consistency (5 comprehensive tests) - [x] Fix `ZipCentralDirectoryEntry.Write()` to properly handle: - Version 63 for LZMA/PPMd/BZip2/ZStandard compression - Version 45 when Zip64HeaderOffset != 0 or actual sizes require it - Version 20 for standard Deflate/None compression - [x] Run tests to ensure no regression (all 374 Zip tests pass) - [x] Format code with CSharpier - [x] Remove `ZipCompressionMethod.None` from version 63 check (per review feedback) ### Changes Made 1. **Modified `ZipCentralDirectoryEntry.cs`**: - Updated version calculation to check compression method first - Then check if Zip64 was used in LFH (Zip64HeaderOffset != 0) - Finally fallback to version 20 for standard compression 2. **Added `Zip64VersionConsistencyTests.cs`**: Created 5 tests - Test with small file and UseZip64=true - Test with small file and UseZip64=false - Test with multiple small files and UseZip64=true - Test with LZMA compression (version 63) - Test with PPMd compression (version 63) ### Test Results - ✅ All 5 new tests pass - ✅ All 374 Zip tests pass (including LZMA/PPMd) This ensures archives created with `UseZip64=true` or advanced compression methods can be opened by System.IO.Packaging and other strict ZIP readers. <!-- START COPILOT CODING AGENT SUFFIX --> <details> <summary>Original prompt</summary> > > ---- > > *This section details on the original issue you should resolve* > > <issue_title>Failed validation when using Zip64 with System.IO.Packaging</issue_title> > <issue_description>Hi there, ran into this little problem when using SharpCompress. > > A zip archive created with `UseZip64` set to `true` will fail to open using `ZipPackage` (from `System.IO.Packaging`) with .NET Framework 4.8. > > The exception occurs in `ZipIOLocalFileBlock.Validate()` here: > ```cs > if (VersionNeededToExtract != centralDirFileHeader.VersionNeededToExtract || GeneralPurposeBitFlag != centralDirFileHeader.GeneralPurposeBitFlag || CompressedSize != centralDirFileHeader.CompressedSize || UncompressedSize != centralDirFileHeader.UncompressedSize || CompressionMethod != centralDirFileHeader.CompressionMethod || Crc32 != centralDirFileHeader.Crc32) > { > throw new FileFormatException(SR.Get("CorruptedData")); > } > ``` > > `VersionNeededToExtract` value is `45` while `centralDirFileHeader.VersionNeededToExtract` is `20`. > This mismatch seems to come from the fact that in SharpCompress the Local File Header will always have its version set to `45` when `UseZip64` is `true`, but the version in the Central Directory File Header will be `45` only if size/offset is over `uint.MaxValue` (which makes sense). > > Not sure what's the best way to handle this, an easy one would probably be to set both versions to `45` as soon as the flag is `true` ? (but this may not be optimal/trivial). > > In SharpCompress these parts seem to be in `ZipWriter.WriteToStream()` / `ZipWriter.WriteHeader()` for the LFH and in `ZipCentralDirectoryEntry.Write()` for the CDFH. > > Small code to reproduce the issue: > ```cs > using System.IO.Packaging; > > using SharpCompress.Archives; > using SharpCompress.Archives.Zip; > using SharpCompress.Common; > using SharpCompress.Writers; > using SharpCompress.Writers.Zip; > > WriterOptions writerOptions = new ZipWriterOptions(CompressionType.Deflate) > { > LeaveStreamOpen = false, > UseZip64 = true > }; > string file = "test_zip64.zip"; > > ZipArchive zipArchive = ZipArchive.Create(); > zipArchive.AddEntry("empty", new MemoryStream()); > zipArchive.SaveTo(file, writerOptions); > > using var package = ZipPackage.Open(file, FileMode.Open, FileAccess.Read); > ``` > > Kind of related to adamhathcock/sharpcompress#164</issue_description> > > ## Comments on the Issue (you are @copilot in this section) > > <comments> > </comments> > </details> - Fixes adamhathcock/sharpcompress#1022 <!-- START COPILOT CODING AGENT TIPS --> --- 💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more [Copilot coding agent tips](https://gh.io/copilot-coding-agent-tips) in the docs.
claunia added the pull-request label 2026-01-29 22:20:36 +00:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: starred/sharpcompress#1446