LCOV - code coverage report
Current view: top level - src - archiver.cpp (source / functions) Coverage Total Hit
Test: jami-coverage-filtered.info Lines: 24.0 % 146 35
Test Date: 2026-06-13 09:18:46 Functions: 33.3 % 15 5

            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          804 : compressGzip(const std::string& str, const std::string& path)
      67              : {
      68          804 :     auto* fi = openGzip(path, "wb");
      69          804 :     gzwrite(fi, str.data(), str.size());
      70          804 :     gzclose(fi);
      71          804 : }
      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          807 : 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          807 :     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              :             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 2.0-1