details: https://hg.nginx.org/njs/rev/99b9f83e4d4d branches: changeset: 2028:99b9f83e4d4d user: Dmitry Volyntsev <xei...@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); + } + } _______________________________________________ nginx-devel mailing list nginx-devel@nginx.org https://mailman.nginx.org/mailman/listinfo/nginx-devel