LCOV - code coverage report
Current view: top level - src/plugin - pluginsutils.cpp (source / functions) Coverage Total Hit
Test: jami-coverage-filtered.info Lines: 5.2 % 153 8
Test Date: 2026-06-13 09:18:46 Functions: 6.5 % 31 2

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

Generated by: LCOV version 2.0-1