From e9606268042187e5957272b7919a0c717e17309b Mon Sep 17 00:00:00 2001 From: Jean-Francois Dockes Date: Sun, 23 Aug 2020 14:22:21 +0200 Subject: [PATCH] Add npupnp support libnpupnp is a C++ modification of libupnp. Signed-off-by: Rosen Penev --- meson_options.txt | 5 +- .../plugins/upnp/ContentDirectoryService.cxx | 94 ++++++++++++++++++- src/lib/upnp/Action.hxx | 2 + src/lib/upnp/ContentDirectoryService.cxx | 34 ++++++- src/lib/upnp/Init.cxx | 6 +- src/lib/upnp/UniqueIxml.hxx | 6 +- src/lib/upnp/ixmlwrap.cxx | 8 +- src/lib/upnp/ixmlwrap.hxx | 4 +- src/lib/upnp/meson.build | 20 +++- 9 files changed, 166 insertions(+), 13 deletions(-) diff --git a/meson_options.txt b/meson_options.txt index 23850eb29..aaf978f92 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -62,7 +62,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 5301c0f35..720940521 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 "lib/upnp/ixmlwrap.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" @@ -27,6 +30,9 @@ #include "util/ScopeExit.hxx" #include "util/StringFormat.hxx" +#include + +#ifdef USING_PUPNP static void ReadResultTag(UPnPDirContent &dirbuf, IXML_Document *response) { @@ -36,6 +42,7 @@ ReadResultTag(UPnPDirContent &dirbuf, IXML_Document *response) dirbuf.Parse(p); } +#endif inline void ContentDirectoryService::readDirSlice(UpnpClient_Handle hdl, @@ -44,6 +51,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(), @@ -79,6 +87,35 @@ ContentDirectoryService::readDirSlice(UpnpClient_Handle hdl, totalp = ParseUnsigned(value); ReadResultTag(dirbuf, response); +#else + std::vector> actionParams{ + {"ObjectID", objectId}, + {"BrowseFlag", "BrowseDirectChildren"}, + {"Filter", "*"}, + {"SortCriteria", ""}, + {"StartingIndex", StringFormat<32>("%u", offset).c_str()}, + {"RequestedCount", StringFormat<32>("%u", count).c_str()}}; + std::vector> 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 @@ -107,6 +144,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, @@ -144,6 +182,36 @@ ContentDirectoryService::search(UpnpClient_Handle hdl, total = ParseUnsigned(value); ReadResultTag(dirbuf, response.get()); +#else + std::vector> actionParams{ + {"ContainerID", objectId}, + {"SearchCriteria", ss}, + {"Filter", "*"}, + {"SortCriteria", ""}, + {"StartingIndex", StringFormat<32>("%u", offset).c_str()}, + {"RequestedCount", "0"}}; + std::vector> 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; @@ -153,6 +221,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, @@ -176,4 +245,27 @@ ContentDirectoryService::getMetadata(UpnpClient_Handle hdl, UPnPDirContent dirbuf; ReadResultTag(dirbuf, response.get()); return dirbuf; +#else + std::vector> actionParams{ + {"ObjectID", objectId}, {"BrowseFlag", "BrowseMetadata"}, + {"Filter", "*"}, {"SortCriteria", ""}, + {"StartingIndex", "0"}, {"RequestedCount", "1"}}; + std::vector> 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(); + } + } + UPnPDirContent dirbuf; + dirbuf.Parse(p); + return dirbuf; +#endif } diff --git a/src/lib/upnp/Action.hxx b/src/lib/upnp/Action.hxx index 8ff8d1d70..45765ae08 100644 --- a/src/lib/upnp/Action.hxx +++ b/src/lib/upnp/Action.hxx @@ -38,6 +38,7 @@ CountNameValuePairs([[maybe_unused]] const char *name, [[maybe_unused]] const ch 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/ContentDirectoryService.cxx b/src/lib/upnp/ContentDirectoryService.cxx index f887c7469..679243cd6 100644 --- a/src/lib/upnp/ContentDirectoryService.cxx +++ b/src/lib/upnp/ContentDirectoryService.cxx @@ -17,13 +17,21 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" + #include "ContentDirectoryService.hxx" -#include "UniqueIxml.hxx" #include "Device.hxx" -#include "ixmlwrap.hxx" -#include "util/UriRelative.hxx" -#include "util/RuntimeError.hxx" +#include "UniqueIxml.hxx" +#ifdef USING_PUPNP +# include "ixmlwrap.hxx" +#endif +#include "Action.hxx" #include "util/IterableSplitString.hxx" +#include "util/RuntimeError.hxx" +#include "util/UriRelative.hxx" +#include "util/UriUtil.hxx" + +#include #include @@ -50,6 +58,7 @@ ContentDirectoryService::~ContentDirectoryService() noexcept = default; std::forward_list ContentDirectoryService::getSearchCapabilities(UpnpClient_Handle hdl) const { +#ifdef USING_PUPNP UniqueIxmlDocument request(UpnpMakeAction("GetSearchCapabilities", m_serviceType.c_str(), 0, nullptr, nullptr)); @@ -68,6 +77,23 @@ ContentDirectoryService::getSearchCapabilities(UpnpClient_Handle hdl) const const char *s = ixmlwrap::getFirstElementValue(response.get(), "SearchCaps"); +#else + std::vector> 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(); + } + } +#endif if (s == nullptr || *s == 0) return {}; diff --git a/src/lib/upnp/Init.cxx b/src/lib/upnp/Init.cxx index 05396a402..f9f0c8515 100644 --- a/src/lib/upnp/Init.cxx +++ b/src/lib/upnp/Init.cxx @@ -23,7 +23,9 @@ #include "util/RuntimeError.hxx" #include -#include +#ifdef USING_PUPNP +# include +#endif #include @@ -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 9e0b94a9e..60da03b75 100644 --- a/src/lib/upnp/UniqueIxml.hxx +++ b/src/lib/upnp/UniqueIxml.hxx @@ -20,9 +20,10 @@ #ifndef MPD_UPNP_UNIQUE_XML_HXX #define MPD_UPNP_UNIQUE_XML_HXX -#include +#ifdef USING_PUPNP +# include -#include +# include struct UpnpIxmlDeleter { void operator()(IXML_Document *doc) noexcept { @@ -37,4 +38,5 @@ struct UpnpIxmlDeleter { typedef std::unique_ptr UniqueIxmlDocument; typedef std::unique_ptr UniqueIxmlNodeList; +#endif /* USING_PUPNP */ #endif diff --git a/src/lib/upnp/ixmlwrap.cxx b/src/lib/upnp/ixmlwrap.cxx index f811b07a6..90e8d3155 100644 --- a/src/lib/upnp/ixmlwrap.cxx +++ b/src/lib/upnp/ixmlwrap.cxx @@ -15,8 +15,11 @@ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ -#include "ixmlwrap.hxx" -#include "UniqueIxml.hxx" +#include "config.h" + +#ifdef USING_PUPNP +# include "ixmlwrap.hxx" +# include "UniqueIxml.hxx" namespace ixmlwrap { @@ -39,3 +42,4 @@ getFirstElementValue(IXML_Document *doc, const char *name) noexcept } } // namespace ixmlwrap +#endif diff --git a/src/lib/upnp/ixmlwrap.hxx b/src/lib/upnp/ixmlwrap.hxx index 2ef142ac7..25ac8695d 100644 --- a/src/lib/upnp/ixmlwrap.hxx +++ b/src/lib/upnp/ixmlwrap.hxx @@ -17,7 +17,8 @@ #ifndef _IXMLWRAP_H_INCLUDED_ #define _IXMLWRAP_H_INCLUDED_ -#include +#ifdef USING_PUPNP +# include namespace ixmlwrap { /** @@ -30,4 +31,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 f7570eb1c..bdc248e6c 100644 --- a/src/lib/upnp/meson.build +++ b/src/lib/upnp/meson.build @@ -1,4 +1,22 @@ -upnp_dep = dependency('libupnp', version: '>= 1.8', 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()