diff options
Diffstat (limited to 'contrib/llvm-project/lldb/source/Utility/ZipFile.cpp')
-rw-r--r-- | contrib/llvm-project/lldb/source/Utility/ZipFile.cpp | 180 |
1 files changed, 180 insertions, 0 deletions
diff --git a/contrib/llvm-project/lldb/source/Utility/ZipFile.cpp b/contrib/llvm-project/lldb/source/Utility/ZipFile.cpp new file mode 100644 index 000000000000..b8ed956cbfcb --- /dev/null +++ b/contrib/llvm-project/lldb/source/Utility/ZipFile.cpp @@ -0,0 +1,180 @@ +//===-- 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); +} |