// Minimal in-memory ZIP writer for tests: stored entries, directory // placeholders, and "raw" entries whose bytes are supplied pre-packed so a // test can fabricate deflate / encrypted / odd-method records without a // compressor. Written independently of the reader under test. #pragma once #include #include #include #include namespace ziptest { inline std::uint32_t crc32(const std::uint8_t* p, std::size_t n, std::uint32_t crc = 0) { static std::uint32_t table[256]; static bool init = false; if (!init) { for (std::uint32_t i = 0; i < 256; ++i) { std::uint32_t c = i; for (int k = 0; k < 8; ++k) c = (c & 1) ? 0xEDB88320u ^ (c >> 1) : c >> 1; table[i] = c; } init = true; } crc = ~crc; for (std::size_t i = 0; i < n; ++i) crc = table[(crc ^ p[i]) & 0xFF] ^ (crc >> 8); return ~crc; } inline std::uint32_t crc32(std::string_view s) { return crc32(reinterpret_cast(s.data()), s.size()); } struct Item { std::string name; std::vector packed; // bytes as they sit in the file std::uint32_t uncompressed_size = 0; std::uint32_t crc = 0; std::uint16_t method = 0; std::uint16_t flags = 0; std::vector local_extra; // extra field on the local header only std::vector central_extra; std::uint32_t local_header_offset = 0; // filled by build() }; class ZipBuilder { public: Item& add(std::string name, std::string_view data) { Item it; it.name = std::move(name); it.packed.assign(data.begin(), data.end()); it.uncompressed_size = static_cast(data.size()); it.crc = crc32(data); items_.push_back(std::move(it)); return items_.back(); } Item& add_dir(std::string name) { if (name.empty() || name.back() != '/') name.push_back('/'); return add(std::move(name), ""); } // Pre-packed bytes with explicit method / sizes / crc. Item& add_raw(std::string name, std::vector packed, std::uint16_t method, std::uint32_t uncompressed_size, std::uint32_t crc) { Item it; it.name = std::move(name); it.packed = std::move(packed); it.method = method; it.uncompressed_size = uncompressed_size; it.crc = crc; items_.push_back(std::move(it)); return items_.back(); } std::vector& items() { return items_; } std::vector build(std::string_view comment = {}) { std::vector out; for (Item& it : items_) { it.local_header_offset = static_cast(out.size()); put32(out, 0x04034b50); put16(out, 20); put16(out, it.flags); put16(out, it.method); put16(out, 0); // time put16(out, 0); // date put32(out, it.crc); put32(out, static_cast(it.packed.size())); put32(out, it.uncompressed_size); put16(out, static_cast(it.name.size())); put16(out, static_cast(it.local_extra.size())); out.insert(out.end(), it.name.begin(), it.name.end()); out.insert(out.end(), it.local_extra.begin(), it.local_extra.end()); out.insert(out.end(), it.packed.begin(), it.packed.end()); } const std::uint32_t cd_offset = static_cast(out.size()); for (const Item& it : items_) { put32(out, 0x02014b50); put16(out, 20); // made by put16(out, 20); // needed put16(out, it.flags); put16(out, it.method); put16(out, 0); put16(out, 0); put32(out, it.crc); put32(out, static_cast(it.packed.size())); put32(out, it.uncompressed_size); put16(out, static_cast(it.name.size())); put16(out, static_cast(it.central_extra.size())); put16(out, 0); // comment len put16(out, 0); // disk put16(out, 0); // internal attrs put32(out, 0); // external attrs put32(out, it.local_header_offset); out.insert(out.end(), it.name.begin(), it.name.end()); out.insert(out.end(), it.central_extra.begin(), it.central_extra.end()); } const std::uint32_t cd_size = static_cast(out.size()) - cd_offset; put32(out, 0x06054b50); put16(out, 0); put16(out, 0); put16(out, static_cast(items_.size())); put16(out, static_cast(items_.size())); put32(out, cd_size); put32(out, cd_offset); put16(out, static_cast(comment.size())); out.insert(out.end(), comment.begin(), comment.end()); return out; } private: static void put16(std::vector& v, std::uint16_t x) { v.push_back(static_cast(x & 0xFF)); v.push_back(static_cast(x >> 8)); } static void put32(std::vector& v, std::uint32_t x) { for (int i = 0; i < 4; ++i) v.push_back(static_cast((x >> (8 * i)) & 0xFF)); } std::vector items_; }; } // namespace ziptest