//===-- ZipFile.cpp -------------------------------------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// #include "lldb/Utility/ZipFile.h" #include "lldb/Utility/DataBuffer.h" #include "lldb/Utility/FileSpec.h" #include "llvm/Support/Endian.h" using namespace lldb_private; using namespace llvm::support; namespace { // Zip headers. // https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT // The end of central directory record. struct EocdRecord { static constexpr char kSignature[] = {0x50, 0x4b, 0x05, 0x06}; char signature[sizeof(kSignature)]; unaligned_uint16_t disks; unaligned_uint16_t cd_start_disk; unaligned_uint16_t cds_on_this_disk; unaligned_uint16_t cd_records; unaligned_uint32_t cd_size; unaligned_uint32_t cd_offset; unaligned_uint16_t comment_length; }; // Logical find limit for the end of central directory record. const size_t kEocdRecordFindLimit = sizeof(EocdRecord) + std::numeric_limits::max(); // Central directory record. struct CdRecord { static constexpr char kSignature[] = {0x50, 0x4b, 0x01, 0x02}; char signature[sizeof(kSignature)]; unaligned_uint16_t version_made_by; unaligned_uint16_t version_needed_to_extract; unaligned_uint16_t general_purpose_bit_flag; unaligned_uint16_t compression_method; unaligned_uint16_t last_modification_time; unaligned_uint16_t last_modification_date; unaligned_uint32_t crc32; unaligned_uint32_t compressed_size; unaligned_uint32_t uncompressed_size; unaligned_uint16_t file_name_length; unaligned_uint16_t extra_field_length; unaligned_uint16_t comment_length; unaligned_uint16_t file_start_disk; unaligned_uint16_t internal_file_attributes; unaligned_uint32_t external_file_attributes; unaligned_uint32_t local_file_header_offset; }; // Immediately after CdRecord, // - file name (file_name_length) // - extra field (extra_field_length) // - comment (comment_length) // Local file header. struct LocalFileHeader { static constexpr char kSignature[] = {0x50, 0x4b, 0x03, 0x04}; char signature[sizeof(kSignature)]; unaligned_uint16_t version_needed_to_extract; unaligned_uint16_t general_purpose_bit_flag; unaligned_uint16_t compression_method; unaligned_uint16_t last_modification_time; unaligned_uint16_t last_modification_date; unaligned_uint32_t crc32; unaligned_uint32_t compressed_size; unaligned_uint32_t uncompressed_size; unaligned_uint16_t file_name_length; unaligned_uint16_t extra_field_length; }; // Immediately after LocalFileHeader, // - file name (file_name_length) // - extra field (extra_field_length) // - file data (should be compressed_size == uncompressed_size, page aligned) const EocdRecord *FindEocdRecord(lldb::DataBufferSP zip_data) { // Find backward the end of central directory record from the end of the zip // file to the find limit. const uint8_t *zip_data_end = zip_data->GetBytes() + zip_data->GetByteSize(); const uint8_t *find_limit = zip_data_end - kEocdRecordFindLimit; const uint8_t *p = zip_data_end - sizeof(EocdRecord); for (; p >= zip_data->GetBytes() && p >= find_limit; p--) { auto eocd = reinterpret_cast(p); if (::memcmp(eocd->signature, EocdRecord::kSignature, sizeof(EocdRecord::kSignature)) == 0) { // Found the end of central directory. Sanity check the values. if (eocd->cd_records * sizeof(CdRecord) > eocd->cd_size || zip_data->GetBytes() + eocd->cd_offset + eocd->cd_size > p) return nullptr; // This is a valid end of central directory record. return eocd; } } return nullptr; } bool GetFile(lldb::DataBufferSP zip_data, uint32_t local_file_header_offset, lldb::offset_t &file_offset, lldb::offset_t &file_size) { auto local_file_header = reinterpret_cast( zip_data->GetBytes() + local_file_header_offset); // The signature should match. if (::memcmp(local_file_header->signature, LocalFileHeader::kSignature, sizeof(LocalFileHeader::kSignature)) != 0) return false; auto file_data = reinterpret_cast(local_file_header + 1) + local_file_header->file_name_length + local_file_header->extra_field_length; // File should be uncompressed. if (local_file_header->compressed_size != local_file_header->uncompressed_size) return false; // This file is valid. Return the file offset and size. file_offset = file_data - zip_data->GetBytes(); file_size = local_file_header->uncompressed_size; return true; } bool FindFile(lldb::DataBufferSP zip_data, const EocdRecord *eocd, const llvm::StringRef file_path, lldb::offset_t &file_offset, lldb::offset_t &file_size) { // Find the file from the central directory records. auto cd = reinterpret_cast(zip_data->GetBytes() + eocd->cd_offset); size_t cd_records = eocd->cd_records; for (size_t i = 0; i < cd_records; i++) { // The signature should match. if (::memcmp(cd->signature, CdRecord::kSignature, sizeof(CdRecord::kSignature)) != 0) return false; // Sanity check the file name values. auto file_name = reinterpret_cast(cd + 1); size_t file_name_length = cd->file_name_length; if (file_name + file_name_length >= reinterpret_cast(eocd) || file_name_length == 0) return false; // Compare the file name. if (file_path == llvm::StringRef(file_name, file_name_length)) { // Found the file. return GetFile(zip_data, cd->local_file_header_offset, file_offset, file_size); } else { // Skip to the next central directory record. cd = reinterpret_cast( reinterpret_cast(cd) + sizeof(CdRecord) + cd->file_name_length + cd->extra_field_length + cd->comment_length); // Sanity check the pointer. if (reinterpret_cast(cd) >= reinterpret_cast(eocd)) return false; } } return false; } } // end anonymous namespace bool ZipFile::Find(lldb::DataBufferSP zip_data, const llvm::StringRef file_path, lldb::offset_t &file_offset, lldb::offset_t &file_size) { const EocdRecord *eocd = FindEocdRecord(zip_data); if (!eocd) return false; return FindFile(zip_data, eocd, file_path, file_offset, file_size); }