From a93a46d6c0198e435fd9bf6f0df7ab31d37bddb9 Mon Sep 17 00:00:00 2001 From: Matt Nadareski Date: Wed, 27 Aug 2025 12:54:36 -0400 Subject: [PATCH] Start fixing PKZIP deserializing This does a forward-reading approach on the archive instead of the "proper" way of reading from the end. This change introduces potential issues, as both "deleted" entries and normal ones will be included. It also makes it more difficult to determine if the archive is ZIP64 or not. But it is far more stable than the read-from-the-end approach of the old code. --- .../Deserializers/PKZIP.cs | 814 +++++++----------- SabreTools.Serialization/Printers/PKZIP.cs | 78 +- 2 files changed, 362 insertions(+), 530 deletions(-) diff --git a/SabreTools.Serialization/Deserializers/PKZIP.cs b/SabreTools.Serialization/Deserializers/PKZIP.cs index 57fa4c88..52934082 100644 --- a/SabreTools.Serialization/Deserializers/PKZIP.cs +++ b/SabreTools.Serialization/Deserializers/PKZIP.cs @@ -1,12 +1,11 @@ -using System; using System.Collections.Generic; using System.IO; using System.Text; using SabreTools.IO.Extensions; +using SabreTools.Matching; using SabreTools.Models.PKZIP; using static SabreTools.Models.PKZIP.Constants; -// TODO: Finish replacing ReadType namespace SabreTools.Serialization.Deserializers { public class PKZIP : BaseBinaryDeserializer @@ -28,258 +27,199 @@ namespace SabreTools.Serialization.Deserializers var archive = new Archive(); - #region End of Central Directory Record - - // Find the end of central directory record - long eocdrOffset = SearchForEndOfCentralDirectoryRecord(data); - if (eocdrOffset < initialOffset || eocdrOffset >= data.Length) - return null; - - // Seek to the end of central directory record - data.Seek(eocdrOffset, SeekOrigin.Begin); - - // Read the end of central directory record - var eocdr = ParseEndOfCentralDirectoryRecord(data); - if (eocdr == null) - return null; - - // Assign the end of central directory record - archive.EndOfCentralDirectoryRecord = eocdr; - - #endregion - - #region ZIP64 End of Central Directory Locator and Record - - // Set a flag for ZIP64 not found by default - bool zip64 = false; - - // Process ZIP64 if any fields are set to max value - if (eocdr.DiskNumber == 0xFFFF - || eocdr.StartDiskNumber == 0xFFFF - || eocdr.TotalEntriesOnDisk == 0xFFFF - || eocdr.TotalEntries == 0xFFFF - || eocdr.CentralDirectorySize == 0xFFFFFFFF - || eocdr.CentralDirectoryOffset == 0xFFFFFFFF) - { - // Set the ZIP64 flag - zip64 = true; - - // Find the ZIP64 end of central directory locator - long eocdlOffset = SearchForZIP64EndOfCentralDirectoryLocator(data); - if (eocdlOffset < initialOffset || eocdlOffset >= data.Length) - return null; - - // Seek to the ZIP64 end of central directory locator - data.Seek(eocdlOffset, SeekOrigin.Begin); - - // Read the ZIP64 end of central directory locator - var eocdl64 = data.ReadType(); - if (eocdl64 == null) - return null; - - // Assign the ZIP64 end of central directory record - archive.ZIP64EndOfCentralDirectoryLocator = eocdl64; - - // Try to get the ZIP64 end of central directory record offset - if ((long)eocdl64.CentralDirectoryOffset < 0 || initialOffset + (long)eocdl64.CentralDirectoryOffset >= data.Length) - return null; - - // Seek to the ZIP64 end of central directory record - data.Seek(initialOffset + (long)eocdl64.CentralDirectoryOffset, SeekOrigin.Begin); - - // Read the ZIP64 end of central directory record - var eocdr64 = ParseEndOfCentralDirectoryRecord64(data); - if (eocdr64 == null) - return null; - - // Assign the ZIP64 end of central directory record - archive.ZIP64EndOfCentralDirectoryRecord = eocdr64; - } - - #endregion - - #region Central Directory Records - - // Try to get the central directory record offset - long cdrOffset, cdrSize; - if (zip64 && archive.ZIP64EndOfCentralDirectoryRecord != null) - { - cdrOffset = initialOffset + (long)archive.ZIP64EndOfCentralDirectoryRecord.CentralDirectoryOffset; - cdrSize = (long)archive.ZIP64EndOfCentralDirectoryRecord.CentralDirectorySize; - } - else if (archive.EndOfCentralDirectoryRecord != null) - { - cdrOffset = initialOffset + archive.EndOfCentralDirectoryRecord.CentralDirectoryOffset; - cdrSize = archive.EndOfCentralDirectoryRecord.CentralDirectorySize; - } - else - { - return null; - } - - // Try to get the central directory record offset - if (cdrOffset < initialOffset || cdrOffset >= data.Length) - return null; - - // Seek to the first central directory record - data.Seek(cdrOffset, SeekOrigin.Begin); - - // Cache the current offset - long currentOffset = data.Position; - - // Read the central directory records - var cdrs = new List(); - while (data.Position < currentOffset + cdrSize) - { - // Read the central directory record - var cdr = ParseCentralDirectoryFileHeader(data); - if (cdr == null) - return null; - - // Add the central directory record - cdrs.Add(cdr); - } - - // Assign the central directory records - archive.CentralDirectoryHeaders = [.. cdrs]; - - #endregion - - // TODO: Handle digital signature -- immediately following central directory records - - #region Archive Extra Data Record - - // Find the archive extra data record - long aedrOffset = SearchForArchiveExtraDataRecord(data, cdrOffset); - if (aedrOffset >= 0 && aedrOffset < data.Length) - { - // Seek to the archive extra data record - data.Seek(aedrOffset, SeekOrigin.Begin); - - // Read the archive extra data record - var aedr = ParseArchiveExtraDataRecord(data); - if (aedr == null) - return null; - - // Assign the archive extra data record - archive.ArchiveExtraDataRecord = aedr; - } - - #endregion - - #region Local File - // Setup all of the collections var localFileHeaders = new List(); var encryptionHeaders = new List(); var fileData = new List(); // TODO: Should this data be read here? var dataDescriptors = new List(); var zip64DataDescriptors = new List(); + var cdrs = new List(); - // Read the local file headers - for (int i = 0; i < archive.CentralDirectoryHeaders.Length; i++) + // Flag if we have a ZIP64 archive + bool? zip64 = null; + + // Read all blocks + do { - var header = archive.CentralDirectoryHeaders[i]; + // Read the signature + long beforeSignature = data.Position; + uint signature = data.ReadUInt32LittleEndian(); + data.Seek(beforeSignature, SeekOrigin.Begin); - // Get the local file header offset - long headerOffset = header.RelativeOffsetOfLocalHeader; - if (headerOffset == 0xFFFFFFFF && header.ExtraField != null) + // Switch based on the signature found + bool validBlock = false; + switch (signature) { - // TODO: Parse into a proper structure instead of this - byte[] extraData = header.ExtraField; - if (BitConverter.ToUInt16(extraData, 0) == 0x0001) - headerOffset = BitConverter.ToInt64(extraData, 4); - } - - if (headerOffset < 0 || initialOffset + headerOffset >= data.Length) - return null; - - // Seek to the local file header - data.Seek(initialOffset + headerOffset, SeekOrigin.Begin); - - // Try to parse the local header - var localFileHeader = ParseLocalFileHeader(data); - if (localFileHeader == null) - { - // Add a placeholder null item - localFileHeaders.Add(new LocalFileHeader()); - encryptionHeaders.Add([]); - fileData.Add([]); - dataDescriptors.Add(new DataDescriptor()); - zip64DataDescriptors.Add(new DataDescriptor64()); - continue; - } - - // Add the local file header - localFileHeaders.Add(localFileHeader); - - // Only read the encryption header if necessary -#if NET20 || NET35 - if ((header.Flags & GeneralPurposeBitFlags.FileEncrypted) != 0) -#else - if (header.Flags.HasFlag(GeneralPurposeBitFlags.FileEncrypted)) -#endif - { - // Try to read the encryption header data -- TODO: Verify amount to read - byte[] encryptionHeader = data.ReadBytes(12); - if (encryptionHeader.Length != 12) - return null; - - // Add the encryption header - encryptionHeaders.Add(encryptionHeader); - } - else - { - // Add the empty encryption header - encryptionHeaders.Add([]); - } - - // Try to read the file data - byte[] fileDatum = data.ReadBytes((int)header.CompressedSize); - if (fileDatum.Length < header.CompressedSize) - return null; - - // Add the file data - fileData.Add(fileDatum); - - // Only read the data descriptor if necessary -#if NET20 || NET35 - if ((header.Flags & GeneralPurposeBitFlags.NoCRC) != 0) -#else - if (header.Flags.HasFlag(GeneralPurposeBitFlags.NoCRC)) -#endif - { - // Select the data descriptor that is being used - if (zip64) - { - // Try to parse the data descriptor - var dataDescriptor64 = ParseDataDescriptor64(data); - if (dataDescriptor64 == null) + // Central Directory File Header + case CentralDirectoryFileHeaderSignature: + var cdr = ParseCentralDirectoryFileHeader(data); + if (cdr == null) return null; - // Add the data descriptor - dataDescriptors.Add(new DataDescriptor()); - zip64DataDescriptors.Add(dataDescriptor64); - } - else - { - // Try to parse the data descriptor - var dataDescriptor = ParseDataDescriptor(data); - if (dataDescriptor == null) + // Add the central directory record + validBlock = true; + cdrs.Add(cdr); + break; + + // Local File + case LocalFileHeaderSignature: + #region Local File Header + + var localFileHeader = ParseLocalFileHeader(data); + if (localFileHeader == null) + break; + + // Add the local file header + localFileHeaders.Add(localFileHeader); + + #endregion + + #region Encryption Header + + // Only read the encryption header if necessary +#if NET20 || NET35 + if ((localFileHeader.Flags & GeneralPurposeBitFlags.FileEncrypted) != 0) +#else + if (localFileHeader.Flags.HasFlag(GeneralPurposeBitFlags.FileEncrypted)) +#endif + { + // Try to read the encryption header data -- TODO: Verify amount to read + byte[] encryptionHeader = data.ReadBytes(12); + if (encryptionHeader.Length != 12) + break; + + // Add the encryption header + encryptionHeaders.Add(encryptionHeader); + } + else + { + // Add the empty encryption header + encryptionHeaders.Add([]); + } + + #endregion + + #region File Data + + // Try to read the file data + byte[] fileDatum = data.ReadBytes((int)localFileHeader.CompressedSize); + if (fileDatum.Length < localFileHeader.CompressedSize) + break; + + // Add the file data + validBlock = true; + fileData.Add(fileDatum); + + #endregion + + break; + + // TODO: Implement + case DigitalSignatureSignature: + break; + + // End of Central Directory Record + case EndOfCentralDirectoryRecordSignature: + var eocdr = ParseEndOfCentralDirectoryRecord(data); + if (eocdr == null) return null; - // Add the data descriptor - dataDescriptors.Add(dataDescriptor); - zip64DataDescriptors.Add(new DataDescriptor64()); - } + // Assign the end of central directory record + validBlock = true; + archive.EndOfCentralDirectoryRecord = eocdr; + break; + + // ZIP64 End of Central Directory + case EndOfCentralDirectoryRecord64Signature: + var eocdr64 = ParseEndOfCentralDirectoryRecord64(data); + if (eocdr64 == null) + return null; + + // Assign the ZIP64 end of central directory record + zip64 = true; + validBlock = true; + archive.ZIP64EndOfCentralDirectoryRecord = eocdr64; + break; + + // ZIP64 End of Central Directory Locator + case EndOfCentralDirectoryLocator64Signature: + var eocdl64 = ParseEndOfCentralDirectoryLocator64(data); + if (eocdl64 == null) + return null; + + // Assign the ZIP64 end of central directory record + zip64 = true; + validBlock = true; + archive.ZIP64EndOfCentralDirectoryLocator = eocdl64; + break; + + // Archive Extra Data Record + case ArchiveExtraDataRecordSignature: + var aedr = ParseArchiveExtraDataRecord(data); + if (aedr == null) + return null; + + // Assign the archive extra data record + validBlock = true; + archive.ArchiveExtraDataRecord = aedr; + break; + + // Data Descriptor -- Usually follows a local file header + case DataDescriptorSignature: + if (zip64 == null) + { + if (data.Position + 16 == data.Length) + { + zip64 = false; + } + else if (data.Position + 24 == data.Length) + { + zip64 = true; + } + else + { + long beforeCheck = data.Position; + data.Seek(16, SeekOrigin.Current); + byte[] nextBlock = data.ReadBytes(2); + data.Seek(beforeCheck, SeekOrigin.Begin); + + zip64 = !nextBlock.EqualsExactly([0x50, 0x4B]); + } + } + + if (zip64 == true) + { + // Try to parse the data descriptor + var dataDescriptor64 = ParseDataDescriptor64(data); + if (dataDescriptor64 == null) + break; + + // Add the data descriptor + validBlock = true; + dataDescriptors.Add(new DataDescriptor()); + zip64DataDescriptors.Add(dataDescriptor64); + } + else + { + // Try to parse the data descriptor + var dataDescriptor = ParseDataDescriptor(data); + if (dataDescriptor == null) + break; + + // Add the data descriptor + validBlock = true; + dataDescriptors.Add(dataDescriptor); + zip64DataDescriptors.Add(new DataDescriptor64()); + } + + break; } - else - { - // Add the null data descriptor - dataDescriptors.Add(new DataDescriptor()); - zip64DataDescriptors.Add(new DataDescriptor64()); - } - } + + // If there was an invalid block + if (!validBlock) + break; + + } while (data.Position < data.Length); // Assign the local file headers archive.LocalFileHeaders = [.. localFileHeaders]; @@ -294,9 +234,8 @@ namespace SabreTools.Serialization.Deserializers archive.DataDescriptors = [.. dataDescriptors]; archive.ZIP64DataDescriptors = [.. zip64DataDescriptors]; - #endregion - - // TODO: Handle archive decryption header + // Assign the central directory records + archive.CentralDirectoryHeaders = [.. cdrs]; return archive; } @@ -308,161 +247,29 @@ namespace SabreTools.Serialization.Deserializers } /// - /// Search for the end of central directory record + /// Parse a Stream into an archive extra data record /// /// Stream to parse - /// Position of the end of central directory record, -1 on error - public static long SearchForEndOfCentralDirectoryRecord(Stream data) + /// Filled archive extra data record on success, null on error + public static ArchiveExtraDataRecord? ParseArchiveExtraDataRecord(Stream data) { - // Cache the current offset - long current = data.Position; + var obj = new ArchiveExtraDataRecord(); - // Seek to the minimum size of the record from the end - data.Seek(-22, SeekOrigin.End); - - // Attempt to find the end of central directory signature - while (data.Position > 0) - { - // Read the potential signature - uint possibleSignature = data.ReadUInt32LittleEndian(); - if (possibleSignature == EndOfCentralDirectoryRecordSignature) - { - long signaturePosition = data.Position - 4; - data.Seek(current, SeekOrigin.Begin); - return signaturePosition; - } - - // If we find any other signature - switch (possibleSignature) - { - case ArchiveExtraDataRecordSignature: - case CentralDirectoryFileHeaderSignature: - case DataDescriptorSignature: - case DigitalSignatureSignature: - case EndOfCentralDirectoryLocator64Signature: - case EndOfCentralDirectoryRecord64Signature: - case LocalFileHeaderSignature: - data.Seek(current, SeekOrigin.Begin); - return -1; - } - - // Seek backward 5 bytes, if possible - data.Seek(-5, SeekOrigin.Current); - } - - // No signature was found - data.Seek(current, SeekOrigin.Begin); - return -1; - } - - /// - /// Parse a Stream into an end of central directory record - /// - /// Stream to parse - /// Filled end of central directory record on success, null on error - public static EndOfCentralDirectoryRecord? ParseEndOfCentralDirectoryRecord(Stream data) - { - var record = new EndOfCentralDirectoryRecord(); - - record.Signature = data.ReadUInt32LittleEndian(); - if (record.Signature != EndOfCentralDirectoryRecordSignature) + obj.Signature = data.ReadUInt32LittleEndian(); + if (obj.Signature != ArchiveExtraDataRecordSignature) return null; - record.DiskNumber = data.ReadUInt16LittleEndian(); - record.StartDiskNumber = data.ReadUInt16LittleEndian(); - record.TotalEntriesOnDisk = data.ReadUInt16LittleEndian(); - record.TotalEntries = data.ReadUInt16LittleEndian(); - record.CentralDirectorySize = data.ReadUInt32LittleEndian(); - record.CentralDirectoryOffset = data.ReadUInt32LittleEndian(); - record.FileCommentLength = data.ReadUInt16LittleEndian(); - if (record.FileCommentLength > 0 && data.Position + record.FileCommentLength <= data.Length) + obj.ExtraFieldLength = data.ReadUInt32LittleEndian(); + if (obj.ExtraFieldLength > 0 && data.Position + obj.ExtraFieldLength <= data.Length) { - byte[] commentBytes = data.ReadBytes(record.FileCommentLength); - if (commentBytes.Length != record.FileCommentLength) + byte[] extraBytes = data.ReadBytes((int)obj.ExtraFieldLength); + if (extraBytes.Length != obj.ExtraFieldLength) return null; - record.FileComment = Encoding.ASCII.GetString(commentBytes); + obj.ExtraFieldData = extraBytes; } - return record; - } - - /// - /// Search for the ZIP64 end of central directory locator - /// - /// Stream to parse - /// Position of the ZIP64 end of central directory locator, -1 on error - public static long SearchForZIP64EndOfCentralDirectoryLocator(Stream data) - { - // Cache the current offset - long current = data.Position; - - // Seek to the minimum size of the record from the minimum start - // of theend of central directory record - data.Seek(-22 + -20, SeekOrigin.Current); - - // Attempt to find the ZIP64 end of central directory locator signature - while (data.Position > 0) - { - // Read the potential signature - uint possibleSignature = data.ReadUInt32LittleEndian(); - if (possibleSignature == EndOfCentralDirectoryLocator64Signature) - { - long signaturePosition = data.Position - 4; - data.Seek(current, SeekOrigin.Begin); - return signaturePosition; - } - - // If we find any other signature - switch (possibleSignature) - { - case ArchiveExtraDataRecordSignature: - case CentralDirectoryFileHeaderSignature: - case DataDescriptorSignature: - case DigitalSignatureSignature: - case EndOfCentralDirectoryRecordSignature: - case EndOfCentralDirectoryRecord64Signature: - case LocalFileHeaderSignature: - data.Seek(current, SeekOrigin.Begin); - return -1; - } - - // Seek backward 5 bytes, if possible - data.Seek(-5, SeekOrigin.Current); - } - - // No signature was found - data.Seek(current, SeekOrigin.Begin); - return -1; - } - - /// - /// Parse a Stream into a ZIP64 end of central directory record - /// - /// Stream to parse - /// Filled ZIP64 end of central directory record on success, null on error - public static EndOfCentralDirectoryRecord64? ParseEndOfCentralDirectoryRecord64(Stream data) - { - var record = new EndOfCentralDirectoryRecord64(); - - record.Signature = data.ReadUInt32LittleEndian(); - if (record.Signature != EndOfCentralDirectoryRecord64Signature) - return null; - - record.DirectoryRecordSize = data.ReadUInt64LittleEndian(); - record.HostSystem = (HostSystem)data.ReadByteValue(); - record.VersionMadeBy = data.ReadByteValue(); - record.VersionNeededToExtract = data.ReadUInt16LittleEndian(); - record.DiskNumber = data.ReadUInt32LittleEndian(); - record.StartDiskNumber = data.ReadUInt32LittleEndian(); - record.TotalEntriesOnDisk = data.ReadUInt64LittleEndian(); - record.TotalEntries = data.ReadUInt64LittleEndian(); - record.CentralDirectorySize = data.ReadUInt64LittleEndian(); - record.CentralDirectoryOffset = data.ReadUInt64LittleEndian(); - - // TODO: Handle the ExtensibleDataSector -- How to detect if exists? - - return record; + return obj; } /// @@ -525,80 +332,129 @@ namespace SabreTools.Serialization.Deserializers } /// - /// Search for the archive extra data record + /// Parse a Stream into a data descriptor /// /// Stream to parse - /// Offset to the first central directory record - /// Position of the archive extra data record, -1 on error - public static long SearchForArchiveExtraDataRecord(Stream data, long centralDirectoryoffset) + /// Filled data descriptor on success, null on error + public static DataDescriptor? ParseDataDescriptor(Stream data) { - // Cache the current offset - long current = data.Position; + var obj = new DataDescriptor(); - // Seek to the minimum size of the record from the central directory - data.Seek(centralDirectoryoffset - 8, SeekOrigin.Begin); + // Signatures are expected but not required + obj.Signature = data.ReadUInt32LittleEndian(); + if (obj.Signature != DataDescriptorSignature) + data.Seek(-4, SeekOrigin.Current); - // Attempt to find the end of central directory signature - while (data.Position > 0) - { - // Read the potential signature - uint possibleSignature = data.ReadUInt32LittleEndian(); - if (possibleSignature == ArchiveExtraDataRecordSignature) - { - long signaturePosition = data.Position - 4; - data.Seek(current, SeekOrigin.Begin); - return signaturePosition; - } + obj.CRC32 = data.ReadUInt32LittleEndian(); + obj.CompressedSize = data.ReadUInt32LittleEndian(); + obj.UncompressedSize = data.ReadUInt32LittleEndian(); - // If we find any other signature - switch (possibleSignature) - { - case CentralDirectoryFileHeaderSignature: - case DataDescriptorSignature: - case DigitalSignatureSignature: - case EndOfCentralDirectoryLocator64Signature: - case EndOfCentralDirectoryRecordSignature: - case EndOfCentralDirectoryRecord64Signature: - case LocalFileHeaderSignature: - data.Seek(current, SeekOrigin.Begin); - return -1; - } - - // Seek backward 5 bytes, if possible - data.Seek(-5, SeekOrigin.Current); - } - - // No signature was found - data.Seek(current, SeekOrigin.Begin); - return -1; + return obj; } /// - /// Parse a Stream into an archive extra data record + /// Parse a Stream into a ZIP64 data descriptor /// /// Stream to parse - /// Filled archive extra data record on success, null on error - public static ArchiveExtraDataRecord? ParseArchiveExtraDataRecord(Stream data) + /// Filled ZIP64 data descriptor on success, null on error + public static DataDescriptor64? ParseDataDescriptor64(Stream data) { - var record = new ArchiveExtraDataRecord(); + var obj = new DataDescriptor64(); + + // Signatures are expected but not required + obj.Signature = data.ReadUInt32LittleEndian(); + if (obj.Signature != DataDescriptorSignature) + data.Seek(-4, SeekOrigin.Current); + + obj.CRC32 = data.ReadUInt32LittleEndian(); + obj.CompressedSize = data.ReadUInt64LittleEndian(); + obj.UncompressedSize = data.ReadUInt64LittleEndian(); + + return obj; + } + + /// + /// Parse a Stream into a ZIP64 end of central directory locator + /// + /// Stream to parse + /// Filled ZIP64 end of central directory locator on success, null on error + public static EndOfCentralDirectoryLocator64? ParseEndOfCentralDirectoryLocator64(Stream data) + { + var obj = new EndOfCentralDirectoryLocator64(); + + // Signatures are expected but not required + obj.Signature = data.ReadUInt32LittleEndian(); + if (obj.Signature != EndOfCentralDirectoryLocator64Signature) + data.Seek(-4, SeekOrigin.Current); + + obj.StartDiskNumber = data.ReadUInt32LittleEndian(); + obj.CentralDirectoryOffset = data.ReadUInt64LittleEndian(); + obj.TotalDisks = data.ReadUInt32LittleEndian(); + + return obj; + } + + /// + /// Parse a Stream into an end of central directory record + /// + /// Stream to parse + /// Filled end of central directory record on success, null on error + public static EndOfCentralDirectoryRecord? ParseEndOfCentralDirectoryRecord(Stream data) + { + var record = new EndOfCentralDirectoryRecord(); record.Signature = data.ReadUInt32LittleEndian(); - if (record.Signature != ArchiveExtraDataRecordSignature) + if (record.Signature != EndOfCentralDirectoryRecordSignature) return null; - record.ExtraFieldLength = data.ReadUInt32LittleEndian(); - if (record.ExtraFieldLength > 0 && data.Position + record.ExtraFieldLength <= data.Length) + record.DiskNumber = data.ReadUInt16LittleEndian(); + record.StartDiskNumber = data.ReadUInt16LittleEndian(); + record.TotalEntriesOnDisk = data.ReadUInt16LittleEndian(); + record.TotalEntries = data.ReadUInt16LittleEndian(); + record.CentralDirectorySize = data.ReadUInt32LittleEndian(); + record.CentralDirectoryOffset = data.ReadUInt32LittleEndian(); + record.FileCommentLength = data.ReadUInt16LittleEndian(); + if (record.FileCommentLength > 0 && data.Position + record.FileCommentLength <= data.Length) { - byte[] extraBytes = data.ReadBytes((int)record.ExtraFieldLength); - if (extraBytes.Length != record.ExtraFieldLength) + byte[] commentBytes = data.ReadBytes(record.FileCommentLength); + if (commentBytes.Length != record.FileCommentLength) return null; - record.ExtraFieldData = extraBytes; + record.FileComment = Encoding.ASCII.GetString(commentBytes); } return record; } + /// + /// Parse a Stream into a ZIP64 end of central directory record + /// + /// Stream to parse + /// Filled ZIP64 end of central directory record on success, null on error + public static EndOfCentralDirectoryRecord64? ParseEndOfCentralDirectoryRecord64(Stream data) + { + var record = new EndOfCentralDirectoryRecord64(); + + record.Signature = data.ReadUInt32LittleEndian(); + if (record.Signature != EndOfCentralDirectoryRecord64Signature) + return null; + + record.DirectoryRecordSize = data.ReadUInt64LittleEndian(); + record.HostSystem = (HostSystem)data.ReadByteValue(); + record.VersionMadeBy = data.ReadByteValue(); + record.VersionNeededToExtract = data.ReadUInt16LittleEndian(); + record.DiskNumber = data.ReadUInt32LittleEndian(); + record.StartDiskNumber = data.ReadUInt32LittleEndian(); + record.TotalEntriesOnDisk = data.ReadUInt64LittleEndian(); + record.TotalEntries = data.ReadUInt64LittleEndian(); + record.CentralDirectorySize = data.ReadUInt64LittleEndian(); + record.CentralDirectoryOffset = data.ReadUInt64LittleEndian(); + + // TODO: Handle the ExtensibleDataSector -- How to detect if exists? + + return record; + } + /// /// Parse a Stream into a local file header /// @@ -606,83 +462,41 @@ namespace SabreTools.Serialization.Deserializers /// Filled local file header on success, null on error public static LocalFileHeader? ParseLocalFileHeader(Stream data) { - var header = new LocalFileHeader(); + var obj = new LocalFileHeader(); - header.Signature = data.ReadUInt32LittleEndian(); - if (header.Signature != LocalFileHeaderSignature) + obj.Signature = data.ReadUInt32LittleEndian(); + if (obj.Signature != LocalFileHeaderSignature) return null; - header.Version = data.ReadUInt16LittleEndian(); - header.Flags = (GeneralPurposeBitFlags)data.ReadUInt16LittleEndian(); - header.CompressionMethod = (CompressionMethod)data.ReadUInt16LittleEndian(); - header.LastModifedFileTime = data.ReadUInt16LittleEndian(); - header.LastModifiedFileDate = data.ReadUInt16LittleEndian(); - header.CRC32 = data.ReadUInt32LittleEndian(); - header.CompressedSize = data.ReadUInt32LittleEndian(); - header.UncompressedSize = data.ReadUInt32LittleEndian(); - header.FileNameLength = data.ReadUInt16LittleEndian(); - header.ExtraFieldLength = data.ReadUInt16LittleEndian(); + obj.Version = data.ReadUInt16LittleEndian(); + obj.Flags = (GeneralPurposeBitFlags)data.ReadUInt16LittleEndian(); + obj.CompressionMethod = (CompressionMethod)data.ReadUInt16LittleEndian(); + obj.LastModifedFileTime = data.ReadUInt16LittleEndian(); + obj.LastModifiedFileDate = data.ReadUInt16LittleEndian(); + obj.CRC32 = data.ReadUInt32LittleEndian(); + obj.CompressedSize = data.ReadUInt32LittleEndian(); + obj.UncompressedSize = data.ReadUInt32LittleEndian(); + obj.FileNameLength = data.ReadUInt16LittleEndian(); + obj.ExtraFieldLength = data.ReadUInt16LittleEndian(); - if (header.FileNameLength > 0 && data.Position + header.FileNameLength <= data.Length) + if (obj.FileNameLength > 0 && data.Position + obj.FileNameLength <= data.Length) { - byte[] filenameBytes = data.ReadBytes(header.FileNameLength); - if (filenameBytes.Length != header.FileNameLength) + byte[] filenameBytes = data.ReadBytes(obj.FileNameLength); + if (filenameBytes.Length != obj.FileNameLength) return null; - header.FileName = Encoding.ASCII.GetString(filenameBytes); + obj.FileName = Encoding.ASCII.GetString(filenameBytes); } - if (header.ExtraFieldLength > 0 && data.Position + header.ExtraFieldLength <= data.Length) + if (obj.ExtraFieldLength > 0 && data.Position + obj.ExtraFieldLength <= data.Length) { - byte[] extraBytes = data.ReadBytes(header.ExtraFieldLength); - if (extraBytes.Length != header.ExtraFieldLength) + byte[] extraBytes = data.ReadBytes(obj.ExtraFieldLength); + if (extraBytes.Length != obj.ExtraFieldLength) return null; - header.ExtraField = extraBytes; + obj.ExtraField = extraBytes; } - return header; - } - - /// - /// Parse a Stream into a data descriptor - /// - /// Stream to parse - /// Filled data descriptor on success, null on error - public static DataDescriptor? ParseDataDescriptor(Stream data) - { - var dataDescriptor = new DataDescriptor(); - - // Signatures are expected but not required - dataDescriptor.Signature = data.ReadUInt32LittleEndian(); - if (dataDescriptor.Signature != DataDescriptorSignature) - data.Seek(-4, SeekOrigin.Current); - - dataDescriptor.CRC32 = data.ReadUInt32LittleEndian(); - dataDescriptor.CompressedSize = data.ReadUInt32LittleEndian(); - dataDescriptor.UncompressedSize = data.ReadUInt32LittleEndian(); - - return dataDescriptor; - } - - /// - /// Parse a Stream into a ZIP64 data descriptor - /// - /// Stream to parse - /// Filled ZIP64 data descriptor on success, null on error - public static DataDescriptor64? ParseDataDescriptor64(Stream data) - { - var zip64DataDescriptor = new DataDescriptor64(); - - // Signatures are expected but not required - zip64DataDescriptor.Signature = data.ReadUInt32LittleEndian(); - if (zip64DataDescriptor.Signature != DataDescriptorSignature) - data.Seek(-4, SeekOrigin.Current); - - zip64DataDescriptor.CRC32 = data.ReadUInt32LittleEndian(); - zip64DataDescriptor.CompressedSize = data.ReadUInt64LittleEndian(); - zip64DataDescriptor.UncompressedSize = data.ReadUInt64LittleEndian(); - - return zip64DataDescriptor; + return obj; } } } diff --git a/SabreTools.Serialization/Printers/PKZIP.cs b/SabreTools.Serialization/Printers/PKZIP.cs index 9c92cd18..b3291475 100644 --- a/SabreTools.Serialization/Printers/PKZIP.cs +++ b/SabreTools.Serialization/Printers/PKZIP.cs @@ -24,9 +24,9 @@ namespace SabreTools.Serialization.Printers Print(builder, archive.LocalFileHeaders, archive.EncryptionHeaders, - archive.FileData, - archive.DataDescriptors, - archive.ZIP64DataDescriptors); + archive.FileData); + Print(builder, archive.DataDescriptors); + Print(builder, archive.ZIP64DataDescriptors); } private static void Print(StringBuilder builder, EndOfCentralDirectoryRecord? record) @@ -158,9 +158,7 @@ namespace SabreTools.Serialization.Printers private static void Print(StringBuilder builder, LocalFileHeader[]? localFileHeaders, byte[][]? encryptionHeaders, - byte[][]? fileData, - DataDescriptor[]? dataDescriptors, - DataDescriptor64[]? zip64DataDescriptors) + byte[][]? fileData) { builder.AppendLine(" Local File Information:"); builder.AppendLine(" -------------------------"); @@ -172,9 +170,7 @@ namespace SabreTools.Serialization.Printers } if (encryptionHeaders == null || localFileHeaders.Length > encryptionHeaders.Length - || fileData == null || localFileHeaders.Length > fileData.Length - || dataDescriptors == null || localFileHeaders.Length > dataDescriptors.Length - || zip64DataDescriptors == null || localFileHeaders.Length > zip64DataDescriptors.Length) + || fileData == null || localFileHeaders.Length > fileData.Length) { builder.AppendLine(" Mismatch in local file array values"); builder.AppendLine(); @@ -185,10 +181,8 @@ namespace SabreTools.Serialization.Printers var localFileHeader = localFileHeaders[i]; var encryptionHeader = encryptionHeaders != null && i < encryptionHeaders.Length ? encryptionHeaders[i] : null; var fileDatum = fileData != null && i < fileData.Length ? fileData[i] : null; - var dataDescriptor = dataDescriptors != null && i < dataDescriptors.Length ? dataDescriptors[i] : null; - var zip64DataDescriptor = zip64DataDescriptors != null && i < zip64DataDescriptors.Length ? zip64DataDescriptors[i] : null; - Print(builder, localFileHeader, encryptionHeader, fileDatum, dataDescriptor, zip64DataDescriptor, i); + Print(builder, localFileHeader, encryptionHeader, fileDatum, i); } builder.AppendLine(); @@ -198,8 +192,6 @@ namespace SabreTools.Serialization.Printers LocalFileHeader localFileHeader, byte[]? encryptionHeader, byte[]? fileData, - DataDescriptor? dataDescriptor, - DataDescriptor64? zip64DataDescriptor, int index) { builder.AppendLine($" Local File Entry {index}"); @@ -236,30 +228,56 @@ namespace SabreTools.Serialization.Printers builder.AppendLine(fileData.Length, " [File Data] Length"); //builder.AppendLine(fileData, " [File Data] Data"); } + } - if (dataDescriptor == null) + private static void Print(StringBuilder builder, DataDescriptor[]? entries) + { + builder.AppendLine(" Data Descriptors Information:"); + builder.AppendLine(" -------------------------"); + if (entries == null || entries.Length == 0) { - builder.AppendLine(" [Data Descriptor]: [NULL]"); - } - else - { - builder.AppendLine(dataDescriptor.Signature, " [Data Descriptor] Signature"); - builder.AppendLine(dataDescriptor.CRC32, " [Data Descriptor] CRC-32"); - builder.AppendLine(dataDescriptor.CompressedSize, " [Data Descriptor] Compressed size"); - builder.AppendLine(dataDescriptor.UncompressedSize, " [Data Descriptor] Uncompressed size"); + builder.AppendLine(" No data descriptors"); + builder.AppendLine(); + return; } - if (zip64DataDescriptor == null) + for (int i = 0; i < entries.Length; i++) { - builder.AppendLine(" [ZIP64 Data Descriptor]: [NULL]"); + var entry = entries[i]; + + builder.AppendLine($" Data Descriptors Entry {i}"); + builder.AppendLine(entry.Signature, " Signature"); + builder.AppendLine(entry.CRC32, $" CRC-32"); + builder.AppendLine(entry.CompressedSize, $" Compressed size"); + builder.AppendLine(entry.UncompressedSize, $" Uncompressed size"); } - else + + builder.AppendLine(); + } + + private static void Print(StringBuilder builder, DataDescriptor64[]? entries) + { + builder.AppendLine(" ZIP64 Data Descriptors Information:"); + builder.AppendLine(" -------------------------"); + if (entries == null || entries.Length == 0) { - builder.AppendLine(zip64DataDescriptor.Signature, " [ZIP64 Data Descriptor] Signature"); - builder.AppendLine(zip64DataDescriptor.CRC32, " [ZIP64 Data Descriptor] CRC-32"); - builder.AppendLine(zip64DataDescriptor.CompressedSize, " [ZIP64 Data Descriptor] Compressed size"); - builder.AppendLine(zip64DataDescriptor.UncompressedSize, " [ZIP64 Data Descriptor] Uncompressed size"); + builder.AppendLine(" No ZIP64 data descriptors"); + builder.AppendLine(); + return; } + + for (int i = 0; i < entries.Length; i++) + { + var entry = entries[i]; + + builder.AppendLine($" ZIP64 Data Descriptors Entry {i}"); + builder.AppendLine(entry.Signature, " Signature"); + builder.AppendLine(entry.CRC32, $" CRC-32"); + builder.AppendLine(entry.CompressedSize, $" Compressed size"); + builder.AppendLine(entry.UncompressedSize, $" Uncompressed size"); + } + + builder.AppendLine(); } } }