17#include "libzarr/detail/common.hpp"
34inline constexpr std::uint32_t kLocalSig = 0x04034b50;
35inline constexpr std::uint32_t kCentralSig = 0x02014b50;
36inline constexpr std::uint32_t kEocdSig = 0x06054b50;
37inline constexpr std::uint32_t kEocd64Sig = 0x06064b50;
38inline constexpr std::uint32_t kEocd64LocatorSig = 0x07064b50;
39inline constexpr std::size_t kEocdSize = 22;
40inline constexpr std::size_t kEocd64Size = 56;
41inline constexpr std::size_t kEocd64LocatorSize = 20;
42inline constexpr std::size_t kLocalHeaderSize = 30;
43inline constexpr std::size_t kCentralHeaderSize = 46;
44inline constexpr std::uint32_t kMax32 = 0xFFFFFFFFU;
45inline constexpr std::uint16_t kMax16 = 0xFFFFU;
47inline std::uint16_t rd16(
const std::uint8_t* p) {
48 return static_cast<std::uint16_t
>(p[0] | (
static_cast<std::uint16_t
>(p[1]) << 8U));
50inline std::uint32_t rd32(
const std::uint8_t* p) {
51 return static_cast<std::uint32_t
>(p[0]) | (
static_cast<std::uint32_t
>(p[1]) << 8U) |
52 (
static_cast<std::uint32_t
>(p[2]) << 16U) | (
static_cast<std::uint32_t
>(p[3]) << 24U);
54inline std::uint64_t rd64(
const std::uint8_t* p) {
55 return static_cast<std::uint64_t
>(rd32(p)) | (
static_cast<std::uint64_t
>(rd32(p + 4)) << 32U);
57inline void wr16(Bytes& out, std::uint16_t v) {
58 out.push_back(
static_cast<std::uint8_t
>(v & 0xFFU));
59 out.push_back(
static_cast<std::uint8_t
>(v >> 8U));
61inline void wr32(Bytes& out, std::uint32_t v) {
62 for (
int i = 0; i < 4; ++i) {
63 out.push_back(
static_cast<std::uint8_t
>(v >> (8 * i)));
66inline void wr64(Bytes& out, std::uint64_t v) {
67 for (
int i = 0; i < 8; ++i) {
68 out.push_back(
static_cast<std::uint8_t
>(v >> (8 * i)));
74inline std::uint32_t crc32(
const std::uint8_t* data, std::size_t size) {
75 static const std::array<std::uint32_t, 256> table = [] {
76 std::array<std::uint32_t, 256> t{};
77 for (std::uint32_t i = 0; i < 256; ++i) {
79 for (
int k = 0; k < 8; ++k) {
80 c = ((c & 1U) != 0) ? 0xEDB88320U ^ (c >> 1U) : c >> 1U;
86 std::uint32_t c = 0xFFFFFFFFU;
87 for (std::size_t i = 0; i < size; ++i) {
88 c = table[(c ^ data[i]) & 0xFFU] ^ (c >> 8U);
90 return c ^ 0xFFFFFFFFU;
94 std::uint64_t header_offset = 0;
95 std::uint64_t size = 0;
96 std::uint16_t method = 0;
99 std::optional<std::uint64_t> data_offset;
102struct CentralDirectory {
103 std::uint64_t count = 0;
104 std::uint64_t size = 0;
105 std::uint64_t offset = 0;
111 std::uint32_t crc = 0;
112 std::uint64_t size = 0;
113 std::uint64_t header_offset = 0;
120inline void append_local_header(Bytes& out,
const PackEntry& e) {
121 wr32(out, kLocalSig);
122 wr16(out, e.wide ? 45 : 20);
128 const auto size32 = e.wide ? kMax32 :
static_cast<std::uint32_t
>(e.size);
131 wr16(out,
static_cast<std::uint16_t
>(e.name.size()));
132 wr16(out, e.wide ? 20 : 0);
133 out.insert(out.end(), e.name.begin(), e.name.end());
143inline void append_central_header(Bytes& cen,
const PackEntry& e) {
144 const bool any64 = e.wide || e.far;
145 const auto size32 = e.wide ? kMax32 :
static_cast<std::uint32_t
>(e.size);
146 wr32(cen, kCentralSig);
147 wr16(cen, any64 ? 45 : 20);
148 wr16(cen, any64 ? 45 : 20);
156 wr16(cen,
static_cast<std::uint16_t
>(e.name.size()));
157 wr16(cen, any64 ?
static_cast<std::uint16_t
>(4 + (e.wide ? 16 : 0) + (e.far ? 8 : 0)) : 0);
162 wr32(cen, e.far ? kMax32 : static_cast<std::uint32_t>(e.header_offset));
163 cen.insert(cen.end(), e.name.begin(), e.name.end());
166 wr16(cen,
static_cast<std::uint16_t
>((e.wide ? 16 : 0) + (e.far ? 8 : 0)));
172 wr64(cen, e.header_offset);
178inline void append_end_records(Bytes& out, std::uint64_t count, std::uint64_t cen_size,
179 std::uint64_t cen_offset,
bool force_zip64) {
180 const bool eocd64 = force_zip64 || count >= kMax16 || cen_size >= kMax32 || cen_offset >= kMax32;
182 const std::uint64_t eocd64_offset = out.size();
183 wr32(out, kEocd64Sig);
184 wr64(out, kEocd64Size - 12);
192 wr64(out, cen_offset);
193 wr32(out, kEocd64LocatorSig);
195 wr64(out, eocd64_offset);
201 const auto count16 = eocd64 ? kMax16 :
static_cast<std::uint16_t
>(count);
204 wr32(out, eocd64 ? kMax32 : static_cast<std::uint32_t>(cen_size));
205 wr32(out, eocd64 ? kMax32 : static_cast<std::uint32_t>(cen_offset));
219 ZipStore(std::shared_ptr<Store> source, std::string archive_key)
220 : source_(std::move(source)), key_(std::move(archive_key)) {
222 throw error(
"ZipStore: null store");
227 [[nodiscard]] std::optional<Bytes>
read(std::string_view key)
override {
232 const auto it = entries_.find(key);
233 if (it == entries_.end()) {
236 detail_zip::Entry& entry = it->second;
237 if (entry.method != 0) {
239 throw error(context() +
": entry '" + std::string(key) +
"' uses compression method " +
240 std::to_string(entry.method) +
"; only STORED entries are supported");
242 std::uint64_t begin = 0;
243 std::uint64_t count = entry.size;
246 throw error(context() +
": range out of bounds for entry '" + std::string(key) +
"' (" +
247 std::to_string(entry.size) +
" bytes)");
252 if (range.
length > entry.size) {
253 throw error(context() +
": suffix out of bounds for entry '" + std::string(key) +
"' (" +
254 std::to_string(entry.size) +
" bytes)");
256 begin = entry.size - range.
length;
259 return must_read(data_offset(key, entry) + begin, count);
262 [[nodiscard]] std::optional<std::uint64_t>
size(std::string_view key)
override {
263 const auto it = entries_.find(key);
264 if (it == entries_.end()) {
267 return it->second.size;
270 [[nodiscard]]
bool exists(std::string_view key)
override {
271 return entries_.find(key) != entries_.end();
275 throw error(
"ZipStore is read-only");
277 void erase(std::string_view )
override {
throw error(
"ZipStore is read-only"); }
279 [[nodiscard]] std::vector<std::string>
list_prefix(std::string_view prefix)
override {
280 check_prefix(prefix);
281 std::vector<std::string> out;
282 for (
auto it = entries_.lower_bound(prefix);
283 it != entries_.end() && detail::starts_with(it->first, prefix); ++it) {
284 out.push_back(it->first);
290 check_prefix(prefix);
292 for (
auto it = entries_.lower_bound(prefix);
293 it != entries_.end() && detail::starts_with(it->first, prefix); ++it) {
294 const auto rest = std::string_view(it->first).substr(prefix.size());
295 const auto slash = rest.find(
'/');
296 if (slash == std::string_view::npos) {
297 out.
keys.emplace_back(rest);
299 const auto child = rest.substr(0, slash);
309 [[nodiscard]] std::size_t
entry_count()
const {
return entries_.size(); }
312 [[nodiscard]] std::string context()
const {
return key_.empty() ?
"zip archive" : key_; }
314 static void check_prefix(std::string_view prefix) {
315 if (!prefix.empty() && prefix.back() !=
'/') {
316 throw error(
"store prefix must be empty or end with '/', got '" + std::string(prefix) +
"'");
320 [[nodiscard]]
Bytes must_read(std::uint64_t offset, std::uint64_t length) {
323 throw error(context() +
": archive disappeared mid-read");
325 return *std::move(bytes);
328 void parse_directory() {
329 namespace z = detail_zip;
330 const auto archive_size = source_->size(key_);
332 throw error(context() +
": not found");
334 if (*archive_size < z::kEocdSize) {
335 throw error(context() +
": too small to be a zip archive");
339 const std::uint64_t tail_len =
340 std::min<std::uint64_t>(*archive_size, z::kEocdSize + z::kMax16 + z::kEocd64LocatorSize);
343 throw error(context() +
": archive disappeared mid-read");
345 const Bytes tail = *std::move(tail_bytes);
346 const std::uint64_t tail_start = *archive_size - tail_len;
348 const std::size_t eocd = find_eocd(tail);
349 const detail_zip::CentralDirectory dir = locate_directory(tail, tail_start, eocd);
350 const Bytes cen = must_read(dir.offset, dir.size);
352 for (std::uint64_t n = 0; n < dir.count; ++n) {
353 parse_entry(cen, pos);
358 [[nodiscard]] std::size_t find_eocd(
const Bytes& tail)
const {
359 namespace z = detail_zip;
360 for (std::size_t i = tail.size() - z::kEocdSize + 1; i-- > 0;) {
361 if (z::rd32(tail.data() + i) == z::kEocdSig &&
362 i + z::kEocdSize + z::rd16(tail.data() + i + 20) == tail.size()) {
366 throw error(context() +
": end-of-central-directory record not found (not a zip archive?)");
370 [[nodiscard]] detail_zip::CentralDirectory locate_directory(
const Bytes& tail,
371 std::uint64_t tail_start,
373 namespace z = detail_zip;
374 if (z::rd16(tail.data() + eocd + 4) != 0 || z::rd16(tail.data() + eocd + 6) != 0) {
375 throw error(context() +
": multi-disk archives are not supported");
377 z::CentralDirectory dir;
378 dir.count = z::rd16(tail.data() + eocd + 10);
379 dir.size = z::rd32(tail.data() + eocd + 12);
380 dir.offset = z::rd32(tail.data() + eocd + 16);
381 if (dir.count != z::kMax16 && dir.size != z::kMax32 && dir.offset != z::kMax32) {
385 if (eocd < z::kEocd64LocatorSize ||
386 z::rd32(tail.data() + eocd - z::kEocd64LocatorSize) != z::kEocd64LocatorSig) {
387 throw error(context() +
": ZIP64 locator not found");
389 const std::uint64_t eocd64_offset = z::rd64(tail.data() + eocd - z::kEocd64LocatorSize + 8);
391 if (eocd64_offset >= tail_start) {
392 const auto local =
static_cast<std::size_t
>(eocd64_offset - tail_start);
396 if (local > tail.size() || z::kEocd64Size > tail.size() - local) {
397 throw error(context() +
": ZIP64 end-of-central-directory record out of range");
399 eocd64.assign(tail.begin() +
static_cast<std::ptrdiff_t
>(local),
400 tail.begin() +
static_cast<std::ptrdiff_t
>(local + z::kEocd64Size));
402 eocd64 = must_read(eocd64_offset, z::kEocd64Size);
406 if (eocd64.size() < z::kEocd64Size) {
407 throw error(context() +
": ZIP64 end-of-central-directory record truncated");
409 if (z::rd32(eocd64.data()) != z::kEocd64Sig) {
410 throw error(context() +
": bad ZIP64 end-of-central-directory record");
412 dir.count = z::rd64(eocd64.data() + 32);
413 dir.size = z::rd64(eocd64.data() + 40);
414 dir.offset = z::rd64(eocd64.data() + 48);
419 void parse_entry(
const Bytes& cen, std::size_t& pos) {
420 namespace z = detail_zip;
421 if (pos + z::kCentralHeaderSize > cen.size() || z::rd32(cen.data() + pos) != z::kCentralSig) {
422 throw error(context() +
": corrupt central directory");
424 const std::uint8_t* h = cen.data() + pos;
426 entry.method = z::rd16(h + 10);
427 std::uint64_t csize = z::rd32(h + 20);
428 entry.size = z::rd32(h + 24);
429 const std::size_t name_len = z::rd16(h + 28);
430 const std::size_t extra_len = z::rd16(h + 30);
431 const std::size_t comment_len = z::rd16(h + 32);
432 entry.header_offset = z::rd32(h + 42);
433 if (pos + z::kCentralHeaderSize + name_len + extra_len + comment_len > cen.size()) {
434 throw error(context() +
": corrupt central directory");
436 std::string name(
reinterpret_cast<const char*
>(h + z::kCentralHeaderSize), name_len);
437 apply_zip64_extra(h + z::kCentralHeaderSize + name_len, extra_len, entry, csize, name);
439 if (entry.method == 0 && csize != entry.size) {
440 throw error(context() +
": STORED entry '" + name +
"' has mismatched sizes");
442 if (!name.empty() && name.back() !=
'/') {
443 entries_.insert_or_assign(std::move(name), entry);
445 pos += z::kCentralHeaderSize + name_len + extra_len + comment_len;
451 void apply_zip64_extra(
const std::uint8_t* extra, std::size_t extra_len, detail_zip::Entry& entry,
452 std::uint64_t& csize,
const std::string& name)
const {
453 namespace z = detail_zip;
454 std::size_t epos = 0;
455 while (epos + 4 <= extra_len) {
456 const std::uint16_t
id = z::rd16(extra + epos);
457 const std::uint16_t len = z::rd16(extra + epos + 2);
458 if (epos + 4 + len > extra_len) {
459 throw error(context() +
": corrupt extra field in '" + name +
"'");
462 const std::uint8_t* f = extra + epos + 4;
463 std::size_t fpos = 0;
464 const auto take64 = [&](std::uint64_t& value) {
465 if (fpos + 8 > len) {
466 throw error(context() +
": truncated ZIP64 extra field in '" + name +
"'");
468 value = z::rd64(f + fpos);
471 if (entry.size == z::kMax32) {
474 if (csize == z::kMax32) {
477 if (entry.header_offset == z::kMax32) {
478 take64(entry.header_offset);
481 epos += std::size_t{4} + len;
488 std::uint64_t data_offset(std::string_view key, detail_zip::Entry& entry) {
489 namespace z = detail_zip;
490 if (!entry.data_offset) {
491 const Bytes lfh = must_read(entry.header_offset, z::kLocalHeaderSize);
492 if (z::rd32(lfh.data()) != z::kLocalSig) {
493 throw error(context() +
": corrupt local header for entry '" + std::string(key) +
"'");
495 entry.data_offset = entry.header_offset + z::kLocalHeaderSize + z::rd16(lfh.data() + 26) +
496 z::rd16(lfh.data() + 28);
498 return *entry.data_offset;
501 std::shared_ptr<Store> source_;
503 std::map<std::string, detail_zip::Entry, std::less<>> entries_;
506namespace detail_zip {
511inline void zip_pack_impl(Store& source, Store& dest,
const std::string& dest_key,
512 const std::string& prefix,
bool force_zip64) {
513 namespace z = detail_zip;
516 std::uint64_t count = 0;
518 for (
const std::string& key : source.list_prefix(prefix)) {
519 const auto value = source.read(key);
524 entry.name = key.substr(prefix.size());
525 if (entry.name.size() > z::kMax16) {
526 throw error(
"zip_pack: entry name too long: '" + entry.name +
"'");
528 entry.crc = z::crc32(value->data(), value->size());
529 entry.size = value->size();
530 entry.header_offset = out.size();
531 entry.wide = force_zip64 || entry.size >= z::kMax32;
532 entry.far = force_zip64 || entry.header_offset >= z::kMax32;
533 z::append_local_header(out, entry);
534 out.insert(out.end(), value->begin(), value->end());
535 z::append_central_header(cen, entry);
539 const std::uint64_t cen_offset = out.size();
540 out.insert(out.end(), cen.begin(), cen.end());
541 z::append_end_records(out, count, cen.size(), cen_offset, force_zip64);
542 dest.write(dest_key, std::move(out));
551 const std::string& prefix =
"") {
552 detail_zip::zip_pack_impl(source, dest, dest_key, prefix,
false);
void erase(std::string_view) override
Remove key; removing an absent key is a no-op.
Definition zip.hpp:277
std::optional< std::uint64_t > size(std::string_view key) override
Definition zip.hpp:262
DirListing list_dir(std::string_view prefix) override
Immediate children under prefix ("" or ending in '/').
Definition zip.hpp:289
std::optional< Bytes > read_range(std::string_view key, ByteRange range) override
Definition zip.hpp:231
bool exists(std::string_view key) override
True if key holds a value.
Definition zip.hpp:270
std::optional< Bytes > read(std::string_view key) override
Full value at key, or std::nullopt if the key is absent.
Definition zip.hpp:227
void write(std::string_view, Bytes) override
Create or replace the value at key.
Definition zip.hpp:274
std::size_t entry_count() const
Number of entries in the archive.
Definition zip.hpp:309
ZipStore(std::shared_ptr< Store > source, std::string archive_key)
Definition zip.hpp:219
std::vector< std::string > list_prefix(std::string_view prefix) override
All keys starting with prefix ("" or ending in '/'), sorted.
Definition zip.hpp:279
Byte-range request for Store::read_range.
Definition store.hpp:28
@ suffix
the final length bytes
@ slice
length bytes starting at offset
std::uint64_t offset
Start of the range; used by Kind::slice only.
Definition store.hpp:39
static constexpr ByteRange full()
The whole value.
Definition store.hpp:44
std::uint64_t length
Number of bytes; used by Kind::slice and Kind::suffix.
Definition store.hpp:41
Kind kind
Which part of the value to read.
Definition store.hpp:37
Immediate children of a prefix, as returned by Store::list_dir.
Definition store.hpp:70
std::vector< std::string > keys
Child keys, relative to the queried prefix, sorted.
Definition store.hpp:72
std::vector< std::string > prefixes
Child prefixes ("directories"), relative, without trailing '/', sorted.
Definition store.hpp:74
std::vector< std::uint8_t > Bytes
Owned byte buffer used throughout the value-based public API.
Definition types.hpp:42
void zip_pack(Store &source, Store &dest, const std::string &dest_key, const std::string &prefix="")
Definition zip.hpp:550