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