Use xmlDocGetRootElement() and xmlNodeGetContent() in list_accounts()
[pwmd.git] / src / xml.c
blob040341c43730a7bf849789898a76abae6828741e
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 gchar *c;
62 for (p = *element, c = p+1; *c; c++)
63 *p++ = *c;
65 *p = 0;
66 return TRUE;
69 return FALSE;
73 * Fails if 'element' begins with punctuation or digit or contains whitespace.
75 * I'm not sure about using g_unichar_isspace() rather than isspace()?
77 gboolean valid_xml_element(xmlChar *element)
79 gunichar c;
80 glong len;
81 gchar *p = (gchar *)element;
83 if (!element || !*element)
84 return FALSE;
86 if (*p == '!')
87 p++;
89 len = g_utf8_strlen(p, -1) - 1;
90 c = g_utf8_get_char(p++);
92 if (g_unichar_ispunct(c) == TRUE || g_unichar_isdigit(c) == TRUE)
93 return FALSE;
95 while (*p && len--) {
96 c = g_utf8_get_char(p++);
98 if (g_unichar_isspace(c))
99 return FALSE;
102 return TRUE;
105 gboolean valid_element_path(gchar **path, gboolean has_value)
107 gchar **p;
109 for (p = path; *p; p++) {
111 * An empty element is valid and don't check the syntax of the
112 * content.
114 if (has_value == TRUE && (!*(p+1) || !*p[0]))
115 break;
117 if (valid_xml_element((xmlChar *)*p) == FALSE)
118 return FALSE;
121 return TRUE;
124 gpg_error_t new_account(xmlDocPtr doc, gchar *name)
126 xmlNodePtr root = xmlDocGetRootElement(doc);
127 xmlAttrPtr a;
128 xmlNodePtr n;
129 gchar *p = name;
131 if (!p || !root)
132 return EPWMD_LIBXML_ERROR;
134 if (is_literal_element_str(p))
135 p++;
137 n = xmlNewNode(NULL, (xmlChar *)"account");
138 n = xmlAddChild(root, n);
139 a = xmlNewProp(n, (xmlChar *)"name", (xmlChar *)p);
140 return 0;
143 xmlChar *new_document()
145 xmlChar *buf;
146 const xmlChar *line = (xmlChar *)
147 "<?xml version=\"1.0\"?>\n"
148 "<!DOCTYPE accounts [\n"
149 "<!ELEMENT accounts (account*)>\n"
150 "<!ATTLIST account name CDATA #REQUIRED>\n"
151 "]>\n"
152 "<accounts/>";
154 buf = gcry_calloc(1, xmlStrlen(line) + 1);
155 return buf ? xmlStrcat(buf, line) : NULL;
159 * The "target" attribute is ignored here.
161 gpg_error_t list_accounts(xmlDocPtr doc, GString **result)
163 xmlNodePtr n = NULL;
164 GSList *list = NULL;
165 gint total, i;
166 GString *string;
167 gpg_error_t error = 0;
169 n = xmlDocGetRootElement(doc);
171 if (!n || !n->children)
172 return EPWMD_EMPTY_ELEMENT;
174 for (n = n->children; n; n = n->next) {
175 xmlAttrPtr a;
176 xmlChar *val;
177 GSList *tlist;
179 if (n->type != XML_ELEMENT_NODE)
180 continue;
182 a = xmlHasProp(n, (xmlChar *)"name");
184 if (!a || !a->children->content)
185 continue;
187 val = xmlNodeGetContent(a->children);
189 if (!val) {
190 error = gpg_error_from_errno(ENOMEM);
191 goto fail;
194 tlist = g_slist_append(list, (gchar *)val);
196 if (!tlist) {
197 error = gpg_error_from_errno(ENOMEM);
198 goto fail;
201 list = tlist;
204 total = g_slist_length(list);
206 if (!total)
207 return EPWMD_EMPTY_ELEMENT;
209 string = g_string_new(NULL);
211 if (!string) {
212 error = gpg_error_from_errno(ENOMEM);
213 goto fail;
216 for (i = 0; i < total; i++) {
217 xmlChar *val = g_slist_nth_data(list, i);
219 g_string_append_printf(string, "%s\n", val);
222 string = g_string_truncate(string, string->len - 1);
223 *result = string;
225 fail:
226 total = g_slist_length(list);
228 for (i = 0; i < total; i++)
229 xmlFree(g_slist_nth_data(list, i));
231 g_slist_free(list);
232 return error;
235 gchar **split_input_line(gchar *str, gchar *delim, gint n)
237 if (!str || !*str)
238 return NULL;
240 return g_strsplit(str, delim, n);
243 static gchar **append_element_path(gchar **dst, gchar **src)
245 gchar **p;
246 gint i;
247 gchar **d;
249 if (!src)
250 return NULL;
252 d = g_strdupv(dst);
254 if (!d)
255 return NULL;
257 i = g_strv_length(d);
259 for (p = src; *p; p++) {
260 gchar **pa;
262 pa = g_realloc(d, (i + 2) * sizeof(gchar *));
264 if (!pa) {
265 g_strfreev(d);
266 return NULL;
269 d = pa;
270 d[i] = g_strdup(*p);
272 if (!d[i]) {
273 g_strfreev(d);
274 return NULL;
277 d[++i] = NULL;
280 return d;
283 static xmlNodePtr find_stop_node(xmlNodePtr node)
285 xmlNodePtr n;
287 for (n = node->parent->children; n; n = n->next) {
288 if (n == node)
289 return n->next;
292 return NULL;
296 * Alot like create_elements_cb() but doesn't use the last element of 'req' as
297 * content but as an element.
299 xmlNodePtr create_target_elements_cb(xmlNodePtr node, gchar **path,
300 gpg_error_t *error, void *data)
302 gint i;
303 char **req = path;
305 if (xmlStrEqual(node->name, (xmlChar *)*req))
306 req++;
308 for (i = 0; req[i]; i++) {
309 xmlNodePtr n;
311 if ((n = find_element(node, req[i], find_stop_node(node))) == NULL ||
312 (n && n->parent == node->parent)) {
313 is_literal_element(&req[i]);
314 n = xmlNewNode(NULL, (xmlChar *)req[i]);
316 if (!n) {
317 *error = gpg_error_from_errno(ENOMEM);
318 return NULL;
321 node = xmlAddChild(node, n);
323 if (!node) {
324 *error = gpg_error_from_errno(ENOMEM);
325 return NULL;
328 else
329 node = n;
332 return node;
335 xmlNodePtr find_text_node(xmlNodePtr node)
337 xmlNodePtr n = node;
339 if (n && n->type == XML_TEXT_NODE)
340 return n;
342 for (n = node; n; n = n->next) {
343 if (n->type == XML_TEXT_NODE)
344 return n;
347 return NULL;
350 xmlNodePtr create_elements_cb(xmlNodePtr node, gchar **elements,
351 gpg_error_t *error, void *data)
353 gint i;
354 gchar **req = elements;
356 if (node->type == XML_TEXT_NODE)
357 node = node->parent;
359 if (node->name && xmlStrEqual(node->name, (xmlChar *)*req))
360 req++;
362 for (i = 0; req[i]; i++) {
363 xmlNodePtr n;
365 if (req[i+1]) {
367 * Strip the first '!' if needed. If there's another, it's an
368 * error. The syntax has already been checked before calling this
369 * function.
371 is_literal_element(&req[i]);
375 * The value of the element tree.
377 if (!req[i+1]) {
378 n = find_text_node(node->children);
380 if (!n)
381 /* Use AddContent here to prevent overwriting any children. */
382 xmlNodeAddContent(node, (xmlChar *)req[i]);
383 else if (n && !*req[i])
384 xmlNodeSetContent(n, NULL);
385 else
386 xmlNodeSetContent(n, (xmlChar *)req[i]);
388 break;
391 n = find_element(node, req[i], find_stop_node(node));
394 * If the found element has the same parent as the current element,
395 * they are siblings and the new element needs to be created as a
396 * child of the current element (node).
398 if (n && n->parent == node->parent)
399 n = NULL;
401 if (!n) {
402 n = xmlNewNode(NULL, (xmlChar *)req[i]);
404 if (!n) {
405 *error = gpg_error_from_errno(ENOMEM);
406 return NULL;
409 node = xmlAddChild(node, n);
411 if (!node) {
412 *error = gpg_error_from_errno(ENOMEM);
413 return NULL;
416 else
417 node = n;
420 return node;
423 xmlNodePtr find_account(xmlDocPtr doc, gchar ***req, gpg_error_t *error,
424 gboolean *target, gint recursion_depth)
426 xmlNodePtr n;
427 gint depth = 0;
428 gchar *account = g_strdup(*req[0]);
429 gboolean literal = is_literal_element(&account);
431 if (!account) {
432 *error = gpg_error_from_errno(ENOMEM);
433 return NULL;
436 *error = 0;
437 recursion_depth++;
439 if (max_recursion_depth >= 1 && recursion_depth > max_recursion_depth) {
440 *error = EPWMD_LOOP;
441 return NULL;
444 for (n = doc->children; n;) {
445 if (n->type == XML_ELEMENT_NODE) {
446 if (depth == 0 && xmlStrEqual(n->name, (xmlChar *)"accounts")) {
447 n = n->children;
448 depth++;
449 continue;
452 if (depth == 1 && xmlStrEqual(n->name, (xmlChar *)"account")) {
453 xmlChar *content = node_has_attribute(n, (xmlChar *)"name");
455 if (!content)
456 continue;
458 if (xmlStrEqual(content, (xmlChar *)account)) {
459 gchar **nreq, **tmp = NULL;
461 if (literal == TRUE) {
462 g_free(account);
463 return n;
466 content = node_has_attribute(n, (xmlChar *)"target");
468 if (!content) {
469 g_free(account);
470 return n;
473 if (strchr((gchar *)content, '\t')) {
474 nreq = split_input_line((gchar *)content, "\t", 0);
476 #if 0
478 * FIXME ENOMEM
480 if (!nreq) {
481 *error = gpg_error_from_errno(ENOMEM);
482 return NULL;
484 #endif
486 tmp = *req;
487 tmp = append_element_path(nreq, tmp+1);
488 g_strfreev(nreq);
490 if (!tmp) {
491 *error = gpg_error_from_errno(ENOMEM);
492 return NULL;
495 g_strfreev(*req);
496 *req = tmp;
498 else {
499 if (strv_printf(&tmp, "%s", content) == FALSE) {
500 *error = gpg_error_from_errno(ENOMEM);
501 return NULL;
504 nreq = *req;
505 nreq = append_element_path(tmp, nreq+1);
506 g_strfreev(tmp);
508 if (!nreq) {
509 *error = gpg_error_from_errno(ENOMEM);
510 return NULL;
513 g_strfreev(*req);
514 *req = nreq;
517 if (target)
518 *target = TRUE;
520 g_free(account);
521 n = find_account(doc, req, error, target, recursion_depth);
522 return n;
527 n = n->next;
530 g_free(account);
531 *error = EPWMD_ELEMENT_NOT_FOUND;
532 return NULL;
535 xmlNodePtr find_sibling(xmlNodePtr node, gchar *element, xmlNodePtr stop)
537 xmlNodePtr n;
539 if (!node || !element)
540 return NULL;
542 for (n = node; n; n = n->next) {
543 if (n->type != XML_ELEMENT_NODE)
544 continue;
546 if (n == stop)
547 break;
549 if (xmlStrEqual(n->name, (xmlChar *)element))
550 return n;
553 return NULL;
556 xmlNodePtr find_element(xmlNodePtr node, gchar *element, xmlNodePtr stop)
558 if (!node || !element)
559 return NULL;
561 return find_sibling(node, element, stop);
564 xmlChar *node_has_attribute(xmlNodePtr n, xmlChar *attr)
566 xmlAttrPtr a = xmlHasProp(n, attr);
568 if (!a)
569 return NULL;
571 if (!a->children || !a->children->content)
572 return NULL;
574 return a->children->content;
577 static gboolean element_to_literal(gchar **element)
579 gchar *p = g_strdup_printf("!%s", *element);
581 if (!p)
582 return FALSE;
584 g_free(*element);
585 *element = p;
586 return TRUE;
589 xmlNodePtr find_elements(xmlDocPtr doc, xmlNodePtr node,
590 gchar **req, gpg_error_t *error, gboolean *target,
591 xmlNodePtr (*found_fn)(xmlNodePtr, gchar **, gpg_error_t *, void *),
592 xmlNodePtr (*not_found_fn)(xmlNodePtr, gchar **, gpg_error_t *, void *),
593 gboolean is_list_command, gint recursion_depth, void *data)
595 xmlNodePtr n, last, last_node;
596 gchar **p;
597 gint found = 0;
599 *error = 0;
600 recursion_depth++;
602 if (max_recursion_depth >= 1 && recursion_depth > max_recursion_depth) {
603 recursion_depth--;
604 *error = EPWMD_LOOP;
605 return NULL;
608 for (last_node = last = n = node, p = req; *p; p++) {
609 xmlNodePtr tmp;
610 gchar *t = g_strdup(*p);
611 gboolean literal;
613 if (!t) {
614 *error = gpg_error_from_errno(ENOMEM);
615 return NULL;
618 literal = is_literal_element(&t);
619 n = find_element(last, t, NULL);
620 g_free(t);
622 if (!n) {
623 if (not_found_fn)
624 return not_found_fn(found ? last_node : last_node->parent, p, error, data);
626 *error = EPWMD_ELEMENT_NOT_FOUND;
627 return NULL;
630 last = n->children;
631 last_node = n;
632 found = 1;
634 if (literal == FALSE) {
635 xmlChar *content = node_has_attribute(n, (xmlChar *)"target");
636 gchar **nreq = NULL, **nnreq;
638 if (!content) {
639 if (is_list_command == TRUE) {
640 if (element_to_literal(&(*p)) == FALSE) {
641 *error = gpg_error_from_errno(ENOMEM);
642 return NULL;
646 continue;
649 if (strchr((gchar *)content, '\t') != NULL) {
650 if ((nreq = split_input_line((gchar *)content, "\t", 0)) == NULL) {
651 *error = EPWMD_INVALID_ELEMENT;
652 return NULL;
655 else {
656 if ((nreq = split_input_line((gchar *)content, " ", 0)) == NULL) {
657 *error = EPWMD_INVALID_ELEMENT;
658 return NULL;
662 tmp = find_account(doc, &nreq, error, target, 0);
664 if (!tmp) {
665 g_strfreev(nreq);
666 return NULL;
669 if (found_fn)
670 found_fn(n, nreq, error, data);
672 nnreq = append_element_path(nreq+1, p+1);
673 g_strfreev(nreq);
675 // FIXME ENOMEM
676 if (!nnreq || !*nnreq) {
677 if (nnreq)
678 g_strfreev(nnreq);
680 return tmp;
683 if (target)
684 *target = TRUE;
686 n = find_elements(doc, tmp->children, nnreq, error, NULL, found_fn,
687 not_found_fn, is_list_command, recursion_depth, data);
689 if (*(p+1)) {
690 gchar **zz = p+1, **qq = nnreq;
692 if (g_strv_length(nnreq) > g_strv_length(p+1))
693 qq = nnreq+1;
695 for (; *qq && *zz; zz++) {
696 g_free(*zz);
697 *zz = g_strdup(*qq++);
699 if (!*zz) {
700 *error = gpg_error_from_errno(ENOMEM);
701 n = NULL;
702 break;
707 g_strfreev(nnreq);
708 return n;
712 return n;
715 gboolean node_has_child_element(xmlNodePtr node)
717 xmlNodePtr n;
719 if (!node)
720 return FALSE;
722 for (n = node; n; n = n->next) {
723 if (n->type == XML_ELEMENT_NODE)
724 return TRUE;
726 if (n->children)
727 return node_has_child_element(n->children);
730 return FALSE;