LCOV - code coverage report
Current view: top level - foo/src - archiver.cpp (source / functions) Hit Total Coverage
Test: jami-coverage-filtered.info Lines: 35 147 23.8 %
Date: 2026-04-01 09:29:43 Functions: 5 15 33.3 %

          Line data    Source code
       1             : /*
       2             :  *  Copyright (C) 2004-2026 Savoir-faire Linux Inc.
       3             :  *
       4             :  *  This program is free software: you can redistribute it and/or modify
       5             :  *  it under the terms of the GNU General Public License as published by
       6             :  *  the Free Software Foundation, either version 3 of the License, or
       7             :  *  (at your option) any later version.
       8             :  *
       9             :  *  This program is distributed in the hope that it will be useful,
      10             :  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
      11             :  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
      12             :  *  GNU General Public License for more details.
      13             :  *
      14             :  *  You should have received a copy of the GNU General Public License
      15             :  *  along with this program. If not, see <https://www.gnu.org/licenses/>.
      16             :  */
      17             : 
      18             : #include "archiver.h"
      19             : 
      20             : #include "fileutils.h"
      21             : 
      22             : #include <opendht/crypto.h>
      23             : #include <json/json.h>
      24             : #include <zlib.h>
      25             : 
      26             : #ifdef ENABLE_PLUGIN
      27             : extern "C" {
      28             : #if defined(__APPLE__)
      29             : #include <minizip/mz.h>
      30             : #include <minizip/mz_strm.h>
      31             : #include <minizip/mz_strm_os.h>
      32             : #include <minizip/mz_zip.h>
      33             : #include <minizip/mz_zip_rw.h>
      34             : #else
      35             : #include <archive.h>
      36             : #include <archive_entry.h>
      37             : #endif
      38             : }
      39             : #endif
      40             : 
      41             : #include <sys/stat.h>
      42             : 
      43             : #include "string_utils.h" // to_string
      44             : 
      45             : using namespace std::literals;
      46             : 
      47             : namespace jami {
      48             : namespace archiver {
      49             : 
      50             : std::vector<uint8_t>
      51          18 : compress(const std::string& str)
      52             : {
      53          18 :     auto destSize = compressBound(str.size());
      54          18 :     std::vector<uint8_t> outbuffer(destSize);
      55          18 :     int ret = ::compress(reinterpret_cast<Bytef*>(outbuffer.data()), &destSize, (Bytef*) str.data(), str.size());
      56          18 :     outbuffer.resize(destSize);
      57             : 
      58          18 :     if (ret != Z_OK) {
      59           0 :         throw std::runtime_error(fmt::format("Exception during zlib compression: ({})", ret));
      60             :     }
      61             : 
      62          36 :     return outbuffer;
      63           0 : }
      64             : 
      65             : void
      66         802 : compressGzip(const std::string& str, const std::string& path)
      67             : {
      68         802 :     auto* fi = openGzip(path, "wb");
      69         802 :     gzwrite(fi, str.data(), str.size());
      70         802 :     gzclose(fi);
      71         802 : }
      72             : 
      73             : void
      74           3 : compressGzip(const std::vector<uint8_t>& dat, const std::string& path)
      75             : {
      76           3 :     auto* fi = openGzip(path, "wb");
      77           3 :     gzwrite(fi, dat.data(), dat.size());
      78           3 :     gzclose(fi);
      79           3 : }
      80             : 
      81             : std::vector<uint8_t>
      82           0 : decompressGzip(const std::string& path)
      83             : {
      84           0 :     std::vector<uint8_t> out;
      85           0 :     auto* fi = openGzip(path, "rb");
      86           0 :     gzrewind(fi);
      87           0 :     while (not gzeof(fi)) {
      88             :         std::array<uint8_t, 32768> outbuffer;
      89           0 :         int len = gzread(fi, outbuffer.data(), outbuffer.size());
      90           0 :         if (len == -1) {
      91           0 :             gzclose(fi);
      92           0 :             throw std::runtime_error("Exception during gzip decompression");
      93             :         }
      94           0 :         out.insert(out.end(), outbuffer.begin(), outbuffer.begin() + len);
      95             :     }
      96           0 :     gzclose(fi);
      97           0 :     return out;
      98           0 : }
      99             : 
     100             : std::vector<uint8_t>
     101         104 : decompress(const std::vector<uint8_t>& str)
     102             : {
     103             :     z_stream zs; // z_stream is zlib's control structure
     104         104 :     memset(&zs, 0, sizeof(zs));
     105             : 
     106         104 :     if (inflateInit2(&zs, 32 + MAX_WBITS) != Z_OK)
     107           0 :         throw std::runtime_error("inflateInit failed while decompressing.");
     108             : 
     109         104 :     zs.next_in = (Bytef*) str.data();
     110         104 :     zs.avail_in = str.size();
     111             : 
     112             :     int ret;
     113         104 :     std::vector<uint8_t> out;
     114             : 
     115             :     // get the decompressed bytes blockwise using repeated calls to inflate
     116             :     do {
     117             :         std::array<uint8_t, 32768> outbuffer;
     118         104 :         zs.next_out = reinterpret_cast<Bytef*>(outbuffer.data());
     119         104 :         zs.avail_out = outbuffer.size();
     120             : 
     121         104 :         ret = inflate(&zs, 0);
     122         104 :         if (ret == Z_DATA_ERROR || ret == Z_MEM_ERROR)
     123             :             break;
     124             : 
     125         104 :         if (out.size() < zs.total_out) {
     126             :             // append the block to the output string
     127         104 :             out.insert(out.end(), outbuffer.begin(), outbuffer.begin() + zs.total_out - out.size());
     128             :         }
     129         104 :     } while (ret == Z_OK);
     130             : 
     131         104 :     inflateEnd(&zs);
     132             : 
     133             :     // an error occurred that was not EOF
     134         104 :     if (ret != Z_STREAM_END) {
     135           0 :         throw(std::runtime_error(fmt::format("Exception during zlib decompression: ({})", ret)));
     136             :     }
     137             : 
     138         208 :     return out;
     139           0 : }
     140             : 
     141             : gzFile
     142         805 : openGzip(const std::string& path, const char* mode)
     143             : {
     144             : #ifdef _WIN32
     145             :     return gzopen_w(jami::to_wstring(path).c_str(), mode);
     146             : #else
     147         805 :     return gzopen(path.c_str(), mode);
     148             : #endif
     149             : }
     150             : 
     151             : #ifdef ENABLE_PLUGIN
     152             : #if !defined(__APPLE__)
     153             : // LIBARCHIVE DEFINITIONS
     154             : //==========================
     155             : using ArchivePtr = std::unique_ptr<archive, void (*)(archive*)>;
     156             : using ArchiveEntryPtr = std::unique_ptr<archive_entry, void (*)(archive_entry*)>;
     157             : 
     158             : struct DataBlock
     159             : {
     160             :     const void* buff;
     161             :     size_t size;
     162             :     int64_t offset;
     163             : };
     164             : 
     165             : static long
     166           0 : readDataBlock(const ArchivePtr& a, DataBlock& b)
     167             : {
     168           0 :     return archive_read_data_block(a.get(), &b.buff, &b.size, &b.offset);
     169             : }
     170             : 
     171             : static long
     172           0 : writeDataBlock(const ArchivePtr& a, DataBlock& b)
     173             : {
     174           0 :     return archive_write_data_block(a.get(), b.buff, b.size, b.offset);
     175             : }
     176             : 
     177             : static ArchivePtr
     178           0 : createArchiveReader()
     179             : {
     180           0 :     ArchivePtr archivePtr {archive_read_new(), [](archive* a) {
     181           0 :                                archive_read_close(a);
     182           0 :                                archive_read_free(a);
     183           0 :                            }};
     184           0 :     return archivePtr;
     185             : }
     186             : 
     187             : static ArchivePtr
     188           0 : createArchiveDiskWriter()
     189             : {
     190           0 :     return {archive_write_disk_new(), [](archive* a) {
     191           0 :                 archive_write_close(a);
     192           0 :                 archive_write_free(a);
     193           0 :             }};
     194             : }
     195             : //==========================
     196             : #endif
     197             : #endif
     198             : 
     199             : void
     200           0 : uncompressArchive(const std::string& archivePath, const std::string& dir, const FileMatchPair& f)
     201             : {
     202             : #ifdef ENABLE_PLUGIN
     203             : #if defined(__APPLE__)
     204             :     mz_zip_file* info = NULL;
     205             : 
     206             :     dhtnet::fileutils::check_dir(dir.c_str());
     207             : 
     208             :     void* zip_handle = mz_zip_create();
     209             :     auto status = mz_zip_reader_open_file(zip_handle, archivePath.c_str());
     210             :     status |= mz_zip_reader_goto_first_entry(zip_handle);
     211             : 
     212             :     while (status == MZ_OK) {
     213             :         status |= mz_zip_reader_entry_get_info(zip_handle, &info);
     214             :         if (status != MZ_OK) {
     215             :             dhtnet::fileutils::removeAll(dir, true);
     216             :             break;
     217             :         }
     218             :         std::string_view filename(info->filename, (size_t) info->filename_size);
     219             :         const auto& fileMatchPair = f(filename);
     220             :         if (fileMatchPair.first) {
     221             :             auto filePath = dir + DIR_SEPARATOR_STR + fileMatchPair.second;
     222             :             std::string directory = filePath.substr(0, filePath.find_last_of(DIR_SEPARATOR_CH));
     223             :             dhtnet::fileutils::check_dir(directory.c_str());
     224             :             mz_zip_reader_entry_open(zip_handle);
     225             :             void* buffStream = mz_stream_os_create();
     226             :             if (mz_stream_os_open(buffStream, filePath.c_str(), MZ_OPEN_MODE_WRITE | MZ_OPEN_MODE_CREATE) == MZ_OK) {
     227             :                 int chunkSize = 8192;
     228             :                 std::vector<uint8_t> fileContent;
     229             :                 fileContent.resize(chunkSize);
     230             :                 while (auto ret = mz_zip_reader_entry_read(zip_handle, (void*) fileContent.data(), chunkSize)) {
     231             :                     ret = mz_stream_os_write(buffStream, (void*) fileContent.data(), ret);
     232             :                     if (ret < 0) {
     233             :                         dhtnet::fileutils::removeAll(dir, true);
     234             :                         status = 1;
     235             :                     }
     236             :                 }
     237             :                 mz_stream_os_close(buffStream);
     238             :                 mz_stream_os_delete(&buffStream);
     239             :             } else {
     240             :                 dhtnet::fileutils::removeAll(dir, true);
     241             :                 status = 1;
     242             :             }
     243             :             mz_zip_reader_entry_close(zip_handle);
     244             :         }
     245             :         status |= mz_zip_reader_goto_next_entry(zip_handle);
     246             :     }
     247             : 
     248             :     mz_zip_reader_close(zip_handle);
     249             :     mz_zip_delete(&zip_handle);
     250             : 
     251             : #else
     252             :     long r;
     253             : 
     254           0 :     ArchivePtr archiveReader = createArchiveReader();
     255           0 :     ArchivePtr archiveDiskWriter = createArchiveDiskWriter();
     256             :     struct archive_entry* entry;
     257             : 
     258           0 :     int flags = ARCHIVE_EXTRACT_TIME | ARCHIVE_EXTRACT_NO_HFS_COMPRESSION;
     259             : 
     260             :     // Set reader formats(archive) and filters(compression)
     261           0 :     archive_read_support_filter_all(archiveReader.get());
     262           0 :     archive_read_support_format_all(archiveReader.get());
     263             : 
     264             :     // Set written files flags and standard lookup(uid/gid)
     265           0 :     archive_write_disk_set_options(archiveDiskWriter.get(), flags);
     266           0 :     archive_write_disk_set_standard_lookup(archiveDiskWriter.get());
     267             : 
     268             :     // Try to read the archive
     269           0 :     if ((r = archive_read_open_filename(archiveReader.get(), archivePath.c_str(), 10240))) {
     270           0 :         throw std::runtime_error("Open Archive: " + archivePath + "\t" + archive_error_string(archiveReader.get()));
     271             :     }
     272             : 
     273             :     while (true) {
     274             :         // Read headers until End of File
     275           0 :         r = archive_read_next_header(archiveReader.get(), &entry);
     276           0 :         if (r == ARCHIVE_EOF) {
     277           0 :             break;
     278             :         }
     279           0 :         if (r != ARCHIVE_OK && r != ARCHIVE_WARN) {
     280           0 :             throw std::runtime_error("Error reading archive: "s + archive_error_string(archiveReader.get()));
     281             :         }
     282             : 
     283           0 :         std::string_view fileEntry(archive_entry_pathname(entry));
     284             : 
     285             :         // File is ok, copy its header to the ext writer
     286           0 :         const auto& fileMatchPair = f(fileEntry);
     287           0 :         if (fileMatchPair.first) {
     288           0 :             std::string entryDestinationPath = dir + DIR_SEPARATOR_CH + fileMatchPair.second;
     289           0 :             archive_entry_set_pathname(entry, entryDestinationPath.c_str());
     290           0 :             r = archive_write_header(archiveDiskWriter.get(), entry);
     291           0 :             if (r != ARCHIVE_OK) {
     292             :                 // Rollback if failed at a write operation
     293           0 :                 dhtnet::fileutils::removeAll(dir);
     294           0 :                 throw std::runtime_error("Write file header: " + fileEntry + "\t"
     295           0 :                                          + archive_error_string(archiveDiskWriter.get()));
     296             :             } else {
     297             :                 // Here both the reader and the writer have moved past the headers
     298             :                 // Copying the data content
     299             :                 DataBlock db;
     300             : 
     301             :                 while (true) {
     302           0 :                     r = readDataBlock(archiveReader, db);
     303           0 :                     if (r == ARCHIVE_EOF) {
     304           0 :                         break;
     305             :                     }
     306             : 
     307           0 :                     if (r != ARCHIVE_OK) {
     308           0 :                         throw std::runtime_error("Read file data: " + fileEntry + "\t"
     309           0 :                                                  + archive_error_string(archiveReader.get()));
     310             :                     }
     311             : 
     312           0 :                     r = writeDataBlock(archiveDiskWriter, db);
     313             : 
     314           0 :                     if (r != ARCHIVE_OK) {
     315             :                         // Rollback if failed at a write operation
     316           0 :                         dhtnet::fileutils::removeAll(dir);
     317           0 :                         throw std::runtime_error("Write file data: " + fileEntry + "\t"
     318           0 :                                                  + archive_error_string(archiveDiskWriter.get()));
     319             :                     }
     320             :                 }
     321             :             }
     322           0 :         }
     323           0 :     }
     324             : #endif
     325             : #endif
     326           0 : }
     327             : 
     328             : std::vector<uint8_t>
     329           0 : readFileFromArchive(const std::string& archivePath, const std::string& fileRelativePathName)
     330             : {
     331           0 :     std::vector<uint8_t> fileContent;
     332             : #ifdef ENABLE_PLUGIN
     333             : #if defined(__APPLE__)
     334             :     mz_zip_file* info;
     335             : 
     336             :     void* zip_handle = mz_zip_create();
     337             :     auto status = mz_zip_reader_open_file(zip_handle, archivePath.c_str());
     338             :     status |= mz_zip_reader_goto_first_entry(zip_handle);
     339             : 
     340             :     while (status == MZ_OK) {
     341             :         status = mz_zip_reader_entry_get_info(zip_handle, &info);
     342             :         if (status != MZ_OK)
     343             :             break;
     344             :         std::string_view filename(info->filename, (size_t) info->filename_size);
     345             :         if (filename == fileRelativePathName) {
     346             :             mz_zip_reader_entry_open(zip_handle);
     347             :             fileContent.resize(info->uncompressed_size);
     348             :             mz_zip_reader_entry_read(zip_handle, (void*) fileContent.data(), info->uncompressed_size);
     349             :             mz_zip_reader_entry_close(zip_handle);
     350             :             status = -1;
     351             :         } else {
     352             :             status = mz_zip_reader_goto_next_entry(zip_handle);
     353             :         }
     354             :     }
     355             : 
     356             :     mz_zip_reader_close(zip_handle);
     357             :     mz_zip_delete(&zip_handle);
     358             : #else
     359             :     long r;
     360           0 :     ArchivePtr archiveReader = createArchiveReader();
     361             :     struct archive_entry* entry;
     362             : 
     363             :     // Set reader formats(archive) and filters(compression)
     364           0 :     archive_read_support_filter_all(archiveReader.get());
     365           0 :     archive_read_support_format_all(archiveReader.get());
     366             : 
     367             :     // Try to read the archive
     368           0 :     if ((r = archive_read_open_filename(archiveReader.get(), archivePath.c_str(), 10240))) {
     369           0 :         throw std::runtime_error("Open Archive: " + archivePath + "\t" + archive_error_string(archiveReader.get()));
     370             :     }
     371             : 
     372             :     while (true) {
     373             :         // Read headers until End of File
     374           0 :         r = archive_read_next_header(archiveReader.get(), &entry);
     375           0 :         if (r == ARCHIVE_EOF) {
     376           0 :             break;
     377             :         }
     378             : 
     379           0 :         std::string fileEntry = archive_entry_pathname(entry) ? archive_entry_pathname(entry) : "";
     380           0 :         if (r != ARCHIVE_OK) {
     381           0 :             throw std::runtime_error(
     382           0 :                 fmt::format("Read file pathname: {}: {}", fileEntry, archive_error_string(archiveReader.get())));
     383             :         }
     384             : 
     385             :         // File is ok and the reader has moved past the header
     386           0 :         if (fileEntry == fileRelativePathName) {
     387             :             // Copying the data content
     388             :             DataBlock db;
     389             : 
     390             :             while (true) {
     391           0 :                 r = readDataBlock(archiveReader, db);
     392           0 :                 if (r == ARCHIVE_EOF) {
     393           0 :                     return fileContent;
     394             :                 }
     395             : 
     396           0 :                 if (r != ARCHIVE_OK) {
     397           0 :                     throw std::runtime_error("Read file data: " + fileEntry + "\t"
     398           0 :                                              + archive_error_string(archiveReader.get()));
     399             :                 }
     400             : 
     401           0 :                 if (fileContent.size() < static_cast<size_t>(db.offset)) {
     402           0 :                     fileContent.resize(db.offset);
     403             :                 }
     404             : 
     405           0 :                 const auto* dat = static_cast<const uint8_t*>(db.buff);
     406             :                 // push the buffer data in the string stream
     407           0 :                 fileContent.insert(fileContent.end(), dat, dat + db.size);
     408           0 :             }
     409             :         }
     410           0 :     }
     411           0 :     throw std::runtime_error("File " + fileRelativePathName + " not found in the archive");
     412             : #endif
     413             : #endif
     414             :     return fileContent;
     415           0 : }
     416             : std::vector<std::string>
     417           0 : listFilesFromArchive(const std::string& path)
     418             : {
     419           0 :     std::vector<std::string> filePaths;
     420             : #ifdef ENABLE_PLUGIN
     421             : #if defined(__APPLE__)
     422             :     mz_zip_file* info = NULL;
     423             : 
     424             :     void* zip_handle = mz_zip_create();
     425             :     auto status = mz_zip_reader_open_file(zip_handle, path.c_str());
     426             :     status |= mz_zip_reader_goto_first_entry(zip_handle);
     427             : 
     428             :     // read all the file path of the archive
     429             :     while (status == MZ_OK) {
     430             :         status = mz_zip_reader_entry_get_info(zip_handle, &info);
     431             :         if (status != MZ_OK)
     432             :             break;
     433             :         std::string filename(info->filename, (size_t) info->filename_size);
     434             :         filePaths.push_back(filename);
     435             :         status = mz_zip_reader_goto_next_entry(zip_handle);
     436             :     }
     437             :     mz_zip_reader_close(zip_handle);
     438             :     mz_zip_delete(&zip_handle);
     439             : #else
     440           0 :     ArchivePtr archiveReader = createArchiveReader();
     441             :     struct archive_entry* entry;
     442             : 
     443           0 :     archive_read_support_format_all(archiveReader.get());
     444           0 :     if (archive_read_open_filename(archiveReader.get(), path.c_str(), 10240)) {
     445           0 :         throw std::runtime_error("Open Archive: " + path + "\t" + archive_error_string(archiveReader.get()));
     446             :     }
     447             : 
     448           0 :     while (archive_read_next_header(archiveReader.get(), &entry) == ARCHIVE_OK) {
     449           0 :         const char* name = archive_entry_pathname(entry);
     450             : 
     451           0 :         filePaths.push_back(name);
     452             :     }
     453             : #endif
     454             : #endif
     455           0 :     return filePaths;
     456           0 : }
     457             : } // namespace archiver
     458             : } // namespace jami

Generated by: LCOV version 1.14