/** * XML Security Library (http://www.aleksey.com/xmlsec). * * This is free software; see the Copyright file in the source distribution for precise wording. * * Copyright (C) 2002-2026 Aleksey Sanin . All Rights Reserved. */ /** * @addtogroup xmlsec_core_xmltree * @brief XML tree functions. */ #include "globals.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "cast_helpers.h" static const xmlChar* g_xmlsec_xmltree_default_linefeed = xmlSecStringLF; /** * @brief Gets the current default linefeed. * @return the current default linefeed. */ const xmlChar* xmlSecGetDefaultLineFeed(void) { return g_xmlsec_xmltree_default_linefeed; } /** * @brief Sets the current default linefeed. * @details The caller must ensure that the linefeed * string exists for the lifetime of the program or until the new linefeed is set. * @param linefeed default linefeed. */ void xmlSecSetDefaultLineFeed(const xmlChar *linefeed) { g_xmlsec_xmltree_default_linefeed = linefeed; } /** * @brief Reads and trims XML node content. * @details Reads @p cur node content and trims it (both sides). The returned * string is newly allocated and must be freed by the caller with xmlFree. * @param cur the pointer to XML node. * @return trimmed node content or NULL if an error occurs. */ xmlChar* xmlSecGetNodeContentAndTrim(const xmlNodePtr cur) { xmlChar * content; xmlChar * bb; xmlChar * ee; xmlSecAssert2(cur != NULL, NULL); content = xmlNodeGetContent(cur); if(content == NULL) { return(NULL); } /* ltrim */ bb = content; while(((*bb) != '\0') && isspace(*bb)) { ++bb; } /* rtrim */ if((*bb) != '\0') { ee = bb + xmlStrlen(bb) - 1; while((bb <= ee) && isspace(*ee)) { --ee; } *(ee + 1) = '\0'; } /* move string to the beginning */ if(content != bb) { memmove(content, bb, (size_t)xmlStrlen(bb) + 1); } return(content); } /** * @brief Reads node content and hex-decodes it into a buffer. * @details Reads @p cur node content (whitespace-trimmed), hex-decodes it, and stores * the result in @p res. The buffer is emptied before writing. * @param cur the pointer to XML node. * @param res the output buffer to store the decoded bytes. * @return 0 on success or -1 on error. */ int xmlSecGetNodeContentAsHex(const xmlNodePtr cur, xmlSecBufferPtr res) { xmlChar* content; int ret; xmlSecAssert2(cur != NULL, -1); xmlSecAssert2(res != NULL, -1); content = xmlSecGetNodeContentAndTrim(cur); if(content == NULL) { xmlSecInternalError("xmlSecGetNodeContentAndTrim", NULL); return(-1); } ret = xmlSecBufferHexRead(res, content); xmlFree(content); if(ret < 0) { xmlSecInternalError("xmlSecBufferHexRead", NULL); return(-1); } return(0); } /** * @brief Hex-encodes data and sets it as node content. * @details Hex-encodes @p data and stores the result as node content for @p node. * @param node the pointer to XML node. * @param data the input bytes. * @param size the input data size. * @return 0 on success or -1 on error. */ int xmlSecSetNodeContentAsHex(xmlNodePtr node, const xmlSecByte* data, xmlSecSize size) { static const xmlChar hexDigits[] = "0123456789abcdef"; xmlChar* content; xmlSecSize contentSize; xmlSecSize ii; xmlSecAssert2(node != NULL, -1); xmlSecAssert2(data != NULL, -1); /* guard against integer overflow in 'contentSize = (2 * size) + 1' */ if(size > ((XMLSEC_SIZE_MAX - 1) / 2)) { xmlSecInvalidSizeError("size", size, ((XMLSEC_SIZE_MAX - 1) / 2), NULL); return(-1); } contentSize = (2 * size) + 1; content = (xmlChar*)xmlMalloc(contentSize); if(content == NULL) { xmlSecMallocError(contentSize, NULL); return(-1); } for(ii = 0; ii < size; ++ii) { content[(2 * ii)] = hexDigits[(data[ii] >> 4) & 0x0F]; content[(2 * ii) + 1] = hexDigits[data[ii] & 0x0F]; } content[2 * size] = '\0'; #if LIBXML_VERSION >= 21300 if(xmlNodeSetContent(node, content) < 0) { xmlSecXmlError("xmlNodeSetContent", node); xmlFree(content); return(-1); } #else /* LIBXML_VERSION >= 21300 */ /* libxml2 < 2.13.0: xmlNodeSetContent() returns void and cannot report errors */ xmlNodeSetContent(node, content); #endif /* LIBXML_VERSION >= 21300 */ xmlFree(content); return(0); } /** * @brief Reads node content and converts it to an xmlSecSize. * @details Reads @p cur node content and converts it to xmlSecSize value. * @param cur the pointer to XML node. * @param defValue the default value that will be returned in @p res if there is no node content. * @param res the pointer to the result value. * @return 0 on success or -1 on error. */ int xmlSecGetNodeContentAsSize(const xmlNodePtr cur, xmlSecSize defValue, xmlSecSize* res) { xmlChar *content; xmlChar *start; unsigned long long int val; char* endptr = NULL; xmlSecAssert2(cur != NULL, -1); xmlSecAssert2(res != NULL, -1); /* a node without children has no content: use the default value */ if(cur->children == NULL) { (*res) = defValue; return(0); } /* Get the content of the node and trim whitespace, the function returns NULL on error or * if the node has no content. Assume no content and return the default value. */ content = xmlSecGetNodeContentAndTrim(cur); if(content == NULL) { (*res) = defValue; return(0); } /* skip whitespaces at the beginning (the content is already trimmed by * xmlSecGetNodeContentAndTrim(), this is defensive) */ start = content; while(isspace((*start))) { ++start; } if((*start) == '\0') { /* empty or whitespace-only content: use the default value */ xmlFree(content); (*res) = defValue; return(0); } /* reject negative values and the non-canonical explicit '+' sign: the * XML Schema types these values come from (xs:positiveInteger / * xs:nonNegativeInteger) forbid any sign, but strtoull() would accept one */ if((*start == '-') || (*start == '+')) { xmlSecInvalidNodeContentError(cur, NULL, "can't parse node content as size (invalid sign)"); xmlFree(content); return(-1); } /* parse as an unsigned 64-bit value and check for overflow; using an unsigned * conversion allows values that exceed LONG_MAX on platforms where long is * narrower than size_t (e.g. LLP64/Windows 64-bit) */ errno = 0; val = strtoull((char*)start, &endptr, 10); if(errno == ERANGE) { xmlSecInvalidNodeContentError(cur, NULL, "can't parse node content as size (value out of range)"); xmlFree(content); return(-1); } /* skip spaces at the end */ while(isspace((unsigned char)(*endptr))) { ++endptr; } if((*endptr) != '\0') { xmlSecInvalidNodeContentError(cur, NULL, "can't parse node content as size (extra characters at the end)"); xmlFree(content); return(-1); } xmlFree(content); /* check that the value fits into xmlSecSize */ XMLSEC_SAFE_CAST_ULLONG_TO_SIZE(val, (*res), return(-1), NULL); return(0); } /** * @brief Searches a node and its next siblings by name and namespace. * @details Searches @p cur and the next siblings of the @p cur node having given name and * namespace href. * @param cur the pointer to XML node. * @param name the name. * @param ns the namespace href (may be NULL). * @return the pointer to the found node or NULL if an error occurs or * node is not found. */ xmlNodePtr xmlSecFindSibling(const xmlNodePtr cur, const xmlChar *name, const xmlChar *ns) { xmlNodePtr tmp; xmlSecAssert2(name != NULL, NULL); for(tmp = cur; tmp != NULL; tmp = tmp->next) { if(tmp->type == XML_ELEMENT_NODE) { if(xmlSecCheckNodeName(tmp, name, ns)) { return(tmp); } } } return(NULL); } /** * @brief Finds a direct child node by name and namespace. * @details Searches a direct child of the @p parent node having given name and * namespace href. * @param parent the pointer to XML node. * @param name the name. * @param ns the namespace href (may be NULL). * @return the pointer to the found node or NULL if an error occurs or * node is not found. */ xmlNodePtr xmlSecFindChild(const xmlNodePtr parent, const xmlChar *name, const xmlChar *ns) { xmlSecAssert2(parent != NULL, NULL); xmlSecAssert2(name != NULL, NULL); return(xmlSecFindSibling(parent->children, name, ns)); } /** * @brief Searches a node and its ancestors by name and namespace. * @details Searches the @p cur node itself and its ancestors (up to the * document root, inclusive) for a node having given name and namespace href. * @param cur the pointer to an XML node. * @param name the name. * @param ns the namespace href (may be NULL). * @return the pointer to the found node or NULL if an error occurs or * node is not found. */ xmlNodePtr xmlSecFindParent(const xmlNodePtr cur, const xmlChar *name, const xmlChar *ns) { xmlNodePtr node; xmlSecAssert2(cur != NULL, NULL); xmlSecAssert2(name != NULL, NULL); for(node = cur; node != NULL; node = node->parent) { if(xmlSecCheckNodeName(node, name, ns)) { return(node); } } return(NULL); } typedef struct { const xmlChar* name; const xmlChar* ns; xmlNodePtr result; } xmlSecFindNodeCtx; static int xmlSecFindNodeCallback(xmlNodePtr cur, void* data) { xmlSecFindNodeCtx* ctx = (xmlSecFindNodeCtx*)data; xmlSecAssert2(ctx != NULL, -1); if((cur->type == XML_ELEMENT_NODE) && xmlSecCheckNodeName(cur, ctx->name, ctx->ns)) { ctx->result = cur; return(0); /* stop the walk */ } return(1); /* continue */ } /** * @brief Searches for a node by name and namespace. * @details Searches the @p parent node, all of its following siblings, and * their entire subtrees for a node having the given name and namespace href. * @param parent the pointer to XML node. * @param name the name. * @param ns the namespace href (may be NULL). * @return the pointer to the found node or NULL if an error occurs or * node is not found. */ xmlNodePtr xmlSecFindNode(const xmlNodePtr parent, const xmlChar *name, const xmlChar *ns) { xmlSecFindNodeCtx ctx; xmlNodePtr cur; int ret; xmlSecAssert2(name != NULL, NULL); /* Setup context */ ctx.name = name; ctx.ns = ns; ctx.result = NULL; /* Walk the tree */ for(cur = parent; cur != NULL; cur = cur->next) { ret = xmlSecDepthFirstTreeWalk(cur, xmlSecFindNodeCallback, &ctx); if(ret < 0) { return(NULL); } if(ctx.result != NULL) { break; } } return(ctx.result); } /** * @brief Gets the node's namespace href. * @param cur the pointer to node. * @return node's namespace href. */ const xmlChar* xmlSecGetNodeNsHref(const xmlNodePtr cur) { xmlNsPtr ns; xmlSecAssert2(cur != NULL, NULL); /* do we have a namespace in the node? */ if(cur->ns != NULL) { return(cur->ns->href); } /* search for default namespace */ ns = xmlSearchNs(cur->doc, cur, NULL); if(ns != NULL) { return(ns->href); } return(NULL); } /** * @brief Checks if a node has the given name and namespace. * @details Checks that the node has a given name and a given namespace href. * @param cur the pointer to an XML node. * @param name the name. * @param ns the namespace href. * @return 1 if the node matches or 0 otherwise. */ int xmlSecCheckNodeName(const xmlNodePtr cur, const xmlChar *name, const xmlChar *ns) { xmlSecAssert2(cur != NULL, 0); return(xmlStrEqual(cur->name, name) && xmlStrEqual(xmlSecGetNodeNsHref(cur), ns)); } /** * @brief Adds a new child element with the given name and namespace. * @details Adds a child to the node @p parent with given @p name and namespace @p ns. * @param parent the pointer to an XML node. * @param name the new node name. * @param ns the new node namespace. * @return pointer to the new node or NULL if an error occurs. */ xmlNodePtr xmlSecAddChild(xmlNodePtr parent, const xmlChar *name, const xmlChar *ns) { xmlNodePtr cur; xmlNodePtr text; xmlSecAssert2(parent != NULL, NULL); xmlSecAssert2(name != NULL, NULL); if(parent->children == NULL) { /* TODO: add indents */ text = xmlNewText(xmlSecGetDefaultLineFeed()); if(text == NULL) { xmlSecXmlError("xmlNewText", NULL); return(NULL); } if(xmlAddChild(parent, text) == NULL) { xmlSecXmlError("xmlAddChild", NULL); xmlFreeNode(text); return(NULL); } } cur = xmlNewChild(parent, NULL, name, NULL); if(cur == NULL) { xmlSecXmlError("xmlNewChild", NULL); return(NULL); } /* namespaces support */ if(ns != NULL) { xmlNsPtr nsPtr; /* find namespace by href and check that its prefix is not overwritten */ nsPtr = xmlSearchNsByHref(cur->doc, cur, ns); if((nsPtr == NULL) || (xmlSearchNs(cur->doc, cur, nsPtr->prefix) != nsPtr)) { nsPtr = xmlNewNs(cur, ns, NULL); if(nsPtr == NULL) { xmlSecXmlError("xmlNewNs", NULL); xmlUnlinkNode(cur); xmlFreeNode(cur); return(NULL); } } xmlSetNs(cur, nsPtr); } /* TODO: add indents */ text = xmlNewText(xmlSecGetDefaultLineFeed()); if(text == NULL) { xmlSecXmlError("xmlNewText", NULL); xmlUnlinkNode(cur); xmlFreeNode(cur); return(NULL); } if(xmlAddChild(parent, text) == NULL) { xmlSecXmlError("xmlAddChild", NULL); xmlFreeNode(text); xmlUnlinkNode(cur); xmlFreeNode(cur); return(NULL); } return(cur); } /** * @brief Adds @p child node to the @p parent node. * @param parent the pointer to an XML node. * @param child the new node. * * Note: in case of failure, the @p parent node might be left in an inconsistent * state. * * @return pointer to the new node or NULL if an error occurs. */ xmlNodePtr xmlSecAddChildNode(xmlNodePtr parent, xmlNodePtr child) { xmlNodePtr text; xmlSecAssert2(parent != NULL, NULL); xmlSecAssert2(child != NULL, NULL); if(parent->children == NULL) { /* TODO: add indents */ text = xmlNewText(xmlSecGetDefaultLineFeed()); if(text == NULL) { xmlSecXmlError("xmlNewText", NULL); return(NULL); } if(xmlAddChild(parent, text) == NULL) { xmlSecXmlError("xmlAddChild", NULL); xmlFreeNode(text); return(NULL); } } if(xmlAddChild(parent, child) == NULL) { xmlSecXmlError("xmlAddChild", NULL); return(NULL); } /* TODO: add indents */ text = xmlNewText(xmlSecGetDefaultLineFeed()); if(text == NULL) { xmlSecXmlError("xmlNewText", NULL); return(NULL); } if(xmlAddChild(parent, text) == NULL) { xmlSecXmlError("xmlAddChild", NULL); xmlFreeNode(text); return(NULL); } return(child); } /** * @brief Finds or creates an empty child element by name and namespace. * @details Searches a direct child of the @p parent node having given name and * namespace href. If not found then element node with given name / namespace * is added. * @param parent the pointer to XML node. * @param name the name. * @param ns the namespace href (may be NULL). * @return the pointer to the found or created node; or NULL if an error occurs. */ xmlNodePtr xmlSecEnsureEmptyChild(xmlNodePtr parent, const xmlChar *name, const xmlChar *ns) { xmlNodePtr cur = NULL; xmlNodePtr tmp; xmlSecAssert2(parent != NULL, NULL); xmlSecAssert2(name != NULL, NULL); /* try to find an empty node first */ tmp = xmlSecFindChild(parent, name, ns); while(tmp != NULL) { if(xmlSecIsEmptyNode(tmp) == 1) { /* found! */ return(tmp); } /* remember that we found this node */ cur = tmp; tmp = xmlSecFindSibling(tmp->next, name, ns); } /* if not found then either add next or add at the end */ if(cur == NULL) { cur = xmlSecAddChild(parent, name, ns); } else { cur = xmlSecAddNextSibling(cur, name, ns); } if(cur == NULL) { xmlSecInternalError2("xmlSecAddChild or xmlSecAddNextSibling", NULL, "node=%s", xmlSecErrorsSafeString(name)); return(NULL); } return(cur); } /** * @brief Adds a new next-sibling element with the given name and namespace. * @details Adds next sibling to the node @p node with given @p name and namespace @p ns. * @param node the pointer to an XML node. * @param name the new node name. * @param ns the new node namespace. * @return pointer to the new node or NULL if an error occurs. */ xmlNodePtr xmlSecAddNextSibling(xmlNodePtr node, const xmlChar *name, const xmlChar *ns) { xmlNodePtr cur; xmlNodePtr text; xmlSecAssert2(node != NULL, NULL); xmlSecAssert2(name != NULL, NULL); cur = xmlNewNode(NULL, name); if(cur == NULL) { xmlSecXmlError("xmlNewNode", NULL); return(NULL); } if(xmlAddNextSibling(node, cur) == NULL) { xmlSecXmlError("xmlAddNextSibling", NULL); xmlFreeNode(cur); return(NULL); } /* namespaces support */ if(ns != NULL) { xmlNsPtr nsPtr; /* find namespace by href and check that its prefix is not overwritten */ nsPtr = xmlSearchNsByHref(cur->doc, cur, ns); if((nsPtr == NULL) || (xmlSearchNs(cur->doc, cur, nsPtr->prefix) != nsPtr)) { nsPtr = xmlNewNs(cur, ns, NULL); if(nsPtr == NULL) { xmlSecXmlError("xmlNewNs", NULL); xmlUnlinkNode(cur); xmlFreeNode(cur); return(NULL); } } xmlSetNs(cur, nsPtr); } /* TODO: add indents */ text = xmlNewText(xmlSecGetDefaultLineFeed()); if(text == NULL) { xmlSecXmlError("xmlNewText", NULL); xmlUnlinkNode(cur); xmlFreeNode(cur); return(NULL); } if(xmlAddNextSibling(node, text) == NULL) { xmlSecXmlError("xmlAddNextSibling", NULL); xmlFreeNode(text); xmlUnlinkNode(cur); xmlFreeNode(cur); return(NULL); } return(cur); } /** * @brief Adds a new previous-sibling element with the given name and namespace. * @details Adds prev sibling to the node @p node with given @p name and namespace @p ns. * @param node the pointer to an XML node. * @param name the new node name. * @param ns the new node namespace. * @return pointer to the new node or NULL if an error occurs. */ xmlNodePtr xmlSecAddPrevSibling(xmlNodePtr node, const xmlChar *name, const xmlChar *ns) { xmlNodePtr cur; xmlNodePtr text; xmlSecAssert2(node != NULL, NULL); xmlSecAssert2(name != NULL, NULL); cur = xmlNewNode(NULL, name); if(cur == NULL) { xmlSecXmlError("xmlNewNode", NULL); return(NULL); } if(xmlAddPrevSibling(node, cur) == NULL) { xmlSecXmlError("xmlAddPrevSibling", NULL); xmlFreeNode(cur); return(NULL); } /* namespaces support */ if(ns != NULL) { xmlNsPtr nsPtr; /* find namespace by href and check that its prefix is not overwritten */ nsPtr = xmlSearchNsByHref(cur->doc, cur, ns); if((nsPtr == NULL) || (xmlSearchNs(cur->doc, cur, nsPtr->prefix) != nsPtr)) { nsPtr = xmlNewNs(cur, ns, NULL); if(nsPtr == NULL) { xmlSecXmlError("xmlNewNs", NULL); xmlUnlinkNode(cur); xmlFreeNode(cur); return(NULL); } } xmlSetNs(cur, nsPtr); } /* TODO: add indents */ text = xmlNewText(xmlSecGetDefaultLineFeed()); if(text == NULL) { xmlSecXmlError("xmlNewText", NULL); xmlUnlinkNode(cur); xmlFreeNode(cur); return(NULL); } if(xmlAddPrevSibling(node, text) == NULL) { xmlSecXmlError("xmlAddPrevSibling", NULL); xmlFreeNode(text); xmlUnlinkNode(cur); xmlFreeNode(cur); return(NULL); } return(cur); } /** * @brief Searches for the next element node. * @param cur the pointer to an XML node. * @return the pointer to next element node or NULL if it is not found. */ xmlNodePtr xmlSecGetNextElementNode(xmlNodePtr cur) { while((cur != NULL) && (cur->type != XML_ELEMENT_NODE)) { cur = cur->next; } return(cur); } /** * @brief Swaps a node with a new node in the XML tree. * @details Swaps the @p node and @p newNode in the XML tree. Both the old * and the new node must have the same node type. * * Note: on error, the state of the @p newNode is undefined and the caller * should not make any assumptions about it. The recommended way is to * release it with xmlUnlinkNode(newNode); xmlFreeNode(newNode); immediately. * * @param node the current node. * @param newNode the new node. * @return 0 on success or a negative value if an error occurs. */ int xmlSecReplaceNode(xmlNodePtr node, xmlNodePtr newNode) { return xmlSecReplaceNodeAndReturn(node, newNode, NULL); } /** * @brief Swaps a node with another and optionally returns the replaced node. * @details Swaps the @p node and @p newNode in the XML tree. Both the old * and the new node must have the same node type. * * Note: on error, the state of the @p newNode is undefined and the caller * should not make any assumptions about it. The recommended way is to * release it with xmlUnlinkNode(newNode); xmlFreeNode(newNode); immediately. * * @param node the current node. * @param newNode the new node. * @param replaced the replaced node, or release it if NULL is given * @return 0 on success or a negative value if an error occurs. */ int xmlSecReplaceNodeAndReturn(xmlNodePtr node, xmlNodePtr newNode, xmlNodePtr* replaced) { xmlNodePtr oldNode; xmlSecAssert2(node != NULL, -1); xmlSecAssert2(newNode != NULL, -1); xmlSecAssert2(node != newNode, -1); xmlSecAssert2(node->type == newNode->type, -1); if((node->doc != NULL) && (xmlDocGetRootElement(node->doc) == node)) { /* Handle Root node */ oldNode = xmlDocSetRootElement(node->doc, newNode); if(oldNode == NULL) { xmlSecXmlError("xmlDocSetRootElement", NULL); return(-1); } } else { /* Handle generic structural replacement */ oldNode = xmlReplaceNode(node, newNode); if(oldNode == NULL) { xmlSecXmlError("xmlReplaceNode", NULL); return(-1); } } /* Return or free the unlinked node */ if(replaced != NULL) { (*replaced) = oldNode; } else { xmlFreeNode(oldNode); } return(0); } /** * @brief Swaps the content of @p node and @p newNode. * @details Swaps the content of @p node and @p newNode. * * Note: on error, the state of the @p newNode is undefined and the caller * should not make any assumptions about it. The recommended way is to * release it with xmlUnlinkNode(newNode); xmlFreeNode(newNode); immediately. * * @param node the current node. * @param newNode the new node. * @return 0 on success or a negative value if an error occurs. */ int xmlSecReplaceContent(xmlNodePtr node, xmlNodePtr newNode) { return xmlSecReplaceContentAndReturn(node, newNode, NULL); } /** * @brief Swaps the content of @p node and @p newNode, optionally returning replaced nodes. * @details Swaps the content of @p node and @p newNode, optionally returning replaced nodes. * * Note: on error, the state of the @p newNode is undefined and the caller * should not make any assumptions about it. The recommended way is to * release it with xmlUnlinkNode(newNode); xmlFreeNode(newNode); immediately. * * @param node the current node. * @param newNode the new node. * @param replaced the replaced nodes, or release them if NULL is given * @return 0 on success or a negative value if an error occurs. */ int xmlSecReplaceContentAndReturn(xmlNodePtr node, xmlNodePtr newNode, xmlNodePtr *replaced) { xmlSecAssert2(node != NULL, -1); xmlSecAssert2(newNode != NULL, -1); xmlSecAssert2(node != newNode, -1); /* return the old nodes if requested */ if(replaced != NULL) { xmlNodePtr cur, next, tail; (*replaced) = tail = NULL; for(cur = node->children; (cur != NULL); cur = next) { next = cur->next; /* unlink cur and append it to the detached *replaced list; * xmlUnlinkNode() does not merge text nodes, so cur stays valid */ xmlUnlinkNode(cur); if(tail != NULL) { tail->next = cur; cur->prev = tail; } else { (*replaced) = cur; } tail = cur; } } else { /* just delete the content */ #if LIBXML_VERSION >= 21300 if(xmlNodeSetContent(node, NULL) < 0) { xmlSecXmlError("xmlNodeSetContent", node); return(-1); } #else /* LIBXML_VERSION >= 21300 */ /* libxml2 < 2.13.0: xmlNodeSetContent() returns void and cannot report errors */ xmlNodeSetContent(node, NULL); #endif /* LIBXML_VERSION >= 21300 */ } /* swap nodes */ if((newNode->doc != NULL) && (newNode->doc->children == newNode)) { newNode->doc->children = newNode->next; } xmlUnlinkNode(newNode); if(xmlAddChildList(node, newNode) == NULL) { xmlSecXmlError("xmlAddChildList", node); return(-1); } return(0); } /** * @brief Replaces a node with parsed XML data from a buffer. * @details Swaps the @p node and the parsed XML data from the @p buffer in the XML tree. * @param node the current node. * @param buffer the XML data. * @param size the XML data size. * @return 0 on success or a negative value if an error occurs. */ int xmlSecReplaceNodeBuffer(xmlNodePtr node, const xmlSecByte *buffer, xmlSecSize size) { return xmlSecReplaceNodeBufferAndReturn(node, buffer, size, NULL); } /** * @brief Replaces a node with parsed XML data from a buffer, optionally returning replaced nodes. * @details Swaps the @p node and the parsed XML data from the @p buffer in the XML tree. * @param node the current node. * @param buffer the XML data. * @param size the XML data size. * @param replaced the replaced nodes, or release them if NULL is given * @return 0 on success or a negative value if an error occurs. */ int xmlSecReplaceNodeBufferAndReturn(xmlNodePtr node, const xmlSecByte *buffer, xmlSecSize size, xmlNodePtr *replaced) { xmlNodePtr results = NULL; xmlNodePtr next = NULL; #if (LIBXML_VERSION >= 21500) xmlChar *oldenc; #else /* (LIBXML_VERSION >= 21500) */ const xmlChar *oldenc; #endif /* (LIBXML_VERSION >= 21500) */ int len; xmlParserErrors ret; xmlSecAssert2(node != NULL, -1); xmlSecAssert2(node->parent != NULL, -1); xmlSecAssert2(node->doc != NULL, -1); xmlSecAssert2(buffer != NULL, -1); /* parse buffer in the context of node's parent */ XMLSEC_SAFE_CAST_SIZE_TO_INT(size, len, return(-1), NULL); oldenc = node->doc->encoding; node->doc->encoding = NULL; ret = xmlParseInNodeContext(node->parent, (const char*)buffer, len, xmlSecParserGetDefaultOptions(), &results); node->doc->encoding = oldenc; if(ret != XML_ERR_OK) { xmlSecXmlError("xmlParseInNodeContext", NULL); return(-1); } /* xmlParseInNodeContext() returns XML_ERR_OK with a NULL result list for * an empty buffer; fail instead of silently deleting @p node without * replacing it */ if(results == NULL) { xmlSecInvalidDataError("buffer does not contain any XML nodes", xmlSecNodeGetName(node)); return(-1); } /* add new nodes */ while (results != NULL) { next = results->next; if(xmlAddPrevSibling(node, results) == NULL) { xmlSecXmlError("xmlAddPrevSibling", NULL); /* release the current node and all remaining parsed nodes */ xmlFreeNode(results); results = next; while(results != NULL) { next = results->next; xmlFreeNode(results); results = next; } return(-1); } results = next; } /* remove old node */ xmlUnlinkNode(node); /* return the old node if requested */ if(replaced != NULL) { (*replaced) = node; } else { xmlFreeNode(node); } return(0); } /** * @brief Encodes special characters in a buffer and sets it as node content. * @details Encodes "special" characters in the @p buffer and sets the result * as the node content. * @param node the pointer to an XML node. * @param buffer the pointer to the node content. * @return 0 on success or a negative value if an error occurs. */ int xmlSecNodeEncodeAndSetContent(xmlNodePtr node, const xmlChar * buffer) { xmlSecAssert2(node != NULL, -1); if(buffer != NULL) { xmlChar * tmp; tmp = xmlEncodeSpecialChars(node->doc, buffer); if (tmp == NULL) { xmlSecXmlError("xmlEncodeSpecialChars", NULL); return(-1); } #if LIBXML_VERSION >= 21300 if(xmlNodeSetContent(node, tmp) < 0) { xmlSecXmlError("xmlNodeSetContent", node); xmlFree(tmp); return(-1); } #else /* LIBXML_VERSION >= 21300 */ /* libxml2 < 2.13.0: xmlNodeSetContent() returns void and cannot report errors */ xmlNodeSetContent(node, tmp); #endif /* LIBXML_VERSION >= 21300 */ xmlFree(tmp); } else { #if LIBXML_VERSION >= 21300 if(xmlNodeSetContent(node, NULL) < 0) { xmlSecXmlError("xmlNodeSetContent", node); return(-1); } #else /* LIBXML_VERSION >= 21300 */ /* libxml2 < 2.13.0: xmlNodeSetContent() returns void and cannot report errors */ xmlNodeSetContent(node, NULL); #endif /* LIBXML_VERSION >= 21300 */ } return(0); } typedef struct { xmlDocPtr doc; const xmlChar** ids; } xmlSecAddIDsCtx; static int xmlSecAddIDsCallback(xmlNodePtr cur, void* data) { xmlSecAddIDsCtx* ctx = (xmlSecAddIDsCtx*)data; xmlAttrPtr attr; xmlAttrPtr tmp; int ii; xmlChar* name; xmlSecAssert2(ctx != NULL, -1); if(cur->type != XML_ELEMENT_NODE) { return(1); /* continue walk */ } for(attr = cur->properties; attr != NULL; attr = attr->next) { for(ii = 0; ctx->ids[ii] != NULL; ++ii) { if(xmlStrEqual(attr->name, ctx->ids[ii]) == 0) { continue; } name = xmlNodeListGetString(ctx->doc, attr->children, 1); if(name == NULL) { continue; } tmp = xmlGetID(ctx->doc, name); if(tmp == NULL) { if(xmlAddID(NULL, ctx->doc, name, attr) == NULL) { xmlSecXmlError("xmlAddID", NULL); xmlFree(name); return(-1); } } else if(tmp != attr) { xmlSecInvalidStringDataError("id", name, "unique id (id already defined)", NULL); /* ignore error */ } xmlFree(name); } } return(1); /* continue walk */ } /** * @brief Registers ID attributes from a node subtree in the document's ID table. * @details Walks thru all children of the @p node and adds all attributes * from the @p ids list to the @p doc document IDs attributes hash. * @param doc the pointer to an XML document. * @param node the pointer to an XML node. * @param ids the pointer to a NULL terminated list of ID attributes. */ void xmlSecAddIDs(xmlDocPtr doc, xmlNodePtr node, const xmlChar** ids) { xmlSecAddIDsCtx ctx; xmlNodePtr cur; int ret; xmlSecAssert(doc != NULL); xmlSecAssert(ids != NULL); ctx.doc = doc; ctx.ids = ids; if((node != NULL) && (node->type == XML_ELEMENT_NODE)) { ret = xmlSecDepthFirstTreeWalk(node, xmlSecAddIDsCallback, &ctx); if(ret < 0) { xmlSecInternalError("xmlSecDepthFirstTreeWalk", NULL); return; } } else if((node == NULL) || (node->type == XML_DOCUMENT_NODE)) { for(cur = doc->children; cur != NULL; cur = cur->next) { if(cur->type != XML_ELEMENT_NODE) { continue; } ret = xmlSecDepthFirstTreeWalk(cur, xmlSecAddIDsCallback, &ctx); if(ret < 0) { xmlSecInternalError("xmlSecDepthFirstTreeWalk", NULL); return; } } } } /** * @brief Creates a new XML document with a single root node. * @details Creates a new XML tree with one root node @p rootNodeName. * @param rootNodeName the root node name. * @param rootNodeNs the root node namespace (optional). * @return pointer to the newly created tree or NULL if an error occurs. */ xmlDocPtr xmlSecCreateTree(const xmlChar* rootNodeName, const xmlChar* rootNodeNs) { xmlDocPtr doc; xmlNodePtr root; xmlNsPtr ns; xmlSecAssert2(rootNodeName != NULL, NULL); /* create doc */ doc = xmlNewDoc(BAD_CAST "1.0"); if(doc == NULL) { xmlSecXmlError("xmlNewDoc", NULL); return(NULL); } /* create root node */ root = xmlNewDocNode(doc, NULL, rootNodeName, NULL); if(root == NULL) { xmlSecXmlError2("xmlNewDocNode", NULL, "node=%s", rootNodeName); xmlFreeDoc(doc); return(NULL); } xmlDocSetRootElement(doc, root); /* and set root node namespace */ if(rootNodeNs != NULL) { ns = xmlNewNs(root, rootNodeNs, NULL); if(ns == NULL) { xmlSecXmlError2("xmlNewNs", NULL, "ns=%s", xmlSecErrorsSafeString(rootNodeNs)); xmlFreeDoc(doc); return(NULL); } xmlSetNs(root, ns); } return(doc); } /** * @brief Checks whether a node is empty (whitespace only). * @details Checks whether the @p node is empty (i.e. has only whitespaces children). * @param node the node to check * @return 1 if @p node is empty, 0 otherwise or a negative value if an error occurs. */ int xmlSecIsEmptyNode(xmlNodePtr node) { xmlChar* content; int res; xmlSecAssert2(node != NULL, -1); if(xmlSecGetNextElementNode(node->children) != NULL) { return(0); } /* xmlNodeGetContent may return NULL if there are no children nodes or content */ content = xmlNodeGetContent(node); if(content == NULL) { return(1); } res = xmlSecIsEmptyString(content); xmlFree(content); return(res); } /** * @brief Checks whether a string contains only whitespace. * @details Checks whether the @p str is empty (i.e. contains only whitespace characters). * @param str the string to check * @return 1 if @p str is empty, 0 otherwise or a negative value if an error occurs. */ int xmlSecIsEmptyString(const xmlChar* str) { xmlSecAssert2(str != NULL, -1); for( ;*str != '\0'; ++str) { if(!isspace((*str))) { return(0); } } return(1); } /* Helpers for XML Tree traversal */ #define xmlSecXmlNodePtrListId (&xmlSecXmlNodePtrListKlass) static xmlSecPtrListKlass xmlSecXmlNodePtrListKlass = { BAD_CAST "xml-node-ptr-list", NULL, /* duplicateItem */ NULL, /* destroyItem */ NULL, /* debugDumpItem */ NULL, /* debugXmlDumpItem */ }; /** * @brief Walks thru the XML tree and calls a callback for each node. * @details Walks thru the XML tree starting from @p node and calls @p callback for each node. * The @p callback is called with the current node and @p data as parameters * and should return 1 to continue the walk, 0 to stop the walk, or a negative * value to stop the walk with an error. * @param node the pointer to an XML node to start the walk from. * @param callback the callback function to call for each node. * @param data the pointer to data to pass to the callback function. * @return 0 on success or a negative value if an error occurs. */ int xmlSecDepthFirstTreeWalk(xmlNodePtr node, xmlSecTreeWalkCallback callback, void* data) { xmlSecPtrList queue; xmlNodePtr cur; int ret; xmlSecAssert2(callback != NULL, -1); if(node == NULL) { return(0); } ret = xmlSecPtrListInitialize(&queue, xmlSecXmlNodePtrListId); if(ret < 0) { xmlSecInternalError("xmlSecPtrListInitialize", NULL); return(-1); } /* seed the queue */ ret = xmlSecPtrListAdd(&queue, (xmlSecPtr)node); if(ret < 0) { xmlSecInternalError("xmlSecPtrListAdd", NULL); xmlSecPtrListFinalize(&queue); return(-1); } /* process the queue */ while((cur = (xmlNodePtr)xmlSecPtrListPopLast(&queue)) != NULL) { /* call the callback */ ret = callback(cur, data); if(ret < 0) { xmlSecInternalError("callback", NULL); xmlSecPtrListFinalize(&queue); return(-1); } else if(ret == 0) { /* stop the walk */ break; } /* element and document nodes have children, push children onto queue * in reverse order for correct traversal */ if((cur->type == XML_ELEMENT_NODE) || (cur->type == XML_DOCUMENT_NODE)) { xmlNodePtr child; for(child = cur->last; child != NULL; child = child->prev) { ret = xmlSecPtrListAdd(&queue, (xmlSecPtr)child); if(ret < 0) { xmlSecInternalError("xmlSecPtrListAdd", NULL); xmlSecPtrListFinalize(&queue); return(-1); } } } } /* done */ xmlSecPtrListFinalize(&queue); return(0); } /** * @brief Encodes an XML string and writes it to a file descriptor. * @details Encodes the @p str (e.g. replaces '&' with '&') and writes it to @p fd. * @param fd the file descriptor to write the XML string to * @param str the string * @return the number of characters written or a negative value if an error occurs. */ int xmlSecPrintXmlString(FILE * fd, const xmlChar * str) { int res; xmlSecAssert2(fd != NULL, -1); if(str != NULL) { xmlChar * encoded_str = NULL; encoded_str = xmlEncodeSpecialChars(NULL, str); if(encoded_str == NULL) { xmlSecXmlError2("xmlEncodeSpecialChars", NULL, "string=%s", xmlSecErrorsSafeString(str)); return(-1); } res = fprintf(fd, "%s", (const char*)encoded_str); xmlFree(encoded_str); } else { res = fprintf(fd, "NULL"); } if(res < 0) { xmlSecIOError("fprintf", NULL, NULL); return(-1); } return(res); } /** * @brief Creates a QName string from a namespace href and a local name. * @details Creates QName (prefix:local) from @p href and @p local in the context of the @p node. * Caller is responsible for freeing returned string with xmlFree. * @param node the context node. * @param href the QName href (can be NULL). * @param local the QName local part. * @return qname or NULL if an error occurs. */ xmlChar* xmlSecGetQName(xmlNodePtr node, const xmlChar* href, const xmlChar* local) { xmlChar* qname; xmlNsPtr ns; int ret; xmlSecAssert2(node != NULL, NULL); xmlSecAssert2(local != NULL, NULL); /* we don't want to create namespace node ourselves because * it might cause collisions */ ns = xmlSearchNsByHref(node->doc, node, href); if((ns == NULL) && (href != NULL)) { xmlSecXmlError2("xmlSearchNsByHref", NULL, "node=%s", xmlSecErrorsSafeString(node->name)); return(NULL); } if((ns != NULL) && (ns->prefix != NULL)) { xmlSecSize size; int len, localLen, prefixLen; localLen = xmlStrlen(local); prefixLen = xmlStrlen(ns->prefix); if(localLen > (INT_MAX - prefixLen - 2)) { xmlSecInvalidSizeError("size", (xmlSecSize)localLen, (xmlSecSize)(INT_MAX - prefixLen - 2), NULL); return(NULL); } len = localLen + prefixLen + 2; XMLSEC_SAFE_CAST_INT_TO_SIZE(len, size, return(NULL), NULL); qname = (xmlChar *)xmlMalloc(size); if(qname == NULL) { xmlSecMallocError(size, NULL); return(NULL); } ret = xmlStrPrintf(qname, len, "%s:%s", ns->prefix, local); if(ret < 0) { xmlSecXmlError("xmlStrPrintf", NULL); xmlFree(qname); return(NULL); } } else { qname = xmlStrdup(local); if(qname == NULL) { xmlSecStrdupError(local, NULL); return(NULL); } } return(qname); } /****************************************************************************** * * QName <-> Integer mapping * *****************************************************************************/ /** * @brief Maps integer @p intValue to a QName prefix. * @param info the qname<->integer mapping information. * @param intValue the integer value. * @return the QName info that is mapped to @p intValue or NULL if such value * is not found. */ xmlSecQName2IntegerInfoConstPtr xmlSecQName2IntegerGetInfo(xmlSecQName2IntegerInfoConstPtr info, int intValue) { unsigned int ii; xmlSecAssert2(info != NULL, NULL); for(ii = 0; info[ii].qnameLocalPart != NULL; ii++) { if(info[ii].intValue == intValue) { return(&info[ii]); } } return(NULL); } /** * @brief Maps a QName to an integer value. * @details Maps a qname to an integer and returns it in @p intValue. * @param info the qname<->integer mapping information. * @param qnameHref the qname href value. * @param qnameLocalPart the qname local part value. * @param intValue the pointer to result integer value. * @return 0 on success or a negative value if an error occurs. */ int xmlSecQName2IntegerGetInteger(xmlSecQName2IntegerInfoConstPtr info, const xmlChar* qnameHref, const xmlChar* qnameLocalPart, int* intValue) { unsigned int ii; xmlSecAssert2(info != NULL, -1); xmlSecAssert2(qnameLocalPart != NULL, -1); xmlSecAssert2(intValue != NULL, -1); for(ii = 0; info[ii].qnameLocalPart != NULL; ii++) { if(xmlStrEqual(info[ii].qnameLocalPart, qnameLocalPart) && xmlStrEqual(info[ii].qnameHref, qnameHref)) { (*intValue) = info[ii].intValue; return(0); } } return(-1); } /** * @brief Converts a QName string to an integer value. * @details Converts @p qname into integer in context of @p node. * @param info the qname<->integer mapping information. * @param node the pointer to node. * @param qname the qname string. * @param intValue the pointer to result integer value. * @return 0 on success or a negative value if an error occurs. */ int xmlSecQName2IntegerGetIntegerFromString(xmlSecQName2IntegerInfoConstPtr info, xmlNodePtr node, const xmlChar* qname, int* intValue) { const xmlChar* qnameLocalPart = NULL; xmlChar* qnamePrefix = NULL; const xmlChar* qnameHref; xmlNsPtr ns; int ret; xmlSecAssert2(info != NULL, -1); xmlSecAssert2(node != NULL, -1); xmlSecAssert2(qname != NULL, -1); xmlSecAssert2(intValue != NULL, -1); qnameLocalPart = xmlStrchr(qname, ':'); if(qnameLocalPart != NULL) { int qnameLen; XMLSEC_SAFE_CAST_PTRDIFF_TO_INT((qnameLocalPart - qname), qnameLen, return(-1), NULL); qnamePrefix = xmlStrndup(qname, qnameLen); if(qnamePrefix == NULL) { xmlSecStrdupError(qname, NULL); return(-1); } qnameLocalPart++; } else { qnamePrefix = NULL; qnameLocalPart = qname; } /* search namespace href */ ns = xmlSearchNs(node->doc, node, qnamePrefix); if((ns == NULL) && (qnamePrefix != NULL)) { xmlSecXmlError2("xmlSearchNs", NULL, "node=%s", xmlSecErrorsSafeString(node->name)); if(qnamePrefix != NULL) { xmlFree(qnamePrefix); } return(-1); } qnameHref = (ns != NULL) ? ns->href : BAD_CAST NULL; /* and finally search for integer */ ret = xmlSecQName2IntegerGetInteger(info, qnameHref, qnameLocalPart, intValue); if(ret < 0) { xmlSecInternalError4("xmlSecQName2IntegerGetInteger", NULL, "node=%s,qnameLocalPart=%s,qnameHref=%s", xmlSecErrorsSafeString(node->name), xmlSecErrorsSafeString(qnameLocalPart), xmlSecErrorsSafeString(qnameHref)); if(qnamePrefix != NULL) { xmlFree(qnamePrefix); } return(-1); } if(qnamePrefix != NULL) { xmlFree(qnamePrefix); } return(0); } /** * @brief Creates a QName string from an integer value. * @details Creates qname string for @p intValue in context of given @p node. Caller * is responsible for freeing returned string with xmlFree(). * @param info the qname<->integer mapping information. * @param node the pointer to node. * @param intValue the integer value. * @return pointer to newly allocated string on success or NULL if an error occurs. */ xmlChar* xmlSecQName2IntegerGetStringFromInteger(xmlSecQName2IntegerInfoConstPtr info, xmlNodePtr node, int intValue) { xmlSecQName2IntegerInfoConstPtr qnameInfo; xmlSecAssert2(info != NULL, NULL); xmlSecAssert2(node != NULL, NULL); qnameInfo = xmlSecQName2IntegerGetInfo(info, intValue); if(qnameInfo == NULL) { xmlSecInternalError3("xmlSecQName2IntegerGetInfo", NULL, "node=%s,intValue=%d", xmlSecErrorsSafeString(node->name), intValue); return(NULL); } return (xmlSecGetQName(node, qnameInfo->qnameHref, qnameInfo->qnameLocalPart)); } /** * @brief Reads a node's content and converts it to an integer using QName mapping. * @details Reads the content of @p node and converts it to an integer using mapping * from @p info. * @param info the qname<->integer mapping information. * @param node the pointer to node. * @param intValue the pointer to result integer value. * @return 0 on success or a negative value if an error occurs. */ int xmlSecQName2IntegerNodeRead(xmlSecQName2IntegerInfoConstPtr info, xmlNodePtr node, int* intValue) { xmlChar* content = NULL; int ret; xmlSecAssert2(info != NULL, -1); xmlSecAssert2(node != NULL, -1); xmlSecAssert2(intValue != NULL, -1); content = xmlSecGetNodeContentAndTrim(node); if(content == NULL) { xmlSecInternalError2("xmlSecGetNodeContentAndTrim", NULL, "node=%s", xmlSecErrorsSafeString(node->name)); return(-1); } ret = xmlSecQName2IntegerGetIntegerFromString(info, node, content, intValue); if(ret < 0) { xmlSecInternalError3("xmlSecQName2IntegerGetIntegerFromString", NULL, "node=%s,value=%s", xmlSecErrorsSafeString(node->name), xmlSecErrorsSafeString(content)); xmlFree(content); return(-1); } xmlFree(content); return(0); } /** * @brief Creates a child node holding a QName string for an integer value. * @details Creates new child node in @p node and sets its value to @p intValue. * @param info the qname<->integer mapping information. * @param node the parent node. * @param nodeName the child node name. * @param nodeNs the child node namespace. * @param intValue the integer value. * * Note: if xmlNodeSetContent() fails, the child node created by xmlSecAddChild() * is left in the tree (not unlinked or freed). * * @return 0 on success or a negative value if an error occurs. */ int xmlSecQName2IntegerNodeWrite(xmlSecQName2IntegerInfoConstPtr info, xmlNodePtr node, const xmlChar* nodeName, const xmlChar* nodeNs, int intValue) { xmlNodePtr cur; xmlChar* qname = NULL; xmlSecAssert2(info != NULL, -1); xmlSecAssert2(node != NULL, -1); xmlSecAssert2(nodeName != NULL, -1); /* find and build qname */ qname = xmlSecQName2IntegerGetStringFromInteger(info, node, intValue); if(qname == NULL) { xmlSecInternalError3("xmlSecQName2IntegerGetStringFromInteger", NULL, "node=%s,intValue=%d", xmlSecErrorsSafeString(node->name), intValue); return(-1); } cur = xmlSecAddChild(node, nodeName, nodeNs); if(cur == NULL) { xmlSecInternalError3("xmlSecAddChild", NULL, "nodeName=%s,intValue=%d", xmlSecErrorsSafeString(nodeName), intValue); xmlFree(qname); return(-1); } #if LIBXML_VERSION >= 21300 if(xmlNodeSetContent(cur, qname) < 0) { xmlSecXmlError("xmlNodeSetContent", cur); xmlFree(qname); return(-1); } #else /* LIBXML_VERSION >= 21300 */ /* libxml2 < 2.13.0: xmlNodeSetContent() returns void and cannot report errors */ xmlNodeSetContent(cur, qname); #endif /* LIBXML_VERSION >= 21300 */ xmlFree(qname); return(0); } /** * @brief Reads an attribute's QName value and converts it to an integer. * @details Gets the value of @p attrName attribute from @p node and converts it to integer * according to @p info. * @param info the qname<->integer mapping information. * @param node the element node. * @param attrName the attribute name. * @param intValue the pointer to result integer value. * @return 0 on success or a negative value if an error occurs. */ int xmlSecQName2IntegerAttributeRead(xmlSecQName2IntegerInfoConstPtr info, xmlNodePtr node, const xmlChar* attrName, int* intValue) { xmlChar* attrValue; int ret; xmlSecAssert2(info != NULL, -1); xmlSecAssert2(node != NULL, -1); xmlSecAssert2(attrName != NULL, -1); xmlSecAssert2(intValue != NULL, -1); attrValue = xmlGetProp(node, attrName); if(attrValue == NULL) { xmlSecXmlError2("xmlGetProp", NULL, "node=%s", xmlSecErrorsSafeString(node->name)); return(-1); } /* todo: trim value? */ ret = xmlSecQName2IntegerGetIntegerFromString(info, node, attrValue, intValue); if(ret < 0) { xmlSecInternalError4("xmlSecQName2IntegerGetIntegerFromString", NULL, "node=%s,attrName=%s,attrValue=%s", xmlSecErrorsSafeString(node->name), xmlSecErrorsSafeString(attrName), xmlSecErrorsSafeString(attrValue)); xmlFree(attrValue); return(-1); } xmlFree(attrValue); return(0); } /** * @brief Converts an integer to a QName and sets it as an attribute value. * @details Converts @p intValue to a qname and sets it to the value of * attribute @p attrName in @p node. * @param info the qname<->integer mapping information. * @param node the parent node. * @param attrName the name of attribute. * @param intValue the integer value. * @return 0 on success or a negative value if an error occurs. */ int xmlSecQName2IntegerAttributeWrite(xmlSecQName2IntegerInfoConstPtr info, xmlNodePtr node, const xmlChar* attrName, int intValue) { xmlChar* qname; xmlAttrPtr attr; xmlSecAssert2(info != NULL, -1); xmlSecAssert2(node != NULL, -1); xmlSecAssert2(attrName != NULL, -1); /* find and build qname */ qname = xmlSecQName2IntegerGetStringFromInteger(info, node, intValue); if(qname == NULL) { xmlSecInternalError4("xmlSecQName2IntegerGetStringFromInteger", NULL, "node=%s,attrName=%s,intValue=%d", xmlSecErrorsSafeString(node->name), xmlSecErrorsSafeString(attrName), intValue); return(-1); } attr = xmlSetProp(node, attrName, qname); if(attr == NULL) { xmlSecInternalError4("xmlSetProp", NULL, "node=%s,attrName=%s,intValue=%d", xmlSecErrorsSafeString(node->name), xmlSecErrorsSafeString(attrName), intValue); xmlFree(qname); return(-1); } xmlFree(qname); return(0); } /** * @brief Prints @p intValue into @p output. * @param info the qname<->integer mapping information. * @param intValue the integer value. * @param name the value name to print. * @param output the pointer to output FILE. */ void xmlSecQName2IntegerDebugDump(xmlSecQName2IntegerInfoConstPtr info, int intValue, const xmlChar* name, FILE* output) { xmlSecQName2IntegerInfoConstPtr qnameInfo; xmlSecAssert(info != NULL); xmlSecAssert(name != NULL); xmlSecAssert(output != NULL); qnameInfo = xmlSecQName2IntegerGetInfo(info, intValue); if(qnameInfo != NULL) { fprintf(output, "== %s: %d (name=\"%s\", href=\"%s\")\n", name, intValue, (qnameInfo->qnameLocalPart) ? qnameInfo->qnameLocalPart : BAD_CAST "", (qnameInfo->qnameHref) ? qnameInfo->qnameHref : BAD_CAST ""); } } /** * @brief Prints @p intValue into @p output in XML format. * @param info the qname<->integer mapping information. * @param intValue the integer value. * @param name the value name to print. * @param output the pointer to output FILE. */ void xmlSecQName2IntegerDebugXmlDump(xmlSecQName2IntegerInfoConstPtr info, int intValue, const xmlChar* name, FILE* output) { xmlSecQName2IntegerInfoConstPtr qnameInfo; xmlSecAssert(info != NULL); xmlSecAssert(name != NULL); xmlSecAssert(output != NULL); qnameInfo = xmlSecQName2IntegerGetInfo(info, intValue); if(qnameInfo != NULL) { fprintf(output, "<%s value=\"%d\" href=\"%s\">%s\n", name, intValue, (qnameInfo->qnameHref) ? qnameInfo->qnameHref : BAD_CAST "", (qnameInfo->qnameLocalPart) ? qnameInfo->qnameLocalPart : BAD_CAST "", name); } } /****************************************************************************** * * QName <-> Bits mask mapping * *****************************************************************************/ /** * @brief Converts @p mask to qname. * @param info the qname<->bit mask mapping information. * @param mask the bit mask. * @return pointer to the qname info for @p mask or NULL if mask is unknown. */ xmlSecQName2BitMaskInfoConstPtr xmlSecQName2BitMaskGetInfo(xmlSecQName2BitMaskInfoConstPtr info, xmlSecBitMask mask) { unsigned int ii; xmlSecAssert2(info != NULL, NULL); for(ii = 0; info[ii].qnameLocalPart != NULL; ii++) { xmlSecAssert2(info[ii].mask != 0, NULL); if(info[ii].mask == mask) { return(&info[ii]); } } return(NULL); } /** * @brief Converts @p qnameLocalPart to @p mask. * @param info the qname<->bit mask mapping information. * @param qnameLocalPart the qname LocalPart value. * @param qnameHref the qname Href value. * @param mask the pointer to result mask. * @return 0 on success or a negative value if an error occurs. */ int xmlSecQName2BitMaskGetBitMask( xmlSecQName2BitMaskInfoConstPtr info, const xmlChar* qnameLocalPart, const xmlChar* qnameHref, xmlSecBitMask* mask ) { unsigned int ii; xmlSecAssert2(info != NULL, -1); xmlSecAssert2(qnameLocalPart != NULL, -1); xmlSecAssert2(mask != NULL, -1); for(ii = 0; info[ii].qnameLocalPart != NULL; ii++) { xmlSecAssert2(info[ii].mask != 0, -1); if(xmlStrEqual(info[ii].qnameLocalPart, qnameLocalPart) && xmlStrEqual(info[ii].qnameHref, qnameHref)) { (*mask) = info[ii].mask; return(0); } } return(-1); } /** * @brief Converts a QName string to a bit mask value. * @details Converts @p qname into a bit mask value in context of @p node. * @param info the qname<->bit mask mapping information. * @param node the pointer to node. * @param qname the qname string. * @param mask the pointer to result mask value. * @return 0 on success or a negative value if an error occurs. */ int xmlSecQName2BitMaskGetBitMaskFromString(xmlSecQName2BitMaskInfoConstPtr info, xmlNodePtr node, const xmlChar* qname, xmlSecBitMask* mask) { const xmlChar* qnameLocalPart = NULL; xmlChar* qnamePrefix = NULL; const xmlChar* qnameHref; xmlNsPtr ns; int ret; xmlSecAssert2(info != NULL, -1); xmlSecAssert2(node != NULL, -1); xmlSecAssert2(qname != NULL, -1); xmlSecAssert2(mask != NULL, -1); qnameLocalPart = xmlStrchr(qname, ':'); if(qnameLocalPart != NULL) { int qnameLen; XMLSEC_SAFE_CAST_PTRDIFF_TO_INT((qnameLocalPart - qname), qnameLen, return(-1), NULL); qnamePrefix = xmlStrndup(qname, qnameLen); if(qnamePrefix == NULL) { xmlSecStrdupError(qname, NULL); return(-1); } qnameLocalPart++; } else { qnamePrefix = NULL; qnameLocalPart = qname; } /* search namespace href */ ns = xmlSearchNs(node->doc, node, qnamePrefix); if((ns == NULL) && (qnamePrefix != NULL)) { xmlSecXmlError2("xmlSearchNs", NULL, "node=%s", xmlSecErrorsSafeString(node->name)); if(qnamePrefix != NULL) { xmlFree(qnamePrefix); } return(-1); } qnameHref = (ns != NULL) ? ns->href : BAD_CAST NULL; /* and finally search for mask */ ret = xmlSecQName2BitMaskGetBitMask(info, qnameLocalPart, qnameHref, mask); if(ret < 0) { xmlSecInternalError4("xmlSecQName2BitMaskGetBitMask", NULL, "node=%s,qnameLocalPart=%s,qnameHref=%s", xmlSecErrorsSafeString(node->name), xmlSecErrorsSafeString(qnameLocalPart), xmlSecErrorsSafeString(qnameHref)); if(qnamePrefix != NULL) { xmlFree(qnamePrefix); } return(-1); } if(qnamePrefix != NULL) { xmlFree(qnamePrefix); } return(0); } /** * @brief Creates a QName string from a bit mask value. * @details Creates qname string for @p mask in context of given @p node. Caller * is responsible for freeing returned string with xmlFree. * @param info the qname<->bit mask mapping information. * @param node the pointer to node. * @param mask the mask. * @return pointer to newly allocated string on success or NULL if an error occurs. */ xmlChar* xmlSecQName2BitMaskGetStringFromBitMask(xmlSecQName2BitMaskInfoConstPtr info, xmlNodePtr node, xmlSecBitMask mask) { xmlSecQName2BitMaskInfoConstPtr qnameInfo; xmlSecAssert2(info != NULL, NULL); xmlSecAssert2(node != NULL, NULL); qnameInfo = xmlSecQName2BitMaskGetInfo(info, mask); if(qnameInfo == NULL) { xmlSecInternalError3("xmlSecQName2BitMaskGetInfo", NULL, "node=%s,mask=%u", xmlSecErrorsSafeString(node->name), mask); return(NULL); } return(xmlSecGetQName(node, qnameInfo->qnameHref, qnameInfo->qnameLocalPart)); } /** * @brief Reads QName elements and accumulates their values into a bit mask. * @details Reads <@p nodeNs:@p nodeName> elements and puts the result bit mask * into @p mask. When function exits, @p node points to the first element node * after all the <@p nodeNs:@p nodeName> elements. * @param info the qname<->bit mask mapping information. * @param node the start. * @param nodeName the mask nodes name. * @param nodeNs the mask nodes namespace. * @param stopOnUnknown if this flag is set then function exits if unknown * value was found. * @param mask the pointer to result mask. * @return 0 on success or a negative value if an error occurs. */ int xmlSecQName2BitMaskNodesRead(xmlSecQName2BitMaskInfoConstPtr info, xmlNodePtr* node, const xmlChar* nodeName, const xmlChar* nodeNs, int stopOnUnknown, xmlSecBitMask* mask) { xmlNodePtr cur; xmlChar* content; xmlSecBitMask tmp; int ret; xmlSecAssert2(info != NULL, -1); xmlSecAssert2(node != NULL, -1); xmlSecAssert2(mask != NULL, -1); xmlSecAssert2(nodeName != NULL, -1); (*mask) = 0; cur = (*node); while((cur != NULL) && (xmlSecCheckNodeName(cur, nodeName, nodeNs))) { content = xmlSecGetNodeContentAndTrim(cur); if(content == NULL) { xmlSecInternalError2("xmlSecGetNodeContentAndTrim", NULL, "node=%s", xmlSecErrorsSafeString(cur->name)); return(-1); } ret = xmlSecQName2BitMaskGetBitMaskFromString(info, cur, content, &tmp); if(ret < 0) { xmlSecInternalError2("xmlSecQName2BitMaskGetBitMaskFromString", NULL, "value=%s", xmlSecErrorsSafeString(content)); xmlFree(content); return(-1); } if((stopOnUnknown != 0) && (tmp == 0)) { xmlSecInvalidNodeContentError(cur, NULL, "unknown value"); xmlFree(content); return(-1); } xmlFree(content); (*mask) |= tmp; cur = xmlSecGetNextElementNode(cur->next); } (*node) = cur; return(0); } /** * @brief Writes bit mask values as QName child elements. * @details Writes <@p nodeNs:@p nodeName> elements with values from @p mask to @p node. * @param info the qname<->bit mask mapping information. * @param node the parent element for mask nodes. * @param nodeName the mask nodes name. * @param nodeNs the mask nodes namespace. * @param mask the bit mask. * * Note: if xmlNodeSetContent() fails, the child node created by xmlSecAddChild() * is left in the tree (not unlinked or freed). * * @return 0 on success or a negative value if an error occurs. */ int xmlSecQName2BitMaskNodesWrite(xmlSecQName2BitMaskInfoConstPtr info, xmlNodePtr node, const xmlChar* nodeName, const xmlChar* nodeNs, xmlSecBitMask mask) { unsigned int ii; xmlSecAssert2(info != NULL, -1); xmlSecAssert2(node != NULL, -1); xmlSecAssert2(nodeName != NULL, -1); for(ii = 0; info[ii].qnameLocalPart != NULL; ii++) { xmlSecAssert2(info[ii].mask != 0, -1); if((mask & info[ii].mask) != 0) { xmlNodePtr cur; xmlChar* qname; qname = xmlSecGetQName(node, info[ii].qnameHref, info[ii].qnameLocalPart); if(qname == NULL) { xmlSecXmlError2("xmlSecGetQName", NULL, "nodeName=%s", xmlSecErrorsSafeString(nodeName)); return(-1); } cur = xmlSecAddChild(node, nodeName, nodeNs); if(cur == NULL) { xmlSecXmlError2("xmlSecAddChild", NULL, "nodeName=%s", xmlSecErrorsSafeString(nodeName)); xmlFree(qname); return(-1); } #if LIBXML_VERSION >= 21300 if(xmlNodeSetContent(cur, qname) < 0) { xmlSecXmlError("xmlNodeSetContent", cur); xmlFree(qname); return(-1); } #else /* LIBXML_VERSION >= 21300 */ /* libxml2 < 2.13.0: xmlNodeSetContent() returns void and cannot report errors */ xmlNodeSetContent(cur, qname); #endif /* LIBXML_VERSION >= 21300 */ xmlFree(qname); } } return(0); } /** * @brief Prints debug information about a bit mask. * @details Prints debug information about @p mask to @p output. * @param info the qname<->bit mask mapping information. * @param mask the bit mask. * @param name the value name to print. * @param output the pointer to output FILE. */ void xmlSecQName2BitMaskDebugDump(xmlSecQName2BitMaskInfoConstPtr info, xmlSecBitMask mask, const xmlChar* name, FILE* output) { unsigned int ii; xmlSecAssert(info != NULL); xmlSecAssert(name != NULL); xmlSecAssert(output != NULL); if(mask == 0) { return; } fprintf(output, "== %s (0x%08x): ", name, mask); for(ii = 0; info[ii].qnameLocalPart != NULL; ii++) { xmlSecAssert(info[ii].mask != 0); if((mask & info[ii].mask) != 0) { fprintf(output, "name=\"%s\" (href=\"%s\"),", info[ii].qnameLocalPart, (info[ii].qnameHref != NULL) ? info[ii].qnameHref : BAD_CAST ""); } } fprintf(output, "\n"); } /** * @brief Prints debug information about a bit mask in XML format. * @details Prints debug information about @p mask to @p output in XML format. * @param info the qname<->bit mask mapping information. * @param mask the bit mask. * @param name the value name to print. * @param output the pointer to output FILE. */ void xmlSecQName2BitMaskDebugXmlDump(xmlSecQName2BitMaskInfoConstPtr info, xmlSecBitMask mask, const xmlChar* name, FILE* output) { unsigned int ii; xmlSecAssert(info != NULL); xmlSecAssert(name != NULL); xmlSecAssert(output != NULL); if(mask == 0) { return; } fprintf(output, "<%sList>\n", name); for(ii = 0; info[ii].qnameLocalPart != NULL; ii++) { xmlSecAssert(info[ii].mask != 0); if((mask & info[ii].mask) != 0) { fprintf(output, "<%s href=\"%s\">%s\n", name, (info[ii].qnameHref != NULL) ? info[ii].qnameHref : BAD_CAST "", info[ii].qnameLocalPart, name); } } fprintf(output, "\n", name); } /****************************************************************************** * * Windows string conversions * *****************************************************************************/ #if defined(XMLSEC_WINDOWS) /** * @brief Converts input string from UTF8 to Unicode. * @param str the string to convert. * @return a pointer to newly allocated string (must be freed with xmlFree) or NULL if an error occurs. */ LPWSTR xmlSecWin32ConvertUtf8ToUnicode(const xmlChar* str) { LPWSTR res = NULL; xmlSecSize size; int len; int ret; xmlSecAssert2(str != NULL, NULL); /* call MultiByteToWideChar first to get the buffer size */ ret = MultiByteToWideChar(CP_UTF8, 0, (LPCCH)str, -1, NULL, 0); if(ret <= 0) { return(NULL); } len = ret; XMLSEC_SAFE_CAST_INT_TO_SIZE(len, size, return(NULL), NULL); /* allocate buffer */ res = (LPWSTR)xmlMalloc(sizeof(WCHAR) * size); if(res == NULL) { xmlSecMallocError(sizeof(WCHAR) * size, NULL); return(NULL); } /* convert */ ret = MultiByteToWideChar(CP_UTF8, 0, (LPCCH)str, -1, res, len); if(ret <= 0) { xmlFree(res); return(NULL); } /* done */ return(res); } /** * @brief Converts input string from Unicode to UTF8. * @param str the string to convert. * @return a pointer to newly allocated string (must be freed with xmlFree) or NULL if an error occurs. */ xmlChar* xmlSecWin32ConvertUnicodeToUtf8(LPCWSTR str) { xmlChar * res = NULL; xmlSecSize size; int len; int ret; xmlSecAssert2(str != NULL, NULL); /* call WideCharToMultiByte first to get the buffer size */ ret = WideCharToMultiByte(CP_UTF8, 0, str, -1, NULL, 0, NULL, NULL); if(ret <= 0) { return(NULL); } len = ret; XMLSEC_SAFE_CAST_INT_TO_SIZE(len, size, return(NULL), NULL); /* allocate buffer */ res = (xmlChar*)xmlMalloc(sizeof(xmlChar) * size); if(res == NULL) { xmlSecMallocError(sizeof(xmlChar) * size, NULL); return(NULL); } /* convert */ ret = WideCharToMultiByte(CP_UTF8, 0, str, -1, (LPSTR)res, len, NULL, NULL); if(ret <= 0) { xmlFree(res); return(NULL); } /* done */ return(res); } /** * @brief Converts input string from current system locale to Unicode. * @param str the string to convert. * @return a pointer to newly allocated string (must be freed with xmlFree) or NULL if an error occurs. */ LPWSTR xmlSecWin32ConvertLocaleToUnicode(const char* str) { LPWSTR res = NULL; xmlSecSize size; int len; int ret; xmlSecAssert2(str != NULL, NULL); /* call MultiByteToWideChar first to get the buffer size */ ret = MultiByteToWideChar(CP_ACP, 0, str, -1, NULL, 0); if(ret <= 0) { return(NULL); } len = ret; XMLSEC_SAFE_CAST_INT_TO_SIZE(len, size, return(NULL), NULL); /* allocate buffer */ res = (LPWSTR)xmlMalloc(sizeof(WCHAR) * size); if(res == NULL) { xmlSecMallocError(sizeof(WCHAR) * size, NULL); return(NULL); } /* convert */ ret = MultiByteToWideChar(CP_ACP, 0, str, -1, res, len); if(ret <= 0) { xmlFree(res); return(NULL); } /* done */ return(res); } /** * @brief Converts input string from locale to UTF8. * @param str the string to convert. * @return a pointer to newly allocated string (must be freed with xmlFree) or NULL if an error occurs. */ xmlChar* xmlSecWin32ConvertLocaleToUtf8(const char * str) { LPWSTR strW = NULL; xmlChar * res = NULL; xmlSecSize size; int len; int ret; xmlSecAssert2(str != NULL, NULL); strW = xmlSecWin32ConvertLocaleToUnicode(str); if(strW == NULL) { return(NULL); } /* call WideCharToMultiByte first to get the buffer size */ ret = WideCharToMultiByte(CP_UTF8, 0, strW, -1, NULL, 0, NULL, NULL); if(ret <= 0) { xmlFree(strW); return(NULL); } len = ret; XMLSEC_SAFE_CAST_INT_TO_SIZE(len, size, return(NULL), NULL); /* allocate buffer */ res = (xmlChar*)xmlMalloc(sizeof(xmlChar) * size); if(res == NULL) { xmlSecMallocError(sizeof(xmlChar) * size, NULL); xmlFree(strW); return(NULL); } /* convert */ ret = WideCharToMultiByte(CP_UTF8, 0, strW, -1, (LPSTR)res, len, NULL, NULL); if(ret <= 0) { xmlFree(strW); xmlFree(res); return(NULL); } /* done */ xmlFree(strW); return(res); } /** * @brief Converts input string from UTF8 to locale. * @param str the string to convert. * @return a pointer to newly allocated string (must be freed with xmlFree) or NULL if an error occurs. */ char * xmlSecWin32ConvertUtf8ToLocale(const xmlChar* str) { LPWSTR strW = NULL; char * res = NULL; xmlSecSize size; int len; int ret; xmlSecAssert2(str != NULL, NULL); strW = xmlSecWin32ConvertUtf8ToUnicode(str); if(strW == NULL) { return(NULL); } /* call WideCharToMultiByte first to get the buffer size */ ret = WideCharToMultiByte(CP_ACP, 0, strW, -1, NULL, 0, NULL, NULL); if(ret <= 0) { xmlFree(strW); return(NULL); } len = ret; XMLSEC_SAFE_CAST_INT_TO_SIZE(len, size, return(NULL), NULL); /* allocate buffer */ res = (char*)xmlMalloc(sizeof(char) * size); if(res == NULL) { xmlSecMallocError(sizeof(char) * size, NULL); xmlFree(strW); return(NULL); } /* convert */ ret = WideCharToMultiByte(CP_ACP, 0, strW, -1, res, len, NULL, NULL); if(ret <= 0) { xmlFree(strW); xmlFree(res); return(NULL); } /* done */ xmlFree(strW); return(res); } /** * @brief Converts a TSTR string (locale or Unicode) to UTF-8. * @details Converts input string from TSTR (locale or Unicode) to UTF8. * @param str the string to convert. * @return a pointer to newly allocated string (must be freed with xmlFree) or NULL if an error occurs. */ xmlChar* xmlSecWin32ConvertTstrToUtf8(LPCTSTR str) { #ifdef UNICODE return xmlSecWin32ConvertUnicodeToUtf8(str); #else /* UNICODE */ return xmlSecWin32ConvertLocaleToUtf8(str); #endif /* UNICODE */ } /** * @brief Converts a UTF-8 string to TSTR (locale or Unicode). * @details Converts input string from UTF8 to TSTR (locale or Unicode). * @param str the string to convert. * @return a pointer to newly allocated string (must be freed with xmlFree) or NULL if an error occurs. */ LPTSTR xmlSecWin32ConvertUtf8ToTstr(const xmlChar* str) { #ifdef UNICODE return xmlSecWin32ConvertUtf8ToUnicode(str); #else /* UNICODE */ return xmlSecWin32ConvertUtf8ToLocale(str); #endif /* UNICODE */ } #endif /* defined(XMLSEC_WINDOWS) */