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