--- /dev/null
+From 61df54155a3cb1846e6bf15e4f007ec8d623de63 Mon Sep 17 00:00:00 2001
+From: Jean-Francois Dockes <jf@dockes.org>
+Date: Sun, 23 Aug 2020 14:22:21 +0200
+Subject: [PATCH] Modification to use npupnp instead of pupnp when the upnp
+ meson option is set
+
+---
+ meson_options.txt | 5 +-
+ .../plugins/upnp/ContentDirectoryService.cxx | 101 ++++++++++++++++++
+ src/lib/upnp/Action.hxx | 2 +
+ src/lib/upnp/ClientInit.cxx | 12 +--
+ src/lib/upnp/Compat.hxx | 4 +-
+ src/lib/upnp/ContentDirectoryService.cxx | 25 +++++
+ src/lib/upnp/Init.cxx | 4 +
+ src/lib/upnp/UniqueIxml.hxx | 2 +
+ src/lib/upnp/ixmlwrap.cxx | 4 +
+ src/lib/upnp/ixmlwrap.hxx | 2 +
+ src/lib/upnp/meson.build | 20 +++-
+ 11 files changed, 170 insertions(+), 11 deletions(-)
+
+diff --git a/meson_options.txt b/meson_options.txt
+index d17ac1ca8..da90ccfd8 100644
+--- a/meson_options.txt
++++ b/meson_options.txt
+@@ -54,7 +54,10 @@ option('dsd', type: 'boolean', value: true, description: 'Support the DSD audio
+ #
+
+ option('database', type: 'boolean', value: true, description: 'enable support for the music database')
+-option('upnp', type: 'feature', description: 'UPnP client support')
++option('upnp', type: 'combo',
++ choices: ['auto', 'pupnp', 'npupnp', 'disabled'],
++ value: 'auto',
++ description: 'UPnP client support')
+ option('libmpdclient', type: 'feature', description: 'libmpdclient support (for the proxy database plugin)')
+
+ #
+diff --git a/src/db/plugins/upnp/ContentDirectoryService.cxx b/src/db/plugins/upnp/ContentDirectoryService.cxx
+index 99893d89d..29d58ca23 100644
+--- a/src/db/plugins/upnp/ContentDirectoryService.cxx
++++ b/src/db/plugins/upnp/ContentDirectoryService.cxx
+@@ -18,7 +18,10 @@
+ */
+
+ #include "lib/upnp/ContentDirectoryService.hxx"
++#include "config.h"
++#ifdef USING_PUPNP
+ #include "lib/upnp/ixmlwrap.hxx"
++#endif
+ #include "lib/upnp/UniqueIxml.hxx"
+ #include "lib/upnp/Action.hxx"
+ #include "Directory.hxx"
+@@ -28,8 +31,11 @@
+ #include "util/ScopeExit.hxx"
+ #include "util/StringFormat.hxx"
+
++#include <algorithm>
++
+ #include <stdio.h>
+
++#ifdef USING_PUPNP
+ static void
+ ReadResultTag(UPnPDirContent &dirbuf, IXML_Document *response)
+ {
+@@ -39,6 +45,7 @@ ReadResultTag(UPnPDirContent &dirbuf, IXML_Document *response)
+
+ dirbuf.Parse(p);
+ }
++#endif
+
+ inline void
+ ContentDirectoryService::readDirSlice(UpnpClient_Handle hdl,
+@@ -47,6 +54,7 @@ ContentDirectoryService::readDirSlice(UpnpClient_Handle hdl,
+ unsigned &didreadp,
+ unsigned &totalp) const
+ {
++#ifdef USING_PUPNP
+ // Some devices require an empty SortCriteria, else bad params
+ IXML_Document *request =
+ MakeActionHelper("Browse", m_serviceType.c_str(),
+@@ -82,6 +90,37 @@ ContentDirectoryService::readDirSlice(UpnpClient_Handle hdl,
+ totalp = ParseUnsigned(value);
+
+ ReadResultTag(dirbuf, response);
++#else
++ std::vector<std::pair<std::string, std::string> > actionParams{
++ { "ObjectID", objectId },
++ { "BrowseFlag", "BrowseDirectChildren" },
++ { "Filter", "*" },
++ { "SortCriteria", "" },
++ { "StartingIndex", StringFormat<32>("%u", offset).c_str() },
++ { "RequestedCount", StringFormat<32>("%u", count).c_str() }
++ };
++ std::vector<std::pair<std::string, std::string> > responseData;
++ int errcode;
++ std::string errdesc;
++ int code =
++ UpnpSendAction(hdl, "", m_actionURL, m_serviceType, "Browse",
++ actionParams, responseData, &errcode, errdesc);
++ if (code != UPNP_E_SUCCESS)
++ throw FormatRuntimeError("UpnpSendAction() failed: %s",
++ UpnpGetErrorMessage(code));
++ const char *p = "";
++ didreadp = 0;
++ for (const auto &entry : responseData) {
++ if (entry.first == "Result") {
++ p = entry.second.c_str();
++ } else if (entry.first == "TotalMatches") {
++ totalp = ParseUnsigned(entry.second.c_str());
++ } else if (entry.first == "NumberReturned") {
++ didreadp = ParseUnsigned(entry.second.c_str());
++ }
++ }
++ dirbuf.Parse(p);
++#endif
+ }
+
+ UPnPDirContent
+@@ -110,6 +149,7 @@ ContentDirectoryService::search(UpnpClient_Handle hdl,
+ unsigned offset = 0, total = -1, count;
+
+ do {
++#ifdef USING_PUPNP
+ UniqueIxmlDocument request(MakeActionHelper("Search", m_serviceType.c_str(),
+ "ContainerID", objectId,
+ "SearchCriteria", ss,
+@@ -147,6 +187,39 @@ ContentDirectoryService::search(UpnpClient_Handle hdl,
+ total = ParseUnsigned(value);
+
+ ReadResultTag(dirbuf, response.get());
++#else
++ std::vector<std::pair<std::string, std::string> > actionParams{
++ { "ContainerID", objectId },
++ { "SearchCriteria", ss },
++ { "Filter", "*" },
++ { "SortCriteria", "" },
++ { "StartingIndex",
++ StringFormat<32>("%u", offset).c_str() },
++ { "RequestedCount", "0" }
++ };
++ std::vector<std::pair<std::string, std::string> > responseData;
++ int errcode;
++ std::string errdesc;
++ int code = UpnpSendAction(hdl, "", m_actionURL, m_serviceType,
++ "Search", actionParams, responseData,
++ &errcode, errdesc);
++ if (code != UPNP_E_SUCCESS)
++ throw FormatRuntimeError("UpnpSendAction() failed: %s",
++ UpnpGetErrorMessage(code));
++ const char *p = "";
++ count = 0;
++ for (const auto &entry : responseData) {
++ if (entry.first == "Result") {
++ p = entry.second.c_str();
++ } else if (entry.first == "TotalMatches") {
++ total = ParseUnsigned(entry.second.c_str());
++ } else if (entry.first == "NumberReturned") {
++ count = ParseUnsigned(entry.second.c_str());
++ offset += count;
++ }
++ }
++ dirbuf.Parse(p);
++#endif
+ } while (count > 0 && offset < total);
+
+ return dirbuf;
+@@ -156,6 +229,7 @@ UPnPDirContent
+ ContentDirectoryService::getMetadata(UpnpClient_Handle hdl,
+ const char *objectId) const
+ {
++#ifdef USING_PUPNP
+ // Create request
+ UniqueIxmlDocument request(MakeActionHelper("Browse", m_serviceType.c_str(),
+ "ObjectID", objectId,
+@@ -179,4 +253,31 @@ ContentDirectoryService::getMetadata(UpnpClient_Handle hdl,
+ UPnPDirContent dirbuf;
+ ReadResultTag(dirbuf, response.get());
+ return dirbuf;
++#else
++ std::vector<std::pair<std::string, std::string> > actionParams{
++ { "ObjectID", objectId }, { "BrowseFlag", "BrowseMetadata" },
++ { "Filter", "*" }, { "SortCriteria", "" },
++ { "StartingIndex", "0" }, { "RequestedCount", "1" }
++ };
++ std::vector<std::pair<std::string, std::string> > responseData;
++ int errcode;
++ std::string errdesc;
++ int code =
++ UpnpSendAction(hdl, "", m_actionURL, m_serviceType, "Browse",
++ actionParams, responseData, &errcode, errdesc);
++ if (code != UPNP_E_SUCCESS)
++ throw FormatRuntimeError("UpnpSendAction() failed: %s",
++ UpnpGetErrorMessage(code));
++ const char *p = "";
++ for (const auto &entry : responseData) {
++ if (entry.first == "Result") {
++ p = entry.second.c_str();
++ break;
++ }
++ }
++
++ UPnPDirContent dirbuf;
++ dirbuf.Parse(p);
++ return dirbuf;
++#endif
+ }
+diff --git a/src/lib/upnp/Action.hxx b/src/lib/upnp/Action.hxx
+index 49ed75198..4ecf4cb06 100644
+--- a/src/lib/upnp/Action.hxx
++++ b/src/lib/upnp/Action.hxx
+@@ -38,6 +38,7 @@ CountNameValuePairs(gcc_unused const char *name, gcc_unused const char *value,
+ return 1 + CountNameValuePairs(args...);
+ }
+
++#ifdef USING_PUPNP
+ /**
+ * A wrapper for UpnpMakeAction() that counts the number of name/value
+ * pairs and adds the nullptr sentinel.
+@@ -52,5 +53,6 @@ MakeActionHelper(const char *action_name, const char *service_type,
+ args...,
+ nullptr, nullptr);
+ }
++#endif
+
+ #endif
+diff --git a/src/lib/upnp/ClientInit.cxx b/src/lib/upnp/ClientInit.cxx
+index 23ba9cade..54b677fa2 100644
+--- a/src/lib/upnp/ClientInit.cxx
++++ b/src/lib/upnp/ClientInit.cxx
+@@ -31,14 +31,12 @@ static Mutex upnp_client_init_mutex;
+ static unsigned upnp_client_ref;
+ static UpnpClient_Handle upnp_client_handle;
+
+-static int
+-UpnpClientCallback(Upnp_EventType et,
+-#if UPNP_VERSION >= 10800
+- const
++static int UpnpClientCallback(Upnp_EventType et,
++#if 1
++ const
+ #endif
+- void *evp,
+- void *cookie) noexcept
+-{
++ void *evp,
++ void *cookie) noexcept {
+ if (cookie == nullptr)
+ /* this is the cookie passed to UpnpRegisterClient();
+ but can this ever happen? Will libupnp ever invoke
+diff --git a/src/lib/upnp/Compat.hxx b/src/lib/upnp/Compat.hxx
+index 7fba1d83b..b9a4d7cf3 100644
+--- a/src/lib/upnp/Compat.hxx
++++ b/src/lib/upnp/Compat.hxx
+@@ -22,14 +22,14 @@
+
+ #include <upnp.h>
+
+-#if UPNP_VERSION < 10800
++#if 0
+ /* emulate the libupnp 1.8 API with older versions */
+
+ using UpnpDiscovery = Upnp_Discovery;
+
+ #endif
+
+-#if UPNP_VERSION < 10624
++#if 0
+ #include "util/Compiler.h"
+
+ gcc_pure
+diff --git a/src/lib/upnp/ContentDirectoryService.cxx b/src/lib/upnp/ContentDirectoryService.cxx
+index ae514c717..eed28b41a 100644
+--- a/src/lib/upnp/ContentDirectoryService.cxx
++++ b/src/lib/upnp/ContentDirectoryService.cxx
+@@ -17,15 +17,21 @@
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
++#include "config.h"
++
+ #include "ContentDirectoryService.hxx"
+ #include "UniqueIxml.hxx"
+ #include "Device.hxx"
++#ifdef USING_PUPNP
+ #include "ixmlwrap.hxx"
++#endif
+ #include "Action.hxx"
+ #include "util/UriUtil.hxx"
+ #include "util/RuntimeError.hxx"
+ #include "util/SplitString.hxx"
+
++#include <algorithm>
++
+ ContentDirectoryService::ContentDirectoryService(const UPnPDevice &device,
+ const UPnPService &service) noexcept
+ :m_actionURL(uri_apply_base(service.controlURL, device.URLBase)),
+@@ -51,6 +57,7 @@ ContentDirectoryService::~ContentDirectoryService() noexcept
+ std::forward_list<std::string>
+ ContentDirectoryService::getSearchCapabilities(UpnpClient_Handle hdl) const
+ {
++#ifdef USING_PUPNP
+ UniqueIxmlDocument request(UpnpMakeAction("GetSearchCapabilities", m_serviceType.c_str(),
+ 0,
+ nullptr, nullptr));
+@@ -69,6 +76,24 @@ ContentDirectoryService::getSearchCapabilities(UpnpClient_Handle hdl) const
+
+ const char *s = ixmlwrap::getFirstElementValue(response.get(),
+ "SearchCaps");
++#else
++ std::vector<std::pair<std::string, std::string> > responseData;
++ int errcode;
++ std::string errdesc;
++ auto code = UpnpSendAction(hdl, "", m_actionURL, m_serviceType,
++ "GetSearchCapabilities", {}, responseData,
++ &errcode, errdesc);
++ if (code != UPNP_E_SUCCESS)
++ throw FormatRuntimeError("UpnpSendAction() failed: %s",
++ UpnpGetErrorMessage(code));
++ const char *s{ nullptr };
++ for (auto &entry : responseData) {
++ if (entry.first == "SearchCaps") {
++ s = entry.second.c_str();
++ break;
++ }
++ }
++#endif
+ if (s == nullptr || *s == 0)
+ /* we could just "return {}" here, but GCC 5 doesn't
+ understand that */
+diff --git a/src/lib/upnp/Init.cxx b/src/lib/upnp/Init.cxx
+index 7ad4d565a..10510402a 100644
+--- a/src/lib/upnp/Init.cxx
++++ b/src/lib/upnp/Init.cxx
+@@ -23,7 +23,9 @@
+
+ #include <upnp.h>
+ #include <upnptools.h>
++#ifdef USING_PUPNP
+ #include <ixml.h>
++#endif
+
+ #include <assert.h>
+
+@@ -44,8 +46,10 @@ DoInit()
+
+ UpnpSetMaxContentLength(2000*1024);
+
++#ifdef USING_PUPNP
+ // Servers sometimes make error (e.g.: minidlna returns bad utf-8)
+ ixmlRelaxParser(1);
++#endif
+ }
+
+ void
+diff --git a/src/lib/upnp/UniqueIxml.hxx b/src/lib/upnp/UniqueIxml.hxx
+index 2ff2afa62..8a0ea0a1f 100644
+--- a/src/lib/upnp/UniqueIxml.hxx
++++ b/src/lib/upnp/UniqueIxml.hxx
+@@ -20,6 +20,7 @@
+ #ifndef MPD_UPNP_UNIQUE_XML_HXX
+ #define MPD_UPNP_UNIQUE_XML_HXX
+
++#ifdef USING_PUPNP
+ #include <ixml.h>
+
+ #include <memory>
+@@ -37,4 +38,5 @@ struct UpnpIxmlDeleter {
+ typedef std::unique_ptr<IXML_Document, UpnpIxmlDeleter> UniqueIxmlDocument;
+ typedef std::unique_ptr<IXML_NodeList, UpnpIxmlDeleter> UniqueIxmlNodeList;
+
++#endif /* USING_PUPNP */
+ #endif
+diff --git a/src/lib/upnp/ixmlwrap.cxx b/src/lib/upnp/ixmlwrap.cxx
+index 4e44f35a6..c7798e557 100644
+--- a/src/lib/upnp/ixmlwrap.cxx
++++ b/src/lib/upnp/ixmlwrap.cxx
+@@ -15,6 +15,9 @@
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
++#include "config.h"
++
++#ifdef USING_PUPNP
+ #include "ixmlwrap.hxx"
+ #include "UniqueIxml.hxx"
+
+@@ -39,3 +42,4 @@ getFirstElementValue(IXML_Document *doc, const char *name) noexcept
+ }
+
+ }
++#endif
+diff --git a/src/lib/upnp/ixmlwrap.hxx b/src/lib/upnp/ixmlwrap.hxx
+index 6713d59bd..4b01801f7 100644
+--- a/src/lib/upnp/ixmlwrap.hxx
++++ b/src/lib/upnp/ixmlwrap.hxx
+@@ -17,6 +17,7 @@
+ #ifndef _IXMLWRAP_H_INCLUDED_
+ #define _IXMLWRAP_H_INCLUDED_
+
++#ifdef USING_PUPNP
+ #include <ixml.h>
+
+ #include <string>
+@@ -32,4 +33,5 @@ namespace ixmlwrap {
+
+ }
+
++#endif /* USING_PUPNP */
+ #endif /* _IXMLWRAP_H_INCLUDED_ */
+diff --git a/src/lib/upnp/meson.build b/src/lib/upnp/meson.build
+index 9e16f7319..bdc248e6c 100644
+--- a/src/lib/upnp/meson.build
++++ b/src/lib/upnp/meson.build
+@@ -1,4 +1,22 @@
+-upnp_dep = dependency('libupnp', required: get_option('upnp'))
++upnp_option = get_option('upnp')
++
++if upnp_option == 'auto'
++ upnp_dep = dependency('libupnp', version: '>= 1.8', required: false)
++ conf.set('USING_PUPNP', upnp_dep.found())
++ if not upnp_dep.found()
++ upnp_dep = dependency('libnpupnp', version: '>= 1.8', required: false)
++ endif
++elif upnp_option == 'pupnp'
++ upnp_dep = dependency('libupnp', version: '>= 1.8', required: true)
++ conf.set('USING_PUPNP', true)
++elif upnp_option == 'npupnp'
++ upnp_dep = dependency('libnpupnp', required: true)
++ conf.set('USING_PUPNP', false)
++elif upnp_option == 'disabled'
++ upnp_dep = dependency('', required: false)
++ subdir_done()
++endif
++
+ conf.set('ENABLE_UPNP', upnp_dep.found())
+ if not upnp_dep.found()
+ subdir_done()