Skip to content
Snippets Groups Projects
Select Git revision
  • 139eaa240c7b77a3d1cf62b4619a775167e7a057
  • master default protected
  • release/202005
  • release/202001
  • release/201912
  • release/201911
  • release/releaseWindowsTestOne
  • release/windowsReleaseTest
  • release/releaseTest
  • release/releaseWindowsTest
  • release/201910
  • release/qt/201910
  • release/windows-test/201910
  • release/201908
  • release/201906
  • release/201905
  • release/201904
  • release/201903
  • release/201902
  • release/201901
  • release/201812
  • 4.0.0
  • 2.2.0
  • 2.1.0
  • 2.0.1
  • 2.0.0
  • 1.4.1
  • 1.4.0
  • 1.3.0
  • 1.2.0
  • 1.1.0
31 results

pluginsutils.cpp

Blame
  • Code owners
    Assign users and groups as approvers for specific file changes. Learn more.
    pluginsutils.cpp 12.26 KiB
    /*
     *  Copyright (C) 2021-2023 Savoir-faire Linux Inc.
     *
     *  Author: Aline Gondim Santos <aline.gondimsantos@savoirfairelinux.com>
     *
     *  This program is free software; you can redistribute it and/or modify
     *  it under the terms of the GNU General Public License as published by
     *  the Free Software Foundation; either version 3 of the License, or
     *  (at your option) any later version.
     *
     *  This program is distributed in the hope that it will be useful,
     *  but WITHOUT ANY WARRANTY; without even the implied warranty of
     *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     *  GNU General Public License for more details.
     *
     *  You should have received a copy of the GNU General Public License
     *  along with this program; if not, write to the Free Software
     *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA.
     */
    
    #include "pluginsutils.h"
    #include "logger.h"
    #include "fileutils.h"
    #include "archiver.h"
    
    #include <msgpack.hpp>
    
    #include <fstream>
    #include <regex>
    
    #if defined(__APPLE__)
        #if (defined(TARGET_OS_IOS) && TARGET_OS_IOS)
            #define ABI "iphone"
        #else
            #define ABI "x86_64-apple-Darwin"
        #endif
    #elif defined(__arm__)
        #if defined(__ARM_ARCH_7A__)
            #define ABI "armeabi-v7a"
        #else
            #define ABI "armeabi"
        #endif
    #elif defined(__i386__)
        #if __ANDROID__
            #define ABI "x86"
        #else
            #define ABI "x86-linux-gnu"
        #endif
    #elif defined(__x86_64__)
        #if __ANDROID__
            #define ABI "x86_64"
        #else
            #define ABI "x86_64-linux-gnu"
        #endif
    #elif defined(__aarch64__)
    #define ABI "arm64-v8a"
    #elif defined(WIN32)
    #define ABI "x64-windows"
    #else
    #define ABI "unknown"
    #endif
    
    namespace jami {
    namespace PluginUtils {
    
    // DATA_REGEX is used to during the plugin jpl uncompressing
    const std::regex DATA_REGEX("^data" DIR_SEPARATOR_STR_ESC ".+");
    // SO_REGEX is used to find libraries during the plugin jpl uncompressing
    const std::regex SO_REGEX("([a-zA-Z0-9]+(?:[_-]?[a-zA-Z0-9]+)*)" DIR_SEPARATOR_STR_ESC
                              "([a-zA-Z0-9_-]+\\.(dylib|so|dll|lib).*)");
    
    std::string
    manifestPath(const std::string& rootPath)
    {
        return rootPath + DIR_SEPARATOR_CH + "manifest.json";
    }
    
    std::map<std::string, std::string>
    getPlatformInfo()
    {
        std::map<std::string, std::string> platformInfo = {};
        platformInfo["os"] = ABI;
        return platformInfo;
    }
    
    std::string
    getRootPathFromSoPath(const std::string& soPath)
    {
        return soPath.substr(0, soPath.find_last_of(DIR_SEPARATOR_CH));
    }
    
    std::string
    dataPath(const std::string& pluginSoPath)
    {
        return getRootPathFromSoPath(pluginSoPath) + DIR_SEPARATOR_CH + "data";
    }
    
    std::map<std::string, std::string>
    checkManifestJsonContentValidity(const Json::Value& root)
    {
        std::string name = root.get("name", "").asString();
        std::string id = root.get("id", name).asString();
        std::string description = root.get("description", "").asString();
        std::string version = root.get("version", "").asString();
        std::string iconPath = root.get("iconPath", "icon.png").asString();
        std::string background = root.get("backgroundPath", "background.jpg").asString();
        if (!name.empty() || !version.empty()) {
            return {
                    {"id", id},
                    {"name", name},
                    {"description", description},
                    {"version", version},
                    {"iconPath", iconPath},
                    {"backgroundPath", background},
                    };
        } else {
            throw std::runtime_error("plugin manifest file: bad format");
        }
    }
    
    std::map<std::string, std::string>
    checkManifestValidity(std::istream& stream)
    {
        Json::Value root;
        Json::CharReaderBuilder rbuilder;
        rbuilder["collectComments"] = false;
        std::string errs;
    
        if (Json::parseFromStream(rbuilder, stream, &root, &errs)) {
            return checkManifestJsonContentValidity(root);
        } else {
            throw std::runtime_error("failed to parse the plugin manifest file");
        }
    }
    
    std::map<std::string, std::string>
    checkManifestValidity(const std::vector<uint8_t>& vec)
    {
        Json::Value root;
        std::unique_ptr<Json::CharReader> json_Reader(Json::CharReaderBuilder {}.newCharReader());
        std::string errs;
    
        bool ok = json_Reader->parse(reinterpret_cast<const char*>(vec.data()),
                                     reinterpret_cast<const char*>(vec.data() + vec.size()),
                                     &root,
                                     &errs);
    
        if (ok) {
            return checkManifestJsonContentValidity(root);
        } else {
            throw std::runtime_error("failed to parse the plugin manifest file");
        }
    }
    
    std::map<std::string, std::string>
    parseManifestFile(const std::string& manifestFilePath, const std::string& rootPath)
    {
        std::lock_guard<std::mutex> guard(fileutils::getFileLock(manifestFilePath));
        std::ifstream file(manifestFilePath);
        if (file) {
            try {
                const auto& traduction = parseManifestTranslation(rootPath, file);
                return checkManifestValidity(std::vector<uint8_t>(traduction.begin(), traduction.end()));
            } catch (const std::exception& e) {
                JAMI_ERR() << e.what();
            }
        }
        return {};
    }
    
    std::string
    parseManifestTranslation(const std::string& rootPath, std::ifstream& manifestFile)
    {
        if (manifestFile) {
            std::stringstream buffer;
            buffer << manifestFile.rdbuf();
            std::string manifest = buffer.str();
            const auto& translation = getLocales(rootPath, getLanguage());
            std::regex pattern(R"(\{\{([^}]+)\}\})");
            std::smatch matches;
            // replace the pattern to the correct translation
            while (std::regex_search(manifest, matches, pattern)) {
                if (matches.size() == 2) {
                    auto it = translation.find(matches[1].str());
                    if (it == translation.end()) {
                        manifest = std::regex_replace(manifest, pattern, "");
                        continue;
                    }
                    manifest = std::regex_replace(manifest, pattern, it->second, std::regex_constants::format_first_only);
                }
            }
            return manifest;
        }
        return {};
    }
    
    bool
    checkPluginValidity(const std::string& rootPath)
    {
        return !parseManifestFile(manifestPath(rootPath), rootPath).empty();
    }
    
    std::map<std::string, std::string>
    readPluginManifestFromArchive(const std::string& jplPath)
    {
        try {
            return checkManifestValidity(archiver::readFileFromArchive(jplPath, "manifest.json"));
        } catch (const std::exception& e) {
            JAMI_ERR() << e.what();
        }
        return {};
    }
    
    std::unique_ptr<dht::crypto::Certificate>
    readPluginCertificate(const std::string& rootPath, const std::string& pluginId)
    {
        std::string certPath = rootPath + DIR_SEPARATOR_CH + pluginId + ".crt";
        try {
            auto cert = fileutils::loadFile(certPath);
            return std::make_unique<dht::crypto::Certificate>(cert);
        } catch (const std::exception& e) {
            JAMI_ERR() << e.what();
        }
        return {};
    }
    
    std::unique_ptr<dht::crypto::Certificate>
    readPluginCertificateFromArchive(const std::string& jplPath) {
        try {
            auto manifest = readPluginManifestFromArchive(jplPath);
            const std::string& name = manifest["name"];
    
            if (name.empty()) {
                return {};
            }
            return std::make_unique<dht::crypto::Certificate>(archiver::readFileFromArchive(jplPath, name + ".crt"));
        } catch(const std::exception& e) {
            JAMI_ERR() << e.what();
            return {};
        }
    }
    
    std::map<std::string, std::vector<uint8_t>>
    readPluginSignatureFromArchive(const std::string& jplPath) {
        try {
            std::vector<uint8_t> vec = archiver::readFileFromArchive(jplPath, "signatures");
            msgpack::object_handle oh = msgpack::unpack(
                            reinterpret_cast<const char*>(vec.data()),
                            vec.size() * sizeof(uint8_t)
                        );
            msgpack::object obj = oh.get();
            return obj.as<std::map<std::string, std::vector<uint8_t>>>();
        } catch(const std::exception& e) {
            JAMI_ERR() << e.what();
            return {};
        }
    }
    
    std::vector<uint8_t>
    readSignatureFileFromArchive(const std::string& jplPath)
    {
        return archiver::readFileFromArchive(jplPath, "signatures.sig");
    }
    
    std::pair<bool, std::string_view>
    uncompressJplFunction(std::string_view relativeFileName)
    {
        std::svmatch match;
        // manifest.json and files under data/ folder remains in the same structure
        // but libraries files are extracted from the folder that matches the running ABI to
        // the main installation path.
        if (std::regex_search(relativeFileName, match, SO_REGEX)) {
            if (std::svsub_match_view(match[1]) != ABI) {
                return std::make_pair(false, std::string_view {});
            } else {
                return std::make_pair(true, std::svsub_match_view(match[2]));
            }
        }
        return std::make_pair(true, relativeFileName);
    }
    
    std::string
    getLanguage()
    {
        std::string lang;
            if (auto envLang = std::getenv("JAMI_LANG"))
                lang = envLang;
            else
                JAMI_INFO() << "Error getting JAMI_LANG env, trying to get system language";
            // If language preference is empty, try to get from the system.
            if (lang.empty()) {
    #ifdef WIN32
                WCHAR localeBuffer[LOCALE_NAME_MAX_LENGTH];
                if (GetUserDefaultLocaleName(localeBuffer, LOCALE_NAME_MAX_LENGTH) != 0) {
                    char utf8Buffer[LOCALE_NAME_MAX_LENGTH] {};
                    WideCharToMultiByte(CP_UTF8,
                                        0,
                                        localeBuffer,
                                        LOCALE_NAME_MAX_LENGTH,
                                        utf8Buffer,
                                        LOCALE_NAME_MAX_LENGTH,
                                        nullptr,
                                        nullptr);
    
                    lang.append(utf8Buffer);
                    string_replace(lang, "-", "_");
                }
                // Even though we default to the system variable in windows, technically this
                // part of the code should not be reached because the client-qt must define that
                // variable and we cannot run the client and the daemon in diferent processes in Windows.
    #else
                // The same way described in the comment just above, the android should not reach this
                // part of the code given the client-android must define "JAMI_LANG" system variable.
                // And even if this part is reached, it should not work since std::locale is not
                // supported by the NDK.
    
                // LC_COLLATE is used to grab the locale for the case when the system user has set different
                // values for the preferred Language and Format.
                lang = setlocale(LC_COLLATE, "");
                // We set the environment to avoid checking from system everytime.
                // This is the case when running daemon and client in different processes
                // like with dbus.
                setenv("JAMI_LANG", lang.c_str(), 1);
    #endif // WIN32
        }
        return lang;
    }
    
    std::map<std::string, std::string>
    getLocales(const std::string& rootPath, const std::string& lang)
    {
        auto pluginName = rootPath.substr(rootPath.find_last_of(DIR_SEPARATOR_CH) + 1);
        auto basePath = fmt::format("{}/data/locale/{}", rootPath, pluginName + "_");
    
        std::map<std::string, std::string> locales = {};
    
        // Get language translations
        if (!lang.empty()) {
            locales = processLocaleFile(basePath + lang + ".json");
        }
    
        // Get default english values if no translations were found
        if (locales.empty()) {
            locales = processLocaleFile(basePath + "en.json");
        }
    
        return locales;
    }
    
    std::map<std::string, std::string>
    processLocaleFile(const std::string& preferenceLocaleFilePath)
    {
        if (!fileutils::isFile(preferenceLocaleFilePath)) {
            return {};
        }
        std::ifstream file(preferenceLocaleFilePath);
        Json::Value root;
        Json::CharReaderBuilder rbuilder;
        rbuilder["collectComments"] = false;
        std::string errs;
        std::map<std::string, std::string> locales {};
        if (file) {
            // Read the file to a json format
            if (Json::parseFromStream(rbuilder, file, &root, &errs)) {
                auto keys = root.getMemberNames();
                for (const auto& key : keys) {
                    locales[key] = root.get(key, "").asString();
                }
            }
        }
        return locales;
    }
    } // namespace PluginUtils
    } // namespace jami