diff --git a/SabreTools.Serialization/Deserializers/PKZIP.cs b/SabreTools.Serialization/Deserializers/PKZIP.cs index 102f32d6..82bfd6c6 100644 --- a/SabreTools.Serialization/Deserializers/PKZIP.cs +++ b/SabreTools.Serialization/Deserializers/PKZIP.cs @@ -63,54 +63,24 @@ namespace SabreTools.Serialization.Deserializers // Local File case LocalFileHeaderSignature: - #region Local File Header - - var localFileHeader = ParseLocalFileHeader(data); - if (localFileHeader == null) + if (!ParseLocalFile(data, + ref zip64, + out LocalFileHeader? localFileHeader, + out byte[]? encryptionHeader, + out byte[]? fileDatum, + out DataDescriptor? dataDescriptor, + out DataDescriptor64? dataDescriptor64)) + { 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 + // Add the local file validBlock = true; - fileData.Add(fileDatum); - - #endregion + localFileHeaders.Add(localFileHeader!); + encryptionHeaders.Add(encryptionHeader!); + fileData.Add(fileDatum!); + dataDescriptors.Add(dataDescriptor!); + zip64DataDescriptors.Add(dataDescriptor64!); break; @@ -163,56 +133,6 @@ namespace SabreTools.Serialization.Deserializers 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; } // If there was an invalid block @@ -232,16 +152,10 @@ namespace SabreTools.Serialization.Deserializers return null; } - // Assign the local file headers + // Assign the local files archive.LocalFileHeaders = [.. localFileHeaders]; - - // Assign the encryption headers archive.EncryptionHeaders = [.. encryptionHeaders]; - - // Assign the file data archive.FileData = [.. fileData]; - - // Assign the data descriptors archive.DataDescriptors = [.. dataDescriptors]; archive.ZIP64DataDescriptors = [.. zip64DataDescriptors]; @@ -466,6 +380,135 @@ namespace SabreTools.Serialization.Deserializers return record; } + /// + /// Parse a Stream into a local file + /// + /// Stream to parse + /// Filled local file on success, null on error + public static bool ParseLocalFile(Stream data, + ref bool? zip64, + out LocalFileHeader? localFileHeader, + out byte[]? encryptionHeader, + out byte[]? fileData, + out DataDescriptor? dataDescriptor, + out DataDescriptor64? dataDescriptor64) + { + // Set default values + localFileHeader = null; + encryptionHeader = null; + fileData = null; + dataDescriptor = null; + dataDescriptor64 = null; + + #region Local File Header + + localFileHeader = ParseLocalFileHeader(data); + if (localFileHeader == null) + return false; + + #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 + encryptionHeader = data.ReadBytes(12); + if (encryptionHeader.Length != 12) + return false; + } + else + { + // Add the empty encryption header + encryptionHeader = []; + } + + #endregion + + #region File Data + + // Try to read the file data + fileData = data.ReadBytes((int)localFileHeader.CompressedSize); + if (fileData.Length < localFileHeader.CompressedSize) + return false; + + #endregion + + #region Data Descriptor + + // Only attempt to read the descriptor if necessary +#if NET20 || NET35 + if ((localFileHeader.Flags & GeneralPurposeBitFlags.NoCRC) == 0) +#else + if (!localFileHeader.Flags.HasFlag(GeneralPurposeBitFlags.NoCRC)) +#endif + { + dataDescriptor = new DataDescriptor(); + dataDescriptor64 = new DataDescriptor64(); + return true; + } + + // Read the signature + long beforeSignature = data.Position; + uint signature = data.ReadUInt32LittleEndian(); + data.Seek(beforeSignature, SeekOrigin.Begin); + + // Don't fail if descriptor is missing + if (signature != DataDescriptorSignature) + { + dataDescriptor = new DataDescriptor(); + dataDescriptor64 = new DataDescriptor64(); + return true; + } + + 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 + dataDescriptor = new DataDescriptor(); + dataDescriptor64 = ParseDataDescriptor64(data); + if (dataDescriptor64 == null) + return false; + } + else + { + // Try to parse the data descriptor + dataDescriptor = ParseDataDescriptor(data); + dataDescriptor64 = new DataDescriptor64(); + if (dataDescriptor == null) + return false; + } + + #endregion + + return true; + } + /// /// Parse a Stream into a local file header ///