Ring Daemon
Loading...
Searching...
No Matches
pluginsutils.cpp
Go to the documentation of this file.
1/*
2 * Copyright (C) 2004-2026 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#include "string_utils.h"
23
24#include <msgpack.hpp>
25
26#include <fstream>
27#include <regex>
28
29#if defined(__APPLE__)
30#if (defined(TARGET_OS_IOS) && TARGET_OS_IOS)
31#define ABI "iphone"
32#else
33#if defined(__x86_64__)
34#define ABI "x86_64-apple-Darwin"
35#else
36#define ABI "arm64-apple-Darwin"
37#endif
38#endif
39#elif defined(__arm__)
40#if defined(__ARM_ARCH_7A__)
41#define ABI "armeabi-v7a"
42#else
43#define ABI "armeabi"
44#endif
45#elif defined(__i386__)
46#if __ANDROID__
47#define ABI "x86"
48#else
49#define ABI "x86-linux-gnu"
50#endif
51#elif defined(__x86_64__)
52#if __ANDROID__
53#define ABI "x86_64"
54#else
55#define ABI "x86_64-linux-gnu"
56#endif
57#elif defined(__aarch64__)
58#define ABI "arm64-v8a"
59#elif defined(WIN32)
60#define ABI "x64-windows"
61#else
62#define ABI "unknown"
63#endif
64
65namespace jami {
66namespace PluginUtils {
67
68// DATA_REGEX is used to during the plugin jpl uncompressing
69const std::regex DATA_REGEX("^data" DIR_SEPARATOR_STR_ESC ".+");
70// SO_REGEX is used to find libraries during the plugin jpl uncompressing
71// lib/ABI/libplugin.SO
72const std::regex SO_REGEX(DIR_SEPARATOR_STR_ESC "(.*)" DIR_SEPARATOR_STR_ESC "([a-zA-Z0-9]+.(dylib|so|dll|lib).*)");
73
74std::filesystem::path
75manifestPath(const std::filesystem::path& rootPath)
76{
77 return rootPath / "manifest.json";
78}
79
80std::map<std::string, std::string>
82{
83 return {{"os", ABI}};
84}
85
86std::filesystem::path
87getRootPathFromSoPath(const std::filesystem::path& soPath)
88{
89 return soPath.parent_path();
90}
91
92std::filesystem::path
93dataPath(const std::filesystem::path& pluginSoPath)
94{
95 return getRootPathFromSoPath(pluginSoPath) / "data";
96}
97
98std::map<std::string, std::string>
100{
101 std::string name = root.get("name", "").asString();
102 std::string id = root.get("id", name).asString();
103 std::string description = root.get("description", "").asString();
104 std::string version = root.get("version", "").asString();
105 std::string iconPath = root.get("iconPath", "icon.png").asString();
106 std::string background = root.get("backgroundPath", "background.jpg").asString();
107 if (!name.empty() || !version.empty()) {
108 return {
109 {"id", id},
110 {"name", name},
111 {"description", description},
112 {"version", version},
113 {"iconPath", iconPath},
114 {"backgroundPath", background},
115 };
116 } else {
117 throw std::runtime_error("plugin manifest file: bad format");
118 }
119}
120
121std::map<std::string, std::string>
122checkManifestValidity(std::istream& stream)
123{
124 Json::Value root;
125 Json::CharReaderBuilder rbuilder;
126 rbuilder["collectComments"] = false;
127 std::string errs;
128
129 if (Json::parseFromStream(rbuilder, stream, &root, &errs)) {
131 } else {
132 throw std::runtime_error("failed to parse the plugin manifest file");
133 }
134}
135
136std::map<std::string, std::string>
137checkManifestValidity(const std::vector<uint8_t>& vec)
138{
139 Json::Value root;
140 std::unique_ptr<Json::CharReader> json_Reader(Json::CharReaderBuilder {}.newCharReader());
141 std::string errs;
142
143 bool ok = json_Reader->parse(reinterpret_cast<const char*>(vec.data()),
144 reinterpret_cast<const char*>(vec.data() + vec.size()),
145 &root,
146 &errs);
147
148 if (ok) {
150 } else {
151 throw std::runtime_error("failed to parse the plugin manifest file");
152 }
153}
154
155std::map<std::string, std::string>
156parseManifestFile(const std::filesystem::path& manifestFilePath, const std::string& rootPath)
157{
158 std::lock_guard guard(dhtnet::fileutils::getFileLock(manifestFilePath));
159 std::ifstream file(manifestFilePath);
160 if (file) {
161 try {
163 return checkManifestValidity(std::vector<uint8_t>(traduction.begin(), traduction.end()));
164 } catch (const std::exception& e) {
165 JAMI_ERR() << e.what();
166 }
167 }
168 return {};
169}
170
171std::string
172parseManifestTranslation(const std::string& rootPath, std::ifstream& manifestFile)
173{
174 if (manifestFile) {
175 std::stringstream buffer;
176 buffer << manifestFile.rdbuf();
177 std::string manifest = buffer.str();
178 const auto& translation = getLocales(rootPath, getLanguage());
179 std::regex pattern(R"(\{\{([^}]+)\}\})");
180 std::smatch matches;
181 // replace the pattern to the correct translation
182 while (std::regex_search(manifest, matches, pattern)) {
183 if (matches.size() == 2) {
184 auto it = translation.find(matches[1].str());
185 if (it == translation.end()) {
186 manifest = std::regex_replace(manifest, pattern, "");
187 continue;
188 }
189 manifest = std::regex_replace(manifest, pattern, it->second, std::regex_constants::format_first_only);
190 }
191 }
192 return manifest;
193 }
194 return {};
195}
196
197bool
198checkPluginValidity(const std::filesystem::path& rootPath)
199{
200 return !parseManifestFile(manifestPath(rootPath), rootPath.string()).empty();
201}
202
203std::map<std::string, std::string>
205{
206 try {
208 } catch (const std::exception& e) {
209 JAMI_ERR() << e.what();
210 }
211 return {};
212}
213
214std::unique_ptr<dht::crypto::Certificate>
215readPluginCertificate(const std::string& rootPath, const std::string& pluginId)
216{
217 std::string certPath = rootPath + DIR_SEPARATOR_CH + pluginId + ".crt";
218 try {
220 return std::make_unique<dht::crypto::Certificate>(cert);
221 } catch (const std::exception& e) {
222 JAMI_ERR() << e.what();
223 }
224 return {};
225}
226
227std::unique_ptr<dht::crypto::Certificate>
229{
230 try {
232 const std::string& name = manifest["id"];
233
234 if (name.empty()) {
235 return {};
236 }
237 return std::make_unique<dht::crypto::Certificate>(archiver::readFileFromArchive(jplPath, name + ".crt"));
238 } catch (const std::exception& e) {
239 JAMI_ERR() << e.what();
240 return {};
241 }
242}
243
244std::map<std::string, std::vector<uint8_t>>
246{
247 try {
248 std::vector<uint8_t> vec = archiver::readFileFromArchive(jplPath, "signatures");
249 msgpack::object_handle oh = msgpack::unpack(reinterpret_cast<const char*>(vec.data()),
250 vec.size() * sizeof(uint8_t));
251 msgpack::object obj = oh.get();
252 return obj.as<std::map<std::string, std::vector<uint8_t>>>();
253 } catch (const std::exception& e) {
254 JAMI_ERR() << e.what();
255 return {};
256 }
257}
258
259std::vector<uint8_t>
261{
262 return archiver::readFileFromArchive(jplPath, "signatures.sig");
263}
264
265std::pair<bool, std::string_view>
267{
268 std::svmatch match;
269 // manifest.json and files under data/ folder remains in the same structure
270 // but libraries files are extracted from the folder that matches the running ABI to
271 // the main installation path.
273 if (std::svsub_match_view(match[1]) != ABI) {
274 return std::make_pair(false, std::string_view {});
275 } else {
276 return std::make_pair(true, std::svsub_match_view(match[2]));
277 }
278 }
279 return std::make_pair(true, relativeFileName);
280}
281
282std::string
284{
285 std::string lang;
286 if (auto* envLang = std::getenv("JAMI_LANG"))
287 lang = envLang;
288 else
289 JAMI_INFO() << "Error getting JAMI_LANG env, attempting to get system language";
290 // If language preference is empty, try to get from the system.
291 if (lang.empty()) {
292#ifdef WIN32
297 0,
302 nullptr,
303 nullptr);
304
305 lang.append(utf8Buffer);
306 string_replace(lang, "-", "_");
307 }
308 // Even though we default to the system variable in Windows, technically this
309 // part of the code should not be reached because the client-qt must define that
310 // variable and is unable to run the client and the daemon in diferent processes in Windows.
311#else
312 // The same way described in the comment just above, Android should not reach this
313 // part of the code given the client-android must define "JAMI_LANG" system variable.
314 // And even if this part is reached, it should not work since std::locale is not
315 // supported by the NDK.
316
317 // LC_COLLATE is used to grab the locale for the case when the system user has set different
318 // values for the preferred Language and Format.
320 // We set the environment to avoid checking from system everytime.
321 // This is the case when running daemon and client in different processes
322 // like with dbus.
323 setenv("JAMI_LANG", lang.c_str(), 1);
324#endif // WIN32
325 }
326 return lang;
327}
328
329std::map<std::string, std::string>
330getLocales(const std::string& rootPath, const std::string& lang)
331{
332 auto pluginName = rootPath.substr(rootPath.find_last_of(DIR_SEPARATOR_CH) + 1);
333 auto basePath = fmt::format("{}/data/locale/{}", rootPath, pluginName + "_");
334
335 std::map<std::string, std::string> locales = {};
336
337 // Get language translations
338 if (!lang.empty()) {
339 locales = processLocaleFile(basePath + lang + ".json");
340 }
341
342 // Get default english values if no translations were found
343 if (locales.empty()) {
344 locales = processLocaleFile(basePath + "en.json");
345 }
346
347 return locales;
348}
349
350std::map<std::string, std::string>
352{
353 std::error_code ec;
354 if (!std::filesystem::is_regular_file(preferenceLocaleFilePath, ec)) {
355 return {};
356 }
357 std::ifstream file(preferenceLocaleFilePath);
358 Json::Value root;
359 Json::CharReaderBuilder rbuilder;
360 rbuilder["collectComments"] = false;
361 std::string errs;
362 std::map<std::string, std::string> locales {};
363 if (file) {
364 // Read the file to a json format
365 if (Json::parseFromStream(rbuilder, file, &root, &errs)) {
366 auto keys = root.getMemberNames();
367 for (const auto& key : keys) {
368 locales[key] = root.get(key, "").asString();
369 }
370 }
371 }
372 return locales;
373}
374} // namespace PluginUtils
375} // namespace jami
#define DIR_SEPARATOR_STR_ESC
Definition fileutils.h:35
#define DIR_SEPARATOR_CH
Definition fileutils.h:34
#define JAMI_ERR(...)
Definition logger.h:230
#define JAMI_INFO(...)
Definition logger.h:227
This namespace provides auxiliary functions to the Plugin System.
std::unique_ptr< dht::crypto::Certificate > readPluginCertificate(const std::string &rootPath, const std::string &pluginId)
Read the plugin's certificate.
std::string getLanguage()
Returns the language of the current locale.
std::map< std::string, std::string > checkManifestJsonContentValidity(const Json::Value &root)
Check if manifest.json has minimum format and parses its content to a map<string, string>.
std::map< std::string, std::string > readPluginManifestFromArchive(const std::string &jplPath)
Reads the manifest file content without uncompressing the whole archive and return a map with manifes...
std::filesystem::path dataPath(const std::filesystem::path &pluginSoPath)
Returns data path given a plugin's library path.
std::map< std::string, std::string > getPlatformInfo()
Returns a map with platform information.
std::filesystem::path getRootPathFromSoPath(const std::filesystem::path &soPath)
Returns installation path given a plugin's library path.
std::vector< uint8_t > readSignatureFileFromArchive(const std::string &jplPath)
Read the signature of the file signature without uncompressing the whole archive.
const std::regex SO_REGEX(DIR_SEPARATOR_STR_ESC "(.*)" DIR_SEPARATOR_STR_ESC "([a-zA-Z0-9]+.(dylib|so|dll|lib).*)")
bool checkPluginValidity(const std::filesystem::path &rootPath)
Validates a plugin based on its manifest.json file.
std::filesystem::path manifestPath(const std::filesystem::path &rootPath)
Returns complete manifest.json file path given a installation path.
std::unique_ptr< dht::crypto::Certificate > readPluginCertificateFromArchive(const std::string &jplPath)
Read plugin certificate without uncompressing the whole archive.and return an object Certificate.
std::map< std::string, std::string > processLocaleFile(const std::string &preferenceLocaleFilePath)
Returns the available keys and translations for a given file.
std::pair< bool, std::string_view > uncompressJplFunction(std::string_view relativeFileName)
Function used by archiver to extract files from plugin jpl to the plugin installation path.
std::map< std::string, std::string > getLocales(const std::string &rootPath, const std::string &lang)
Returns the available keys and translations for a given plugin.
std::map< std::string, std::string > parseManifestFile(const std::filesystem::path &manifestFilePath, const std::string &rootPath)
Parses the manifest file of an installed plugin if it's valid.
std::map< std::string, std::vector< uint8_t > > readPluginSignatureFromArchive(const std::string &jplPath)
Reads signature file content without uncompressing the whole archive and.
const std::regex DATA_REGEX("^data" DIR_SEPARATOR_STR_ESC ".+")
std::string parseManifestTranslation(const std::string &rootPath, std::ifstream &manifestFile)
Parses the manifest file of an installed plugin if it's valid.
std::map< std::string, std::string > checkManifestValidity(std::istream &stream)
Reads manifest.json stream and checks if it's valid.
std::vector< uint8_t > readFileFromArchive(const std::string &archivePath, const std::string &fileRelativePathName)
readFileFromArchive read a file from an archive without uncompressing the whole archive
Definition archiver.cpp:329
std::vector< uint8_t > loadFile(const std::filesystem::path &path, const std::filesystem::path &default_dir)
Read the full content of a file at path.
static constexpr int version
void emitSignal(Args... args)
Definition jami_signal.h:64
void string_replace(std::string &str, const std::string &from, const std::string &to)
match_results< string_view::const_iterator > svmatch
constexpr string_view svsub_match_view(const svsub_match &submatch) noexcept
bool regex_search(string_view sv, svmatch &m, const regex &e, regex_constants::match_flag_type flags=regex_constants::match_default)
#define ABI