125 lines
4.2 KiB
C++
125 lines
4.2 KiB
C++
// Copyright 2021-2023 The Mumble Developers. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style license
|
|
// that can be found in the LICENSE file at the root of the
|
|
// Mumble source tree or at <https://www.mumble.info/LICENSE>.
|
|
|
|
#include "PluginManifest.h"
|
|
|
|
#include <regex>
|
|
|
|
#include <Poco/DOM/DOMParser.h>
|
|
#include <Poco/DOM/Document.h>
|
|
#include <Poco/DOM/Element.h>
|
|
#include <Poco/DOM/NodeList.h>
|
|
#include <Poco/SAX/InputSource.h>
|
|
#include <Poco/SAX/SAXException.h>
|
|
|
|
void PluginManifest::parse(std::istream &input) {
|
|
Poco::XML::InputSource source(input);
|
|
|
|
Poco::XML::DOMParser parser;
|
|
Poco::AutoPtr< Poco::XML::Document > doc;
|
|
try {
|
|
doc = parser.parse(&source);
|
|
} catch (const Poco::XML::SAXParseException &) {
|
|
throw PluginManifestException("Plugin manifest uses malformed XML");
|
|
}
|
|
|
|
Poco::XML::Element *rootElement = doc->documentElement();
|
|
|
|
if (rootElement->nodeName() != "bundle") {
|
|
throw PluginManifestException("Plugin manifest is missing the root \"bundle\" element");
|
|
}
|
|
if (!rootElement->hasAttribute("version")) {
|
|
throw PluginManifestException("Plugin manifest specifies \"bundle\" element without the \"version\" attribute");
|
|
}
|
|
|
|
std::string formatVersion = rootElement->getAttribute("version");
|
|
if (formatVersion == "1.0.0") {
|
|
parse_v1_0_0(doc);
|
|
} else {
|
|
throw PluginManifestException(std::string("Plugin manifest uses unknown format version \"") + formatVersion
|
|
+ "\"");
|
|
}
|
|
}
|
|
|
|
bool PluginManifest::specifiesPluginPath(const std::string &os, const std::string &arch) const {
|
|
return m_pluginMap.find({ os, arch }) != m_pluginMap.end();
|
|
}
|
|
|
|
std::string PluginManifest::getPluginPath(const std::string &os, const std::string &arch) const {
|
|
auto it = m_pluginMap.find({ os, arch });
|
|
|
|
if (it == m_pluginMap.end()) {
|
|
throw PluginManifestException("PluginManifest: Invalid access to getPluginPath");
|
|
}
|
|
|
|
return it->second;
|
|
}
|
|
|
|
const std::unordered_map< PluginRuntimeSpec, std::string > &PluginManifest::getSpecifiedPluginPaths() const {
|
|
return m_pluginMap;
|
|
}
|
|
|
|
void PluginManifest::parse_v1_0_0(Poco::AutoPtr< Poco::XML::Document > document) {
|
|
Poco::XML::Element *assets = document->documentElement()->getChildElement("assets");
|
|
|
|
if (!assets) {
|
|
throw PluginManifestException("Plugin manifest does not contain the \"assets\" element");
|
|
}
|
|
Poco::AutoPtr< Poco::XML::NodeList > pluginNodes = assets->getElementsByTagName("plugin");
|
|
|
|
for (std::size_t i = 0; i < pluginNodes->length(); ++i) {
|
|
Poco::XML::Element *current =
|
|
dynamic_cast< Poco::XML::Element * >(pluginNodes->item(static_cast< unsigned long >(i)));
|
|
if (!current) {
|
|
throw PluginManifestException("Plugin manifest uses \"plugin\" node of unexpected type");
|
|
}
|
|
|
|
std::string binaryPath = current->innerText();
|
|
std::string os = current->getAttribute("os");
|
|
std::string arch = current->getAttribute("arch");
|
|
|
|
if (binaryPath.empty()) {
|
|
throw PluginManifestException("Plugin manifest declares \"plugin\" element with empty path");
|
|
}
|
|
if (os.empty()) {
|
|
throw PluginManifestException("Plugin manifest declares \"plugin\" element without \"os\" attribute");
|
|
}
|
|
if (arch.empty()) {
|
|
throw PluginManifestException("Plugin manifest declares \"plugin\" element without \"arch\" attribute");
|
|
}
|
|
|
|
PluginRuntimeSpec spec = { std::move(os), std::move(arch) };
|
|
|
|
m_pluginMap.insert({ std::move(spec), std::move(binaryPath) });
|
|
}
|
|
|
|
Poco::XML::Element *nameElement = document->documentElement()->getChildElement("name");
|
|
if (!nameElement) {
|
|
throw PluginManifestException("Plugin manifest does not specify the \"name\" element");
|
|
}
|
|
|
|
Poco::XML::Element *versionElement = document->documentElement()->getChildElement("version");
|
|
if (!versionElement) {
|
|
throw PluginManifestException("Plugin manifest does not specify the \"version\" element");
|
|
}
|
|
|
|
m_name = nameElement->innerText();
|
|
m_version = versionElement->innerText();
|
|
|
|
if (m_name.empty()) {
|
|
throw PluginManifestException("Plugin manifest specifies empty name");
|
|
}
|
|
if (m_version.empty()) {
|
|
throw PluginManifestException("Plugin manifest specifies empty version");
|
|
}
|
|
|
|
std::regex versionRegex("\\d+\\.\\d+\\.\\d+");
|
|
|
|
if (!std::regex_match(m_version, versionRegex)) {
|
|
throw PluginManifestException(
|
|
"Plugin manifest specifies version that does not follow the format major.minor.path");
|
|
}
|
|
}
|