blob: ae5e80b816f7a5f92ca4c235d9e6d4a1102d3aa8 [file] [log] [blame]
/*
* 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