Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package libupnp for openSUSE:Factory checked in at 2026-09-23 14:32:57 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/libupnp (Old) and /work/SRC/openSUSE:Factory/.libupnp.new.383539 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "libupnp" Wed Sep 23 14:32:57 2026 rev:51 rq:1379917 version:22.1.2 Changes: -------- --- /work/SRC/openSUSE:Factory/libupnp/libupnp.changes 2026-09-12 21:16:14.762709941 +0200 +++ /work/SRC/openSUSE:Factory/.libupnp.new.383539/libupnp.changes 2026-09-23 14:34:21.250841059 +0200 @@ -1,0 +2,14 @@ +Wed Sep 23 08:08:24 UTC 2026 - Jan Engelhardt <[email protected]> + +- Update to release 22.1.2 + * Fix quadratic-time DOM construction in ixmlParseBufferEx() + [GHSA-xr5m-v53v-jfq9] + +------------------------------------------------------------------- +Tue Sep 22 15:21:50 UTC 2026 - Jan Engelhardt <[email protected]> + +- Update to release 22.1.1 + * Fix unauthenticated remote crash in SOAP QueryStateVariable + handling. [GHSA-7mx2-6v7x-xhv7] + +------------------------------------------------------------------- Old: ---- libupnp-22.1.0.tar.bz2 New: ---- libupnp-22.1.2.tar.bz2 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ libupnp.spec ++++++ --- /var/tmp/diff_new_pack.qlKBbF/_old 2026-09-23 14:34:22.259883243 +0200 +++ /var/tmp/diff_new_pack.qlKBbF/_new 2026-09-23 14:34:22.261883326 +0200 @@ -19,7 +19,7 @@ %define pnpver 22 Name: libupnp -Version: 22.1.0 +Version: 22.1.2 Release: 0 Summary: An implementation of Universal Plug and Play (UPnP) License: BSD-3-Clause ++++++ _scmsync.obsinfo ++++++ --- /var/tmp/diff_new_pack.qlKBbF/_old 2026-09-23 14:34:22.295884748 +0200 +++ /var/tmp/diff_new_pack.qlKBbF/_new 2026-09-23 14:34:22.298884873 +0200 @@ -1,5 +1,5 @@ -mtime: 1789122216 -commit: 7620613e0282ae9d953b9d7c029340a392d984d6b2028c5fdf9a81699046b79f +mtime: 1790151648 +commit: 91d5869ac7d557af80fd47e55774a1b7e3af5aa5315fa84bde9dd2ca7fd9629e url: https://src.opensuse.org/jengelh/libupnp revision: master ++++++ build.specials.obscpio ++++++ ++++++ build.specials.obscpio ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/.gitignore new/.gitignore --- old/.gitignore 1970-01-01 01:00:00.000000000 +0100 +++ new/.gitignore 2026-09-23 10:20:48.000000000 +0200 @@ -0,0 +1 @@ +.osc ++++++ libupnp-22.1.0.tar.bz2 -> libupnp-22.1.2.tar.bz2 ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libupnp-22.1.0/CMakeLists.txt new/libupnp-22.1.2/CMakeLists.txt --- old/libupnp-22.1.0/CMakeLists.txt 2026-09-10 22:47:00.000000000 +0200 +++ new/libupnp-22.1.2/CMakeLists.txt 2026-09-23 02:23:04.000000000 +0200 @@ -7,7 +7,7 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS ON) project(PUPNP - VERSION 22.1.0 + VERSION 22.1.2 LANGUAGES C) include(GNUInstallDirs) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libupnp-22.1.0/ChangeLog new/libupnp-22.1.2/ChangeLog --- old/libupnp-22.1.0/ChangeLog 2026-09-10 22:47:00.000000000 +0200 +++ new/libupnp-22.1.2/ChangeLog 2026-09-23 02:23:04.000000000 +0200 @@ -1,4 +1,38 @@ ******************************************************************************* +Version 22.1.2 +******************************************************************************* + +- GHSA-xr5m-v53v-jfq9: Fix quadratic-time DOM construction in + ixmlParseBufferEx() allowing pre-auth denial of service. The parser appended + every node by walking its parent's child list from the head, resolved the + default namespace and every element prefix by walking the whole stack of + open elements, and checked every attribute against all the attributes + before it. A well-formed SOAP body within the 64000-byte entity limit, sent + unauthenticated to any control URL, could burn up to about 2 s of CPU per + request (CWE-407). Children are now appended in constant time, the default + namespace in effect is recorded on each element as it is opened, and prefix + lookups skip the elements that bind no prefix. Elements are limited to 256 + attributes, namespace declarations included, and at most 256 namespace + prefix bindings may be in scope at once; documents above either limit are + rejected with IXML_SYNTAX_ERR. Reported by Denis K. + + +******************************************************************************* +Version 22.1.1 +******************************************************************************* + +- GHSA-7mx2-6v7x-xhv7: Fix unauthenticated remote crash in SOAP + QueryStateVariable handling. check_soap_request() passed the local name of + the first child of the QueryStateVariable action node straight to + strcmp(), but ixmlNode_getLocalName() returns NULL for text and CDATA + nodes. A single POST to any control URL, with the QueryStateVariable + SOAPACTION and a text or CDATA node where <varName> is expected, crashed + the whole process (CWE-476) before the device callback ran. Fixed by adding + the same NULL check the neighbouring local-name checks already use; such + requests are now rejected with HTTP 400. Reported by Alex J. + + +******************************************************************************* Version 22.1.0 ******************************************************************************* diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libupnp-22.1.0/docs/Doxyfile new/libupnp-22.1.2/docs/Doxyfile --- old/libupnp-22.1.0/docs/Doxyfile 2026-09-10 22:47:00.000000000 +0200 +++ new/libupnp-22.1.2/docs/Doxyfile 2026-09-23 02:23:04.000000000 +0200 @@ -38,7 +38,7 @@ # could be handy for archiving the generated documentation or if some version # control system is used. -PROJECT_NUMBER = 22.1.0 +PROJECT_NUMBER = 22.1.2 # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer a diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libupnp-22.1.0/fuzzer/CMakeLists.txt new/libupnp-22.1.2/fuzzer/CMakeLists.txt --- old/libupnp-22.1.0/fuzzer/CMakeLists.txt 2026-09-10 22:47:00.000000000 +0200 +++ new/libupnp-22.1.2/fuzzer/CMakeLists.txt 2026-09-23 02:23:04.000000000 +0200 @@ -4,6 +4,7 @@ add_executable(FuzzIxml FuzzIxml.c) add_executable(FuzzHttp FuzzHttp.c) add_executable(FuzzServiceTable FuzzServiceTable.c) +add_executable(FuzzIxmlDom FuzzIxmlDom.c) if(NOT DEFINED LOCAL_RUN) message("LOCAL_RUN is not defined") @@ -14,6 +15,7 @@ target_link_libraries(FuzzHttp upnp_static ${LIB_FUZZING_ENGINE}) target_include_directories(FuzzServiceTable PRIVATE ${CMAKE_SOURCE_DIR}/upnp/src/threadutil) target_link_libraries(FuzzServiceTable upnp_static ${LIB_FUZZING_ENGINE}) + target_link_libraries(FuzzIxmlDom ixml_static ${LIB_FUZZING_ENGINE}) else() message("LOCAL_RUN is ${LOCAL_RUN}") @@ -43,4 +45,10 @@ ${LIB_IXML_INCLUDE}) target_link_libraries(FuzzServiceTable PRIVATE ${LIB_UPNP} ${LIB_IXML} ${LIB_FUZZING_ENGINE}) + + target_include_directories(FuzzIxmlDom PUBLIC + ${LIB_UPNP_INCLUDE} + ${LIB_IXML_INCLUDE}) + target_link_libraries(FuzzIxmlDom PRIVATE + ${LIB_IXML} ${LIB_FUZZING_ENGINE}) endif() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libupnp-22.1.0/fuzzer/FuzzIxmlDom.c new/libupnp-22.1.2/fuzzer/FuzzIxmlDom.c --- old/libupnp-22.1.0/fuzzer/FuzzIxmlDom.c 1970-01-01 01:00:00.000000000 +0100 +++ new/libupnp-22.1.2/fuzzer/FuzzIxmlDom.c 2026-09-23 02:23:04.000000000 +0200 @@ -0,0 +1,152 @@ +#include "ixml.h" + +#include <stddef.h> +#include <stdint.h> +#include <stdlib.h> +#include <string.h> + +#define MAX_DEPTH 64 +#define MAX_NODES 4096 + +static void visit_attributes(IXML_Node *nodeptr) +{ + IXML_NamedNodeMap *map; + unsigned long i; + unsigned long len; + + map = ixmlNode_getAttributes(nodeptr); + if (!map) { + return; + } + + len = ixmlNamedNodeMap_getLength(map); + for (i = 0; i < len; i++) { + IXML_Node *attr = ixmlNamedNodeMap_item(map, i); + const DOMString name; + + if (!attr) { + continue; + } + (void)ixmlNode_getNodeValue(attr); + (void)ixmlNode_getLocalName(attr); + (void)ixmlNode_getNamespaceURI(attr); + (void)ixmlNode_getPrefix(attr); + + name = ixmlNode_getNodeName(attr); + if (name) { + (void)ixmlNamedNodeMap_getNamedItem(map, name); + } + } + ixmlNamedNodeMap_free(map); +} + +static void walk(IXML_Node *nodeptr, int depth, unsigned long *budget) +{ + IXML_Node *child; + + if (!nodeptr || depth > MAX_DEPTH || *budget == 0) { + return; + } + (*budget)--; + + if (ixmlNode_hasAttributes(nodeptr)) { + visit_attributes(nodeptr); + } + + for (child = ixmlNode_getFirstChild(nodeptr); child; + child = ixmlNode_getNextSibling(child)) { + walk(child, depth + 1, budget); + } +} + +static void visit_child_list(IXML_Node *nodeptr) +{ + IXML_NodeList *children = ixmlNode_getChildNodes(nodeptr); + unsigned long i; + unsigned long len; + + if (!children) { + return; + } + len = ixmlNodeList_length(children); + for (i = 0; i < len; i++) { + (void)ixmlNodeList_item(children, i); + } + ixmlNodeList_free(children); +} + +static void query_document(IXML_Document *doc) +{ + IXML_NodeList *list; + IXML_Node *first; + + list = ixmlDocument_getElementsByTagName(doc, "*"); + if (list) { + unsigned long i; + unsigned long len = ixmlNodeList_length(list); + + for (i = 0; i < len; i++) { + IXML_Node *node = ixmlNodeList_item(list, i); + IXML_Element *element = (IXML_Element *)node; + const DOMString tag; + + if (!node) { + continue; + } + tag = ixmlElement_getTagName(element); + if (tag) { + (void)ixmlElement_getAttribute(element, tag); + (void)ixmlElement_hasAttribute(element, tag); + (void)ixmlElement_getAttributeNode( + element, tag); + } + } + ixmlNodeList_free(list); + } + + list = ixmlDocument_getElementsByTagNameNS(doc, "*", "*"); + if (list) { + ixmlNodeList_free(list); + } + + first = ixmlNode_getFirstChild((IXML_Node *)doc); + if (first) { + IXML_Node *clone = ixmlNode_cloneNode(first, 1); + + if (clone) { + (void)ixmlNode_getFirstChild(clone); + (void)ixmlNode_hasChildNodes(clone); + ixmlNode_free(clone); + } + } +} + +extern int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) +{ + IXML_Document *doc = NULL; + char *xml; + + if (Size < 1 || Size > 65536) { + return 0; + } + + xml = malloc(Size + 1); + if (!xml) { + return 0; + } + memcpy(xml, Data, Size); + xml[Size] = '\0'; + + if (ixmlParseBufferEx(xml, &doc) == IXML_SUCCESS && doc) { + unsigned long budget = MAX_NODES; + + walk((IXML_Node *)doc, 0, &budget); + visit_child_list((IXML_Node *)doc); + query_document(doc); + ixmlDocument_free(doc); + } + + free(xml); + + return 0; +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libupnp-22.1.0/ixml/src/inc/ixmlparser.h new/libupnp-22.1.2/ixml/src/inc/ixmlparser.h --- old/libupnp-22.1.0/ixml/src/inc/ixmlparser.h 2026-09-10 22:47:00.000000000 +0200 +++ new/libupnp-22.1.2/ixml/src/inc/ixmlparser.h 2026-09-23 02:23:04.000000000 +0200 @@ -48,6 +48,16 @@ #define ESC_HEX "&#x" #define ESC_DEC "&#" +/*! Maximum number of attributes, namespace declarations included, on one + * element. Checking a new attribute for duplicates walks the attributes + * before it, so without a cap one element costs O(n^2) to parse. */ +#define IXML_MAX_ATTRIBUTES 256 + +/*! Maximum number of namespace prefix bindings in scope at once. Resolving a + * prefix walks the bindings in scope, so without a cap n prefixed elements + * cost O(n^2) to parse. */ +#define IXML_MAX_NAMESPACES 256 + typedef struct _IXML_NamespaceURI { char *nsURI; @@ -62,6 +72,18 @@ char *namespaceUri; IXML_NamespaceURI *pNsURI; struct _IXML_ElementStack *nextElement; + /*! Nearest frame below this one that defines a default namespace, + * NULL if none. */ + struct _IXML_ElementStack *defaultNsElement; + /*! Number of entries in pNsURI. */ + int numNsURI; + /*! Nonzero if namespaceUri is what the frames below resolve prefix + * to, so that this frame binds nothing new for it. */ + int prefixInherited; + /*! Nearest frame below this one that binds a prefix, NULL if none. */ + struct _IXML_ElementStack *nsScopeElement; + /*! Number of prefix bindings in the frames below this one. */ + int nsInScope; } IXML_ElementStack; typedef enum @@ -84,6 +106,10 @@ IXML_Node *pNeedPrefixNode; IXML_ElementStack *pCurElement; IXML_Node *currentNodePtr; + /*! Last child of currentNodePtr, NULL if it has none yet. */ + IXML_Node *lastChild; + /*! Number of attributes read so far on currentNodePtr. */ + int numAttributes; PARSER_STATE state; int bHasTopLevel; } Parser; @@ -256,6 +282,23 @@ IXML_Node *src); /*! + * \brief Same as ixmlNode_appendChild(), but in constant time when the + * caller knows the current last child of nodeptr. + * + * The hint is used only if it is still the last child of nodeptr; otherwise + * the child list is walked as ixmlNode_appendChild() does. + * + * \return Same as ixmlNode_appendChild(). + */ +int ixmlNode_appendChildAfter( + /*! [in] The parent node. */ + IXML_Node *nodeptr, + /*! [in] The node to append. */ + IXML_Node *newChild, + /*! [in] The last child of nodeptr, or NULL if unknown. */ + IXML_Node *lastChild); + +/*! * \brief Initializes a nodelist */ void ixmlNodeList_init( diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libupnp-22.1.0/ixml/src/ixmlparser.c new/libupnp-22.1.2/ixml/src/ixmlparser.c --- old/libupnp-22.1.0/ixml/src/ixmlparser.c 2026-09-10 22:47:00.000000000 +0200 +++ new/libupnp-22.1.2/ixml/src/ixmlparser.c 2026-09-23 02:23:04.000000000 +0200 @@ -1783,6 +1783,7 @@ free(pCur->namespaceUri); } pCur->namespaceUri = safe_strdup(newNode->nodeValue); + pCur->prefixInherited = 0; if (pCur->namespaceUri == NULL) { ret = IXML_INSUFFICIENT_MEMORY; line = __LINE__; @@ -1810,6 +1811,7 @@ free(pCur->namespaceUri); } pCur->namespaceUri = safe_strdup(newNode->nodeValue); + pCur->prefixInherited = 0; if (pCur->namespaceUri == NULL) { ret = IXML_INSUFFICIENT_MEMORY; line = __LINE__; @@ -1860,6 +1862,7 @@ } else { pPrevNs->nextNsURI = pNewNs; } + pCur->numNsURI++; } else { /* update the namespace */ if (pNs->nsURI != NULL) { @@ -2167,6 +2170,20 @@ } /*! + * \brief Counts the namespace prefixes a frame binds. + * + * Its namespace declarations, plus its own prefix unless that resolved to + * what the frames below already bind. + */ +static int Parser_numNsBindings( + /*! [in] The element stack frame. */ + const IXML_ElementStack *pCur) +{ + return pCur->numNsURI + + (pCur->prefix != NULL && !pCur->prefixInherited ? 1 : 0); +} + +/*! * \brief Decides whether element's prefix is already defined. */ static int Parser_ElementPrefixDefined( @@ -2180,6 +2197,8 @@ IXML_ElementStack *pCur = xmlParser->pCurElement; IXML_NamespaceURI *pNsUri; + /* Frames that bind no prefix would only repeat what the frames below + * them resolve to, so they are skipped. */ while (pCur != NULL) { if ((pCur->prefix != NULL) && (strcmp(pCur->prefix, newNode->prefix) == 0)) { @@ -2199,7 +2218,7 @@ } } - pCur = pCur->nextElement; + pCur = pCur->nsScopeElement; } return 0; @@ -2276,6 +2295,9 @@ IXML_Attr *attr = NULL; int rc = IXML_SUCCESS; + if (++xmlParser->numAttributes > IXML_MAX_ATTRIBUTES) { + return IXML_SYNTAX_ERR; + } if (isDuplicateAttribute(xmlParser, newNode)) { return IXML_SYNTAX_ERR; } @@ -2353,6 +2375,35 @@ pCurElement = xmlParser->pCurElement; + /* The parent's attributes, where its namespaces are declared, + * have all been read, so the default namespace in effect below + * it cannot change any more. */ + if (pCurElement != NULL) { + int numBindings = Parser_numNsBindings(pCurElement); + + if (pCurElement->prefix == NULL && + pCurElement->namespaceUri != NULL) { + pNewStackElement->defaultNsElement = + pCurElement; + } else { + pNewStackElement->defaultNsElement = + pCurElement->defaultNsElement; + } + if (numBindings > 0) { + pNewStackElement->nsScopeElement = pCurElement; + } else { + pNewStackElement->nsScopeElement = + pCurElement->nsScopeElement; + } + pNewStackElement->nsInScope = + pCurElement->nsInScope + numBindings; + if (pNewStackElement->nsInScope > IXML_MAX_NAMESPACES) { + Parser_freeElementStackItem(pNewStackElement); + free(pNewStackElement); + return IXML_SYNTAX_ERR; + } + } + /* insert the new element into the top of the stack */ pNewStackElement->nextElement = pCurElement; xmlParser->pCurElement = pNewStackElement; @@ -2385,13 +2436,18 @@ { IXML_ElementStack *pCur = xmlParser->pCurElement; - while (pCur != NULL) { - if ((pCur->prefix == NULL) && (pCur->namespaceUri != NULL)) { - *nsURI = pCur->namespaceUri; - return 1; - } else { - pCur = pCur->nextElement; - } + if (pCur == NULL) { + return 0; + } + /* Only the top frame can still be changing; the default namespace in + * effect below it was recorded when it was pushed. */ + if ((pCur->prefix == NULL) && (pCur->namespaceUri != NULL)) { + *nsURI = pCur->namespaceUri; + return 1; + } + if (pCur->defaultNsElement != NULL) { + *nsURI = pCur->defaultNsElement->namespaceUri; + return 1; } return 0; @@ -2412,6 +2468,7 @@ { IXML_Element *newElement = NULL; char *nsURI = NULL; + int prefixInherited = 0; int rc = IXML_SUCCESS; if (xmlParser->bHasTopLevel) { @@ -2444,6 +2501,7 @@ } else { /* fill in the namespace */ Parser_setElementNamespace(newElement, nsURI); + prefixInherited = 1; } } else { /* does element has default namespace */ @@ -2463,17 +2521,23 @@ } } - rc = ixmlNode_appendChild( - xmlParser->currentNodePtr, (IXML_Node *)newElement); + rc = ixmlNode_appendChildAfter(xmlParser->currentNodePtr, + (IXML_Node *)newElement, + xmlParser->lastChild); if (rc != IXML_SUCCESS) { ixmlElement_free(newElement); return rc; } xmlParser->currentNodePtr = (IXML_Node *)newElement; + xmlParser->lastChild = NULL; + xmlParser->numAttributes = 0; /* push element to stack */ rc = Parser_pushElement(xmlParser, (IXML_Node *)newElement); + if (rc == IXML_SUCCESS) { + xmlParser->pCurElement->prefixInherited = prefixInherited; + } return rc; } @@ -2553,6 +2617,8 @@ if (strcmp(newNode->nodeName, xmlParser->currentNodePtr->nodeName) == 0) { + /* The element being closed is the last child of its parent. */ + xmlParser->lastChild = xmlParser->currentNodePtr; xmlParser->currentNodePtr = xmlParser->currentNodePtr->parentNode; } else { @@ -2624,13 +2690,15 @@ goto ErrorHandler; } - rc = ixmlNode_appendChild( + rc = ixmlNode_appendChildAfter( xmlParser->currentNodePtr, - tempNode); + tempNode, + xmlParser->lastChild); if (rc != IXML_SUCCESS) { ixmlNode_free(tempNode); goto ErrorHandler; } + xmlParser->lastChild = tempNode; break; @@ -2642,14 +2710,17 @@ if (rc != IXML_SUCCESS) { goto ErrorHandler; } - rc = ixmlNode_appendChild( + rc = ixmlNode_appendChildAfter( xmlParser->currentNodePtr, - (IXML_Node *)cdataSecNode); + (IXML_Node *)cdataSecNode, + xmlParser->lastChild); if (rc != IXML_SUCCESS) { ixmlNode_free((IXML_Node *) cdataSecNode); goto ErrorHandler; } + xmlParser->lastChild = + (IXML_Node *)cdataSecNode; break; case eATTRIBUTE_NODE: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libupnp-22.1.0/ixml/src/node.c new/libupnp-22.1.2/ixml/src/node.c --- old/libupnp-22.1.0/ixml/src/node.c 2026-09-10 22:47:00.000000000 +0200 +++ new/libupnp-22.1.2/ixml/src/node.c 2026-09-23 02:23:04.000000000 +0200 @@ -660,6 +660,12 @@ int ixmlNode_appendChild(IXML_Node *nodeptr, IXML_Node *newChild) { + return ixmlNode_appendChildAfter(nodeptr, newChild, NULL); +} + +int ixmlNode_appendChildAfter( + IXML_Node *nodeptr, IXML_Node *newChild, IXML_Node *lastChild) +{ IXML_Node *prev = NULL; IXML_Node *next = NULL; @@ -688,6 +694,12 @@ /* if the first child */ if (!nodeptr->firstChild) { nodeptr->firstChild = newChild; + } else if (lastChild && lastChild != newChild && + lastChild->parentNode == nodeptr && + !lastChild->nextSibling) { + /* The caller knows the end of the list: skip the walk. */ + lastChild->nextSibling = newChild; + newChild->prevSibling = lastChild; } else { prev = nodeptr->firstChild; next = prev->nextSibling; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libupnp-22.1.0/ixml/test/CMakeLists.txt new/libupnp-22.1.2/ixml/test/CMakeLists.txt --- old/libupnp-22.1.0/ixml/test/CMakeLists.txt 2026-09-10 22:47:00.000000000 +0200 +++ new/libupnp-22.1.2/ixml/test/CMakeLists.txt 2026-09-23 02:23:04.000000000 +0200 @@ -14,6 +14,10 @@ ixml_add_unit_test(test-ixml-poc-ghsa-j247-clonenode poc_ghsa_j247_clonenode.c "") ixml_add_unit_test(test-ixml-poc-ghsa-j247-isancestor poc_ghsa_j247_isancestor.c "") ixml_add_unit_test(test-ixml-poc-ghsa-q54q poc_ghsa_q54q.c "") +ixml_add_unit_test(test-ixml-poc-ghsa-xr5m-siblings poc_ghsa_xr5m_siblings.c "") +ixml_add_unit_test(test-ixml-poc-ghsa-xr5m-depth poc_ghsa_xr5m_depth.c "") +ixml_add_unit_test(test-ixml-poc-ghsa-xr5m-attributes poc_ghsa_xr5m_attributes.c "") +ixml_add_unit_test(test-ixml-poc-ghsa-xr5m-prefix poc_ghsa_xr5m_prefix.c "") if(NOT UPNP_ENABLE_TESTING_INTEGRATION) return() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libupnp-22.1.0/ixml/test/poc_ghsa_xr5m.h new/libupnp-22.1.2/ixml/test/poc_ghsa_xr5m.h --- old/libupnp-22.1.0/ixml/test/poc_ghsa_xr5m.h 1970-01-01 01:00:00.000000000 +0100 +++ new/libupnp-22.1.2/ixml/test/poc_ghsa_xr5m.h 2026-09-23 02:23:04.000000000 +0200 @@ -0,0 +1,84 @@ +/* Shared helpers for the GHSA-xr5m-v53v-jfq9 regression tests. + * + * The bug is a complexity bug: the documents parse correctly, only too + * slowly. So the tests compare the parse cost of an adversarial document + * against a control document with the same number of nodes, where the + * quadratic walk cannot grow. Absolute times vary with the machine and the + * build type (Debug, sanitizers); the ratio between the two does not. */ + +#ifndef POC_GHSA_XR5M_H +#define POC_GHSA_XR5M_H + +#include "ixml.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> + +/* A linear parser keeps the ratio near 1; the quadratic one reaches 50 and + * more at the node counts used here. */ +#define XR5M_MAX_RATIO 8.0 + +/* Repeat each measurement until at least this much CPU time has elapsed, so + * that a coarse clock() (15 ms ticks on Windows) does not dominate. */ +#define XR5M_MIN_CLOCKS (CLOCKS_PER_SEC / 10) + +/* Append n copies of s at *p. */ +static char *xr5m_repeat(char *p, const char *s, int n) +{ + size_t len = strlen(s); + int i; + + for (i = 0; i < n; i++) { + memcpy(p, s, len); + p += len; + } + return p; +} + +/* Parse buf repeatedly and return the average cost of one parse, in clock() + * ticks. Returns a negative value if the document does not parse. */ +static double xr5m_parse_cost(const char *buf) +{ + clock_t start = clock(); + clock_t elapsed; + int runs = 0; + + do { + IXML_Document *doc = NULL; + int rc = ixmlParseBufferEx(buf, &doc); + if (rc != IXML_SUCCESS || !doc) { + fprintf(stderr, "parse failed: rc=%d\n", rc); + return -1.0; + } + ixmlDocument_free(doc); + runs++; + elapsed = clock() - start; + } while (elapsed < XR5M_MIN_CLOCKS); + + return (double)elapsed / runs; +} + +/* Returns 0 if parsing test costs no more than XR5M_MAX_RATIO times parsing + * control, 1 otherwise. */ +static int xr5m_check_ratio( + const char *name, const char *test, const char *control) +{ + double t = xr5m_parse_cost(test); + double c = xr5m_parse_cost(control); + double ratio; + + if (t < 0 || c <= 0) + return 1; + ratio = t / c; + printf("%s: %.2f ms vs control %.2f ms, ratio %.1f (max %.1f)\n", + name, + t * 1000.0 / CLOCKS_PER_SEC, + c * 1000.0 / CLOCKS_PER_SEC, + ratio, + XR5M_MAX_RATIO); + return ratio > XR5M_MAX_RATIO; +} + +#endif /* POC_GHSA_XR5M_H */ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libupnp-22.1.0/ixml/test/poc_ghsa_xr5m_attributes.c new/libupnp-22.1.2/ixml/test/poc_ghsa_xr5m_attributes.c --- old/libupnp-22.1.0/ixml/test/poc_ghsa_xr5m_attributes.c 1970-01-01 01:00:00.000000000 +0100 +++ new/libupnp-22.1.2/ixml/test/poc_ghsa_xr5m_attributes.c 2026-09-23 02:23:04.000000000 +0200 @@ -0,0 +1,71 @@ +/* Regression test for GHSA-xr5m-v53v-jfq9 (attribute lists). + * + * Every attribute the parser reads costs three walks of the element's + * attribute list: the duplicate check in isDuplicateAttribute() + * (ixml/src/ixmlparser.c), the duplicate check in get_attribute_node() and + * the walk to the end of the list in ixmlElement_setAttributeNode_common() + * (ixml/src/element.c). n attributes on one element therefore cost O(n^2): + * 7 000 of them in a 64 KB SOAP body take about 0.17 s. + * + * The duplicate checks are required for well-formedness, so the fix caps the + * number of attributes per element at 256 and rejects documents above it with + * IXML_SYNTAX_ERR. This test checks both sides of the cap. */ + +#include "ixml.h" + +#include <stdio.h> +#include <stdlib.h> + +#define MAX_ATTRIBUTES 256 + +int main(void); + +/* Parse <r a0="" a1="" ... a(n-1)=""/> and return the result code. */ +static int parse_with_attributes(int n) +{ + /* " a" + up to 5 digits + "=\"\"" per attribute. */ + char *buf = malloc((size_t)n * 10 + 8); + char *p = buf; + IXML_Document *doc = NULL; + int rc; + int i; + + if (!buf) + return IXML_INSUFFICIENT_MEMORY; + p += sprintf(p, "<r"); + for (i = 0; i < n; i++) + p += sprintf(p, " a%d=\"\"", i); + sprintf(p, "/>"); + + rc = ixmlParseBufferEx(buf, &doc); + ixmlDocument_free(doc); + free(buf); + return rc; +} + +int main(void) +{ + int failed = 0; + int rc; + + rc = parse_with_attributes(MAX_ATTRIBUTES); + printf("%d attributes: rc=%d (expected %d)\n", + MAX_ATTRIBUTES, + rc, + IXML_SUCCESS); + failed |= rc != IXML_SUCCESS; + + rc = parse_with_attributes(MAX_ATTRIBUTES + 1); + printf("%d attributes: rc=%d (expected %d)\n", + MAX_ATTRIBUTES + 1, + rc, + IXML_SYNTAX_ERR); + failed |= rc != IXML_SYNTAX_ERR; + + /* The reporter's shape: as many attributes as fit in 64 KB. */ + rc = parse_with_attributes(7000); + printf("7000 attributes: rc=%d (expected %d)\n", rc, IXML_SYNTAX_ERR); + failed |= rc != IXML_SYNTAX_ERR; + + return failed; +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libupnp-22.1.0/ixml/test/poc_ghsa_xr5m_depth.c new/libupnp-22.1.2/ixml/test/poc_ghsa_xr5m_depth.c --- old/libupnp-22.1.0/ixml/test/poc_ghsa_xr5m_depth.c 1970-01-01 01:00:00.000000000 +0100 +++ new/libupnp-22.1.2/ixml/test/poc_ghsa_xr5m_depth.c 2026-09-23 02:23:04.000000000 +0200 @@ -0,0 +1,49 @@ +/* Regression test for GHSA-xr5m-v53v-jfq9 (nesting depth). + * + * Parser_hasDefaultNamespace() in ixml/src/ixmlparser.c walks the whole + * stack of open elements on every unprefixed start tag, looking for a default + * namespace. When there is none, the walk reaches the bottom of the stack + * every time, so n nested elements cost O(n^2) to parse. The fix must find + * the default namespace in effect in constant time. + * + * The control is the same document with a default namespace declared on the + * root. Every open element then carries that namespace, the walk stops at the + * first frame, and the cost stays linear. */ + +#include "poc_ghsa_xr5m.h" + +#define NESTING_DEPTH 16000 + +int main(void); + +int main(void) +{ + size_t len = (size_t)NESTING_DEPTH * 7 + 32; + char *plain = malloc(len + 1); + char *defns = malloc(len + 1); + char *p; + int failed; + + if (!plain || !defns) + return 1; + + /* <r><a><a>...</a></a></r> */ + p = xr5m_repeat(plain, "<r>", 1); + p = xr5m_repeat(p, "<a>", NESTING_DEPTH); + p = xr5m_repeat(p, "</a>", NESTING_DEPTH); + p = xr5m_repeat(p, "</r>", 1); + *p = '\0'; + + /* <r xmlns="u"><a><a>...</a></a></r> */ + p = xr5m_repeat(defns, "<r xmlns=\"u\">", 1); + p = xr5m_repeat(p, "<a>", NESTING_DEPTH); + p = xr5m_repeat(p, "</a>", NESTING_DEPTH); + p = xr5m_repeat(p, "</r>", 1); + *p = '\0'; + + failed = xr5m_check_ratio("deep, no namespace", plain, defns); + + free(plain); + free(defns); + return failed; +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libupnp-22.1.0/ixml/test/poc_ghsa_xr5m_prefix.c new/libupnp-22.1.2/ixml/test/poc_ghsa_xr5m_prefix.c --- old/libupnp-22.1.0/ixml/test/poc_ghsa_xr5m_prefix.c 1970-01-01 01:00:00.000000000 +0100 +++ new/libupnp-22.1.2/ixml/test/poc_ghsa_xr5m_prefix.c 2026-09-23 02:23:04.000000000 +0200 @@ -0,0 +1,95 @@ +/* Regression test for GHSA-xr5m-v53v-jfq9 (namespace prefixes). + * + * Parser_ElementPrefixDefined() in ixml/src/ixmlparser.c resolves the prefix + * of every prefixed start tag by walking the whole stack of open elements, + * and every namespace declaration on each of them, until it finds the + * prefix. n prefixed elements declared d levels up therefore cost + * O(n * d) to parse. The fix must skip the elements that declare nothing, + * and caps the namespace declarations in scope at 256 so that the remaining + * walk is bounded; documents above the cap are rejected with IXML_SYNTAX_ERR. + * + * The timing control holds the same elements at the same depth, with the + * prefix declared on the direct parent, where the walk stops at once. */ + +#include "poc_ghsa_xr5m.h" + +#define NESTING_DEPTH 16000 +#define NUM_ELEMENTS 16000 +#define MAX_NAMESPACES 256 + +int main(void); + +/* Parse n nested elements, each declaring a new prefix, around one child, and + * return the result code. */ +static int parse_with_namespaces(int n) +{ + /* "<a xmlns:p" + up to 5 digits + "=\"u\">" + "</a>" per level. */ + char *buf = malloc((size_t)n * 26 + 16); + char *p = buf; + IXML_Document *doc = NULL; + int rc; + int i; + + if (!buf) + return IXML_INSUFFICIENT_MEMORY; + for (i = 0; i < n; i++) + p += sprintf(p, "<a xmlns:p%d=\"u\">", i); + p = xr5m_repeat(p, "<b/>", 1); + p = xr5m_repeat(p, "</a>", n); + *p = '\0'; + + rc = ixmlParseBufferEx(buf, &doc); + ixmlDocument_free(doc); + free(buf); + return rc; +} + +int main(void) +{ + size_t len = (size_t)NESTING_DEPTH * 7 + (size_t)NUM_ELEMENTS * 6 + 32; + char *far = malloc(len + 1); + char *near = malloc(len + 1); + char *p; + int failed = 0; + int rc; + + if (!far || !near) + return 1; + + /* <r xmlns:p="u"><a>...<a><p:x/>...</a>...</a></r> */ + p = xr5m_repeat(far, "<r xmlns:p=\"u\">", 1); + p = xr5m_repeat(p, "<a>", NESTING_DEPTH); + p = xr5m_repeat(p, "<p:x/>", NUM_ELEMENTS); + p = xr5m_repeat(p, "</a>", NESTING_DEPTH); + p = xr5m_repeat(p, "</r>", 1); + *p = '\0'; + + /* <r><a>...<a xmlns:p="u"><p:x/>...</a>...</a></r> */ + p = xr5m_repeat(near, "<r>", 1); + p = xr5m_repeat(p, "<a>", NESTING_DEPTH - 1); + p = xr5m_repeat(p, "<a xmlns:p=\"u\">", 1); + p = xr5m_repeat(p, "<p:x/>", NUM_ELEMENTS); + p = xr5m_repeat(p, "</a>", NESTING_DEPTH); + p = xr5m_repeat(p, "</r>", 1); + *p = '\0'; + + failed |= xr5m_check_ratio("prefix declared far up", far, near); + free(far); + free(near); + + rc = parse_with_namespaces(MAX_NAMESPACES); + printf("%d namespaces in scope: rc=%d (expected %d)\n", + MAX_NAMESPACES, + rc, + IXML_SUCCESS); + failed |= rc != IXML_SUCCESS; + + rc = parse_with_namespaces(MAX_NAMESPACES + 1); + printf("%d namespaces in scope: rc=%d (expected %d)\n", + MAX_NAMESPACES + 1, + rc, + IXML_SYNTAX_ERR); + failed |= rc != IXML_SYNTAX_ERR; + + return failed; +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libupnp-22.1.0/ixml/test/poc_ghsa_xr5m_siblings.c new/libupnp-22.1.2/ixml/test/poc_ghsa_xr5m_siblings.c --- old/libupnp-22.1.0/ixml/test/poc_ghsa_xr5m_siblings.c 1970-01-01 01:00:00.000000000 +0100 +++ new/libupnp-22.1.2/ixml/test/poc_ghsa_xr5m_siblings.c 2026-09-23 02:23:04.000000000 +0200 @@ -0,0 +1,65 @@ +/* Regression test for GHSA-xr5m-v53v-jfq9 (sibling lists). + * + * ixmlNode_appendChild() in ixml/src/node.c walks the whole child list to + * find its end before every append, and the parser appends every node it + * reads through it. A flat list of n siblings therefore costs O(n^2) to + * parse: 16 000 empty elements in a 64 KB SOAP body take about 0.25 s, and + * alternating text and elements about 2 s. The fix must append in constant + * time. + * + * The control holds the same elements in groups of 64, so no child list is + * longer than 64 and the walk cannot grow. */ + +#include "poc_ghsa_xr5m.h" + +#define NUM_ELEMENTS 16000 +#define GROUP_SIZE 64 + +int main(void); + +int main(void) +{ + /* The largest document is the control: <g> and </g> per group. */ + size_t len = (size_t)NUM_ELEMENTS * 4 + + (size_t)(NUM_ELEMENTS / GROUP_SIZE) * 7 + 8; + char *flat = malloc(len + 1); + char *mixed = malloc(len + 1); + char *grouped = malloc(len + 1); + char *p; + int i; + int failed = 0; + + if (!flat || !mixed || !grouped) + return 1; + + /* <r><a/><a/>...</r> */ + p = xr5m_repeat(flat, "<r>", 1); + p = xr5m_repeat(p, "<a/>", NUM_ELEMENTS); + p = xr5m_repeat(p, "</r>", 1); + *p = '\0'; + + /* <r>x<a/>x<a/>...</r>: the same node count as flat, half of them text + * nodes, which go through ixmlNode_appendChild() too. */ + p = xr5m_repeat(mixed, "<r>", 1); + p = xr5m_repeat(p, "x<a/>", NUM_ELEMENTS / 2); + p = xr5m_repeat(p, "</r>", 1); + *p = '\0'; + + /* <r><g><a/>...</g><g><a/>...</g>...</r> */ + p = xr5m_repeat(grouped, "<r>", 1); + for (i = 0; i < NUM_ELEMENTS / GROUP_SIZE; i++) { + p = xr5m_repeat(p, "<g>", 1); + p = xr5m_repeat(p, "<a/>", GROUP_SIZE); + p = xr5m_repeat(p, "</g>", 1); + } + p = xr5m_repeat(p, "</r>", 1); + *p = '\0'; + + failed |= xr5m_check_ratio("flat siblings", flat, grouped); + failed |= xr5m_check_ratio("text/element siblings", mixed, grouped); + + free(flat); + free(mixed); + free(grouped); + return failed; +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libupnp-22.1.0/libupnp.spec new/libupnp-22.1.2/libupnp.spec --- old/libupnp-22.1.0/libupnp.spec 2026-09-10 22:47:00.000000000 +0200 +++ new/libupnp-22.1.2/libupnp.spec 2026-09-23 02:23:04.000000000 +0200 @@ -1,4 +1,4 @@ -Version: 22.1.0 +Version: 22.1.2 Summary: Universal Plug and Play (UPnP) SDK Name: libupnp-1.18 Release: 1%{?dist} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libupnp-22.1.0/upnp/src/soap/soap_device.c new/libupnp-22.1.2/upnp/src/soap/soap_device.c --- old/libupnp-22.1.0/upnp/src/soap/soap_device.c 2026-09-10 22:47:00.000000000 +0200 +++ new/libupnp-22.1.2/upnp/src/soap/soap_device.c 2026-09-23 02:23:04.000000000 +0200 @@ -726,7 +726,7 @@ goto error_handler; } local_name = ixmlNode_getLocalName(varname_node); - if (strcmp(local_name, "varName") != 0) { + if (NULL == local_name || strcmp(local_name, "varName") != 0) { goto error_handler; } nametxt_node = ixmlNode_getFirstChild(varname_node); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libupnp-22.1.0/upnp/test/CMakeLists.txt new/libupnp-22.1.2/upnp/test/CMakeLists.txt --- old/libupnp-22.1.0/upnp/test/CMakeLists.txt 2026-09-10 22:47:00.000000000 +0200 +++ new/libupnp-22.1.2/upnp/test/CMakeLists.txt 2026-09-23 02:23:04.000000000 +0200 @@ -305,6 +305,23 @@ ) endif() +# GHSA-7mx2-6v7x-xhv7: a QueryStateVariable SOAP request with a text or +# CDATA node where <varName> is expected made check_soap_request() pass a +# NULL local name to strcmp(), crashing the process. +UPNP_Add_Unit_Test(test-upnp-ghsa-xhv7 poc_ghsa_xhv7_soap_varname.c) +if(UPNP_BUILD_SHARED) + target_link_libraries(test-upnp-ghsa-xhv7 PRIVATE + $<$<BOOL:${SOCKET_LIBRARY}>:${SOCKET_LIBRARY}> + $<$<BOOL:${NSL_LIBRARY}>:${NSL_LIBRARY}> + ) +endif() +if(UPNP_BUILD_STATIC) + target_link_libraries(test-upnp-ghsa-xhv7-static PRIVATE + $<$<BOOL:${SOCKET_LIBRARY}>:${SOCKET_LIBRARY}> + $<$<BOOL:${NSL_LIBRARY}>:${NSL_LIBRARY}> + ) +endif() + # Issue #347: HTTP response headers must use title-case names. # Starts the UPnP HTTP server via UpnpInit2, sends a GET request over a raw # TCP socket, and verifies title-case header names in the response. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libupnp-22.1.0/upnp/test/poc_ghsa_xhv7_soap_varname.c new/libupnp-22.1.2/upnp/test/poc_ghsa_xhv7_soap_varname.c --- old/libupnp-22.1.0/upnp/test/poc_ghsa_xhv7_soap_varname.c 1970-01-01 01:00:00.000000000 +0100 +++ new/libupnp-22.1.2/upnp/test/poc_ghsa_xhv7_soap_varname.c 2026-09-23 02:23:04.000000000 +0200 @@ -0,0 +1,265 @@ +/* poc_ghsa_xhv7_soap_varname.c + * + * Regression test for GHSA-7mx2-6v7x-xhv7: unauthenticated remote crash in + * SOAP QueryStateVariable handling. + * + * check_soap_request() in soap_device.c takes the first child of the + * <QueryStateVariable> action node and passes its local name straight to + * strcmp(). ixmlNode_getLocalName() returns NULL for text and CDATA nodes, + * so a request whose action node starts with a text or CDATA node instead + * of a <varName> element made the worker thread dereference NULL and took + * the whole process down (CWE-476). The request is handled inside the + * library, before the device callback runs, and needs no authentication. + * + * Test cases: + * A. Valid <varName> element -> HTTP 200 (baseline, also run last to + * prove the server survived). + * B. Text node where <varName> is expected -> HTTP 400. + * C. CDATA node where <varName> is expected -> HTTP 400. + * + * Pre-fix: the process crashes (SIGSEGV) on case B. + * Post-fix: B and C are rejected with 400 and the server keeps running. + * + * regression: GHSA-7mx2-6v7x-xhv7 + */ + +#include "upnp.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#ifndef _WIN32 + #include <arpa/inet.h> + #include <netinet/in.h> + #include <sys/socket.h> + #include <sys/time.h> + #include <unistd.h> +#endif + +#define CONTROL_URL_PATH "/control/xhv7" + +/* Minimal device description with a single service. */ +static const char DEVICE_DESC[] = + "<?xml version=\"1.0\"?>" + "<root xmlns=\"urn:schemas-upnp-org:device-1-0\">" + "<specVersion><major>1</major><minor>0</minor></specVersion>" + "<device>" + "<deviceType>urn:schemas-upnp-org:device:Basic:1</deviceType>" + "<friendlyName>xhv7</friendlyName>" + "<manufacturer>Test</manufacturer>" + "<modelName>Test</modelName>" + "<UDN>uuid:xhv7-0000-0000-0000-000000000000</UDN>" + "<serviceList><service>" + "<serviceType>urn:schemas-upnp-org:service:Basic:1</serviceType>" + "<serviceId>urn:upnp-org:serviceId:Basic</serviceId>" + "<SCPDURL>/scpd.xml</SCPDURL>" + "<controlURL>" CONTROL_URL_PATH "</controlURL>" + "<eventSubURL>/event/xhv7</eventSubURL>" + "</service></serviceList>" + "</device>" + "</root>"; + +#define SOAP_PREFIX \ + "<?xml version=\"1.0\"?>" \ + "<s:Envelope " \ + "xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" " \ + "s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">" \ + "<s:Body>" \ + "<u:QueryStateVariable " \ + "xmlns:u=\"urn:schemas-upnp-org:control-1-0\">" + +#define SOAP_SUFFIX \ + "</u:QueryStateVariable>" \ + "</s:Body>" \ + "</s:Envelope>" + +static int device_callback(Upnp_EventType t, void *e, void *c) +{ + (void)c; + if (t == UPNP_CONTROL_GET_VAR_REQUEST) { + UpnpStateVarRequest_set_CurrentVal( + (UpnpStateVarRequest *)e, "42"); + } + return 0; +} + +#ifndef _WIN32 + +/* POST a QueryStateVariable SOAP body to the control URL. Returns the HTTP + * status code, or -1 on socket/parse error. */ +static int send_query( + const char *server_ip, unsigned short server_port, const char *body) +{ + int fd = -1; + int status = -1; + char req[2048]; + int req_len; + struct sockaddr_in addr; + char resp[512]; + struct timeval tv; + ssize_t n; + + req_len = snprintf(req, + sizeof(req), + "POST " CONTROL_URL_PATH " HTTP/1.1\r\n" + "Host: %s:%u\r\n" + "Content-Type: text/xml; charset=\"utf-8\"\r\n" + "SOAPACTION: " + "\"urn:schemas-upnp-org:control-1-0#QueryStateVariable\"\r\n" + "Content-Length: %u\r\n" + "Connection: close\r\n\r\n" + "%s", + server_ip, + (unsigned)server_port, + (unsigned)strlen(body), + body); + if (req_len < 0 || (size_t)req_len >= sizeof(req)) { + fprintf(stderr, "request too large\n"); + return -1; + } + + fd = socket(AF_INET, SOCK_STREAM, 0); + if (fd < 0) { + perror("socket"); + goto done; + } + + tv.tv_sec = 5; + tv.tv_usec = 0; + setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)); + + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(server_port); + if (inet_pton(AF_INET, server_ip, &addr.sin_addr) != 1) { + fprintf(stderr, "inet_pton(%s) failed\n", server_ip); + goto done; + } + if (connect(fd, (struct sockaddr *)&addr, sizeof(addr)) != 0) { + perror("connect"); + goto done; + } + + if (send(fd, req, (size_t)req_len, 0) < 0) { + perror("send"); + goto done; + } + + n = recv(fd, resp, sizeof(resp) - 1, 0); + if (n > 0) { + resp[n] = '\0'; + if (sscanf(resp, "HTTP/1.%*d %d", &status) != 1) + status = -1; + } + +done: + if (fd >= 0) + close(fd); + return status; +} + +static int check_query(const char *name, + const char *server_ip, + unsigned short server_port, + const char *body, + int expected) +{ + int status = send_query(server_ip, server_port, body); + + printf("%s -> HTTP %d\n", name, status); + if (status != expected) { + fprintf(stderr, + "%s FAIL: expected HTTP %d, got %d.\n", + name, + expected, + status); + return -1; + } + printf("%s PASS\n", name); + return 0; +} + +#endif /* !_WIN32 */ + +int main(void) +{ +#ifdef _WIN32 + puts("SKIP: raw POSIX socket test not supported on Windows."); + return EXIT_SUCCESS; +#else + static const char valid_body[] = + SOAP_PREFIX "<u:varName>Status</u:varName>" SOAP_SUFFIX; + static const char text_body[] = SOAP_PREFIX "Status" SOAP_SUFFIX; + static const char cdata_body[] = + SOAP_PREFIX "<![CDATA[Status]]>" SOAP_SUFFIX; + int rc; + UpnpDevice_Handle handle = -1; + const char *server_ip; + unsigned short server_port; + int result = 0; + + rc = UpnpInit2(NULL, 0); + if (rc != UPNP_E_SUCCESS) { + fprintf(stderr, + "UpnpInit2 failed (%d); skipping (no network?)\n", + rc); + return EXIT_SUCCESS; + } + + server_ip = UpnpGetServerIpAddress(); + server_port = UpnpGetServerPort(); + if (!server_ip || !server_port) { + fprintf(stderr, "Could not determine server address\n"); + UpnpFinish(); + return EXIT_FAILURE; + } + printf("Server: %s:%u\n", server_ip, server_port); + + rc = UpnpRegisterRootDevice2(UPNPREG_BUF_DESC, + DEVICE_DESC, + sizeof(DEVICE_DESC) - 1, + 1, + device_callback, + NULL, + &handle); + if (rc != UPNP_E_SUCCESS) { + fprintf(stderr, "UpnpRegisterRootDevice2 failed: %d\n", rc); + UpnpFinish(); + return EXIT_FAILURE; + } + + if (check_query("Test A (valid varName, baseline)", + server_ip, + server_port, + valid_body, + 200) != 0) { + fprintf(stderr, "Baseline failed; skipping (environment?)\n"); + UpnpUnRegisterRootDevice(handle); + UpnpFinish(); + return EXIT_SUCCESS; + } + if (check_query("Test B (text node)", + server_ip, + server_port, + text_body, + 400) != 0) + result = 1; + if (check_query("Test C (CDATA node)", + server_ip, + server_port, + cdata_body, + 400) != 0) + result = 1; + if (check_query("Test A (valid varName, server still alive)", + server_ip, + server_port, + valid_body, + 200) != 0) + result = 1; + + UpnpUnRegisterRootDevice(handle); + UpnpFinish(); + return result ? EXIT_FAILURE : EXIT_SUCCESS; +#endif /* !_WIN32 */ +}
