| //===-- 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<decltype(EocdRecord::comment_length)>::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<const EocdRecord *>(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<const LocalFileHeader *>( | |
| 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<const uint8_t *>(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<const CdRecord *>(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<const char *>(cd + 1); | |
| size_t file_name_length = cd->file_name_length; | |
| if (file_name + file_name_length > reinterpret_cast<const char *>(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<const CdRecord *>( | |
| reinterpret_cast<const char *>(cd) + sizeof(CdRecord) + | |
| cd->file_name_length + cd->extra_field_length + cd->comment_length); | |
| // Sanity check the pointer. | |
| if (reinterpret_cast<const char *>(cd) >= | |
| reinterpret_cast<const char *>(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); | |
| } |