Replaced occurrences of 'error' with 'rc' since an error() function
[pwmd.git] / src / xml.c
blob678f5a2e8f50312f5f890ed2fac03c28adaa426d
1 /* vim:tw=78:ts=8:sw=4:set ft=c: */
2 /*
3 Copyright (C) 2006-2008 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 "misc.h"
37 #include "xml.h"
39 void log_write(const gchar *fmt, ...);
40 static xmlNodePtr find_element(xmlNodePtr node, gchar *element, xmlNodePtr stop);
41 static xmlChar *node_has_attribute(xmlNodePtr n, xmlChar *attr);
44 * 'element' must be allocated.
46 gboolean is_literal_element(gchar **element)
48 gchar *p;
50 if (!element || !*element)
51 return FALSE;
53 if (*(*element) == '!') {
54 gchar *c;
56 for (p = *element, c = p+1; *c; c++)
57 *p++ = *c;
59 *p = 0;
60 return TRUE;
63 return FALSE;
67 * Fails if 'element' begins with punctuation or digit or contains whitespace.
69 * I'm not sure about using g_unichar_isspace() rather than isspace()?
71 gboolean valid_xml_element(xmlChar *element)
73 gunichar c;
74 glong len;
75 gchar *p = (gchar *)element;
77 if (!element || !*element)
78 return FALSE;
80 if (*p == '!')
81 p++;
83 len = g_utf8_strlen(p, -1) - 1;
84 c = g_utf8_get_char(p++);
86 if (g_unichar_ispunct(c) == TRUE || g_unichar_isdigit(c) == TRUE)
87 return FALSE;
89 while (*p && len--) {
90 c = g_utf8_get_char(p++);
92 if (g_unichar_isspace(c))
93 return FALSE;
96 return TRUE;
99 gboolean valid_element_path(gchar **path, gboolean has_value)
101 gchar **p;
103 for (p = path; *p; p++) {
105 * An empty element is valid and don't check the syntax of the
106 * content.
108 if (has_value == TRUE && (!*(p+1) || !*p[0]))
109 break;
111 if (valid_xml_element((xmlChar *)*p) == FALSE)
112 return FALSE;
115 return TRUE;
118 gpg_error_t new_account(xmlDocPtr doc, gchar *name)
120 xmlNodePtr root = xmlDocGetRootElement(doc);
121 xmlAttrPtr a;
122 xmlNodePtr n;
123 gchar *p = name;
125 if (!p || !root)
126 return EPWMD_LIBXML_ERROR;
128 if (*p == '!')
129 p++;
131 n = xmlNewNode(NULL, (xmlChar *)"account");
132 n = xmlAddChild(root, n);
133 a = xmlNewProp(n, (xmlChar *)"name", (xmlChar *)p);
134 return 0;
137 xmlChar *new_document()
139 xmlChar *buf;
140 const xmlChar *line = (xmlChar *)
141 "<?xml version=\"1.0\"?>"
142 "<!DOCTYPE accounts ["
143 "<!ELEMENT accounts (account*)>"
144 "<!ATTLIST account name CDATA #REQUIRED>"
145 "]>"
146 "<accounts/>";
148 buf = gcry_calloc(1, xmlStrlen(line) + 1);
149 return buf ? xmlStrcat(buf, line) : NULL;
153 * Lists root account element names. If there's a target attribute both
154 * literal and non-literal element names will be added.
156 gpg_error_t list_accounts(xmlDocPtr doc, GString **result)
158 xmlNodePtr n = NULL;
159 GSList *list = NULL;
160 gint total, i;
161 GString *string;
162 gpg_error_t rc = 0;
164 n = xmlDocGetRootElement(doc);
166 if (!n || !n->children)
167 return EPWMD_EMPTY_ELEMENT;
169 for (n = n->children; n; n = n->next) {
170 xmlAttrPtr a;
171 xmlChar *val, *target;
172 GSList *tlist;
173 gchar *tmp;
175 if (n->type != XML_ELEMENT_NODE)
176 continue;
178 a = xmlHasProp(n, (xmlChar *)"name");
180 if (!a || !a->children->content)
181 continue;
183 val = xmlNodeGetContent(a->children);
185 if (!val) {
186 rc = gpg_error_from_errno(ENOMEM);
187 goto fail;
190 tmp = g_strdup_printf("!%s", (gchar *)val);
192 if (!tmp) {
193 xmlFree(val);
194 rc = gpg_error_from_errno(ENOMEM);
195 goto fail;
198 tlist = g_slist_append(list, tmp);
200 if (!tlist) {
201 xmlFree(val);
202 rc = gpg_error_from_errno(ENOMEM);
203 goto fail;
206 list = tlist;
207 target = node_has_attribute(n, (xmlChar *)"target");
209 if (target) {
210 gchar *t = g_strdup((gchar *)val);
212 if (!t) {
213 xmlFree(val);
214 xmlFree(target);
215 rc = gpg_error_from_errno(ENOMEM);
216 goto fail;
219 tlist = g_slist_append(list, t);
221 if (!tlist) {
222 g_free(t);
223 xmlFree(target);
224 rc = gpg_error_from_errno(ENOMEM);
225 goto fail;
228 list = tlist;
231 xmlFree(val);
232 xmlFree(target);
235 total = g_slist_length(list);
237 if (!total)
238 return EPWMD_EMPTY_ELEMENT;
240 string = g_string_new(NULL);
242 if (!string) {
243 rc = gpg_error_from_errno(ENOMEM);
244 goto fail;
247 for (i = 0; i < total; i++) {
248 gchar *val = g_slist_nth_data(list, i);
250 g_string_append_printf(string, "%s\n", val);
253 string = g_string_truncate(string, string->len - 1);
254 *result = string;
256 fail:
257 total = g_slist_length(list);
259 for (i = 0; i < total; i++)
260 g_free(g_slist_nth_data(list, i));
262 g_slist_free(list);
263 return rc;
266 // FIXME return a gboolean in case of memory allocation failure
267 gchar **split_input_line(gchar *str, gchar *delim, gint n)
269 if (!str || !*str)
270 return NULL;
272 return g_strsplit(str, delim, n);
276 * Prevents a sibling element past the current element path with the same
277 * element name.
279 static xmlNodePtr find_stop_node(xmlNodePtr node)
281 xmlNodePtr n;
283 for (n = node->parent->children; n; n = n->next) {
284 if (n == node)
285 return n->next;
288 return NULL;
292 * Alot like create_elements_cb() but doesn't use the last element of 'req' as
293 * content but as an element.
295 xmlNodePtr create_target_elements_cb(xmlNodePtr node, gchar **path,
296 gpg_error_t *rc, void *data)
298 gint i;
299 char **req = path;
301 if (xmlStrEqual(node->name, (xmlChar *)*req))
302 req++;
304 for (i = 0; req[i]; i++) {
305 xmlNodePtr n;
307 if ((n = find_element(node, req[i], find_stop_node(node))) == NULL ||
308 (n && n->parent == node->parent)) {
309 is_literal_element(&req[i]);
310 n = xmlNewNode(NULL, (xmlChar *)req[i]);
312 if (!n) {
313 *rc = gpg_error_from_errno(ENOMEM);
314 return NULL;
317 node = xmlAddChild(node, n);
319 if (!node) {
320 *rc = gpg_error_from_errno(ENOMEM);
321 return NULL;
324 else
325 node = n;
328 return node;
331 xmlNodePtr find_text_node(xmlNodePtr node)
333 xmlNodePtr n = node;
335 if (n && n->type == XML_TEXT_NODE)
336 return n;
338 for (n = node; n; n = n->next) {
339 if (n->type == XML_TEXT_NODE)
340 return n;
343 return NULL;
346 xmlNodePtr create_elements_cb(xmlNodePtr node, gchar **elements,
347 gpg_error_t *rc, void *data)
349 gint i;
350 gchar **req = elements;
352 if (node->type == XML_TEXT_NODE)
353 node = node->parent;
355 if (node->name && xmlStrEqual(node->name, (xmlChar *)*req))
356 req++;
358 for (i = 0; req[i]; i++) {
359 xmlNodePtr n;
361 if (req[i+1]) {
363 * Strip the first '!' if needed. If there's another, it's an
364 * rc. The syntax has already been checked before calling this
365 * function.
367 is_literal_element(&req[i]);
371 * The value of the element tree.
373 if (!req[i+1]) {
374 n = find_text_node(node->children);
376 if (!n)
377 /* Use AddContent here to prevent overwriting any children. */
378 xmlNodeAddContent(node, (xmlChar *)req[i]);
379 else if (n && !*req[i])
380 xmlNodeSetContent(n, NULL);
381 else
382 xmlNodeSetContent(n, (xmlChar *)req[i]);
384 break;
387 n = find_element(node, req[i], find_stop_node(node));
390 * If the found element has the same parent as the current element,
391 * they are siblings and the new element needs to be created as a
392 * child of the current element (node).
394 if (n && n->parent == node->parent)
395 n = NULL;
397 if (!n) {
398 n = xmlNewNode(NULL, (xmlChar *)req[i]);
400 if (!n) {
401 *rc = gpg_error_from_errno(ENOMEM);
402 return NULL;
405 node = xmlAddChild(node, n);
407 if (!node) {
408 *rc = gpg_error_from_errno(ENOMEM);
409 return NULL;
412 else
413 node = n;
416 return node;
419 xmlNodePtr find_account(xmlDocPtr doc, gchar ***req, gpg_error_t *rc,
420 gboolean *target, gint recursion_depth)
422 xmlNodePtr n = xmlDocGetRootElement(doc);
423 gint depth = 0;
424 gchar *account = g_strdup(*req[0]);
425 gboolean literal = is_literal_element(&account);
427 if (!account) {
428 *rc = gpg_error_from_errno(ENOMEM);
429 return NULL;
432 *rc = 0;
433 recursion_depth++;
435 if (max_recursion_depth >= 1 && recursion_depth > max_recursion_depth) {
436 xmlChar *t = xmlGetNodePath(n);
438 log_write("%s: %s", pwmd_strerror(EPWMD_LOOP), t);
439 xmlFree(t);
440 *rc = EPWMD_LOOP;
441 return NULL;
444 while (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 xmlFree(content);
463 g_free(account);
464 return n;
467 xmlFree(content);
468 content = node_has_attribute(n, (xmlChar *)"target");
470 if (!content) {
471 g_free(account);
472 return n;
475 if (strchr((gchar *)content, '\t')) {
476 nreq = split_input_line((gchar *)content, "\t", 0);
477 xmlFree(content);
479 #if 0
481 * FIXME ENOMEM
483 if (!nreq) {
484 *rc = gpg_error_from_errno(ENOMEM);
485 return NULL;
487 #endif
489 tmp = *req;
490 tmp = strvcatv(nreq, tmp+1);
491 g_strfreev(nreq);
493 if (!tmp) {
494 *rc = gpg_error_from_errno(ENOMEM);
495 return NULL;
498 g_strfreev(*req);
499 *req = tmp;
501 else {
502 if (strv_printf(&tmp, "%s", content) == FALSE) {
503 xmlFree(content);
504 *rc = gpg_error_from_errno(ENOMEM);
505 return NULL;
508 xmlFree(content);
509 nreq = *req;
510 nreq = strvcatv(tmp, nreq+1);
511 g_strfreev(tmp);
513 if (!nreq) {
514 *rc = gpg_error_from_errno(ENOMEM);
515 return NULL;
518 g_strfreev(*req);
519 *req = nreq;
522 if (target)
523 *target = TRUE;
525 g_free(account);
526 n = find_account(doc, req, rc, target, recursion_depth);
527 return n;
532 n = n->next;
535 g_free(account);
536 *rc = EPWMD_ELEMENT_NOT_FOUND;
537 return NULL;
540 static xmlNodePtr find_element(xmlNodePtr node, gchar *element, xmlNodePtr stop)
542 xmlNodePtr n;
544 if (!node || !element)
545 return NULL;
547 for (n = node; n; n = n->next) {
548 if (n->type != XML_ELEMENT_NODE)
549 continue;
551 if (n == stop)
552 break;
554 if (xmlStrEqual(n->name, (xmlChar *)element))
555 return n;
558 return NULL;
561 static xmlChar *node_has_attribute(xmlNodePtr n, xmlChar *attr)
563 xmlAttrPtr a = xmlHasProp(n, attr);
565 if (!a)
566 return NULL;
568 if (!a->children || !a->children->content)
569 return NULL;
571 return xmlGetProp(n, attr);
574 static gboolean element_to_literal(gchar **element)
576 gchar *p = g_strdup_printf("!%s", *element);
578 if (!p)
579 return FALSE;
581 g_free(*element);
582 *element = p;
583 return TRUE;
586 xmlNodePtr find_elements(xmlDocPtr doc, xmlNodePtr node,
587 gchar **req, gpg_error_t *rc, gboolean *target,
588 xmlNodePtr (*found_fn)(xmlNodePtr, gchar **, gpg_error_t *, gchar **, void *),
589 xmlNodePtr (*not_found_fn)(xmlNodePtr, gchar **, gpg_error_t *, void *),
590 gboolean is_list_command, gint recursion_depth, void *data)
592 xmlNodePtr n, last, last_node;
593 gchar **p;
594 gint found = 0;
596 *rc = 0;
597 recursion_depth++;
599 if (max_recursion_depth >= 1 && recursion_depth > max_recursion_depth) {
600 xmlChar *t = xmlGetNodePath(node);
602 log_write("%s: %s", pwmd_strerror(EPWMD_LOOP), t);
603 xmlFree(t);
604 recursion_depth--;
605 *rc = EPWMD_LOOP;
606 return NULL;
609 for (last_node = last = n = node, p = req; *p; p++) {
610 xmlNodePtr tmp;
611 gchar *t = g_strdup(*p);
612 gboolean literal;
614 if (!t) {
615 *rc = gpg_error_from_errno(ENOMEM);
616 return NULL;
619 literal = is_literal_element(&t);
620 n = find_element(last, t, NULL);
621 g_free(t);
623 if (!n) {
624 if (not_found_fn)
625 return not_found_fn(found ? last_node : last_node->parent, p, rc, data);
627 *rc = EPWMD_ELEMENT_NOT_FOUND;
628 return NULL;
631 last = n->children;
632 last_node = n;
633 found = 1;
635 if (literal == FALSE) {
636 xmlChar *content = node_has_attribute(n, (xmlChar *)"target");
637 gchar **nreq = NULL, **nnreq;
639 if (!content) {
640 if (is_list_command == TRUE) {
641 if (element_to_literal(&(*p)) == FALSE) {
642 *rc = gpg_error_from_errno(ENOMEM);
643 return NULL;
647 continue;
650 if (strchr((gchar *)content, '\t') != NULL) {
651 if ((nreq = split_input_line((gchar *)content, "\t", 0)) == NULL) {
652 xmlFree(content);
653 *rc = EPWMD_INVALID_ELEMENT;
654 return NULL;
657 else {
658 if ((nreq = split_input_line((gchar *)content, " ", 0)) == NULL) {
659 xmlFree(content);
660 *rc = EPWMD_INVALID_ELEMENT;
661 return NULL;
665 xmlFree(content);
666 tmp = find_account(doc, &nreq, rc, target, 0);
668 if (!tmp) {
669 g_strfreev(nreq);
670 return NULL;
673 if (found_fn) {
674 found_fn(tmp, nreq, rc, p+1, data);
676 if (*rc) {
677 g_strfreev(nreq);
678 return NULL;
682 nnreq = strvcatv(nreq+1, p+1);
683 g_strfreev(nreq);
685 // FIXME ENOMEM
686 if (!nnreq || !*nnreq) {
687 if (nnreq)
688 g_strfreev(nnreq);
690 return tmp;
693 if (target)
694 *target = TRUE;
696 n = find_elements(doc, tmp->children, nnreq, rc, NULL, found_fn,
697 not_found_fn, is_list_command, recursion_depth, data);
699 if (*(p+1)) {
700 gchar **zz = p+1, **qq = nnreq;
702 if (g_strv_length(nnreq) > g_strv_length(p+1))
703 qq = nnreq+1;
705 for (; *qq && *zz; zz++) {
706 g_free(*zz);
707 *zz = g_strdup(*qq++);
709 if (!*zz) {
710 *rc = gpg_error_from_errno(ENOMEM);
711 n = NULL;
712 break;
717 g_strfreev(nnreq);
718 return n;
722 return n;
725 static gboolean update_element_list(struct element_list_s *elements)
727 gchar *line;
728 GSList *l;
730 if (!elements || !elements->elements)
731 return TRUE;
733 line = g_strjoinv("\t", elements->elements);
735 if (!line)
736 return FALSE;
738 g_strfreev(elements->elements);
739 elements->elements = NULL;
740 l = g_slist_append(elements->list, line);
742 if (!l)
743 return FALSE;
745 elements->list = l;
746 return TRUE;
749 static gpg_error_t path_list_recurse(xmlDocPtr doc, xmlNodePtr node,
750 struct element_list_s *elements)
752 gpg_error_t rc = 0;
753 xmlNodePtr n;
755 for (n = node; n; n = n->next) {
756 xmlChar *target = NULL;
758 if (n->type != XML_ELEMENT_NODE)
759 goto children;
761 if (strv_printf(&elements->elements, "%s\t!%s", elements->prefix, n->name) == FALSE)
762 return gpg_err_code_from_errno(ENOMEM);
764 if (update_element_list(elements) == FALSE)
765 return gpg_err_code_from_errno(ENOMEM);
767 target = node_has_attribute(n, (xmlChar *)"target");
769 if (target) {
770 gchar *tmp;
771 gchar *save = elements->prefix;
772 gboolean r = elements->resolving;
774 elements->depth++;
776 if (max_recursion_depth >= 1 && elements->depth > max_recursion_depth) {
777 xmlChar *t = xmlGetNodePath(n);
778 log_write("%s: %s", pwmd_strerror(EPWMD_LOOP), t);
779 xmlFree(t);
780 xmlFree(target);
781 return EPWMD_LOOP;
784 if (strv_printf(&elements->elements, "%s\t%s", elements->prefix, n->name) == FALSE) {
785 xmlFree(target);
786 return gpg_err_code_from_errno(ENOMEM);
789 tmp = g_strjoinv("\t", elements->elements);
791 if (!tmp) {
792 xmlFree(target);
793 return gpg_err_code_from_errno(ENOMEM);
796 if (update_element_list(elements) == FALSE) {
797 xmlFree(target);
798 return gpg_err_code_from_errno(ENOMEM);
801 elements->prefix = tmp;
802 elements->resolving = TRUE;
803 rc = create_path_list(doc, elements, (gchar *)target);
804 xmlFree(target);
805 elements->resolving = r;
806 elements->depth--;
807 g_free(tmp);
808 elements->prefix = save;
810 if (rc)
811 return rc;
814 children:
815 if (n->children) {
816 gchar *tmp = g_strdup_printf("%s\t!%s", elements->prefix, n->name);
817 gchar *save = elements->prefix;
819 if (!tmp)
820 return gpg_err_code_from_errno(ENOMEM);
822 elements->prefix = tmp;
823 rc = path_list_recurse(doc, n->children, elements);
824 g_free(elements->prefix);
825 elements->prefix = save;
827 if (rc)
828 return rc;
832 return rc;
836 * From the element path 'path', find sub-nodes and append them to the list.
838 gpg_error_t create_path_list(xmlDocPtr doc, struct element_list_s *elements,
839 gchar *path)
841 gpg_error_t rc;
842 gchar **req, **req_orig;
843 xmlNodePtr n;
844 gboolean a_target = FALSE;
846 req = split_input_line(path, "\t", 0);
848 if (!req) {
849 req = split_input_line(path, " ", 0);
851 if (!req)
852 return EPWMD_COMMAND_SYNTAX;
855 req_orig = g_strdupv(req);
857 if (!req_orig) {
858 rc = gpg_err_code_from_errno(ENOMEM);
859 goto fail;
862 n = find_account(doc, &req, &rc, &a_target, 0);
864 if (!n && rc == EPWMD_ELEMENT_NOT_FOUND && elements->resolving == TRUE) {
865 rc = 0;
866 goto fail;
868 else if (!n)
869 goto fail;
871 if (a_target == TRUE) {
872 g_free(*req);
873 *req = g_strdup(*req_orig);
876 if (*(req+1)) {
877 gboolean e_target = FALSE;
879 n = find_elements(doc, n->children, req+1, &rc, &e_target, NULL, NULL, TRUE, 0, NULL);
881 if (!n && rc == EPWMD_ELEMENT_NOT_FOUND && elements->resolving == TRUE) {
882 rc = 0;
883 goto fail;
885 else if (!n)
886 goto fail;
889 if (!elements->prefix) {
891 * FIXME
893 * If any req_orig element contains no target the element should be prefixed with
894 * the literal character. Not really crucial if the client isn't human
895 * because child elements are prefixed for the current path. But may
896 * be confusing if editing by hand.
898 elements->prefix = g_strjoinv("\t", req_orig);
900 if (!elements->prefix) {
901 rc = gpg_err_code_from_errno(ENOMEM);
902 goto fail;
905 if (strv_printf(&elements->elements, "%s", elements->prefix) == FALSE) {
906 rc = gpg_err_code_from_errno(ENOMEM);
907 goto fail;
910 if (update_element_list(elements) == FALSE) {
911 rc = gpg_err_code_from_errno(ENOMEM);
912 goto fail;
916 rc = path_list_recurse(doc, n->children, elements);
918 fail:
919 if (req_orig)
920 g_strfreev(req_orig);
922 g_strfreev(req);
923 return rc;
926 gpg_error_t recurse_xpath_nodeset(xmlDocPtr doc, xmlNodeSetPtr nodes,
927 xmlChar *value, xmlBufferPtr *result)
929 gint i = value ? nodes->nodeNr - 1 : 0;
930 xmlBufferPtr buf;
932 buf = xmlBufferCreate();
934 if (!buf)
935 return gpg_err_code_from_errno(ENOMEM);
937 for (; value ? i >= 0 : i < nodes->nodeNr; value ? i-- : i++) {
938 xmlNodePtr n = nodes->nodeTab[i];
940 if (!n)
941 continue;
943 if (!value) {
944 if (xmlNodeDump(buf, doc, n, 0, 0) == -1) {
945 *result = buf;
946 return EPWMD_LIBXML_ERROR;
949 continue;
952 xmlNodeSetContent(n, value);
955 *result = buf;
956 return 0;