137 lines
4.6 KiB
C++
137 lines
4.6 KiB
C++
#include "zip.hpp"
|
|
|
|
#include <array>
|
|
#include <chrono>
|
|
#include <ctime>
|
|
#include <filesystem>
|
|
#include <stdexcept>
|
|
|
|
namespace connector {
|
|
namespace {
|
|
|
|
void append_u16(std::vector<std::uint8_t>& output, std::uint16_t value) {
|
|
output.push_back(static_cast<std::uint8_t>(value));
|
|
output.push_back(static_cast<std::uint8_t>(value >> 8));
|
|
}
|
|
|
|
void append_u32(std::vector<std::uint8_t>& output, std::uint32_t value) {
|
|
append_u16(output, static_cast<std::uint16_t>(value));
|
|
append_u16(output, static_cast<std::uint16_t>(value >> 16));
|
|
}
|
|
|
|
std::uint32_t crc32(const std::vector<std::uint8_t>& data) {
|
|
static const std::array<std::uint32_t, 256> table = [] {
|
|
std::array<std::uint32_t, 256> values{};
|
|
for (std::uint32_t value = 0; value < values.size(); ++value) {
|
|
std::uint32_t crc = value;
|
|
for (int bit = 0; bit < 8; ++bit) crc = (crc & 1) ? 0xedb88320U ^ (crc >> 1) : crc >> 1;
|
|
values[value] = crc;
|
|
}
|
|
return values;
|
|
}();
|
|
std::uint32_t crc = 0xffffffffU;
|
|
for (const auto byte : data) crc = table[(crc ^ byte) & 0xff] ^ (crc >> 8);
|
|
return crc ^ 0xffffffffU;
|
|
}
|
|
|
|
std::string archive_path(const std::string& raw) {
|
|
std::string value = raw;
|
|
for (char& c : value) if (c == '\\') c = '/';
|
|
while (value.rfind("./", 0) == 0) value.erase(0, 2);
|
|
if (value.empty() || value.front() == '/' || value == ".." || value.rfind("../", 0) == 0 ||
|
|
value.find("/../") != std::string::npos) {
|
|
throw std::runtime_error("ZIP 文件路径无效");
|
|
}
|
|
return value;
|
|
}
|
|
|
|
struct Timestamp {
|
|
std::uint16_t time;
|
|
std::uint16_t date;
|
|
};
|
|
|
|
Timestamp dos_timestamp() {
|
|
const std::time_t now = std::time(nullptr);
|
|
std::tm local{};
|
|
#ifdef _WIN32
|
|
localtime_s(&local, &now);
|
|
#else
|
|
localtime_r(&now, &local);
|
|
#endif
|
|
const int year = std::max(1980, local.tm_year + 1900);
|
|
return {
|
|
static_cast<std::uint16_t>((local.tm_hour << 11) | (local.tm_min << 5) | (local.tm_sec / 2)),
|
|
static_cast<std::uint16_t>(((year - 1980) << 9) | ((local.tm_mon + 1) << 5) | local.tm_mday),
|
|
};
|
|
}
|
|
|
|
} // namespace
|
|
|
|
std::vector<std::uint8_t> create_zip(const std::vector<ArchiveFile>& files) {
|
|
if (files.size() > 65535) throw std::runtime_error("ZIP 文件数量过多");
|
|
struct CentralRecord {
|
|
std::string name;
|
|
std::uint32_t crc;
|
|
std::uint32_t size;
|
|
std::uint32_t offset;
|
|
};
|
|
std::vector<std::uint8_t> output;
|
|
std::vector<CentralRecord> records;
|
|
const Timestamp timestamp = dos_timestamp();
|
|
for (const auto& file : files) {
|
|
const std::string name = archive_path(file.name);
|
|
if (name.size() > 65535 || file.data.size() > 0xffffffffULL || output.size() > 0xffffffffULL) {
|
|
throw std::runtime_error("ZIP 文件超过 Store 格式限制");
|
|
}
|
|
const auto checksum = crc32(file.data);
|
|
records.push_back({name, checksum, static_cast<std::uint32_t>(file.data.size()),
|
|
static_cast<std::uint32_t>(output.size())});
|
|
append_u32(output, 0x04034b50);
|
|
append_u16(output, 20);
|
|
append_u16(output, 0x0800);
|
|
append_u16(output, 0);
|
|
append_u16(output, timestamp.time);
|
|
append_u16(output, timestamp.date);
|
|
append_u32(output, checksum);
|
|
append_u32(output, static_cast<std::uint32_t>(file.data.size()));
|
|
append_u32(output, static_cast<std::uint32_t>(file.data.size()));
|
|
append_u16(output, static_cast<std::uint16_t>(name.size()));
|
|
append_u16(output, 0);
|
|
output.insert(output.end(), name.begin(), name.end());
|
|
output.insert(output.end(), file.data.begin(), file.data.end());
|
|
}
|
|
const auto central_offset = static_cast<std::uint32_t>(output.size());
|
|
for (const auto& record : records) {
|
|
append_u32(output, 0x02014b50);
|
|
append_u16(output, 20);
|
|
append_u16(output, 20);
|
|
append_u16(output, 0x0800);
|
|
append_u16(output, 0);
|
|
append_u16(output, timestamp.time);
|
|
append_u16(output, timestamp.date);
|
|
append_u32(output, record.crc);
|
|
append_u32(output, record.size);
|
|
append_u32(output, record.size);
|
|
append_u16(output, static_cast<std::uint16_t>(record.name.size()));
|
|
append_u16(output, 0);
|
|
append_u16(output, 0);
|
|
append_u16(output, 0);
|
|
append_u16(output, 0);
|
|
append_u32(output, 0);
|
|
append_u32(output, record.offset);
|
|
output.insert(output.end(), record.name.begin(), record.name.end());
|
|
}
|
|
const auto central_size = static_cast<std::uint32_t>(output.size()) - central_offset;
|
|
append_u32(output, 0x06054b50);
|
|
append_u16(output, 0);
|
|
append_u16(output, 0);
|
|
append_u16(output, static_cast<std::uint16_t>(records.size()));
|
|
append_u16(output, static_cast<std::uint16_t>(records.size()));
|
|
append_u32(output, central_size);
|
|
append_u32(output, central_offset);
|
|
append_u16(output, 0);
|
|
return output;
|
|
}
|
|
|
|
} // namespace connector
|