Skip to content

Commit

Permalink
Add zip64 support for ZipArchive extraction
Browse files Browse the repository at this point in the history
  • Loading branch information
Mark Rydstrom committed Jan 24, 2017
1 parent 8e51d9d commit 6be6ef0
Show file tree
Hide file tree
Showing 10 changed files with 177 additions and 41 deletions.
10 changes: 10 additions & 0 deletions src/SharpCompress/Common/Zip/Headers/DirectoryEndHeader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,5 +48,15 @@ internal override void Write(BinaryWriter writer)
public byte[] Comment { get; private set; }

public ushort TotalNumberOfEntries { get; private set; }

public bool IsZip64
{
get
{
return TotalNumberOfEntriesInDisk == ushort.MaxValue
|| DirectorySize == uint.MaxValue
|| DirectoryStartOffsetRelativeToDisk == uint.MaxValue;
}
}
}
}
54 changes: 54 additions & 0 deletions src/SharpCompress/Common/Zip/Headers/Zip64DirectoryEndHeader.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
using System;
using System.IO;

namespace SharpCompress.Common.Zip.Headers
{
internal class Zip64DirectoryEndHeader : ZipHeader
{
public Zip64DirectoryEndHeader()
: base(ZipHeaderType.Zip64DirectoryEnd)
{
}

internal override void Read(BinaryReader reader)
{
SizeOfDirectoryEndRecord = (long)reader.ReadUInt64();
VersionMadeBy = reader.ReadUInt16();
VersionNeededToExtract = reader.ReadUInt16();
VolumeNumber = reader.ReadUInt32();
FirstVolumeWithDirectory = reader.ReadUInt32();
TotalNumberOfEntriesInDisk = (long)reader.ReadUInt64();
TotalNumberOfEntries = (long)reader.ReadUInt64();
DirectorySize = (long)reader.ReadUInt64();
DirectoryStartOffsetRelativeToDisk = (long)reader.ReadUInt64();
DataSector = reader.ReadBytes((int)(SizeOfDirectoryEndRecord - SizeOfFixedHeaderDataExceptSignatureAndSizeFields));
}

const int SizeOfFixedHeaderDataExceptSignatureAndSizeFields = 44;

internal override void Write(BinaryWriter writer)
{
throw new System.NotImplementedException();
}

public long SizeOfDirectoryEndRecord { get; private set; }

public ushort VersionMadeBy { get; private set; }

public ushort VersionNeededToExtract { get; private set; }

public uint VolumeNumber { get; private set; }

public uint FirstVolumeWithDirectory { get; private set; }

public long TotalNumberOfEntriesInDisk { get; private set; }

public long TotalNumberOfEntries { get; private set; }

public long DirectorySize { get; private set; }

public long DirectoryStartOffsetRelativeToDisk { get; private set; }

public byte[] DataSector { get; private set; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
using System.IO;

namespace SharpCompress.Common.Zip.Headers
{
internal class Zip64DirectoryEndLocatorHeader : ZipHeader
{
public Zip64DirectoryEndLocatorHeader()
: base(ZipHeaderType.Zip64DirectoryEndLocator)
{
}

internal override void Read(BinaryReader reader)
{
FirstVolumeWithDirectory = reader.ReadUInt32();
RelativeOffsetOfTheEndOfDirectoryRecord = (long)reader.ReadUInt64();
TotalNumberOfVolumes = reader.ReadUInt32();
}

internal override void Write(BinaryWriter writer)
{
throw new System.NotImplementedException();
}

public uint FirstVolumeWithDirectory { get; private set; }

public long RelativeOffsetOfTheEndOfDirectoryRecord { get; private set; }

public uint TotalNumberOfVolumes { get; private set; }
}
}
6 changes: 4 additions & 2 deletions src/SharpCompress/Common/Zip/Headers/ZipFileEntry.cs
Original file line number Diff line number Diff line change
Expand Up @@ -57,11 +57,11 @@ protected byte[] EncodeString(string str)

internal ZipCompressionMethod CompressionMethod { get; set; }

internal uint CompressedSize { get; set; }
internal long CompressedSize { get; set; }

internal long? DataStartPosition { get; set; }

internal uint UncompressedSize { get; set; }
internal long UncompressedSize { get; set; }

internal List<ExtraData> Extra { get; set; }

Expand Down Expand Up @@ -112,5 +112,7 @@ protected void LoadExtra(byte[] extra)
}

internal ZipFilePart Part { get; set; }

internal bool IsZip64 { get { return CompressedSize == uint.MaxValue; } }
}
}
4 changes: 3 additions & 1 deletion src/SharpCompress/Common/Zip/Headers/ZipHeaderType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ internal enum ZipHeaderType
LocalEntry,
DirectoryEntry,
DirectoryEnd,
Split
Split,
Zip64DirectoryEnd,
Zip64DirectoryEndLocator
}
}
77 changes: 50 additions & 27 deletions src/SharpCompress/Common/Zip/SeekableZipHeaderFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ namespace SharpCompress.Common.Zip
internal class SeekableZipHeaderFactory : ZipHeaderFactory
{
private const int MAX_ITERATIONS_FOR_DIRECTORY_HEADER = 4096;
private bool zip64;

internal SeekableZipHeaderFactory(string password)
: base(StreamingMode.Seekable, password)
Expand All @@ -17,40 +18,39 @@ internal SeekableZipHeaderFactory(string password)

internal IEnumerable<DirectoryEntryHeader> ReadSeekableHeader(Stream stream)
{
long offset = 0;
uint signature;
BinaryReader reader = new BinaryReader(stream);

int iterationCount = 0;
do
{
if ((stream.Length + offset) - 4 < 0)
{
throw new ArchiveException("Failed to locate the Zip Header");
}
stream.Seek(offset - 4, SeekOrigin.End);
signature = reader.ReadUInt32();
offset--;
iterationCount++;
if (iterationCount > MAX_ITERATIONS_FOR_DIRECTORY_HEADER)
{
throw new ArchiveException(
"Could not find Zip file Directory at the end of the file. File may be corrupted.");
}
}
while (signature != DIRECTORY_END_HEADER_BYTES);
var reader = new BinaryReader(stream);

SeekBackToHeader(stream, reader, DIRECTORY_END_HEADER_BYTES);
var entry = new DirectoryEndHeader();
entry.Read(reader);
stream.Seek(entry.DirectoryStartOffsetRelativeToDisk, SeekOrigin.Begin);

DirectoryEntryHeader directoryEntryHeader = null;
if (entry.IsZip64)
{
zip64 = true;
SeekBackToHeader(stream, reader, ZIP64_END_OF_CENTRAL_DIRECTORY_LOCATOR);
var zip64Locator = new Zip64DirectoryEndLocatorHeader();
zip64Locator.Read(reader);

stream.Seek(zip64Locator.RelativeOffsetOfTheEndOfDirectoryRecord, SeekOrigin.Begin);
uint zip64Signature = reader.ReadUInt32();
if(zip64Signature != ZIP64_END_OF_CENTRAL_DIRECTORY)
throw new ArchiveException("Failed to locate the Zip64 Header");

var zip64Entry = new Zip64DirectoryEndHeader();
zip64Entry.Read(reader);
stream.Seek(zip64Entry.DirectoryStartOffsetRelativeToDisk, SeekOrigin.Begin);
}
else
{
stream.Seek(entry.DirectoryStartOffsetRelativeToDisk, SeekOrigin.Begin);
}

long position = stream.Position;
while (true)
{
stream.Position = position;
signature = reader.ReadUInt32();
directoryEntryHeader = ReadHeader(signature, reader) as DirectoryEntryHeader;
uint signature = reader.ReadUInt32();
var directoryEntryHeader = ReadHeader(signature, reader, zip64) as DirectoryEntryHeader;
position = stream.Position;
if (directoryEntryHeader == null)
{
Expand All @@ -63,12 +63,35 @@ internal IEnumerable<DirectoryEntryHeader> ReadSeekableHeader(Stream stream)
}
}

private static void SeekBackToHeader(Stream stream, BinaryReader reader, uint headerSignature)
{
long offset = 0;
uint signature;
int iterationCount = 0;
do
{
if ((stream.Length + offset) - 4 < 0)
{
throw new ArchiveException("Failed to locate the Zip Header");
}
stream.Seek(offset - 4, SeekOrigin.End);
signature = reader.ReadUInt32();
offset--;
iterationCount++;
if (iterationCount > MAX_ITERATIONS_FOR_DIRECTORY_HEADER)
{
throw new ArchiveException("Could not find Zip file Directory at the end of the file. File may be corrupted.");
}
}
while (signature != headerSignature);
}

internal LocalEntryHeader GetLocalHeader(Stream stream, DirectoryEntryHeader directoryEntryHeader)
{
stream.Seek(directoryEntryHeader.RelativeOffsetOfEntryHeader, SeekOrigin.Begin);
BinaryReader reader = new BinaryReader(stream);
uint signature = reader.ReadUInt32();
var localEntryHeader = ReadHeader(signature, reader) as LocalEntryHeader;
var localEntryHeader = ReadHeader(signature, reader, zip64) as LocalEntryHeader;
if (localEntryHeader == null)
{
throw new InvalidOperationException();
Expand Down
3 changes: 2 additions & 1 deletion src/SharpCompress/Common/Zip/ZipFilePart.cs
Original file line number Diff line number Diff line change
Expand Up @@ -133,8 +133,9 @@ protected Stream GetCryptoStream(Stream plainStream)
throw new NotSupportedException("Cannot encrypt file with unknown size at start.");
}

if ((Header.CompressedSize == 0)
if ((Header.CompressedSize == 0
&& FlagUtility.HasFlag(Header.Flags, HeaderFlags.UsePostDataDescriptor))
|| Header.IsZip64)
{
plainStream = new NonDisposingStream(plainStream); //make sure AES doesn't close
}
Expand Down
21 changes: 12 additions & 9 deletions src/SharpCompress/Common/Zip/ZipHeaderFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ internal class ZipHeaderFactory
internal const uint DIGITAL_SIGNATURE = 0x05054b50;
internal const uint SPLIT_ARCHIVE_HEADER_BYTES = 0x30304b50;

private const uint ZIP64_END_OF_CENTRAL_DIRECTORY = 0x06064b50;
private const uint ZIP64_END_OF_CENTRAL_DIRECTORY_LOCATOR = 0x07064b50;
internal const uint ZIP64_END_OF_CENTRAL_DIRECTORY = 0x06064b50;
internal const uint ZIP64_END_OF_CENTRAL_DIRECTORY_LOCATOR = 0x07064b50;

protected LocalEntryHeader lastEntryHeader;
private readonly string password;
Expand All @@ -30,7 +30,7 @@ protected ZipHeaderFactory(StreamingMode mode, string password)
this.password = password;
}

protected ZipHeader ReadHeader(uint headerBytes, BinaryReader reader)
protected ZipHeader ReadHeader(uint headerBytes, BinaryReader reader, bool zip64 = false)
{
switch (headerBytes)
{
Expand All @@ -54,14 +54,12 @@ protected ZipHeader ReadHeader(uint headerBytes, BinaryReader reader)
if (FlagUtility.HasFlag(lastEntryHeader.Flags, HeaderFlags.UsePostDataDescriptor))
{
lastEntryHeader.Crc = reader.ReadUInt32();
lastEntryHeader.CompressedSize = reader.ReadUInt32();
lastEntryHeader.UncompressedSize = reader.ReadUInt32();
lastEntryHeader.CompressedSize = zip64 ? (long)reader.ReadUInt64() : reader.ReadUInt32();
lastEntryHeader.UncompressedSize = zip64 ? (long)reader.ReadUInt64() : reader.ReadUInt32();
}
else
{
reader.ReadUInt32();
reader.ReadUInt32();
reader.ReadUInt32();
reader.ReadBytes(zip64 ? 20 : 12);
}
return null;
}
Expand All @@ -78,9 +76,14 @@ protected ZipHeader ReadHeader(uint headerBytes, BinaryReader reader)
return new SplitHeader();
}
case ZIP64_END_OF_CENTRAL_DIRECTORY:
{
var entry = new Zip64DirectoryEndHeader();
entry.Read(reader);
return entry;
}
case ZIP64_END_OF_CENTRAL_DIRECTORY_LOCATOR:
{
var entry = new IgnoreHeader(ZipHeaderType.Ignore);
var entry = new Zip64DirectoryEndLocatorHeader();
entry.Read(reader);
return entry;
}
Expand Down
13 changes: 12 additions & 1 deletion test/SharpCompress.Test/Zip/ZipArchiveTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ public void Zip_ZipX_ArchiveStreamRead()
ArchiveStreamRead("Zip.zipx");
}


[Fact]
public void Zip_BZip2_Streamed_ArchiveStreamRead()
{
Expand Down Expand Up @@ -130,6 +129,18 @@ public void Zip_None_ArchiveFileRead()
ArchiveFileRead("Zip.none.zip");
}

[Fact]
public void Zip_Zip64_ArchiveStreamRead()
{
ArchiveStreamRead("Zip.zip64.zip");
}

[Fact]
public void Zip_Zip64_ArchiveFileRead()
{
ArchiveFileRead("Zip.zip64.zip");
}

[Fact]
public void Zip_Random_Write_Remove()
{
Expand Down
Binary file added test/TestArchives/Archives/Zip.zip64.zip
Binary file not shown.

0 comments on commit 6be6ef0

Please sign in to comment.