blob: 54157add554a31e51dd9d2cb39a60d30a6a9f816 [file] [log] [blame]
/*
* Copyright (C) 2004-2021 Savoir-faire Linux Inc.
*
* Author: Alexandre Lision <alexandre.lision@savoirfairelinux.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "archiver.h"
#include "client/ring_signal.h"
#include "account_const.h"
#include "configurationmanager_interface.h"
#include "manager.h"
#include "fileutils.h"
#include "logger.h"
#include <opendht/crypto.h>
#include <json/json.h>
#include <zlib.h>
#ifdef ENABLE_PLUGIN
extern "C" {
#if defined(__APPLE__)
#include <mz.h>
#include <mz_strm.h>
#include <mz_strm_os.h>
#include <mz_zip.h>
#include <mz_zip_rw.h>
#else
#include <archive.h>
#include <archive_entry.h>
#endif
}
#endif
#include <sys/stat.h>
#include <fstream>
using namespace std::literals;
namespace jami {
namespace archiver {
std::map<std::string, std::string>
jsonValueToAccount(Json::Value& value, const std::string& accountId)
{
auto idPath_ = fileutils::get_data_dir() + DIR_SEPARATOR_STR + accountId;
fileutils::check_dir(idPath_.c_str(), 0700);
auto detailsMap = DRing::getAccountTemplate(
value[DRing::Account::ConfProperties::TYPE].asString());
for (Json::ValueIterator itr = value.begin(); itr != value.end(); itr++) {
if (itr->asString().empty())
continue;
if (itr.key().asString().compare(DRing::Account::ConfProperties::TLS::CA_LIST_FILE) == 0) {
std::string fileContent(itr->asString());
fileutils::saveFile(idPath_ + DIR_SEPARATOR_STR "ca.key",
{fileContent.begin(), fileContent.end()},
0600);
} else if (itr.key().asString().compare(
DRing::Account::ConfProperties::TLS::PRIVATE_KEY_FILE)
== 0) {
std::string fileContent(itr->asString());
fileutils::saveFile(idPath_ + DIR_SEPARATOR_STR "dht.key",
{fileContent.begin(), fileContent.end()},
0600);
} else if (itr.key().asString().compare(
DRing::Account::ConfProperties::TLS::CERTIFICATE_FILE)
== 0) {
std::string fileContent(itr->asString());
fileutils::saveFile(idPath_ + DIR_SEPARATOR_STR "dht.crt",
{fileContent.begin(), fileContent.end()},
0600);
} else
detailsMap[itr.key().asString()] = itr->asString();
}
return detailsMap;
}
Json::Value
accountToJsonValue(const std::map<std::string, std::string>& details)
{
Json::Value root;
for (const auto& i : details) {
if (i.first == DRing::Account::ConfProperties::Ringtone::PATH) {
// Ringtone path is not exportable
} else if (i.first == DRing::Account::ConfProperties::TLS::CA_LIST_FILE
|| i.first == DRing::Account::ConfProperties::TLS::CERTIFICATE_FILE
|| i.first == DRing::Account::ConfProperties::TLS::PRIVATE_KEY_FILE) {
// replace paths by the files content
std::ifstream ifs = fileutils::ifstream(i.second);
std::string fileContent((std::istreambuf_iterator<char>(ifs)),
std::istreambuf_iterator<char>());
root[i.first] = fileContent;
} else
root[i.first] = i.second;
}
return root;
}
std::vector<uint8_t>
compress(const std::string& str)
{
auto destSize = compressBound(str.size());
std::vector<uint8_t> outbuffer(destSize);
int ret = ::compress(reinterpret_cast<Bytef*>(outbuffer.data()),
&destSize,
(Bytef*) str.data(),
str.size());
outbuffer.resize(destSize);
if (ret != Z_OK) {
std::ostringstream oss;
oss << "Exception during zlib compression: (" << ret << ") ";
throw std::runtime_error(oss.str());
}
return outbuffer;
}
void
compressGzip(const std::string& str, const std::string& path)
{
auto fi = openGzip(path, "wb");
gzwrite(fi, str.data(), str.size());
gzclose(fi);
}
void
compressGzip(const std::vector<uint8_t>& dat, const std::string& path)
{
auto fi = openGzip(path, "wb");
gzwrite(fi, dat.data(), dat.size());
gzclose(fi);
}
std::vector<uint8_t>
decompressGzip(const std::string& path)
{
std::vector<uint8_t> out;
auto fi = openGzip(path, "rb");
gzrewind(fi);
while (not gzeof(fi)) {
std::array<uint8_t, 32768> outbuffer;
int len = gzread(fi, outbuffer.data(), outbuffer.size());
if (len == -1) {
gzclose(fi);
throw std::runtime_error("Exception during gzip decompression");
}
out.insert(out.end(), outbuffer.begin(), outbuffer.begin() + len);
}
gzclose(fi);
return out;
}
std::vector<uint8_t>
decompress(const std::vector<uint8_t>& str)
{
z_stream zs; // z_stream is zlib's control structure
memset(&zs, 0, sizeof(zs));
if (inflateInit2(&zs, 32+MAX_WBITS) != Z_OK)
throw std::runtime_error("inflateInit failed while decompressing.");
zs.next_in = (Bytef*) str.data();
zs.avail_in = str.size();
int ret;
std::vector<uint8_t> out;
// get the decompressed bytes blockwise using repeated calls to inflate
do {
std::array<uint8_t, 32768> outbuffer;
zs.next_out = reinterpret_cast<Bytef*>(outbuffer.data());
zs.avail_out = outbuffer.size();
ret = inflate(&zs, 0);
if (ret == Z_DATA_ERROR || ret == Z_MEM_ERROR)
break;
if (out.size() < zs.total_out) {
// append the block to the output string
out.insert(out.end(), outbuffer.begin(), outbuffer.begin() + zs.total_out - out.size());
}
} while (ret == Z_OK);
inflateEnd(&zs);
// an error occurred that was not EOF
if (ret != Z_STREAM_END) {
std::ostringstream oss;
oss << "Exception during zlib decompression: (" << ret << ") " << zs.msg;
throw(std::runtime_error(oss.str()));
}
return out;
}
gzFile
openGzip(const std::string& path, const char* mode)
{
#ifdef _WIN32
return gzopen_w(jami::to_wstring(path).c_str(), mode);
#else
return gzopen(path.c_str(), mode);
#endif
}
#ifdef ENABLE_PLUGIN
#if !defined(__APPLE__)
// LIBARCHIVE DEFINITIONS
//==========================
using ArchivePtr = std::unique_ptr<archive, void (*)(archive*)>;
using ArchiveEntryPtr = std::unique_ptr<archive_entry, void (*)(archive_entry*)>;
struct DataBlock
{
const void* buff;
size_t size;
int64_t offset;
};
long
readDataBlock(const ArchivePtr& a, DataBlock& b)
{
return archive_read_data_block(a.get(), &b.buff, &b.size, &b.offset);
}
long
writeDataBlock(const ArchivePtr& a, DataBlock& b)
{
return archive_write_data_block(a.get(), b.buff, b.size, b.offset);
}
ArchivePtr
createArchiveReader()
{
ArchivePtr archivePtr {archive_read_new(), [](archive* a) {
archive_read_close(a);
archive_read_free(a);
}};
return archivePtr;
}
static ArchivePtr
createArchiveDiskWriter()
{
return {archive_write_disk_new(), [](archive* a) {
archive_write_close(a);
archive_write_free(a);
}};
}
//==========================
#endif
#endif
void
uncompressArchive(const std::string& archivePath, const std::string& dir, const FileMatchPair& f)
{
#ifdef ENABLE_PLUGIN
#if defined(__APPLE__)
void* zip_handle = NULL;
mz_zip_file* info = NULL;
fileutils::check_dir(dir.c_str());
mz_zip_create(&zip_handle);
auto status = mz_zip_reader_open_file(zip_handle, archivePath.c_str());
status |= mz_zip_reader_goto_first_entry(zip_handle);
while (status == MZ_OK) {
status |= mz_zip_reader_entry_get_info(zip_handle, &info);
if (status != MZ_OK) {
fileutils::removeAll(dir, true);
break;
}
std::string_view filename(info->filename, (size_t) info->filename_size);
const auto& fileMatchPair = f(filename);
if (fileMatchPair.first) {
auto filePath = dir + DIR_SEPARATOR_STR + fileMatchPair.second;
std::string directory = filePath.substr(0, filePath.find_last_of(DIR_SEPARATOR_CH));
fileutils::check_dir(directory.c_str());
mz_zip_reader_entry_open(zip_handle);
void* buffStream = NULL;
buffStream = mz_stream_os_create(&buffStream);
if (mz_stream_os_open(buffStream,
filePath.c_str(),
MZ_OPEN_MODE_WRITE | MZ_OPEN_MODE_CREATE)
== MZ_OK) {
int chunkSize = 8192;
std::vector<uint8_t> fileContent;
fileContent.resize(chunkSize);
while (auto ret = mz_zip_reader_entry_read(zip_handle,
(void*) fileContent.data(),
chunkSize)) {
ret = mz_stream_os_write(buffStream, (void*) fileContent.data(), ret);
if (ret < 0) {
fileutils::removeAll(dir, true);
status = 1;
}
}
mz_stream_os_close(buffStream);
mz_stream_os_delete(&buffStream);
} else {
fileutils::removeAll(dir, true);
status = 1;
}
mz_zip_reader_entry_close(zip_handle);
}
status |= mz_zip_reader_goto_next_entry(zip_handle);
}
mz_zip_reader_close(zip_handle);
mz_zip_delete(&zip_handle);
#else
int r;
ArchivePtr archiveReader = createArchiveReader();
ArchivePtr archiveDiskWriter = createArchiveDiskWriter();
struct archive_entry* entry;
int flags = ARCHIVE_EXTRACT_TIME | ARCHIVE_EXTRACT_NO_HFS_COMPRESSION;
// Set reader formats(archive) and filters(compression)
archive_read_support_filter_all(archiveReader.get());
archive_read_support_format_all(archiveReader.get());
// Set written files flags and standard lookup(uid/gid)
archive_write_disk_set_options(archiveDiskWriter.get(), flags);
archive_write_disk_set_standard_lookup(archiveDiskWriter.get());
// Try to read the archive
if ((r = archive_read_open_filename(archiveReader.get(), archivePath.c_str(), 10240))) {
throw std::runtime_error("Open Archive: " + archivePath + "\t"
+ archive_error_string(archiveReader.get()));
}
while (true) {
// Read headers until End of File
r = archive_read_next_header(archiveReader.get(), &entry);
if (r == ARCHIVE_EOF) {
break;
}
if (r != ARCHIVE_OK && r != ARCHIVE_WARN) {
throw std::runtime_error("Error reading archive: "s
+ archive_error_string(archiveReader.get()));
}
std::string_view fileEntry(archive_entry_pathname(entry));
// File is ok, copy its header to the ext writer
const auto& fileMatchPair = f(fileEntry);
if (fileMatchPair.first) {
std::string entryDestinationPath = dir + DIR_SEPARATOR_CH + fileMatchPair.second;
archive_entry_set_pathname(entry, entryDestinationPath.c_str());
r = archive_write_header(archiveDiskWriter.get(), entry);
if (r != ARCHIVE_OK) {
// Rollback if failed at a write operation
fileutils::removeAll(dir);
throw std::runtime_error("Write file header: " + fileEntry + "\t"
+ archive_error_string(archiveDiskWriter.get()));
} else {
// Here both the reader and the writer have moved past the headers
// Copying the data content
DataBlock db;
while (true) {
r = readDataBlock(archiveReader, db);
if (r == ARCHIVE_EOF) {
break;
}
if (r != ARCHIVE_OK) {
throw std::runtime_error("Read file data: " + fileEntry + "\t"
+ archive_error_string(archiveReader.get()));
}
r = writeDataBlock(archiveDiskWriter, db);
if (r != ARCHIVE_OK) {
// Rollback if failed at a write operation
fileutils::removeAll(dir);
throw std::runtime_error("Write file data: " + fileEntry + "\t"
+ archive_error_string(archiveDiskWriter.get()));
}
}
}
}
}
#endif
#endif
}
std::vector<uint8_t>
readFileFromArchive(const std::string& archivePath, const std::string& fileRelativePathName)
{
std::vector<uint8_t> fileContent;
#ifdef ENABLE_PLUGIN
#if defined(__APPLE__)
void* zip_handle = NULL;
mz_zip_file* info;
mz_zip_create(&zip_handle);
auto status = mz_zip_reader_open_file(zip_handle, archivePath.c_str());
status |= mz_zip_reader_goto_first_entry(zip_handle);
while (status == MZ_OK) {
status = mz_zip_reader_entry_get_info(zip_handle, &info);
if (status != MZ_OK)
break;
std::string_view filename(info->filename, (size_t) info->filename_size);
if (filename == fileRelativePathName) {
mz_zip_reader_entry_open(zip_handle);
fileContent.resize(info->uncompressed_size);
mz_zip_reader_entry_read(zip_handle,
(void*) fileContent.data(),
info->uncompressed_size);
mz_zip_reader_entry_close(zip_handle);
status = -1;
} else {
status = mz_zip_reader_goto_next_entry(zip_handle);
}
}
mz_zip_reader_close(zip_handle);
mz_zip_delete(&zip_handle);
#else
long r;
ArchivePtr archiveReader = createArchiveReader();
struct archive_entry* entry;
// Set reader formats(archive) and filters(compression)
archive_read_support_filter_all(archiveReader.get());
archive_read_support_format_all(archiveReader.get());
// Try to read the archive
if ((r = archive_read_open_filename(archiveReader.get(), archivePath.c_str(), 10240))) {
throw std::runtime_error("Open Archive: " + archivePath + "\t"
+ archive_error_string(archiveReader.get()));
}
while (true) {
// Read headers until End of File
r = archive_read_next_header(archiveReader.get(), &entry);
if (r == ARCHIVE_EOF) {
break;
}
std::string fileEntry = archive_entry_pathname(entry) ? archive_entry_pathname(entry) : "";
if (r != ARCHIVE_OK) {
throw std::runtime_error("Read file pathname: " + fileEntry + "\t"
+ archive_error_string(archiveReader.get()));
}
// File is ok and the reader has moved past the header
if (fileEntry == fileRelativePathName) {
// Copying the data content
DataBlock db;
while (true) {
r = readDataBlock(archiveReader, db);
if (r == ARCHIVE_EOF) {
return fileContent;
}
if (r != ARCHIVE_OK) {
throw std::runtime_error("Read file data: " + fileEntry + "\t"
+ archive_error_string(archiveReader.get()));
}
if (fileContent.size() < static_cast<size_t>(db.offset)) {
fileContent.resize(db.offset);
}
auto dat = static_cast<const uint8_t*>(db.buff);
// push the buffer data in the string stream
fileContent.insert(fileContent.end(), dat, dat + db.size);
}
}
}
throw std::runtime_error("File " + fileRelativePathName + " not found in the archive");
#endif
#endif
return fileContent;
}
} // namespace archiver
} // namespace jami