Skip to content

Commit

Permalink
Zip: omit Disk Start Number from the Zip64 Central Directory Entry
Browse files Browse the repository at this point in the history
Clause 4.5.3 of the PKWare spec clearly states that it MUST only appear in
the Extra block when the corresponding field in the CDE (or LDE) was set to
-1 (0xFFFF in the case of the Disk Start Number.)

Previously, this would only be the case when Zip64 was presumed or the disk
number was actually outside the range for the CDE. In every other case, the
Disk Start Number would go into both locations, violating the spec.

Some tools (such as 7-Zip) show warnings in this case, but often work as
expected. Others (such as older versions of System.IO.Compression) follow
the spec more stringently and will refuse to use the values from the Zip64
Extra block when the related values do not match their expectation.

However, fixing this to always write a conformant CDE that has its Disk
Start Number as 0xFFFF with the actual value in the Extra block breaks other
tools (such as marmelroy/Zip on iOS) that do not fully support Zip64.

The spec does allow the Extra block to omit the Disk Start Number (and the
Relativ Header Offset) when they fit in the CDE, so we do just that for
general compatibility.

Fixes haf#260
  • Loading branch information
BhaaLseN committed Mar 10, 2023
1 parent 42a096d commit e34edb6
Showing 1 changed file with 25 additions and 9 deletions.
34 changes: 25 additions & 9 deletions src/Zip.Shared/ZipEntry.Write.cs
Original file line number Diff line number Diff line change
Expand Up @@ -230,11 +230,11 @@ this file starts
* that have a header set in Zip64, but doesn't have -1 value in
* appropriate fields of the record itself.
*
* Currently we always set 'Relative Header' and 'Disk Start' inside
* the Extra block for Zip64, meaning that we also need to make sure
* that their non-Zip64 counterparts would be -1 (0xFFFFFFFF
* and 0xFFFF respectively), regardless of the fact if the value fits
* into uint32/uint16 range or not.
* Currently we always set 'Relative Header' inside the Extra block
* for Zip64 and 'Disk Start' when necessary, meaning that we also
* need to make sure that their non-Zip64 counterparts would be -1
* (0xFFFFFFFF and 0xFFFF respectively), regardless of the fact if
* the value fits into uint32/uint16 range or not.
*/
bytes[i++] = 0xFF;
bytes[i++] = 0xFF;
Expand Down Expand Up @@ -384,7 +384,20 @@ private byte[] ConstructExtraField(bool forCentralDirectory)
{
// add extra field for zip64 here
// workitem 7924
int sz = 4 + (forCentralDirectory ? 28 : 16);
int sz = 4 + (forCentralDirectory ? 24 : 16);
// for segmented ZIP, the disk number goes into the extra field
// and ends up as 0xFFFF in the central directory.
// this should match WriteCentralDirectoryEntry to avoid issues
// with tools and libraries that do not fully support Zip64
// (by not putting the disk number in the extra field when
// it can be avoided.)
bool segmented = (this._container.ZipFile != null) &&
(this._container.ZipFile.MaxOutputSegmentSize64 != 0);
bool diskNumberInExtraField = segmented &&
(_presumeZip64 || _diskNumber > 0xFFFF);
if (forCentralDirectory && diskNumberInExtraField)
sz += 4;

block = new byte[sz];
int i = 0;

Expand All @@ -402,7 +415,7 @@ private byte[] ConstructExtraField(bool forCentralDirectory)
}

// DataSize
block[i++] = (byte)(sz - 4); // decimal 28 or 16 (workitem 7924)
block[i++] = (byte)(sz - 4); // decimal 24/28 or 16 (workitem 7924)
block[i++] = 0x00;

// The actual metadata - we may or may not have real values yet...
Expand All @@ -423,8 +436,11 @@ private byte[] ConstructExtraField(bool forCentralDirectory)
Array.Copy(BitConverter.GetBytes(_RelativeOffsetOfLocalHeader), 0, block, i, 8);
i += 8;

// starting disk number
Array.Copy(BitConverter.GetBytes(_diskNumber), 0, block, i, 4);
if (diskNumberInExtraField)
{
// starting disk number
Array.Copy(BitConverter.GetBytes(_diskNumber), 0, block, i, 4);
}
}

listOfBlocks.Add(block);
Expand Down

0 comments on commit e34edb6

Please sign in to comment.