From 993e4e157e4a8c4898b45982045cf63e3b57a6a1 Mon Sep 17 00:00:00 2001
From: Jean-Francois Dockes <jf@dockes.org>
Date: Fri, 13 Mar 2020 09:19:04 +0100
Subject: [PATCH] Quick changes for working with NPUPNP

(Rebased and made default)
Signed-off-by: Rosen Penev <rosenp@gmail.com>
---
 CMakeLists.txt                           | 35 +++++++++++++++---------
 src/action_request.cc                    | 11 +++++++-
 src/device_description_handler.cc        |  4 +++
 src/file_request_handler.cc              |  4 +++
 src/iohandler/file_io_handler.cc         |  1 -
 src/iohandler/io_handler.cc              |  1 -
 src/iohandler/mem_io_handler.cc          |  1 -
 src/serve_request_handler.cc             |  8 ++++++
 src/server.cc                            |  8 ++++++
 src/transcoding/transcode_ext_handler.cc |  1 -
 src/upnp_cds.cc                          | 11 ++++++++
 src/upnp_cm.cc                           | 11 ++++++++
 src/upnp_mrreg.cc                        |  7 ++++-
 src/url_request_handler.cc               |  5 +++-
 src/util/upnp_clients.cc                 | 12 ++++++++
 src/util/upnp_headers.cc                 | 14 ++++++++++
 src/web/web_request_handler.cc           |  4 +++
 17 files changed, 118 insertions(+), 20 deletions(-)

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 81f7818e..56472b97 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -35,6 +35,7 @@ if (CONAN_EXPORTED)
 endif()
 
 set(CMAKE_VERBOSE_MAKEFILE off CACHE BOOL "Show verbose build commands")
+set(WITH_NPUPNP         0 CACHE BOOL "Use npupnp instead of pupnp")
 set(WITH_MAGIC          1 CACHE BOOL "Use libmagic to identify file mime types")
 set(WITH_MYSQL          0 CACHE BOOL "Store media information in MySQL DB")
 set(WITH_CURL           1 CACHE BOOL "CURL required for online services")
@@ -303,23 +304,31 @@ add_definitions(${LFS_DEFINITIONS})
 add_compile_options(${LFS_COMPILE_OPTIONS})
 target_link_libraries(libgerbera ${LFS_LIBRARIES})
 
-find_package (pupnp "1.12.1" REQUIRED)
+if (WITH_NPUPNP)
+    pkg_check_modules (NPUPNP REQUIRED libnpupnp)
+    include_directories (${NPUPNP_INCLUDE_DIRS})
+    set(CMAKE_REQUIRED_LIBRARIES npupnp)
+    add_definitions(-DUSING_NPUPNP)
+    target_link_libraries (libgerbera ${NPUPNP_LIBRARIES})
+else()
+    find_package (pupnp "1.12.1" REQUIRED)
 
-set(CMAKE_REQUIRED_LIBRARIES pupnp::pupnp)
+    set(CMAKE_REQUIRED_LIBRARIES pupnp::pupnp)
 
-check_cxx_symbol_exists(UPNP_ENABLE_IPV6 "upnpconfig.h" UPNP_HAS_IPV6)
-if (NOT UPNP_HAS_IPV6)
-    message(FATAL_ERROR "Gerbera requires libupnp with IPv6 support.")
-endif()
+    check_cxx_symbol_exists(UPNP_ENABLE_IPV6 "upnpconfig.h" UPNP_HAS_IPV6)
+    if (NOT UPNP_HAS_IPV6)
+        message(FATAL_ERROR "Gerbera requires libupnp with IPv6 support.")
+    endif()
 
-check_cxx_symbol_exists(UPNP_MINISERVER_REUSEADDR "upnpconfig.h" UPNP_HAS_REUSEADDR)
-if (NOT UPNP_HAS_REUSEADDR)
-    message(WARNING [=[
-!! It is strongly recommended to build libupnp with --enable-reuseaddr !!
-Without this option Gerbera will be unable to restart with the same port number.]=])
-endif()
+    check_cxx_symbol_exists(UPNP_MINISERVER_REUSEADDR "upnpconfig.h" UPNP_HAS_REUSEADDR)
+    if (NOT UPNP_HAS_REUSEADDR)
+        message(WARNING [=[
+    !! It is strongly recommended to build libupnp with --enable-reuseaddr !!
+    Without this option Gerbera will be unable to restart with the same port number.]=])
+    endif()
 
-target_link_libraries (libgerbera pupnp::pupnp)
+    target_link_libraries (libgerbera pupnp::pupnp)
+endif()
 
 find_package(fmt REQUIRED)
 target_link_libraries(libgerbera fmt::fmt)
diff --git a/src/action_request.cc b/src/action_request.cc
index fab0e910..5677e61e 100644
--- a/src/action_request.cc
+++ b/src/action_request.cc
@@ -65,10 +65,14 @@ std::string ActionRequest::getServiceID() const
 
 std::unique_ptr<pugi::xml_document> ActionRequest::getRequest() const
 {
-    DOMString cxml = ixmlPrintDocument(UpnpActionRequest_get_ActionRequest(upnp_request));
     auto request = std::make_unique<pugi::xml_document>();
+#if defined(USING_NPUPNP)
+    auto ret = request->load_string(upnp_request->xmlAction.c_str());
+#else
+    DOMString cxml = ixmlPrintDocument(UpnpActionRequest_get_ActionRequest(upnp_request));
     auto ret = request->load_string(cxml);
     ixmlFreeDOMString(cxml);
+#endif
 
     if (ret.status != pugi::xml_parse_status::status_ok)
         throw_std_runtime_error("Unable to parse ixml");
@@ -94,6 +98,10 @@ void ActionRequest::update()
         std::string xml = buf.str();
         log_debug("ActionRequest::update(): {}", xml.c_str());
 
+#if defined(USING_NPUPNP)
+        UpnpActionRequest_set_xmlResponse(upnp_request, xml);
+        UpnpActionRequest_set_ErrCode(upnp_request, errCode);
+#else
         IXML_Document* result = nullptr;
         int err = ixmlParseBufferEx(xml.c_str(), &result);
 
@@ -105,6 +113,7 @@ void ActionRequest::update()
             UpnpActionRequest_set_ActionResult(upnp_request, result);
             UpnpActionRequest_set_ErrCode(upnp_request, errCode);
         }
+#endif
     } else {
         // ok, here there can be two cases
         // either the function below already did set an error code,
diff --git a/src/device_description_handler.cc b/src/device_description_handler.cc
index 6aca745e..cf2e8015 100644
--- a/src/device_description_handler.cc
+++ b/src/device_description_handler.cc
@@ -45,7 +45,11 @@ void DeviceDescriptionHandler::getInfo(const char* filename, UpnpFileInfo* info)
 {
     // We should be able to do the generation here, but libupnp doesnt support the request cookies yet
     UpnpFileInfo_set_FileLength(info, -1);
+#if defined(USING_NPUPNP)
     UpnpFileInfo_set_ContentType(info, "application/xml");
+#else
+    UpnpFileInfo_set_ContentType(info, ixmlCloneDOMString("application/xml"));
+#endif
     UpnpFileInfo_set_IsReadable(info, 1);
     UpnpFileInfo_set_IsDirectory(info, 0);
 }
diff --git a/src/file_request_handler.cc b/src/file_request_handler.cc
index cfa3eaed..915e411b 100644
--- a/src/file_request_handler.cc
+++ b/src/file_request_handler.cc
@@ -238,7 +238,11 @@ void FileRequestHandler::getInfo(const char* filename, UpnpFileInfo* info)
 
     UpnpFileInfo_set_LastModified(info, statbuf.st_mtime);
     UpnpFileInfo_set_IsDirectory(info, S_ISDIR(statbuf.st_mode));
+#if defined(USING_NPUPNP)
+    UpnpFileInfo_set_ContentType(info, mimeType);
+#else
     UpnpFileInfo_set_ContentType(info, ixmlCloneDOMString(mimeType.c_str()));
+#endif
 
     headers->writeHeaders(info);
 
diff --git a/src/iohandler/file_io_handler.cc b/src/iohandler/file_io_handler.cc
index 7e239250..b023e85b 100644
--- a/src/iohandler/file_io_handler.cc
+++ b/src/iohandler/file_io_handler.cc
@@ -32,7 +32,6 @@
 #include "file_io_handler.h" // API
 
 #include <cstdio>
-#include <ixml.h>
 #include <utility>
 
 #include "cds_objects.h"
diff --git a/src/iohandler/io_handler.cc b/src/iohandler/io_handler.cc
index f9789425..1153ce6b 100644
--- a/src/iohandler/io_handler.cc
+++ b/src/iohandler/io_handler.cc
@@ -31,7 +31,6 @@
 
 #include "io_handler.h" // API
 
-#include <ixml.h>
 #include <unistd.h>
 
 #include "server.h"
diff --git a/src/iohandler/mem_io_handler.cc b/src/iohandler/mem_io_handler.cc
index 5574a16d..223746ef 100644
--- a/src/iohandler/mem_io_handler.cc
+++ b/src/iohandler/mem_io_handler.cc
@@ -35,7 +35,6 @@
 #include <cstdlib>
 #include <cstring>
 #include <ctime>
-#include <ixml.h>
 #include <sys/stat.h>
 #include <sys/types.h>
 #include <unistd.h>
diff --git a/src/serve_request_handler.cc b/src/serve_request_handler.cc
index 210140a3..01dde69b 100644
--- a/src/serve_request_handler.cc
+++ b/src/serve_request_handler.cc
@@ -94,7 +94,11 @@ void ServeRequestHandler::getInfo(const char* filename, UpnpFileInfo* info)
             UpnpFileInfo_set_IsReadable(info, 0);
         }
 
+#if defined(USING_NPUPNP)
+        UpnpFileInfo_set_ContentType(info, mimetype);
+#else
         UpnpFileInfo_set_ContentType(info, ixmlCloneDOMString(mimetype.c_str()));
+#endif
     } else {
         throw_std_runtime_error("Not a regular file: " + path);
     }
@@ -158,7 +162,11 @@ std::unique_ptr<IOHandler> ServeRequestHandler::open(const char* filename,
         }
 
 
+#if defined(USING_NPUPNP)
+        info->content_type = mimetype;
+#else
         info->content_type = ixmlCloneDOMString(mimetype.c_str());
+#endif
         */
     } else {
         throw_std_runtime_error("Not a regular file: " + path);
diff --git a/src/server.cc b/src/server.cc
index a83c28cd..d4ce3e51 100644
--- a/src/server.cc
+++ b/src/server.cc
@@ -393,9 +393,17 @@ int Server::handleUpnpClientEvent(Upnp_EventType eventType, const void* event)
     case UPNP_DISCOVERY_ADVERTISEMENT_ALIVE:
     case UPNP_DISCOVERY_SEARCH_RESULT: {
         auto d_event = reinterpret_cast<const UpnpDiscovery*>(event);
+#if defined(USING_NPUPNP)
+        const char* userAgent = UpnpDiscovery_get_Os_cstr(d_event);
+#else
         const char* userAgent = UpnpString_get_String(UpnpDiscovery_get_Os(d_event));
+#endif
         const struct sockaddr_storage* destAddr = UpnpDiscovery_get_DestAddr(d_event);
+#if defined(USING_NPUPNP)
+        const char* location = UpnpDiscovery_get_Location_cstr(d_event);
+#else
         const char* location = UpnpString_get_String(UpnpDiscovery_get_Location(d_event));
+#endif
 
         Clients::addClientByDiscovery(destAddr, userAgent, location);
         break;
diff --git a/src/transcoding/transcode_ext_handler.cc b/src/transcoding/transcode_ext_handler.cc
index 67ee79d9..1da59ea2 100644
--- a/src/transcoding/transcode_ext_handler.cc
+++ b/src/transcoding/transcode_ext_handler.cc
@@ -37,7 +37,6 @@
 #include <cstring>
 #include <fcntl.h>
 #include <filesystem>
-#include <ixml.h>
 #include <sys/stat.h>
 #include <sys/types.h>
 #include <unistd.h>
diff --git a/src/upnp_cds.cc b/src/upnp_cds.cc
index 12ffeea2..5c2e1043 100644
--- a/src/upnp_cds.cc
+++ b/src/upnp_cds.cc
@@ -284,6 +284,11 @@ void ContentDirectoryService::processSubscriptionRequest(const std::unique_ptr<S
     propset->print(buf, "", 0);
     std::string xml = buf.str();
 
+#if defined(USING_NPUPNP)
+    UpnpAcceptSubscriptionXML(
+        deviceHandle, config->getOption(CFG_SERVER_UDN).c_str(),
+        DESC_CDS_SERVICE_ID, xml, request->getSubscriptionID().c_str());
+#else
     IXML_Document* event = nullptr;
     int err = ixmlParseBufferEx(xml.c_str(), &event);
     if (err != IXML_SUCCESS) {
@@ -295,6 +300,7 @@ void ContentDirectoryService::processSubscriptionRequest(const std::unique_ptr<S
         DESC_CDS_SERVICE_ID, event, request->getSubscriptionID().c_str());
 
     ixmlDocument_free(event);
+#endif
     log_debug("end");
 }
 
@@ -313,6 +319,10 @@ void ContentDirectoryService::sendSubscriptionUpdate(const std::string& containe
     propset->print(buf, "", 0);
     std::string xml = buf.str();
 
+#if defined(USING_NPUPNP)
+    UpnpNotifyXML(deviceHandle, config->getOption(CFG_SERVER_UDN).c_str(),
+        DESC_CDS_SERVICE_ID, xml);
+#else
     IXML_Document* event = nullptr;
     int err = ixmlParseBufferEx(xml.c_str(), &event);
     if (err != IXML_SUCCESS) {
@@ -325,6 +335,7 @@ void ContentDirectoryService::sendSubscriptionUpdate(const std::string& containe
         DESC_CDS_SERVICE_ID, event);
 
     ixmlDocument_free(event);
+#endif
 
     log_debug("end");
 }
diff --git a/src/upnp_cm.cc b/src/upnp_cm.cc
index aa608480..d7ab40cf 100644
--- a/src/upnp_cm.cc
+++ b/src/upnp_cm.cc
@@ -127,6 +127,11 @@ void ConnectionManagerService::processSubscriptionRequest(const std::unique_ptr<
     propset->print(buf, "", 0);
     std::string xml = buf.str();
 
+#if defined(USING_NPUPNP)
+    UpnpAcceptSubscriptionXML(
+        deviceHandle, config->getOption(CFG_SERVER_UDN).c_str(),
+        DESC_CM_SERVICE_ID, xml, request->getSubscriptionID().c_str());
+#else
     IXML_Document* event = nullptr;
     int err = ixmlParseBufferEx(xml.c_str(), &event);
     if (err != IXML_SUCCESS) {
@@ -138,6 +143,7 @@ void ConnectionManagerService::processSubscriptionRequest(const std::unique_ptr<
         DESC_CM_SERVICE_ID, event, request->getSubscriptionID().c_str());
 
     ixmlDocument_free(event);
+#endif
 }
 
 void ConnectionManagerService::sendSubscriptionUpdate(const std::string& sourceProtocol_CSV)
@@ -150,6 +156,10 @@ void ConnectionManagerService::sendSubscriptionUpdate(const std::string& sourceP
     propset->print(buf, "", 0);
     std::string xml = buf.str();
 
+#if defined(USING_NPUPNP)
+    UpnpNotifyXML(deviceHandle, config->getOption(CFG_SERVER_UDN).c_str(),
+        DESC_CM_SERVICE_ID, xml);
+#else
     IXML_Document* event = nullptr;
     int err = ixmlParseBufferEx(xml.c_str(), &event);
     if (err != IXML_SUCCESS) {
@@ -162,4 +172,5 @@ void ConnectionManagerService::sendSubscriptionUpdate(const std::string& sourceP
         DESC_CM_SERVICE_ID, event);
 
     ixmlDocument_free(event);
+#endif
 }
diff --git a/src/upnp_mrreg.cc b/src/upnp_mrreg.cc
index 16eefaed..ecb49025 100644
--- a/src/upnp_mrreg.cc
+++ b/src/upnp_mrreg.cc
@@ -34,7 +34,6 @@
 #include <utility>
 
 #include "config/config_manager.h"
-#include "ixml.h"
 #include "server.h"
 #include "storage/storage.h"
 #include "upnp_xml.h"
@@ -120,6 +119,11 @@ void MRRegistrarService::processSubscriptionRequest(const std::unique_ptr<Subscr
     propset->print(buf, "", 0);
     std::string xml = buf.str();
 
+#if defined(USING_NPUPNP)
+    UpnpAcceptSubscriptionXML(
+        deviceHandle, config->getOption(CFG_SERVER_UDN).c_str(),
+        DESC_MRREG_SERVICE_ID, xml, request->getSubscriptionID().c_str());
+#else
     IXML_Document* event = nullptr;
     int err = ixmlParseBufferEx(xml.c_str(), &event);
     if (err != IXML_SUCCESS) {
@@ -131,6 +135,7 @@ void MRRegistrarService::processSubscriptionRequest(const std::unique_ptr<Subscr
         DESC_MRREG_SERVICE_ID, event, request->getSubscriptionID().c_str());
 
     ixmlDocument_free(event);
+#endif
 }
 
 // TODO: FIXME
diff --git a/src/url_request_handler.cc b/src/url_request_handler.cc
index f2a99c94..7de2227d 100644
--- a/src/url_request_handler.cc
+++ b/src/url_request_handler.cc
@@ -32,7 +32,6 @@
 #ifdef HAVE_CURL
 #include "url_request_handler.h" // API
 
-#include <ixml.h>
 #include <utility>
 
 #include "config/config_manager.h"
@@ -138,7 +137,11 @@ void URLRequestHandler::getInfo(const char* filename, UpnpFileInfo* info)
     //            ixmlCloneDOMString(header.c_str()));
     //    }
 
+#if defined(USING_NPUPNP)
+    UpnpFileInfo_set_ContentType(info, mimeType);
+#else
     UpnpFileInfo_set_ContentType(info, ixmlCloneDOMString(mimeType.c_str()));
+#endif
     log_debug("web_get_info(): end");
 
     /// \todo transcoding for get_info
diff --git a/src/util/upnp_clients.cc b/src/util/upnp_clients.cc
index 2033cf31..1dd7964d 100644
--- a/src/util/upnp_clients.cc
+++ b/src/util/upnp_clients.cc
@@ -268,6 +268,17 @@ bool Clients::getInfoByType(const std::string& match, ClientMatchType type, cons
 
 bool Clients::downloadDescription(const std::string& location, std::unique_ptr<pugi::xml_document>& xml)
 {
+#if defined(USING_NPUPNP)
+    std::string description, ct;
+    int errCode = UpnpDownloadUrlItem(location, description, ct);
+    if (errCode != UPNP_E_SUCCESS) {
+        log_debug("Error obtaining client description from {} -- error = {}", location, errCode);
+        return false;
+    }
+    const char* cxml = description.c_str();
+    xml = std::make_unique<pugi::xml_document>();
+    auto ret = xml->load_string(cxml);
+#else
     IXML_Document* descDoc = nullptr;
     int errCode = UpnpDownloadXmlDoc(location.c_str(), &descDoc);
     if (errCode != UPNP_E_SUCCESS) {
@@ -281,6 +292,7 @@ bool Clients::downloadDescription(const std::string& location, std::unique_ptr<p
 
     ixmlFreeDOMString(cxml);
     ixmlDocument_free(descDoc);
+#endif
 
     if (ret.status != pugi::xml_parse_status::status_ok) {
         log_debug("Unable to parse xml client description from {}", location);
diff --git a/src/util/upnp_headers.cc b/src/util/upnp_headers.cc
index ef85106b..aec13850 100644
--- a/src/util/upnp_headers.cc
+++ b/src/util/upnp_headers.cc
@@ -25,11 +25,13 @@
 
 #include "upnp_headers.h" // API
 
+#if !defined(USING_NPUPNP)
 #if (UPNP_VERSION > 11201)
 #include <UpnpExtraHeaders.h>
 #else
 #include <ExtraHeaders.h>
 #endif
+#endif
 #include <string>
 
 #include "common.h"
@@ -101,18 +103,29 @@ void Headers::writeHeaders(UpnpFileInfo* fileInfo) const
     if (headers == nullptr)
         return;
 
+#if defined(USING_NPUPNP)
+    for (const auto& iter : *headers) {
+        fileInfo->response_headers.push_back(iter);
+    }
+#else
     auto head = const_cast<UpnpListHead*>(UpnpFileInfo_get_ExtraHeadersList(fileInfo));
     for (const auto& iter : *headers) {
         UpnpExtraHeaders* h = UpnpExtraHeaders_new();
         UpnpExtraHeaders_set_resp(h, formatHeader(iter, false).c_str());
         UpnpListInsert(head, UpnpListEnd(head), const_cast<UpnpListHead*>(UpnpExtraHeaders_get_node(h)));
     }
+#endif
 }
 
 std::unique_ptr<std::map<std::string, std::string>> Headers::readHeaders(UpnpFileInfo* fileInfo)
 {
     auto ret = std::make_unique<std::map<std::string, std::string>>();
 
+#if defined(USING_NPUPNP)
+    for (const auto& entry : fileInfo->request_headers) {
+        ret->insert(entry);
+    }
+#else
     auto head = const_cast<UpnpListHead*>(UpnpFileInfo_get_ExtraHeadersList(fileInfo));
     UpnpListIter pos;
     for (pos = UpnpListBegin(head); pos != UpnpListEnd(head); pos = UpnpListNext(head, pos)) {
@@ -121,6 +134,7 @@ std::unique_ptr<std::map<std::string, std::string>> Headers::readHeaders(UpnpFil
         auto add = parseHeader(header);
         ret->insert(add);
     }
+#endif
 
     return ret;
 }
diff --git a/src/web/web_request_handler.cc b/src/web/web_request_handler.cc
index 60e2d028..117dcbfa 100644
--- a/src/web/web_request_handler.cc
+++ b/src/web/web_request_handler.cc
@@ -112,7 +112,11 @@ void WebRequestHandler::getInfo(const char* filename, UpnpFileInfo* info)
     std::string mimetype = (returnType == "xml") ? MIMETYPE_XML : MIMETYPE_JSON;
     std::string contentType = mimetype + "; charset=" + DEFAULT_INTERNAL_CHARSET;
 
+#if defined(USING_NPUPNP)
+    UpnpFileInfo_set_ContentType(info, contentType);
+#else
     UpnpFileInfo_set_ContentType(info, ixmlCloneDOMString(contentType.c_str()));
+#endif
     Headers headers;
     headers.addHeader(std::string { "Cache-Control" }, std::string { "no-cache, must-revalidate" });
     headers.writeHeaders(info);
