| /* |
| * Copyright (C) 2021 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 <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::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 description = root.get("description", "").asString(); |
| std::string version = root.get("version", "").asString(); |
| std::string iconPath = root.get("iconPath", "icon.png").asString(); |
| if (!name.empty() || !version.empty()) { |
| return {{"name", name}, |
| {"description", description}, |
| {"version", version}, |
| {"iconPath", iconPath}}; |
| } 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) |
| { |
| std::lock_guard<std::mutex> guard(fileutils::getFileLock(manifestFilePath)); |
| std::ifstream file(manifestFilePath); |
| if (file) { |
| try { |
| return checkManifestValidity(file); |
| } catch (const std::exception& e) { |
| JAMI_ERR() << e.what(); |
| } |
| } |
| return {}; |
| } |
| |
| bool |
| checkPluginValidity(const std::string& rootPath) |
| { |
| return !parseManifestFile(manifestPath(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::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 (relativeFileName == "manifest.json" || std::regex_match(relativeFileName, DATA_REGEX)) { |
| return std::make_pair(true, relativeFileName); |
| } else if (std::regex_search(relativeFileName, match, SO_REGEX)) { |
| if (std::svsub_match_view(match[1]) == ABI) { |
| return std::make_pair(true, std::svsub_match_view(match[2])); |
| } |
| } |
| return std::make_pair(false, std::string_view {}); |
| } |
| } // namespace PluginUtils |
| } // namespace jami |