| 1 | //===-- ZipFile.cpp -------------------------------------------------------===// |
| 2 | // |
| 3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| 4 | // See https://llvm.org/LICENSE.txt for license information. |
| 5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| 6 | // |
| 7 | //===----------------------------------------------------------------------===// |
| 8 | |
| 9 | #include "lldb/Utility/ZipFile.h" |
| 10 | #include "lldb/Utility/DataBuffer.h" |
| 11 | #include "lldb/Utility/FileSpec.h" |
| 12 | #include "llvm/Support/Endian.h" |
| 13 | |
| 14 | using namespace lldb_private; |
| 15 | using namespace llvm::support; |
| 16 | |
| 17 | namespace { |
| 18 | |
| 19 | // Zip headers. |
| 20 | // https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT |
| 21 | |
| 22 | // The end of central directory record. |
| 23 | struct EocdRecord { |
| 24 | static constexpr char kSignature[] = {0x50, 0x4b, 0x05, 0x06}; |
| 25 | char signature[sizeof(kSignature)]; |
| 26 | unaligned_uint16_t disks; |
| 27 | unaligned_uint16_t cd_start_disk; |
| 28 | unaligned_uint16_t cds_on_this_disk; |
| 29 | unaligned_uint16_t cd_records; |
| 30 | unaligned_uint32_t cd_size; |
| 31 | unaligned_uint32_t cd_offset; |
| 32 | unaligned_uint16_t ; |
| 33 | }; |
| 34 | |
| 35 | // Logical find limit for the end of central directory record. |
| 36 | const size_t kEocdRecordFindLimit = |
| 37 | sizeof(EocdRecord) + |
| 38 | std::numeric_limits<decltype(EocdRecord::comment_length)>::max(); |
| 39 | |
| 40 | // Central directory record. |
| 41 | struct CdRecord { |
| 42 | static constexpr char kSignature[] = {0x50, 0x4b, 0x01, 0x02}; |
| 43 | char signature[sizeof(kSignature)]; |
| 44 | unaligned_uint16_t version_made_by; |
| 45 | unaligned_uint16_t ; |
| 46 | unaligned_uint16_t general_purpose_bit_flag; |
| 47 | unaligned_uint16_t compression_method; |
| 48 | unaligned_uint16_t last_modification_time; |
| 49 | unaligned_uint16_t last_modification_date; |
| 50 | unaligned_uint32_t crc32; |
| 51 | unaligned_uint32_t compressed_size; |
| 52 | unaligned_uint32_t uncompressed_size; |
| 53 | unaligned_uint16_t file_name_length; |
| 54 | unaligned_uint16_t ; |
| 55 | unaligned_uint16_t ; |
| 56 | unaligned_uint16_t file_start_disk; |
| 57 | unaligned_uint16_t internal_file_attributes; |
| 58 | unaligned_uint32_t external_file_attributes; |
| 59 | unaligned_uint32_t ; |
| 60 | }; |
| 61 | // Immediately after CdRecord, |
| 62 | // - file name (file_name_length) |
| 63 | // - extra field (extra_field_length) |
| 64 | // - comment (comment_length) |
| 65 | |
| 66 | // Local file header. |
| 67 | struct { |
| 68 | static constexpr char [] = {0x50, 0x4b, 0x03, 0x04}; |
| 69 | char [sizeof(kSignature)]; |
| 70 | unaligned_uint16_t ; |
| 71 | unaligned_uint16_t ; |
| 72 | unaligned_uint16_t ; |
| 73 | unaligned_uint16_t ; |
| 74 | unaligned_uint16_t ; |
| 75 | unaligned_uint32_t ; |
| 76 | unaligned_uint32_t ; |
| 77 | unaligned_uint32_t ; |
| 78 | unaligned_uint16_t ; |
| 79 | unaligned_uint16_t ; |
| 80 | }; |
| 81 | // Immediately after LocalFileHeader, |
| 82 | // - file name (file_name_length) |
| 83 | // - extra field (extra_field_length) |
| 84 | // - file data (should be compressed_size == uncompressed_size, page aligned) |
| 85 | |
| 86 | const EocdRecord *FindEocdRecord(lldb::DataBufferSP zip_data) { |
| 87 | // Find backward the end of central directory record from the end of the zip |
| 88 | // file to the find limit. |
| 89 | const uint8_t *zip_data_end = zip_data->GetBytes() + zip_data->GetByteSize(); |
| 90 | const uint8_t *find_limit = zip_data_end - kEocdRecordFindLimit; |
| 91 | const uint8_t *p = zip_data_end - sizeof(EocdRecord); |
| 92 | for (; p >= zip_data->GetBytes() && p >= find_limit; p--) { |
| 93 | auto eocd = reinterpret_cast<const EocdRecord *>(p); |
| 94 | if (::memcmp(s1: eocd->signature, s2: EocdRecord::kSignature, |
| 95 | n: sizeof(EocdRecord::kSignature)) == 0) { |
| 96 | // Found the end of central directory. Sanity check the values. |
| 97 | if (eocd->cd_records * sizeof(CdRecord) > eocd->cd_size || |
| 98 | zip_data->GetBytes() + eocd->cd_offset + eocd->cd_size > p) |
| 99 | return nullptr; |
| 100 | |
| 101 | // This is a valid end of central directory record. |
| 102 | return eocd; |
| 103 | } |
| 104 | } |
| 105 | return nullptr; |
| 106 | } |
| 107 | |
| 108 | bool GetFile(lldb::DataBufferSP zip_data, uint32_t , |
| 109 | lldb::offset_t &file_offset, lldb::offset_t &file_size) { |
| 110 | auto = reinterpret_cast<const LocalFileHeader *>( |
| 111 | zip_data->GetBytes() + local_file_header_offset); |
| 112 | // The signature should match. |
| 113 | if (::memcmp(s1: local_file_header->signature, s2: LocalFileHeader::kSignature, |
| 114 | n: sizeof(LocalFileHeader::kSignature)) != 0) |
| 115 | return false; |
| 116 | |
| 117 | auto file_data = reinterpret_cast<const uint8_t *>(local_file_header + 1) + |
| 118 | local_file_header->file_name_length + |
| 119 | local_file_header->extra_field_length; |
| 120 | // File should be uncompressed. |
| 121 | if (local_file_header->compressed_size != |
| 122 | local_file_header->uncompressed_size) |
| 123 | return false; |
| 124 | |
| 125 | // This file is valid. Return the file offset and size. |
| 126 | file_offset = file_data - zip_data->GetBytes(); |
| 127 | file_size = local_file_header->uncompressed_size; |
| 128 | return true; |
| 129 | } |
| 130 | |
| 131 | bool FindFile(lldb::DataBufferSP zip_data, const EocdRecord *eocd, |
| 132 | const llvm::StringRef file_path, lldb::offset_t &file_offset, |
| 133 | lldb::offset_t &file_size) { |
| 134 | // Find the file from the central directory records. |
| 135 | auto cd = reinterpret_cast<const CdRecord *>(zip_data->GetBytes() + |
| 136 | eocd->cd_offset); |
| 137 | size_t cd_records = eocd->cd_records; |
| 138 | for (size_t i = 0; i < cd_records; i++) { |
| 139 | // The signature should match. |
| 140 | if (::memcmp(s1: cd->signature, s2: CdRecord::kSignature, |
| 141 | n: sizeof(CdRecord::kSignature)) != 0) |
| 142 | return false; |
| 143 | |
| 144 | // Sanity check the file name values. |
| 145 | auto file_name = reinterpret_cast<const char *>(cd + 1); |
| 146 | size_t file_name_length = cd->file_name_length; |
| 147 | if (file_name + file_name_length >= reinterpret_cast<const char *>(eocd) || |
| 148 | file_name_length == 0) |
| 149 | return false; |
| 150 | |
| 151 | // Compare the file name. |
| 152 | if (file_path == llvm::StringRef(file_name, file_name_length)) { |
| 153 | // Found the file. |
| 154 | return GetFile(zip_data, local_file_header_offset: cd->local_file_header_offset, file_offset, |
| 155 | file_size); |
| 156 | } else { |
| 157 | // Skip to the next central directory record. |
| 158 | cd = reinterpret_cast<const CdRecord *>( |
| 159 | reinterpret_cast<const char *>(cd) + sizeof(CdRecord) + |
| 160 | cd->file_name_length + cd->extra_field_length + cd->comment_length); |
| 161 | // Sanity check the pointer. |
| 162 | if (reinterpret_cast<const char *>(cd) >= |
| 163 | reinterpret_cast<const char *>(eocd)) |
| 164 | return false; |
| 165 | } |
| 166 | } |
| 167 | |
| 168 | return false; |
| 169 | } |
| 170 | |
| 171 | } // end anonymous namespace |
| 172 | |
| 173 | bool ZipFile::Find(lldb::DataBufferSP zip_data, const llvm::StringRef file_path, |
| 174 | lldb::offset_t &file_offset, lldb::offset_t &file_size) { |
| 175 | const EocdRecord *eocd = FindEocdRecord(zip_data); |
| 176 | if (!eocd) |
| 177 | return false; |
| 178 | |
| 179 | return FindFile(zip_data, eocd, file_path, file_offset, file_size); |
| 180 | } |
| 181 | |