144 lines
5.4 KiB
C++
144 lines
5.4 KiB
C++
// 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 <cstdint>
|
|
#include <string>
|
|
#include <string_view>
|
|
#include <vector>
|
|
|
|
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<const std::uint8_t*>(s.data()), s.size());
|
|
}
|
|
|
|
struct Item {
|
|
std::string name;
|
|
std::vector<std::uint8_t> 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<std::uint8_t> local_extra; // extra field on the local header only
|
|
std::vector<std::uint8_t> 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<std::uint32_t>(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<std::uint8_t> 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<Item>& items() { return items_; }
|
|
|
|
std::vector<std::uint8_t> build(std::string_view comment = {}) {
|
|
std::vector<std::uint8_t> out;
|
|
for (Item& it : items_) {
|
|
it.local_header_offset = static_cast<std::uint32_t>(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<std::uint32_t>(it.packed.size()));
|
|
put32(out, it.uncompressed_size);
|
|
put16(out, static_cast<std::uint16_t>(it.name.size()));
|
|
put16(out, static_cast<std::uint16_t>(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<std::uint32_t>(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<std::uint32_t>(it.packed.size()));
|
|
put32(out, it.uncompressed_size);
|
|
put16(out, static_cast<std::uint16_t>(it.name.size()));
|
|
put16(out, static_cast<std::uint16_t>(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<std::uint32_t>(out.size()) - cd_offset;
|
|
put32(out, 0x06054b50);
|
|
put16(out, 0);
|
|
put16(out, 0);
|
|
put16(out, static_cast<std::uint16_t>(items_.size()));
|
|
put16(out, static_cast<std::uint16_t>(items_.size()));
|
|
put32(out, cd_size);
|
|
put32(out, cd_offset);
|
|
put16(out, static_cast<std::uint16_t>(comment.size()));
|
|
out.insert(out.end(), comment.begin(), comment.end());
|
|
return out;
|
|
}
|
|
|
|
private:
|
|
static void put16(std::vector<std::uint8_t>& v, std::uint16_t x) {
|
|
v.push_back(static_cast<std::uint8_t>(x & 0xFF));
|
|
v.push_back(static_cast<std::uint8_t>(x >> 8));
|
|
}
|
|
static void put32(std::vector<std::uint8_t>& v, std::uint32_t x) {
|
|
for (int i = 0; i < 4; ++i) v.push_back(static_cast<std::uint8_t>((x >> (8 * i)) & 0xFF));
|
|
}
|
|
|
|
std::vector<Item> items_;
|
|
};
|
|
|
|
} // namespace ziptest
|