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

Propsal for handling Zip with long comment #570

Merged
merged 2 commits into from Feb 14, 2021
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
69 changes: 52 additions & 17 deletions src/SharpCompress/Common/Zip/SeekableZipHeaderFactory.cs
Expand Up @@ -8,7 +8,10 @@ namespace SharpCompress.Common.Zip
{
internal sealed class SeekableZipHeaderFactory : ZipHeaderFactory
{
private const int MAX_ITERATIONS_FOR_DIRECTORY_HEADER = 4096;
private const int MINIMUM_EOCD_LENGTH = 22;
private const int ZIP64_EOCD_LENGTH = 20;
// Comment may be within 64kb + structure 22 bytes
private const int MAX_SEARCH_LENGTH_FOR_EOCD = 65557;
private bool _zip64;

internal SeekableZipHeaderFactory(string? password, ArchiveEncoding archiveEncoding)
Expand All @@ -20,14 +23,24 @@ internal IEnumerable<ZipHeader> ReadSeekableHeader(Stream stream)
{
var reader = new BinaryReader(stream);

SeekBackToHeader(stream, reader, DIRECTORY_END_HEADER_BYTES);
SeekBackToHeader(stream, reader);

var eocd_location = stream.Position;
var entry = new DirectoryEndHeader();
entry.Read(reader);

if (entry.IsZip64)
{
_zip64 = true;
SeekBackToHeader(stream, reader, ZIP64_END_OF_CENTRAL_DIRECTORY_LOCATOR);

// ZIP64_END_OF_CENTRAL_DIRECTORY_LOCATOR should be before the EOCD
stream.Seek(eocd_location - ZIP64_EOCD_LENGTH - 4, SeekOrigin.Begin);
uint zip64_locator = reader.ReadUInt32();
if( zip64_locator != ZIP64_END_OF_CENTRAL_DIRECTORY_LOCATOR )
{
throw new ArchiveException("Failed to locate the Zip64 Directory Locator");
}

var zip64Locator = new Zip64DirectoryEndLocatorHeader();
zip64Locator.Read(reader);

Expand Down Expand Up @@ -73,27 +86,49 @@ internal IEnumerable<ZipHeader> ReadSeekableHeader(Stream stream)
}
}

private static void SeekBackToHeader(Stream stream, BinaryReader reader, uint headerSignature)
private static bool IsMatch( byte[] haystack, int position, byte[] needle)
{
long offset = 0;
uint signature;
int iterationCount = 0;
do
for( int i = 0; i < needle.Length; i++ )
{
if ((stream.Length + offset) - 4 < 0)
if( haystack[ position + i ] != needle[ i ] )
{
throw new ArchiveException("Failed to locate the Zip Header");
return false;
}
stream.Seek(offset - 4, SeekOrigin.End);
signature = reader.ReadUInt32();
offset--;
iterationCount++;
if (iterationCount > MAX_ITERATIONS_FOR_DIRECTORY_HEADER)
}

return true;
}
private static void SeekBackToHeader(Stream stream, BinaryReader reader)
{
// Minimum EOCD length
if (stream.Length < MINIMUM_EOCD_LENGTH)
{
throw new ArchiveException("Could not find Zip file Directory at the end of the file. File may be corrupted.");
}

int len = stream.Length < MAX_SEARCH_LENGTH_FOR_EOCD ? (int)stream.Length : MAX_SEARCH_LENGTH_FOR_EOCD;
// We search for marker in reverse to find the first occurance
byte[] needle = { 0x06, 0x05, 0x4b, 0x50 };

stream.Seek(-len, SeekOrigin.End);

byte[] seek = reader.ReadBytes(len);

// Search in reverse
Array.Reverse(seek);

var max_search_area = len - MINIMUM_EOCD_LENGTH;

for( int pos_from_end = 0; pos_from_end < max_search_area; ++pos_from_end)
{
if( IsMatch(seek, pos_from_end, needle) )
{
throw new ArchiveException("Could not find Zip file Directory at the end of the file. File may be corrupted.");
stream.Seek(-pos_from_end, SeekOrigin.End);
return;
}
}
while (signature != headerSignature);

throw new ArchiveException("Failed to locate the Zip Header");
}

internal LocalEntryHeader GetLocalHeader(Stream stream, DirectoryEntryHeader directoryEntryHeader)
Expand Down
12 changes: 12 additions & 0 deletions tests/SharpCompress.Test/Zip/ZipArchiveTests.cs
Expand Up @@ -564,5 +564,17 @@ public void Zip_NoCompression_DataDescriptors_Read()
}
}
}

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

using(ZipArchive za = ZipArchive.Open(zipPath))
{
var count = za.Entries.Count;
Assert.Equal(1, count);
}
}
}
}
Binary file added tests/TestArchives/Archives/Zip.LongComment.zip
Binary file not shown.