// license:BSD-3-Clause // copyright-holders:Aaron Giles, Vas Crabb /*************************************************************************** un7z.c Functions to manipulate data within 7z files. ***************************************************************************/ // this is based on unzip.c, with modifications needed to use the 7zip library #include "unzip.h" #include "corestr.h" #include "unicode.h" #include "lzma/C/7z.h" #include "lzma/C/7zCrc.h" #include "lzma/C/7zVersion.h" #include #include #include #include #include #include #include #include namespace util { namespace { /*************************************************************************** TYPE DEFINITIONS ***************************************************************************/ struct CSzFile { CSzFile() : currfpos(0), length(0), osdfile() {} long currfpos; std::uint64_t length; osd_file::ptr osdfile; WRes read(void *data, std::size_t &size) { if (!osdfile) { std::printf("un7z.c: called File_Read without file\n"); return 1; } if (!size) return 0; size_t originalSize = size; std::uint32_t read_length; //osd_file::error err = osdfile->read(data, currfpos, originalSize, read_length); size = read_length; currfpos += read_length; if (size == originalSize) return 0; return 0; } WRes seek(Int64 &pos, ESzSeek origin) { if (origin == 0) currfpos = pos; if (origin == 1) currfpos = currfpos + pos; if (origin == 2) currfpos = length -pos; pos = currfpos; return 0; } }; struct CFileInStream : public ISeekInStream, public CSzFile { CFileInStream(); }; class m7z_file_impl { public: typedef std::unique_ptr ptr; m7z_file_impl(const std::string &filename); ~m7z_file_impl() { if (m_out_buffer) IAlloc_Free(&m_alloc_imp, m_out_buffer); if (m_inited) SzArEx_Free(&m_db, &m_alloc_imp); } static ptr find_cached(const std::string &filename) { std::lock_guard guard(s_cache_mutex); for (std::size_t cachenum = 0; cachenum < s_cache.size(); cachenum++) { // if we have a valid entry and it matches our filename, use it and remove from the cache if (s_cache[cachenum] && (filename == s_cache[cachenum]->m_filename)) { ptr result; std::swap(s_cache[cachenum], result); return result; } } return ptr(); } static void close(ptr &&archive); static void cache_clear() { // clear call cache entries std::lock_guard guard(s_cache_mutex); for (std::size_t cachenum = 0; cachenum < s_cache.size(); s_cache[cachenum++].reset()) { } } archive_file::error initialize(); int first_file() { return search(0, 0, std::string(), false, false); } int next_file() { return (m_curr_file_idx < 0) ? -1 : search(m_curr_file_idx + 1, 0, std::string(), false, false); } int search(std::uint32_t crc) { return search(0, crc, std::string(), true, false); } int search(const std::string &filename) { return search(0, 0, filename, false, true); } int search(std::uint32_t crc, const std::string &filename) { return search(0, crc, filename, true, true); } bool current_is_directory() const { return m_curr_is_dir; } const std::string ¤t_name() const { return m_curr_name; } std::uint64_t current_uncompressed_length() const { return m_curr_length; } std::uint32_t current_crc() const { return m_curr_crc; } archive_file::error decompress(void *buffer, std::uint32_t length); private: m7z_file_impl(const m7z_file_impl &) = delete; m7z_file_impl(m7z_file_impl &&) = delete; m7z_file_impl &operator=(const m7z_file_impl &) = delete; m7z_file_impl &operator=(m7z_file_impl &&) = delete; int search(int i, std::uint32_t search_crc, const std::string &search_filename, bool matchcrc, bool matchname); void make_utf8_name(int index); static constexpr std::size_t CACHE_SIZE = 8; static std::array s_cache; static std::mutex s_cache_mutex; const std::string m_filename; // copy of _7Z filename (for caching) int m_curr_file_idx; // current file index bool m_curr_is_dir; // current file is directory std::string m_curr_name; // current file name std::uint64_t m_curr_length; // current file uncompressed length std::uint32_t m_curr_crc; // current file crc std::vector m_utf16_buf; std::vector m_uchar_buf; std::vector m_utf8_buf; CFileInStream m_archive_stream; CLookToRead m_look_stream; CSzArEx m_db; ISzAlloc m_alloc_imp; ISzAlloc m_alloc_temp_imp; bool m_inited; // cached stuff for solid blocks UInt32 m_block_index; Byte * m_out_buffer; std::size_t m_out_buffer_size; }; class m7z_file_wrapper : public archive_file { public: m7z_file_wrapper(m7z_file_impl::ptr &&impl) : m_impl(std::move(impl)) { assert(m_impl); } virtual ~m7z_file_wrapper() override { m7z_file_impl::close(std::move(m_impl)); } virtual int first_file() override { return m_impl->first_file(); } virtual int next_file() override { return m_impl->next_file(); } virtual int search(std::uint32_t crc) override { return m_impl->search(crc); } virtual int search(const std::string &filename) override { return m_impl->search(filename); } virtual int search(std::uint32_t crc, const std::string &filename) override { return m_impl->search(crc, filename); } virtual bool current_is_directory() const override { return m_impl->current_is_directory(); } virtual const std::string ¤t_name() const override { return m_impl->current_name(); } virtual std::uint64_t current_uncompressed_length() const override { return m_impl->current_uncompressed_length(); } virtual std::uint32_t current_crc() const override { return m_impl->current_crc(); } virtual error decompress(void *buffer, std::uint32_t length) override { return m_impl->decompress(buffer, length); } private: m7z_file_impl::ptr m_impl; }; /*************************************************************************** GLOBAL VARIABLES ***************************************************************************/ std::array m7z_file_impl::s_cache; std::mutex m7z_file_impl::s_cache_mutex; /*************************************************************************** 7Zip Memory / File handling (adapted from 7zfile.c/.h and 7zalloc.c/.h) ***************************************************************************/ /* ---------- FileInStream ---------- */ extern "C" { static void *SZipAlloc(void *p, std::size_t size) { return (size == 0) ? nullptr : std::malloc(size); } static void SZipFree(void *p, void *address) { std::free(address); } static SRes FileInStream_Read(void *pp, void *buf, size_t *size) { return (reinterpret_cast(pp)->read(buf, *size) == 0) ? SZ_OK : SZ_ERROR_READ; } static SRes FileInStream_Seek(void *pp, Int64 *pos, ESzSeek origin) { return reinterpret_cast(pp)->seek(*pos, origin); } } // extern "C" CFileInStream::CFileInStream() { Read = &FileInStream_Read; Seek = &FileInStream_Seek; } /*************************************************************************** CACHE MANAGEMENT ***************************************************************************/ m7z_file_impl::m7z_file_impl(const std::string &filename) : m_filename(filename) , m_curr_file_idx(-1) , m_curr_is_dir(false) , m_curr_name() , m_curr_length(0) , m_curr_crc(0) , m_utf16_buf(128) , m_uchar_buf(128) , m_utf8_buf(512) , m_inited(false) , m_block_index(0xffffffff) // it can have any value before first call (if outBuffer = 0) , m_out_buffer(nullptr) // it must be 0 before first call for each new archive , m_out_buffer_size(0) // it can have any value before first call (if outBuffer = 0) { m_alloc_imp.Alloc = SZipAlloc; m_alloc_imp.Free = SZipFree; m_alloc_temp_imp.Alloc = SZipAlloc; m_alloc_temp_imp.Free = SZipFree; } archive_file::error m7z_file_impl::initialize() { osd_file::error const err = osd_file::open(m_filename, OPEN_FLAG_READ, m_archive_stream.osdfile, m_archive_stream.length); if (err != osd_file::error::NONE) return archive_file::error::FILE_ERROR; LookToRead_CreateVTable(&m_look_stream, False); m_look_stream.realStream = &m_archive_stream; LookToRead_Init(&m_look_stream); CrcGenerateTable(); SzArEx_Init(&m_db); m_inited = true; SRes const res = SzArEx_Open(&m_db, &m_look_stream.s, &m_alloc_imp, &m_alloc_temp_imp); if (res != SZ_OK) return archive_file::error::FILE_ERROR; return archive_file::error::NONE; } /*------------------------------------------------- _7z_file_close - close a _7Z file and add it to the cache -------------------------------------------------*/ void m7z_file_impl::close(ptr &&archive) { if (!archive) return; // close the open files archive->m_archive_stream.osdfile.reset(); // find the first NULL entry in the cache std::lock_guard guard(s_cache_mutex); std::size_t cachenum; for (cachenum = 0; cachenum < s_cache.size(); cachenum++) if (!s_cache[cachenum]) break; // if no room left in the cache, free the bottommost entry if (cachenum == s_cache.size()) s_cache[--cachenum].reset(); // move everyone else down and place us at the top for ( ; cachenum > 0; cachenum--) s_cache[cachenum] = std::move(s_cache[cachenum - 1]); s_cache[0] = std::move(archive); } /*************************************************************************** 7Z FILE ACCESS ***************************************************************************/ /*------------------------------------------------- _7z_file_decompress - decompress a file from a _7Z into the target buffer -------------------------------------------------*/ archive_file::error m7z_file_impl::decompress(void *buffer, std::uint32_t length) { // make sure the file is open.. if (!m_archive_stream.osdfile) { m_archive_stream.currfpos = 0; osd_file::error const err = osd_file::open(m_filename, OPEN_FLAG_READ, m_archive_stream.osdfile, m_archive_stream.length); if (err != osd_file::error::NONE) return archive_file::error::FILE_ERROR; } size_t offset = 0; size_t out_size_processed = 0; SRes const res = SzArEx_Extract( &m_db, &m_look_stream.s, m_curr_file_idx, &m_block_index, &m_out_buffer, &m_out_buffer_size, &offset, &out_size_processed, &m_alloc_imp, &m_alloc_temp_imp); if (res != SZ_OK) return archive_file::error::FILE_ERROR; std::memcpy(buffer, m_out_buffer + offset, length); return archive_file::error::NONE; } int m7z_file_impl::search(int i, std::uint32_t search_crc, const std::string &search_filename, bool matchcrc, bool matchname) { for ( ; i < m_db.db.NumFiles; i++) { const CSzFileItem &f(m_db.db.Files[i]); make_utf8_name(i); const std::uint64_t size(f.Size); const std::uint32_t crc(f.Crc); const bool crcmatch(crc == search_crc); const bool namematch(!core_stricmp(search_filename.c_str(), &m_utf8_buf[0])); const bool found = ((!matchcrc && !matchname) || !f.IsDir) && (!matchcrc || crcmatch) && (!matchname || namematch); if (found) { m_curr_file_idx = i; m_curr_is_dir = bool(f.IsDir); m_curr_name = &m_utf8_buf[0]; m_curr_length = size; m_curr_crc = crc; return i; } } return -1; } void m7z_file_impl::make_utf8_name(int index) { std::size_t len, out_pos; len = SzArEx_GetFileNameUtf16(&m_db, index, nullptr); m_utf16_buf.resize((std::max)(m_utf16_buf.size(), len)); SzArEx_GetFileNameUtf16(&m_db, index, &m_utf16_buf[0]); m_uchar_buf.resize((std::max)(m_uchar_buf.size(), len)); out_pos = 0; for (std::size_t in_pos = 0; in_pos < (len - 1); ) { const int used = uchar_from_utf16(&m_uchar_buf[out_pos], &m_utf16_buf[in_pos], len - in_pos); if (used < 0) { in_pos++; m_uchar_buf[out_pos++] = 0x00fffd; // Unicode REPLACEMENT CHARACTER } else { assert(used > 0); in_pos += used; out_pos++; } } len = out_pos; m_utf8_buf.resize((std::max)(m_utf8_buf.size(), 4 * len + 1)); out_pos = 0; for (std::size_t in_pos = 0; in_pos < len; in_pos++) { int produced = utf8_from_uchar(&m_utf8_buf[out_pos], m_utf8_buf.size() - out_pos, m_uchar_buf[in_pos]); if (produced < 0) produced = utf8_from_uchar(&m_utf8_buf[out_pos], m_utf8_buf.size() - out_pos, 0x00fffd); if (produced >= 0) out_pos += produced; assert(out_pos < m_utf8_buf.size()); } m_utf8_buf[out_pos++] = '\0'; m_utf8_buf.resize(out_pos); } } // anonymous namespace archive_file::error archive_file::open_7z(const std::string &filename, ptr &result) { // ensure we start with a NULL result result.reset(); // see if we are in the cache, and reopen if so m7z_file_impl::ptr newimpl(m7z_file_impl::find_cached(filename)); if (!newimpl) { // allocate memory for the 7z file structure try { newimpl = std::make_unique(filename); } catch (...) { return error::OUT_OF_MEMORY; } error const err = newimpl->initialize(); if (err != error::NONE) return err; } try { result = std::make_unique(std::move(newimpl)); return error::NONE; } catch (...) { m7z_file_impl::close(std::move(newimpl)); return error::OUT_OF_MEMORY; } } /*------------------------------------------------- _7z_file_cache_clear - clear the _7Z file cache and free all memory -------------------------------------------------*/ void m7z_file_cache_clear() { // This is a trampoline called from unzip.cpp to avoid the need to have the zip and 7zip code in one file m7z_file_impl::cache_clear(); } } // namespace util