Select Git revision
routing_table.cpp
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
pluginsutils.cpp 12.26 KiB
/*
* Copyright (C) 2021-2023 Savoir-faire Linux Inc.
*
* Author: Aline Gondim Santos <aline.gondimsantos@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 "pluginsutils.h"
#include "logger.h"
#include "fileutils.h"
#include "archiver.h"
#include <msgpack.hpp>
#include <fstream>
#include <regex>
#if defined(__APPLE__)
#if (defined(TARGET_OS_IOS) && TARGET_OS_IOS)
#define ABI "iphone"
#else
#define ABI "x86_64-apple-Darwin"
#endif
#elif defined(__arm__)
#if defined(__ARM_ARCH_7A__)
#define ABI "armeabi-v7a"
#else
#define ABI "armeabi"
#endif
#elif defined(__i386__)
#if __ANDROID__
#define ABI "x86"
#else
#define ABI "x86-linux-gnu"
#endif
#elif defined(__x86_64__)
#if __ANDROID__
#define ABI "x86_64"
#else
#define ABI "x86_64-linux-gnu"
#endif
#elif defined(__aarch64__)
#define ABI "arm64-v8a"
#elif defined(WIN32)
#define ABI "x64-windows"
#else
#define ABI "unknown"
#endif
namespace jami {
namespace PluginUtils {
// DATA_REGEX is used to during the plugin jpl uncompressing
const std::regex DATA_REGEX("^data" DIR_SEPARATOR_STR_ESC ".+");
// SO_REGEX is used to find libraries during the plugin jpl uncompressing
const std::regex SO_REGEX("([a-zA-Z0-9]+(?:[_-]?[a-zA-Z0-9]+)*)" DIR_SEPARATOR_STR_ESC
"([a-zA-Z0-9_-]+\\.(dylib|so|dll|lib).*)");
std::string
manifestPath(const std::string& rootPath)
{
return rootPath + DIR_SEPARATOR_CH + "manifest.json";
}
std::map<std::string, std::string>
getPlatformInfo()
{
std::map<std::string, std::string> platformInfo = {};
platformInfo["os"] = ABI;
return platformInfo;
}
std::string
getRootPathFromSoPath(const std::string& soPath)
{
return soPath.substr(0, soPath.find_last_of(DIR_SEPARATOR_CH));
}
std::string
dataPath(const std::string& pluginSoPath)
{
return getRootPathFromSoPath(pluginSoPath) + DIR_SEPARATOR_CH + "data";
}
std::map<std::string, std::string>
checkManifestJsonContentValidity(const Json::Value& root)
{
std::string name = root.get("name", "").asString();
std::string id = root.get("id", name).asString();
std::string description = root.get("description", "").asString();
std::string version = root.get("version", "").asString();
std::string iconPath = root.get("iconPath", "icon.png").asString();
std::string background = root.get("backgroundPath", "background.jpg").asString();
if (!name.empty() || !version.empty()) {
return {
{"id", id},
{"name", name},
{"description", description},
{"version", version},
{"iconPath", iconPath},
{"backgroundPath", background},
};
} else {
throw std::runtime_error("plugin manifest file: bad format");
}
}
std::map<std::string, std::string>
checkManifestValidity(std::istream& stream)
{
Json::Value root;
Json::CharReaderBuilder rbuilder;
rbuilder["collectComments"] = false;
std::string errs;
if (Json::parseFromStream(rbuilder, stream, &root, &errs)) {
return checkManifestJsonContentValidity(root);
} else {
throw std::runtime_error("failed to parse the plugin manifest file");
}
}
std::map<std::string, std::string>
checkManifestValidity(const std::vector<uint8_t>& vec)
{
Json::Value root;
std::unique_ptr<Json::CharReader> json_Reader(Json::CharReaderBuilder {}.newCharReader());
std::string errs;
bool ok = json_Reader->parse(reinterpret_cast<const char*>(vec.data()),
reinterpret_cast<const char*>(vec.data() + vec.size()),
&root,
&errs);
if (ok) {
return checkManifestJsonContentValidity(root);
} else {
throw std::runtime_error("failed to parse the plugin manifest file");
}
}
std::map<std::string, std::string>
parseManifestFile(const std::string& manifestFilePath, const std::string& rootPath)
{
std::lock_guard<std::mutex> guard(fileutils::getFileLock(manifestFilePath));
std::ifstream file(manifestFilePath);
if (file) {
try {
const auto& traduction = parseManifestTranslation(rootPath, file);
return checkManifestValidity(std::vector<uint8_t>(traduction.begin(), traduction.end()));
} catch (const std::exception& e) {
JAMI_ERR() << e.what();
}
}
return {};
}
std::string
parseManifestTranslation(const std::string& rootPath, std::ifstream& manifestFile)
{
if (manifestFile) {
std::stringstream buffer;
buffer << manifestFile.rdbuf();
std::string manifest = buffer.str();
const auto& translation = getLocales(rootPath, getLanguage());
std::regex pattern(R"(\{\{([^}]+)\}\})");
std::smatch matches;
// replace the pattern to the correct translation
while (std::regex_search(manifest, matches, pattern)) {
if (matches.size() == 2) {
auto it = translation.find(matches[1].str());
if (it == translation.end()) {
manifest = std::regex_replace(manifest, pattern, "");
continue;
}
manifest = std::regex_replace(manifest, pattern, it->second, std::regex_constants::format_first_only);
}
}
return manifest;
}
return {};
}
bool
checkPluginValidity(const std::string& rootPath)
{
return !parseManifestFile(manifestPath(rootPath), rootPath).empty();
}
std::map<std::string, std::string>
readPluginManifestFromArchive(const std::string& jplPath)
{
try {
return checkManifestValidity(archiver::readFileFromArchive(jplPath, "manifest.json"));
} catch (const std::exception& e) {
JAMI_ERR() << e.what();
}
return {};
}
std::unique_ptr<dht::crypto::Certificate>
readPluginCertificate(const std::string& rootPath, const std::string& pluginId)
{
std::string certPath = rootPath + DIR_SEPARATOR_CH + pluginId + ".crt";
try {
auto cert = fileutils::loadFile(certPath);
return std::make_unique<dht::crypto::Certificate>(cert);
} catch (const std::exception& e) {
JAMI_ERR() << e.what();
}
return {};
}
std::unique_ptr<dht::crypto::Certificate>
readPluginCertificateFromArchive(const std::string& jplPath) {
try {
auto manifest = readPluginManifestFromArchive(jplPath);
const std::string& name = manifest["name"];
if (name.empty()) {
return {};
}
return std::make_unique<dht::crypto::Certificate>(archiver::readFileFromArchive(jplPath, name + ".crt"));
} catch(const std::exception& e) {
JAMI_ERR() << e.what();
return {};
}
}
std::map<std::string, std::vector<uint8_t>>
readPluginSignatureFromArchive(const std::string& jplPath) {
try {
std::vector<uint8_t> vec = archiver::readFileFromArchive(jplPath, "signatures");
msgpack::object_handle oh = msgpack::unpack(
reinterpret_cast<const char*>(vec.data()),
vec.size() * sizeof(uint8_t)
);
msgpack::object obj = oh.get();
return obj.as<std::map<std::string, std::vector<uint8_t>>>();
} catch(const std::exception& e) {
JAMI_ERR() << e.what();
return {};
}
}
std::vector<uint8_t>
readSignatureFileFromArchive(const std::string& jplPath)
{
return archiver::readFileFromArchive(jplPath, "signatures.sig");
}
std::pair<bool, std::string_view>
uncompressJplFunction(std::string_view relativeFileName)
{
std::svmatch match;
// manifest.json and files under data/ folder remains in the same structure
// but libraries files are extracted from the folder that matches the running ABI to
// the main installation path.
if (std::regex_search(relativeFileName, match, SO_REGEX)) {
if (std::svsub_match_view(match[1]) != ABI) {
return std::make_pair(false, std::string_view {});
} else {
return std::make_pair(true, std::svsub_match_view(match[2]));
}
}
return std::make_pair(true, relativeFileName);
}
std::string
getLanguage()
{
std::string lang;
if (auto envLang = std::getenv("JAMI_LANG"))
lang = envLang;
else
JAMI_INFO() << "Error getting JAMI_LANG env, trying to get system language";
// If language preference is empty, try to get from the system.
if (lang.empty()) {
#ifdef WIN32
WCHAR localeBuffer[LOCALE_NAME_MAX_LENGTH];
if (GetUserDefaultLocaleName(localeBuffer, LOCALE_NAME_MAX_LENGTH) != 0) {
char utf8Buffer[LOCALE_NAME_MAX_LENGTH] {};
WideCharToMultiByte(CP_UTF8,
0,
localeBuffer,
LOCALE_NAME_MAX_LENGTH,
utf8Buffer,
LOCALE_NAME_MAX_LENGTH,
nullptr,
nullptr);
lang.append(utf8Buffer);
string_replace(lang, "-", "_");
}
// Even though we default to the system variable in windows, technically this
// part of the code should not be reached because the client-qt must define that
// variable and we cannot run the client and the daemon in diferent processes in Windows.
#else
// The same way described in the comment just above, the android should not reach this
// part of the code given the client-android must define "JAMI_LANG" system variable.
// And even if this part is reached, it should not work since std::locale is not
// supported by the NDK.
// LC_COLLATE is used to grab the locale for the case when the system user has set different
// values for the preferred Language and Format.
lang = setlocale(LC_COLLATE, "");
// We set the environment to avoid checking from system everytime.
// This is the case when running daemon and client in different processes
// like with dbus.
setenv("JAMI_LANG", lang.c_str(), 1);
#endif // WIN32
}
return lang;
}
std::map<std::string, std::string>
getLocales(const std::string& rootPath, const std::string& lang)
{
auto pluginName = rootPath.substr(rootPath.find_last_of(DIR_SEPARATOR_CH) + 1);
auto basePath = fmt::format("{}/data/locale/{}", rootPath, pluginName + "_");
std::map<std::string, std::string> locales = {};
// Get language translations
if (!lang.empty()) {
locales = processLocaleFile(basePath + lang + ".json");
}
// Get default english values if no translations were found
if (locales.empty()) {
locales = processLocaleFile(basePath + "en.json");
}
return locales;
}
std::map<std::string, std::string>
processLocaleFile(const std::string& preferenceLocaleFilePath)
{
if (!fileutils::isFile(preferenceLocaleFilePath)) {
return {};
}
std::ifstream file(preferenceLocaleFilePath);
Json::Value root;
Json::CharReaderBuilder rbuilder;
rbuilder["collectComments"] = false;
std::string errs;
std::map<std::string, std::string> locales {};
if (file) {
// Read the file to a json format
if (Json::parseFromStream(rbuilder, file, &root, &errs)) {
auto keys = root.getMemberNames();
for (const auto& key : keys) {
locales[key] = root.get(key, "").asString();
}
}
}
return locales;
}
} // namespace PluginUtils
} // namespace jami