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
31 #include "libxml/parser.h"
32 #include "libxml/c14n.h"
33 #include "libxml/xmlversion.h"
37 #include "sipe-backend.h"
38 #include "sipe-utils.h"
48 GHashTable
*attributes
;
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
);
67 static void callback_start_element(void *user_data
, const xmlChar
*name
, const xmlChar
**attrs
)
69 struct _parser_data
*pd
= user_data
;
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
);
85 sipe_xml
*current
= pd
->current
;
87 node
->parent
= current
;
89 current
->last
->sibling
= node
;
91 current
->first
= node
;
99 node
->attributes
= g_hash_table_new_full(sipe_ascii_strdown_hash
,
100 (GEqualFunc
) sipe_strcase_equal
,
102 while ((key
= *attrs
++) != NULL
) {
103 if ((tmp
= strchr((char *)key
, ':')) != NULL
) {
104 key
= (xmlChar
*)tmp
+ 1;
106 /* libxml2 decodes all entities except &.
107 & is replaced by the equivalent & */
108 g_hash_table_insert(node
->attributes
,
109 g_strdup((gchar
*) key
),
110 sipe_utils_str_replace((gchar
*) *attrs
++, "&", "&"));
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
;
132 if (!pd
->current
|| pd
->error
|| !text
|| !text_len
) return;
136 node
->data
= g_string_append_len(node
->data
, (gchar
*)text
, text_len
);
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
;
150 errmsg
= g_strdup_vprintf(msg
, args
);
153 SIPE_DEBUG_ERROR("error parsing xml string: %s", 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
)) {
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)");
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)");
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 */
201 callback_error
, /* error */
202 NULL
, /* fatalError */
203 NULL
, /* getParameterEntity */
204 NULL
, /* cdataBlock */
205 NULL
, /* externalSubset */
206 XML_SAX2_MAGIC
, /* initialized */
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
))
224 sipe_xml_free(pd
->root
);
235 void sipe_xml_free(sipe_xml
*node
)
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...");
249 sipe_xml
*tmp
= child
->sibling
;
250 child
->parent
= NULL
; /* detach from tree, see above */
251 sipe_xml_free(child
);
257 if (node
->data
) g_string_free(node
->data
, TRUE
);
258 if (node
->attributes
) g_hash_table_destroy(node
->attributes
);
262 static void sipe_xml_stringify_attribute(gpointer key
, gpointer value
,
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
);
290 g_string_append(s
, "/>");
294 gchar
*sipe_xml_stringify(const sipe_xml
*node
)
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
)
308 const sipe_xml
*child
= NULL
;
310 if (!parent
|| !name
) return NULL
;
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
))
321 /* recurse into path */
322 if (child
&& names
[1])
323 child
= sipe_xml_child(child
, names
[1]);
329 const sipe_xml
*sipe_xml_twin(const sipe_xml
*node
)
333 if (!node
) return NULL
;
335 for (sibling
= node
->sibling
; sibling
; sibling
= sibling
->sibling
) {
336 if (sipe_strequal(node
->name
, sibling
->name
))
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
,
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);
372 void sipe_xml_dump(const sipe_xml
*node
, const gchar
*path
)
374 const sipe_xml
*child
;
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
;
383 g_string_append_printf(buf
, "%s ", (gchar
*)entry
->data
);
386 SIPE_DEBUG_INFO("%s [%s]", new_path
, buf
->str
);
387 g_string_free(buf
, TRUE
);
390 SIPE_DEBUG_INFO_NOFORMAT(new_path
);
392 for (child
= node
->first
; child
; child
= child
->sibling
)
393 sipe_xml_dump(child
, new_path
);
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);
412 /* Apply canonicalization */
413 size
= xmlC14NDocDumpMemory(doc
,
415 #if LIBXML_VERSION > 20703
416 /* new API: int mode (a xmlC14NMode) */
417 XML_C14N_EXCLUSIVE_1_0
,
419 /* old API: int exclusive */
428 SIPE_DEBUG_INFO("sipe_xml_exc_c14n:\noriginal: %s\ncanonicalized: %s",
430 canon
= g_strndup((gchar
*) buffer
, size
);
433 SIPE_DEBUG_ERROR("sipe_xml_exc_c14n: failed to canonicalize xml string:\n%s",
437 SIPE_DEBUG_ERROR("sipe_xml_exc_c14n: error parsing xml string:\n%s",
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
);
450 const gchar
*start
= strstr(xml
, tag_start
);
453 const gchar
*end
= strstr(start
+ strlen(tag_start
), tag_end
);
456 data
= g_strndup(start
, end
+ strlen(tag_end
) - start
);
458 const gchar
*tmp
= strchr(start
+ strlen(tag_start
), '>') + 1;
459 data
= g_strndup(tmp
, end
- tmp
);
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
);
474 const gchar
*start
= strstr(xml
, tag_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>",
488 const gchar
*end
= strstr(start
+ strlen(tag_start
),
494 data
= g_strndup(p
, end
+ strlen(tag_end
) - p
);
496 const gchar
*tmp
= strchr(start
+ strlen(tag_start
), '>') + 1;
497 data
= g_strndup(tmp
, end
- tmp
);
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 */
516 data
= sipe_xml_extract_any_raw(xml
, tag
, include_tag
);