1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 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);
}
|