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
|