/// <summary> /// Returns the Local File record starting at the current position of the provided buffer /// and advances the buffer's position immediately past the end of the record. The record /// consists of the Local File Header, data, and (if present) Data Descriptor. /// </summary> /// <param name="apk"></param> /// <param name="cdRecord"></param> /// <param name="cdStartOffset"></param> /// <returns></returns> public static LocalFileRecord GetRecord(DataSource apk, CentralDirectoryRecord cdRecord, long cdStartOffset) { return(GetRecord( apk, cdRecord, cdStartOffset, true, // obtain extra field contents true // include Data Descriptor (if present) )); }
/// <summary> /// Returns the uncompressed data pointed to by the provided ZIP Central Directory (CD) record. /// </summary> /// <param name="source"></param> /// <param name="cdRecord"></param> /// <param name="cdStartOffsetInArchive"></param> /// <returns></returns> public static byte[] GetUncompressedData(DataSource source, CentralDirectoryRecord cdRecord, long cdStartOffsetInArchive) { byte[] result = new byte[cdRecord.UncompressedSize]; var resultSink = new MemoryStream(result, true); OutputUncompressedData( source, cdRecord, cdStartOffsetInArchive, resultSink); return(result); }
public static int BY_LOCAL_FILE_HEADER_OFFSET_COMPARATOR(CentralDirectoryRecord r1, CentralDirectoryRecord r2) { var offset1 = r1.LocalFileHeaderOffset; var offset2 = r2.LocalFileHeaderOffset; if (offset1 > offset2) { return(1); } else if (offset1 < offset2) { return(-1); } else { return(0); } }
/// <summary> /// Sends uncompressed data pointed to by the provided ZIP Central Directory (CD) record into the /// provided data sink. /// </summary> /// <param name="source"></param> /// <param name="cdRecord"></param> /// <param name="cdStartOffsetInArchive"></param> /// <param name="sink"></param> public static void OutputUncompressedData( DataSource source, CentralDirectoryRecord cdRecord, long cdStartOffsetInArchive, Stream sink) { // IMPLEMENTATION NOTE: This method attempts to mimic the behavior of Android platform // exhibited when reading an APK for the purposes of verifying its signatures. // When verifying an APK, Android doesn't care reading the extra field or the Data // Descriptor. LocalFileRecord lfhRecord = GetRecord( source, cdRecord, cdStartOffsetInArchive, false, // don't care about the extra field false // don't read the Data Descriptor ); lfhRecord.OutputUncompressedData(source, sink); }
/// <summary> /// Returns the Local File record starting at the current position of the provided buffer /// and advances the buffer's position immediately past the end of the record. The record /// consists of the Local File Header, data, and (if present) Data Descriptor. /// </summary> /// <param name="apk"></param> /// <param name="cdRecord"></param> /// <param name="cdStartOffset"></param> /// <param name="extraFieldContentsNeeded"></param> /// <param name="dataDescriptorIncluded"></param> /// <returns></returns> private static LocalFileRecord GetRecord(DataSource apk, CentralDirectoryRecord cdRecord, long cdStartOffset, bool extraFieldContentsNeeded, bool dataDescriptorIncluded) { // IMPLEMENTATION NOTE: This method attempts to mimic the behavior of Android platform // exhibited when reading an APK for the purposes of verifying its signatures. var entryName = cdRecord.Name; var cdRecordEntryNameSizeBytes = cdRecord.NameSizeBytes; var headerSizeWithName = HeaderSizeBytes + cdRecordEntryNameSizeBytes; var headerStartOffset = cdRecord.LocalFileHeaderOffset; var headerEndOffset = headerStartOffset + headerSizeWithName; if (headerEndOffset > cdStartOffset) { throw new ZipFormatException( "Local File Header of " + entryName + " extends beyond start of Central" + " Directory. LFH end: " + headerEndOffset + ", CD start: " + cdStartOffset); } byte[] header; try { header = apk.GetByteBuffer(headerStartOffset, headerSizeWithName); } catch (IOException e) { throw new IOException("Failed to read Local File Header of " + entryName, e); } var recordSignature = BitConverter.ToInt32(header, 0); if (recordSignature != RecordSignature) { throw new ZipFormatException( "Not a Local File Header record for entry " + entryName + ". Signature: 0x" + (recordSignature & 0xffffffffL).ToString("X")); } var gpFlags = BitConverter.ToInt16(header, GpFlagsOffset); var dataDescriptorUsed = (gpFlags & ZipUtils.GpFlagDataDescriptorUsed) != 0; var cdDataDescriptorUsed = (cdRecord.GpFlags & ZipUtils.GpFlagDataDescriptorUsed) != 0; if (dataDescriptorUsed != cdDataDescriptorUsed) { throw new ZipFormatException( "Data Descriptor presence mismatch between Local File Header and Central" + " Directory for entry " + entryName + ". LFH: " + dataDescriptorUsed + ", CD: " + cdDataDescriptorUsed); } var uncompressedDataCrc32FromCdRecord = cdRecord.Crc32; var compressedDataSizeFromCdRecord = cdRecord.CompressedSize; var uncompressedDataSizeFromCdRecord = cdRecord.UncompressedSize; if (!dataDescriptorUsed) { long crc32 = BitConverter.ToUInt32(header, Crc32Offset); if (crc32 != uncompressedDataCrc32FromCdRecord) { throw new ZipFormatException( "CRC-32 mismatch between Local File Header and Central Directory for entry " + entryName + ". LFH: " + crc32 + ", CD: " + uncompressedDataCrc32FromCdRecord); } long compressedSize = BitConverter.ToUInt32(header, CompressedSizeOffset); if (compressedSize != compressedDataSizeFromCdRecord) { throw new ZipFormatException( "Compressed size mismatch between Local File Header and Central Directory" + " for entry " + entryName + ". LFH: " + compressedSize + ", CD: " + compressedDataSizeFromCdRecord); } long uncompressedSize = BitConverter.ToUInt32(header, UncompressedSizeOffset); if (uncompressedSize != uncompressedDataSizeFromCdRecord) { throw new ZipFormatException( "Uncompressed size mismatch between Local File Header and Central Directory" + " for entry " + entryName + ". LFH: " + uncompressedSize + ", CD: " + uncompressedDataSizeFromCdRecord); } } int nameLength = BitConverter.ToUInt16(header, NameLengthOffset); if (nameLength > cdRecordEntryNameSizeBytes) { throw new ZipFormatException( "Name mismatch between Local File Header and Central Directory for entry" + entryName + ". LFH: " + nameLength + " bytes, CD: " + cdRecordEntryNameSizeBytes + " bytes"); } var name = CentralDirectoryRecord.GetName(header, NameOffset, nameLength); if (!entryName.Equals(name)) { throw new ZipFormatException( "Name mismatch between Local File Header and Central Directory. LFH: \"" + name + "\", CD: \"" + entryName + "\""); } int extraLength = BitConverter.ToUInt16(header, ExtraLengthOffset); var dataStartOffset = headerStartOffset + HeaderSizeBytes + nameLength + extraLength; long dataSize; var compressed = (cdRecord.CompressionMethod != ZipUtils.CompressionMethodStored); if (compressed) { dataSize = compressedDataSizeFromCdRecord; } else { dataSize = uncompressedDataSizeFromCdRecord; } var dataEndOffset = dataStartOffset + dataSize; if (dataEndOffset > cdStartOffset) { throw new ZipFormatException( "Local File Header data of " + entryName + " overlaps with Central Directory" + ". LFH data start: " + dataStartOffset + ", LFH data end: " + dataEndOffset + ", CD start: " + cdStartOffset); } var extra = new byte[0]; if ((extraFieldContentsNeeded) && (extraLength > 0)) { extra = apk.GetByteBuffer( headerStartOffset + HeaderSizeBytes + nameLength, extraLength); } var recordEndOffset = dataEndOffset; // Include the Data Descriptor (if requested and present) into the record. if ((dataDescriptorIncluded) && ((gpFlags & ZipUtils.GpFlagDataDescriptorUsed) != 0)) { // The record's data is supposed to be followed by the Data Descriptor. Unfortunately, // the descriptor's size is not known in advance because the spec lets the signature // field (the first four bytes) be omitted. Thus, there's no 100% reliable way to tell // how long the Data Descriptor record is. Most parsers (including Android) check // whether the first four bytes look like Data Descriptor record signature and, if so, // assume that it is indeed the record's signature. However, this is the wrong // conclusion if the record's CRC-32 (next field after the signature) has the same value // as the signature. In any case, we're doing what Android is doing. var dataDescriptorEndOffset = dataEndOffset + DataDescriptorSizeBytesWithoutSignature; if (dataDescriptorEndOffset > cdStartOffset) { throw new ZipFormatException( "Data Descriptor of " + entryName + " overlaps with Central Directory" + ". Data Descriptor end: " + dataEndOffset + ", CD start: " + cdStartOffset); } var dataDescriptorPotentialSig = apk.GetByteBuffer(dataEndOffset, 4); if (BitConverter.ToInt32(dataDescriptorPotentialSig, 0) == DataDescriptorSignature) { dataDescriptorEndOffset += 4; if (dataDescriptorEndOffset > cdStartOffset) { throw new ZipFormatException( "Data Descriptor of " + entryName + " overlaps with Central Directory" + ". Data Descriptor end: " + dataEndOffset + ", CD start: " + cdStartOffset); } } recordEndOffset = dataDescriptorEndOffset; } var recordSize = recordEndOffset - headerStartOffset; var dataStartOffsetInRecord = HeaderSizeBytes + nameLength + extraLength; return(new LocalFileRecord( entryName, cdRecordEntryNameSizeBytes, extra, headerStartOffset, recordSize, dataStartOffsetInRecord, dataSize, compressed, uncompressedDataSizeFromCdRecord)); }