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);
}
|