LCOV - code coverage report
Current view: top level - foo/src/plugin - pluginsutils.cpp (source / functions) Hit Total Coverage
Test: jami-coverage-filtered.info Lines: 0 152 0.0 %
Date: 2025-12-18 10:07:43 Functions: 0 19 0.0 %

          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 "pluginsutils.h"
      19             : #include "logger.h"
      20             : #include "fileutils.h"
      21             : #include "archiver.h"
      22             : 
      23             : #include <msgpack.hpp>
      24             : 
      25             : #include <fstream>
      26             : #include <regex>
      27             : 
      28             : #if defined(__APPLE__)
      29             : #if (defined(TARGET_OS_IOS) && TARGET_OS_IOS)
      30             : #define ABI "iphone"
      31             : #else
      32             : #if defined(__x86_64__)
      33             : #define ABI "x86_64-apple-Darwin"
      34             : #else
      35             : #define ABI "arm64-apple-Darwin"
      36             : #endif
      37             : #endif
      38             : #elif defined(__arm__)
      39             : #if defined(__ARM_ARCH_7A__)
      40             : #define ABI "armeabi-v7a"
      41             : #else
      42             : #define ABI "armeabi"
      43             : #endif
      44             : #elif defined(__i386__)
      45             : #if __ANDROID__
      46             : #define ABI "x86"
      47             : #else
      48             : #define ABI "x86-linux-gnu"
      49             : #endif
      50             : #elif defined(__x86_64__)
      51             : #if __ANDROID__
      52             : #define ABI "x86_64"
      53             : #else
      54             : #define ABI "x86_64-linux-gnu"
      55             : #endif
      56             : #elif defined(__aarch64__)
      57             : #define ABI "arm64-v8a"
      58             : #elif defined(WIN32)
      59             : #define ABI "x64-windows"
      60             : #else
      61             : #define ABI "unknown"
      62             : #endif
      63             : 
      64             : namespace jami {
      65             : namespace PluginUtils {
      66             : 
      67             : // DATA_REGEX is used to during the plugin jpl uncompressing
      68             : const std::regex DATA_REGEX("^data" DIR_SEPARATOR_STR_ESC ".+");
      69             : // SO_REGEX is used to find libraries during the plugin jpl uncompressing
      70             : // lib/ABI/libplugin.SO
      71             : const std::regex SO_REGEX(DIR_SEPARATOR_STR_ESC "(.*)" DIR_SEPARATOR_STR_ESC "([a-zA-Z0-9]+.(dylib|so|dll|lib).*)");
      72             : 
      73             : std::filesystem::path
      74           0 : manifestPath(const std::filesystem::path& rootPath)
      75             : {
      76           0 :     return rootPath / "manifest.json";
      77             : }
      78             : 
      79             : std::map<std::string, std::string>
      80           0 : getPlatformInfo()
      81             : {
      82           0 :     return {{"os", ABI}};
      83             : }
      84             : 
      85             : std::filesystem::path
      86           0 : getRootPathFromSoPath(const std::filesystem::path& soPath)
      87             : {
      88           0 :     return soPath.parent_path();
      89             : }
      90             : 
      91             : std::filesystem::path
      92           0 : dataPath(const std::filesystem::path& pluginSoPath)
      93             : {
      94           0 :     return getRootPathFromSoPath(pluginSoPath) / "data";
      95             : }
      96             : 
      97             : std::map<std::string, std::string>
      98           0 : checkManifestJsonContentValidity(const Json::Value& root)
      99             : {
     100           0 :     std::string name = root.get("name", "").asString();
     101           0 :     std::string id = root.get("id", name).asString();
     102           0 :     std::string description = root.get("description", "").asString();
     103           0 :     std::string version = root.get("version", "").asString();
     104           0 :     std::string iconPath = root.get("iconPath", "icon.png").asString();
     105           0 :     std::string background = root.get("backgroundPath", "background.jpg").asString();
     106           0 :     if (!name.empty() || !version.empty()) {
     107             :         return {
     108             :             {"id", id},
     109             :             {"name", name},
     110             :             {"description", description},
     111             :             {"version", version},
     112             :             {"iconPath", iconPath},
     113             :             {"backgroundPath", background},
     114           0 :         };
     115             :     } else {
     116           0 :         throw std::runtime_error("plugin manifest file: bad format");
     117             :     }
     118           0 : }
     119             : 
     120             : std::map<std::string, std::string>
     121           0 : checkManifestValidity(std::istream& stream)
     122             : {
     123           0 :     Json::Value root;
     124           0 :     Json::CharReaderBuilder rbuilder;
     125           0 :     rbuilder["collectComments"] = false;
     126           0 :     std::string errs;
     127             : 
     128           0 :     if (Json::parseFromStream(rbuilder, stream, &root, &errs)) {
     129           0 :         return checkManifestJsonContentValidity(root);
     130             :     } else {
     131           0 :         throw std::runtime_error("failed to parse the plugin manifest file");
     132             :     }
     133           0 : }
     134             : 
     135             : std::map<std::string, std::string>
     136           0 : checkManifestValidity(const std::vector<uint8_t>& vec)
     137             : {
     138           0 :     Json::Value root;
     139           0 :     std::unique_ptr<Json::CharReader> json_Reader(Json::CharReaderBuilder {}.newCharReader());
     140           0 :     std::string errs;
     141             : 
     142           0 :     bool ok = json_Reader->parse(reinterpret_cast<const char*>(vec.data()),
     143           0 :                                  reinterpret_cast<const char*>(vec.data() + vec.size()),
     144             :                                  &root,
     145             :                                  &errs);
     146             : 
     147           0 :     if (ok) {
     148           0 :         return checkManifestJsonContentValidity(root);
     149             :     } else {
     150           0 :         throw std::runtime_error("failed to parse the plugin manifest file");
     151             :     }
     152           0 : }
     153             : 
     154             : std::map<std::string, std::string>
     155           0 : parseManifestFile(const std::filesystem::path& manifestFilePath, const std::string& rootPath)
     156             : {
     157           0 :     std::lock_guard guard(dhtnet::fileutils::getFileLock(manifestFilePath));
     158           0 :     std::ifstream file(manifestFilePath);
     159           0 :     if (file) {
     160             :         try {
     161           0 :             const auto& traduction = parseManifestTranslation(rootPath, file);
     162           0 :             return checkManifestValidity(std::vector<uint8_t>(traduction.begin(), traduction.end()));
     163           0 :         } catch (const std::exception& e) {
     164           0 :             JAMI_ERR() << e.what();
     165           0 :         }
     166             :     }
     167           0 :     return {};
     168           0 : }
     169             : 
     170             : std::string
     171           0 : parseManifestTranslation(const std::string& rootPath, std::ifstream& manifestFile)
     172             : {
     173           0 :     if (manifestFile) {
     174           0 :         std::stringstream buffer;
     175           0 :         buffer << manifestFile.rdbuf();
     176           0 :         std::string manifest = buffer.str();
     177           0 :         const auto& translation = getLocales(rootPath, getLanguage());
     178           0 :         std::regex pattern(R"(\{\{([^}]+)\}\})");
     179           0 :         std::smatch matches;
     180             :         // replace the pattern to the correct translation
     181           0 :         while (std::regex_search(manifest, matches, pattern)) {
     182           0 :             if (matches.size() == 2) {
     183           0 :                 auto it = translation.find(matches[1].str());
     184           0 :                 if (it == translation.end()) {
     185           0 :                     manifest = std::regex_replace(manifest, pattern, "");
     186           0 :                     continue;
     187             :                 }
     188           0 :                 manifest = std::regex_replace(manifest, pattern, it->second, std::regex_constants::format_first_only);
     189             :             }
     190             :         }
     191           0 :         return manifest;
     192           0 :     }
     193           0 :     return {};
     194             : }
     195             : 
     196             : bool
     197           0 : checkPluginValidity(const std::filesystem::path& rootPath)
     198             : {
     199           0 :     return !parseManifestFile(manifestPath(rootPath), rootPath.string()).empty();
     200             : }
     201             : 
     202             : std::map<std::string, std::string>
     203           0 : readPluginManifestFromArchive(const std::string& jplPath)
     204             : {
     205             :     try {
     206           0 :         return checkManifestValidity(archiver::readFileFromArchive(jplPath, "manifest.json"));
     207           0 :     } catch (const std::exception& e) {
     208           0 :         JAMI_ERR() << e.what();
     209           0 :     }
     210           0 :     return {};
     211             : }
     212             : 
     213             : std::unique_ptr<dht::crypto::Certificate>
     214           0 : readPluginCertificate(const std::string& rootPath, const std::string& pluginId)
     215             : {
     216           0 :     std::string certPath = rootPath + DIR_SEPARATOR_CH + pluginId + ".crt";
     217             :     try {
     218           0 :         auto cert = fileutils::loadFile(certPath);
     219           0 :         return std::make_unique<dht::crypto::Certificate>(cert);
     220           0 :     } catch (const std::exception& e) {
     221           0 :         JAMI_ERR() << e.what();
     222           0 :     }
     223           0 :     return {};
     224           0 : }
     225             : 
     226             : std::unique_ptr<dht::crypto::Certificate>
     227           0 : readPluginCertificateFromArchive(const std::string& jplPath)
     228             : {
     229             :     try {
     230           0 :         auto manifest = readPluginManifestFromArchive(jplPath);
     231           0 :         const std::string& name = manifest["id"];
     232             : 
     233           0 :         if (name.empty()) {
     234           0 :             return {};
     235             :         }
     236           0 :         return std::make_unique<dht::crypto::Certificate>(archiver::readFileFromArchive(jplPath, name + ".crt"));
     237           0 :     } catch (const std::exception& e) {
     238           0 :         JAMI_ERR() << e.what();
     239           0 :         return {};
     240           0 :     }
     241             : }
     242             : 
     243             : std::map<std::string, std::vector<uint8_t>>
     244           0 : readPluginSignatureFromArchive(const std::string& jplPath)
     245             : {
     246             :     try {
     247           0 :         std::vector<uint8_t> vec = archiver::readFileFromArchive(jplPath, "signatures");
     248           0 :         msgpack::object_handle oh = msgpack::unpack(reinterpret_cast<const char*>(vec.data()),
     249           0 :                                                     vec.size() * sizeof(uint8_t));
     250           0 :         msgpack::object obj = oh.get();
     251           0 :         return obj.as<std::map<std::string, std::vector<uint8_t>>>();
     252           0 :     } catch (const std::exception& e) {
     253           0 :         JAMI_ERR() << e.what();
     254           0 :         return {};
     255           0 :     }
     256             : }
     257             : 
     258             : std::vector<uint8_t>
     259           0 : readSignatureFileFromArchive(const std::string& jplPath)
     260             : {
     261           0 :     return archiver::readFileFromArchive(jplPath, "signatures.sig");
     262             : }
     263             : 
     264             : std::pair<bool, std::string_view>
     265           0 : uncompressJplFunction(std::string_view relativeFileName)
     266             : {
     267           0 :     std::svmatch match;
     268             :     // manifest.json and files under data/ folder remains in the same structure
     269             :     // but libraries files are extracted from the folder that matches the running ABI to
     270             :     // the main installation path.
     271           0 :     if (std::regex_search(relativeFileName, match, SO_REGEX)) {
     272           0 :         if (std::svsub_match_view(match[1]) != ABI) {
     273           0 :             return std::make_pair(false, std::string_view {});
     274             :         } else {
     275           0 :             return std::make_pair(true, std::svsub_match_view(match[2]));
     276             :         }
     277             :     }
     278           0 :     return std::make_pair(true, relativeFileName);
     279           0 : }
     280             : 
     281             : std::string
     282           0 : getLanguage()
     283             : {
     284           0 :     std::string lang;
     285           0 :     if (auto envLang = std::getenv("JAMI_LANG"))
     286           0 :         lang = envLang;
     287             :     else
     288           0 :         JAMI_INFO() << "Error getting JAMI_LANG env, attempting to get system language";
     289             :     // If language preference is empty, try to get from the system.
     290           0 :     if (lang.empty()) {
     291             : #ifdef WIN32
     292             :         WCHAR localeBuffer[LOCALE_NAME_MAX_LENGTH];
     293             :         if (GetUserDefaultLocaleName(localeBuffer, LOCALE_NAME_MAX_LENGTH) != 0) {
     294             :             char utf8Buffer[LOCALE_NAME_MAX_LENGTH] {};
     295             :             WideCharToMultiByte(CP_UTF8,
     296             :                                 0,
     297             :                                 localeBuffer,
     298             :                                 LOCALE_NAME_MAX_LENGTH,
     299             :                                 utf8Buffer,
     300             :                                 LOCALE_NAME_MAX_LENGTH,
     301             :                                 nullptr,
     302             :                                 nullptr);
     303             : 
     304             :             lang.append(utf8Buffer);
     305             :             string_replace(lang, "-", "_");
     306             :         }
     307             :         // Even though we default to the system variable in Windows, technically this
     308             :         // part of the code should not be reached because the client-qt must define that
     309             :         // variable and is unable to run the client and the daemon in diferent processes in Windows.
     310             : #else
     311             :         // The same way described in the comment just above, Android should not reach this
     312             :         // part of the code given the client-android must define "JAMI_LANG" system variable.
     313             :         // And even if this part is reached, it should not work since std::locale is not
     314             :         // supported by the NDK.
     315             : 
     316             :         // LC_COLLATE is used to grab the locale for the case when the system user has set different
     317             :         // values for the preferred Language and Format.
     318           0 :         lang = setlocale(LC_COLLATE, "");
     319             :         // We set the environment to avoid checking from system everytime.
     320             :         // This is the case when running daemon and client in different processes
     321             :         // like with dbus.
     322           0 :         setenv("JAMI_LANG", lang.c_str(), 1);
     323             : #endif // WIN32
     324             :     }
     325           0 :     return lang;
     326           0 : }
     327             : 
     328             : std::map<std::string, std::string>
     329           0 : getLocales(const std::string& rootPath, const std::string& lang)
     330             : {
     331           0 :     auto pluginName = rootPath.substr(rootPath.find_last_of(DIR_SEPARATOR_CH) + 1);
     332           0 :     auto basePath = fmt::format("{}/data/locale/{}", rootPath, pluginName + "_");
     333             : 
     334           0 :     std::map<std::string, std::string> locales = {};
     335             : 
     336             :     // Get language translations
     337           0 :     if (!lang.empty()) {
     338           0 :         locales = processLocaleFile(basePath + lang + ".json");
     339             :     }
     340             : 
     341             :     // Get default english values if no translations were found
     342           0 :     if (locales.empty()) {
     343           0 :         locales = processLocaleFile(basePath + "en.json");
     344             :     }
     345             : 
     346           0 :     return locales;
     347           0 : }
     348             : 
     349             : std::map<std::string, std::string>
     350           0 : processLocaleFile(const std::string& preferenceLocaleFilePath)
     351             : {
     352           0 :     std::error_code ec;
     353           0 :     if (!std::filesystem::is_regular_file(preferenceLocaleFilePath, ec)) {
     354           0 :         return {};
     355             :     }
     356           0 :     std::ifstream file(preferenceLocaleFilePath);
     357           0 :     Json::Value root;
     358           0 :     Json::CharReaderBuilder rbuilder;
     359           0 :     rbuilder["collectComments"] = false;
     360           0 :     std::string errs;
     361           0 :     std::map<std::string, std::string> locales {};
     362           0 :     if (file) {
     363             :         // Read the file to a json format
     364           0 :         if (Json::parseFromStream(rbuilder, file, &root, &errs)) {
     365           0 :             auto keys = root.getMemberNames();
     366           0 :             for (const auto& key : keys) {
     367           0 :                 locales[key] = root.get(key, "").asString();
     368             :             }
     369           0 :         }
     370             :     }
     371           0 :     return locales;
     372           0 : }
     373             : } // namespace PluginUtils
     374             : } // namespace jami

Generated by: LCOV version 1.14