Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/mumble-voip/mumble.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRobert Adam <dev@robert-adam.de>2021-06-13 21:13:03 +0300
committerRobert Adam <dev@robert-adam.de>2021-06-15 10:22:20 +0300
commit1ec6507044ec73ac6f007d654941a92ff24770b0 (patch)
tree51fcdbe272fbd7d1266545addda7b380dbe3d271
parentfd8e6dbe5d930317b5778abd9010b8c3a15cc983 (diff)
FIX(client): Ambiguity in plugin installer
Previously the plugin installer attempted to select the correct plugin binary by its file extension but it turns out that this is not a unique choice (e.g. .so is supported on macOS and on Linux). Therefore this commit introduces a mandatory manifest file that is to be present in plugin bundles that contains the mapping for which binary to use on which OS and for which architecture. Because a plugin bundle now has to follow such a strict format, it is now also mandatory for the bundle to have the file extension .mumble_plugin (previously .zip was allowed as well).
-rw-r--r--CMakeLists.txt28
-rw-r--r--cmake/TargetArch.cmake147
-rw-r--r--docs/dev/plugins/Bundling.md102
-rw-r--r--src/mumble/CMakeLists.txt2
-rw-r--r--src/mumble/PluginInstaller.cpp59
-rw-r--r--src/mumble/PluginManifest.cpp122
-rw-r--r--src/mumble/PluginManifest.h67
-rw-r--r--src/mumble_exe/CMakeLists.txt3
-rw-r--r--src/murmur/CMakeLists.txt2
9 files changed, 465 insertions, 67 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 7878a1612..88886237a 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -47,6 +47,7 @@ list(APPEND CMAKE_MODULE_PATH
include(pkg-utils)
include(project-utils)
+include(TargetArch)
option(tests "Build tests" OFF)
@@ -74,16 +75,13 @@ endif()
include(compiler)
include(os)
-if (64_BIT)
- set(ARCH "x64")
-else()
- set(ARCH "x86")
-endif()
+target_architecture(MUMBLE_TARGET_ARCH)
+string(TOLOWER "${MUMBLE_TARGET_ARCH}" MUMBLE_TARGET_ARCH)
message(STATUS "##################################################")
message(STATUS "Mumble release ID: ${RELEASE_ID}")
message(STATUS "Mumble version: ${PROJECT_VERSION}")
-message(STATUS "Architecture: ${ARCH}")
+message(STATUS "Architecture: ${MUMBLE_TARGET_ARCH}")
if(NOT IS_MULTI_CONFIG)
message(STATUS "Build type: ${CMAKE_BUILD_TYPE}")
else()
@@ -109,6 +107,16 @@ if(tests)
include(CTest)
endif()
+if (WIN32)
+ set(MUMBLE_TARGET_OS "windows")
+elseif (APPLE)
+ set(MUMBLE_TARGET_OS "macos")
+elseif (UNIX)
+ set(MUMBLE_TARGET_OS "linux")
+else()
+ message(FATAL_ERROR "Unable to determine target OS")
+endif()
+
# Make the build year accessible as a macro
add_compile_definitions(MUMBLE_BUILD_YEAR=${MUMBLE_BUILD_YEAR})
@@ -116,6 +124,14 @@ add_compile_definitions(MUMBLE_BUILD_YEAR=${MUMBLE_BUILD_YEAR})
# Make sure that math constants are always defined
add_compile_definitions(_USE_MATH_DEFINES)
+
+# Provide the information about the target architecture to all Mumble source files in form of a macro
+add_compile_definitions(MUMBLE_TARGET_ARCH="${MUMBLE_TARGET_ARCH}")
+
+# Also provide information about the target OS
+add_compile_definitions(MUMBLE_TARGET_OS="${MUMBLE_TARGET_OS}")
+
+
set(CMAKE_UNITY_BUILD_BATCH_SIZE 40)
add_subdirectory(src)
diff --git a/cmake/TargetArch.cmake b/cmake/TargetArch.cmake
new file mode 100644
index 000000000..b90765513
--- /dev/null
+++ b/cmake/TargetArch.cmake
@@ -0,0 +1,147 @@
+# Taken from https://github.com/axr/solar-cmake/blob/master/TargetArch.cmake
+
+# Copyright (c) 2012 Petroules Corporation. All rights reserved.
+# Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
+# - Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
+# - Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+
+# Based on the Qt 5 processor detection code, so should be very accurate
+# https://qt.gitorious.org/qt/qtbase/blobs/master/src/corelib/global/qprocessordetection.h
+# Currently handles arm (v5, v6, v7), x86 (32/64), ia64, and ppc (32/64)
+
+# Regarding POWER/PowerPC, just as is noted in the Qt source,
+# "There are many more known variants/revisions that we do not handle/detect."
+
+set(archdetect_c_code "
+#if defined(__arm__) || defined(__TARGET_ARCH_ARM)
+ #if defined(__ARM_ARCH_7__) \\
+ || defined(__ARM_ARCH_7A__) \\
+ || defined(__ARM_ARCH_7R__) \\
+ || defined(__ARM_ARCH_7M__) \\
+ || (defined(__TARGET_ARCH_ARM) && __TARGET_ARCH_ARM-0 >= 7)
+ #error cmake_ARCH armv7
+ #elif defined(__ARM_ARCH_6__) \\
+ || defined(__ARM_ARCH_6J__) \\
+ || defined(__ARM_ARCH_6T2__) \\
+ || defined(__ARM_ARCH_6Z__) \\
+ || defined(__ARM_ARCH_6K__) \\
+ || defined(__ARM_ARCH_6ZK__) \\
+ || defined(__ARM_ARCH_6M__) \\
+ || (defined(__TARGET_ARCH_ARM) && __TARGET_ARCH_ARM-0 >= 6)
+ #error cmake_ARCH armv6
+ #elif defined(__ARM_ARCH_5TEJ__) \\
+ || (defined(__TARGET_ARCH_ARM) && __TARGET_ARCH_ARM-0 >= 5)
+ #error cmake_ARCH armv5
+ #else
+ #error cmake_ARCH arm
+ #endif
+#elif defined(__i386) || defined(__i386__) || defined(_M_IX86)
+ #error cmake_ARCH x86
+#elif defined(__x86_64) || defined(__x86_64__) || defined(__amd64) || defined(_M_X64)
+ #error cmake_ARCH x64
+#elif defined(__ia64) || defined(__ia64__) || defined(_M_IA64)
+ #error cmake_ARCH ia64
+#elif defined(__ppc__) || defined(__ppc) || defined(__powerpc__) \\
+ || defined(_ARCH_COM) || defined(_ARCH_PWR) || defined(_ARCH_PPC) \\
+ || defined(_M_MPPC) || defined(_M_PPC)
+ #if defined(__ppc64__) || defined(__powerpc64__) || defined(__64BIT__)
+ #error cmake_ARCH ppc64
+ #else
+ #error cmake_ARCH ppc
+ #endif
+#endif
+#error cmake_ARCH unknown
+")
+
+# Set ppc_support to TRUE before including this file or ppc and ppc64
+# will be treated as invalid architectures since they are no longer supported by Apple
+
+function(target_architecture output_var)
+ if(APPLE AND CMAKE_OSX_ARCHITECTURES)
+ # On OS X we use CMAKE_OSX_ARCHITECTURES *if* it was set
+ # First let's normalize the order of the values
+
+ # Note that it's not possible to compile PowerPC applications if you are using
+ # the OS X SDK version 10.6 or later - you'll need 10.4/10.5 for that, so we
+ # disable it by default
+ # See this page for more information:
+ # http://stackoverflow.com/questions/5333490/how-can-we-restore-ppc-ppc64-as-well-as-full-10-4-10-5-sdk-support-to-xcode-4
+
+ # Architecture defaults to i386 or ppc on OS X 10.5 and earlier, depending on the CPU type detected at runtime.
+ # On OS X 10.6+ the default is x86_64 if the CPU supports it, i386 otherwise.
+
+ foreach(osx_arch ${CMAKE_OSX_ARCHITECTURES})
+ if("${osx_arch}" STREQUAL "ppc" AND ppc_support)
+ set(osx_arch_ppc TRUE)
+ elseif("${osx_arch}" STREQUAL "i386")
+ set(osx_arch_i386 TRUE)
+ elseif("${osx_arch}" STREQUAL "x86_64")
+ set(osx_arch_x86_64 TRUE)
+ elseif("${osx_arch}" STREQUAL "ppc64" AND ppc_support)
+ set(osx_arch_ppc64 TRUE)
+ else()
+ message(FATAL_ERROR "Invalid OS X arch name: ${osx_arch}")
+ endif()
+ endforeach()
+
+ # Now add all the architectures in our normalized order
+ if(osx_arch_ppc)
+ list(APPEND ARCH ppc)
+ endif()
+
+ if(osx_arch_i386)
+ list(APPEND ARCH i386)
+ endif()
+
+ if(osx_arch_x86_64)
+ list(APPEND ARCH x86_64)
+ endif()
+
+ if(osx_arch_ppc64)
+ list(APPEND ARCH ppc64)
+ endif()
+ else()
+ file(WRITE "${CMAKE_BINARY_DIR}/arch.c" "${archdetect_c_code}")
+
+ enable_language(C)
+
+ # Detect the architecture in a rather creative way...
+ # This compiles a small C program which is a series of ifdefs that selects a
+ # particular #error preprocessor directive whose message string contains the
+ # target architecture. The program will always fail to compile (both because
+ # file is not a valid C program, and obviously because of the presence of the
+ # #error preprocessor directives... but by exploiting the preprocessor in this
+ # way, we can detect the correct target architecture even when cross-compiling,
+ # since the program itself never needs to be run (only the compiler/preprocessor)
+ try_run(
+ run_result_unused
+ compile_result_unused
+ "${CMAKE_BINARY_DIR}"
+ "${CMAKE_BINARY_DIR}/arch.c"
+ COMPILE_OUTPUT_VARIABLE ARCH
+ CMAKE_FLAGS CMAKE_OSX_ARCHITECTURES=${CMAKE_OSX_ARCHITECTURES}
+ )
+
+ # Parse the architecture name from the compiler output
+ string(REGEX MATCH "cmake_ARCH ([a-zA-Z0-9_]+)" ARCH "${ARCH}")
+
+ # Get rid of the value marker leaving just the architecture name
+ string(REPLACE "cmake_ARCH " "" ARCH "${ARCH}")
+
+ # If we are compiling with an unknown architecture this variable should
+ # already be set to "unknown" but in the case that it's empty (i.e. due
+ # to a typo in the code), then set it to unknown
+ if (NOT ARCH)
+ set(ARCH "unknown")
+ endif()
+ endif()
+
+ set(${output_var} "${ARCH}" PARENT_SCOPE)
+endfunction()
diff --git a/docs/dev/plugins/Bundling.md b/docs/dev/plugins/Bundling.md
index 6bbc9d471..ef53b2a5e 100644
--- a/docs/dev/plugins/Bundling.md
+++ b/docs/dev/plugins/Bundling.md
@@ -5,46 +5,84 @@ library and tell them to install them, it might get inconvenient at times. Espec
Therefore Mumble provides a way to package your plugin in a user-friendly and cross-platform way.
-It works by putting the shared libraries for the different platforms into a single zip-file and then changing the archive's file extension from `.zip`
-to `.mumble_plugin`.
+Mumble plugin bundles are essentially zip-files with a special contents structure and the file extension `.mumble_plugin` instead of `.zip`. Such a
+bundle must contain a `manifest.xml` as a top-level entry in the archive. The manifest file is used to specify which plugin library (also bundled
+within the archive) is to be used for which OS and architecture.
-The archive must not contain anything besides the plugin libraries and these should follow the following naming conventions:
-- The shared library should contain the name of the plugin
-- followed by the OS specifier
-- followed by the architecture specifier
+## The manifest file format
-The OS specifier may be one of the following
-| **OS** | **Specifier** |
+The manifest file _always_ has to be named `manifest.xml` (case-sensitive!) and it must use the `bundle` tag as the top-level element of the XML
+structure. Furthermore the `bundle` opening tag needs to specify the `format` attribute which will determine the rules the rest of the XML tree has to
+follow.
+
+Unless otherwise noted the entire manifest file is case-sensitive (including paths specified in it)!
+
+### v1.0.0
+
+| Property | Value |
+| -------- | ----- |
+| `version` attribute | `1.0.0` |
+| Available since: | Mumble v1.4.0 |
+
+Inside the `bundle` element there have to be the `assets`, `name` and `version` elements. `name` is just the name of your plugin (arbitrary String)
+whereas `version` is the version of your plugin. Note that the version must be given in the format `<major>.<minor>.<patch>`.
+
+Inside the `assets` element comes a list of one or more `plugin` elements. Each of these must specify the `os` and `arch` attributes for specifying
+the operating system and architecture respectively. The contents of these elements is the path to the corresponding plugin library inside the archive
+(relative to the `manifest.xml` file). Note that the paths _must not_ start with `./` and as path separator a single forward slash is to be used (even
+on Windows).
+
+The possible values for the `os` attribute are
+| **OS** | **Attribute** |
| ------ | ------------- |
-| Windows | `_win` |
-| Linux | `_linux` |
-| macOS | `_macos` |
+| Windows | `windows` |
+| Linux | `linux` |
+| macOS | `macos` |
-and the architecture specifier is one of
-| **Architecture** | **Specifier** |
+and the possible values for the `arch` attribute are
+| **Architecture** | **Attribute** |
| ---------------- | ------------- |
-| x86 (64-bit) | `_x86_64` |
-| x86 (32-bit) | `_i386` |
-| ARM (v5) | `_armv5` |
-| ARM (v6) | `_armv6` |
-| ARM (v7) | `_armv7` |
+| x86 (32-bit) | `x86` |
+| x86 (64-bit) | `x64`|
-Therefore a plugin library may be named for instance
-```
-myPlugin_linux_x86_64.so
-```
-or
-```
-myPlugin_win_i386.dll
-```
+Putting this together a sample `manifest.xml` can look like this:
-Platform-specific prefixes such as `lib` on Unix-systems are allowed as well. Therefore
+```xml
+<?xml version="1.0" encoding="UTF-8"?>
+<bundle version="1.0.0">
+ <assets>
+ <plugin os="windows" arch="x86">myPlugin.dll</plugin>
+ <plugin os="windows" arch="x64">sub/myPlugin.dll</plugin>
+ <plugin os="linux" arch="x64">sub/libmyPlugin.so</plugin>
+ </assets>
+ <name>MyPlugin</name>
+ <version>1.0.0</version>
+</bundle>
```
-libmyPlugin_linux_x86_64.so
+
+## Creating a plugin bundle
+
+As written before, a plugin bundle essentially is just a zip file. The only restriction here is that it must not be created using the proprietary
+`Deflate64` algorithm.
+
+The steps to create a bundle file are as follows
+1. Create an empty working directory
+2. Copy the plugin libraries for the different OS and architectures into this directory (potentially in sub-directories)
+3. Create a `manifest.xml` at the root of the working directory
+4. Add all files and folders in your working directory (but not the working directory itself!) to a zip file
+5. Change the file extension from `.zip` to `.mumble_plugin`
+
+On Linux you can do this by using the `zip` command line tool like this:
+```bash
+zip my_plugin.mumble_plugin myPlugin.dll sub/myPlugin.dll sub/libmyPlugin.so manifest.xml
```
-is completely equivalent to the example given above.
-Note that plugin names **must not** contain version numbers. At least not when they are distributed to end users. This is because Mumble assumes that
-the new version of a plugin will use a shared library with the exact same name as the old one. If this assumption is violated, the update mechanism
-will not work properly causing the new and the old version of the plugin to be installed in parallel.
+## Notes
+
+- You **must not** add any additional files to this archive. Keep the contents of the created archive strictly to the parts mentioned above. It
+ might be tempting to treat the plugin bundle as a regular zip file that can be filled with arbitrary contents but this is not allowed! Instead
+ consider the plugin bundle a binary of its own that has to follow a strict format as outlined above.
+- Plugin names **must not** contain version numbers. At least not when they are distributed to end users. This is because Mumble assumes that
+ the new version of a plugin will use a shared library with the exact same name as the old one. If this assumption is violated, the update mechanism
+ will not work properly causing the new and the old version of the plugin to be installed in parallel.
diff --git a/src/mumble/CMakeLists.txt b/src/mumble/CMakeLists.txt
index 8432e7ee1..a561449d3 100644
--- a/src/mumble/CMakeLists.txt
+++ b/src/mumble/CMakeLists.txt
@@ -180,6 +180,8 @@ set(MUMBLE_SOURCES
"PluginInstaller.ui"
"PluginManager.cpp"
"PluginManager.h"
+ "PluginManifest.cpp"
+ "PluginManifest.h"
"PluginUpdater.cpp"
"PluginUpdater.h"
"PluginUpdater.ui"
diff --git a/src/mumble/PluginInstaller.cpp b/src/mumble/PluginInstaller.cpp
index 6e0cdebdb..b08bf2030 100644
--- a/src/mumble/PluginInstaller.cpp
+++ b/src/mumble/PluginInstaller.cpp
@@ -4,6 +4,7 @@
// Mumble source tree or at <https://www.mumble.info/LICENSE>.
#include "PluginInstaller.h"
+#include "PluginManifest.h"
#include "Global.h"
#include <QtCore/QDir>
@@ -39,9 +40,8 @@ bool PluginInstaller::canBePluginFile(const QFileInfo &fileInfo) noexcept {
return false;
}
- if (fileInfo.suffix().compare(PluginInstaller::pluginFileExtension, Qt::CaseInsensitive) == 0
- || fileInfo.suffix().compare(QLatin1String("zip"), Qt::CaseInsensitive) == 0) {
- // A plugin file has either the extension given in PluginInstaller::pluginFileExtension or zip
+ if (fileInfo.suffix().compare(PluginInstaller::pluginFileExtension, Qt::CaseInsensitive) == 0) {
+ // A plugin file has the extension given in PluginInstaller::pluginFileExtension
return true;
}
@@ -83,38 +83,43 @@ void PluginInstaller::init() {
try {
Poco::FileInputStream zipInput(m_pluginArchive.filePath().toStdString());
Poco::Zip::ZipArchive archive(zipInput);
+ auto manifestIt = archive.findHeader("manifest.xml");
- // Iterate over all files in the archive to see which ones could be the correct plugin library
- QString pluginName;
- auto it = archive.fileInfoBegin();
- while (it != archive.fileInfoEnd()) {
- QString currentFileName = QString::fromStdString(it->first);
-
- if (QLibrary::isLibrary(currentFileName)) {
- if (!pluginName.isEmpty()) {
- // There seem to be multiple plugins in here. That's not allowed
- throw PluginInstallException(
- tr("Found more than one plugin library for the current OS in \"%1\" (\"%2\" and \"%3\")!")
- .arg(m_pluginArchive.fileName())
- .arg(pluginName)
- .arg(currentFileName));
- }
-
- pluginName = currentFileName;
- }
-
- it++;
+ if (manifestIt == archive.headerEnd()) {
+ throw PluginInstallException(tr("Unable to locate the plugin manifest (manifest.xml)"));
}
- if (pluginName.isEmpty()) {
+ zipInput.clear();
+ Poco::Zip::ZipInputStream manifestStream(zipInput, manifestIt->second);
+
+ PluginManifest manifest;
+ try {
+ manifest.parse(manifestStream);
+ } catch (const PluginManifestException &e) {
+ throw PluginInstallException(
+ tr("Error while processing manifest: %1").arg(QString::fromUtf8(e.what())));
+ }
+
+ if (!manifest.specifiesPluginPath(MUMBLE_TARGET_OS, MUMBLE_TARGET_ARCH)) {
throw PluginInstallException(
- tr("Unable to find a plugin for the current OS in \"%1\"").arg(m_pluginArchive.fileName()));
+ tr("Unable to find plugin for the current OS (\"%1\") and architecture (\"%2\")")
+ .arg(QString::fromUtf8(MUMBLE_TARGET_OS))
+ .arg(QString::fromUtf8(MUMBLE_TARGET_ARCH)));
}
+ std::string pluginPath = manifest.getPluginPath(MUMBLE_TARGET_OS, MUMBLE_TARGET_ARCH);
+
// Unpack the plugin library into the tmp dir
// We don't have to create the directory structure as we're only interested in the library itself
- QString tmpPluginPath = QDir::temp().filePath(QFileInfo(pluginName).fileName());
- auto pluginIt = archive.findHeader(pluginName.toStdString());
+ QString tmpPluginPath = QDir::temp().filePath(QFileInfo(QString::fromStdString(pluginPath)).fileName());
+ auto pluginIt = archive.findHeader(pluginPath);
+
+ if (pluginIt == archive.headerEnd()) {
+ throw PluginInstallException(
+ tr("Unable to locate plugin library specified in manifest (\"%1\") in the bundle")
+ .arg(QString::fromStdString(pluginPath)));
+ }
+
zipInput.clear();
Poco::Zip::ZipInputStream zipin(zipInput, pluginIt->second);
std::ofstream out(tmpPluginPath.toStdString(), std::ios::out | std::ios::binary);
diff --git a/src/mumble/PluginManifest.cpp b/src/mumble/PluginManifest.cpp
new file mode 100644
index 000000000..7aaa85a69
--- /dev/null
+++ b/src/mumble/PluginManifest.cpp
@@ -0,0 +1,122 @@
+// Copyright 2021 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/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 &e) {
+ 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(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 specifes version that does not follow the format major.minor.path");
+ }
+}
diff --git a/src/mumble/PluginManifest.h b/src/mumble/PluginManifest.h
new file mode 100644
index 000000000..2d541846c
--- /dev/null
+++ b/src/mumble/PluginManifest.h
@@ -0,0 +1,67 @@
+// Copyright 2021 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>.
+
+#ifndef MUMBLE_MUMBLE_PLUGINMANIFEST_H_
+#define MUMBLE_MUMBLE_PLUGINMANIFEST_H_
+
+#include <istream>
+#include <stdexcept>
+#include <string>
+#include <unordered_map>
+
+#include <Poco/DOM/AutoPtr.h>
+
+namespace Poco {
+namespace XML {
+ class Document;
+}; // namespace XML
+}; // namespace Poco
+
+struct PluginManifestException : std::runtime_error {
+ PluginManifestException(const std::string &msg = "") : std::runtime_error(msg) {}
+};
+
+struct PluginRuntimeSpec {
+ std::string os;
+ std::string architecture;
+
+ bool operator==(const PluginRuntimeSpec &other) const {
+ return os == other.os && architecture == other.architecture;
+ }
+};
+
+// Make PluginRuntimeSpec hashable
+namespace std {
+template<> struct hash< PluginRuntimeSpec > {
+ std::size_t operator()(const PluginRuntimeSpec &spec) const {
+ return std::hash< std::string >()(spec.os) ^ (std::hash< std::string >()(spec.architecture) << 1);
+ }
+};
+}; // namespace std
+
+class PluginManifest {
+public:
+ PluginManifest() = default;
+
+ void parse(std::istream &input);
+
+ bool specifiesPluginPath(const std::string &os, const std::string &arch) const;
+ std::string getPluginPath(const std::string &os, const std::string &arch) const;
+
+ const std::unordered_map< PluginRuntimeSpec, std::string > &getSpecifiedPluginPaths() const;
+
+ const std::string &getName() const;
+
+ const std::string &getVersion() const;
+
+protected:
+ std::string m_version;
+ std::string m_name;
+ std::unordered_map< PluginRuntimeSpec, std::string > m_pluginMap;
+
+ void parse_v1_0_0(Poco::AutoPtr< Poco::XML::Document > document);
+};
+
+#endif // MUMBLE_MUMBLE_PLUGINMANIFEST_H_
diff --git a/src/mumble_exe/CMakeLists.txt b/src/mumble_exe/CMakeLists.txt
index ece074a13..b46c31ede 100644
--- a/src/mumble_exe/CMakeLists.txt
+++ b/src/mumble_exe/CMakeLists.txt
@@ -54,9 +54,10 @@ if(packaging)
list(APPEND installer_vars "--all-languages")
endif()
+
list(APPEND installer_vars
"--version" ${PROJECT_VERSION}
- "--arch" ${ARCH}
+ "--arch" "${MUMBLE_TARGET_ARCH}"
)
if(overlay)
diff --git a/src/murmur/CMakeLists.txt b/src/murmur/CMakeLists.txt
index 48a7c686d..806b173fb 100644
--- a/src/murmur/CMakeLists.txt
+++ b/src/murmur/CMakeLists.txt
@@ -345,7 +345,7 @@ if(packaging)
list(APPEND installer_vars
"--version" ${PROJECT_VERSION}
- "--arch" ${ARCH}
+ "--arch" ${MUMBLE_TARGET_ARCH}
)
file(COPY