Another LIST output fix. This one fixes complex target recursion and
[pwmd.git] / src / xml.c
blob7f357bb2de1a9c7dbd1f827f7fae28bd030b1b39
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, *target;
177 GSList *tlist;
178 gchar *tmp;
180 if (n->type != XML_ELEMENT_NODE)
181 continue;
183 a = xmlHasProp(n, (xmlChar *)"name");
185 if (!a || !a->children->content)
186 continue;
188 val = xmlNodeGetContent(a->children);
190 if (!val) {
191 error = gpg_error_from_errno(ENOMEM);
192 goto fail;
195 tmp = g_strdup_printf("!%s", (gchar *)val);
197 if (!tmp) {
198 xmlFree(val);
199 error = gpg_error_from_errno(ENOMEM);
200 goto fail;
203 tlist = g_slist_append(list, tmp);
205 if (!tlist) {
206 xmlFree(val);
207 error = gpg_error_from_errno(ENOMEM);
208 goto fail;
211 list = tlist;
212 target = node_has_attribute(n, (xmlChar *)"target");
214 if (target) {
215 gchar *t = g_strdup((gchar *)val);
217 if (!t) {
218 xmlFree(val);
219 error = gpg_error_from_errno(ENOMEM);
220 goto fail;
223 tlist = g_slist_append(list, t);
225 if (!tlist) {
226 g_free(t);
227 error = gpg_error_from_errno(ENOMEM);
228 goto fail;
231 list = tlist;
234 xmlFree(val);
237 total = g_slist_length(list);
239 if (!total)
240 return EPWMD_EMPTY_ELEMENT;
242 string = g_string_new(NULL);
244 if (!string) {
245 error = gpg_error_from_errno(ENOMEM);
246 goto fail;
249 for (i = 0; i < total; i++) {
250 gchar *val = g_slist_nth_data(list, i);
252 g_string_append_printf(string, "%s\n", val);
255 string = g_string_truncate(string, string->len - 1);
256 *result = string;
258 fail:
259 total = g_slist_length(list);
261 for (i = 0; i < total; i++)
262 g_free(g_slist_nth_data(list, i));
264 g_slist_free(list);
265 return error;
268 // FIXME return a gboolean in case of memory allocation failure
269 gchar **split_input_line(gchar *str, gchar *delim, gint n)
271 if (!str || !*str)
272 return NULL;
274 return g_strsplit(str, delim, n);
277 static gchar **append_element_path(gchar **dst, gchar **src)
279 gchar **p;
280 gint i;
281 gchar **d;
283 if (!src)
284 return NULL;
286 d = g_strdupv(dst);
288 if (!d)
289 return NULL;
291 i = g_strv_length(d);
293 for (p = src; *p; p++) {
294 gchar **pa;
296 pa = g_realloc(d, (i + 2) * sizeof(gchar *));
298 if (!pa) {
299 g_strfreev(d);
300 return NULL;
303 d = pa;
304 d[i] = g_strdup(*p);
306 if (!d[i]) {
307 g_strfreev(d);
308 return NULL;
311 d[++i] = NULL;
314 return d;
317 static xmlNodePtr find_stop_node(xmlNodePtr node)
319 xmlNodePtr n;
321 for (n = node->parent->children; n; n = n->next) {
322 if (n == node)
323 return n->next;
326 return NULL;
330 * Alot like create_elements_cb() but doesn't use the last element of 'req' as
331 * content but as an element.
333 xmlNodePtr create_target_elements_cb(xmlNodePtr node, gchar **path,
334 gpg_error_t *error, void *data)
336 gint i;
337 char **req = path;
339 if (xmlStrEqual(node->name, (xmlChar *)*req))
340 req++;
342 for (i = 0; req[i]; i++) {
343 xmlNodePtr n;
345 if ((n = find_element(node, req[i], find_stop_node(node))) == NULL ||
346 (n && n->parent == node->parent)) {
347 is_literal_element(&req[i]);
348 n = xmlNewNode(NULL, (xmlChar *)req[i]);
350 if (!n) {
351 *error = gpg_error_from_errno(ENOMEM);
352 return NULL;
355 node = xmlAddChild(node, n);
357 if (!node) {
358 *error = gpg_error_from_errno(ENOMEM);
359 return NULL;
362 else
363 node = n;
366 return node;
369 xmlNodePtr find_text_node(xmlNodePtr node)
371 xmlNodePtr n = node;
373 if (n && n->type == XML_TEXT_NODE)
374 return n;
376 for (n = node; n; n = n->next) {
377 if (n->type == XML_TEXT_NODE)
378 return n;
381 return NULL;
384 xmlNodePtr create_elements_cb(xmlNodePtr node, gchar **elements,
385 gpg_error_t *error, void *data)
387 gint i;
388 gchar **req = elements;
390 if (node->type == XML_TEXT_NODE)
391 node = node->parent;
393 if (node->name && xmlStrEqual(node->name, (xmlChar *)*req))
394 req++;
396 for (i = 0; req[i]; i++) {
397 xmlNodePtr n;
399 if (req[i+1]) {
401 * Strip the first '!' if needed. If there's another, it's an
402 * error. The syntax has already been checked before calling this
403 * function.
405 is_literal_element(&req[i]);
409 * The value of the element tree.
411 if (!req[i+1]) {
412 n = find_text_node(node->children);
414 if (!n)
415 /* Use AddContent here to prevent overwriting any children. */
416 xmlNodeAddContent(node, (xmlChar *)req[i]);
417 else if (n && !*req[i])
418 xmlNodeSetContent(n, NULL);
419 else
420 xmlNodeSetContent(n, (xmlChar *)req[i]);
422 break;
425 n = find_element(node, req[i], find_stop_node(node));
428 * If the found element has the same parent as the current element,
429 * they are siblings and the new element needs to be created as a
430 * child of the current element (node).
432 if (n && n->parent == node->parent)
433 n = NULL;
435 if (!n) {
436 n = xmlNewNode(NULL, (xmlChar *)req[i]);
438 if (!n) {
439 *error = gpg_error_from_errno(ENOMEM);
440 return NULL;
443 node = xmlAddChild(node, n);
445 if (!node) {
446 *error = gpg_error_from_errno(ENOMEM);
447 return NULL;
450 else
451 node = n;
454 return node;
457 xmlNodePtr find_account(xmlDocPtr doc, gchar ***req, gpg_error_t *error,
458 gboolean *target, gint recursion_depth)
460 xmlNodePtr n = xmlDocGetRootElement(doc);
461 gint depth = 0;
462 gchar *account = g_strdup(*req[0]);
463 gboolean literal = is_literal_element(&account);
465 if (!account) {
466 *error = gpg_error_from_errno(ENOMEM);
467 return NULL;
470 *error = 0;
471 recursion_depth++;
473 if (max_recursion_depth >= 1 && recursion_depth > max_recursion_depth) {
474 xmlChar *t = xmlGetNodePath(n);
476 log_write("%s: %s", pwmd_strerror(EPWMD_LOOP), t);
477 xmlFree(t);
478 *error = EPWMD_LOOP;
479 return NULL;
482 while (n) {
483 if (n->type == XML_ELEMENT_NODE) {
484 if (depth == 0 && xmlStrEqual(n->name, (xmlChar *)"accounts")) {
485 n = n->children;
486 depth++;
487 continue;
490 if (depth == 1 && xmlStrEqual(n->name, (xmlChar *)"account")) {
491 xmlChar *content = node_has_attribute(n, (xmlChar *)"name");
493 if (!content)
494 continue;
496 if (xmlStrEqual(content, (xmlChar *)account)) {
497 gchar **nreq, **tmp = NULL;
499 if (literal == TRUE) {
500 g_free(account);
501 return n;
504 content = node_has_attribute(n, (xmlChar *)"target");
506 if (!content) {
507 g_free(account);
508 return n;
511 if (strchr((gchar *)content, '\t')) {
512 nreq = split_input_line((gchar *)content, "\t", 0);
514 #if 0
516 * FIXME ENOMEM
518 if (!nreq) {
519 *error = gpg_error_from_errno(ENOMEM);
520 return NULL;
522 #endif
524 tmp = *req;
525 tmp = append_element_path(nreq, tmp+1);
526 g_strfreev(nreq);
528 if (!tmp) {
529 *error = gpg_error_from_errno(ENOMEM);
530 return NULL;
533 g_strfreev(*req);
534 *req = tmp;
536 else {
537 if (strv_printf(&tmp, "%s", content) == FALSE) {
538 *error = gpg_error_from_errno(ENOMEM);
539 return NULL;
542 nreq = *req;
543 nreq = append_element_path(tmp, nreq+1);
544 g_strfreev(tmp);
546 if (!nreq) {
547 *error = gpg_error_from_errno(ENOMEM);
548 return NULL;
551 g_strfreev(*req);
552 *req = nreq;
555 if (target)
556 *target = TRUE;
558 g_free(account);
559 n = find_account(doc, req, error, target, recursion_depth);
560 return n;
565 n = n->next;
568 g_free(account);
569 *error = EPWMD_ELEMENT_NOT_FOUND;
570 return NULL;
573 xmlNodePtr find_sibling(xmlNodePtr node, gchar *element, xmlNodePtr stop)
575 xmlNodePtr n;
577 if (!node || !element)
578 return NULL;
580 for (n = node; n; n = n->next) {
581 if (n->type != XML_ELEMENT_NODE)
582 continue;
584 if (n == stop)
585 break;
587 if (xmlStrEqual(n->name, (xmlChar *)element))
588 return n;
591 return NULL;
594 xmlNodePtr find_element(xmlNodePtr node, gchar *element, xmlNodePtr stop)
596 if (!node || !element)
597 return NULL;
599 return find_sibling(node, element, stop);
602 // FIXME: return xmlNodeGetContent(a->children).
603 xmlChar *node_has_attribute(xmlNodePtr n, xmlChar *attr)
605 xmlAttrPtr a = xmlHasProp(n, attr);
607 if (!a)
608 return NULL;
610 if (!a->children || !a->children->content)
611 return NULL;
613 return a->children->content;
616 static gboolean element_to_literal(gchar **element)
618 gchar *p = g_strdup_printf("!%s", *element);
620 if (!p)
621 return FALSE;
623 g_free(*element);
624 *element = p;
625 return TRUE;
628 xmlNodePtr find_elements(xmlDocPtr doc, xmlNodePtr node,
629 gchar **req, gpg_error_t *error, gboolean *target,
630 xmlNodePtr (*found_fn)(xmlNodePtr, gchar **, gpg_error_t *, void *),
631 xmlNodePtr (*not_found_fn)(xmlNodePtr, gchar **, gpg_error_t *, void *),
632 gboolean is_list_command, gint recursion_depth, void *data)
634 xmlNodePtr n, last, last_node;
635 gchar **p;
636 gint found = 0;
638 *error = 0;
639 recursion_depth++;
641 if (max_recursion_depth >= 1 && recursion_depth > max_recursion_depth) {
642 xmlChar *t = xmlGetNodePath(node);
644 log_write("%s: %s", pwmd_strerror(EPWMD_LOOP), t);
645 xmlFree(t);
646 recursion_depth--;
647 *error = EPWMD_LOOP;
648 return NULL;
651 for (last_node = last = n = node, p = req; *p; p++) {
652 xmlNodePtr tmp;
653 gchar *t = g_strdup(*p);
654 gboolean literal;
656 if (!t) {
657 *error = gpg_error_from_errno(ENOMEM);
658 return NULL;
661 literal = is_literal_element(&t);
662 n = find_element(last, t, NULL);
663 g_free(t);
665 if (!n) {
666 if (not_found_fn)
667 return not_found_fn(found ? last_node : last_node->parent, p, error, data);
669 *error = EPWMD_ELEMENT_NOT_FOUND;
670 return NULL;
673 last = n->children;
674 last_node = n;
675 found = 1;
677 if (literal == FALSE) {
678 xmlChar *content = node_has_attribute(n, (xmlChar *)"target");
679 gchar **nreq = NULL, **nnreq;
681 if (!content) {
682 if (is_list_command == TRUE) {
683 if (element_to_literal(&(*p)) == FALSE) {
684 *error = gpg_error_from_errno(ENOMEM);
685 return NULL;
689 continue;
692 if (strchr((gchar *)content, '\t') != NULL) {
693 if ((nreq = split_input_line((gchar *)content, "\t", 0)) == NULL) {
694 *error = EPWMD_INVALID_ELEMENT;
695 return NULL;
698 else {
699 if ((nreq = split_input_line((gchar *)content, " ", 0)) == NULL) {
700 *error = EPWMD_INVALID_ELEMENT;
701 return NULL;
705 tmp = find_account(doc, &nreq, error, target, 0);
707 if (!tmp) {
708 g_strfreev(nreq);
709 return NULL;
712 if (found_fn)
713 found_fn(n, nreq, error, data);
715 nnreq = append_element_path(nreq+1, p+1);
716 g_strfreev(nreq);
718 // FIXME ENOMEM
719 if (!nnreq || !*nnreq) {
720 if (nnreq)
721 g_strfreev(nnreq);
723 return tmp;
726 if (target)
727 *target = TRUE;
729 n = find_elements(doc, tmp->children, nnreq, error, NULL, found_fn,
730 not_found_fn, is_list_command, recursion_depth, data);
732 if (*(p+1)) {
733 gchar **zz = p+1, **qq = nnreq;
735 if (g_strv_length(nnreq) > g_strv_length(p+1))
736 qq = nnreq+1;
738 for (; *qq && *zz; zz++) {
739 g_free(*zz);
740 *zz = g_strdup(*qq++);
742 if (!*zz) {
743 *error = gpg_error_from_errno(ENOMEM);
744 n = NULL;
745 break;
750 g_strfreev(nnreq);
751 return n;
755 return n;
758 gboolean node_has_child_element(xmlNodePtr node)
760 xmlNodePtr n;
762 if (!node)
763 return FALSE;
765 for (n = node; n; n = n->next) {
766 if (n->type == XML_ELEMENT_NODE)
767 return TRUE;
769 if (n->children)
770 return node_has_child_element(n->children);
773 return FALSE;
776 static gboolean update_element_list(struct element_list_s *elements)
778 gchar *line;
779 GSList *l;
781 if (!elements || !elements->elements)
782 return TRUE;
784 line = g_strjoinv("\t", elements->elements);
786 if (!line)
787 return FALSE;
789 g_strfreev(elements->elements);
790 elements->elements = NULL;
791 l = g_slist_append(elements->list, line);
793 if (!l)
794 return FALSE;
796 elements->list = l;
797 return TRUE;
800 static gpg_error_t path_list_recurse(xmlDocPtr doc, xmlNodePtr node,
801 struct element_list_s *elements)
803 gpg_error_t rc = 0;
804 xmlNodePtr n;
806 for (n = node; n; n = n->next) {
807 xmlChar *target = NULL;
809 if (n->type != XML_ELEMENT_NODE)
810 goto children;
812 target = node_has_attribute(n, (xmlChar *)"target");
814 if (strv_printf(&elements->elements, "%s\t!%s", elements->prefix, n->name) == FALSE)
815 return gpg_err_code_from_errno(ENOMEM);
817 if (update_element_list(elements) == FALSE)
818 return gpg_err_code_from_errno(ENOMEM);
820 if (target) {
821 gchar *tmp;
822 gchar *save = elements->prefix;
823 gboolean r = elements->resolving;
825 elements->depth++;
827 if (max_recursion_depth >= 1 && elements->depth > max_recursion_depth) {
828 xmlChar *t = xmlGetNodePath(n);
829 log_write("%s: %s", pwmd_strerror(EPWMD_LOOP), t);
830 xmlFree(t);
831 return EPWMD_LOOP;
834 if (strv_printf(&elements->elements, "%s\t%s", elements->prefix, n->name) == FALSE)
835 return gpg_err_code_from_errno(ENOMEM);
837 tmp = g_strjoinv("\t", elements->elements);
839 if (!tmp)
840 return gpg_err_code_from_errno(ENOMEM);
842 if (update_element_list(elements) == FALSE)
843 return gpg_err_code_from_errno(ENOMEM);
845 elements->prefix = tmp;
846 elements->resolving = TRUE;
847 rc = create_path_list(doc, elements, (gchar *)target);
848 elements->resolving = r;
849 elements->depth--;
850 g_free(tmp);
851 elements->prefix = save;
853 if (rc)
854 return rc;
857 children:
858 if (n->children) {
859 gchar *tmp = g_strdup_printf("%s\t!%s", elements->prefix, n->name);
860 gchar *save = elements->prefix;
862 if (!tmp)
863 return gpg_err_code_from_errno(ENOMEM);
865 elements->prefix = tmp;
866 rc = path_list_recurse(doc, n->children, elements);
867 g_free(elements->prefix);
868 elements->prefix = save;
870 if (rc)
871 return rc;
875 return rc;
879 * From the element path 'path', find sub-nodes and append them to the list.
881 gpg_error_t create_path_list(xmlDocPtr doc, struct element_list_s *elements,
882 gchar *path)
884 gpg_error_t rc;
885 gchar **req, **req_orig;
886 xmlNodePtr n;
887 gboolean a_target = FALSE;
889 req = split_input_line(path, "\t", 0);
891 if (!req) {
892 req = split_input_line(path, " ", 0);
894 if (!req)
895 return EPWMD_COMMAND_SYNTAX;
898 req_orig = g_strdupv(req);
900 if (!req_orig) {
901 rc = gpg_err_code_from_errno(ENOMEM);
902 goto fail;
905 n = find_account(doc, &req, &rc, &a_target, 0);
907 if (!n && rc == EPWMD_ELEMENT_NOT_FOUND && elements->resolving == TRUE) {
908 rc = 0;
909 goto fail;
911 else if (!n)
912 goto fail;
914 if (a_target == TRUE) {
915 g_free(*req);
916 *req = g_strdup(*req_orig);
919 if (*(req+1)) {
920 gboolean e_target = FALSE;
922 n = find_elements(doc, n->children, req+1, &rc, &e_target, NULL, NULL, TRUE, 0, NULL);
924 if (!n && rc == EPWMD_ELEMENT_NOT_FOUND && elements->resolving == TRUE) {
925 rc = 0;
926 goto fail;
928 else if (!n)
929 goto fail;
932 if (!elements->prefix) {
934 * FIXME
936 * If any req_orig element contains no target the element should be prefixed with
937 * the literal character. Not really crucial if the client isn't human
938 * because sub-nodes/sub-elements are prefixed when needed.
940 elements->prefix = g_strjoinv("\t", req_orig);
942 if (!elements->prefix) {
943 rc = gpg_err_code_from_errno(ENOMEM);
944 goto fail;
947 if (strv_printf(&elements->elements, "%s", elements->prefix) == FALSE) {
948 rc = gpg_err_code_from_errno(ENOMEM);
949 goto fail;
952 if (update_element_list(elements) == FALSE) {
953 rc = gpg_err_code_from_errno(ENOMEM);
954 goto fail;
958 rc = path_list_recurse(doc, n->children, elements);
960 fail:
961 if (req_orig)
962 g_strfreev(req_orig);
964 g_strfreev(req);
965 return rc;