LCOV - code coverage report
Current view: top level - src/plugin - pluginsutils.cpp (source / functions) Hit Total Coverage
Test: jami-coverage-filtered.info Lines: 120 151 79.5 %
Date: 2024-12-21 08:56:24 Functions: 17 19 89.5 %

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

Generated by: LCOV version 1.14