mirror of
https://github.com/adamhathcock/sharpcompress.git
synced 2026-02-15 05:26:01 +00:00
442 lines
15 KiB
C#
442 lines
15 KiB
C#
using System;
|
|
using System.Buffers.Binary;
|
|
using System.IO;
|
|
using SharpCompress.Archives;
|
|
using SharpCompress.Archives.Zip;
|
|
using SharpCompress.Common;
|
|
using SharpCompress.Writers;
|
|
using SharpCompress.Writers.Zip;
|
|
using Xunit;
|
|
|
|
namespace SharpCompress.Test.Zip;
|
|
|
|
/// <summary>
|
|
/// Tests for verifying version consistency between Local File Header (LFH)
|
|
/// and Central Directory File Header (CDFH) when using Zip64.
|
|
/// </summary>
|
|
public class Zip64VersionConsistencyTests : WriterTests
|
|
{
|
|
public Zip64VersionConsistencyTests()
|
|
: base(ArchiveType.Zip) { }
|
|
|
|
[Fact]
|
|
public void Zip64_Small_File_With_UseZip64_Should_Have_Matching_Versions()
|
|
{
|
|
// Create a zip with UseZip64=true but with a small file
|
|
var filename = Path.Combine(SCRATCH2_FILES_PATH, "zip64_version_test.zip");
|
|
|
|
if (File.Exists(filename))
|
|
{
|
|
File.Delete(filename);
|
|
}
|
|
|
|
// Create archive with UseZip64=true
|
|
WriterOptions writerOptions = new ZipWriterOptions(CompressionType.Deflate)
|
|
{
|
|
LeaveStreamOpen = false,
|
|
UseZip64 = true,
|
|
};
|
|
|
|
var zipArchive = ZipArchive.CreateArchive();
|
|
zipArchive.AddEntry("empty", new MemoryStream());
|
|
zipArchive.SaveTo(filename, writerOptions);
|
|
|
|
// Now read the raw bytes to verify version consistency
|
|
using var fs = File.OpenRead(filename);
|
|
using var br = new BinaryReader(fs);
|
|
|
|
// Read Local File Header
|
|
var lfhSignature = br.ReadUInt32();
|
|
Assert.Equal(0x04034b50u, lfhSignature); // Local file header signature
|
|
|
|
var lfhVersion = br.ReadUInt16();
|
|
|
|
// Skip to Central Directory
|
|
// Find Central Directory by searching from the end
|
|
fs.Seek(-22, SeekOrigin.End); // Min EOCD size
|
|
var eocdSignature = br.ReadUInt32();
|
|
|
|
if (eocdSignature != 0x06054b50u)
|
|
{
|
|
// Might have Zip64 EOCD, search backwards
|
|
fs.Seek(-100, SeekOrigin.End);
|
|
var buffer = new byte[100];
|
|
fs.Read(buffer, 0, 100);
|
|
|
|
// Find EOCD signature
|
|
for (int i = buffer.Length - 4; i >= 0; i--)
|
|
{
|
|
if (BinaryPrimitives.ReadUInt32LittleEndian(buffer.AsSpan(i)) == 0x06054b50u)
|
|
{
|
|
fs.Seek(-100 + i, SeekOrigin.End);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Read EOCD
|
|
fs.Seek(-22, SeekOrigin.End);
|
|
br.ReadUInt32(); // EOCD signature
|
|
br.ReadUInt16(); // disk number
|
|
br.ReadUInt16(); // disk with central dir
|
|
br.ReadUInt16(); // entries on this disk
|
|
br.ReadUInt16(); // total entries
|
|
br.ReadUInt32(); // central directory size (unused)
|
|
var cdOffset = br.ReadUInt32();
|
|
|
|
// If Zip64, need to read from Zip64 EOCD
|
|
if (cdOffset == 0xFFFFFFFF)
|
|
{
|
|
// Find Zip64 EOCD Locator
|
|
fs.Seek(-22 - 20, SeekOrigin.End);
|
|
var z64eocdlSig = br.ReadUInt32();
|
|
if (z64eocdlSig == 0x07064b50u)
|
|
{
|
|
br.ReadUInt32(); // disk number
|
|
var z64eocdOffset = br.ReadUInt64();
|
|
br.ReadUInt32(); // total disks
|
|
|
|
// Read Zip64 EOCD
|
|
fs.Seek((long)z64eocdOffset, SeekOrigin.Begin);
|
|
br.ReadUInt32(); // signature
|
|
br.ReadUInt64(); // size of EOCD64
|
|
br.ReadUInt16(); // version made by
|
|
br.ReadUInt16(); // version needed
|
|
br.ReadUInt32(); // disk number
|
|
br.ReadUInt32(); // disk with CD
|
|
br.ReadUInt64(); // entries on disk
|
|
br.ReadUInt64(); // total entries
|
|
br.ReadUInt64(); // CD size
|
|
cdOffset = (uint)br.ReadUInt64(); // CD offset
|
|
}
|
|
}
|
|
|
|
// Read Central Directory Header
|
|
fs.Seek(cdOffset, SeekOrigin.Begin);
|
|
var cdhSignature = br.ReadUInt32();
|
|
Assert.Equal(0x02014b50u, cdhSignature); // Central directory header signature
|
|
|
|
br.ReadUInt16(); // version made by
|
|
var cdhVersionNeeded = br.ReadUInt16();
|
|
|
|
// The versions should match when UseZip64 is true
|
|
Assert.Equal(lfhVersion, cdhVersionNeeded);
|
|
}
|
|
|
|
[Fact]
|
|
public void Zip64_Small_File_Without_UseZip64_Should_Have_Version_20()
|
|
{
|
|
// Create a zip without UseZip64
|
|
var filename = Path.Combine(SCRATCH2_FILES_PATH, "no_zip64_version_test.zip");
|
|
|
|
if (File.Exists(filename))
|
|
{
|
|
File.Delete(filename);
|
|
}
|
|
|
|
// Create archive without UseZip64
|
|
WriterOptions writerOptions = new ZipWriterOptions(CompressionType.Deflate)
|
|
{
|
|
LeaveStreamOpen = false,
|
|
UseZip64 = false,
|
|
};
|
|
|
|
var zipArchive = ZipArchive.CreateArchive();
|
|
zipArchive.AddEntry("empty", new MemoryStream());
|
|
zipArchive.SaveTo(filename, writerOptions);
|
|
|
|
// Read the raw bytes
|
|
using var fs = File.OpenRead(filename);
|
|
using var br = new BinaryReader(fs);
|
|
|
|
// Read Local File Header version
|
|
var lfhSignature = br.ReadUInt32();
|
|
Assert.Equal(0x04034b50u, lfhSignature);
|
|
var lfhVersion = br.ReadUInt16();
|
|
|
|
// Read Central Directory Header version
|
|
fs.Seek(-22, SeekOrigin.End);
|
|
br.ReadUInt32(); // EOCD signature
|
|
br.ReadUInt16(); // disk number
|
|
br.ReadUInt16(); // disk with central dir
|
|
br.ReadUInt16(); // entries on this disk
|
|
br.ReadUInt16(); // total entries
|
|
br.ReadUInt32(); // CD size
|
|
var cdOffset = br.ReadUInt32();
|
|
|
|
fs.Seek(cdOffset, SeekOrigin.Begin);
|
|
var cdhSignature = br.ReadUInt32();
|
|
Assert.Equal(0x02014b50u, cdhSignature);
|
|
br.ReadUInt16(); // version made by
|
|
var cdhVersionNeeded = br.ReadUInt16();
|
|
|
|
// Both should be version 20 (or less)
|
|
Assert.True(lfhVersion <= 20);
|
|
Assert.Equal(lfhVersion, cdhVersionNeeded);
|
|
}
|
|
|
|
[Fact]
|
|
public void LZMA_Compression_Should_Use_Version_63()
|
|
{
|
|
// Create a zip with LZMA compression
|
|
var filename = Path.Combine(SCRATCH2_FILES_PATH, "lzma_version_test.zip");
|
|
|
|
if (File.Exists(filename))
|
|
{
|
|
File.Delete(filename);
|
|
}
|
|
|
|
WriterOptions writerOptions = new ZipWriterOptions(CompressionType.LZMA)
|
|
{
|
|
LeaveStreamOpen = false,
|
|
UseZip64 = false,
|
|
};
|
|
|
|
var zipArchive = ZipArchive.CreateArchive();
|
|
var data = new byte[100];
|
|
new Random(42).NextBytes(data);
|
|
zipArchive.AddEntry("test.bin", new MemoryStream(data));
|
|
zipArchive.SaveTo(filename, writerOptions);
|
|
|
|
// Read the raw bytes
|
|
using var fs = File.OpenRead(filename);
|
|
using var br = new BinaryReader(fs);
|
|
|
|
// Read Local File Header version
|
|
var lfhSignature = br.ReadUInt32();
|
|
Assert.Equal(0x04034b50u, lfhSignature);
|
|
var lfhVersion = br.ReadUInt16();
|
|
|
|
// Read Central Directory Header version
|
|
fs.Seek(-22, SeekOrigin.End);
|
|
br.ReadUInt32(); // EOCD signature
|
|
br.ReadUInt16(); // disk number
|
|
br.ReadUInt16(); // disk with central dir
|
|
br.ReadUInt16(); // entries on this disk
|
|
br.ReadUInt16(); // total entries
|
|
br.ReadUInt32(); // CD size
|
|
var cdOffset = br.ReadUInt32();
|
|
|
|
fs.Seek(cdOffset, SeekOrigin.Begin);
|
|
var cdhSignature = br.ReadUInt32();
|
|
Assert.Equal(0x02014b50u, cdhSignature);
|
|
br.ReadUInt16(); // version made by
|
|
var cdhVersionNeeded = br.ReadUInt16();
|
|
|
|
// Both should be version 63 for LZMA
|
|
Assert.Equal(63, lfhVersion);
|
|
Assert.Equal(lfhVersion, cdhVersionNeeded);
|
|
}
|
|
|
|
[Fact]
|
|
public void PPMd_Compression_Should_Use_Version_63()
|
|
{
|
|
// Create a zip with PPMd compression
|
|
var filename = Path.Combine(SCRATCH2_FILES_PATH, "ppmd_version_test.zip");
|
|
|
|
if (File.Exists(filename))
|
|
{
|
|
File.Delete(filename);
|
|
}
|
|
|
|
WriterOptions writerOptions = new ZipWriterOptions(CompressionType.PPMd)
|
|
{
|
|
LeaveStreamOpen = false,
|
|
UseZip64 = false,
|
|
};
|
|
|
|
var zipArchive = ZipArchive.CreateArchive();
|
|
var data = new byte[100];
|
|
new Random(42).NextBytes(data);
|
|
zipArchive.AddEntry("test.bin", new MemoryStream(data));
|
|
zipArchive.SaveTo(filename, writerOptions);
|
|
|
|
// Read the raw bytes
|
|
using var fs = File.OpenRead(filename);
|
|
using var br = new BinaryReader(fs);
|
|
|
|
// Read Local File Header version
|
|
var lfhSignature = br.ReadUInt32();
|
|
Assert.Equal(0x04034b50u, lfhSignature);
|
|
var lfhVersion = br.ReadUInt16();
|
|
|
|
// Read Central Directory Header version
|
|
fs.Seek(-22, SeekOrigin.End);
|
|
br.ReadUInt32(); // EOCD signature
|
|
br.ReadUInt16(); // disk number
|
|
br.ReadUInt16(); // disk with central dir
|
|
br.ReadUInt16(); // entries on this disk
|
|
br.ReadUInt16(); // total entries
|
|
br.ReadUInt32(); // CD size
|
|
var cdOffset = br.ReadUInt32();
|
|
|
|
fs.Seek(cdOffset, SeekOrigin.Begin);
|
|
var cdhSignature = br.ReadUInt32();
|
|
Assert.Equal(0x02014b50u, cdhSignature);
|
|
br.ReadUInt16(); // version made by
|
|
var cdhVersionNeeded = br.ReadUInt16();
|
|
|
|
// Both should be version 63 for PPMd
|
|
Assert.Equal(63, lfhVersion);
|
|
Assert.Equal(lfhVersion, cdhVersionNeeded);
|
|
}
|
|
|
|
[Fact]
|
|
public void Zip64_Multiple_Small_Files_With_UseZip64_Should_Have_Matching_Versions()
|
|
{
|
|
// Create a zip with UseZip64=true but with multiple small files
|
|
var filename = Path.Combine(SCRATCH2_FILES_PATH, "zip64_version_multiple_test.zip");
|
|
|
|
if (File.Exists(filename))
|
|
{
|
|
File.Delete(filename);
|
|
}
|
|
|
|
WriterOptions writerOptions = new ZipWriterOptions(CompressionType.Deflate)
|
|
{
|
|
LeaveStreamOpen = false,
|
|
UseZip64 = true,
|
|
};
|
|
|
|
var zipArchive = ZipArchive.CreateArchive();
|
|
for (int i = 0; i < 5; i++)
|
|
{
|
|
var data = new byte[100];
|
|
new Random(i).NextBytes(data);
|
|
zipArchive.AddEntry($"file{i}.bin", new MemoryStream(data));
|
|
}
|
|
zipArchive.SaveTo(filename, writerOptions);
|
|
|
|
// Verify that all entries have matching versions
|
|
using var fs = File.OpenRead(filename);
|
|
using var br = new BinaryReader(fs);
|
|
|
|
// Read all LFH versions
|
|
var lfhVersions = new System.Collections.Generic.List<ushort>();
|
|
while (true)
|
|
{
|
|
var sig = br.ReadUInt32();
|
|
if (sig == 0x04034b50u) // LFH signature
|
|
{
|
|
var version = br.ReadUInt16();
|
|
lfhVersions.Add(version);
|
|
|
|
// Skip rest of LFH
|
|
br.ReadUInt16(); // flags
|
|
br.ReadUInt16(); // compression
|
|
br.ReadUInt32(); // mod time
|
|
br.ReadUInt32(); // crc
|
|
br.ReadUInt32(); // compressed size
|
|
br.ReadUInt32(); // uncompressed size
|
|
var fnLen = br.ReadUInt16();
|
|
var extraLen = br.ReadUInt16();
|
|
fs.Seek(fnLen + extraLen, SeekOrigin.Current);
|
|
|
|
// Skip compressed data by reading compressed size from extra field if zip64
|
|
// For simplicity in this test, we'll just find the next signature
|
|
var found = false;
|
|
|
|
while (fs.Position < fs.Length - 4)
|
|
{
|
|
var b = br.ReadByte();
|
|
if (b == 0x50)
|
|
{
|
|
var nextBytes = br.ReadBytes(3);
|
|
if (
|
|
(nextBytes[0] == 0x4b && nextBytes[1] == 0x03 && nextBytes[2] == 0x04)
|
|
|| // LFH
|
|
(nextBytes[0] == 0x4b && nextBytes[1] == 0x01 && nextBytes[2] == 0x02)
|
|
) // CDH
|
|
{
|
|
fs.Seek(-4, SeekOrigin.Current);
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!found)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
else if (sig == 0x02014b50u) // CDH signature
|
|
{
|
|
break; // Reached central directory
|
|
}
|
|
else
|
|
{
|
|
break; // Unknown signature
|
|
}
|
|
}
|
|
|
|
// Find Central Directory
|
|
fs.Seek(-22, SeekOrigin.End);
|
|
br.ReadUInt32(); // EOCD signature
|
|
br.ReadUInt16(); // disk number
|
|
br.ReadUInt16(); // disk with central dir
|
|
br.ReadUInt16(); // entries on this disk
|
|
var totalEntries = br.ReadUInt16();
|
|
br.ReadUInt32(); // CD size
|
|
var cdOffset = br.ReadUInt32();
|
|
|
|
// Check if we need Zip64 EOCD
|
|
if (cdOffset == 0xFFFFFFFF)
|
|
{
|
|
fs.Seek(-22 - 20, SeekOrigin.End);
|
|
var z64eocdlSig = br.ReadUInt32();
|
|
if (z64eocdlSig == 0x07064b50u)
|
|
{
|
|
br.ReadUInt32(); // disk number
|
|
var z64eocdOffset = br.ReadUInt64();
|
|
fs.Seek((long)z64eocdOffset, SeekOrigin.Begin);
|
|
br.ReadUInt32(); // signature
|
|
br.ReadUInt64(); // size
|
|
br.ReadUInt16(); // version made by
|
|
br.ReadUInt16(); // version needed
|
|
br.ReadUInt32(); // disk number
|
|
br.ReadUInt32(); // disk with CD
|
|
br.ReadUInt64(); // entries on disk
|
|
totalEntries = (ushort)br.ReadUInt64(); // total entries
|
|
br.ReadUInt64(); // CD size
|
|
cdOffset = (uint)br.ReadUInt64(); // CD offset
|
|
}
|
|
}
|
|
|
|
// Read CDH versions
|
|
fs.Seek(cdOffset, SeekOrigin.Begin);
|
|
var cdhVersions = new System.Collections.Generic.List<ushort>();
|
|
for (int i = 0; i < totalEntries; i++)
|
|
{
|
|
var sig = br.ReadUInt32();
|
|
Assert.Equal(0x02014b50u, sig);
|
|
br.ReadUInt16(); // version made by
|
|
var version = br.ReadUInt16();
|
|
cdhVersions.Add(version);
|
|
|
|
// Skip rest of CDH
|
|
br.ReadUInt16(); // flags
|
|
br.ReadUInt16(); // compression
|
|
br.ReadUInt32(); // mod time
|
|
br.ReadUInt32(); // crc
|
|
br.ReadUInt32(); // compressed size
|
|
br.ReadUInt32(); // uncompressed size
|
|
var fnLen = br.ReadUInt16();
|
|
var extraLen = br.ReadUInt16();
|
|
var commentLen = br.ReadUInt16();
|
|
br.ReadUInt16(); // disk number start
|
|
br.ReadUInt16(); // internal attributes
|
|
br.ReadUInt32(); // external attributes
|
|
br.ReadUInt32(); // LFH offset
|
|
fs.Seek(fnLen + extraLen + commentLen, SeekOrigin.Current);
|
|
}
|
|
|
|
// Verify all versions match
|
|
Assert.Equal(lfhVersions.Count, cdhVersions.Count);
|
|
for (int i = 0; i < lfhVersions.Count; i++)
|
|
{
|
|
Assert.Equal(lfhVersions[i], cdhVersions[i]);
|
|
}
|
|
}
|
|
}
|