/// <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);
        }
Пример #3
0
        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));
        }