* Purple is the legal property of its developers, whose names are too numerous * to list here. Please refer to the COPYRIGHT file distributed with this * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA /* A lot of this code at least resembles the code in libxode, but since * libxode uses memory pools that we simply have no need for, I decided to * write my own stuff. Also, re-writing this lets me be as lightweight * as I want to be. Thank you libxode for giving me a good starting point */ #include <libxml/parser.h> # define NEWLINE_S "\r\n" new_node(const char *name, PurpleXmlNodeType type) PurpleXmlNode *node = g_new0(PurpleXmlNode, 1); node->name = g_strdup(name); purple_xmlnode_new(const char *name) g_return_val_if_fail(name != NULL && *name != '\0', NULL); return new_node(name, PURPLE_XMLNODE_TYPE_TAG); purple_xmlnode_new_child(PurpleXmlNode *parent, const char *name) g_return_val_if_fail(parent != NULL, NULL); g_return_val_if_fail(name != NULL && *name != '\0', NULL); node = new_node(name, PURPLE_XMLNODE_TYPE_TAG); purple_xmlnode_insert_child(parent, node); purple_xmlnode_insert_child(PurpleXmlNode *parent, PurpleXmlNode *child) g_return_if_fail(parent != NULL); g_return_if_fail(child != NULL); parent->lastchild->next = child; parent->lastchild = child; purple_xmlnode_insert_data(PurpleXmlNode *node, const char *data, gssize size) g_return_if_fail(node != NULL); g_return_if_fail(data != NULL); g_return_if_fail(size != 0); real_size = size == -1 ? strlen(data) : (gsize)size; child = new_node(NULL, PURPLE_XMLNODE_TYPE_DATA); child->data = g_memdup(data, real_size); child->data_sz = real_size; purple_xmlnode_insert_child(node, child); purple_xmlnode_remove_attrib(PurpleXmlNode *node, const char *attr) PurpleXmlNode *attr_node, *sibling = NULL; g_return_if_fail(node != NULL); g_return_if_fail(attr != NULL); if(attr_node->type == PURPLE_XMLNODE_TYPE_ATTRIB && purple_strequal(attr_node->name, attr)) if (node->lastchild == attr_node) { node->lastchild = sibling; node->child = attr_node->next; purple_xmlnode_free(attr_node); sibling->next = attr_node->next; sibling = attr_node->next; purple_xmlnode_free(attr_node); attr_node = attr_node->next; purple_xmlnode_remove_attrib_with_namespace(PurpleXmlNode *node, const char *attr, const char *xmlns) PurpleXmlNode *attr_node, *sibling = NULL; g_return_if_fail(node != NULL); g_return_if_fail(attr != NULL); for(attr_node = node->child; attr_node; attr_node = attr_node->next) if(attr_node->type == PURPLE_XMLNODE_TYPE_ATTRIB && purple_strequal(attr, attr_node->name) && purple_strequal(xmlns, attr_node->xmlns)) node->child = attr_node->next; sibling->next = attr_node->next; if (node->lastchild == attr_node) { node->lastchild = sibling; purple_xmlnode_free(attr_node); purple_xmlnode_set_attrib(PurpleXmlNode *node, const char *attr, const char *value) purple_xmlnode_remove_attrib(node, attr); purple_xmlnode_set_attrib_full(node, attr, NULL, NULL, value); purple_xmlnode_set_attrib_full(PurpleXmlNode *node, const char *attr, const char *xmlns, const char *prefix, const char *value) PurpleXmlNode *attrib_node; g_return_if_fail(node != NULL); g_return_if_fail(attr != NULL); g_return_if_fail(value != NULL); purple_xmlnode_remove_attrib_with_namespace(node, attr, xmlns); attrib_node = new_node(attr, PURPLE_XMLNODE_TYPE_ATTRIB); attrib_node->data = g_strdup(value); attrib_node->xmlns = g_strdup(xmlns); attrib_node->prefix = g_strdup(prefix); purple_xmlnode_insert_child(node, attrib_node); purple_xmlnode_get_attrib(const PurpleXmlNode *node, const char *attr) g_return_val_if_fail(node != NULL, NULL); g_return_val_if_fail(attr != NULL, NULL); for(x = node->child; x; x = x->next) { if(x->type == PURPLE_XMLNODE_TYPE_ATTRIB && purple_strequal(attr, x->name)) { purple_xmlnode_get_attrib_with_namespace(const PurpleXmlNode *node, const char *attr, const char *xmlns) g_return_val_if_fail(node != NULL, NULL); g_return_val_if_fail(attr != NULL, NULL); for(x = node->child; x; x = x->next) { if(x->type == PURPLE_XMLNODE_TYPE_ATTRIB && purple_strequal(attr, x->name) && purple_strequal(xmlns, x->xmlns)) { void purple_xmlnode_set_namespace(PurpleXmlNode *node, const char *xmlns) g_return_if_fail(node != NULL); node->xmlns = g_strdup(xmlns); if (node->namespace_map) { g_hash_table_insert(node->namespace_map, g_strdup(""), g_strdup(xmlns)); const char *purple_xmlnode_get_namespace(const PurpleXmlNode *node) g_return_val_if_fail(node != NULL, NULL); const char *purple_xmlnode_get_default_namespace(const PurpleXmlNode *node) const PurpleXmlNode *current_node; g_return_val_if_fail(node != NULL, NULL); /* If this node does *not* have a prefix, node->xmlns is the default * namespace. Otherwise, it's the prefix namespace. if (!current_node->prefix && current_node->xmlns) { return current_node->xmlns; } else if (current_node->namespace_map) { ns = g_hash_table_lookup(current_node->namespace_map, ""); current_node = current_node->parent; void purple_xmlnode_set_prefix(PurpleXmlNode *node, const char *prefix) g_return_if_fail(node != NULL); node->prefix = g_strdup(prefix); const char *purple_xmlnode_get_prefix(const PurpleXmlNode *node) g_return_val_if_fail(node != NULL, NULL); const char *purple_xmlnode_get_prefix_namespace(const PurpleXmlNode *node, const char *prefix) const PurpleXmlNode *current_node; g_return_val_if_fail(node != NULL, NULL); g_return_val_if_fail(prefix != NULL, purple_xmlnode_get_default_namespace(node)); if (current_node->prefix && g_str_equal(prefix, current_node->prefix) && return current_node->xmlns; } else if (current_node->namespace_map) { const char *ns = g_hash_table_lookup(current_node->namespace_map, prefix); current_node = current_node->parent; void purple_xmlnode_strip_prefixes(PurpleXmlNode *node) g_return_if_fail(node != NULL); for (child = node->child; child; child = child->next) { if (child->type == PURPLE_XMLNODE_TYPE_TAG) purple_xmlnode_strip_prefixes(child); prefix = purple_xmlnode_get_prefix(node); const char *ns = purple_xmlnode_get_prefix_namespace(node, prefix); purple_xmlnode_set_namespace(node, ns); purple_xmlnode_set_prefix(node, NULL); purple_xmlnode_set_namespace(node, purple_xmlnode_get_default_namespace(node)); PurpleXmlNode *purple_xmlnode_get_parent(const PurpleXmlNode *child) g_return_val_if_fail(child != NULL, NULL); purple_xmlnode_free(PurpleXmlNode *node) g_return_if_fail(node != NULL); /* if we're part of a tree, remove ourselves from the tree first */ if(NULL != node->parent) { if(node->parent->child == node) { node->parent->child = node->next; if (node->parent->lastchild == node) node->parent->lastchild = node->next; PurpleXmlNode *prev = node->parent->child; while(prev && prev->next != node) { if (node->parent->lastchild == node) node->parent->lastchild = prev; /* now free our children */ /* now dispose of ourselves */ g_hash_table_destroy(node->namespace_map); purple_xmlnode_get_child(const PurpleXmlNode *parent, const char *name) return purple_xmlnode_get_child_with_namespace(parent, name, NULL); purple_xmlnode_get_child_with_namespace(const PurpleXmlNode *parent, const char *name, const char *ns) PurpleXmlNode *x, *ret = NULL; char *parent_name, *child_name; g_return_val_if_fail(parent != NULL, NULL); g_return_val_if_fail(name != NULL, NULL); names = g_strsplit(name, "/", 2); for(x = parent->child; x; x = x->next) { /* XXX: Is it correct to ignore the namespace for the match if none was specified? */ const char *xmlns = NULL; xmlns = purple_xmlnode_get_namespace(x); if(x->type == PURPLE_XMLNODE_TYPE_TAG && purple_strequal(parent_name, x->name) && purple_strequal(ns, xmlns)) { ret = purple_xmlnode_get_child(ret, child_name); purple_xmlnode_get_data(const PurpleXmlNode *node) g_return_val_if_fail(node != NULL, NULL); for(c = node->child; c; c = c->next) { if(c->type == PURPLE_XMLNODE_TYPE_DATA) { str = g_string_new_len(c->data, c->data_sz); str = g_string_append_len(str, c->data, c->data_sz); return g_string_free(str, FALSE); purple_xmlnode_get_data_unescaped(const PurpleXmlNode *node) char *escaped = purple_xmlnode_get_data(node); char *unescaped = escaped ? purple_unescape_html(escaped) : NULL; purple_xmlnode_to_str_foreach_append_ns(const char *key, const char *value, g_string_append_printf(buf, " xmlns:%s='%s'", key, value); g_string_append_printf(buf, " xmlns='%s'", value); purple_xmlnode_to_str_helper(const PurpleXmlNode *node, int *len, gboolean formatting, int depth) char *node_name, *esc, *esc2, *tab = NULL; gboolean need_end = FALSE, pretty = formatting; g_return_val_if_fail(node != NULL, NULL); tab = g_strnfill(depth, '\t'); text = g_string_append(text, tab); node_name = g_markup_escape_text(node->name, -1); prefix = purple_xmlnode_get_prefix(node); g_string_append_printf(text, "<%s:%s", prefix, node_name); g_string_append_printf(text, "<%s", node_name); if (node->namespace_map) { g_hash_table_foreach(node->namespace_map, (GHFunc)purple_xmlnode_to_str_foreach_append_ns, text); /* Figure out if this node has a different default namespace from parent */ const char *xmlns = NULL; const char *parent_xmlns = NULL; xmlns = purple_xmlnode_get_default_namespace(node); parent_xmlns = purple_xmlnode_get_default_namespace(node->parent); if (!purple_strequal(xmlns, parent_xmlns)) char *escaped_xmlns = g_markup_escape_text(xmlns, -1); g_string_append_printf(text, " xmlns='%s'", escaped_xmlns); for(c = node->child; c; c = c->next) if(c->type == PURPLE_XMLNODE_TYPE_ATTRIB) { const char *aprefix = purple_xmlnode_get_prefix(c); esc = g_markup_escape_text(c->name, -1); esc2 = g_markup_escape_text(c->data, -1); g_string_append_printf(text, " %s:%s='%s'", aprefix, esc, esc2); g_string_append_printf(text, " %s='%s'", esc, esc2); } else if(c->type == PURPLE_XMLNODE_TYPE_TAG || c->type == PURPLE_XMLNODE_TYPE_DATA) { if(c->type == PURPLE_XMLNODE_TYPE_DATA) g_string_append_printf(text, ">%s", pretty ? NEWLINE_S : ""); for(c = node->child; c; c = c->next) if(c->type == PURPLE_XMLNODE_TYPE_TAG) { esc = purple_xmlnode_to_str_helper(c, &esc_len, pretty, depth+1); text = g_string_append_len(text, esc, esc_len); } else if(c->type == PURPLE_XMLNODE_TYPE_DATA && c->data_sz > 0) { esc = g_markup_escape_text(c->data, c->data_sz); text = g_string_append(text, esc); text = g_string_append(text, tab); g_string_append_printf(text, "</%s:%s>%s", prefix, node_name, formatting ? NEWLINE_S : ""); g_string_append_printf(text, "</%s>%s", node_name, formatting ? NEWLINE_S : ""); g_string_append_printf(text, "/>%s", formatting ? NEWLINE_S : ""); return g_string_free(text, FALSE); purple_xmlnode_to_str(const PurpleXmlNode *node, int *len) return purple_xmlnode_to_str_helper(node, len, FALSE, 0); purple_xmlnode_to_formatted_str(const PurpleXmlNode *node, int *len) char *xml, *xml_with_declaration; g_return_val_if_fail(node != NULL, NULL); xml = purple_xmlnode_to_str_helper(node, len, TRUE, 0); g_strdup_printf("<?xml version='1.0' encoding='UTF-8' ?>" NEWLINE_S NEWLINE_S "%s", xml); *len += sizeof("<?xml version='1.0' encoding='UTF-8' ?>" NEWLINE_S NEWLINE_S) - 1; return xml_with_declaration; struct _xmlnode_parser_data { purple_xmlnode_parser_element_start_libxml(void *user_data, const xmlChar *element_name, const xmlChar *prefix, const xmlChar *xmlns, int nb_namespaces, const xmlChar **namespaces, int nb_attributes, int nb_defaulted, const xmlChar **attributes) struct _xmlnode_parser_data *xpd = user_data; if(!element_name || xpd->error) { node = purple_xmlnode_new_child(xpd->current, (const char*) element_name); node = purple_xmlnode_new((const char *) element_name); purple_xmlnode_set_namespace(node, (const char *) xmlns); purple_xmlnode_set_prefix(node, (const char *)prefix); if (nb_namespaces != 0) { node->namespace_map = g_hash_table_new_full( g_str_hash, g_str_equal, g_free, g_free); for (i = 0, j = 0; i < nb_namespaces; i++, j += 2) { const char *key = (const char *)namespaces[j]; const char *val = (const char *)namespaces[j + 1]; g_hash_table_insert(node->namespace_map, g_strdup(key ? key : ""), g_strdup(val ? val : "")); for(i=0; i < nb_attributes * 5; i+=5) { const char *name = (const char *)attributes[i]; const char *prefix = (const char *)attributes[i+1]; int attrib_len = attributes[i+4] - attributes[i+3]; char *attrib = g_strndup((const char *)attributes[i+3], attrib_len); attrib = purple_unescape_text(txt); purple_xmlnode_set_attrib_full(node, name, NULL, prefix, attrib); purple_xmlnode_parser_element_end_libxml(void *user_data, const xmlChar *element_name, const xmlChar *prefix, const xmlChar *xmlns) struct _xmlnode_parser_data *xpd = user_data; if(!element_name || !xpd->current || xpd->error) if(xpd->current->parent) { if(!xmlStrcmp((xmlChar*) xpd->current->name, element_name)) xpd->current = xpd->current->parent; purple_xmlnode_parser_element_text_libxml(void *user_data, const xmlChar *text, int text_len) struct _xmlnode_parser_data *xpd = user_data; if(!xpd->current || xpd->error) purple_xmlnode_insert_data(xpd->current, (const char*) text, text_len); purple_xmlnode_parser_error_libxml(void *user_data, const char *msg, ...) struct _xmlnode_parser_data *xpd = user_data; g_vsnprintf(errmsg, sizeof(errmsg), msg, args); purple_debug_error("xmlnode", "Error parsing xml file: %s", errmsg); purple_xmlnode_parser_structural_error_libxml(void *user_data, xmlErrorPtr error) struct _xmlnode_parser_data *xpd = user_data; if (error && (error->level == XML_ERR_ERROR || error->level == XML_ERR_FATAL)) { purple_debug_error("xmlnode", "XML parser error for PurpleXmlNode %p: " "Domain %i, code %i, level %i: %s", user_data, error->domain, error->code, error->level, error->message ? error->message : "(null)\n"); purple_debug_warning("xmlnode", "XML parser error for PurpleXmlNode %p: " "Domain %i, code %i, level %i: %s", user_data, error->domain, error->code, error->level, error->message ? error->message : "(null)\n"); purple_debug_warning("xmlnode", "XML parser error for PurpleXmlNode %p\n", static xmlSAXHandler purple_xmlnode_parser_libxml = { NULL, /* internalSubset */ NULL, /* hasInternalSubset */ NULL, /* hasExternalSubset */ NULL, /* resolveEntity */ NULL, /* attributeDecl */ NULL, /* unparsedEntityDecl */ NULL, /* setDocumentLocator */ NULL, /* startDocument */ purple_xmlnode_parser_element_text_libxml, /* characters */ NULL, /* ignorableWhitespace */ NULL, /* processingInstruction */ purple_xmlnode_parser_error_libxml, /* error */ NULL, /* getParameterEntity */ NULL, /* externalSubset */ XML_SAX2_MAGIC, /* initialized */ purple_xmlnode_parser_element_start_libxml, /* startElementNs */ purple_xmlnode_parser_element_end_libxml, /* endElementNs */ purple_xmlnode_parser_structural_error_libxml, /* serror */ purple_xmlnode_from_str(const char *str, gssize size) struct _xmlnode_parser_data *xpd; g_return_val_if_fail(str != NULL, NULL); real_size = size < 0 ? strlen(str) : (gsize)size; xpd = g_new0(struct _xmlnode_parser_data, 1); if (xmlSAXUserParseMemory(&purple_xmlnode_parser_libxml, xpd, str, real_size) < 0) { while(xpd->current && xpd->current->parent) xpd->current = xpd->current->parent; purple_xmlnode_free(xpd->current); purple_xmlnode_free(xpd->current); purple_xmlnode_from_file(const char *dir, const char *filename, const char *description, const char *process) PurpleXmlNode *node = NULL; g_return_val_if_fail(dir != NULL, NULL); purple_debug_misc(process, "Reading file %s from directory %s\n", filename_full = g_build_filename(dir, filename, NULL); if (!g_file_test(filename_full, G_FILE_TEST_EXISTS)) purple_debug_info(process, "File %s does not exist (this is not " "necessarily an error)\n", filename_full); if (!g_file_get_contents(filename_full, &contents, &length, &error)) purple_debug_error(process, "Error reading file %s: %s\n", filename_full, error->message); if ((contents != NULL) && (length > 0)) node = purple_xmlnode_from_str(contents, length); /* If we were unable to parse the file then save its contents to a backup file */ gchar *filename_temp, *filename_temp_full; filename_temp = g_strdup_printf("%s~", filename); filename_temp_full = g_build_filename(dir, filename_temp, NULL); purple_debug_error("util", "Error parsing file %s. Renaming old " "file to %s\n", filename_full, filename_temp); purple_util_write_data_to_file_absolute(filename_temp_full, contents, length); g_free(filename_temp_full); /* If we could not parse the file then show the user an error message */ title = g_strdup_printf(_("Error Reading %s"), filename); msg = g_strdup_printf(_("An error was encountered reading your " "%s. The file has not been loaded, and the old file " "has been renamed to %s~."), description, filename_full); purple_notify_error(NULL, NULL, title, msg, NULL); purple_xmlnode_copy_foreach_ns(gpointer key, gpointer value, gpointer user_data) GHashTable *ret = (GHashTable *)user_data; g_hash_table_insert(ret, g_strdup(key), g_strdup(value)); purple_xmlnode_copy(const PurpleXmlNode *src) PurpleXmlNode *sibling = NULL; g_return_val_if_fail(src != NULL, NULL); ret = new_node(src->name, src->type); ret->xmlns = g_strdup(src->xmlns); ret->data = g_memdup(src->data, src->data_sz); ret->data_sz = src->data_sz; ret->data = g_strdup(src->data); ret->prefix = g_strdup(src->prefix); if (src->namespace_map) { ret->namespace_map = g_hash_table_new_full(g_str_hash, g_str_equal, g_hash_table_foreach(src->namespace_map, purple_xmlnode_copy_foreach_ns, ret->namespace_map); for (child = src->child; child; child = child->next) { sibling->next = purple_xmlnode_copy(child); ret->child = sibling = purple_xmlnode_copy(child); ret->lastchild = sibling; purple_xmlnode_get_next_twin(PurpleXmlNode *node) const char *ns = purple_xmlnode_get_namespace(node); g_return_val_if_fail(node != NULL, NULL); g_return_val_if_fail(node->type == PURPLE_XMLNODE_TYPE_TAG, NULL); for(sibling = node->next; sibling; sibling = sibling->next) { /* XXX: Is it correct to ignore the namespace for the match if none was specified? */ const char *xmlns = NULL; xmlns = purple_xmlnode_get_namespace(sibling); if(sibling->type == PURPLE_XMLNODE_TYPE_TAG && purple_strequal(node->name, sibling->name) && purple_strequal(ns, xmlns)) purple_xmlnode_get_type(void) type = g_boxed_type_register_static("PurpleXmlNode", (GBoxedCopyFunc)purple_xmlnode_copy, (GBoxedFreeFunc)purple_xmlnode_free);