Files
SpineParticlesWeb/connector/native/zip.cpp
T
2026-09-05 23:35:45 +08:00

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