181 lines · cpp
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-exception6//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 14using namespace lldb_private;15using namespace llvm::support;16 17namespace {18 19// Zip headers.20// https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT21 22// The end of central directory record.23struct 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 comment_length;33};34 35// Logical find limit for the end of central directory record.36const size_t kEocdRecordFindLimit =37 sizeof(EocdRecord) +38 std::numeric_limits<decltype(EocdRecord::comment_length)>::max();39 40// Central directory record.41struct 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 version_needed_to_extract;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 extra_field_length;55 unaligned_uint16_t comment_length;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 local_file_header_offset;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.67struct LocalFileHeader {68 static constexpr char kSignature[] = {0x50, 0x4b, 0x03, 0x04};69 char signature[sizeof(kSignature)];70 unaligned_uint16_t version_needed_to_extract;71 unaligned_uint16_t general_purpose_bit_flag;72 unaligned_uint16_t compression_method;73 unaligned_uint16_t last_modification_time;74 unaligned_uint16_t last_modification_date;75 unaligned_uint32_t crc32;76 unaligned_uint32_t compressed_size;77 unaligned_uint32_t uncompressed_size;78 unaligned_uint16_t file_name_length;79 unaligned_uint16_t extra_field_length;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 86const EocdRecord *FindEocdRecord(lldb::DataBufferSP zip_data) {87 // Find backward the end of central directory record from the end of the zip88 // 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(eocd->signature, EocdRecord::kSignature,95 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 108bool GetFile(lldb::DataBufferSP zip_data, uint32_t local_file_header_offset,109 lldb::offset_t &file_offset, lldb::offset_t &file_size) {110 auto local_file_header = reinterpret_cast<const LocalFileHeader *>(111 zip_data->GetBytes() + local_file_header_offset);112 // The signature should match.113 if (::memcmp(local_file_header->signature, LocalFileHeader::kSignature,114 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 131bool 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(cd->signature, CdRecord::kSignature,141 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, 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 namespace172 173bool 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