[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