Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add zip64 support for ZipArchive extraction #205

Merged
merged 1 commit into from
Jan 24, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.