1 /* vim:tw=78:ts=8:sw=4:set ft=c: */
3 Copyright (C) 2006-2010 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
30 #include <libxml/xmlwriter.h>
36 #include "pwmd_error.h"
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
)
80 len
= g_utf8_strlen(p
, -1) - 1;
81 c
= g_utf8_get_char(p
++);
83 if (g_unichar_ispunct(c
) == TRUE
|| g_unichar_isdigit(c
) == TRUE
||
84 g_unichar_isspace(c
) == TRUE
)
88 c
= g_utf8_get_char(p
++);
90 if (g_unichar_isspace(c
))
97 gpg_error_t
new_root_element(xmlDocPtr doc
, gchar
*name
)
99 xmlNodePtr root
= xmlDocGetRootElement(doc
);
105 return EPWMD_LIBXML_ERROR
;
110 if (!valid_xml_element((xmlChar
*)p
))
111 return EPWMD_INVALID_ELEMENT
;
113 n
= xmlNewNode(NULL
, (xmlChar
*)"root");
114 n
= xmlAddChild(root
, n
);
115 a
= xmlNewProp(n
, (xmlChar
*)"name", (xmlChar
*)p
);
119 xmlDocPtr
create_dtd()
122 xmlTextWriterPtr wr
= xmlNewTextWriterDoc(&doc
, 0);
127 if (xmlTextWriterStartDocument(wr
, NULL
, NULL
, NULL
))
130 if (xmlTextWriterStartDTD(wr
, (xmlChar
*)"pwmd", NULL
, NULL
) == -1)
133 if (xmlTextWriterWriteDTDElement(wr
, (xmlChar
*)"pwmd",
134 (xmlChar
*)"(root)") == -1)
137 xmlTextWriterEndDTDElement(wr
);
139 if (xmlTextWriterWriteDTDAttlist(wr
, (xmlChar
*)"root",
140 (xmlChar
*)"name CDATA #REQUIRED") == -1)
143 xmlTextWriterEndDTDAttlist(wr
);
144 xmlTextWriterEndDTD(wr
);
146 if (xmlTextWriterStartElement(wr
, (xmlChar
*)"pwmd"))
149 xmlTextWriterEndElement(wr
);
150 xmlTextWriterEndDocument(wr
);
151 xmlFreeTextWriter(wr
);
155 xmlTextWriterEndDocument(wr
);
156 xmlFreeTextWriter(wr
);
161 xmlChar
*new_document()
165 xmlDocPtr doc
= create_dtd();
170 xmlDocDumpMemory(doc
, &xml
, &len
);
176 * Lists root element names; the value of the attribute "name" of an element
177 * "root". If there's a target attribute both literal and non-literal element
178 * names will be added. This is the primary reason why XML entities cannot be
179 * used. There wouldn't be a way to get the literal an non-literal element
182 gpg_error_t
list_root_elements(xmlDocPtr doc
, GString
**result
)
190 n
= xmlDocGetRootElement(doc
);
192 if (!n
|| !n
->children
)
193 return EPWMD_EMPTY_ELEMENT
;
195 for (n
= n
->children
; n
; n
= n
->next
) {
197 xmlChar
*val
, *target
;
201 if (n
->type
!= XML_ELEMENT_NODE
)
204 a
= xmlHasProp(n
, (xmlChar
*)"name");
206 if (!a
|| !a
->children
->content
)
209 val
= xmlNodeGetContent(a
->children
);
212 rc
= gpg_error_from_errno(ENOMEM
);
216 tmp
= g_strdup_printf("!%s", (gchar
*)val
);
220 rc
= gpg_error_from_errno(ENOMEM
);
224 tlist
= g_slist_append(list
, tmp
);
228 rc
= gpg_error_from_errno(ENOMEM
);
233 target
= node_has_attribute(n
, (xmlChar
*)"target");
236 gchar
*t
= g_strdup((gchar
*)val
);
241 rc
= gpg_error_from_errno(ENOMEM
);
245 tlist
= g_slist_append(list
, t
);
250 rc
= gpg_error_from_errno(ENOMEM
);
261 total
= g_slist_length(list
);
264 return EPWMD_EMPTY_ELEMENT
;
266 string
= g_string_new(NULL
);
269 rc
= gpg_error_from_errno(ENOMEM
);
273 for (i
= 0; i
< total
; i
++) {
274 gchar
*val
= g_slist_nth_data(list
, i
);
276 g_string_append_printf(string
, "%s\n", val
);
279 string
= g_string_truncate(string
, string
->len
- 1);
283 total
= g_slist_length(list
);
285 for (i
= 0; i
< total
; i
++)
286 g_free(g_slist_nth_data(list
, i
));
293 * Prevents a sibling element past the current element path with the same
296 static xmlNodePtr
find_stop_node(xmlNodePtr node
)
300 for (n
= node
->parent
->children
; n
; n
= n
->next
) {
309 * Alot like create_elements_cb() but doesn't use the last element of 'req' as
310 * content but as an element.
312 xmlNodePtr
create_target_elements_cb(xmlNodePtr node
, gchar
**path
,
313 gpg_error_t
*rc
, void *data
)
318 if (xmlStrEqual(node
->name
, (xmlChar
*)*req
))
321 for (i
= 0; req
[i
]; i
++) {
324 if ((n
= find_element(node
, req
[i
], find_stop_node(node
))) == NULL
||
325 (n
&& n
->parent
== node
->parent
)) {
326 is_literal_element(&req
[i
]);
328 if (!valid_xml_element((xmlChar
*)req
[i
])) {
329 *rc
= EPWMD_INVALID_ELEMENT
;
333 n
= xmlNewNode(NULL
, (xmlChar
*)req
[i
]);
336 *rc
= gpg_error_from_errno(ENOMEM
);
340 node
= xmlAddChild(node
, n
);
343 *rc
= gpg_error_from_errno(ENOMEM
);
354 xmlNodePtr
find_text_node(xmlNodePtr node
)
358 if (n
&& n
->type
== XML_TEXT_NODE
)
361 for (n
= node
; n
; n
= n
->next
) {
362 if (n
->type
== XML_TEXT_NODE
)
369 xmlNodePtr
create_elements_cb(xmlNodePtr node
, gchar
**elements
,
370 gpg_error_t
*rc
, void *data
)
373 gchar
**req
= elements
;
375 if (node
->type
== XML_TEXT_NODE
)
378 if (node
->name
&& xmlStrEqual(node
->name
, (xmlChar
*)*req
))
381 for (i
= 0; req
[i
]; i
++) {
386 * Strip the first '!' if needed. If there's another, it's an
387 * rc. The syntax has already been checked before calling this
390 is_literal_element(&req
[i
]);
394 * The value of the element tree.
397 n
= find_text_node(node
->children
);
400 /* Use AddContent here to prevent overwriting any children. */
401 xmlNodeAddContent(node
, (xmlChar
*)req
[i
]);
402 else if (n
&& !*req
[i
])
403 xmlNodeSetContent(n
, NULL
);
405 xmlNodeSetContent(n
, (xmlChar
*)req
[i
]);
410 n
= find_element(node
, req
[i
], find_stop_node(node
));
413 * If the found element has the same parent as the current element,
414 * they are siblings and the new element needs to be created as a
415 * child of the current element (node).
417 if (n
&& n
->parent
== node
->parent
)
421 if (!valid_xml_element((xmlChar
*)req
[i
])) {
422 *rc
= EPWMD_INVALID_ELEMENT
;
426 n
= xmlNewNode(NULL
, (xmlChar
*)req
[i
]);
429 *rc
= gpg_error_from_errno(ENOMEM
);
433 node
= xmlAddChild(node
, n
);
436 *rc
= gpg_error_from_errno(ENOMEM
);
447 /* The root element is really req[0]. It is need as a pointer in case there is
448 * a target attribute so it can be updated. */
449 xmlNodePtr
find_root_element(xmlDocPtr doc
, gchar
***req
, gpg_error_t
*rc
,
450 gboolean
*target
, gint recursion_depth
, gboolean stop
)
452 xmlNodePtr n
= xmlDocGetRootElement(doc
);
454 gchar
*root
= g_strdup(*req
[0]);
455 gboolean literal
= is_literal_element(&root
);
458 *rc
= gpg_error_from_errno(ENOMEM
);
465 if (max_recursion_depth
>= 1 && recursion_depth
> max_recursion_depth
) {
466 xmlChar
*t
= xmlGetNodePath(n
);
468 log_write("%s: %s", pwmd_strerror(EPWMD_LOOP
), t
);
476 if (n
->type
== XML_ELEMENT_NODE
) {
477 if (depth
== 0 && xmlStrEqual(n
->name
, (xmlChar
*)"pwmd")) {
483 if (depth
== 1 && xmlStrEqual(n
->name
, (xmlChar
*)"root")) {
484 xmlChar
*content
= node_has_attribute(n
, (xmlChar
*)"name");
489 if (xmlStrEqual(content
, (xmlChar
*)root
)) {
490 gchar
**nreq
, **tmp
= NULL
;
492 if (literal
== TRUE
) {
499 content
= node_has_attribute(n
, (xmlChar
*)"target");
504 if (!content
|| stop
) {
512 if (strchr((gchar
*)content
, '\t')) {
513 nreq
= split_input_line((gchar
*)content
, "\t", 0);
521 *rc
= gpg_error_from_errno(ENOMEM
);
527 tmp
= strvcatv(nreq
, tmp
+1);
532 *rc
= gpg_error_from_errno(ENOMEM
);
540 if (strv_printf(&tmp
, "%s", content
) == FALSE
) {
543 *rc
= gpg_error_from_errno(ENOMEM
);
549 nreq
= strvcatv(tmp
, nreq
+1);
553 *rc
= gpg_error_from_errno(ENOMEM
);
563 n
= find_root_element(doc
, req
, rc
, target
, recursion_depth
, FALSE
);
575 *rc
= EPWMD_ELEMENT_NOT_FOUND
;
579 static xmlNodePtr
find_element(xmlNodePtr node
, gchar
*element
, xmlNodePtr stop
)
583 if (!node
|| !element
)
586 for (n
= node
; n
; n
= n
->next
) {
587 if (n
->type
!= XML_ELEMENT_NODE
)
593 if (xmlStrEqual(n
->name
, (xmlChar
*)element
))
600 static xmlChar
*node_has_attribute(xmlNodePtr n
, xmlChar
*attr
)
602 xmlAttrPtr a
= xmlHasProp(n
, attr
);
607 if (!a
->children
|| !a
->children
->content
)
610 return xmlGetProp(n
, attr
);
613 static gboolean
element_to_literal(gchar
**element
)
615 gchar
*p
= g_strdup_printf("!%s", *element
);
625 /* Resolves elements in 'req' one at a time. It's recursive in case of
626 * "target" attributes. */
627 xmlNodePtr
find_elements(xmlDocPtr doc
, xmlNodePtr node
,
628 gchar
**req
, gpg_error_t
*rc
, gboolean
*target
,
629 xmlNodePtr (*found_fn
)(xmlNodePtr
, gchar
**, gpg_error_t
*, gchar
**, void *),
630 xmlNodePtr (*not_found_fn
)(xmlNodePtr
, gchar
**, gpg_error_t
*, void *),
631 gboolean is_list_command
, gint recursion_depth
, void *data
, gboolean stop
)
633 xmlNodePtr n
, last
, last_node
;
640 if (max_recursion_depth
>= 1 && recursion_depth
> max_recursion_depth
) {
641 xmlChar
*t
= xmlGetNodePath(node
);
643 log_write("%s: %s", pwmd_strerror(EPWMD_LOOP
), t
);
650 for (last_node
= last
= n
= node
, p
= req
; *p
; p
++) {
652 gchar
*t
= g_strdup(*p
);
656 *rc
= gpg_error_from_errno(ENOMEM
);
660 literal
= is_literal_element(&t
);
661 n
= find_element(last
, t
, NULL
);
666 return not_found_fn(found
? last_node
: last_node
->parent
, p
, rc
, data
);
668 *rc
= EPWMD_ELEMENT_NOT_FOUND
;
676 if (literal
== FALSE
) {
677 xmlChar
*content
= node_has_attribute(n
, (xmlChar
*)"target");
678 gchar
**nreq
= NULL
, **nnreq
;
681 if (is_list_command
== TRUE
) {
682 if (element_to_literal(&(*p
)) == FALSE
) {
683 *rc
= gpg_error_from_errno(ENOMEM
);
694 if (!*(p
+1) && stop
) {
699 if (strchr((gchar
*)content
, '\t') != NULL
) {
700 if ((nreq
= split_input_line((gchar
*)content
, "\t", 0)) == NULL
) {
702 *rc
= EPWMD_INVALID_ELEMENT
;
707 if ((nreq
= split_input_line((gchar
*)content
, " ", 0)) == NULL
) {
709 *rc
= EPWMD_INVALID_ELEMENT
;
715 tmp
= find_root_element(doc
, &nreq
, rc
, target
, 0, FALSE
);
723 found_fn(tmp
, nreq
, rc
, p
+1, data
);
731 nnreq
= strvcatv(nreq
+1, p
+1);
735 if (!nnreq
|| !*nnreq
) {
742 n
= find_elements(doc
, tmp
->children
, nnreq
, rc
, NULL
, found_fn
,
743 not_found_fn
, is_list_command
, recursion_depth
, data
, stop
);
746 gchar
**zz
= p
+1, **qq
= nnreq
;
748 if (g_strv_length(nnreq
) > g_strv_length(p
+1))
751 for (; *qq
&& *zz
; zz
++) {
753 *zz
= g_strdup(*qq
++);
756 *rc
= gpg_error_from_errno(ENOMEM
);
771 static gboolean
update_element_list(struct element_list_s
*elements
)
776 if (!elements
|| !elements
->elements
)
779 line
= g_strjoinv("\t", elements
->elements
);
784 g_strfreev(elements
->elements
);
785 elements
->elements
= NULL
;
786 l
= g_slist_append(elements
->list
, line
);
795 static gpg_error_t
path_list_recurse(xmlDocPtr doc
, xmlNodePtr node
,
796 struct element_list_s
*elements
)
801 for (n
= node
; n
; n
= n
->next
) {
802 xmlChar
*target
= NULL
;
804 if (n
->type
!= XML_ELEMENT_NODE
)
807 if (strv_printf(&elements
->elements
, "%s\t!%s", elements
->prefix
, n
->name
) == FALSE
)
808 return gpg_err_code_from_errno(ENOMEM
);
810 if (update_element_list(elements
) == FALSE
)
811 return gpg_err_code_from_errno(ENOMEM
);
813 target
= node_has_attribute(n
, (xmlChar
*)"target");
817 gchar
*save
= elements
->prefix
;
818 gboolean r
= elements
->resolving
;
822 if (max_recursion_depth
>= 1 && elements
->depth
> max_recursion_depth
) {
823 xmlChar
*t
= xmlGetNodePath(n
);
824 log_write("%s: %s", pwmd_strerror(EPWMD_LOOP
), t
);
830 if (strv_printf(&elements
->elements
, "%s\t%s", elements
->prefix
, n
->name
) == FALSE
) {
832 return gpg_err_code_from_errno(ENOMEM
);
835 tmp
= g_strjoinv("\t", elements
->elements
);
839 return gpg_err_code_from_errno(ENOMEM
);
842 if (update_element_list(elements
) == FALSE
) {
844 return gpg_err_code_from_errno(ENOMEM
);
847 elements
->prefix
= tmp
;
848 elements
->resolving
= TRUE
;
849 rc
= create_path_list(doc
, elements
, (gchar
*)target
);
851 elements
->resolving
= r
;
854 elements
->prefix
= save
;
862 gchar
*tmp
= g_strdup_printf("%s\t!%s", elements
->prefix
, n
->name
);
863 gchar
*save
= elements
->prefix
;
866 return gpg_err_code_from_errno(ENOMEM
);
868 elements
->prefix
= tmp
;
869 rc
= path_list_recurse(doc
, n
->children
, elements
);
870 g_free(elements
->prefix
);
871 elements
->prefix
= save
;
881 gpg_error_t
add_attribute(xmlNodePtr node
, const gchar
*name
,
886 if ((a
= xmlHasProp(node
, (xmlChar
*)name
)) == NULL
) {
887 a
= xmlNewProp(node
, (xmlChar
*)name
, (xmlChar
*)value
);
890 return EPWMD_LIBXML_ERROR
;
893 xmlNodeSetContent(a
->children
, (xmlChar
*)value
);
898 gpg_error_t
update_timestamp(xmlDocPtr doc
)
900 xmlNodePtr n
= xmlDocGetRootElement(doc
);
901 gchar
*t
= g_strdup_printf("%li", time(NULL
));
904 rc
= add_attribute(n
, "age", t
);
910 * From the element path 'path', find sub-nodes and append them to the list.
912 gpg_error_t
create_path_list(xmlDocPtr doc
, struct element_list_s
*elements
,
916 gchar
**req
, **req_orig
;
918 gboolean a_target
= FALSE
;
920 req
= split_input_line(path
, "\t", 0);
923 req
= split_input_line(path
, " ", 0);
926 return EPWMD_COMMAND_SYNTAX
;
929 req_orig
= g_strdupv(req
);
932 rc
= gpg_err_code_from_errno(ENOMEM
);
936 n
= find_root_element(doc
, &req
, &rc
, &a_target
, 0, FALSE
);
938 if (!n
&& rc
== EPWMD_ELEMENT_NOT_FOUND
&& elements
->resolving
== TRUE
) {
945 if (a_target
== TRUE
) {
947 *req
= g_strdup(*req_orig
);
951 gboolean e_target
= FALSE
;
953 n
= find_elements(doc
, n
->children
, req
+1, &rc
, &e_target
, NULL
, NULL
, TRUE
, 0, NULL
, FALSE
);
955 if (!n
&& rc
== EPWMD_ELEMENT_NOT_FOUND
&& elements
->resolving
== TRUE
) {
963 if (!elements
->prefix
) {
967 * If any req_orig element contains no target the element should be
968 * prefixed with the literal character. Not really crucial if the
969 * client isn't human because child elements are prefixed for the
970 * current path. But may be confusing if editing by hand.
972 elements
->prefix
= g_strjoinv("\t", req_orig
);
974 if (!elements
->prefix
) {
975 rc
= gpg_err_code_from_errno(ENOMEM
);
979 if (strv_printf(&elements
->elements
, "%s", elements
->prefix
) == FALSE
) {
980 rc
= gpg_err_code_from_errno(ENOMEM
);
984 if (update_element_list(elements
) == FALSE
) {
985 rc
= gpg_err_code_from_errno(ENOMEM
);
990 rc
= path_list_recurse(doc
, n
->children
, elements
);
994 g_strfreev(req_orig
);
1000 gpg_error_t
recurse_xpath_nodeset(xmlDocPtr doc
, xmlNodeSetPtr nodes
,
1001 xmlChar
*value
, xmlBufferPtr
*result
)
1003 gint i
= value
? nodes
->nodeNr
- 1 : 0;
1006 buf
= xmlBufferCreate();
1009 return gpg_err_code_from_errno(ENOMEM
);
1011 for (; value
? i
>= 0 : i
< nodes
->nodeNr
; value
? i
-- : i
++) {
1012 xmlNodePtr n
= nodes
->nodeTab
[i
];
1018 if (xmlNodeDump(buf
, doc
, n
, 0, 0) == -1) {
1020 return EPWMD_LIBXML_ERROR
;
1026 xmlNodeSetContent(n
, value
);
1033 /* Updates the DTD and renames the root "accounts" and "account" elements. */
1034 gpg_error_t
convert_xml(gchar
**xml
, goffset
*len
)
1036 gpg_error_t rc
= EPWMD_LIBXML_ERROR
;
1037 xmlDocPtr doc
, new = NULL
;
1040 doc
= xmlReadMemory(*xml
, *len
, NULL
, "UTF-8", XML_PARSE_NOBLANKS
);
1043 return EPWMD_LIBXML_ERROR
;
1047 n
= xmlDocGetRootElement(doc
);
1048 xmlNodeSetName(n
, (xmlChar
*)"pwmd");
1050 for (n
= n
->children
; n
; n
= n
->next
) {
1051 if (xmlStrcmp(n
->name
, (xmlChar
*)"account") == 0)
1052 xmlNodeSetName(n
, (xmlChar
*)"root");
1060 n
= xmlDocGetRootElement(doc
);
1061 xmlDocSetRootElement(new, n
);
1062 xmlDocDumpMemory(new, (xmlChar
**)xml
, (gint
*)len
);
1063 xmlDocSetRootElement(new, xmlCopyNode(n
, 0));