haf / DotNetZip.Semverd

Please use System.IO.Compression! A fork of the DotNetZip project without signing with a solution that compiles cleanly. This project aims to follow semver to avoid versioning conflicts. DotNetZip is a FAST, FREE class library and toolset for manipulating zip files. Use VB, C# or any .NET language to easily create, extract, or update zip files.
Other
545 stars 218 forks source link

Zip64 writes incorrect central directory file header/extended information extra field #260

Open BhaaLseN opened 2 years ago

BhaaLseN commented 2 years ago

I noticed that some of my files couldn't be extracted properly using System.IO.Compression, and after investigating it seems that it discards the extra field for being of unexpected size. This affects anything zip64; which is practically either zip files above 4gb size, files inside the zip file above 4gb size and/or files above the 4gb boundary (where the relative offsets cannot be represented anymore in just 4 bytes).

Other tools (such as recent 7zip) also note inconsistencies, but they often just show warnings or ignore the issue altogether; and read the file/extract it correctly anyways.

To reproduce, simply create a zip file and force zip64 to be used (the actual size doesn't matter for this, since it has the same issue as large files):

// <PackageReference Include="DotNetZip" Version="1.16.0" />
using DNZip = Ionic.Zip.ZipFile;
using SICZip = System.IO.Compression.ZipFile;

const string targetZip = "test.zip";
if (!File.Exists(targetZip))
{
    using var czip = new DNZip { UseZip64WhenSaving = Ionic.Zip.Zip64Option.Always };
    czip.AddEntry("SingleFile", (fn, s) => s.WriteByte(0));
    czip.Save(targetZip);
}

using var zip = SICZip.OpenRead(targetZip);
var singleFile = zip.Entries.First();
_ = singleFile.CompressedLength; // this is returned as 0x0000_0000_ffff_ffff because the extra field is ignored
//using var _ = singleFile.Open(); // throws System.IO.InvalidDataException (A local file header is corrupt)
//SICZip.ExtractToDirectory(targetZip, "test"); // throws System.IO.InvalidDataException (A local file header is corrupt)

When staring at the file (for almost too long), it turns out that the compressed, uncompressed file size and relative header offset are set to 0xffff_ffff in the central directory file header; but the extra field has size 0x1c (28, which means it includes the disk start number). This extra field repeats the disk start number as zero (which is not set to 0xffff in the central directory file header).

All but the most recent version of System.IO.Compression have this piece of validation code that requires the fields to conform to the PKWare specification (which must have the disk start number as 0xffff in the regular record, or omit it from the extra field): https://github.com/dotnet/runtime/blob/83adfae6a6273d8fb4c69554aa3b1cc7cbf01c71/src/libraries/System.IO.Compression/src/System/IO/Compression/ZipBlocks.cs#L205-L206

They expect that extra field to be 0x18 (24), and ignore it altogether.

This was since addressed by dotnet/runtime#68106 to fix dotnet/runtime#49580; but it doesn't seem to have landed in any public release yet (at least none that aren't previews).

DotNetZip on the other hand should either:

  1. set the central directory file header field for the disk start number to 0xffff and keep the extra field as-is, or
  2. don't write the disk start number to the extra field.