diff options
author | Robert Adam <dev@robert-adam.de> | 2021-06-13 21:13:03 +0300 |
---|---|---|
committer | Robert Adam <dev@robert-adam.de> | 2021-06-15 10:22:20 +0300 |
commit | 1ec6507044ec73ac6f007d654941a92ff24770b0 (patch) | |
tree | 51fcdbe272fbd7d1266545addda7b380dbe3d271 | |
parent | fd8e6dbe5d930317b5778abd9010b8c3a15cc983 (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.txt | 28 | ||||
-rw-r--r-- | cmake/TargetArch.cmake | 147 | ||||
-rw-r--r-- | docs/dev/plugins/Bundling.md | 102 | ||||
-rw-r--r-- | src/mumble/CMakeLists.txt | 2 | ||||
-rw-r--r-- | src/mumble/PluginInstaller.cpp | 59 | ||||
-rw-r--r-- | src/mumble/PluginManifest.cpp | 122 | ||||
-rw-r--r-- | src/mumble/PluginManifest.h | 67 | ||||
-rw-r--r-- | src/mumble_exe/CMakeLists.txt | 3 | ||||
-rw-r--r-- | src/murmur/CMakeLists.txt | 2 |
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 |