summaryrefslogtreecommitdiff
path: root/src/rexmpp_xml.c
diff options
context:
space:
mode:
authordefanor <defanor@uberspace.net>2023-05-23 12:05:13 +0300
committerdefanor <defanor@uberspace.net>2023-05-23 12:05:13 +0300
commit122b13ec955deb718aca280112584b645c9caea0 (patch)
treefdaa1dce84956a33f669605e67df1a587a01fc1d /src/rexmpp_xml.c
parentae42bb94fd49a450690014b6039a812a251f64cd (diff)
Replace libxml2's xmlNode with a custom XML structure
The new structure (rexmpp_xml) is simpler, and should allow manipulation from Rust without any dependency on libxml2 from the Rust code (while Rust has its own parsers, such as rxml). Alternative XML parsers (e.g., libexpat) now can be used from the C code. The replacement/abstraction is not quite complete yet: the parsing process itself (xmlParseChunk and friends) should be abstracted out.
Diffstat (limited to 'src/rexmpp_xml.c')
-rw-r--r--src/rexmpp_xml.c592
1 files changed, 592 insertions, 0 deletions
diff --git a/src/rexmpp_xml.c b/src/rexmpp_xml.c
new file mode 100644
index 0000000..8c91d32
--- /dev/null
+++ b/src/rexmpp_xml.c
@@ -0,0 +1,592 @@
+/**
+ @file rexmpp_xml.c
+ @brief XML structures and functions for rexmpp
+ @author defanor <defanor@uberspace.net>
+ @date 2023
+ @copyright MIT license.
+*/
+
+#include <string.h>
+#include <libxml/tree.h>
+#include <libxml/xmlsave.h>
+#include "rexmpp.h"
+#include "rexmpp_xml.h"
+
+void rexmpp_xml_qname_free (rexmpp_xml_qname_t *qname) {
+ if (qname->name != NULL) {
+ free(qname->name);
+ qname->name = NULL;
+ }
+ if (qname->namespace != NULL) {
+ free(qname->namespace);
+ qname->namespace = NULL;
+ }
+}
+
+void rexmpp_xml_attribute_free (rexmpp_xml_attr_t *attr) {
+ if (attr == NULL) {
+ return;
+ }
+ rexmpp_xml_qname_free(&(attr->qname));
+ if (attr->value != NULL) {
+ free(attr->value);
+ attr->value = NULL;
+ }
+ free(attr);
+}
+
+void rexmpp_xml_attribute_free_list (rexmpp_xml_attr_t *attr) {
+ rexmpp_xml_attr_t *next = attr;
+ while (attr != NULL) {
+ next = attr->next;
+ rexmpp_xml_attribute_free(attr);
+ attr = next;
+ }
+}
+
+void rexmpp_xml_free (rexmpp_xml_t *node) {
+ if (node == NULL) {
+ return;
+ }
+ if (node->type == REXMPP_XML_TEXT) {
+ if (node->alt.text != NULL) {
+ free(node->alt.text);
+ node->alt.text = NULL;
+ }
+ } if (node->type == REXMPP_XML_ELEMENT) {
+ rexmpp_xml_qname_free(&(node->alt.elem.qname));
+ rexmpp_xml_attribute_free_list(node->alt.elem.attributes);
+ rexmpp_xml_free_list(node->alt.elem.children);
+ }
+ free(node);
+}
+
+void rexmpp_xml_free_list (rexmpp_xml_t *node) {
+ rexmpp_xml_t *next = node;
+ while (node != NULL) {
+ next = node->next;
+ rexmpp_xml_free(node);
+ node = next;
+ }
+}
+
+rexmpp_xml_t *rexmpp_xml_clone (rexmpp_xml_t *node) {
+ if (node == NULL) {
+ return NULL;
+ }
+
+ if (node->type == REXMPP_XML_TEXT) {
+ return rexmpp_xml_new_text(node->alt.text);
+ } else if (node->type == REXMPP_XML_ELEMENT) {
+ rexmpp_xml_t *ret =
+ rexmpp_xml_new_elem(node->alt.elem.qname.name,
+ node->alt.elem.qname.namespace);
+ rexmpp_xml_attr_t **next_attr = &(ret->alt.elem.attributes);
+ rexmpp_xml_attr_t *old_attr;
+ for (old_attr = node->alt.elem.attributes;
+ old_attr != NULL;
+ old_attr = old_attr->next)
+ {
+ rexmpp_xml_attr_t *new_attr =
+ rexmpp_xml_attr_new(old_attr->qname.name,
+ old_attr->qname.namespace,
+ old_attr->value);
+ *next_attr = new_attr;
+ next_attr = &(new_attr->next);
+ }
+
+ ret->alt.elem.children =
+ rexmpp_xml_clone_list(node->alt.elem.children);
+ return ret;
+ }
+ return NULL;
+}
+
+rexmpp_xml_t *rexmpp_xml_clone_list (rexmpp_xml_t *node) {
+ rexmpp_xml_t *first, *last;
+ if (node == NULL) {
+ return NULL;
+ }
+ first = rexmpp_xml_clone(node);
+ for (last = first, node = node->next;
+ node != NULL;
+ last = last->next, node = node->next)
+ {
+ last->next = rexmpp_xml_clone(node);
+ }
+ return first;
+}
+
+
+rexmpp_xml_t *rexmpp_xml_from_libxml2 (xmlNodePtr from) {
+ if (from == NULL) {
+ return NULL;
+ }
+
+ rexmpp_xml_t *to = NULL;
+ if (from->type == XML_ELEMENT_NODE) {
+ to = malloc(sizeof(rexmpp_xml_t));
+
+ /* Type */
+ to->type = REXMPP_XML_ELEMENT;
+
+ /* Name and namespace */
+ to->alt.elem.qname.name = strdup(from->name);
+ if (from->nsDef != NULL && from->nsDef->href != NULL) {
+ to->alt.elem.qname.namespace = strdup(from->nsDef->href);
+ } else {
+ to->alt.elem.qname.namespace = NULL;
+ }
+
+ /* Attributes */
+ to->alt.elem.attributes = NULL;
+ struct _xmlAttr *from_attr;
+ rexmpp_xml_attr_t **to_next_attr = &(to->alt.elem.attributes);
+ for (from_attr = from->properties;
+ from_attr != NULL;
+ from_attr = from_attr->next)
+ {
+ rexmpp_xml_attr_t *to_attr =
+ malloc(sizeof(rexmpp_xml_attr_t));
+ to_attr->qname.name = strdup(from_attr->name);
+ to_attr->qname.namespace = NULL;
+ if (from_attr->ns != NULL && from_attr->ns->href != NULL) {
+ to_attr->qname.namespace = strdup(from_attr->ns->href);
+ to_attr->value =
+ xmlGetNsProp(from, to_attr->qname.name, to_attr->qname.namespace);
+ } else {
+ to_attr->value = xmlGetProp(from, to_attr->qname.name);
+ }
+ to_attr->next = NULL;
+
+ *to_next_attr = to_attr;
+ to_next_attr = &(to_attr->next);
+ }
+
+ /* Children */
+ to->alt.elem.children = NULL;
+ xmlNodePtr from_child;
+ rexmpp_xml_t **to_next_child = &(to->alt.elem.children);
+ for (from_child = from->children;
+ from_child != NULL;
+ from_child = from_child->next)
+ {
+ rexmpp_xml_t *next_child = rexmpp_xml_from_libxml2(from_child);
+ if (next_child != NULL) {
+ *to_next_child = next_child;
+ to_next_child = &(next_child->next);
+ }
+ }
+
+ /* Next */
+ to->next = NULL;
+
+ } else if (from->type == XML_TEXT_NODE) {
+ to = malloc(sizeof(rexmpp_xml_t));
+ to->type = REXMPP_XML_TEXT;
+ to->alt.text = xmlNodeGetContent(from);
+ to->next = NULL;
+ }
+ return to;
+}
+
+rexmpp_xml_t *rexmpp_xml_from_libxml2_list (xmlNodePtr from) {
+ if (from == NULL) {
+ return NULL;
+ }
+ rexmpp_xml_t *to = rexmpp_xml_from_libxml2(from);
+ if (from->next != NULL) {
+ to->next = rexmpp_xml_from_libxml2_list(from->next);
+ }
+ return to;
+}
+
+xmlNodePtr rexmpp_xml_to_libxml2 (rexmpp_xml_t *from) {
+ if (from == NULL) {
+ return NULL;
+ }
+
+ if (from->type == REXMPP_XML_TEXT) {
+ xmlNodePtr to = xmlNewText(from->alt.text);
+ to->next = rexmpp_xml_to_libxml2(from->next);
+ return to;
+ }
+
+ /* Name and namespace */
+ xmlNodePtr to = xmlNewNode(NULL, from->alt.elem.qname.name);
+ if (from->alt.elem.qname.namespace != NULL) {
+ xmlNewNs(to, from->alt.elem.qname.namespace, NULL);
+ }
+
+ /* Attributes */
+ rexmpp_xml_attr_t *attr = from->alt.elem.attributes;
+ while (attr != NULL) {
+ /* TODO: Would be nice to take namespaces into account, though
+ they are currently not used for attributes. */
+ xmlNewProp(to, attr->qname.name, attr->value);
+ attr = attr->next;
+ }
+
+ /* Children */
+ rexmpp_xml_t *child = from->alt.elem.children;
+ while (child != NULL) {
+ xmlAddChild(to, rexmpp_xml_to_libxml2(child));
+ child = child->next;
+ }
+ return to;
+}
+
+xmlNodePtr rexmpp_xml_to_libxml2_list (rexmpp_xml_t *from) {
+ xmlNodePtr to = rexmpp_xml_to_libxml2(from);
+ if (from->next != NULL) {
+ xmlAddNextSibling(to, rexmpp_xml_to_libxml2_list(from->next));
+ }
+ return to;
+}
+
+rexmpp_xml_t *rexmpp_xml_new_text (const char *str) {
+ rexmpp_xml_t *node = malloc(sizeof(rexmpp_xml_t));
+ node->type = REXMPP_XML_TEXT;
+ node->alt.text = strdup(str);
+ node->next = NULL;
+ return node;
+}
+
+void rexmpp_xml_add_child (rexmpp_xml_t *node,
+ rexmpp_xml_t *child)
+{
+ rexmpp_xml_t **last_ptr = &(node->alt.elem.children);
+ while (*last_ptr != NULL) {
+ last_ptr = &((*last_ptr)->next);
+ }
+ *last_ptr = child;
+}
+
+int rexmpp_xml_add_text (rexmpp_xml_t *node,
+ const char *str)
+{
+ rexmpp_xml_t *text_node = rexmpp_xml_new_text(str);
+ if (text_node != NULL) {
+ rexmpp_xml_add_child(node, text_node);
+ return 0;
+ }
+ return -1;
+}
+
+
+rexmpp_xml_t *rexmpp_xml_new_elem (const char *name,
+ const char *namespace)
+{
+ rexmpp_xml_t *node = malloc(sizeof(rexmpp_xml_t));
+ node->type = REXMPP_XML_ELEMENT;
+ node->alt.elem.qname.name = strdup(name);
+ if (namespace != NULL) {
+ node->alt.elem.qname.namespace = strdup(namespace);
+ } else {
+ node->alt.elem.qname.namespace = NULL;
+ }
+ node->alt.elem.attributes = NULL;
+ node->alt.elem.children = NULL;
+ node->next = NULL;
+ return node;
+}
+
+rexmpp_xml_attr_t *rexmpp_xml_attr_new (const char *name,
+ const char *namespace,
+ const char *value)
+{
+ rexmpp_xml_attr_t *attr = malloc(sizeof(rexmpp_xml_attr_t));
+ attr->qname.name = strdup(name);
+ if (namespace != NULL) {
+ attr->qname.namespace = strdup(namespace);
+ } else {
+ attr->qname.namespace = NULL;
+ }
+ attr->value = strdup(value);
+ attr->next = NULL;
+ return attr;
+}
+
+int rexmpp_xml_add_attr_ns (rexmpp_xml_t *node,
+ const char *name,
+ const char *namespace,
+ const char *value)
+{
+ if (node == NULL || node->type != REXMPP_XML_ELEMENT) {
+ return -1;
+ }
+ rexmpp_xml_attr_t *attr =
+ rexmpp_xml_attr_new(name, namespace, value);
+ attr->next = node->alt.elem.attributes;
+ node->alt.elem.attributes = attr;
+ return 0;
+}
+
+int rexmpp_xml_remove_attr_ns (rexmpp_xml_t *node,
+ const char *name,
+ const char *namespace) {
+ if (node == NULL || node->type != REXMPP_XML_ELEMENT) {
+ return -1;
+ }
+
+ rexmpp_xml_attr_t **attr, *next_attr;
+ for (attr = &(node->alt.elem.attributes); *attr != NULL; attr = &((*attr)->next)) {
+ if (rexmpp_xml_attr_match(*attr, namespace, name)) {
+ next_attr = (*attr)->next;
+ rexmpp_xml_attribute_free(*attr);
+ *attr = next_attr;
+ return 0;
+ }
+ }
+ return 1;
+}
+
+int rexmpp_xml_add_attr (rexmpp_xml_t *node,
+ const char *name,
+ const char *value)
+{
+ return rexmpp_xml_add_attr_ns(node, name, NULL, value);
+}
+
+int rexmpp_xml_remove_attr (rexmpp_xml_t *node,
+ const char *name) {
+ return rexmpp_xml_remove_attr_ns(node, name, NULL);
+}
+
+rexmpp_xml_t *
+rexmpp_xml_add_id (rexmpp_t *s,
+ rexmpp_xml_t *node)
+{
+ char *buf = rexmpp_gen_id(s);
+ if (buf == NULL) {
+ return NULL;
+ }
+ rexmpp_xml_add_attr(node, "id", buf);
+ free(buf);
+ return node;
+}
+
+char *rexmpp_xml_serialize (rexmpp_xml_t *node) {
+ xmlNodePtr node_libxml2 = rexmpp_xml_to_libxml2(node);
+ xmlBufferPtr buf = xmlBufferCreate();
+ xmlSaveCtxtPtr ctx = xmlSaveToBuffer(buf, "utf-8", 0);
+ xmlSaveTree(ctx, node_libxml2);
+ xmlSaveFlush(ctx);
+ xmlSaveClose(ctx);
+ unsigned char *out = xmlBufferDetach(buf);
+ xmlBufferFree(buf);
+ xmlFreeNode(node_libxml2);
+ return out;
+}
+
+xmlNodePtr rexmpp_xml_parse_libxml2 (const char *str, int str_len) {
+ xmlNodePtr elem = NULL;
+ xmlDocPtr doc = xmlReadMemory(str, str_len, "", "utf-8", XML_PARSE_NONET);
+ if (doc != NULL) {
+ elem = xmlCopyNode(xmlDocGetRootElement(doc), 1);
+ xmlFreeDoc(doc);
+ }
+ return elem;
+}
+
+rexmpp_xml_t *rexmpp_xml_parse (const char *str, int str_len) {
+ xmlNodePtr node_lxml2 = rexmpp_xml_parse_libxml2(str, str_len);
+ if (node_lxml2 != NULL) {
+ rexmpp_xml_t *node = rexmpp_xml_from_libxml2(node_lxml2);
+ xmlFreeNode(node_lxml2);
+ return node;
+ }
+ return NULL;
+}
+
+rexmpp_xml_t *rexmpp_xml_read_file (const char *path) {
+ xmlDocPtr doc = xmlReadFile(path, "utf-8", XML_PARSE_NONET);
+ xmlNodePtr lxml2 = xmlDocGetRootElement(doc);
+ rexmpp_xml_t *ret = rexmpp_xml_from_libxml2(lxml2);
+ xmlFreeDoc(doc);
+ return ret;
+}
+
+int rexmpp_xml_write_file (const char *path, rexmpp_xml_t* node) {
+ xmlDocPtr doc = xmlNewDoc("1.0");
+ xmlNodePtr node_lxml2 = rexmpp_xml_to_libxml2(node);
+ xmlDocSetRootElement(doc, node_lxml2);
+ xmlSaveFileEnc(path, doc, "utf-8");
+ xmlFreeDoc(doc);
+ return 0;
+}
+
+unsigned int rexmpp_xml_siblings_count (rexmpp_xml_t *node) {
+ unsigned int i = 0;
+ for (i = 0; node != NULL; i++) {
+ node = node->next;
+ }
+ return i;
+}
+
+int rexmpp_xml_match (rexmpp_xml_t *node,
+ const char *namespace,
+ const char *name)
+{
+ if (node == NULL) {
+ return 0;
+ }
+ if (node->type != REXMPP_XML_ELEMENT) {
+ return 0;
+ }
+ if (name != NULL) {
+ if (strcmp(name, node->alt.elem.qname.name) != 0) {
+ return 0;
+ }
+ }
+ if (namespace != NULL) {
+ if (node->alt.elem.qname.namespace == NULL &&
+ strcmp(namespace, "jabber:client") != 0) {
+ return 0;
+ } else if (node->alt.elem.qname.namespace != NULL) {
+ if (strcmp(namespace, node->alt.elem.qname.namespace) != 0) {
+ return 0;
+ }
+ }
+ }
+ return 1;
+}
+
+int rexmpp_xml_attr_match (rexmpp_xml_attr_t *attr,
+ const char *namespace,
+ const char *name)
+{
+ if (attr == NULL) {
+ return 0;
+ }
+ if (name != NULL) {
+ if (strcmp(name, attr->qname.name) != 0) {
+ return 0;
+ }
+ }
+ if (namespace != NULL) {
+ if (attr->qname.namespace == NULL &&
+ strcmp(namespace, "jabber:client") != 0) {
+ return 0;
+ } else if (strcmp(namespace, attr->qname.namespace) != 0) {
+ return 0;
+ }
+ }
+ return 1;
+}
+
+int rexmpp_xml_is_stanza (rexmpp_xml_t *node) {
+ return rexmpp_xml_match(node, "jabber:client", "message") ||
+ rexmpp_xml_match(node, "jabber:client", "iq") ||
+ rexmpp_xml_match(node, "jabber:client", "presence");
+}
+
+rexmpp_xml_t *rexmpp_xml_error (const char *type, const char *condition) {
+ rexmpp_xml_t * error = rexmpp_xml_new_elem("error", NULL);
+ rexmpp_xml_add_attr(error, "type", type);
+ rexmpp_xml_t * cond =
+ rexmpp_xml_new_elem(condition, "urn:ietf:params:xml:ns:xmpp-stanzas");
+ rexmpp_xml_add_child(error, cond);
+ return error;
+}
+
+rexmpp_xml_attr_t *
+rexmpp_xml_find_attr (rexmpp_xml_t *node,
+ const char *name,
+ const char *namespace)
+{
+ if (node == NULL || node->type != REXMPP_XML_ELEMENT) {
+ return NULL;
+ }
+ rexmpp_xml_attr_t *attr;
+ for (attr = node->alt.elem.attributes; attr != NULL; attr = attr->next) {
+ if (rexmpp_xml_attr_match(attr, namespace, name)) {
+ return attr;
+ }
+ }
+ return NULL;
+}
+
+const char *rexmpp_xml_find_attr_val_ns (rexmpp_xml_t *node,
+ const char *name,
+ const char *namespace) {
+ rexmpp_xml_attr_t *attr = rexmpp_xml_find_attr(node, name, namespace);
+ if (attr != NULL) {
+ return attr->value;
+ }
+ return NULL;
+}
+
+const char *rexmpp_xml_find_attr_val (rexmpp_xml_t *node,
+ const char *name) {
+ return rexmpp_xml_find_attr_val_ns(node, name, NULL);
+}
+
+rexmpp_xml_t *
+rexmpp_xml_find_child (rexmpp_xml_t *node,
+ const char *namespace,
+ const char *name)
+{
+ if (node == NULL || node->type != REXMPP_XML_ELEMENT) {
+ return NULL;
+ }
+ rexmpp_xml_t *child;
+ for (child = node->alt.elem.children; child != NULL; child = child->next) {
+ if (rexmpp_xml_match(child, namespace, name)) {
+ return child;
+ }
+ }
+ return NULL;
+}
+
+int rexmpp_xml_eq (rexmpp_xml_t *n1, rexmpp_xml_t *n2) {
+ /* Just serialize and compare strings for now: awkward, but
+ simple. */
+ char *n1str = rexmpp_xml_serialize(n1);
+ char *n2str = rexmpp_xml_serialize(n2);
+ int eq = (strcmp(n1str, n2str) == 0);
+ free(n1str);
+ free(n2str);
+ return eq;
+}
+
+rexmpp_xml_t *rexmpp_xml_children (rexmpp_xml_t *node) {
+ if (node != NULL && node->type == REXMPP_XML_ELEMENT) {
+ return node->alt.elem.children;
+ }
+ return NULL;
+}
+
+rexmpp_xml_t *rexmpp_xml_first_elem_child (rexmpp_xml_t *node) {
+ rexmpp_xml_t *child;
+ for (child = rexmpp_xml_children(node); child != NULL; child = child->next) {
+ if (child->type == REXMPP_XML_ELEMENT) {
+ return child;
+ }
+ }
+ return NULL;
+}
+
+rexmpp_xml_t *rexmpp_xml_next_elem_sibling (rexmpp_xml_t *node) {
+ if (node == NULL) {
+ return NULL;
+ }
+ rexmpp_xml_t *sibling;
+ for (sibling = node->next; sibling != NULL; sibling = sibling->next) {
+ if (sibling->type == REXMPP_XML_ELEMENT) {
+ return sibling;
+ }
+ }
+ return NULL;
+}
+
+const char *rexmpp_xml_text (rexmpp_xml_t *node) {
+ if (node != NULL && node->type == REXMPP_XML_TEXT) {
+ return node->alt.text;
+ }
+ return NULL;
+}
+
+const char *rexmpp_xml_text_child (rexmpp_xml_t *node) {
+ return rexmpp_xml_text(rexmpp_xml_children(node));
+}