From 0bb68a88e6dc6a04685825e80b4e3dca1dc097d2 Mon Sep 17 00:00:00 2001 From: Andrei Karas Date: Fri, 24 Feb 2017 19:10:28 +0300 Subject: Add support for extract files from zip archives. --- src/fs/virtfile.cpp | 4 +- src/fs/virtfile.h | 1 + src/fs/virtfileprivate.cpp | 6 +-- src/fs/virtfileprivate.h | 5 +- src/fs/zip.cpp | 114 ++++++++++++++++++++++++++++++++++++++++++- src/fs/zip.h | 6 +++ src/fs/zip_unittest.cc | 117 +++++++++++++++++++++++++++++++++++++++++++-- src/fs/ziplocalheader.h | 4 +- 8 files changed, 245 insertions(+), 12 deletions(-) diff --git a/src/fs/virtfile.cpp b/src/fs/virtfile.cpp index 9f9ab6e98..fb719f05c 100644 --- a/src/fs/virtfile.cpp +++ b/src/fs/virtfile.cpp @@ -27,11 +27,13 @@ #include "debug.h" VirtFile::VirtFile() : - mPrivate(nullptr) + mPrivate(nullptr), + mBuf(nullptr) { } VirtFile::~VirtFile() { delete2(mPrivate); + delete [] mBuf; } diff --git a/src/fs/virtfile.h b/src/fs/virtfile.h index cf7ded1a4..cb6211e83 100644 --- a/src/fs/virtfile.h +++ b/src/fs/virtfile.h @@ -34,6 +34,7 @@ struct VirtFile final ~VirtFile(); VirtFilePrivate *mPrivate; + uint8_t *mBuf; }; #endif // UTILS_VIRTFILE_H diff --git a/src/fs/virtfileprivate.cpp b/src/fs/virtfileprivate.cpp index 1dcc6116c..5ebb234cc 100644 --- a/src/fs/virtfileprivate.cpp +++ b/src/fs/virtfileprivate.cpp @@ -21,6 +21,7 @@ #include "fs/virtfileprivate.h" #include +#include #include "debug.h" @@ -36,7 +37,7 @@ VirtFilePrivate::VirtFilePrivate(const int fd) : { } -VirtFilePrivate::VirtFilePrivate(PHYSFS_file *const file) : +VirtFilePrivate::VirtFilePrivate(PHYSFS_file *restrict const file) : mFile(file), mFd(-1) { @@ -45,10 +46,7 @@ VirtFilePrivate::VirtFilePrivate(PHYSFS_file *const file) : VirtFilePrivate::~VirtFilePrivate() { if (mFile != nullptr) - { PHYSFS_close(mFile); - mFile = nullptr; - } if (mFd != -1) close(mFd); } diff --git a/src/fs/virtfileprivate.h b/src/fs/virtfileprivate.h index 62e510142..ace7b49e0 100644 --- a/src/fs/virtfileprivate.h +++ b/src/fs/virtfileprivate.h @@ -32,7 +32,7 @@ struct VirtFilePrivate final { VirtFilePrivate(); - explicit VirtFilePrivate(PHYSFS_file *const file); + explicit VirtFilePrivate(PHYSFS_file *restrict const file); explicit VirtFilePrivate(const int fd); @@ -40,7 +40,10 @@ struct VirtFilePrivate final ~VirtFilePrivate(); + // physfs fields PHYSFS_file *mFile; + + // dirfs fields int mFd; }; diff --git a/src/fs/zip.cpp b/src/fs/zip.cpp index 57aa9fc76..cff4a3e6f 100644 --- a/src/fs/zip.cpp +++ b/src/fs/zip.cpp @@ -20,6 +20,7 @@ #include "fs/zip.h" +#include "fs/virtfile.h" #include "fs/ziplocalheader.h" #include "utils/checkutils.h" @@ -27,6 +28,7 @@ #include #include +#include #include "debug.h" @@ -58,6 +60,7 @@ namespace Zip size_t cnt = 0U; uint8_t *const buf = new uint8_t[65535 + 10]; uint16_t val16 = 0U; + uint16_t method = 0U; ZipLocalHeader *header = nullptr; logger->log("Read archive: %s", archiveName.c_str()); @@ -77,7 +80,13 @@ namespace Zip header = new ZipLocalHeader; header->archiveName = archiveName; // skip useless fields - fseek(arcFile, 14, SEEK_CUR); // + 14 + fseek(arcFile, 4, SEEK_CUR); // + 4 + // file header pointer on 8 + // +++ need add endian specific decoding for method + readVal(&method, 2, "compression method") // + 2 + header->compressed = (method != 0); + // file header pointer on 10 + fseek(arcFile, 8, SEEK_CUR); // + 8 // file header pointer on 18 readVal(&header->compressSize, 4, "zip compressed size") // + 4 @@ -115,6 +124,8 @@ namespace Zip headers.push_back(header); logger->log(" file name: %s", header->fileName.c_str()); + logger->log(" compression method: %u", + CAST_U32(method)); logger->log(" compressed size: %u", header->compressSize); logger->log(" uncompressed size: %u", @@ -160,4 +171,105 @@ namespace Zip return true; } + void reportZlibError(const std::string &text, + const int err) + { + reportAlways("Zlib error: '%s' in %s", + text.c_str(), + getZlibError(err).c_str()); + } + + std::string getZlibError(const int err) + { + switch (err) + { + case Z_OK: + return std::string(); + default: + return "unknown zlib error"; + } + } + + uint8_t *readCompressedFile(const ZipLocalHeader *restrict const header) + { + if (header == nullptr) + { + reportAlways("Zip::readCompressedFile: header is null"); + return nullptr; + } + FILE *restrict const arcFile = fopen(header->archiveName.c_str(), + "r"); + if (arcFile == nullptr) + { + reportAlways("Can't open zip file %s", + header->archiveName.c_str()); + return nullptr; + } + + fseek(arcFile, header->dataOffset, SEEK_SET); + const uint32_t compressSize = header->compressSize; + uint8_t *const buf = new uint8_t[compressSize]; + if (fread(static_cast(buf), 1, compressSize, arcFile) != + compressSize) + { + reportAlways("Read zip compressed file error from archive: %s", + header->archiveName.c_str()); + fclose(arcFile); + delete [] buf; + return nullptr; + } + fclose(arcFile); + return buf; + } + + uint8_t *readFile(const ZipLocalHeader *restrict const header) + { + if (header == nullptr) + { + reportAlways("Open zip file error. header is null."); + return nullptr; + } + uint8_t *restrict const in = readCompressedFile(header); + if (in == nullptr) + return nullptr; + if (header->compressed == false) + return in; // return as is if data not compressed + const size_t outSize = header->uncompressSize; + uint8_t *restrict const out = new uint8_t[outSize]; + if (outSize == 0) + return out; + + z_stream strm; + strm.zalloc = nullptr; + strm.zfree = nullptr; + strm.opaque = nullptr; + strm.next_in = in; + strm.avail_in = header->compressSize; + strm.next_out = out; + strm.avail_out = outSize; + + int ret = inflateInit2(&strm, -MAX_WBITS); + if (ret != Z_OK) + { + reportZlibError(header->archiveName, ret); + delete [] in; + delete [] out; + return nullptr; + } + ret = inflate(&strm, Z_FINISH); +// ret = inflate(&strm, Z_SYNC_FLUSH); + if (ret != Z_OK && + ret != Z_STREAM_END) + { + reportZlibError("file decompression error", + ret); + inflateEnd(&strm); + delete [] in; + delete [] out; + return nullptr; + } + inflateEnd(&strm); + delete [] in; + return out; + } } // namespace Zip diff --git a/src/fs/zip.h b/src/fs/zip.h index 145be5c88..a54b08129 100644 --- a/src/fs/zip.h +++ b/src/fs/zip.h @@ -26,12 +26,18 @@ #include #include +struct VirtFile; struct ZipLocalHeader; namespace Zip { bool readArchiveInfo(const std::string &restrict archiveName, std::vector &restrict headers); + std::string getZlibError(const int err); + void reportZlibError(const std::string &text, + const int err); + uint8_t *readCompressedFile(const ZipLocalHeader *restrict const header); + uint8_t *readFile(const ZipLocalHeader *restrict const header); } // namespace Zip #endif // UTILS_ZIP_H diff --git a/src/fs/zip_unittest.cc b/src/fs/zip_unittest.cc index 3f0c49cdf..9661b7d19 100644 --- a/src/fs/zip_unittest.cc +++ b/src/fs/zip_unittest.cc @@ -40,7 +40,7 @@ TEST_CASE("Zip readArchiveInfo") if (Files::existsLocal(name) == false) prefix = "../"; - SECTION("test 1") + SECTION("test.zip") { name = prefix + "data/test/test.zip"; @@ -56,7 +56,7 @@ TEST_CASE("Zip readArchiveInfo") REQUIRE(headers[1]->uncompressSize == 1959); } - SECTION("test 2") + SECTION("test2.zip") { name = prefix + "data/test/test2.zip"; @@ -118,7 +118,7 @@ TEST_CASE("Zip readArchiveInfo") REQUIRE(headers[10]->uncompressSize == 306); } - SECTION("test 3") + SECTION("test3.zip") { name = prefix + "data/test/test3.zip"; @@ -134,7 +134,7 @@ TEST_CASE("Zip readArchiveInfo") REQUIRE(headers[1]->uncompressSize == 306); } - SECTION("test 4") + SECTION("test4.zip") { name = prefix + "data/test/test4.zip"; @@ -145,3 +145,112 @@ TEST_CASE("Zip readArchiveInfo") delete_all(headers); delete2(logger); } + +TEST_CASE("Zip readCompressedFile") +{ + logger = new Logger(); + std::string name("data/test/test.zip"); + std::string prefix; + std::vector headers; + if (Files::existsLocal(name) == false) + prefix = "../"; + + SECTION("empty") + { + REQUIRE_THROWS(Zip::readCompressedFile(nullptr)); + } + + SECTION("test2.zip") + { + name = prefix + "data/test/test2.zip"; + + REQUIRE(Zip::readArchiveInfo(name, headers)); + REQUIRE(headers.size() == 11); + // test.txt + uint8_t *const buf = Zip::readCompressedFile(headers[0]); + REQUIRE(buf != nullptr); + delete [] buf; + } + + delete_all(headers); + delete2(logger); +} + +TEST_CASE("Zip readFile") +{ + logger = new Logger(); + std::string name("data/test/test.zip"); + std::string prefix; + std::vector headers; + if (Files::existsLocal(name) == false) + prefix = "../"; + + SECTION("empty") + { + REQUIRE_THROWS(Zip::readFile(nullptr)); + } + + SECTION("test.zip") + { + name = prefix + "data/test/test.zip"; + + REQUIRE(Zip::readArchiveInfo(name, headers)); + REQUIRE(headers.size() == 2); + for (int f = 0; f < 2; f ++) + { + logger->log("test header: %s, %u, %u", + headers[f]->fileName.c_str(), + headers[f]->compressSize, + headers[f]->uncompressSize); + uint8_t *const buf = Zip::readFile(headers[f]); + REQUIRE(buf != nullptr); + delete [] buf; + } + } + + SECTION("test2.zip") + { + name = prefix + "data/test/test2.zip"; + + REQUIRE(Zip::readArchiveInfo(name, headers)); + REQUIRE(headers.size() == 11); + // test.txt + uint8_t *buf = Zip::readFile(headers[0]); + REQUIRE(buf != nullptr); + const std::string str = std::string(reinterpret_cast(buf), + headers[0]->uncompressSize); + REQUIRE(str == "test line 1\ntest line 2"); + delete [] buf; + for (int f = 0; f < 11; f ++) + { + logger->log("test header: %s, %u, %u", + headers[f]->fileName.c_str(), + headers[f]->compressSize, + headers[f]->uncompressSize); + buf = Zip::readFile(headers[f]); + REQUIRE(buf != nullptr); + delete [] buf; + } + } + + SECTION("test3.zip") + { + name = prefix + "data/test/test3.zip"; + + REQUIRE(Zip::readArchiveInfo(name, headers)); + REQUIRE(headers.size() == 2); + for (int f = 0; f < 2; f ++) + { + logger->log("test header: %s, %u, %u", + headers[f]->fileName.c_str(), + headers[f]->compressSize, + headers[f]->uncompressSize); + uint8_t *const buf = Zip::readFile(headers[f]); + REQUIRE(buf != nullptr); + delete [] buf; + } + } + + delete_all(headers); + delete2(logger); +} diff --git a/src/fs/ziplocalheader.h b/src/fs/ziplocalheader.h index 4a71d74c0..8905d4c48 100644 --- a/src/fs/ziplocalheader.h +++ b/src/fs/ziplocalheader.h @@ -32,7 +32,8 @@ struct ZipLocalHeader final fileName(), dataOffset(0U), compressSize(0U), - uncompressSize(0U) + uncompressSize(0U), + compressed(false) { } @@ -43,6 +44,7 @@ struct ZipLocalHeader final uint32_t dataOffset; uint32_t compressSize; uint32_t uncompressSize; + bool compressed; }; #endif // UTILS_ZIPLOCALHEADER_H -- cgit v1.2.3-60-g2f50