[njs] Added "xml" module for working with XML documents.

Dmitry Volyntsev xeioex at nginx.com
Thu Jan 26 06:00:00 UTC 2023


details:   https://hg.nginx.org/njs/rev/99b9f83e4d4d
branches:  
changeset: 2028:99b9f83e4d4d
user:      Dmitry Volyntsev <xeioex at nginx.com>
date:      Wed Jan 25 21:54:47 2023 -0800
description:
Added "xml" module for working with XML documents.

    - xml.parse(string|buffer) returns an XMLDoc wrapper object around
        XML structure.
    - xml.c14n(root_node[, excluding_node]]) canonicalizes root_node and
        its children according to https://www.w3.org/TR/xml-c14n, optionally
        excluding_node allows to omit from the output a part of the
        document.
    - xml.exclusiveC14n(root_node[, excluding_node[, withComments [,
            prefix_list]]]) canonicalizes root_node and its children
        according to https://www.w3.org/TR/xml-exc-c14n/.  excluding_node
        allows to omit from the output a part of the document
        corresponding to the node and its children.  withComments
        is a boolean and is false by default. When withComments is true
        canonicalization corresponds to
        http://www.w3.org/2001/10/xml-exc-c14n#WithComments.  prefix_list is
        an optional string with a space separated namespace prefixes for
        namespaces that should also be included into the output.

    - XMLDoc an XMLDoc wrapper object around XML structure.
        doc.xxx returns the first root tag named "xxx" as XMLNode wrapper
        object.

    - XMLNode an XMLNode wrapper object around XML tag node.
        node.$tag$xxx returns the first child tag named "xxx" as XMLNode
        wrapper object.
        node.xxx a shorthand syntax for node.$tag$xxx.
        node.$tags$xxx? returns an array of all children tags named xxx.

        node.$attr$xxx returns an attribute value of xxx.
        node.$attrs returns an XMLAttr wrapper object.
        node.$name returns the tag name of the node.
        node.$ns returns the namespace of the node.
        node.$parent returns the parent of the node.
        node.$text returns the node's content.

    - XMLAttrs an XMLAttrs wrapper object around XML node attributes.
        attrs.xxx returns a value of the xxx attribute.

    - Example:
        const xml = require("xml");
        let data = `<note><to b="bar" a= "foo" >Tove</to><from>Jani</from></note>`;
        let doc = xml.parse(data);

        console.log(doc.note.to.$text) /* 'Tove' */
        console.log(doc.note.to.$attr$b) /* 'bar' */
        console.log(doc.note.$tags[1].$text) /* 'Jani' */

        let dec = new TextDecoder();
        let c14n = dec.decode(xml.exclusiveC14n(doc.note));
        console.log(c14n) /* '<note><to a="foo" b="bar">Tove</to><from>Jani</from></note>' */

        c14n = dec.decode(xml.exclusiveC14n(doc.note.to));
        console.log(c14n) /* '<to a="foo" b="bar">Tove</to>' */

        c14n = dec.decode(xml.exclusiveC14n(doc.note, doc.note.to /* excluding 'to' */));
        console.log(c14n) /* '<note><from>Jani</from></note>' */

diffstat:

 auto/libxml2                                       |    77 +
 auto/modules                                       |     8 +
 auto/options                                       |     2 +
 configure                                          |     1 +
 external/njs_xml_module.c                          |  1322 ++++++++++++++++++++
 nginx/config                                       |     5 +-
 nginx/config.make                                  |     2 +-
 nginx/ngx_js.c                                     |     2 +
 src/test/njs_unit_test.c                           |    93 +
 test/harness/compatNjs.js                          |    18 +
 test/harness/compatXml.js                          |     8 +
 test/xml/README.rst                                |    26 +
 test/xml/auth_r.xml                                |    27 +
 test/xml/auth_r_prefix_list.xml                    |    28 +
 test/xml/auth_r_prefix_list_signed.xml             |    23 +
 test/xml/auth_r_signed.xml                         |    22 +
 test/xml/auth_r_signed2.xml                        |    22 +
 test/xml/auth_r_with_comments_signed.xml           |    22 +
 test/xml/example.com.crt                           |    13 +
 test/xml/response_assertion_and_message_signed.xml |    46 +
 test/xml/response_signed.xml                       |    42 +
 test/xml/response_signed_broken.xml                |    42 +
 test/xml/response_signed_broken2.xml               |    42 +
 test/xml/saml_verify.t.js                          |   228 +++
 24 files changed, 2118 insertions(+), 3 deletions(-)

diffs (truncated from 2287 to 1000 lines):

diff -r 742347841e34 -r 99b9f83e4d4d auto/libxml2
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/auto/libxml2	Wed Jan 25 21:54:47 2023 -0800
@@ -0,0 +1,77 @@
+
+# Copyright (C) Dmitry Volyntsev
+# Copyright (C) NGINX, Inc.
+
+NJS_HAVE_LIBXML2=NO
+
+if [ $NJS_LIBXML2 = YES ]; then
+    njs_found=no
+
+    njs_feature="libxml2"
+    njs_feature_name=NJS_HAVE_LIBXML2
+    njs_feature_run=no
+    njs_feature_incs="/usr/include/libxml2"
+    njs_feature_libs="-lxml2"
+    njs_feature_test="#include <libxml/parser.h>
+                      #include <libxml/tree.h>
+
+                      int main() {
+                          xmlDocPtr  tree;
+                          tree = xmlReadMemory(NULL, 0, NULL, NULL, 0);
+                          xmlFreeDoc(tree);
+                          xmlCleanupParser();
+                          return 0;
+                      }"
+    . auto/feature
+
+    if [ $njs_found = no ]; then
+
+        # FreeBSD port
+
+        njs_feature="libxml2 in /usr/local/"
+        njs_feature_incs="/usr/local/include/libxml2 /usr/local/include"
+        njs_feature_libs="-L/usr/local/lib -lxml2"
+
+        . auto/feature
+    fi
+
+    if [ $njs_found = no ]; then
+
+        # NetBSD port
+
+        njs_feature="libxml2 in /usr/pkg/"
+        njs_feature_incs="/usr/pkg/include/libxml2 /usr/pkg/include"
+        njs_feature_libs="-L/usr/pkg/lib -lxml2"
+
+        . auto/feature
+    fi
+
+    if [ $njs_found = no ]; then
+
+        # MacPorts
+
+        njs_feature="libxml2 in /opt/local/"
+        njs_feature_incs="/opt/local/include/libxml2 /opt/local/include"
+        njs_feature_libs="-L/opt/local/lib -lxml2 -lxslt"
+
+        . auto/feature
+    fi
+
+    if [ $njs_found = yes ]; then
+        njs_feature="libxml2 version"
+        njs_feature_name=NJS_LIBXML2_VERSION
+        njs_feature_run=value
+        njs_feature_test="#include <libxml/xmlversion.h>
+                          #include <stdio.h>
+
+                          int main() {
+                              printf(\"\\\"%s\\\"\", LIBXML_DOTTED_VERSION);
+                              return 0;
+                          }"
+        . auto/feature
+
+        NJS_HAVE_LIBXML2=YES
+        NJS_LIB_INCS="$NJS_LIB_INCS $njs_feature_incs"
+        NJS_LIB_AUX_LIBS="$NJS_LIB_AUX_LIBS $njs_feature_libs"
+    fi
+fi
diff -r 742347841e34 -r 99b9f83e4d4d auto/modules
--- a/auto/modules	Fri Jan 20 20:15:03 2023 -0800
+++ b/auto/modules	Wed Jan 25 21:54:47 2023 -0800
@@ -21,6 +21,14 @@ if [ $NJS_OPENSSL = YES -a $NJS_HAVE_OPE
 	. auto/module
 fi
 
+if [ $NJS_LIBXML2 = YES -a $NJS_HAVE_LIBXML2 = YES ]; then
+	njs_module_name=njs_xml_module
+	njs_module_incs=
+	njs_module_srcs=external/njs_xml_module.c
+
+	. auto/module
+fi
+
 njs_module_name=njs_fs_module
 njs_module_incs=
 njs_module_srcs=external/njs_fs_module.c
diff -r 742347841e34 -r 99b9f83e4d4d auto/options
--- a/auto/options	Fri Jan 20 20:15:03 2023 -0800
+++ b/auto/options	Wed Jan 25 21:54:47 2023 -0800
@@ -16,6 +16,7 @@ NJS_ADDR2LINE=NO
 NJS_TEST262=YES
 
 NJS_OPENSSL=YES
+NJS_LIBXML2=YES
 
 NJS_PCRE=YES
 NJS_TRY_PCRE2=YES
@@ -48,6 +49,7 @@ do
         --test262=*)                     NJS_TEST262="$value"                ;;
 
         --no-openssl)                    NJS_OPENSSL=NO                      ;;
+        --no-libxml2)                    NJS_LIBXML2=NO                      ;;
 
         --no-pcre)                       NJS_PCRE=NO                         ;;
         --no-pcre2)                      NJS_TRY_PCRE2=NO                    ;;
diff -r 742347841e34 -r 99b9f83e4d4d configure
--- a/configure	Fri Jan 20 20:15:03 2023 -0800
+++ b/configure	Wed Jan 25 21:54:47 2023 -0800
@@ -51,6 +51,7 @@ NJS_LIB_AUX_LIBS=
 . auto/pcre
 . auto/readline
 . auto/openssl
+. auto/libxml2
 . auto/libbfd
 . auto/link
 
diff -r 742347841e34 -r 99b9f83e4d4d external/njs_xml_module.c
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/external/njs_xml_module.c	Wed Jan 25 21:54:47 2023 -0800
@@ -0,0 +1,1322 @@
+
+/*
+ * Copyright (C) Dmitry Volyntsev
+ * Copyright (C) NGINX, Inc.
+ */
+
+
+#include <njs.h>
+#include <string.h>
+#include <libxml/parser.h>
+#include <libxml/tree.h>
+#include <libxml/c14n.h>
+#include <libxml/xpathInternals.h>
+
+
+typedef struct {
+    xmlDoc         *doc;
+    xmlParserCtxt  *ctx;
+} njs_xml_doc_t;
+
+
+typedef enum {
+    XML_NSET_TREE = 0,
+    XML_NSET_TREE_NO_COMMENTS,
+    XML_NSET_TREE_INVERT,
+} njs_xml_nset_type_t;
+
+
+typedef struct njs_xml_nset_s  njs_xml_nset_t;
+
+struct njs_xml_nset_s {
+    xmlNodeSet           *nodes;
+    xmlDoc               *doc;
+    njs_xml_nset_type_t  type;
+    njs_xml_nset_t       *next;
+    njs_xml_nset_t       *prev;
+};
+
+static njs_int_t njs_xml_ext_parse(njs_vm_t *vm, njs_value_t *args,
+    njs_uint_t nargs, njs_index_t unused);
+static njs_int_t njs_xml_ext_canonicalization(njs_vm_t *vm, njs_value_t *args,
+    njs_uint_t nargs, njs_index_t unused);
+static njs_int_t njs_xml_doc_ext_prop_keys(njs_vm_t *vm, njs_value_t *value,
+    njs_value_t *keys);
+static njs_int_t njs_xml_doc_ext_root(njs_vm_t *vm, njs_object_prop_t *prop,
+     njs_value_t *value, njs_value_t *unused, njs_value_t *retval);
+static void njs_xml_doc_cleanup(void *data);
+static njs_int_t njs_xml_node_ext_prop_keys(njs_vm_t *vm, njs_value_t *value,
+    njs_value_t *keys);
+static njs_int_t njs_xml_node_ext_prop_handler(njs_vm_t *vm,
+    njs_object_prop_t *prop, njs_value_t *value, njs_value_t *unused,
+    njs_value_t *retval);
+static njs_int_t njs_xml_attr_ext_prop_keys(njs_vm_t *vm, njs_value_t *value,
+    njs_value_t *keys);
+static njs_int_t njs_xml_attr_ext_prop_handler(njs_vm_t *vm,
+    njs_object_prop_t *prop, njs_value_t *value, njs_value_t *unused,
+    njs_value_t *retval);
+static njs_int_t njs_xml_node_ext_attrs(njs_vm_t *vm, njs_object_prop_t *prop,
+    njs_value_t *value, njs_value_t *setval, njs_value_t *retval);
+static njs_int_t njs_xml_node_ext_name(njs_vm_t *vm, njs_object_prop_t *prop,
+    njs_value_t *value, njs_value_t *setval, njs_value_t *retval);
+static njs_int_t njs_xml_node_ext_ns(njs_vm_t *vm, njs_object_prop_t *prop,
+    njs_value_t *value, njs_value_t *setval, njs_value_t *retval);
+static njs_int_t njs_xml_node_ext_parent(njs_vm_t *vm, njs_object_prop_t *prop,
+     njs_value_t *value, njs_value_t *setval, njs_value_t *retval);
+static njs_int_t njs_xml_node_ext_tags(njs_vm_t *vm, njs_object_prop_t *prop,
+     njs_value_t *value, njs_value_t *setval, njs_value_t *retval);
+static njs_int_t njs_xml_node_ext_text(njs_vm_t *vm, njs_object_prop_t *prop,
+    njs_value_t *value, njs_value_t *setval, njs_value_t *retval);
+
+static njs_xml_nset_t *njs_xml_nset_create(njs_vm_t *vm, xmlDoc *doc,
+    xmlNodeSet *nodes, njs_xml_nset_type_t type);
+static njs_xml_nset_t *njs_xml_nset_children(njs_vm_t *vm, xmlNode *parent);
+static njs_xml_nset_t *njs_xml_nset_add(njs_xml_nset_t *nset,
+    njs_xml_nset_t *add);
+static void njs_xml_nset_cleanup(void *data);
+static void njs_xml_error(njs_vm_t *vm, njs_xml_doc_t *tree, const char *fmt,
+    ...);
+static njs_int_t njs_xml_init(njs_vm_t *vm);
+
+
+static njs_external_t  njs_ext_xml[] = {
+
+    {
+        .flags = NJS_EXTERN_PROPERTY | NJS_EXTERN_SYMBOL,
+        .name.symbol = NJS_SYMBOL_TO_STRING_TAG,
+        .u.property = {
+            .value = "xml",
+        }
+    },
+
+    {
+        .flags = NJS_EXTERN_METHOD,
+        .name.string = njs_str("parse"),
+        .writable = 1,
+        .configurable = 1,
+        .u.method = {
+            .native = njs_xml_ext_parse,
+            .magic8 = 0,
+        }
+    },
+
+    {
+        .flags = NJS_EXTERN_METHOD,
+        .name.string = njs_str("c14n"),
+        .writable = 1,
+        .configurable = 1,
+        .enumerable = 1,
+        .u.method = {
+            .native = njs_xml_ext_canonicalization,
+        }
+    },
+
+    {
+        .flags = NJS_EXTERN_METHOD,
+        .name.string = njs_str("exclusiveC14n"),
+        .writable = 1,
+        .configurable = 1,
+        .enumerable = 1,
+        .u.method = {
+            .native = njs_xml_ext_canonicalization,
+            .magic8 = 1,
+        }
+    },
+
+};
+
+
+static njs_external_t  njs_ext_xml_doc[] = {
+
+    {
+        .flags = NJS_EXTERN_PROPERTY | NJS_EXTERN_SYMBOL,
+        .name.symbol = NJS_SYMBOL_TO_STRING_TAG,
+        .u.property = {
+            .value = "XMLDoc",
+        }
+    },
+
+    {
+        .flags = NJS_EXTERN_SELF,
+        .u.object = {
+            .enumerable = 1,
+            .prop_handler = njs_xml_doc_ext_root,
+            .keys = njs_xml_doc_ext_prop_keys,
+        }
+    },
+
+    {
+        .flags = NJS_EXTERN_PROPERTY,
+        .name.string = njs_str("$root"),
+        .enumerable = 1,
+        .u.property = {
+            .handler = njs_xml_doc_ext_root,
+            .magic32 = 1,
+        }
+    },
+
+};
+
+
+static njs_external_t  njs_ext_xml_node[] = {
+
+    {
+        .flags = NJS_EXTERN_PROPERTY | NJS_EXTERN_SYMBOL,
+        .name.symbol = NJS_SYMBOL_TO_STRING_TAG,
+        .u.property = {
+            .value = "XMLNode",
+        }
+    },
+
+    {
+        .flags = NJS_EXTERN_SELF,
+        .u.object = {
+            .enumerable = 1,
+            .prop_handler = njs_xml_node_ext_prop_handler,
+            .keys = njs_xml_node_ext_prop_keys,
+        }
+    },
+
+    {
+        .flags = NJS_EXTERN_PROPERTY,
+        .name.string = njs_str("$attrs"),
+        .enumerable = 1,
+        .u.property = {
+            .handler = njs_xml_node_ext_attrs,
+        }
+    },
+
+    {
+        .flags = NJS_EXTERN_PROPERTY,
+        .name.string = njs_str("$name"),
+        .enumerable = 1,
+        .u.property = {
+            .handler = njs_xml_node_ext_name,
+        }
+    },
+
+    {
+        .flags = NJS_EXTERN_PROPERTY,
+        .name.string = njs_str("$ns"),
+        .enumerable = 1,
+        .u.property = {
+            .handler = njs_xml_node_ext_ns,
+        }
+    },
+
+    {
+        .flags = NJS_EXTERN_PROPERTY,
+        .name.string = njs_str("$parent"),
+        .enumerable = 1,
+        .u.property = {
+            .handler = njs_xml_node_ext_parent,
+        }
+    },
+
+    {
+        .flags = NJS_EXTERN_PROPERTY,
+        .name.string = njs_str("$tags"),
+        .enumerable = 1,
+        .u.property = {
+            .handler = njs_xml_node_ext_tags,
+        }
+    },
+
+    {
+        .flags = NJS_EXTERN_PROPERTY,
+        .name.string = njs_str("$text"),
+        .enumerable = 1,
+        .u.property = {
+            .handler = njs_xml_node_ext_text,
+        }
+    },
+
+};
+
+
+static njs_external_t  njs_ext_xml_attr[] = {
+
+    {
+        .flags = NJS_EXTERN_PROPERTY | NJS_EXTERN_SYMBOL,
+        .name.symbol = NJS_SYMBOL_TO_STRING_TAG,
+        .u.property = {
+            .value = "XMLAttr",
+        }
+    },
+
+    {
+        .flags = NJS_EXTERN_SELF,
+        .u.object = {
+            .enumerable = 1,
+            .prop_handler = njs_xml_attr_ext_prop_handler,
+            .keys = njs_xml_attr_ext_prop_keys,
+        }
+    },
+
+};
+
+
+njs_module_t  njs_xml_module = {
+    .name = njs_str("xml"),
+    .init = njs_xml_init,
+};
+
+
+static njs_int_t    njs_xml_doc_proto_id;
+static njs_int_t    njs_xml_node_proto_id;
+static njs_int_t    njs_xml_attr_proto_id;
+
+
+static njs_int_t
+njs_xml_ext_parse(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
+    njs_index_t unused)
+{
+    njs_int_t         ret;
+    njs_str_t         data;
+    njs_xml_doc_t     *tree;
+    njs_mp_cleanup_t  *cln;
+
+    ret = njs_vm_value_to_bytes(vm, &data, njs_arg(args, nargs, 1));
+    if (njs_slow_path(ret != NJS_OK)) {
+        return NJS_ERROR;
+    }
+
+    tree = njs_mp_zalloc(njs_vm_memory_pool(vm), sizeof(njs_xml_doc_t));
+    if (njs_slow_path(tree == NULL)) {
+        njs_vm_memory_error(vm);
+        return NJS_ERROR;
+    }
+
+    tree->ctx = xmlNewParserCtxt();
+    if (njs_slow_path(tree->ctx == NULL)) {
+        njs_vm_error(vm, "xmlNewParserCtxt() failed");
+        return NJS_ERROR;
+    }
+
+    tree->doc = xmlCtxtReadMemory(tree->ctx, (char *) data.start, data.length,
+                                  NULL, NULL, XML_PARSE_DTDVALID
+                                              | XML_PARSE_NOWARNING
+                                              | XML_PARSE_NOERROR);
+    if (njs_slow_path(tree->doc == NULL)) {
+        njs_xml_error(vm, tree, "failed to parse XML");
+        return NJS_ERROR;
+    }
+
+    cln = njs_mp_cleanup_add(njs_vm_memory_pool(vm), 0);
+    if (njs_slow_path(cln == NULL)) {
+        njs_vm_memory_error(vm);
+        return NJS_ERROR;
+    }
+
+    cln->handler = njs_xml_doc_cleanup;
+    cln->data = tree;
+
+    return njs_vm_external_create(vm, njs_vm_retval(vm), njs_xml_doc_proto_id,
+                                  tree, 0);
+}
+
+
+static njs_int_t
+njs_xml_doc_ext_prop_keys(njs_vm_t *vm, njs_value_t *value, njs_value_t *keys)
+{
+    xmlNode        *node;
+    njs_int_t      ret;
+    njs_value_t    *push;
+    njs_xml_doc_t  *tree;
+
+    tree = njs_vm_external(vm, njs_xml_doc_proto_id, value);
+    if (njs_slow_path(tree == NULL)) {
+        njs_value_undefined_set(keys);
+        return NJS_DECLINED;
+    }
+
+    ret = njs_vm_array_alloc(vm, keys, 2);
+    if (njs_slow_path(ret != NJS_OK)) {
+        return NJS_ERROR;
+    }
+
+    for (node = xmlDocGetRootElement(tree->doc);
+         node != NULL;
+         node = node->next)
+    {
+        if (node->type != XML_ELEMENT_NODE) {
+            continue;
+        }
+
+        push = njs_vm_array_push(vm, keys);
+        if (njs_slow_path(push == NULL)) {
+            return NJS_ERROR;
+        }
+
+        ret = njs_vm_value_string_create(vm, push, node->name,
+                                         njs_strlen(node->name));
+        if (njs_slow_path(ret != NJS_OK)) {
+            return NJS_ERROR;
+        }
+    }
+
+    return NJS_OK;
+}
+
+
+static njs_int_t
+njs_xml_doc_ext_root(njs_vm_t *vm, njs_object_prop_t *prop, njs_value_t *value,
+     njs_value_t *unused, njs_value_t *retval)
+{
+    xmlNode        *node;
+    njs_int_t      ret;
+    njs_str_t      name;
+    njs_bool_t     any;
+    njs_xml_doc_t  *tree;
+
+    tree = njs_vm_external(vm, njs_xml_doc_proto_id, value);
+    if (njs_slow_path(tree == NULL)) {
+        njs_value_undefined_set(retval);
+        return NJS_DECLINED;
+    }
+
+    any = njs_vm_prop_magic32(prop);
+
+    if (!any) {
+        ret = njs_vm_prop_name(vm, prop, &name);
+        if (njs_slow_path(ret != NJS_OK)) {
+            njs_value_undefined_set(retval);
+            return NJS_DECLINED;
+        }
+    }
+
+    for (node = xmlDocGetRootElement(tree->doc);
+         node != NULL;
+         node = node->next)
+    {
+        if (node->type != XML_ELEMENT_NODE) {
+            continue;
+        }
+
+        if (!any) {
+            if (name.length != njs_strlen(node->name)
+                || njs_strncmp(name.start, node->name, name.length) != 0)
+            {
+                continue;
+            }
+        }
+
+        return njs_vm_external_create(vm, retval, njs_xml_node_proto_id, node,
+                                      0);
+    }
+
+    njs_value_undefined_set(retval);
+
+    return NJS_DECLINED;
+}
+
+
+static void
+njs_xml_doc_cleanup(void *data)
+{
+    njs_xml_doc_t  *current = data;
+
+    xmlFreeDoc(current->doc);
+    xmlFreeParserCtxt(current->ctx);
+}
+
+
+static njs_int_t
+njs_xml_node_ext_prop_keys(njs_vm_t *vm, njs_value_t *value, njs_value_t *keys)
+{
+    xmlNode      *node, *current;
+    njs_int_t    ret;
+    njs_value_t  *push;
+
+    current = njs_vm_external(vm, njs_xml_node_proto_id, value);
+    if (njs_slow_path(current == NULL)) {
+        njs_value_undefined_set(keys);
+        return NJS_DECLINED;
+    }
+
+    ret = njs_vm_array_alloc(vm, keys, 2);
+    if (njs_slow_path(ret != NJS_OK)) {
+        return NJS_ERROR;
+    }
+
+    if (current->name != NULL && current->type == XML_ELEMENT_NODE) {
+        push = njs_vm_array_push(vm, keys);
+        if (njs_slow_path(push == NULL)) {
+            return NJS_ERROR;
+        }
+
+        ret = njs_vm_value_string_set(vm, push, (u_char *) "$name",
+                                      njs_length("$name"));
+        if (njs_slow_path(ret != NJS_OK)) {
+            return NJS_ERROR;
+        }
+    }
+
+    if (current->ns != NULL) {
+        push = njs_vm_array_push(vm, keys);
+        if (njs_slow_path(push == NULL)) {
+            return NJS_ERROR;
+        }
+
+        ret = njs_vm_value_string_set(vm, push, (u_char *) "$ns",
+                                      njs_length("$ns"));
+        if (njs_slow_path(ret != NJS_OK)) {
+            return NJS_ERROR;
+        }
+    }
+
+    if (current->properties != NULL) {
+        push = njs_vm_array_push(vm, keys);
+        if (njs_slow_path(push == NULL)) {
+            return NJS_ERROR;
+        }
+
+        ret = njs_vm_value_string_set(vm, push, (u_char *) "$attrs",
+                                      njs_length("$attrs"));
+        if (njs_slow_path(ret != NJS_OK)) {
+            return NJS_ERROR;
+        }
+    }
+
+    if (current->children != NULL && current->children->content != NULL) {
+        push = njs_vm_array_push(vm, keys);
+        if (njs_slow_path(push == NULL)) {
+            return NJS_ERROR;
+        }
+
+        ret = njs_vm_value_string_set(vm, push, (u_char *) "$text",
+                                      njs_length("$text"));
+        if (njs_slow_path(ret != NJS_OK)) {
+            return NJS_ERROR;
+        }
+    }
+
+    for (node = current->children; node != NULL; node = node->next) {
+        if (node->type != XML_ELEMENT_NODE) {
+            continue;
+        }
+
+        push = njs_vm_array_push(vm, keys);
+        if (njs_slow_path(push == NULL)) {
+            return NJS_ERROR;
+        }
+
+        ret = njs_vm_value_string_set(vm, push, (u_char *) "$tags",
+                                      njs_length("$tags"));
+        if (njs_slow_path(ret != NJS_OK)) {
+            return NJS_ERROR;
+        }
+
+        break;
+    }
+
+    return NJS_OK;
+}
+
+
+static njs_int_t
+njs_xml_node_ext_prop_handler(njs_vm_t *vm, njs_object_prop_t *prop,
+    njs_value_t *value, njs_value_t *unused, njs_value_t *retval)
+{
+    size_t        size;
+    xmlAttr       *attr;
+    xmlNode       *node, *current;
+    njs_int_t     ret;
+    njs_str_t     name;
+    njs_value_t   *push;
+    const u_char  *content;
+
+    /*
+     * $tag$foo - the first tag child with the name "foo"
+     * $tags$foo - the all children with the name "foo" as an array
+     * $attr$foo - the attribute with the name "foo"
+     * foo - the same as $tag$foo
+     */
+
+    current = njs_vm_external(vm, njs_xml_node_proto_id, value);
+    if (njs_slow_path(current == NULL)) {
+        njs_value_undefined_set(retval);
+        return NJS_DECLINED;
+    }
+
+    ret = njs_vm_prop_name(vm, prop, &name);
+    if (njs_slow_path(ret != NJS_OK)) {
+        njs_value_undefined_set(retval);
+        return NJS_DECLINED;
+    }
+
+    if (name.length > 1 && name.start[0] == '$') {
+        if (name.length > njs_length("$attr$")
+            && njs_strncmp(&name.start[1], "attr$", njs_length("attr$")) == 0)
+        {
+            for (attr = current->properties; attr != NULL; attr = attr->next) {
+                if (attr->type != XML_ATTRIBUTE_NODE) {
+                    continue;
+                }
+
+                size = njs_strlen(attr->name);
+
+                if (name.length != (size + njs_length("$attr$"))
+                    || njs_strncmp(&name.start[njs_length("$attr$")],
+                                   attr->name, size) != 0)
+                {
+                    continue;
+                }
+
+                content = (const u_char *) attr->children->content;
+
+                return njs_vm_value_string_create(vm, retval, content,
+                                                  njs_strlen(content));
+            }
+        }
+
+        if (name.length > njs_length("$tag$")
+            && njs_strncmp(&name.start[1], "tag$", njs_length("tag$")) == 0)
+        {
+            for (node = current->children; node != NULL; node = node->next) {
+                if (node->type != XML_ELEMENT_NODE) {
+                    continue;
+                }
+
+                size = njs_strlen(node->name);
+
+                if (name.length != (size + njs_length("$tag$"))
+                    || njs_strncmp(&name.start[njs_length("$tag$")],
+                                   node->name, size) != 0)
+                {
+                    continue;
+                }
+
+                return njs_vm_external_create(vm, retval, njs_xml_node_proto_id,
+                                              node, 0);
+            }
+        }
+
+        if (name.length >= njs_length("$tags$")
+            && njs_strncmp(&name.start[1], "tags$", njs_length("tags$")) == 0)
+        {
+            ret = njs_vm_array_alloc(vm, retval, 2);
+            if (njs_slow_path(ret != NJS_OK)) {
+                return NJS_ERROR;
+            }
+
+            for (node = current->children; node != NULL; node = node->next) {
+                if (node->type != XML_ELEMENT_NODE) {
+                    continue;
+                }
+
+                size = njs_strlen(node->name);
+
+                if (name.length > njs_length("$tags$")
+                    && (name.length != (size + njs_length("$tags$"))
+                        || njs_strncmp(&name.start[njs_length("$tags$")],
+                                       node->name, size) != 0))
+                {
+                    continue;
+                }
+
+                push = njs_vm_array_push(vm, retval);
+                if (njs_slow_path(push == NULL)) {
+                    return NJS_ERROR;
+                }
+
+                ret = njs_vm_external_create(vm, push, njs_xml_node_proto_id,
+                                             node, 0);
+                if (njs_slow_path(ret != NJS_OK)) {
+                    return NJS_ERROR;
+                }
+            }
+
+            return NJS_OK;
+        }
+    }
+
+    for (node = current->children; node != NULL; node = node->next) {
+        if (node->type != XML_ELEMENT_NODE) {
+            continue;
+        }
+
+        size = njs_strlen(node->name);
+
+        if (name.length != size
+            || njs_strncmp(name.start, node->name, size) != 0)
+        {
+            continue;
+        }
+
+        return njs_vm_external_create(vm, retval, njs_xml_node_proto_id,
+                                      node, 0);
+    }
+
+    njs_value_undefined_set(retval);
+
+    return NJS_DECLINED;
+}
+
+
+static njs_int_t
+njs_xml_node_ext_attrs(njs_vm_t *vm, njs_object_prop_t *prop,
+    njs_value_t *value, njs_value_t *setval, njs_value_t *retval)
+{
+    xmlNode  *current;
+
+    current = njs_vm_external(vm, njs_xml_node_proto_id, value);
+    if (njs_slow_path(current == NULL || current->properties == NULL)) {
+        njs_value_undefined_set(retval);
+        return NJS_DECLINED;
+    }
+
+    return njs_vm_external_create(vm, retval, njs_xml_attr_proto_id,
+                                  current->properties, 0);
+}
+
+
+static njs_int_t
+njs_xml_node_ext_name(njs_vm_t *vm, njs_object_prop_t *prop, njs_value_t *value,
+     njs_value_t *setval, njs_value_t *retval)
+{
+    xmlNode  *current;
+
+    current = njs_vm_external(vm, njs_xml_node_proto_id, value);
+    if (current == NULL || current->type != XML_ELEMENT_NODE) {
+        njs_value_undefined_set(retval);
+        return NJS_DECLINED;
+    }
+
+    return njs_vm_value_string_create(vm, retval, current->name,
+                                      njs_strlen(current->name));
+}
+
+
+static njs_int_t
+njs_xml_node_ext_ns(njs_vm_t *vm, njs_object_prop_t *prop,
+    njs_value_t *value, njs_value_t *setval, njs_value_t *retval)
+{
+    xmlNode  *current;
+
+    current = njs_vm_external(vm, njs_xml_node_proto_id, value);
+    if (njs_slow_path(current == NULL || current->ns == NULL)) {
+        njs_value_undefined_set(retval);
+        return NJS_DECLINED;
+    }
+
+    return njs_vm_value_string_create(vm, retval, current->ns->href,
+                                      njs_strlen(current->ns->href));
+}
+
+
+static njs_int_t
+njs_xml_node_ext_parent(njs_vm_t *vm, njs_object_prop_t *prop,
+    njs_value_t *value, njs_value_t *setval, njs_value_t *retval)
+{
+    xmlNode  *current;
+
+    current = njs_vm_external(vm, njs_xml_node_proto_id, value);
+    if (njs_slow_path(current == NULL
+                      || current->parent == NULL
+                      || current->parent->type != XML_ELEMENT_NODE))
+    {
+        njs_value_undefined_set(retval);
+        return NJS_DECLINED;
+    }
+
+    return njs_vm_external_create(vm, retval, njs_xml_node_proto_id,
+                                  current->parent, 0);
+}
+
+
+static njs_int_t
+njs_xml_node_ext_tags(njs_vm_t *vm, njs_object_prop_t *prop, njs_value_t *value,
+     njs_value_t *setval, njs_value_t *retval)
+{
+    xmlNode      *node, *current;
+    njs_int_t    ret;
+    njs_value_t  *push;
+
+    current = njs_vm_external(vm, njs_xml_node_proto_id, value);
+    if (njs_slow_path(current == NULL || current->children == NULL)) {
+        njs_value_undefined_set(retval);
+        return NJS_DECLINED;
+    }
+
+    ret = njs_vm_array_alloc(vm, retval, 2);
+    if (njs_slow_path(ret != NJS_OK)) {
+        return NJS_ERROR;
+    }
+
+    for (node = current->children; node != NULL; node = node->next) {
+        if (node->type != XML_ELEMENT_NODE) {
+            continue;
+        }
+
+        push = njs_vm_array_push(vm, retval);
+        if (njs_slow_path(push == NULL)) {
+            return NJS_ERROR;
+        }
+
+        ret = njs_vm_external_create(vm, push, njs_xml_node_proto_id, node, 0);
+        if (njs_slow_path(ret != NJS_OK)) {
+            return NJS_ERROR;
+        }
+    }
+
+    return NJS_OK;
+}
+
+
+static njs_int_t
+njs_xml_node_ext_text(njs_vm_t *vm, njs_object_prop_t *prop, njs_value_t *value,
+     njs_value_t *setval, njs_value_t *retval)
+{
+    xmlNode    *current, *node;
+    njs_int_t  ret;
+    njs_chb_t  chain;
+
+    current = njs_vm_external(vm, njs_xml_node_proto_id, value);
+    if (njs_slow_path(current == NULL)) {
+        njs_value_undefined_set(retval);
+        return NJS_DECLINED;
+    }
+
+    njs_chb_init(&chain, njs_vm_memory_pool(vm));
+
+    for (node = current->children; node != NULL; node = node->next) {
+        if (node->type != XML_TEXT_NODE) {
+            continue;
+        }
+
+        njs_chb_append(&chain, node->content, njs_strlen(node->content));
+    }
+
+    ret = njs_vm_value_string_create_chb(vm, retval, &chain);
+
+    njs_chb_destroy(&chain);
+
+    return ret;
+}
+
+
+static int
+njs_xml_buf_write_cb(void *context, const char *buffer, int len)
+{
+    njs_chb_t  *chain = context;
+
+    njs_chb_append(chain, buffer, len);
+
+    return chain->error ? -1 : len;
+}
+
+
+static int
+njs_xml_node_one_contains(njs_xml_nset_t *nset, xmlNode *node, xmlNode *parent)
+{
+    int    in;
+    xmlNs  ns;
+
+    if (nset->type == XML_NSET_TREE_NO_COMMENTS
+        && node->type == XML_COMMENT_NODE)
+    {
+        return 0;
+    }
+
+    in = 1;
+
+    if (nset->nodes != NULL) {
+        if (node->type != XML_NAMESPACE_DECL) {
+            in = xmlXPathNodeSetContains(nset->nodes, node);
+
+        } else {
+
+            memcpy(&ns, node, sizeof(ns));
+
+            /* libxml2 workaround, check xpath.c for details */
+
+            if ((parent != NULL) && (parent->type == XML_ATTRIBUTE_NODE)) {
+                ns.next = (xmlNs *) parent->parent;
+
+            } else {
+                ns.next = (xmlNs *) parent;
+            }
+
+            in = xmlXPathNodeSetContains(nset->nodes, (xmlNode *) &ns);
+        }
+    }
+
+    switch (nset->type) {
+    case XML_NSET_TREE:
+    case XML_NSET_TREE_NO_COMMENTS:
+        if (in != 0) {
+            return 1;
+        }
+
+        if ((parent != NULL) && (parent->type == XML_ELEMENT_NODE)) {
+            return njs_xml_node_one_contains(nset, parent, parent->parent);
+        }
+
+        return 0;
+
+    case XML_NSET_TREE_INVERT:
+    default:
+        if (in != 0) {
+            return 0;
+        }
+
+        if ((parent != NULL) && (parent->type == XML_ELEMENT_NODE)) {
+            return njs_xml_node_one_contains(nset, parent, parent->parent);
+        }
+    }


More information about the nginx-devel mailing list