Skip to content

Commit

Permalink
Merge pull request #596 from DannyBoyk/issue_595_conditionally_read_z…
Browse files Browse the repository at this point in the history
…ip64_extra

Conditionally parse Zip64 extra field based on specification
  • Loading branch information
adamhathcock committed Jun 4, 2021
2 parents a34f5a8 + fdc33e9 commit e07046a
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 e07046a

Please sign in to comment.