#include "ZipFileDevice.hpp" #include #include "debug/Logger.hpp" #include "io/memory_istream.hpp" #include "io/deflate_istream.hpp" #include "util/data_io.hpp" #include "util/Buffer.hpp" static debug::Logger logger("zip-file"); using namespace io; static constexpr uint32_t EOCD_SIGNATURE = 0x06054b50; static constexpr uint32_t CENTRAL_DIR_SIGNATURE = 0x02014b50; static constexpr uint32_t LOCAL_FILE_SIGNATURE = 0x04034b50; static constexpr uint32_t COMPRESSION_NONE = 0; static constexpr uint32_t COMPRESSION_DEFLATE = 8; template static T read_int(std::unique_ptr& file) { T value = 0; file->read(reinterpret_cast(&value), sizeof(value)); return dataio::le2h(value); } template static void read_int(std::unique_ptr& file, T& value) { file->read(reinterpret_cast(&value), sizeof(value)); value = dataio::le2h(value); } ZipFileDevice::Entry ZipFileDevice::readEntry() { // Read entry info Entry entry {}; read_int(file, entry.versionMadeBy); read_int(file, entry.versionNeeded); read_int(file, entry.flags); read_int(file, entry.compressionMethod); read_int(file, entry.modTime); read_int(file, entry.modDate); read_int(file, entry.crc32); read_int(file, entry.compressedSize); read_int(file, entry.uncompressedSize); auto fileNameLength = read_int(file); auto extraFieldLength = read_int(file); auto fileCommentLength = read_int(file); read_int(file, entry.diskNumberStart); read_int(file, entry.internalAttributes); read_int(file, entry.externalAttributes); read_int(file, entry.localHeaderOffset); entry.fileName.resize(fileNameLength, '\0'); file->read(entry.fileName.data(), fileNameLength); // Skip extra field and file comment file->seekg(extraFieldLength + fileCommentLength, std::ios::cur); if (entry.diskNumberStart == 0xFF) { throw std::runtime_error("zip64 is not supported"); } return entry; } void ZipFileDevice::findBlob(Entry& entry) { file->seekg(entry.localHeaderOffset); if (read_int(file) != LOCAL_FILE_SIGNATURE) { throw std::runtime_error("invalid local file signature"); } read_int(file); // version read_int(file); // flags read_int(file); // compression method read_int(file); // last modification time read_int(file); // last modification date read_int(file); // crc32 read_int(file); // compressed size read_int(file); // uncompressed size auto nameLength = read_int(file); auto extraFieldLength = read_int(file); // Skip extra field and file comment file->seekg(nameLength + extraFieldLength, std::ios::cur); entry.blobOffset = file->tellg(); for (size_t i = 0; i < entry.fileName.length(); i++) { if (entry.fileName[i] == '\\') { entry.fileName[i] = '/'; } } if (entry.fileName[entry.fileName.length() - 1] == '/') { entry.isDirectory = true; entry.fileName = entry.fileName.substr(0, entry.fileName.length() - 1); } } ZipFileDevice::ZipFileDevice(std::unique_ptr filePtr) : file(std::move(filePtr)) { // Searching for EOCD file->seekg(0, std::ios::end); std::streampos fileSize = file->tellg(); bool foundEOCD = false; for (int pos = static_cast(fileSize)-4; pos >= 0; --pos) { file->seekg(pos); if (read_int(file) == EOCD_SIGNATURE) { foundEOCD = true; break; } } if (!foundEOCD) { throw std::runtime_error("EOCD not found, ZIP file is invalid"); } // Reading EOCD read_int(file); // diskNumber read_int(file); // centralDirDisk read_int(file); // numEntriesThisDisk auto totalEntries = read_int(file); read_int(file); // centralDirSize auto centralDirOffset = read_int(file); read_int(file); // commentLength file->seekg(centralDirOffset); for (uint16_t i = 0; i < totalEntries; i++) { if (read_int(file) != CENTRAL_DIR_SIGNATURE) { logger.error() << "invalid central directory entry"; break; } // Read entry info Entry entry = readEntry(); entries[entry.fileName] = std::move(entry); } for (auto& [_, entry] : entries) { findBlob(entry); } } std::filesystem::path ZipFileDevice::resolve(std::string_view path) { throw std::runtime_error("unable to resolve filesystem path"); } std::unique_ptr ZipFileDevice::write(std::string_view path) { return nullptr; } std::unique_ptr ZipFileDevice::read(std::string_view path) { const auto& found = entries.find(std::string(path)); if (found == entries.end()) { throw std::runtime_error("could not to open file zip://" + std::string(path)); } auto& entry = found->second; if (entry.isDirectory) { throw std::runtime_error("zip://" + std::string(path) + " is directory"); } if (entry.blobOffset == 0) { findBlob(entry); } file->seekg(entry.blobOffset); util::Buffer buffer(entry.compressedSize); file->read(buffer.data(), buffer.size()); auto memoryStream = std::make_unique(std::move(buffer)); if (entry.compressionMethod == COMPRESSION_NONE) { return memoryStream; } else if (entry.compressionMethod == COMPRESSION_DEFLATE) { return std::make_unique(std::move(memoryStream)); } else { throw std::runtime_error("unsupported compression method"); } } size_t ZipFileDevice::size(std::string_view path) { const auto& found = entries.find(std::string(path)); if (found == entries.end()) { return false; } return found->second.uncompressedSize; } bool ZipFileDevice::exists(std::string_view path) { return entries.find(std::string(path)) != entries.end(); } bool ZipFileDevice::isdir(std::string_view path) { const auto& found = entries.find(std::string(path)); if (found == entries.end()) { return false; } return found->second.isDirectory; } bool ZipFileDevice::isfile(std::string_view path) { const auto& found = entries.find(std::string(path)); if (found == entries.end()) { return false; } return !found->second.isDirectory; } bool ZipFileDevice::mkdir(std::string_view path) { return false; } bool ZipFileDevice::mkdirs(std::string_view path) { return false; } bool ZipFileDevice::remove(std::string_view path) { return false; } uint64_t ZipFileDevice::removeAll(std::string_view path) { return 0; } std::unique_ptr ZipFileDevice::list(std::string_view path) { return nullptr; }