LCOV - code coverage report
Current view: top level - foo/src/plugin - pluginsutils.cpp (source / functions) Hit Total Coverage
Test: jami-coverage-filtered.info Lines: 8 152 5.3 %
Date: 2026-04-01 09:29:43 Functions: 2 19 10.5 %

          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             : }
      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_ERR() << 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_ERR() << 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_ERR() << 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_ERR() << 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_ERR() << 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_INFO() << "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 1.14