diff --git a/build_tools/menuselect-deps.in b/build_tools/menuselect-deps.in index 161c67b39a..f66d7bd64d 100644 --- a/build_tools/menuselect-deps.in +++ b/build_tools/menuselect-deps.in @@ -32,6 +32,7 @@ KQUEUE=@PBX_KQUEUE@ LDAP=@PBX_LDAP@ LIBEDIT=@PBX_LIBEDIT@ LIBXML2=@PBX_LIBXML2@ +LIBXSLT=@PBX_LIBXSLT@ XMLSTARLET=@PBX_XMLSTARLET@ BASH=@PBX_BASH@ LUA=@PBX_LUA@ diff --git a/configure b/configure index e227e22bdb..1705820095 100755 --- a/configure +++ b/configure @@ -15022,6 +15022,8 @@ if test "${DISABLE_XMLDOC}" != "yes"; then $as_echo "#define AST_XML_DOCS 1" >>confdefs.h +fi + if test "x${PBX_LIBXSLT}" != "x1" -a "${USE_LIBXSLT}" != "no"; then pbxlibdir="" @@ -15215,8 +15217,6 @@ fi -fi - # Check whether --enable-permanent-dlopen was given. if test "${enable_permanent_dlopen+set}" = set; then : enableval=$enable_permanent_dlopen; case "${enableval}" in @@ -23863,8 +23863,8 @@ rm -f core conftest.err conftest.$ac_objext \ if test "x${PBX_NETSNMP}" != "x1" -a "${USE_NETSNMP}" != "no"; then pkg_failed=no -{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for netsnmp-agent" >&5 -$as_echo_n "checking for netsnmp-agent... " >&6; } +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for NETSNMP" >&5 +$as_echo_n "checking for NETSNMP... " >&6; } if test -n "$NETSNMP_CFLAGS"; then pkg_cv_NETSNMP_CFLAGS="$NETSNMP_CFLAGS" @@ -23904,7 +23904,7 @@ fi if test $pkg_failed = yes; then - { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then @@ -23925,7 +23925,7 @@ fi elif test $pkg_failed = untried; then - { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } PBX_NETSNMP=0 diff --git a/configure.ac b/configure.ac index 6071ef4aaf..9fcea1816f 100644 --- a/configure.ac +++ b/configure.ac @@ -768,11 +768,11 @@ AC_ARG_ENABLE([xmldoc], AC_SUBST([DISABLE_XMLDOC]) if test "${DISABLE_XMLDOC}" != "yes"; then AC_DEFINE([AST_XML_DOCS], 1, [Define to enable XML documentation.]) - AST_EXT_LIB_CHECK([LIBXSLT], [xslt], [xsltLoadStylesheetPI], [libxslt/xsltInternals.h], [${LIBXML2_LIB}], [${LIBXML2_INCLUDE}]) - AST_EXT_LIB_CHECK([LIBXSLT_CLEANUP], [xslt], [xsltCleanupGlobals], [libxslt/xsltInternals.h], [${LIBXML2_LIB}], [${LIBXML2_INCLUDE}]) - fi +AST_EXT_LIB_CHECK([LIBXSLT], [xslt], [xsltLoadStylesheetPI], [libxslt/xsltInternals.h], [${LIBXML2_LIB}], [${LIBXML2_INCLUDE}]) +AST_EXT_LIB_CHECK([LIBXSLT_CLEANUP], [xslt], [xsltCleanupGlobals], [libxslt/xsltInternals.h], [${LIBXML2_LIB}], [${LIBXML2_INCLUDE}]) + AC_ARG_ENABLE([permanent-dlopen], [AS_HELP_STRING([--enable-permanent-dlopen], [Enable when your libc has a permanent dlopen like musl])], diff --git a/include/asterisk/config.h b/include/asterisk/config.h index c66b10fbf6..50b358e470 100644 --- a/include/asterisk/config.h +++ b/include/asterisk/config.h @@ -1004,6 +1004,23 @@ int ast_variable_list_replace_variable(struct ast_variable **head, struct ast_va struct ast_str *ast_variable_list_join(const struct ast_variable *head, const char *item_separator, const char *name_value_separator, const char *quote_char, struct ast_str **str); +/*! + * \brief Parse a string into an ast_variable list. The reverse of ast_variable_list_join + * + * \param input The name-value pair string to parse. + * \param item_separator The string used to separate the list items. + * Only the first character in the string will be used. + * If NULL, "," will be used. + * \param name_value_separator The string used to separate each item's name and value. + * Only the first character in the string will be used. + * If NULL, "=" will be used. + * + * \retval A pointer to a list of ast_variables. + * \retval NULL if there was an error or no variables could be parsed. + */ +struct ast_variable *ast_variable_list_from_string(const char *input, const char *item_separator, + const char *name_value_separator); + /*! * \brief Update variable value within a config * diff --git a/include/asterisk/xml.h b/include/asterisk/xml.h index 73006e7567..7b30ed88f7 100644 --- a/include/asterisk/xml.h +++ b/include/asterisk/xml.h @@ -21,9 +21,12 @@ * \brief Asterisk XML abstraction layer */ +#include "asterisk/vector.h" + struct ast_xml_node; struct ast_xml_doc; struct ast_xml_xpath_results; +struct ast_xslt_doc; /*! * \brief Initialize the XML library implementation. @@ -184,6 +187,19 @@ int ast_xml_set_attribute(struct ast_xml_node *node, const char *name, const cha */ struct ast_xml_node *ast_xml_find_element(struct ast_xml_node *root_node, const char *name, const char *attrname, const char *attrvalue); struct ast_xml_ns *ast_xml_find_namespace(struct ast_xml_doc *doc, struct ast_xml_node *node, const char *ns_name); + +/*! + * \brief Get the prefix of a namespace. + * \param ns The namespace + * \return The prefix of the namespace. + */ +const char *ast_xml_get_ns_prefix(struct ast_xml_ns *ns); + +/*! + * \brief Get the href of a namespace. + * \param ns The namespace + * \return The href of the namespace. + */ const char *ast_xml_get_ns_href(struct ast_xml_ns *ns); /*! @@ -259,6 +275,15 @@ int ast_xml_xpath_num_results(struct ast_xml_xpath_results *results); */ struct ast_xml_node *ast_xml_xpath_get_first_result(struct ast_xml_xpath_results *results); +/*! + * \brief Return a specific result node of an XPath query + * \param results The XPath results object to get the result from + * \param n The index of the result to get + * \return The nth result in the XPath object on success + * \retval NULL on error + */ +struct ast_xml_node *ast_xml_xpath_get_result(struct ast_xml_xpath_results *results, int n); + /*! * \brief Execute an XPath query on an XML document * \param doc The XML document to query @@ -270,4 +295,80 @@ struct ast_xml_node *ast_xml_xpath_get_first_result(struct ast_xml_xpath_results */ struct ast_xml_xpath_results *ast_xml_query(struct ast_xml_doc *doc, const char *xpath_str); +/*! + * \brief Namespace definition + */ +struct ast_xml_namespace_def { + const char *prefix; + const char *href; +}; + +AST_VECTOR(ast_xml_namespace_def_vector, struct ast_xml_namespace_def); + +/*! + * \brief Execute an XPath query on an XML document with namespaces + * \param doc XML document to query + * \param xpath_str The XPath query string to execute on the document + * \param namespaces A vector of ast_xml_namespace structures (not pointers) + * \return An object containing the results of the XPath query on success + * \retval NULL on failure + */ +struct ast_xml_xpath_results *ast_xml_query_with_namespaces(struct ast_xml_doc *doc, const char *xpath_str, + struct ast_xml_namespace_def_vector *namespaces); + +#ifdef HAVE_LIBXSLT + +/*! \brief Open an XSLT document that resides in memory. + * + * \param buffer The address where the stylesheet is stored + * \param size The number of bytes in the stylesheet + * + * \return The stylesheet document. Must be closed with ast_xslt_close(). + */ +struct ast_xslt_doc *ast_xslt_read_memory(char *buffer, size_t size); + +/*! + * \brief Open an XSLT document. + * + * \param filename stylesheet path. + * + * \return The stylesheet document. Must be closed with ast_xslt_close(). + */ +struct ast_xslt_doc *ast_xslt_open(char *filename); + +/*! + * \brief Close a stylesheet document and free its resources. + * + * \param xslt XSLT stylesheet to close + */ +void ast_xslt_close(struct ast_xslt_doc *xslt); + +/*! + * \brief Apply an XSLT stylesheet to an XML document + * + * \param xslt XSLT stylesheet to apply. + * \param xml XML document the stylesheet will be applied to. + * \param params An array of name value pairs to pass as parameters + * The array must terminate with a NULL sentinel. + * Example: { "name1", "value1", "name2", "value2", NULL } + * + * \return A pointer to the result document which must be freed with ast_xml_close() + */ +struct ast_xml_doc *ast_xslt_apply(struct ast_xslt_doc *xslt, struct ast_xml_doc *doc, const char **params); + +/*! + * \brief Save the results of applying a stylesheet to a string + * + * \param buffer[out] A pointer to a char * to receive the address of the result string. + * The buffer must be freed with ast_xml_free_text(). + * \param length[out] A pointer to an int to receive the result string length. + * \param result The result document from ast_xslt_apply. + * \param xslt The stylesheet that was applied. + * + * \return 0 on success, any other value on failure. + */ +int ast_xslt_save_result_to_string(char **buffer, int *length, struct ast_xml_doc *result, + struct ast_xslt_doc *xslt); + +#endif /* HAVE_LIBXSLT */ #endif /* _ASTERISK_XML_H */ diff --git a/main/config.c b/main/config.c index b74f6123f1..da893b64be 100644 --- a/main/config.c +++ b/main/config.c @@ -724,6 +724,39 @@ struct ast_str *ast_variable_list_join(const struct ast_variable *head, const ch return local_str; } +struct ast_variable *ast_variable_list_from_string(const char *input, const char *item_separator, + const char *name_value_separator) +{ + char item_sep; + char nv_sep; + struct ast_variable *new_list = NULL; + struct ast_variable *new_var = NULL; + char *item_string; + char *item; + char *item_name; + char *item_value; + + if (ast_strlen_zero(input)) { + return NULL; + } + + item_sep = ast_strlen_zero(item_separator) ? ',' : item_separator[0]; + nv_sep = ast_strlen_zero(name_value_separator) ? '=' : name_value_separator[0]; + item_string = ast_strip(ast_strdupa(input)); + + while ((item = ast_strsep(&item_string, item_sep, AST_STRSEP_ALL))) { + item_name = ast_strsep(&item, nv_sep, AST_STRSEP_ALL); + item_value = ast_strsep(&item, nv_sep, AST_STRSEP_ALL); + new_var = ast_variable_new(item_name, item_value, ""); + if (!new_var) { + ast_variables_destroy(new_list); + return NULL; + } + ast_variable_list_append(&new_list, new_var); + } + return new_list; +} + const char *ast_config_option(struct ast_config *cfg, const char *cat, const char *var) { const char *tmp; diff --git a/main/xml.c b/main/xml.c index 88c9edffde..987f125399 100644 --- a/main/xml.c +++ b/main/xml.c @@ -36,25 +36,33 @@ #include #include #include +#include /* libxml2 ast_xml implementation. */ #ifdef HAVE_LIBXSLT #include #include + #include #endif /* HAVE_LIBXSLT */ int ast_xml_init(void) { LIBXML_TEST_VERSION - +#ifdef HAVE_LIBXSLT + xsltInit(); +#endif return 0; } int ast_xml_finish(void) { xmlCleanupParser(); +#ifdef HAVE_LIBXSLT #ifdef HAVE_LIBXSLT_CLEANUP xsltCleanupGlobals(); +#else + xsltUninit(); +#endif #endif return 0; @@ -68,6 +76,8 @@ struct ast_xml_doc *ast_xml_open(char *filename) return NULL; } + xmlSubstituteEntitiesDefault(1); + doc = xmlReadFile(filename, NULL, XML_PARSE_RECOVER); if (!doc) { return NULL; @@ -309,6 +319,11 @@ struct ast_xml_ns *ast_xml_find_namespace(struct ast_xml_doc *doc, struct ast_xm return (struct ast_xml_ns *) ns; } +const char *ast_xml_get_ns_prefix(struct ast_xml_ns *ns) +{ + return (const char *) ((xmlNsPtr) ns)->prefix; +} + const char *ast_xml_get_ns_href(struct ast_xml_ns *ns) { return (const char *) ((xmlNsPtr) ns)->href; @@ -376,13 +391,24 @@ struct ast_xml_node *ast_xml_xpath_get_first_result(struct ast_xml_xpath_results return (struct ast_xml_node *) ((xmlXPathObjectPtr) results)->nodesetval->nodeTab[0]; } +struct ast_xml_node *ast_xml_xpath_get_result(struct ast_xml_xpath_results *results, int i) +{ + return (struct ast_xml_node *) ((xmlXPathObjectPtr) results)->nodesetval->nodeTab[i]; +} + void ast_xml_xpath_results_free(struct ast_xml_xpath_results *results) { + if (!results) { + return; + } xmlXPathFreeObject((xmlXPathObjectPtr) results); } int ast_xml_xpath_num_results(struct ast_xml_xpath_results *results) { + if (!results) { + return 0; + } return ((xmlXPathObjectPtr) results)->nodesetval->nodeNr; } @@ -408,4 +434,149 @@ struct ast_xml_xpath_results *ast_xml_query(struct ast_xml_doc *doc, const char return (struct ast_xml_xpath_results *) result; } +struct ast_xml_xpath_results *ast_xml_query_with_namespaces(struct ast_xml_doc *doc, const char *xpath_str, + struct ast_xml_namespace_def_vector *namespaces) +{ + xmlXPathContextPtr context; + xmlXPathObjectPtr result; + int i; + + if (!(context = xmlXPathNewContext((xmlDoc *) doc))) { + ast_log(LOG_ERROR, "Could not create XPath context!\n"); + return NULL; + } + + for (i = 0; i < AST_VECTOR_SIZE(namespaces); i++) { + struct ast_xml_namespace_def ns = AST_VECTOR_GET(namespaces, i); + if (xmlXPathRegisterNs(context, (xmlChar *)ns.prefix, + (xmlChar *)ns.href) != 0) { + xmlXPathFreeContext(context); + ast_log(LOG_ERROR, "Could not register namespace %s:%s\n", + ns.prefix, ns.href); + return NULL; + } + } + + result = xmlXPathEvalExpression((xmlChar *) xpath_str, context); + xmlXPathFreeContext(context); + if (!result) { + ast_log(LOG_WARNING, "Error for query: %s\n", xpath_str); + return NULL; + } + if (xmlXPathNodeSetIsEmpty(result->nodesetval)) { + xmlXPathFreeObject(result); + ast_debug(5, "No results for query: %s\n", xpath_str); + return NULL; + } + return (struct ast_xml_xpath_results *) result; +} + +#ifdef HAVE_LIBXSLT +struct ast_xslt_doc *ast_xslt_open(char *filename) +{ + xsltStylesheet *xslt; + xmlDoc *xml; + + xmlSubstituteEntitiesDefault(1); + + xml = xmlReadFile(filename, NULL, XML_PARSE_RECOVER); + if (!xml) { + return NULL; + } + + if (xmlXIncludeProcess(xml) < 0) { + xmlFreeDoc(xml); + return NULL; + } + xmlXPathOrderDocElems(xml); + + if (!(xslt = xsltParseStylesheetDoc(xml))) { + xmlFreeDoc(xml); + return NULL; + } + + return (struct ast_xslt_doc *) xslt; +} + +struct ast_xslt_doc *ast_xslt_read_memory(char *buffer, size_t size) +{ + xsltStylesheet *xslt; + xmlDoc *doc; + + if (!buffer) { + return NULL; + } + + xmlSubstituteEntitiesDefault(1); + + if (!(doc = xmlParseMemory(buffer, (int) size))) { + return NULL; + } + + if (xmlXIncludeProcess(doc) < 0) { + xmlFreeDoc(doc); + return NULL; + } + + if (!(xslt = xsltParseStylesheetDoc(doc))) { + xmlFreeDoc(doc); + return NULL; + } + + return (struct ast_xslt_doc *) xslt; +} + +void ast_xslt_close(struct ast_xslt_doc *axslt) +{ + if (!axslt) { + return; + } + + xsltFreeStylesheet((xsltStylesheet *) axslt); +} + +struct ast_xml_doc *ast_xslt_apply(struct ast_xslt_doc *axslt, struct ast_xml_doc *axml, const char **params) +{ + xsltStylesheet *xslt = (xsltStylesheet *)axslt; + xmlDoc *xml = (xmlDoc *)axml; + xsltTransformContextPtr ctxt; + xmlNs *ns; + xmlDoc *res; + int options = XSLT_PARSE_OPTIONS; + + /* + * Normally we could just call xsltApplyStylesheet() without creating + * our own transform context but we need to pass parameters to it + * that have namespace prefixes and that's not supported. Instead + * we have to create a transform context, iterate over the namespace + * declarations in the stylesheet (not the incoming xml document), + * and add them to the transform context's xpath context. + * + * The alternative would be to pass the parameters with namespaces + * as text strings but that's not intuitive and results in much + * slower performance than adding the namespaces here. + */ + ctxt = xsltNewTransformContext(xslt, xml); + xsltSetCtxtParseOptions(ctxt, options); + + for (ns = xslt->doc->children->nsDef; ns; ns = ns->next) { + if (xmlXPathRegisterNs(ctxt->xpathCtxt, ns->prefix, ns->href) != 0) { + xmlXPathFreeContext(ctxt->xpathCtxt); + xsltFreeTransformContext(ctxt); + return NULL; + } + } + + res = xsltApplyStylesheetUser(xslt, xml, params, NULL, NULL, ctxt); + + return (struct ast_xml_doc *)res; +} + +int ast_xslt_save_result_to_string(char **buffer, int *length, struct ast_xml_doc *result, + struct ast_xslt_doc *axslt) +{ + return xsltSaveResultToString((xmlChar **)buffer, length, (xmlDoc *)result, (xsltStylesheet *)axslt); +} + +#endif /* defined(HAVE_LIBXSLT) */ #endif /* defined(HAVE_LIBXML2) */ diff --git a/tests/test_config.c b/tests/test_config.c index 5b44b446e0..1a0ddaf836 100644 --- a/tests/test_config.c +++ b/tests/test_config.c @@ -1943,6 +1943,35 @@ AST_TEST_DEFINE(variable_list_join_replace) return AST_TEST_PASS; } + +AST_TEST_DEFINE(variable_list_from_string) +{ + RAII_VAR(struct ast_variable *, list, NULL, ast_variables_destroy); + RAII_VAR(struct ast_str *, str, NULL, ast_free); + char *parse_string; + + switch (cmd) { + case TEST_INIT: + info->name = "variable_list_from_string"; + info->category = "/main/config/"; + info->summary = "Test parsing a string into a variable list"; + info->description = info->summary; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + parse_string = "abc = 'def', ghi = 'j,kl', mno='pq=r', stu = 'vwx=\"yz\", ABC = \"DEF\"'"; + list = ast_variable_list_from_string(parse_string, ",", "="); + ast_test_validate(test, list != NULL); + str = ast_variable_list_join(list, "|", "^", "@", NULL); + + ast_test_validate(test, + strcmp(ast_str_buffer(str), "abc^@def@|ghi^@j,kl@|mno^@pq=r@|stu^@vwx=\"yz\", ABC = \"DEF\"@") == 0); + + return AST_TEST_PASS; +} + static int unload_module(void) { AST_TEST_UNREGISTER(config_save); @@ -1956,6 +1985,7 @@ static int unload_module(void) AST_TEST_UNREGISTER(config_dialplan_function); AST_TEST_UNREGISTER(variable_lists_match); AST_TEST_UNREGISTER(variable_list_join_replace); + AST_TEST_UNREGISTER(variable_list_from_string); return 0; } @@ -1972,6 +2002,7 @@ static int load_module(void) AST_TEST_REGISTER(config_dialplan_function); AST_TEST_REGISTER(variable_lists_match); AST_TEST_REGISTER(variable_list_join_replace); + AST_TEST_REGISTER(variable_list_from_string); return AST_MODULE_LOAD_SUCCESS; }