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-03-28 08:00:27 Functions: 17 19 89.5 %

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

Generated by: LCOV version 1.14