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