Skip to content

Commit

Permalink
Conditionally parse Zip64 extra field based on specification
Browse files Browse the repository at this point in the history
The Zip64 extra field should look for values based on the corresponding
values in the local entry header.

Fixes #595
  • Loading branch information
DannyBoyk committed Apr 26, 2021
1 parent a34f5a8 commit fdc33e9
Show file tree
Hide file tree
Showing 6 changed files with 69 additions and 19 deletions.
2 changes: 2 additions & 0 deletions src/SharpCompress/Common/Zip/Headers/DirectoryEntryHeader.cs
Expand Up @@ -63,6 +63,8 @@ internal override void Read(BinaryReader reader)
var zip64ExtraData = Extra.OfType<Zip64ExtendedInformationExtraField>().FirstOrDefault();
if (zip64ExtraData != null)
{
zip64ExtraData.Process(UncompressedSize, CompressedSize, RelativeOffsetOfEntryHeader, DiskNumberStart);

if (CompressedSize == uint.MaxValue)
{
CompressedSize = zip64ExtraData.CompressedSize;
Expand Down
2 changes: 2 additions & 0 deletions src/SharpCompress/Common/Zip/Headers/LocalEntryHeader.cs
Expand Up @@ -53,6 +53,8 @@ internal override void Read(BinaryReader reader)
var zip64ExtraData = Extra.OfType<Zip64ExtendedInformationExtraField>().FirstOrDefault();
if (zip64ExtraData != null)
{
zip64ExtraData.Process(UncompressedSize, CompressedSize, 0, 0);

if (CompressedSize == uint.MaxValue)
{
CompressedSize = zip64ExtraData.CompressedSize;
Expand Down
Expand Up @@ -66,46 +66,74 @@ internal sealed class Zip64ExtendedInformationExtraField : ExtraData
public Zip64ExtendedInformationExtraField(ExtraDataType type, ushort length, byte[] dataBytes)
: base(type, length, dataBytes)
{
Process();
}

private void Process()
// From the spec, values are only in the extradata if the standard
// value is set to 0xFFFFFFFF (or 0xFFFF for the Disk Start Number).
// Values, if present, must appear in the following order:
// - Original Size
// - Compressed Size
// - Relative Header Offset
// - Disk Start Number
public void Process(long uncompressedFileSize, long compressedFileSize, long relativeHeaderOffset, ushort diskNumber)
{
if (DataBytes.Length >= 8)
var bytesRequired = ((uncompressedFileSize == uint.MaxValue) ? 8 : 0)
+ ((compressedFileSize == uint.MaxValue) ? 8 : 0)
+ ((relativeHeaderOffset == uint.MaxValue) ? 8 : 0)
+ ((diskNumber == ushort.MaxValue) ? 4 : 0);
var currentIndex = 0;

if (bytesRequired > DataBytes.Length)
{
UncompressedSize = BinaryPrimitives.ReadInt64LittleEndian(DataBytes);
throw new ArchiveException("Zip64 extended information extra field is not large enough for the required information");
}

if (DataBytes.Length >= 16)
if (uncompressedFileSize == uint.MaxValue)
{
CompressedSize = BinaryPrimitives.ReadInt64LittleEndian(DataBytes.AsSpan(8));
UncompressedSize = BinaryPrimitives.ReadInt64LittleEndian(DataBytes.AsSpan(currentIndex));
currentIndex += 8;
}

if (DataBytes.Length >= 24)
if (compressedFileSize == uint.MaxValue)
{
RelativeOffsetOfEntryHeader = BinaryPrimitives.ReadInt64LittleEndian(DataBytes.AsSpan(16));
CompressedSize = BinaryPrimitives.ReadInt64LittleEndian(DataBytes.AsSpan(currentIndex));
currentIndex += 8;
}

if (DataBytes.Length >= 28)
if (relativeHeaderOffset == uint.MaxValue)
{
VolumeNumber = BinaryPrimitives.ReadUInt32LittleEndian(DataBytes.AsSpan(24));
RelativeOffsetOfEntryHeader = BinaryPrimitives.ReadInt64LittleEndian(DataBytes.AsSpan(currentIndex));
currentIndex += 8;
}

switch (DataBytes.Length)
if (diskNumber == ushort.MaxValue)
{
case 8:
case 16:
case 24:
case 28:
break;
default:
throw new ArchiveException($"Unexpected size of of Zip64 extended information extra field: {DataBytes.Length}");
VolumeNumber = BinaryPrimitives.ReadUInt32LittleEndian(DataBytes.AsSpan(currentIndex));
}
}

/// <summary>
/// Uncompressed file size. Only valid after <see cref="Process(long, long, long, ushort)"/> has been called and if the
/// original entry header had a corresponding 0xFFFFFFFF value.
/// </summary>
public long UncompressedSize { get; private set; }

/// <summary>
/// Compressed file size. Only valid after <see cref="Process(long, long, long, ushort)"/> has been called and if the
/// original entry header had a corresponding 0xFFFFFFFF value.
/// </summary>
public long CompressedSize { get; private set; }

/// <summary>
/// Relative offset of the entry header. Only valid after <see cref="Process(long, long, long, ushort)"/> has been called and if the
/// original entry header had a corresponding 0xFFFFFFFF value.
/// </summary>
public long RelativeOffsetOfEntryHeader { get; private set; }

/// <summary>
/// Volume number. Only valid after <see cref="Process(long, long, long, ushort)"/> has been called and if the
/// original entry header had a corresponding 0xFFFF value.
/// </summary>
public uint VolumeNumber { get; private set; }
}

Expand Down
2 changes: 1 addition & 1 deletion src/SharpCompress/Common/Zip/Headers/ZipFileEntry.cs
Expand Up @@ -105,6 +105,6 @@ protected void LoadExtra(byte[] extra)

internal ZipFilePart Part { get; set; }

internal bool IsZip64 => CompressedSize == uint.MaxValue;
internal bool IsZip64 => CompressedSize >= uint.MaxValue;
}
}
18 changes: 18 additions & 0 deletions tests/SharpCompress.Test/Zip/ZipArchiveTests.cs
Expand Up @@ -576,5 +576,23 @@ public void Zip_LongComment_Read()
Assert.Equal(1, count);
}
}

[Fact]
public void Zip_Zip64_CompressedSizeExtraOnly_Read()
{
string zipPath = Path.Combine(TEST_ARCHIVES_PATH, "Zip.zip64.compressedonly.zip");

using (ZipArchive za = ZipArchive.Open(zipPath))
{
var firstEntry = za.Entries.First(x => x.Key == "test/test.txt");

using (var memoryStream = new MemoryStream())
using (var firstStream = firstEntry.OpenEntryStream())
{
firstStream.CopyTo(memoryStream);
Assert.Equal(15, memoryStream.Length);
}
}
}
}
}
Binary file not shown.

0 comments on commit fdc33e9

Please sign in to comment.