Files
BackOne/service/AddonHost.cpp
T
2026-06-30 13:10:35 +07:00

284 lines
9.1 KiB
C++

/*
* Copyright (c)2019 ZeroTier, Inc.
*
* Use of this software is governed by the Business Source License included
* in the LICENSE.TXT file in the project's root directory.
*/
/****/
#include "AddonHost.hpp"
#include "../node/Constants.hpp"
#include "../osdep/OSUtils.hpp"
namespace ZeroTier {
AddonHost::AddonHost(const std::string &homePath) :
_homePath(homePath),
_systemAddonsPath(OSUtils::platformDefaultSystemAddonsPath()),
_enabledRegistryPath(homePath + ZT_PATH_SEPARATOR_S "addons.d" + ZT_PATH_SEPARATOR_S "enabled.json")
{
}
bool AddonHost::isAddonEnabled(const std::string &id, const nlohmann::json &localConfig) const
{
try {
if (localConfig.contains("settings") && localConfig["settings"].is_object()) {
const nlohmann::json &settings = localConfig["settings"];
if (settings.contains("enabledAddons") && settings["enabledAddons"].is_array()) {
for (const auto &entry : settings["enabledAddons"]) {
if (entry.is_string() && entry.get<std::string>() == id) {
return true;
}
}
return false;
}
}
} catch ( ... ) {
}
return true;
}
void AddonHost::loadFromDirectory(const std::string &dir, bool systemDir)
{
if (!OSUtils::fileExists(dir.c_str(), false)) {
return;
}
const std::vector<std::string> entries(OSUtils::listDirectory(dir.c_str()));
for (std::vector<std::string>::const_iterator i(entries.begin()); i != entries.end(); ++i) {
if (*i == "." || *i == "..") {
continue;
}
const std::string manifestPath = dir + ZT_PATH_SEPARATOR_S + *i + ZT_PATH_SEPARATOR_S "addon.json";
std::string manifestRaw;
if (!OSUtils::readFile(manifestPath.c_str(), manifestRaw)) {
continue;
}
try {
nlohmann::json manifest = OSUtils::jsonParse(manifestRaw);
if (!manifest.contains("id") || !manifest["id"].is_string()) {
continue;
}
AddonEntry entry;
entry.id = manifest["id"].get<std::string>();
entry.name = manifest.contains("name") && manifest["name"].is_string()
? manifest["name"].get<std::string>() : entry.id;
entry.version = manifest.contains("version") && manifest["version"].is_string()
? manifest["version"].get<std::string>() : "0.0.0";
entry.manifest = manifest;
entry.systemPath = dir + ZT_PATH_SEPARATOR_S + *i;
if (systemDir) {
entry.appPath = _homePath + ZT_PATH_SEPARATOR_S "app" + ZT_PATH_SEPARATOR_S "addons"
+ ZT_PATH_SEPARATOR_S + entry.id;
} else {
entry.appPath = entry.systemPath + ZT_PATH_SEPARATOR_S "frontend";
}
for (std::vector<AddonEntry>::iterator j(_addons.begin()); j != _addons.end(); ++j) {
if (j->id == entry.id) {
* j = entry;
goto next_entry;
}
}
_addons.push_back(entry);
} catch ( ... ) {
}
next_entry:;
}
}
void AddonHost::load(const nlohmann::json &localConfig)
{
_addons.clear();
loadFromDirectory(_systemAddonsPath, true);
loadFromDirectory(_homePath + ZT_PATH_SEPARATOR_S "addons.d", false);
std::vector<AddonEntry> filtered;
for (std::vector<AddonEntry>::const_iterator i(_addons.begin()); i != _addons.end(); ++i) {
if (isAddonEnabled(i->id, localConfig)) {
filtered.push_back(*i);
}
}
_addons.swap(filtered);
}
nlohmann::json AddonHost::mergedManifest() const
{
nlohmann::json out = nlohmann::json::array();
for (std::vector<AddonEntry>::const_iterator i(_addons.begin()); i != _addons.end(); ++i) {
if (!i->manifest.contains("frontend") || !i->manifest["frontend"].is_object()) {
continue;
}
nlohmann::json item = nlohmann::json::object();
item["id"] = i->id;
item["name"] = i->name;
item["version"] = i->version;
if (i->manifest.contains("frontend") && i->manifest["frontend"].is_object()) {
item["frontend"] = i->manifest["frontend"];
}
if (i->manifest.contains("requires") && i->manifest["requires"].is_array()) {
item["requires"] = i->manifest["requires"];
}
out.push_back(item);
}
return out;
}
void AddonHost::configureStaticMounts(httplib::Server &s, httplib::Server &sv6)
{
for (std::vector<AddonEntry>::const_iterator i(_addons.begin()); i != _addons.end(); ++i) {
if (!OSUtils::fileExists((i->appPath + ZT_PATH_SEPARATOR_S "index.js").c_str())
&& !OSUtils::fileExists((i->appPath + ZT_PATH_SEPARATOR_S "index.html").c_str())) {
continue;
}
const std::string mountPath = std::string("/app/addons/") + i->id;
s.set_mount_point(mountPath, i->appPath);
sv6.set_mount_point(mountPath, i->appPath);
}
}
void AddonHost::configureRoutes(
httplib::Server &s,
httplib::Server &sv6,
const std::function<void(const httplib::Request&, httplib::Response&, std::string)> &setContent)
{
auto manifestGet = [this, setContent](const httplib::Request &req, httplib::Response &res) {
setContent(req, res, mergedManifest().dump());
};
s.Get("/app/addons/manifest.json", manifestGet);
sv6.Get("/app/addons/manifest.json", manifestGet);
const std::string contactsPath = _homePath + ZT_PATH_SEPARATOR_S "contacts.json";
auto contactsGet = [contactsPath, setContent](const httplib::Request &req, httplib::Response &res) {
std::string raw;
if (!OSUtils::readFile(contactsPath.c_str(), raw)) {
setContent(req, res, "{}");
return;
}
try {
nlohmann::json c = OSUtils::jsonParse(raw);
if (!c.is_object()) {
setContent(req, res, "{}");
return;
}
setContent(req, res, c.dump());
} catch ( ... ) {
setContent(req, res, "{}");
}
};
s.Get("/app/api/contacts", contactsGet);
sv6.Get("/app/api/contacts", contactsGet);
s.Get("/api/backone/contacts", contactsGet);
sv6.Get("/api/backone/contacts", contactsGet);
auto contactsPut = [contactsPath, setContent](const httplib::Request &req, httplib::Response &res) {
try {
nlohmann::json incoming = OSUtils::jsonParse(req.body);
if (!incoming.is_object()) {
res.status = 400;
setContent(req, res, "{\"error\":\"body must be a JSON object\"}");
return;
}
nlohmann::json merged = nlohmann::json::object();
std::string existing;
if (OSUtils::readFile(contactsPath.c_str(), existing)) {
nlohmann::json cur = OSUtils::jsonParse(existing);
if (cur.is_object()) {
merged = cur;
}
}
for (auto it = incoming.begin(); it != incoming.end(); ++it) {
merged[it.key()] = it.value();
}
const std::string out = merged.dump();
if (!OSUtils::writeFile(contactsPath.c_str(), out.data(), (unsigned int)out.length())) {
res.status = 500;
setContent(req, res, "{\"error\":\"could not write contacts\"}");
return;
}
OSUtils::lockDownFile(contactsPath.c_str(), false);
setContent(req, res, merged.dump());
} catch ( ... ) {
res.status = 400;
setContent(req, res, "{\"error\":\"invalid JSON\"}");
}
};
s.Put("/app/api/contacts", contactsPut);
s.Post("/app/api/contacts", contactsPut);
sv6.Put("/app/api/contacts", contactsPut);
sv6.Post("/app/api/contacts", contactsPut);
s.Put("/api/backone/contacts", contactsPut);
s.Post("/api/backone/contacts", contactsPut);
sv6.Put("/api/backone/contacts", contactsPut);
sv6.Post("/api/backone/contacts", contactsPut);
const std::string authTokenPath = _homePath + ZT_PATH_SEPARATOR_S "authtoken.secret";
auto authtokenGet = [authTokenPath, setContent](const httplib::Request &req, httplib::Response &res) {
std::string token;
if (!OSUtils::readFile(authTokenPath.c_str(), token)) {
res.status = 404;
setContent(req, res, "{\"error\":\"authtoken not found\"}");
return;
}
while (!token.empty() && (token.back() == '\n' || token.back() == '\r' || token.back() == ' ' || token.back() == '\t')) {
token.pop_back();
}
size_t start = 0;
while (start < token.size() && (token[start] == ' ' || token[start] == '\t' || token[start] == '\n' || token[start] == '\r')) {
++start;
}
if (start > 0) {
token = token.substr(start);
}
nlohmann::json out = nlohmann::json::object();
out["token"] = token;
out["source"] = "authtoken.secret";
setContent(req, res, out.dump());
};
s.Get("/app/api/authtoken", authtokenGet);
sv6.Get("/app/api/authtoken", authtokenGet);
s.Get("/api/backone/authtoken", authtokenGet);
sv6.Get("/api/backone/authtoken", authtokenGet);
}
bool AddonHost::hasUiAddons(const std::string &homePath)
{
const std::string manifestPath = homePath + ZT_PATH_SEPARATOR_S "app" + ZT_PATH_SEPARATOR_S "addons"
+ ZT_PATH_SEPARATOR_S "manifest.json";
std::string manifestRaw;
if (OSUtils::readFile(manifestPath.c_str(), manifestRaw)) {
try {
nlohmann::json manifest = OSUtils::jsonParse(manifestRaw);
if (manifest.is_array() && !manifest.empty()) {
return true;
}
} catch ( ... ) {
}
}
const std::string addonsDir(OSUtils::platformDefaultSystemAddonsPath());
if (!OSUtils::fileExists(addonsDir.c_str(), false)) {
return false;
}
const std::vector<std::string> entries(OSUtils::listDirectory(addonsDir.c_str()));
for (std::vector<std::string>::const_iterator i(entries.begin()); i != entries.end(); ++i) {
if (*i == "." || *i == "..") {
continue;
}
std::string manifestRaw2;
const std::string path = addonsDir + ZT_PATH_SEPARATOR_S + *i + ZT_PATH_SEPARATOR_S "addon.json";
if (!OSUtils::readFile(path.c_str(), manifestRaw2)) {
continue;
}
try {
nlohmann::json m = OSUtils::jsonParse(manifestRaw2);
if (m.contains("frontend") && m["frontend"].is_object()) {
return true;
}
} catch ( ... ) {
}
}
return false;
}
} // namespace ZeroTier