-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathManifest.cpp
More file actions
244 lines (210 loc) · 10.6 KB
/
Manifest.cpp
File metadata and controls
244 lines (210 loc) · 10.6 KB
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
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
#include "Manifest.h"
#include "Logging.h"
#include "StdString.h"
#include "Utils.h"
#include "ZipUtils.h"
#include "Path.h"
#include <tuple>
#include <map>
#include <functional>
#include <string.h>
#include "minizip_extra.h"
namespace ZipSync {
bool FileMetainfo::IsLess_ByZip(const FileMetainfo &a, const FileMetainfo &b) {
#define WRAP_TUPLE(m) std::tie( \
/*the main properties to sort by*/ \
m.zipPath.rel, m.filename, \
/*the above props should always differ under normal use*/ \
/*but better provide tie-breaker to make order deterministic*/ \
/*and we need this in fuzzy tests (not a normal use of course)*/ \
m.contentsHash, m.compressedHash, m.package, \
m.props.lastModTime, m.props.compressionMethod, m.props.generalPurposeBitFlag, \
m.props.internalAttribs, m.props.externalAttribs, m.props.compressedSize, \
m.props.contentsSize, m.props.crc32, \
/*these come last: they are missing anyway in target manifests*/ \
m.byterange[0], m.byterange[1] \
)
return WRAP_TUPLE(a) < WRAP_TUPLE(b);
}
void FileMetainfo::Nullify() {
byterange[0] = byterange[1] = 0;
location = FileLocation::Nowhere;
compressedHash.Clear();
contentsHash.Clear();
memset(&props, 0, sizeof(props));
}
void FileMetainfo::DontProvide() {
byterange[0] = byterange[1] = 0;
location = FileLocation::Nowhere;
}
void AnalyzeCurrentFile(unzFile zf, FileMetainfo &filemeta, bool hashContents, bool hashCompressed) {
char filename[SIZE_PATH];
unz_file_info info;
SAFE_CALL(unzGetCurrentFileInfo(zf, &info, filename, sizeof(filename), NULL, 0, NULL, 0));
ZipSyncAssertF(info.version == 0, "File %s has made-by version %d (not supported)", filename, info.version);
ZipSyncAssertF(info.version_needed == 20, "File %s needs zip version %d (not supported)", filename, info.version_needed);
ZipSyncAssertF((info.flag & 0x08) == 0, "File %s has data descriptor (not supported)", filename);
ZipSyncAssertF((info.flag & 0x01) == 0, "File %s is encrypted (not supported)", filename);
ZipSyncAssertF((info.flag & (~0x06)) == 0, "File %s has flags %d (not supported)", filename, info.flag);
ZipSyncAssertF(info.compression_method == 0 || info.compression_method == 8, "File %s has compression %d (not supported)", filename, info.compression_method);
ZipSyncAssertF(info.size_file_extra == 0, "File %s has extra field in header (not supported)", filename);
ZipSyncAssertF(info.size_file_comment == 0, "File %s has comment in header (not supported)", filename);
ZipSyncAssertF(info.disk_num_start == 0, "File %s has disk nonzero number (not supported)", filename);
//TODO: check that extra field is empty in local file header?...
filemeta.filename = filename;
filemeta.props.crc32 = info.crc;
filemeta.props.compressedSize = info.compressed_size;
filemeta.props.contentsSize = info.uncompressed_size;
filemeta.props.compressionMethod = info.compression_method;
filemeta.props.generalPurposeBitFlag = info.flag;
filemeta.props.lastModTime = info.dosDate;
filemeta.props.internalAttribs = info.internal_fa;
filemeta.props.externalAttribs = info.external_fa;
unzGetCurrentFilePosition(zf, &filemeta.byterange[0], NULL, &filemeta.byterange[1]);
for (int mode = 0; mode < 2; mode++) {
if (!(mode == 0 ? hashCompressed : hashContents))
continue;
SAFE_CALL(unzOpenCurrentFile2(zf, NULL, NULL, !mode));
Hasher hasher;
char buffer[SIZE_FILEBUFFER];
int processedBytes = 0;
while (1) {
int bytes = unzReadCurrentFile(zf, buffer, sizeof(buffer));
if (bytes < 0)
SAFE_CALL(bytes);
if (bytes == 0)
break;
hasher.Update(buffer, bytes);
processedBytes += bytes;
}
HashDigest cmpHash = hasher.Finalize();
SAFE_CALL(unzCloseCurrentFile(zf));
if (mode == 0) {
ZipSyncAssertF(processedBytes == filemeta.props.compressedSize, "File %s has wrong compressed size: %d instead of %d", filename, filemeta.props.compressedSize, processedBytes);
filemeta.compressedHash = cmpHash;
}
else {
ZipSyncAssertF(processedBytes == filemeta.props.contentsSize, "File %s has wrong uncompressed size: %d instead of %d", filename, filemeta.props.contentsSize, processedBytes);
filemeta.contentsHash = cmpHash;
}
}
}
void AppendManifestsFromLocalZip(
const std::string &zipPathAbs, const std::string &rootDir,
FileLocation location,
const std::string &packageName,
Manifest &mani
) {
PathAR zipPath = PathAR::FromAbs(zipPathAbs, rootDir);
UnzFileHolder zf(zipPath.abs.c_str());
ZipSyncAssertF(!unzIsZip64(zf), "Zip64 is not supported!");
SAFE_CALL(unzGoToFirstFile(zf));
while (1) {
FileMetainfo filemeta;
filemeta.zipPath = zipPath;
filemeta.location = location;
filemeta.package = packageName;
AnalyzeCurrentFile(zf, filemeta);
mani.AppendFile(filemeta);
int err = unzGoToNextFile(zf);
if (err == UNZ_END_OF_LIST_OF_FILE)
break;
SAFE_CALL(err);
}
zf.reset();
}
void Manifest::AppendLocalZip(const std::string &zipPath, const std::string &rootDir, const std::string &packageName) {
ZipSyncAssert(PathAR::IsHttp(rootDir) == false);
AppendManifestsFromLocalZip(zipPath, rootDir, FileLocation::Local, packageName, *this);
}
void Manifest::AppendManifest(const Manifest &other) {
AppendVector(_files, other._files);
}
IniData Manifest::WriteToIni() const {
//sort files by INI order
std::vector<const FileMetainfo*> order;
for (const auto &f : _files)
order.push_back(&f);
std::sort(order.begin(), order.end(), [](const FileMetainfo *a, const FileMetainfo *b) {
return FileMetainfo::IsLess_ByZip(*a, *b);
});
IniData ini;
for (const FileMetainfo *pf : order) {
IniSect section;
section.push_back(std::make_pair("contentsHash", pf->contentsHash.Hex()));
section.push_back(std::make_pair("compressedHash", pf->compressedHash.Hex()));
section.push_back(std::make_pair("byterange", std::to_string(pf->byterange[0]) + "-" + std::to_string(pf->byterange[1])));
section.push_back(std::make_pair("package", pf->package));
section.push_back(std::make_pair("crc32", std::to_string(pf->props.crc32)));
section.push_back(std::make_pair("lastModTime", std::to_string(pf->props.lastModTime)));
section.push_back(std::make_pair("compressionMethod", std::to_string(pf->props.compressionMethod)));
section.push_back(std::make_pair("gpbitFlag", std::to_string(pf->props.generalPurposeBitFlag)));
section.push_back(std::make_pair("compressedSize", std::to_string(pf->props.compressedSize)));
section.push_back(std::make_pair("contentsSize", std::to_string(pf->props.contentsSize)));
section.push_back(std::make_pair("internalAttribs", std::to_string(pf->props.internalAttribs)));
section.push_back(std::make_pair("externalAttribs", std::to_string(pf->props.externalAttribs)));
std::string secName = "File " + GetFullPath(pf->zipPath.rel, pf->filename);
ini.push_back(std::make_pair(secName, std::move(section)));
}
return ini;
}
void Manifest::ReadFromIni(const IniData &data, const std::string &rootDir) {
bool remote = PathAR::IsHttp(rootDir);
for (const auto &pNS : data) {
FileMetainfo pf;
pf.location = (remote ? FileLocation::RemoteHttp : FileLocation::Local);
std::string name = pNS.first;
if (!stdext::starts_with(name, "File "))
continue;
name = name.substr(5);
const IniSect &sec = pNS.second;
ParseFullPath(name, pf.zipPath.rel, pf.filename);
pf.zipPath = PathAR::FromRel(pf.zipPath.rel, rootDir);
//note: since nobody would ever write manifest by hand
//here we rely on order of properties as written in WriteToIni
int propIdx = 0;
auto ReadProperty = [&sec,&propIdx](const char *key) -> const std::string& {
ZipSyncAssertF(propIdx < sec.size(), "No property while %s expected", key);
ZipSyncAssertF(sec[propIdx].first == key, "Expected property %s, got %s", key, sec[propIdx].first.c_str());
return sec[propIdx++].second;
};
pf.contentsHash.Parse(ReadProperty("contentsHash").c_str());
pf.compressedHash.Parse(ReadProperty("compressedHash").c_str());
const std::string &byterange = ReadProperty("byterange");
size_t pos = byterange.find('-');
ZipSyncAssertF(pos != std::string::npos, "Byterange %s has no hyphen", byterange.c_str());
pf.byterange[0] = std::stoul(byterange.substr(0, pos));
pf.byterange[1] = std::stoul(byterange.substr(pos+1));
if (pf.byterange[0] || pf.byterange[1]) {
ZipSyncAssert(pf.byterange[0] < pf.byterange[1]);
}
else
pf.location = FileLocation::Nowhere;
pf.package = ReadProperty("package");
pf.props.crc32 = std::stoul(ReadProperty("crc32"));
pf.props.lastModTime = std::stoul(ReadProperty("lastModTime"));
pf.props.compressionMethod = std::stoul(ReadProperty("compressionMethod"));
pf.props.generalPurposeBitFlag = std::stoul(ReadProperty("gpbitFlag"));
pf.props.compressedSize = std::stoul(ReadProperty("compressedSize"));
pf.props.contentsSize = std::stoul(ReadProperty("contentsSize"));
pf.props.internalAttribs = std::stoul(ReadProperty("internalAttribs"));
pf.props.externalAttribs = std::stoul(ReadProperty("externalAttribs"));
AppendFile(pf);
}
}
void Manifest::ReRoot(const std::string &rootDir) {
bool remote = PathAR::IsHttp(rootDir);
for (FileMetainfo &filemeta : _files) {
filemeta.zipPath = PathAR::FromRel(filemeta.zipPath.rel, rootDir);
if (filemeta.location != FileLocation::Nowhere)
filemeta.location = (remote ? FileLocation::RemoteHttp : FileLocation::Local);
}
}
Manifest Manifest::Filter(const std::function<bool(const FileMetainfo&)> &ifCopy) const {
Manifest res;
for (int i = 0; i < _files.size(); i++)
if (ifCopy(_files[i]))
res.AppendFile(_files[i]);
return res;
}
}