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