Files
gmipf f6ade446c6 Locate the PKZIP file data instead of reading it into the model (#102)
* Locate the PKZIP file data instead of reading it into the model

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Keep only the offset of the PKZIP file data

The length is the compressed size from the local file header or the ZIP64
extended field, which the printer already shows.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Keep the file data property as a comment for documentation

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 21:04:01 -04:00

1501 lines
68 KiB
C#

using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using SabreTools.Data.Models.PKZIP;
using SabreTools.IO.Extensions;
using SabreTools.Matching;
using SabreTools.Numerics.Extensions;
using static SabreTools.Data.Models.PKZIP.Constants;
#pragma warning disable IDE0017 // Simplify object initialization
namespace SabreTools.Serialization.Readers
{
public class PKZIP : BaseBinaryReader<Archive>
{
/// <inheritdoc/>
public override Archive? Deserialize(Stream? data)
{
// If the data is invalid
if (data is null || !data.CanRead)
return null;
try
{
// Cache the current offset
long initialOffset = data.Position;
var archive = new Archive();
// Setup all of the collections
var localFiles = new List<LocalFile>();
var cdrs = new List<CentralDirectoryFileHeader>();
// Read all blocks
do
{
// Read the signature
long beforeSignature = data.Position;
uint signature = data.ReadUInt32LittleEndian();
data.SeekIfPossible(beforeSignature, SeekOrigin.Begin);
// Switch based on the signature found
bool validBlock = false;
switch (signature)
{
// Central Directory File Header
case CentralDirectoryFileHeaderSignature:
var cdr = ParseCentralDirectoryFileHeader(data, Debug);
if (cdr is null)
return null;
// Add the central directory record
validBlock = true;
cdrs.Add(cdr);
break;
// Local File
case LocalFileHeaderSignature:
var lf = ParseLocalFile(data, Debug);
if (lf is null)
return null;
// Add the local file
validBlock = true;
localFiles.Add(lf);
break;
// TODO: Implement
case DigitalSignatureSignature:
break;
// End of Central Directory Record
case EndOfCentralDirectoryRecordSignature:
var eocdr = ParseEndOfCentralDirectoryRecord(data);
if (eocdr is null)
return null;
// 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 is null)
return null;
// Assign the ZIP64 end of central directory record
validBlock = true;
archive.ZIP64EndOfCentralDirectoryRecord = eocdr64;
break;
// ZIP64 End of Central Directory Locator
case EndOfCentralDirectoryLocator64Signature:
var eocdl64 = ParseEndOfCentralDirectoryLocator64(data);
if (eocdl64 is null)
return null;
// Assign the ZIP64 end of central directory record
validBlock = true;
archive.ZIP64EndOfCentralDirectoryLocator = eocdl64;
break;
// Archive Extra Data Record
case ArchiveExtraDataRecordSignature:
var aedr = ParseArchiveExtraDataRecord(data);
if (aedr is null)
return null;
// Assign the archive extra data record
validBlock = true;
archive.ArchiveExtraDataRecord = aedr;
break;
default:
// TODO: Log invalid values
break;
}
// If there was an invalid block
if (!validBlock)
break;
} while (data.Position < data.Length);
// If no blocks were read
if (localFiles.Count == 0 && cdrs.Count == 0)
return null;
// Assign the local files
archive.LocalFiles = [.. localFiles];
// Assign the central directory records
archive.CentralDirectoryHeaders = [.. cdrs];
return archive;
}
catch
{
// Ignore the actual error
return null;
}
}
/// <summary>
/// Parse a Stream into an archive extra data record
/// </summary>
/// <param name="data">Stream to parse</param>
/// <returns>Filled archive extra data record on success, null on error</returns>
public static ArchiveExtraDataRecord? ParseArchiveExtraDataRecord(Stream data)
{
var obj = new ArchiveExtraDataRecord();
obj.Signature = data.ReadUInt32LittleEndian();
if (obj.Signature != ArchiveExtraDataRecordSignature)
return null;
obj.ExtraFieldLength = data.ReadUInt32LittleEndian();
if (obj.ExtraFieldLength > 0 && data.Position + obj.ExtraFieldLength <= data.Length)
{
byte[] extraBytes = data.ReadBytes((int)obj.ExtraFieldLength);
if (extraBytes.Length != obj.ExtraFieldLength)
return null;
obj.ExtraFieldData = extraBytes;
}
return obj;
}
/// <summary>
/// Parse a Stream into a central directory file header
/// </summary>
/// <param name="data">Stream to parse</param>
/// <param name="includeDebug">True to include debug data, false otherwise</param>
/// <returns>Filled central directory file header on success, null on error</returns>
public static CentralDirectoryFileHeader? ParseCentralDirectoryFileHeader(Stream data, bool includeDebug = false)
{
var obj = new CentralDirectoryFileHeader();
obj.Signature = data.ReadUInt32LittleEndian();
if (obj.Signature != CentralDirectoryFileHeaderSignature)
return null;
obj.HostSystem = (HostSystem)data.ReadByteValue();
obj.VersionMadeBy = data.ReadByteValue();
obj.VersionNeededToExtract = 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();
obj.FileCommentLength = data.ReadUInt16LittleEndian();
obj.DiskNumberStart = data.ReadUInt16LittleEndian();
obj.InternalFileAttributes = (InternalFileAttributes)data.ReadUInt16LittleEndian();
obj.ExternalFileAttributes = data.ReadUInt32LittleEndian();
obj.RelativeOffsetOfLocalHeader = data.ReadUInt32LittleEndian();
#if NET20 || NET35
bool utf8 = (obj.Flags & GeneralPurposeBitFlags.LanguageEncodingFlag) != 0;
#else
bool utf8 = obj.Flags.HasFlag(GeneralPurposeBitFlags.LanguageEncodingFlag);
#endif
if (obj.FileNameLength > 0 && data.Position + obj.FileNameLength <= data.Length)
{
byte[] filenameBytes = data.ReadBytes(obj.FileNameLength);
if (filenameBytes.Length != obj.FileNameLength)
return null;
if (utf8)
obj.FileName = Encoding.UTF8.GetString(filenameBytes);
else
obj.FileName = Encoding.ASCII.GetString(filenameBytes);
}
if (obj.ExtraFieldLength > 0 && data.Position + obj.ExtraFieldLength <= data.Length)
{
byte[] extraBytes = data.ReadBytes(obj.ExtraFieldLength);
if (extraBytes.Length != obj.ExtraFieldLength)
return null;
obj.ExtraFields = ParseExtraFields(obj, extraBytes, includeDebug);
}
if (obj.FileCommentLength > 0 && data.Position + obj.FileCommentLength <= data.Length)
{
byte[] commentBytes = data.ReadBytes(obj.FileCommentLength);
if (commentBytes.Length != obj.FileCommentLength)
return null;
if (utf8)
obj.FileComment = Encoding.UTF8.GetString(commentBytes);
else
obj.FileComment = Encoding.ASCII.GetString(commentBytes);
}
return obj;
}
/// <summary>
/// Parse a Stream into a data descriptor
/// </summary>
/// <param name="data">Stream to parse</param>
/// <returns>Filled data descriptor on success, null on error</returns>
public static DataDescriptor? ParseDataDescriptor(Stream data)
{
var obj = new DataDescriptor();
// Cache the current position
long currentPosition = data.Position;
// Check if the block is a 12- or 16-byte
bool isShort = false;
if (data.Position + 12 == data.Length)
{
isShort = true;
}
else
{
data.SeekIfPossible(12, SeekOrigin.Current);
byte[] nextBlock = data.ReadBytes(2);
data.SeekIfPossible(currentPosition, SeekOrigin.Begin);
if (nextBlock.EqualsExactly([0x50, 0x4B]))
isShort = true;
}
// If the 16-byte variant
if (!isShort)
{
obj.Signature = data.ReadUInt32LittleEndian();
if (obj.Signature != DataDescriptorSignature)
return null;
}
obj.CRC32 = data.ReadUInt32LittleEndian();
obj.CompressedSize = data.ReadUInt32LittleEndian();
obj.UncompressedSize = data.ReadUInt32LittleEndian();
return obj;
}
/// <summary>
/// Parse a Stream into a ZIP64 data descriptor
/// </summary>
/// <param name="data">Stream to parse</param>
/// <returns>Filled ZIP64 data descriptor on success, null on error</returns>
public static DataDescriptor64? ParseDataDescriptor64(Stream data)
{
var obj = new DataDescriptor64();
// Cache the current position
long currentPosition = data.Position;
// Check if the block is a 20- or 24-byte
bool isShort = false;
if (data.Position + 20 == data.Length)
{
isShort = true;
}
else
{
data.SeekIfPossible(20, SeekOrigin.Current);
byte[] nextBlock = data.ReadBytes(2);
data.SeekIfPossible(currentPosition, SeekOrigin.Begin);
if (nextBlock.EqualsExactly([0x50, 0x4B]))
isShort = true;
}
// If the 24-byte variant
if (!isShort)
{
obj.Signature = data.ReadUInt32LittleEndian();
if (obj.Signature != DataDescriptorSignature)
return null;
}
obj.CRC32 = data.ReadUInt32LittleEndian();
obj.CompressedSize = data.ReadUInt64LittleEndian();
obj.UncompressedSize = data.ReadUInt64LittleEndian();
return obj;
}
/// <summary>
/// Parse a Stream into a ZIP64 end of central directory locator
/// </summary>
/// <param name="data">Stream to parse</param>
/// <returns>Filled ZIP64 end of central directory locator on success, null on error</returns>
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.SeekIfPossible(-4, SeekOrigin.Current);
obj.StartDiskNumber = data.ReadUInt32LittleEndian();
obj.CentralDirectoryOffset = data.ReadUInt64LittleEndian();
obj.TotalDisks = data.ReadUInt32LittleEndian();
return obj;
}
/// <summary>
/// Parse a Stream into an end of central directory record
/// </summary>
/// <param name="data">Stream to parse</param>
/// <returns>Filled end of central directory record on success, null on error</returns>
public static EndOfCentralDirectoryRecord? ParseEndOfCentralDirectoryRecord(Stream data)
{
var obj = new EndOfCentralDirectoryRecord();
obj.Signature = data.ReadUInt32LittleEndian();
if (obj.Signature != EndOfCentralDirectoryRecordSignature)
return null;
obj.DiskNumber = data.ReadUInt16LittleEndian();
obj.StartDiskNumber = data.ReadUInt16LittleEndian();
obj.TotalEntriesOnDisk = data.ReadUInt16LittleEndian();
obj.TotalEntries = data.ReadUInt16LittleEndian();
obj.CentralDirectorySize = data.ReadUInt32LittleEndian();
obj.CentralDirectoryOffset = data.ReadUInt32LittleEndian();
obj.FileCommentLength = data.ReadUInt16LittleEndian();
if (obj.FileCommentLength > 0 && data.Position + obj.FileCommentLength <= data.Length)
{
byte[] commentBytes = data.ReadBytes(obj.FileCommentLength);
if (commentBytes.Length != obj.FileCommentLength)
return null;
obj.FileComment = Encoding.ASCII.GetString(commentBytes);
}
return obj;
}
/// <summary>
/// Parse a Stream into a ZIP64 end of central directory record
/// </summary>
/// <param name="data">Stream to parse</param>
/// <returns>Filled ZIP64 end of central directory record on success, null on error</returns>
public static EndOfCentralDirectoryRecord64? ParseEndOfCentralDirectoryRecord64(Stream data)
{
var obj = new EndOfCentralDirectoryRecord64();
obj.Signature = data.ReadUInt32LittleEndian();
if (obj.Signature != EndOfCentralDirectoryRecord64Signature)
return null;
obj.DirectoryRecordSize = data.ReadUInt64LittleEndian();
obj.HostSystem = (HostSystem)data.ReadByteValue();
obj.VersionMadeBy = data.ReadByteValue();
obj.VersionNeededToExtract = data.ReadUInt16LittleEndian();
obj.DiskNumber = data.ReadUInt32LittleEndian();
obj.StartDiskNumber = data.ReadUInt32LittleEndian();
obj.TotalEntriesOnDisk = data.ReadUInt64LittleEndian();
obj.TotalEntries = data.ReadUInt64LittleEndian();
obj.CentralDirectorySize = data.ReadUInt64LittleEndian();
obj.CentralDirectoryOffset = data.ReadUInt64LittleEndian();
// TODO: Handle the ExtensibleDataSector -- How to detect if exists?
return obj;
}
/// <summary>
/// Parse a Stream into a local file
/// </summary>
/// <param name="data">Stream to parse</param>
/// <param name="includeDebug">True to include debug data, false otherwise</param>
/// <returns>Filled local file on success, null on error</returns>
public static LocalFile? ParseLocalFile(Stream data, bool includeDebug = false)
{
var obj = new LocalFile();
#region Local File Header
// Try to read the header
var localFileHeader = ParseLocalFileHeader(data, includeDebug);
if (localFileHeader is null)
return null;
// Assign the header
obj.LocalFileHeader = localFileHeader;
ulong compressedSize = localFileHeader.CompressedSize;
if (localFileHeader.ExtraFields is not null)
{
foreach (var field in localFileHeader.ExtraFields)
{
if (field is not Zip64ExtendedInformationExtraField infoField)
continue;
if (infoField.CompressedSize is null)
continue;
compressedSize = infoField.CompressedSize.Value;
}
}
#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[] encryptionHeaders = data.ReadBytes(12);
if (encryptionHeaders.Length != 12)
return null;
// Set the encryption headers
obj.EncryptionHeaders = encryptionHeaders;
}
else
{
// Add the empty encryption header
obj.EncryptionHeaders = [];
}
#endregion
#region File Data
// The file data is skipped, so a large archive is not read into memory
if (compressedSize > (ulong)(data.Length - data.Position))
return null;
// Set the location of the file data
obj.FileDataOffset = data.Position;
data.SeekIfPossible((long)compressedSize, SeekOrigin.Current);
#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
{
obj.DataDescriptor = new DataDescriptor();
obj.ZIP64DataDescriptor = new DataDescriptor64();
return obj;
}
// Read the signature
long beforeSignature = data.Position;
uint signature = data.ReadUInt32LittleEndian();
data.SeekIfPossible(beforeSignature, SeekOrigin.Begin);
// Don't fail if descriptor is missing
if (signature != DataDescriptorSignature)
{
obj.DataDescriptor = new DataDescriptor();
obj.ZIP64DataDescriptor = new DataDescriptor64();
return obj;
}
// Determine if the entry is ZIP64
bool zip64 = IsZip64Descriptor(data);
// Try to parse the correct data descriptor
if (zip64)
{
obj.DataDescriptor = new DataDescriptor();
obj.ZIP64DataDescriptor = ParseDataDescriptor64(data);
if (obj.ZIP64DataDescriptor is null)
return null;
}
else
{
obj.DataDescriptor = ParseDataDescriptor(data);
obj.ZIP64DataDescriptor = new DataDescriptor64();
if (obj.DataDescriptor is null)
return null;
}
#endregion
return obj;
}
/// <summary>
/// Parse a Stream into a local file header
/// </summary>
/// <param name="data">Stream to parse</param>
/// <param name="includeDebug">True to include debug data, false otherwise</param>
/// <returns>Filled local file header on success, null on error</returns>
public static LocalFileHeader? ParseLocalFileHeader(Stream data, bool includeDebug = false)
{
var obj = new LocalFileHeader();
obj.Signature = data.ReadUInt32LittleEndian();
if (obj.Signature != LocalFileHeaderSignature)
return null;
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 NET20 || NET35
bool utf8 = (obj.Flags & GeneralPurposeBitFlags.LanguageEncodingFlag) != 0;
#else
bool utf8 = obj.Flags.HasFlag(GeneralPurposeBitFlags.LanguageEncodingFlag);
#endif
if (obj.FileNameLength > 0 && data.Position + obj.FileNameLength <= data.Length)
{
byte[] filenameBytes = data.ReadBytes(obj.FileNameLength);
if (filenameBytes.Length != obj.FileNameLength)
return null;
if (utf8)
obj.FileName = Encoding.UTF8.GetString(filenameBytes);
else
obj.FileName = Encoding.ASCII.GetString(filenameBytes);
}
if (obj.ExtraFieldLength > 0 && data.Position + obj.ExtraFieldLength <= data.Length)
{
byte[] extraBytes = data.ReadBytes(obj.ExtraFieldLength);
if (extraBytes.Length != obj.ExtraFieldLength)
return null;
obj.ExtraFields = ParseExtraFields(obj, extraBytes, includeDebug);
}
return obj;
}
#region Extras Fields
/// <summary>
/// Process all extensible data fields in a central directory file extras block
/// </summary>
/// <param name="header">Central directory file header</param>
/// <param name="data">Byte array to parse</param>
/// <param name="includeDebug">True to include debug data, false otherwise</param>
/// <returns>Array of data fields on success, null otherwise</returns>
public static ExtensibleDataField[]? ParseExtraFields(CentralDirectoryFileHeader header, byte[]? data, bool includeDebug = false)
{
if (data is null)
return null;
List<ExtensibleDataField> fields = [];
int offset = 0;
while (offset < data.Length)
{
// Peek at the next header ID
HeaderID id = (HeaderID)data.ReadUInt16LittleEndian(ref offset);
offset -= 2;
// Read based on the ID
ExtensibleDataField? field = id switch
{
HeaderID.Zip64ExtendedInformation => ParseZip64ExtendedInformationExtraField(data, ref offset, header),
HeaderID.AVInfo => ParseUnknownExtraField(data, ref offset, includeDebug), // TODO: Implement model
HeaderID.ExtendedLanguageEncodingData => ParseUnknownExtraField(data, ref offset, includeDebug), // TODO: Implement model
HeaderID.OS2 => ParseOS2ExtraField(data, ref offset),
HeaderID.NTFS => ParseNTFSExtraField(data, ref offset),
HeaderID.OpenVMS => ParseOpenVMSExtraField(data, ref offset),
HeaderID.UNIX => ParseUnixExtraField(data, ref offset),
HeaderID.FileStreamFork => ParseUnknownExtraField(data, ref offset, includeDebug), // TODO: Implement
HeaderID.PatchDescriptor => ParsePatchDescriptorExtraField(data, ref offset),
HeaderID.PKCSStore => ParsePKCS7Store(data, ref offset),
HeaderID.X509IndividualFile => ParseX509IndividualFile(data, ref offset),
HeaderID.X509CentralDirectory => ParseX509CentralDirectory(data, ref offset),
HeaderID.StrongEncryptionHeader => ParseStrongEncryptionHeader(data, ref offset),
HeaderID.RecordManagementControls => ParseRecordManagementControls(data, ref offset),
HeaderID.PKCSCertificateList => ParsePKCS7EncryptionRecipientCertificateList(data, ref offset),
HeaderID.Timestamp => ParseUnknownExtraField(data, ref offset, includeDebug), // TODO: Implement
HeaderID.PolicyDecryptionKey => ParsePolicyDecryptionKeyRecordExtraField(data, ref offset),
HeaderID.SmartcryptKeyProvider => ParseKeyProviderRecordExtraField(data, ref offset),
HeaderID.SmartcryptPolicyKeyData => ParsePolicyKeyDataRecordRecordExtraField(data, ref offset),
HeaderID.IBMS390AttributesUncompressed => ParseAS400ExtraFieldAttribute(data, ref offset),
HeaderID.IBMS390AttributesCompressed => ParseUnknownExtraField(data, ref offset, includeDebug), // TODO: Implement
HeaderID.POSZIP4690 => ParseUnknownExtraField(data, ref offset, includeDebug), // TODO: Implement
HeaderID.Macintosh => ParseUnknownExtraField(data, ref offset, includeDebug), // TODO: Implement
HeaderID.PixarUSD => ParseUnknownExtraField(data, ref offset, includeDebug), // TODO: Implement
HeaderID.ZipItMacintosh => ParseZipItMacintoshExtraField(data, ref offset),
HeaderID.ZipItMacintosh135Plus => ParseZipItMacintoshShortFileExtraField(data, ref offset),
HeaderID.ZipItMacintosh135PlusAlt => ParseZipItMacintoshShortDirectoryExtraField(data, ref offset),
HeaderID.InfoZIPMacintosh => ParseUnknownExtraField(data, ref offset, includeDebug), // TODO: Implement
HeaderID.AcornSparkFS => ParseUnknownExtraField(data, ref offset, includeDebug), // TODO: Implement
HeaderID.WindowsNTSecurityDescriptor => ParseUnknownExtraField(data, ref offset, includeDebug), // TODO: Implement
HeaderID.VMCMS => ParseUnknownExtraField(data, ref offset, includeDebug), // TODO: Implement
HeaderID.MVS => ParseUnknownExtraField(data, ref offset, includeDebug), // TODO: Implement
HeaderID.THEOSold => ParseUnknownExtraField(data, ref offset, includeDebug), // TODO: Implement
HeaderID.FWKCSMD5 => ParseFWKCSMD5ExtraField(data, ref offset),
HeaderID.OS2AccessControlList => ParseUnknownExtraField(data, ref offset, includeDebug), // TODO: Implement
HeaderID.InfoZIPOpenVMS => ParseUnknownExtraField(data, ref offset, includeDebug), // TODO: Implement
HeaderID.MacintoshSmartzip => ParseUnknownExtraField(data, ref offset, includeDebug), // TODO: Implement
HeaderID.XceedOriginalLocation => ParseUnknownExtraField(data, ref offset, includeDebug), // TODO: Implement
HeaderID.ADSVS => ParseUnknownExtraField(data, ref offset, includeDebug), // TODO: Implement
HeaderID.ExtendedTimestamp => ParseUnknownExtraField(data, ref offset, includeDebug), // TODO: Implement
HeaderID.XceedUnicode => ParseUnknownExtraField(data, ref offset, includeDebug), // TODO: Implement
HeaderID.InfoZIPUNIX => ParseUnknownExtraField(data, ref offset, includeDebug), // TODO: Implement
HeaderID.InfoZIPUnicodeComment => ParseInfoZIPUnicodeCommentExtraField(data, ref offset),
HeaderID.BeOSBeBox => ParseUnknownExtraField(data, ref offset, includeDebug), // TODO: Implement
HeaderID.THEOS => ParseUnknownExtraField(data, ref offset, includeDebug), // TODO: Implement
HeaderID.InfoZIPUnicodePath => ParseInfoZIPUnicodePathExtraField(data, ref offset),
HeaderID.AtheOSSyllable => ParseUnknownExtraField(data, ref offset, includeDebug), // TODO: Implement
HeaderID.ASiUNIX => ParseUnknownExtraField(data, ref offset, includeDebug), // TODO: Implement
HeaderID.InfoZIPUNIXNew => ParseUnknownExtraField(data, ref offset, includeDebug), // TODO: Implement
HeaderID.InfoZIPUNIXNewer => ParseUnknownExtraField(data, ref offset, includeDebug), // TODO: Implement
HeaderID.DataStreamAlignment => ParseDataStreamAlignment(data, ref offset),
HeaderID.MicrosoftOpenPackagingGrowthHint => ParseMicrosoftOpenPackagingGrowthHint(data, ref offset),
HeaderID.JavaJAR => ParseUnknownExtraField(data, ref offset, includeDebug), // TODO: Implement
HeaderID.AndroidZIPAlignment => ParseUnknownExtraField(data, ref offset, includeDebug), // TODO: Implement
HeaderID.KoreanZIPCodePage => ParseUnknownExtraField(data, ref offset, includeDebug), // TODO: Implement
HeaderID.SMSQDOS => ParseUnknownExtraField(data, ref offset, includeDebug), // TODO: Implement
HeaderID.AExEncryptionStructure => ParseUnknownExtraField(data, ref offset, includeDebug), // TODO: Implement
HeaderID.Unknown => ParseUnknownExtraField(data, ref offset, includeDebug), // TODO: Implement
_ => ParseUnknownExtraField(data, ref offset, includeDebug),
};
if (field is not null)
fields.Add(field);
}
return [.. fields];
}
/// <summary>
/// Process all extensible data fields in a local file extras block
/// </summary>
/// <param name="header">Local file header</param>
/// <param name="data">Byte array to parse</param>
/// <param name="includeDebug">True to include debug data, false otherwise</param>
/// <returns>Array of data fields on success, null otherwise</returns>
public static ExtensibleDataField[]? ParseExtraFields(LocalFileHeader header, byte[]? data, bool includeDebug = false)
{
if (data is null)
return null;
List<ExtensibleDataField> fields = [];
int offset = 0;
while (offset < data.Length)
{
// Peek at the next header ID
HeaderID id = (HeaderID)data.ReadUInt16LittleEndian(ref offset);
offset -= 2;
// Read based on the ID
ExtensibleDataField? field = id switch
{
HeaderID.Zip64ExtendedInformation => ParseZip64ExtendedInformationExtraField(data, ref offset, header),
HeaderID.AVInfo => ParseUnknownExtraField(data, ref offset, includeDebug), // TODO: Implement model
HeaderID.ExtendedLanguageEncodingData => ParseUnknownExtraField(data, ref offset, includeDebug), // TODO: Implement model
HeaderID.OS2 => ParseOS2ExtraField(data, ref offset),
HeaderID.NTFS => ParseNTFSExtraField(data, ref offset),
HeaderID.OpenVMS => ParseOpenVMSExtraField(data, ref offset),
HeaderID.UNIX => ParseUnixExtraField(data, ref offset),
HeaderID.FileStreamFork => ParseUnknownExtraField(data, ref offset, includeDebug), // TODO: Implement
HeaderID.PatchDescriptor => ParsePatchDescriptorExtraField(data, ref offset),
HeaderID.PKCSStore => ParsePKCS7Store(data, ref offset),
HeaderID.X509IndividualFile => ParseX509IndividualFile(data, ref offset),
HeaderID.X509CentralDirectory => ParseX509CentralDirectory(data, ref offset),
HeaderID.StrongEncryptionHeader => ParseStrongEncryptionHeader(data, ref offset),
HeaderID.RecordManagementControls => ParseRecordManagementControls(data, ref offset),
HeaderID.PKCSCertificateList => ParsePKCS7EncryptionRecipientCertificateList(data, ref offset),
HeaderID.Timestamp => ParseUnknownExtraField(data, ref offset, includeDebug), // TODO: Implement
HeaderID.PolicyDecryptionKey => ParsePolicyDecryptionKeyRecordExtraField(data, ref offset),
HeaderID.SmartcryptKeyProvider => ParseKeyProviderRecordExtraField(data, ref offset),
HeaderID.SmartcryptPolicyKeyData => ParsePolicyKeyDataRecordRecordExtraField(data, ref offset),
HeaderID.IBMS390AttributesUncompressed => ParseAS400ExtraFieldAttribute(data, ref offset),
HeaderID.IBMS390AttributesCompressed => ParseUnknownExtraField(data, ref offset, includeDebug), // TODO: Implement
HeaderID.POSZIP4690 => ParseUnknownExtraField(data, ref offset, includeDebug), // TODO: Implement
HeaderID.Macintosh => ParseUnknownExtraField(data, ref offset, includeDebug), // TODO: Implement
HeaderID.PixarUSD => ParseUnknownExtraField(data, ref offset, includeDebug), // TODO: Implement
HeaderID.ZipItMacintosh => ParseZipItMacintoshExtraField(data, ref offset),
HeaderID.ZipItMacintosh135Plus => ParseZipItMacintoshShortFileExtraField(data, ref offset),
HeaderID.ZipItMacintosh135PlusAlt => ParseZipItMacintoshShortDirectoryExtraField(data, ref offset),
HeaderID.InfoZIPMacintosh => ParseUnknownExtraField(data, ref offset, includeDebug), // TODO: Implement
HeaderID.AcornSparkFS => ParseUnknownExtraField(data, ref offset, includeDebug), // TODO: Implement
HeaderID.WindowsNTSecurityDescriptor => ParseUnknownExtraField(data, ref offset, includeDebug), // TODO: Implement
HeaderID.VMCMS => ParseUnknownExtraField(data, ref offset, includeDebug), // TODO: Implement
HeaderID.MVS => ParseUnknownExtraField(data, ref offset, includeDebug), // TODO: Implement
HeaderID.THEOSold => ParseUnknownExtraField(data, ref offset, includeDebug), // TODO: Implement
HeaderID.FWKCSMD5 => ParseFWKCSMD5ExtraField(data, ref offset),
HeaderID.OS2AccessControlList => ParseUnknownExtraField(data, ref offset, includeDebug), // TODO: Implement
HeaderID.InfoZIPOpenVMS => ParseUnknownExtraField(data, ref offset, includeDebug), // TODO: Implement
HeaderID.MacintoshSmartzip => ParseUnknownExtraField(data, ref offset, includeDebug), // TODO: Implement
HeaderID.XceedOriginalLocation => ParseUnknownExtraField(data, ref offset, includeDebug), // TODO: Implement
HeaderID.ADSVS => ParseUnknownExtraField(data, ref offset, includeDebug), // TODO: Implement
HeaderID.ExtendedTimestamp => ParseUnknownExtraField(data, ref offset, includeDebug), // TODO: Implement
HeaderID.XceedUnicode => ParseUnknownExtraField(data, ref offset, includeDebug), // TODO: Implement
HeaderID.InfoZIPUNIX => ParseUnknownExtraField(data, ref offset, includeDebug), // TODO: Implement
HeaderID.InfoZIPUnicodeComment => ParseInfoZIPUnicodeCommentExtraField(data, ref offset),
HeaderID.BeOSBeBox => ParseUnknownExtraField(data, ref offset, includeDebug), // TODO: Implement
HeaderID.THEOS => ParseUnknownExtraField(data, ref offset, includeDebug), // TODO: Implement
HeaderID.InfoZIPUnicodePath => ParseInfoZIPUnicodePathExtraField(data, ref offset),
HeaderID.AtheOSSyllable => ParseUnknownExtraField(data, ref offset, includeDebug), // TODO: Implement
HeaderID.ASiUNIX => ParseUnknownExtraField(data, ref offset, includeDebug), // TODO: Implement
HeaderID.InfoZIPUNIXNew => ParseUnknownExtraField(data, ref offset, includeDebug), // TODO: Implement
HeaderID.InfoZIPUNIXNewer => ParseUnknownExtraField(data, ref offset, includeDebug), // TODO: Implement
HeaderID.DataStreamAlignment => ParseDataStreamAlignment(data, ref offset),
HeaderID.MicrosoftOpenPackagingGrowthHint => ParseMicrosoftOpenPackagingGrowthHint(data, ref offset),
HeaderID.JavaJAR => ParseUnknownExtraField(data, ref offset, includeDebug), // TODO: Implement
HeaderID.AndroidZIPAlignment => ParseUnknownExtraField(data, ref offset, includeDebug), // TODO: Implement
HeaderID.KoreanZIPCodePage => ParseUnknownExtraField(data, ref offset, includeDebug), // TODO: Implement
HeaderID.SMSQDOS => ParseUnknownExtraField(data, ref offset, includeDebug), // TODO: Implement
HeaderID.AExEncryptionStructure => ParseUnknownExtraField(data, ref offset, includeDebug), // TODO: Implement
HeaderID.Unknown => ParseUnknownExtraField(data, ref offset, includeDebug), // TODO: Implement
_ => ParseUnknownExtraField(data, ref offset, includeDebug),
};
if (field is not null)
fields.Add(field);
}
return [.. fields];
}
/// <summary>
/// Parse a Stream into an unknown extras field
/// </summary>
/// <param name="data">Byte array to parse</param>
/// <param name="offset">Offset into the byte array</param>
/// <param name="includeDebug">True to include debug data, false otherwise</param>
/// <returns>Filled unknown extras field on success, null on error</returns>
private static UnknownExtraField? ParseUnknownExtraField(byte[] data, ref int offset, bool includeDebug)
{
var obj = new UnknownExtraField();
obj.HeaderID = (HeaderID)data.ReadUInt16LittleEndian(ref offset);
obj.DataSize = data.ReadUInt16LittleEndian(ref offset);
if (obj.DataSize > data.Length - offset)
{
ushort remainingSize = (ushort)(data.Length - offset);
if (includeDebug) Console.WriteLine($"Extra field of type '{obj.HeaderID}' requested {obj.DataSize} bytes, but only {remainingSize} remain");
obj.DataSize = remainingSize;
}
if (obj.DataSize > 0)
obj.Data = data.ReadBytes(ref offset, obj.DataSize);
return obj;
}
/// <summary>
/// Parse a Stream into a Zip64ExtendedInformationExtraField
/// </summary>
/// <param name="data">Byte array to parse</param>
/// <param name="offset">Offset into the byte array</param>
/// <returns>Filled Zip64ExtendedInformationExtraField on success, null on error</returns>
private static Zip64ExtendedInformationExtraField? ParseZip64ExtendedInformationExtraField(byte[] data, ref int offset, CentralDirectoryFileHeader header)
{
var obj = new Zip64ExtendedInformationExtraField();
obj.HeaderID = (HeaderID)data.ReadUInt16LittleEndian(ref offset);
if (obj.HeaderID != HeaderID.Zip64ExtendedInformation)
return null;
obj.DataSize = data.ReadUInt16LittleEndian(ref offset);
int bytesRemaining = obj.DataSize;
if (header.UncompressedSize == uint.MaxValue && bytesRemaining >= 8)
{
obj.OriginalSize = data.ReadUInt64LittleEndian(ref offset);
bytesRemaining -= 8;
}
if (header.CompressedSize == uint.MaxValue && bytesRemaining >= 8)
{
obj.CompressedSize = data.ReadUInt64LittleEndian(ref offset);
bytesRemaining -= 8;
}
if (header.RelativeOffsetOfLocalHeader == uint.MaxValue && bytesRemaining >= 8)
{
obj.RelativeHeaderOffset = data.ReadUInt64LittleEndian(ref offset);
bytesRemaining -= 8;
}
if (header.DiskNumberStart == ushort.MaxValue && bytesRemaining >= 4)
{
obj.DiskStartNumber = data.ReadUInt32LittleEndian(ref offset);
//bytesRemaining -= 4;
}
return obj;
}
/// <summary>
/// Parse a Stream into a Zip64ExtendedInformationExtraField
/// </summary>
/// <param name="data">Byte array to parse</param>
/// <param name="offset">Offset into the byte array</param>
/// <returns>Filled Zip64ExtendedInformationExtraField on success, null on error</returns>
private static Zip64ExtendedInformationExtraField? ParseZip64ExtendedInformationExtraField(byte[] data, ref int offset, LocalFileHeader header)
{
var obj = new Zip64ExtendedInformationExtraField();
obj.HeaderID = (HeaderID)data.ReadUInt16LittleEndian(ref offset);
if (obj.HeaderID != HeaderID.Zip64ExtendedInformation)
return null;
obj.DataSize = data.ReadUInt16LittleEndian(ref offset);
int bytesRemaining = obj.DataSize;
if (header.UncompressedSize == uint.MaxValue)
{
obj.OriginalSize = data.ReadUInt64LittleEndian(ref offset);
bytesRemaining -= 8;
}
if (header.CompressedSize == uint.MaxValue)
{
obj.CompressedSize = data.ReadUInt64LittleEndian(ref offset);
bytesRemaining -= 8;
}
// TODO: These rely on values from the central directory
if (bytesRemaining >= 8)
{
obj.RelativeHeaderOffset = data.ReadUInt64LittleEndian(ref offset);
bytesRemaining -= 8;
}
if (bytesRemaining >= 4)
obj.DiskStartNumber = data.ReadUInt32LittleEndian(ref offset);
return obj;
}
/// <summary>
/// Parse a Stream into an OS2ExtraField
/// </summary>
/// <param name="data">Byte array to parse</param>
/// <param name="offset">Offset into the byte array</param>
/// <returns>Filled OS2ExtraField on success, null on error</returns>
private static OS2ExtraField? ParseOS2ExtraField(byte[] data, ref int offset)
{
var obj = new OS2ExtraField();
obj.HeaderID = (HeaderID)data.ReadUInt16LittleEndian(ref offset);
if (obj.HeaderID != HeaderID.OS2)
return null;
obj.DataSize = data.ReadUInt16LittleEndian(ref offset);
obj.UncompressedBlockSize = data.ReadUInt32LittleEndian(ref offset);
obj.CompressionType = data.ReadUInt16LittleEndian(ref offset);
obj.CRC32 = data.ReadUInt32LittleEndian(ref offset);
obj.Data = data.ReadBytes(ref offset, obj.DataSize - 10);
return obj;
}
/// <summary>
/// Parse a Stream into a NTFSExtraField
/// </summary>
/// <param name="data">Byte array to parse</param>
/// <param name="offset">Offset into the byte array</param>
/// <returns>Filled NTFSExtraField on success, null on error</returns>
private static NTFSExtraField? ParseNTFSExtraField(byte[] data, ref int offset)
{
var obj = new NTFSExtraField();
obj.HeaderID = (HeaderID)data.ReadUInt16LittleEndian(ref offset);
if (obj.HeaderID != HeaderID.NTFS)
return null;
obj.DataSize = data.ReadUInt16LittleEndian(ref offset);
obj.Reserved = data.ReadUInt32LittleEndian(ref offset);
List<TagSizeVar> entries = [];
int bytesRemaining = obj.DataSize - 4;
while (bytesRemaining > 0)
{
var entry = new TagSizeVar();
entry.Tag = data.ReadUInt16LittleEndian(ref offset);
entry.Size = data.ReadUInt16LittleEndian(ref offset);
entry.Var = data.ReadBytes(ref offset, entry.Size);
entries.Add(entry);
bytesRemaining -= 4 + entry.Size;
}
obj.TagSizeVars = [.. entries];
return obj;
}
/// <summary>
/// Parse a Stream into an OpenVMSExtraField
/// </summary>
/// <param name="data">Byte array to parse</param>
/// <param name="offset">Offset into the byte array</param>
/// <returns>Filled OpenVMSExtraField on success, null on error</returns>
private static OpenVMSExtraField? ParseOpenVMSExtraField(byte[] data, ref int offset)
{
var obj = new OpenVMSExtraField();
obj.HeaderID = (HeaderID)data.ReadUInt16LittleEndian(ref offset);
if (obj.HeaderID != HeaderID.OpenVMS)
return null;
obj.DataSize = data.ReadUInt16LittleEndian(ref offset);
obj.CRC = data.ReadUInt32LittleEndian(ref offset);
List<TagSizeVar> entries = [];
int bytesRemaining = obj.DataSize - 4;
while (bytesRemaining > 0)
{
var entry = new TagSizeVar();
entry.Tag = data.ReadUInt16LittleEndian(ref offset);
entry.Size = data.ReadUInt16LittleEndian(ref offset);
entry.Var = data.ReadBytes(ref offset, entry.Size);
entries.Add(entry);
bytesRemaining -= 4 + entry.Size;
}
obj.TagSizeVars = [.. entries];
return obj;
}
/// <summary>
/// Parse a Stream into a UnixExtraField
/// </summary>
/// <param name="data">Byte array to parse</param>
/// <param name="offset">Offset into the byte array</param>
/// <returns>Filled UnixExtraField on success, null on error</returns>
private static UnixExtraField? ParseUnixExtraField(byte[] data, ref int offset)
{
var obj = new UnixExtraField();
obj.HeaderID = (HeaderID)data.ReadUInt16LittleEndian(ref offset);
if (obj.HeaderID != HeaderID.UNIX)
return null;
obj.DataSize = data.ReadUInt16LittleEndian(ref offset);
obj.FileLastAccessTime = data.ReadUInt32LittleEndian(ref offset);
obj.FileLastModificationTime = data.ReadUInt32LittleEndian(ref offset);
obj.FileUserID = data.ReadUInt16LittleEndian(ref offset);
obj.FileGroupID = data.ReadUInt16LittleEndian(ref offset);
obj.Data = data.ReadBytes(ref offset, obj.DataSize - 12);
return obj;
}
/// <summary>
/// Parse a Stream into a PatchDescriptorExtraField
/// </summary>
/// <param name="data">Byte array to parse</param>
/// <param name="offset">Offset into the byte array</param>
/// <returns>Filled PatchDescriptorExtraField on success, null on error</returns>
private static PatchDescriptorExtraField? ParsePatchDescriptorExtraField(byte[] data, ref int offset)
{
var obj = new PatchDescriptorExtraField();
obj.HeaderID = (HeaderID)data.ReadUInt16LittleEndian(ref offset);
if (obj.HeaderID != HeaderID.PatchDescriptor)
return null;
obj.DataSize = data.ReadUInt16LittleEndian(ref offset);
obj.Version = data.ReadUInt16LittleEndian(ref offset);
obj.Flags = (ActionsReactions)data.ReadUInt32LittleEndian(ref offset);
obj.OldSize = data.ReadUInt32LittleEndian(ref offset);
obj.OldCRC = data.ReadUInt32LittleEndian(ref offset);
obj.NewSize = data.ReadUInt32LittleEndian(ref offset);
obj.NewCRC = data.ReadUInt32LittleEndian(ref offset);
return obj;
}
/// <summary>
/// Parse a Stream into a PKCS7Store
/// </summary>
/// <param name="data">Byte array to parse</param>
/// <param name="offset">Offset into the byte array</param>
/// <returns>Filled PKCS7Store on success, null on error</returns>
private static PKCS7Store? ParsePKCS7Store(byte[] data, ref int offset)
{
var obj = new PKCS7Store();
obj.HeaderID = (HeaderID)data.ReadUInt16LittleEndian(ref offset);
if (obj.HeaderID != HeaderID.PKCSStore)
return null;
obj.DataSize = data.ReadUInt16LittleEndian(ref offset);
obj.TData = data.ReadBytes(ref offset, obj.DataSize);
return obj;
}
/// <summary>
/// Parse a Stream into a X509IndividualFile
/// </summary>
/// <param name="data">Byte array to parse</param>
/// <param name="offset">Offset into the byte array</param>
/// <returns>Filled X509IndividualFile on success, null on error</returns>
private static X509IndividualFile? ParseX509IndividualFile(byte[] data, ref int offset)
{
var obj = new X509IndividualFile();
obj.HeaderID = (HeaderID)data.ReadUInt16LittleEndian(ref offset);
if (obj.HeaderID != HeaderID.X509IndividualFile)
return null;
obj.DataSize = data.ReadUInt16LittleEndian(ref offset);
obj.TData = data.ReadBytes(ref offset, obj.DataSize);
return obj;
}
/// <summary>
/// Parse a Stream into a X509CentralDirectory
/// </summary>
/// <param name="data">Byte array to parse</param>
/// <param name="offset">Offset into the byte array</param>
/// <returns>Filled X509CentralDirectory on success, null on error</returns>
private static X509CentralDirectory? ParseX509CentralDirectory(byte[] data, ref int offset)
{
var obj = new X509CentralDirectory();
obj.HeaderID = (HeaderID)data.ReadUInt16LittleEndian(ref offset);
if (obj.HeaderID != HeaderID.X509CentralDirectory)
return null;
obj.DataSize = data.ReadUInt16LittleEndian(ref offset);
obj.TData = data.ReadBytes(ref offset, obj.DataSize);
return obj;
}
/// <summary>
/// Parse a Stream into a StrongEncryptionHeader
/// </summary>
/// <param name="data">Byte array to parse</param>
/// <param name="offset">Offset into the byte array</param>
/// <returns>Filled StrongEncryptionHeader on success, null on error</returns>
private static StrongEncryptionHeader? ParseStrongEncryptionHeader(byte[] data, ref int offset)
{
var obj = new StrongEncryptionHeader();
obj.HeaderID = (HeaderID)data.ReadUInt16LittleEndian(ref offset);
if (obj.HeaderID != HeaderID.StrongEncryptionHeader)
return null;
obj.DataSize = data.ReadUInt16LittleEndian(ref offset);
obj.Format = data.ReadUInt16LittleEndian(ref offset);
obj.AlgID = data.ReadUInt16LittleEndian(ref offset);
obj.Bitlen = data.ReadUInt16LittleEndian(ref offset);
obj.Flags = data.ReadUInt16LittleEndian(ref offset);
obj.CertData = data.ReadBytes(ref offset, obj.DataSize - 8);
return obj;
}
/// <summary>
/// Parse a Stream into an RecordManagementControls
/// </summary>
/// <param name="data">Byte array to parse</param>
/// <param name="offset">Offset into the byte array</param>
/// <returns>Filled RecordManagementControls on success, null on error</returns>
private static RecordManagementControls? ParseRecordManagementControls(byte[] data, ref int offset)
{
var obj = new RecordManagementControls();
obj.HeaderID = (HeaderID)data.ReadUInt16LittleEndian(ref offset);
if (obj.HeaderID != HeaderID.RecordManagementControls)
return null;
obj.DataSize = data.ReadUInt16LittleEndian(ref offset);
List<TagSizeVar> entries = [];
int bytesRemaining = obj.DataSize - 4;
while (bytesRemaining > 0)
{
var entry = new TagSizeVar();
entry.Tag = data.ReadUInt16LittleEndian(ref offset);
entry.Size = data.ReadUInt16LittleEndian(ref offset);
entry.Var = data.ReadBytes(ref offset, entry.Size);
entries.Add(entry);
bytesRemaining -= 4 + entry.Size;
}
obj.TagSizeVars = [.. entries];
return obj;
}
/// <summary>
/// Parse a Stream into an PKCS7EncryptionRecipientCertificateList
/// </summary>
/// <param name="data">Byte array to parse</param>
/// <param name="offset">Offset into the byte array</param>
/// <returns>Filled PKCS7EncryptionRecipientCertificateList on success, null on error</returns>
private static PKCS7EncryptionRecipientCertificateList? ParsePKCS7EncryptionRecipientCertificateList(byte[] data, ref int offset)
{
var obj = new PKCS7EncryptionRecipientCertificateList();
obj.HeaderID = (HeaderID)data.ReadUInt16LittleEndian(ref offset);
if (obj.HeaderID != HeaderID.PKCSCertificateList)
return null;
obj.DataSize = data.ReadUInt16LittleEndian(ref offset);
obj.Version = data.ReadUInt16LittleEndian(ref offset);
obj.CStore = data.ReadBytes(ref offset, obj.DataSize - 2);
return obj;
}
/// <summary>
/// Parse a Stream into a PolicyDecryptionKeyRecordExtraField
/// </summary>
/// <param name="data">Byte array to parse</param>
/// <param name="offset">Offset into the byte array</param>
/// <returns>Filled PolicyDecryptionKeyRecordExtraField on success, null on error</returns>
private static PolicyDecryptionKeyRecordExtraField? ParsePolicyDecryptionKeyRecordExtraField(byte[] data, ref int offset)
{
var obj = new PolicyDecryptionKeyRecordExtraField();
obj.HeaderID = (HeaderID)data.ReadUInt16LittleEndian(ref offset);
if (obj.HeaderID != HeaderID.PolicyDecryptionKey)
return null;
obj.DataSize = data.ReadUInt16LittleEndian(ref offset);
obj.TData = data.ReadBytes(ref offset, obj.DataSize);
return obj;
}
/// <summary>
/// Parse a Stream into a KeyProviderRecordExtraField
/// </summary>
/// <param name="data">Byte array to parse</param>
/// <param name="offset">Offset into the byte array</param>
/// <returns>Filled KeyProviderRecordExtraField on success, null on error</returns>
private static KeyProviderRecordExtraField? ParseKeyProviderRecordExtraField(byte[] data, ref int offset)
{
var obj = new KeyProviderRecordExtraField();
obj.HeaderID = (HeaderID)data.ReadUInt16LittleEndian(ref offset);
if (obj.HeaderID != HeaderID.SmartcryptKeyProvider)
return null;
obj.DataSize = data.ReadUInt16LittleEndian(ref offset);
obj.TData = data.ReadBytes(ref offset, obj.DataSize);
return obj;
}
/// <summary>
/// Parse a Stream into a PolicyKeyDataRecordRecordExtraField
/// </summary>
/// <param name="data">Byte array to parse</param>
/// <param name="offset">Offset into the byte array</param>
/// <returns>Filled PolicyKeyDataRecordRecordExtraField on success, null on error</returns>
private static PolicyKeyDataRecordRecordExtraField? ParsePolicyKeyDataRecordRecordExtraField(byte[] data, ref int offset)
{
var obj = new PolicyKeyDataRecordRecordExtraField();
obj.HeaderID = (HeaderID)data.ReadUInt16LittleEndian(ref offset);
if (obj.HeaderID != HeaderID.SmartcryptPolicyKeyData)
return null;
obj.DataSize = data.ReadUInt16LittleEndian(ref offset);
obj.TData = data.ReadBytes(ref offset, obj.DataSize);
return obj;
}
/// <summary>
/// Parse a Stream into a AS400ExtraFieldAttribute
/// </summary>
/// <param name="data">Byte array to parse</param>
/// <param name="offset">Offset into the byte array</param>
/// <returns>Filled AS400ExtraFieldAttribute on success, null on error</returns>
/// <remarks>
/// This header ID is shared with MVSExtraField, OS400ExtraField, and ZOSExtraFieldAttribute.
/// This code makes an assumption that it's always AS400ExtraFieldAttribute.
/// </remarks>
private static AS400ExtraFieldAttribute? ParseAS400ExtraFieldAttribute(byte[] data, ref int offset)
{
var obj = new AS400ExtraFieldAttribute();
obj.HeaderID = (HeaderID)data.ReadUInt16LittleEndian(ref offset);
if (obj.HeaderID != HeaderID.IBMS390AttributesUncompressed)
return null;
obj.DataSize = data.ReadUInt16LittleEndian(ref offset);
obj.FieldLength = data.ReadUInt16BigEndian(ref offset);
obj.FieldCode = (AS400ExtraFieldAttributeFieldCode)data.ReadUInt16LittleEndian(ref offset);
obj.Data = data.ReadBytes(ref offset, obj.DataSize - 4);
return obj;
}
/// <summary>
/// Parse a Stream into a ZipItMacintoshExtraField
/// </summary>
/// <param name="data">Byte array to parse</param>
/// <param name="offset">Offset into the byte array</param>
/// <returns>Filled ZipItMacintoshExtraField on success, null on error</returns>
private static ZipItMacintoshExtraField? ParseZipItMacintoshExtraField(byte[] data, ref int offset)
{
var obj = new ZipItMacintoshExtraField();
obj.HeaderID = (HeaderID)data.ReadUInt16LittleEndian(ref offset);
if (obj.HeaderID != HeaderID.ZipItMacintosh)
return null;
obj.DataSize = data.ReadUInt16LittleEndian(ref offset);
obj.ExtraFieldSignature = data.ReadUInt32LittleEndian(ref offset);
obj.FnLen = data.ReadByteValue(ref offset);
byte[] filenameBytes = data.ReadBytes(ref offset, obj.FnLen);
obj.FileName = Encoding.ASCII.GetString(filenameBytes);
obj.FileType = data.ReadBytes(ref offset, 4);
obj.Creator = data.ReadBytes(ref offset, 4);
return obj;
}
/// <summary>
/// Parse a Stream into a ZipItMacintoshShortFileExtraField
/// </summary>
/// <param name="data">Byte array to parse</param>
/// <param name="offset">Offset into the byte array</param>
/// <returns>Filled ZipItMacintoshShortFileExtraField on success, null on error</returns>
private static ZipItMacintoshShortFileExtraField? ParseZipItMacintoshShortFileExtraField(byte[] data, ref int offset)
{
var obj = new ZipItMacintoshShortFileExtraField();
obj.HeaderID = (HeaderID)data.ReadUInt16LittleEndian(ref offset);
if (obj.HeaderID != HeaderID.ZipItMacintosh135Plus)
return null;
obj.DataSize = data.ReadUInt16LittleEndian(ref offset);
obj.ExtraFieldSignature = data.ReadUInt32LittleEndian(ref offset);
obj.FileType = data.ReadBytes(ref offset, 4);
obj.Creator = data.ReadBytes(ref offset, 4);
if (obj.DataSize > 12)
obj.FdFlags = data.ReadUInt16LittleEndian(ref offset);
if (obj.DataSize > 14)
obj.Reserved = data.ReadUInt16LittleEndian(ref offset);
return obj;
}
/// <summary>
/// Parse a Stream into a ZipItMacintoshShortDirectoryExtraField
/// </summary>
/// <param name="data">Byte array to parse</param>
/// <param name="offset">Offset into the byte array</param>
/// <returns>Filled ZipItMacintoshShortDirectoryExtraField on success, null on error</returns>
private static ZipItMacintoshShortDirectoryExtraField? ParseZipItMacintoshShortDirectoryExtraField(byte[] data, ref int offset)
{
var obj = new ZipItMacintoshShortDirectoryExtraField();
obj.HeaderID = (HeaderID)data.ReadUInt16LittleEndian(ref offset);
if (obj.HeaderID != HeaderID.ZipItMacintosh135PlusAlt)
return null;
obj.DataSize = data.ReadUInt16LittleEndian(ref offset);
obj.ExtraFieldSignature = data.ReadUInt32LittleEndian(ref offset);
if (obj.DataSize > 4)
obj.FrFlags = data.ReadUInt16LittleEndian(ref offset);
if (obj.DataSize > 6)
obj.View = (ZipItInternalSettings)data.ReadUInt16LittleEndian(ref offset);
return obj;
}
/// <summary>
/// Parse a Stream into a FWKCSMD5ExtraField
/// </summary>
/// <param name="data">Byte array to parse</param>
/// <param name="offset">Offset into the byte array</param>
/// <returns>Filled FWKCSMD5ExtraField on success, null on error</returns>
private static FWKCSMD5ExtraField? ParseFWKCSMD5ExtraField(byte[] data, ref int offset)
{
var obj = new FWKCSMD5ExtraField();
obj.HeaderID = (HeaderID)data.ReadUInt16LittleEndian(ref offset);
if (obj.HeaderID != HeaderID.FWKCSMD5)
return null;
obj.DataSize = data.ReadUInt16LittleEndian(ref offset);
obj.Preface = data.ReadBytes(ref offset, 3);
obj.MD5 = data.ReadBytes(ref offset, 16);
return obj;
}
/// <summary>
/// Parse a Stream into a InfoZIPUnicodeCommentExtraField
/// </summary>
/// <param name="data">Byte array to parse</param>
/// <param name="offset">Offset into the byte array</param>
/// <returns>Filled InfoZIPUnicodeCommentExtraField on success, null on error</returns>
private static InfoZIPUnicodeCommentExtraField? ParseInfoZIPUnicodeCommentExtraField(byte[] data, ref int offset)
{
var obj = new InfoZIPUnicodeCommentExtraField();
obj.HeaderID = (HeaderID)data.ReadUInt16LittleEndian(ref offset);
if (obj.HeaderID != HeaderID.InfoZIPUnicodeComment)
return null;
obj.DataSize = data.ReadUInt16LittleEndian(ref offset);
obj.Version = data.ReadByteValue(ref offset);
obj.ComCRC32 = data.ReadUInt32LittleEndian(ref offset);
byte[] unicodeBytes = data.ReadBytes(ref offset, obj.DataSize - 5);
obj.UnicodeCom = Encoding.UTF8.GetString(unicodeBytes);
return obj;
}
/// <summary>
/// Parse a Stream into a InfoZIPUnicodePathExtraField
/// </summary>
/// <param name="data">Byte array to parse</param>
/// <param name="offset">Offset into the byte array</param>
/// <returns>Filled InfoZIPUnicodePathExtraField on success, null on error</returns>
private static InfoZIPUnicodePathExtraField? ParseInfoZIPUnicodePathExtraField(byte[] data, ref int offset)
{
var obj = new InfoZIPUnicodePathExtraField();
obj.HeaderID = (HeaderID)data.ReadUInt16LittleEndian(ref offset);
if (obj.HeaderID != HeaderID.InfoZIPUnicodePath)
return null;
obj.DataSize = data.ReadUInt16LittleEndian(ref offset);
obj.Version = data.ReadByteValue(ref offset);
obj.NameCRC32 = data.ReadUInt32LittleEndian(ref offset);
byte[] unicodeBytes = data.ReadBytes(ref offset, obj.DataSize - 5);
obj.UnicodeName = Encoding.UTF8.GetString(unicodeBytes);
return obj;
}
/// <summary>
/// Parse a Stream into a DataStreamAlignment
/// </summary>
/// <param name="data">Byte array to parse</param>
/// <param name="offset">Offset into the byte array</param>
/// <returns>Filled DataStreamAlignment on success, null on error</returns>
private static DataStreamAlignment? ParseDataStreamAlignment(byte[] data, ref int offset)
{
var obj = new DataStreamAlignment();
obj.HeaderID = (HeaderID)data.ReadUInt16LittleEndian(ref offset);
if (obj.HeaderID != HeaderID.DataStreamAlignment)
return null;
obj.DataSize = data.ReadUInt16LittleEndian(ref offset);
obj.Alignment = data.ReadUInt16LittleEndian(ref offset);
obj.Padding = data.ReadBytes(ref offset, obj.DataSize - 2);
return obj;
}
/// <summary>
/// Parse a Stream into a MicrosoftOpenPackagingGrowthHint
/// </summary>
/// <param name="data">Byte array to parse</param>
/// <param name="offset">Offset into the byte array</param>
/// <returns>Filled MicrosoftOpenPackagingGrowthHint on success, null on error</returns>
private static MicrosoftOpenPackagingGrowthHint? ParseMicrosoftOpenPackagingGrowthHint(byte[] data, ref int offset)
{
var obj = new MicrosoftOpenPackagingGrowthHint();
obj.HeaderID = (HeaderID)data.ReadUInt16LittleEndian(ref offset);
if (obj.HeaderID != HeaderID.MicrosoftOpenPackagingGrowthHint)
return null;
obj.DataSize = data.ReadUInt16LittleEndian(ref offset);
obj.Sig = data.ReadUInt16LittleEndian(ref offset);
obj.PadVal = data.ReadUInt16LittleEndian(ref offset);
obj.Padding = data.ReadBytes(ref offset, obj.DataSize - 2);
return obj;
}
#endregion
#region Helpers
/// <summary>
/// Determine if a data descriptor is 64-bit
/// </summary>
/// <param name="data">Stream to parse</param>
/// <returns>True if the descriptor is 64-bit, false otherwise</returns>
private static bool IsZip64Descriptor(Stream data)
{
// Last item in the stream
if (data.Position + 12 == data.Length) // Short 32-bit
return false;
else if (data.Position + 16 == data.Length) // Long 32-bit
return false;
else if (data.Position + 20 == data.Length) // Short 64-bit
return true;
else if (data.Position + 24 == data.Length) // Long 64-bit
return true;
// Cache the current position
long currentPosition = data.Position;
// Short 32-bit
data.SeekIfPossible(12, SeekOrigin.Current);
byte[] nextBlock = data.ReadBytes(2);
data.SeekIfPossible(currentPosition, SeekOrigin.Begin);
if (nextBlock.EqualsExactly([0x50, 0x4B]))
return false;
// Long 32-bit
data.SeekIfPossible(16, SeekOrigin.Current);
nextBlock = data.ReadBytes(2);
data.SeekIfPossible(currentPosition, SeekOrigin.Begin);
if (nextBlock.EqualsExactly([0x50, 0x4B]))
return false;
// Assume 64-bit otherwise
return true;
}
#endregion
}
}