cfc53115bc038aeb453027290622c4f87360b0b1
[siplcs.git] / src / core / sipe-xml.c
blobcfc53115bc038aeb453027290622c4f87360b0b1
1 /**
2 * @file sipe-xml.c
4 * pidgin-sipe
6 * Copyright (C) 2010-2015 SIPE Project <http://sipe.sourceforge.net/>
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
24 * This code is loosely based on libpurple xmlnode.c
27 #include <stdarg.h>
28 #include <string.h>
29 #include <time.h>
31 #include "libxml/parser.h"
32 #include "libxml/c14n.h"
33 #include "libxml/xmlversion.h"
35 #include "glib.h"
37 #include "sipe-backend.h"
38 #include "sipe-utils.h"
39 #include "sipe-xml.h"
41 struct _sipe_xml {
42 gchar *name;
43 sipe_xml *parent;
44 sipe_xml *sibling;
45 sipe_xml *first;
46 sipe_xml *last;
47 GString *data;
48 GHashTable *attributes;
51 struct _parser_data {
52 sipe_xml *root;
53 sipe_xml *current;
54 gboolean error;
57 /* our string equal function is case insensitive -> hash must be too! */
58 static guint sipe_ascii_strdown_hash(gconstpointer key)
60 gchar *lc = g_ascii_strdown((const gchar *) key, -1);
61 guint bucket = g_str_hash(lc);
62 g_free(lc);
64 return(bucket);
67 static void callback_start_element(void *user_data, const xmlChar *name, const xmlChar **attrs)
69 struct _parser_data *pd = user_data;
70 const char *tmp;
71 sipe_xml *node;
73 if (!name || pd->error) return;
75 node = g_new0(sipe_xml, 1);
77 if ((tmp = strchr((char *)name, ':')) != NULL) {
78 name = (xmlChar *)tmp + 1;
80 node->name = g_strdup((gchar *)name);
82 if (!pd->root) {
83 pd->root = node;
84 } else {
85 sipe_xml *current = pd->current;
87 node->parent = current;
88 if (current->last) {
89 current->last->sibling = node;
90 } else {
91 current->first = node;
93 current->last = node;
96 if (attrs) {
97 const xmlChar *key;
99 node->attributes = g_hash_table_new_full(sipe_ascii_strdown_hash,
100 (GEqualFunc) sipe_strcase_equal,
101 g_free, g_free);
102 while ((key = *attrs++) != NULL) {
103 if ((tmp = strchr((char *)key, ':')) != NULL) {
104 key = (xmlChar *)tmp + 1;
106 /* libxml2 decodes all entities except &amp;.
107 &amp; is replaced by the equivalent &#38; */
108 g_hash_table_insert(node->attributes,
109 g_strdup((gchar *) key),
110 sipe_utils_str_replace((gchar *) *attrs++, "&#38;", "&"));
114 pd->current = node;
117 static void callback_end_element(void *user_data, const xmlChar *name)
119 struct _parser_data *pd = user_data;
121 if (!name || !pd->current || pd->error) return;
123 if (pd->current->parent)
124 pd->current = pd->current->parent;
127 static void callback_characters(void *user_data, const xmlChar *text, int text_len)
129 struct _parser_data *pd = user_data;
130 sipe_xml *node;
132 if (!pd->current || pd->error || !text || !text_len) return;
134 node = pd->current;
135 if (node->data)
136 node->data = g_string_append_len(node->data, (gchar *)text, text_len);
137 else
138 node->data = g_string_new_len((gchar *)text, text_len);
141 static void callback_error(void *user_data, const char *msg, ...)
143 struct _parser_data *pd = user_data;
144 gchar *errmsg;
145 va_list args;
147 pd->error = TRUE;
149 va_start(args, msg);
150 errmsg = g_strdup_vprintf(msg, args);
151 va_end(args);
153 SIPE_DEBUG_ERROR("error parsing xml string: %s", errmsg);
154 g_free(errmsg);
157 static void callback_serror(void *user_data, xmlErrorPtr error)
159 struct _parser_data *pd = user_data;
161 if (error && (error->level == XML_ERR_ERROR ||
162 error->level == XML_ERR_FATAL)) {
163 pd->error = TRUE;
164 SIPE_DEBUG_ERROR("XML parser error: Domain %i, code %i, level %i: %s",
165 error->domain, error->code, error->level,
166 error->message ? error->message : "(null)");
167 } else if (error) {
168 SIPE_DEBUG_WARNING("XML parser error: Domain %i, code %i, level %i: %s",
169 error->domain, error->code, error->level,
170 error->message ? error->message : "(null)");
171 } else {
172 /* *sigh* macro expects at least two parameters */
173 SIPE_DEBUG_WARNING_NOFORMAT("XML parser error");
177 /* API doesn't accept const data structure */
178 static xmlSAXHandler parser = {
179 NULL, /* internalSubset */
180 NULL, /* isStandalone */
181 NULL, /* hasInternalSubset */
182 NULL, /* hasExternalSubset */
183 NULL, /* resolveEntity */
184 NULL, /* getEntity */
185 NULL, /* entityDecl */
186 NULL, /* notationDecl */
187 NULL, /* attributeDecl */
188 NULL, /* elementDecl */
189 NULL, /* unparsedEntityDecl */
190 NULL, /* setDocumentLocator */
191 NULL, /* startDocument */
192 NULL, /* endDocument */
193 callback_start_element, /* startElement */
194 callback_end_element, /* endElement */
195 NULL, /* reference */
196 callback_characters, /* characters */
197 NULL, /* ignorableWhitespace */
198 NULL, /* processingInstruction */
199 NULL, /* comment */
200 NULL, /* warning */
201 callback_error, /* error */
202 NULL, /* fatalError */
203 NULL, /* getParameterEntity */
204 NULL, /* cdataBlock */
205 NULL, /* externalSubset */
206 XML_SAX2_MAGIC, /* initialized */
207 NULL, /* _private */
208 NULL, /* startElementNs */
209 NULL, /* endElementNs */
210 callback_serror, /* serror */
213 sipe_xml *sipe_xml_parse(const gchar *string, gsize length)
215 sipe_xml *result = NULL;
217 if (string && length) {
218 struct _parser_data *pd = g_new0(struct _parser_data, 1);
220 if (xmlSAXUserParseMemory(&parser, pd, string, length))
221 pd->error = TRUE;
223 if (pd->error) {
224 sipe_xml_free(pd->root);
225 } else {
226 result = pd->root;
229 g_free(pd);
232 return result;
235 void sipe_xml_free(sipe_xml *node)
237 sipe_xml *child;
239 if (!node) return;
241 /* we don't support partial tree deletion */
242 if (node->parent != NULL) {
243 SIPE_DEBUG_ERROR_NOFORMAT("sipe_xml_free: partial delete attempt! Expect crash or memory leaks...");
246 /* free children */
247 child = node->first;
248 while (child) {
249 sipe_xml *tmp = child->sibling;
250 child->parent = NULL; /* detach from tree, see above */
251 sipe_xml_free(child);
252 child = tmp;
255 /* free node */
256 g_free(node->name);
257 if (node->data) g_string_free(node->data, TRUE);
258 if (node->attributes) g_hash_table_destroy(node->attributes);
259 g_free(node);
262 static void sipe_xml_stringify_attribute(gpointer key, gpointer value,
263 gpointer user_data)
265 g_string_append_printf(user_data, " %s=\"%s\"",
266 (const gchar *) key, (const gchar *) value);
269 static void sipe_xml_stringify_node(GString *s, const sipe_xml *node)
271 g_string_append_printf(s, "<%s", node->name);
273 if (node->attributes) {
274 g_hash_table_foreach(node->attributes,
275 (GHFunc) sipe_xml_stringify_attribute,
279 if (node->data || node->first) {
280 const sipe_xml *child;
282 g_string_append_printf(s, ">%s",
283 node->data ? node->data->str : "");
285 for (child = node->first; child; child = child->sibling)
286 sipe_xml_stringify_node(s, child);
288 g_string_append_printf(s, "</%s>", node->name);
289 } else {
290 g_string_append(s, "/>");
294 gchar *sipe_xml_stringify(const sipe_xml *node)
296 GString *s;
298 if (!node) return NULL;
300 s = g_string_new("");
301 sipe_xml_stringify_node(s, node);
302 return g_string_free(s, FALSE);
305 const sipe_xml *sipe_xml_child(const sipe_xml *parent, const gchar *name)
307 gchar **names;
308 const sipe_xml *child = NULL;
310 if (!parent || !name) return NULL;
312 /* 0: child name */
313 /* 1: trailing path (optional) */
314 names = g_strsplit(name, "/", 2);
316 for (child = parent->first; child; child = child->sibling) {
317 if (sipe_strequal(names[0], child->name))
318 break;
321 /* recurse into path */
322 if (child && names[1])
323 child = sipe_xml_child(child, names[1]);
325 g_strfreev(names);
326 return child;
329 const sipe_xml *sipe_xml_twin(const sipe_xml *node)
331 sipe_xml *sibling;
333 if (!node) return NULL;
335 for (sibling = node->sibling; sibling; sibling = sibling->sibling) {
336 if (sipe_strequal(node->name, sibling->name))
337 return sibling;
339 return NULL;
342 const gchar *sipe_xml_name(const sipe_xml *node)
344 return(node ? node->name : NULL);
347 const gchar *sipe_xml_attribute(const sipe_xml *node, const gchar *attr)
349 if (!node || !attr || !node->attributes) return NULL;
350 return(g_hash_table_lookup(node->attributes, attr));
353 guint sipe_xml_int_attribute(const sipe_xml *node, const gchar *attr,
354 guint fallback)
356 const gchar *value = sipe_xml_attribute(node, attr);
357 return(value ? g_ascii_strtoull(value, NULL, 10) : fallback);
360 gchar *sipe_xml_data(const sipe_xml *node)
362 if (!node || !node->data || !node->data->str) return NULL;
363 return g_strdup(node->data->str);
367 * Set to 1 to enable debugging code and then add this line to your code:
369 * sipe_xml_dump(node, NULL);
371 #if 0
372 void sipe_xml_dump(const sipe_xml *node, const gchar *path)
374 const sipe_xml *child;
375 gchar *new_path;
376 if (!node) return;
377 new_path = g_strdup_printf("%s/%s", path ? path : "", node->name);
378 if (node->attributes) {
379 GList *attrs = g_hash_table_get_keys(node->attributes);
380 GString *buf = g_string_new("");
381 GList *entry = attrs;
382 while (entry) {
383 g_string_append_printf(buf, "%s ", (gchar *)entry->data);
384 entry = entry->next;
386 SIPE_DEBUG_INFO("%s [%s]", new_path, buf->str);
387 g_string_free(buf, TRUE);
388 g_list_free(attrs);
389 } else {
390 SIPE_DEBUG_INFO_NOFORMAT(new_path);
392 for (child = node->first; child; child = child->sibling)
393 sipe_xml_dump(child, new_path);
394 g_free(new_path);
396 #endif
399 * Other XML convenience functions not based on libpurple xmlnode.c
402 gchar *sipe_xml_exc_c14n(const gchar *string)
404 /* Parse string to XML document */
405 xmlDocPtr doc = xmlReadMemory(string, strlen(string), "", NULL, 0);
406 gchar *canon = NULL;
408 if (doc) {
409 xmlChar *buffer;
410 int size;
412 /* Apply canonicalization */
413 size = xmlC14NDocDumpMemory(doc,
414 NULL,
415 #if LIBXML_VERSION > 20703
416 /* new API: int mode (a xmlC14NMode) */
417 XML_C14N_EXCLUSIVE_1_0,
418 #else
419 /* old API: int exclusive */
421 #endif
422 NULL,
424 &buffer);
425 xmlFreeDoc(doc);
427 if (size >= 0) {
428 SIPE_DEBUG_INFO("sipe_xml_exc_c14n:\noriginal: %s\ncanonicalized: %s",
429 string, buffer);
430 canon = g_strndup((gchar *) buffer, size);
431 xmlFree(buffer);
432 } else {
433 SIPE_DEBUG_ERROR("sipe_xml_exc_c14n: failed to canonicalize xml string:\n%s",
434 string);
436 } else {
437 SIPE_DEBUG_ERROR("sipe_xml_exc_c14n: error parsing xml string:\n%s",
438 string);
441 return(canon);
444 static gchar *sipe_xml_extract_exact_raw(const gchar *xml, const gchar *tag,
445 gboolean include_tag)
447 gchar *tag_start = g_strdup_printf("<%s", tag);
448 gchar *tag_end = g_strdup_printf("</%s>", tag);
449 gchar *data = NULL;
450 const gchar *start = strstr(xml, tag_start);
452 if (start) {
453 const gchar *end = strstr(start + strlen(tag_start), tag_end);
454 if (end) {
455 if (include_tag) {
456 data = g_strndup(start, end + strlen(tag_end) - start);
457 } else {
458 const gchar *tmp = strchr(start + strlen(tag_start), '>') + 1;
459 data = g_strndup(tmp, end - tmp);
464 g_free(tag_end);
465 g_free(tag_start);
466 return data;
469 static gchar *sipe_xml_extract_any_raw(const gchar *xml, const gchar *tag,
470 gboolean include_tag)
472 gchar *tag_start = g_strdup_printf(":%s", tag);
473 gchar *data = NULL;
474 const gchar *start = strstr(xml, tag_start);
476 if (start) {
477 const gchar *p = start - 1;
479 /* search for beginning of tag */
480 while ((*p != '<') && (p >= xml)) p--;
482 /* namespace identifier found? */
483 if ((p >= xml) && (p != (start - 1))) {
484 gchar *ns = g_strndup(p + 1, start - p);
485 gchar *tag_end = g_strdup_printf("</%s%s>",
487 tag);
488 const gchar *end = strstr(start + strlen(tag_start),
489 tag_end);
490 g_free(ns);
492 if (end) {
493 if (include_tag) {
494 data = g_strndup(p, end + strlen(tag_end) - p);
495 } else {
496 const gchar *tmp = strchr(start + strlen(tag_start), '>') + 1;
497 data = g_strndup(tmp, end - tmp);
501 g_free(tag_end);
505 g_free(tag_start);
506 return data;
508 gchar *sipe_xml_extract_raw(const gchar *xml, const gchar *tag,
509 gboolean include_tag)
511 /* first try exact match */
512 gchar *data = sipe_xml_extract_exact_raw(xml, tag, include_tag);
514 /* otherwise match tag in any name space */
515 if (!data)
516 data = sipe_xml_extract_any_raw(xml, tag, include_tag);
518 return(data);
522 Local Variables:
523 mode: c
524 c-file-style: "bsd"
525 indent-tabs-mode: t
526 tab-width: 8
527 End: