#include "zip.hpp" #include #include #include #include #include namespace connector { namespace { void append_u16(std::vector& output, std::uint16_t value) { output.push_back(static_cast(value)); output.push_back(static_cast(value >> 8)); } void append_u32(std::vector& output, std::uint32_t value) { append_u16(output, static_cast(value)); append_u16(output, static_cast(value >> 16)); } std::uint32_t crc32(const std::vector& data) { static const std::array table = [] { std::array 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((local.tm_hour << 11) | (local.tm_min << 5) | (local.tm_sec / 2)), static_cast(((year - 1980) << 9) | ((local.tm_mon + 1) << 5) | local.tm_mday), }; } } // namespace std::vector create_zip(const std::vector& 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 output; std::vector 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(file.data.size()), static_cast(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(file.data.size())); append_u32(output, static_cast(file.data.size())); append_u16(output, static_cast(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(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(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(output.size()) - central_offset; append_u32(output, 0x06054b50); append_u16(output, 0); append_u16(output, 0); append_u16(output, static_cast(records.size())); append_u16(output, static_cast(records.size())); append_u32(output, central_size); append_u32(output, central_offset); append_u16(output, 0); return output; } } // namespace connector