Removed the gunichar stuff. It was only being used to test for the
[pwmd.git] / src / xml.c
blobc53422c9d0bbabe4fff7fd0f451c9f1f0a6db10a
1 /* vim:tw=78:ts=8:sw=4:set ft=c: */
2 /*
3 Copyright (C) 2006-2007 Ben Kibbey <bjk@luxsci.net>
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2 of the License, or
8 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19 #include <stdio.h>
20 #include <stdlib.h>
21 #include <unistd.h>
22 #include <err.h>
23 #include <errno.h>
24 #include <string.h>
25 #include <sys/stat.h>
26 #include <fcntl.h>
27 #include <ctype.h>
28 #include <glib.h>
29 #include <gcrypt.h>
31 #ifdef HAVE_CONFIG_H
32 #include <config.h>
33 #endif
35 #include "pwmd_error.h"
36 #include "xml.h"
38 void log_write(const gchar *fmt, ...);
39 gboolean strv_printf(gchar ***array, const gchar *fmt, ...);
41 gboolean is_literal_element_str(gchar *element)
43 if (!element || !*element)
44 return FALSE;
46 return *element == '!' ? TRUE : FALSE;
50 * 'element' must be allocated.
52 gboolean is_literal_element(gchar **element)
54 gchar *p;
56 if (!element || !*element)
57 return FALSE;
59 if (*(*element) == '!') {
60 p = g_strdup(*element+1);
61 g_free(*element);
62 *element = p;
63 return TRUE;
66 return FALSE;
70 * libxml doesn't seem to mind funny element names so we won't either.
72 gboolean valid_xml_element(xmlChar *element)
74 if (!element || !*element)
75 return FALSE;
77 return TRUE;
80 gpg_error_t new_account(xmlDocPtr doc, gchar *name)
82 xmlNodePtr root = xmlDocGetRootElement(doc);
83 xmlAttrPtr a;
84 xmlNodePtr n;
86 if (!name || !root)
87 return EPWMD_LIBXML_ERROR;
90 * ! as the first character of an account name is bad. This character is
91 * used to get an element path reguardless if there is a "target"
92 * attribute.
94 if (*name == '!')
95 return EPWMD_INVALID_ELEMENT;
97 n = xmlNewNode(NULL, (xmlChar *)"account");
98 n = xmlAddChild(root, n);
99 a = xmlNewProp(n, (xmlChar *)"name", (xmlChar *)name);
100 return 0;
103 gchar *new_document()
105 gchar *buf;
106 const char *line =
107 "<?xml version=\"1.0\"?>\n"
108 "<!DOCTYPE accounts [\n"
109 "<!ELEMENT accounts (account*)>\n"
110 "<!ATTLIST account name CDATA #REQUIRED>\n"
111 "]>\n"
112 "<accounts/>";
114 if ((buf = gcry_malloc(strlen(line) + 1)) == NULL)
115 return NULL;
117 strcpy(buf, line);
118 return buf;
121 gint sort_element_list(gconstpointer a, gconstpointer b)
123 gchar *ka, *kb;
124 gint n;
126 ka = g_utf8_collate_key(a, -1);
127 kb = g_utf8_collate_key(b, -1);
128 n = strcmp(ka, kb);
129 g_free(ka);
130 g_free(kb);
131 return n;
135 * The "target" attribute is ignored here.
137 gpg_error_t list_accounts(xmlDocPtr doc, GString **result)
139 xmlNodePtr n = NULL, t;
140 GSList *list = NULL;
141 gchar *p;
142 gint total, i;
143 GString *string;
145 for (t = doc->children; t; t = t->next) {
146 if (t->type == XML_ELEMENT_NODE) {
147 if (xmlStrEqual(t->name, (xmlChar *)"accounts")) {
148 n = t;
149 break;
154 if (!n || !n->children)
155 return EPWMD_EMPTY_ELEMENT;
157 for (n = n->children; n; n = n->next) {
158 xmlAttrPtr a;
160 if (n->type != XML_ELEMENT_NODE)
161 continue;
163 a = xmlHasProp(n, (xmlChar *)"name");
165 if (!a || !a->children->content)
166 continue;
168 list = g_slist_append(list, g_strdup(a->children->content));
171 total = g_slist_length(list);
173 if (!total)
174 return EPWMD_EMPTY_ELEMENT;
176 list = g_slist_sort(list, sort_element_list);
177 string = g_string_new(NULL);
179 for (i = 0; i < total; i++) {
180 gchar *tmp = g_slist_nth_data(list, i);
182 g_string_append_printf(string, "%s\n", tmp);
183 g_free(tmp);
186 g_slist_free(list);
187 *result = string;
188 return 0;
191 gchar **split_input_line(gchar *str, gchar *delim, gint n)
193 if (!str || !*str)
194 return NULL;
196 return g_strsplit(str, delim, n);
199 static gchar **append_element_path(gchar **dst, gchar **src)
201 gchar **p;
202 gint i;
203 gchar **d;
205 if (!src)
206 return NULL;
208 d = g_strdupv(dst);
209 i = g_strv_length(d);
211 for (p = src; *p; p++) {
212 d = g_realloc(d, (i + 2) * sizeof(gchar *));
213 d[i++] = g_strdup(*p);
214 d[i] = NULL;
217 return d;
221 * Alot like create_elements_cb() but doesn't use the last element of 'req' as
222 * content but as an element.
224 xmlNodePtr create_target_elements_cb(xmlNodePtr node, gchar **path,
225 gpg_error_t *error, void *data)
227 gint i;
228 char **req = path;
230 if (xmlStrEqual(node->name, (xmlChar *)*req))
231 req++;
233 for (i = 0; req[i]; i++) {
234 xmlNodePtr n;
237 * Whitespace is allowed in values or attributes but not in element
238 * names.
240 if (valid_xml_element((xmlChar *)req[i]) == FALSE) {
241 *error = EPWMD_INVALID_ELEMENT;
242 return NULL;
245 if ((n = find_element(node, (xmlChar *)req[i])) == NULL) {
246 n = xmlNewNode(NULL, (xmlChar *)req[i]);
247 node = xmlAddChild(node, n);
249 else
250 node = n;
253 return node;
256 xmlNodePtr create_elements_cb(xmlNodePtr node, gchar **elements,
257 gpg_error_t *error, void *data)
259 gint i;
260 gchar **req = elements;
262 if (node->type == XML_TEXT_NODE)
263 node = node->parent;
265 if (node->name && xmlStrEqual(node->name, (xmlChar *)*req))
266 req++;
268 for (i = 0; req[i]; i++) {
269 xmlNodePtr n;
272 * Whitespace is allowed in values or attributes but not in element
273 * names.
275 if (req[i+1] && valid_xml_element((xmlChar *)req[i]) == FALSE) {
276 *error = EPWMD_INVALID_ELEMENT;
277 return NULL;
281 * The value of the element tree.
283 if (!req[i+1]) {
284 if (node->children) {
285 n = node->children->next ? xmlCopyNode(node->children->next, 1) :
286 xmlCopyNode(node->children, 1);
288 if (n->content) {
289 xmlFree(n->content);
290 n->content = NULL;
293 else
294 n = NULL;
296 if (req[i])
297 xmlNodeSetContent(node, req[i]);
298 else if (node->content) {
299 xmlFree(node->content);
300 node->content = NULL;
303 if (n)
304 xmlAddChild(node, n);
306 break;
309 n = find_element(node, req[i]);
311 if (!n) {
312 n = xmlNewNode(NULL, req[i]);
313 node = xmlAddChild(node, n);
315 else
316 node = n;
319 return node;
322 xmlNodePtr find_account(xmlDocPtr doc, gchar ***req, gpg_error_t *error,
323 gboolean *target)
325 xmlNodePtr n;
326 gint depth = 0;
327 gchar *account = g_strdup(*req[0]);
328 gboolean literal = is_literal_element(&account);
330 *error = 0;
332 for (n = doc->children; n;) {
333 if (n->type == XML_ELEMENT_NODE) {
334 if (depth == 0 && xmlStrEqual(n->name, (xmlChar *)"accounts")) {
335 n = n->children;
336 depth++;
337 continue;
340 if (depth == 1 && xmlStrEqual(n->name, (xmlChar *)"account")) {
341 xmlChar *content = node_has_attribute(n, "name");
343 if (!content)
344 continue;
346 if (xmlStrEqual(content, account)) {
347 gchar **nreq, **tmp = NULL;
349 if (literal == TRUE) {
350 g_free(account);
351 return n;
354 content = node_has_attribute(n, "target");
356 if (!content) {
357 g_free(account);
358 return n;
361 if (strchr((gchar *)content, '\t')) {
362 nreq = split_input_line((gchar *)content, "\t", 0);
363 tmp = *req;
364 tmp = append_element_path(nreq, tmp+1);
365 g_strfreev(nreq);
366 g_strfreev(*req);
367 *req = tmp;
369 else {
370 strv_printf(&tmp, "%s", content);
371 nreq = *req;
372 nreq = append_element_path(tmp, nreq+1);
373 g_strfreev(*req);
374 *req = nreq;
377 if (target)
378 *target = TRUE;
380 g_free(account);
381 return find_account(doc, req, error, target);
386 n = n->next;
389 g_free(account);
390 *error = EPWMD_ELEMENT_NOT_FOUND;
391 return NULL;
394 xmlNodePtr find_sibling(xmlNodePtr node, gchar *element)
396 xmlNodePtr n;
398 if (!node || !element)
399 return NULL;
401 for (n = node; n; n = n->next) {
402 if (n->type != XML_ELEMENT_NODE)
403 continue;
405 if (xmlStrEqual(n->name, (xmlChar *)element))
406 return n;
409 return NULL;
412 xmlNodePtr find_element(xmlNodePtr node, gchar *element)
414 if (!node || !element)
415 return NULL;
417 return find_sibling(node, element);
420 static void element_to_literal(gchar **element)
422 gchar *p = g_strdup_printf("!%s", *element);
423 g_free(*element);
424 *element = p;
427 xmlChar *node_has_attribute(xmlNodePtr n, xmlChar *attr)
429 xmlAttrPtr a = xmlHasProp(n, attr);
431 if (!a)
432 return NULL;
434 if (!a->children || !a->children->content)
435 return NULL;
437 return a->children->content;
440 xmlNodePtr find_elements(xmlDocPtr doc, xmlNodePtr node,
441 gchar **req, gpg_error_t *error, gboolean *target,
442 xmlNodePtr (*found_fn)(xmlNodePtr, gchar **, gpg_error_t *, void *),
443 xmlNodePtr (*not_found_fn)(xmlNodePtr, gchar **, gpg_error_t *, void *),
444 void *data)
446 xmlNodePtr n, last, last_node;
447 gchar **p;
448 gint found = 0;
450 *error = 0;
452 for (last_node = last = n = node, p = req; *p; p++) {
453 xmlNodePtr tmp;
454 gchar *t = g_strdup(*p);
455 gboolean literal = is_literal_element(&t);
457 n = find_element(last, t);
458 g_free(t);
460 if (!n) {
461 if (not_found_fn)
462 return not_found_fn(found ? last_node : last_node->parent, p, error, data);
464 *error = EPWMD_ELEMENT_NOT_FOUND;
465 return NULL;
468 last = n->children;
469 last_node = n;
470 found = 1;
472 if (literal == FALSE) {
473 xmlChar *content = node_has_attribute(n, "target");
474 gchar **nreq = NULL, **nnreq;
476 if (!content) {
477 element_to_literal(&(*p));
478 continue;
481 if (strchr((gchar *)content, '\t') != NULL) {
482 if ((nreq = split_input_line((gchar *)content, "\t", 0)) == NULL) {
483 *error = EPWMD_INVALID_ELEMENT;
484 return NULL;
487 else {
488 if ((nreq = split_input_line((gchar *)content, " ", 0)) == NULL) {
489 *error = EPWMD_INVALID_ELEMENT;
490 return NULL;
494 tmp = find_account(doc, &nreq, error, target);
496 if (!tmp) {
497 g_strfreev(nreq);
498 return NULL;
501 if (found_fn)
502 found_fn(n, nreq, error, data);
504 nnreq = append_element_path(nreq+1, p+1);
505 g_strfreev(nreq);
507 if (!nnreq || !*nnreq)
508 return tmp;
510 if (target)
511 *target = TRUE;
513 n = find_elements(doc, tmp->children, nnreq, error, NULL, found_fn,
514 not_found_fn, data);
516 if (*(p+1)) {
517 gchar **zz = p+1, **qq = nnreq;
519 if (g_strv_length(nnreq) > g_strv_length(p+1))
520 qq = nnreq+1;
522 for (; *qq && *zz; zz++) {
523 g_free(*zz);
524 *zz = g_strdup(*qq++);
528 g_strfreev(nnreq);
529 return n;
533 return n;
536 gboolean node_has_child_element(xmlNodePtr node)
538 xmlNodePtr n;
540 if (!node)
541 return FALSE;
543 for (n = node; n; n = n->next) {
544 if (n->type == XML_ELEMENT_NODE)
545 return TRUE;
547 if (n->children)
548 return node_has_child_element(n->children);
551 return FALSE;