1 /* vim:tw=78:ts=8:sw=4:set ft=c: */
3 Copyright (C) 2006-2009 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 02110-1301 USA
35 #include "pwmd_error.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
)
50 if (!element
|| !*element
)
53 if (*(*element
) == '!') {
56 for (p
= *element
, c
= p
+1; *c
; c
++)
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
)
75 gchar
*p
= (gchar
*)element
;
77 if (!element
|| !*element
)
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 g_unichar_isspace(c
) == TRUE
)
91 c
= g_utf8_get_char(p
++);
93 if (g_unichar_isspace(c
))
100 gboolean
valid_element_path(gchar
**path
, gboolean has_value
)
104 for (p
= path
; *p
; p
++) {
106 * An empty element is valid and don't check the syntax of the
109 if (has_value
== TRUE
&& (!*(p
+1) || !*p
[0]))
112 if (valid_xml_element((xmlChar
*)*p
) == FALSE
)
119 gpg_error_t
new_account(xmlDocPtr doc
, gchar
*name
)
121 xmlNodePtr root
= xmlDocGetRootElement(doc
);
127 return EPWMD_LIBXML_ERROR
;
132 n
= xmlNewNode(NULL
, (xmlChar
*)"account");
133 n
= xmlAddChild(root
, n
);
134 a
= xmlNewProp(n
, (xmlChar
*)"name", (xmlChar
*)p
);
138 xmlChar
*new_document()
141 const xmlChar
*line
= (xmlChar
*)
142 "<?xml version=\"1.0\"?>"
143 "<!DOCTYPE accounts ["
144 "<!ELEMENT accounts (account*)>"
145 "<!ATTLIST account name CDATA #REQUIRED>"
149 buf
= gcry_calloc(1, xmlStrlen(line
) + 1);
150 return buf
? xmlStrcat(buf
, line
) : NULL
;
154 * Lists root account element names. If there's a target attribute both
155 * literal and non-literal element names will be added.
157 gpg_error_t
list_accounts(xmlDocPtr doc
, GString
**result
)
165 n
= xmlDocGetRootElement(doc
);
167 if (!n
|| !n
->children
)
168 return EPWMD_EMPTY_ELEMENT
;
170 for (n
= n
->children
; n
; n
= n
->next
) {
172 xmlChar
*val
, *target
;
176 if (n
->type
!= XML_ELEMENT_NODE
)
179 a
= xmlHasProp(n
, (xmlChar
*)"name");
181 if (!a
|| !a
->children
->content
)
184 val
= xmlNodeGetContent(a
->children
);
187 rc
= gpg_error_from_errno(ENOMEM
);
191 tmp
= g_strdup_printf("!%s", (gchar
*)val
);
195 rc
= gpg_error_from_errno(ENOMEM
);
199 tlist
= g_slist_append(list
, tmp
);
203 rc
= gpg_error_from_errno(ENOMEM
);
208 target
= node_has_attribute(n
, (xmlChar
*)"target");
211 gchar
*t
= g_strdup((gchar
*)val
);
216 rc
= gpg_error_from_errno(ENOMEM
);
220 tlist
= g_slist_append(list
, t
);
225 rc
= gpg_error_from_errno(ENOMEM
);
236 total
= g_slist_length(list
);
239 return EPWMD_EMPTY_ELEMENT
;
241 string
= g_string_new(NULL
);
244 rc
= gpg_error_from_errno(ENOMEM
);
248 for (i
= 0; i
< total
; i
++) {
249 gchar
*val
= g_slist_nth_data(list
, i
);
251 g_string_append_printf(string
, "%s\n", val
);
254 string
= g_string_truncate(string
, string
->len
- 1);
258 total
= g_slist_length(list
);
260 for (i
= 0; i
< total
; i
++)
261 g_free(g_slist_nth_data(list
, i
));
267 // FIXME return a gboolean in case of memory allocation failure
268 gchar
**split_input_line(gchar
*str
, gchar
*delim
, gint n
)
273 return g_strsplit(str
, delim
, n
);
277 * Prevents a sibling element past the current element path with the same
280 static xmlNodePtr
find_stop_node(xmlNodePtr node
)
284 for (n
= node
->parent
->children
; n
; n
= n
->next
) {
293 * Alot like create_elements_cb() but doesn't use the last element of 'req' as
294 * content but as an element.
296 xmlNodePtr
create_target_elements_cb(xmlNodePtr node
, gchar
**path
,
297 gpg_error_t
*rc
, void *data
)
302 if (xmlStrEqual(node
->name
, (xmlChar
*)*req
))
305 for (i
= 0; req
[i
]; i
++) {
308 if ((n
= find_element(node
, req
[i
], find_stop_node(node
))) == NULL
||
309 (n
&& n
->parent
== node
->parent
)) {
310 is_literal_element(&req
[i
]);
311 n
= xmlNewNode(NULL
, (xmlChar
*)req
[i
]);
314 *rc
= gpg_error_from_errno(ENOMEM
);
318 node
= xmlAddChild(node
, n
);
321 *rc
= gpg_error_from_errno(ENOMEM
);
332 xmlNodePtr
find_text_node(xmlNodePtr node
)
336 if (n
&& n
->type
== XML_TEXT_NODE
)
339 for (n
= node
; n
; n
= n
->next
) {
340 if (n
->type
== XML_TEXT_NODE
)
347 xmlNodePtr
create_elements_cb(xmlNodePtr node
, gchar
**elements
,
348 gpg_error_t
*rc
, void *data
)
351 gchar
**req
= elements
;
353 if (node
->type
== XML_TEXT_NODE
)
356 if (node
->name
&& xmlStrEqual(node
->name
, (xmlChar
*)*req
))
359 for (i
= 0; req
[i
]; i
++) {
364 * Strip the first '!' if needed. If there's another, it's an
365 * rc. The syntax has already been checked before calling this
368 is_literal_element(&req
[i
]);
372 * The value of the element tree.
375 n
= find_text_node(node
->children
);
378 /* Use AddContent here to prevent overwriting any children. */
379 xmlNodeAddContent(node
, (xmlChar
*)req
[i
]);
380 else if (n
&& !*req
[i
])
381 xmlNodeSetContent(n
, NULL
);
383 xmlNodeSetContent(n
, (xmlChar
*)req
[i
]);
388 n
= find_element(node
, req
[i
], find_stop_node(node
));
391 * If the found element has the same parent as the current element,
392 * they are siblings and the new element needs to be created as a
393 * child of the current element (node).
395 if (n
&& n
->parent
== node
->parent
)
399 n
= xmlNewNode(NULL
, (xmlChar
*)req
[i
]);
402 *rc
= gpg_error_from_errno(ENOMEM
);
406 node
= xmlAddChild(node
, n
);
409 *rc
= gpg_error_from_errno(ENOMEM
);
420 xmlNodePtr
find_account(xmlDocPtr doc
, gchar
***req
, gpg_error_t
*rc
,
421 gboolean
*target
, gint recursion_depth
)
423 xmlNodePtr n
= xmlDocGetRootElement(doc
);
425 gchar
*account
= g_strdup(*req
[0]);
426 gboolean literal
= is_literal_element(&account
);
429 *rc
= gpg_error_from_errno(ENOMEM
);
436 if (max_recursion_depth
>= 1 && recursion_depth
> max_recursion_depth
) {
437 xmlChar
*t
= xmlGetNodePath(n
);
439 log_write("%s: %s", pwmd_strerror(EPWMD_LOOP
), t
);
446 if (n
->type
== XML_ELEMENT_NODE
) {
447 if (depth
== 0 && xmlStrEqual(n
->name
, (xmlChar
*)"accounts")) {
453 if (depth
== 1 && xmlStrEqual(n
->name
, (xmlChar
*)"account")) {
454 xmlChar
*content
= node_has_attribute(n
, (xmlChar
*)"name");
459 if (xmlStrEqual(content
, (xmlChar
*)account
)) {
460 gchar
**nreq
, **tmp
= NULL
;
462 if (literal
== TRUE
) {
469 content
= node_has_attribute(n
, (xmlChar
*)"target");
476 if (strchr((gchar
*)content
, '\t')) {
477 nreq
= split_input_line((gchar
*)content
, "\t", 0);
485 *rc
= gpg_error_from_errno(ENOMEM
);
491 tmp
= strvcatv(nreq
, tmp
+1);
495 *rc
= gpg_error_from_errno(ENOMEM
);
503 if (strv_printf(&tmp
, "%s", content
) == FALSE
) {
505 *rc
= gpg_error_from_errno(ENOMEM
);
511 nreq
= strvcatv(tmp
, nreq
+1);
515 *rc
= gpg_error_from_errno(ENOMEM
);
527 n
= find_account(doc
, req
, rc
, target
, recursion_depth
);
537 *rc
= EPWMD_ELEMENT_NOT_FOUND
;
541 static xmlNodePtr
find_element(xmlNodePtr node
, gchar
*element
, xmlNodePtr stop
)
545 if (!node
|| !element
)
548 for (n
= node
; n
; n
= n
->next
) {
549 if (n
->type
!= XML_ELEMENT_NODE
)
555 if (xmlStrEqual(n
->name
, (xmlChar
*)element
))
562 static xmlChar
*node_has_attribute(xmlNodePtr n
, xmlChar
*attr
)
564 xmlAttrPtr a
= xmlHasProp(n
, attr
);
569 if (!a
->children
|| !a
->children
->content
)
572 return xmlGetProp(n
, attr
);
575 static gboolean
element_to_literal(gchar
**element
)
577 gchar
*p
= g_strdup_printf("!%s", *element
);
587 xmlNodePtr
find_elements(xmlDocPtr doc
, xmlNodePtr node
,
588 gchar
**req
, gpg_error_t
*rc
, gboolean
*target
,
589 xmlNodePtr (*found_fn
)(xmlNodePtr
, gchar
**, gpg_error_t
*, gchar
**, void *),
590 xmlNodePtr (*not_found_fn
)(xmlNodePtr
, gchar
**, gpg_error_t
*, void *),
591 gboolean is_list_command
, gint recursion_depth
, void *data
)
593 xmlNodePtr n
, last
, last_node
;
600 if (max_recursion_depth
>= 1 && recursion_depth
> max_recursion_depth
) {
601 xmlChar
*t
= xmlGetNodePath(node
);
603 log_write("%s: %s", pwmd_strerror(EPWMD_LOOP
), t
);
610 for (last_node
= last
= n
= node
, p
= req
; *p
; p
++) {
612 gchar
*t
= g_strdup(*p
);
616 *rc
= gpg_error_from_errno(ENOMEM
);
620 literal
= is_literal_element(&t
);
621 n
= find_element(last
, t
, NULL
);
626 return not_found_fn(found
? last_node
: last_node
->parent
, p
, rc
, data
);
628 *rc
= EPWMD_ELEMENT_NOT_FOUND
;
636 if (literal
== FALSE
) {
637 xmlChar
*content
= node_has_attribute(n
, (xmlChar
*)"target");
638 gchar
**nreq
= NULL
, **nnreq
;
641 if (is_list_command
== TRUE
) {
642 if (element_to_literal(&(*p
)) == FALSE
) {
643 *rc
= gpg_error_from_errno(ENOMEM
);
651 if (strchr((gchar
*)content
, '\t') != NULL
) {
652 if ((nreq
= split_input_line((gchar
*)content
, "\t", 0)) == NULL
) {
654 *rc
= EPWMD_INVALID_ELEMENT
;
659 if ((nreq
= split_input_line((gchar
*)content
, " ", 0)) == NULL
) {
661 *rc
= EPWMD_INVALID_ELEMENT
;
667 tmp
= find_account(doc
, &nreq
, rc
, target
, 0);
675 found_fn(tmp
, nreq
, rc
, p
+1, data
);
683 nnreq
= strvcatv(nreq
+1, p
+1);
687 if (!nnreq
|| !*nnreq
) {
697 n
= find_elements(doc
, tmp
->children
, nnreq
, rc
, NULL
, found_fn
,
698 not_found_fn
, is_list_command
, recursion_depth
, data
);
701 gchar
**zz
= p
+1, **qq
= nnreq
;
703 if (g_strv_length(nnreq
) > g_strv_length(p
+1))
706 for (; *qq
&& *zz
; zz
++) {
708 *zz
= g_strdup(*qq
++);
711 *rc
= gpg_error_from_errno(ENOMEM
);
726 static gboolean
update_element_list(struct element_list_s
*elements
)
731 if (!elements
|| !elements
->elements
)
734 line
= g_strjoinv("\t", elements
->elements
);
739 g_strfreev(elements
->elements
);
740 elements
->elements
= NULL
;
741 l
= g_slist_append(elements
->list
, line
);
750 static gpg_error_t
path_list_recurse(xmlDocPtr doc
, xmlNodePtr node
,
751 struct element_list_s
*elements
)
756 for (n
= node
; n
; n
= n
->next
) {
757 xmlChar
*target
= NULL
;
759 if (n
->type
!= XML_ELEMENT_NODE
)
762 if (strv_printf(&elements
->elements
, "%s\t!%s", elements
->prefix
, n
->name
) == FALSE
)
763 return gpg_err_code_from_errno(ENOMEM
);
765 if (update_element_list(elements
) == FALSE
)
766 return gpg_err_code_from_errno(ENOMEM
);
768 target
= node_has_attribute(n
, (xmlChar
*)"target");
772 gchar
*save
= elements
->prefix
;
773 gboolean r
= elements
->resolving
;
777 if (max_recursion_depth
>= 1 && elements
->depth
> max_recursion_depth
) {
778 xmlChar
*t
= xmlGetNodePath(n
);
779 log_write("%s: %s", pwmd_strerror(EPWMD_LOOP
), t
);
785 if (strv_printf(&elements
->elements
, "%s\t%s", elements
->prefix
, n
->name
) == FALSE
) {
787 return gpg_err_code_from_errno(ENOMEM
);
790 tmp
= g_strjoinv("\t", elements
->elements
);
794 return gpg_err_code_from_errno(ENOMEM
);
797 if (update_element_list(elements
) == FALSE
) {
799 return gpg_err_code_from_errno(ENOMEM
);
802 elements
->prefix
= tmp
;
803 elements
->resolving
= TRUE
;
804 rc
= create_path_list(doc
, elements
, (gchar
*)target
);
806 elements
->resolving
= r
;
809 elements
->prefix
= save
;
817 gchar
*tmp
= g_strdup_printf("%s\t!%s", elements
->prefix
, n
->name
);
818 gchar
*save
= elements
->prefix
;
821 return gpg_err_code_from_errno(ENOMEM
);
823 elements
->prefix
= tmp
;
824 rc
= path_list_recurse(doc
, n
->children
, elements
);
825 g_free(elements
->prefix
);
826 elements
->prefix
= save
;
837 * From the element path 'path', find sub-nodes and append them to the list.
839 gpg_error_t
create_path_list(xmlDocPtr doc
, struct element_list_s
*elements
,
843 gchar
**req
, **req_orig
;
845 gboolean a_target
= FALSE
;
847 req
= split_input_line(path
, "\t", 0);
850 req
= split_input_line(path
, " ", 0);
853 return EPWMD_COMMAND_SYNTAX
;
856 req_orig
= g_strdupv(req
);
859 rc
= gpg_err_code_from_errno(ENOMEM
);
863 n
= find_account(doc
, &req
, &rc
, &a_target
, 0);
865 if (!n
&& rc
== EPWMD_ELEMENT_NOT_FOUND
&& elements
->resolving
== TRUE
) {
872 if (a_target
== TRUE
) {
874 *req
= g_strdup(*req_orig
);
878 gboolean e_target
= FALSE
;
880 n
= find_elements(doc
, n
->children
, req
+1, &rc
, &e_target
, NULL
, NULL
, TRUE
, 0, NULL
);
882 if (!n
&& rc
== EPWMD_ELEMENT_NOT_FOUND
&& elements
->resolving
== TRUE
) {
890 if (!elements
->prefix
) {
894 * If any req_orig element contains no target the element should be prefixed with
895 * the literal character. Not really crucial if the client isn't human
896 * because child elements are prefixed for the current path. But may
897 * be confusing if editing by hand.
899 elements
->prefix
= g_strjoinv("\t", req_orig
);
901 if (!elements
->prefix
) {
902 rc
= gpg_err_code_from_errno(ENOMEM
);
906 if (strv_printf(&elements
->elements
, "%s", elements
->prefix
) == FALSE
) {
907 rc
= gpg_err_code_from_errno(ENOMEM
);
911 if (update_element_list(elements
) == FALSE
) {
912 rc
= gpg_err_code_from_errno(ENOMEM
);
917 rc
= path_list_recurse(doc
, n
->children
, elements
);
921 g_strfreev(req_orig
);
927 gpg_error_t
recurse_xpath_nodeset(xmlDocPtr doc
, xmlNodeSetPtr nodes
,
928 xmlChar
*value
, xmlBufferPtr
*result
)
930 gint i
= value
? nodes
->nodeNr
- 1 : 0;
933 buf
= xmlBufferCreate();
936 return gpg_err_code_from_errno(ENOMEM
);
938 for (; value
? i
>= 0 : i
< nodes
->nodeNr
; value
? i
-- : i
++) {
939 xmlNodePtr n
= nodes
->nodeTab
[i
];
945 if (xmlNodeDump(buf
, doc
, n
, 0, 0) == -1) {
947 return EPWMD_LIBXML_ERROR
;
953 xmlNodeSetContent(n
, value
);