1 /* vim:tw=78:ts=8:sw=4:set ft=c: */
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
35 #include "pwmd_error.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
)
46 return *element
== '!' ? TRUE
: FALSE
;
50 * 'element' must be allocated.
52 gboolean
is_literal_element(gchar
**element
)
56 if (!element
|| !*element
)
59 if (*(*element
) == '!') {
62 for (p
= *element
, c
= p
+1; *c
; c
++)
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
)
81 gchar
*p
= (gchar
*)element
;
83 if (!element
|| !*element
)
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
)
96 c
= g_utf8_get_char(p
++);
98 if (g_unichar_isspace(c
))
105 gboolean
valid_element_path(gchar
**path
, gboolean has_value
)
109 for (p
= path
; *p
; p
++) {
111 * An empty element is valid and don't check the syntax of the
114 if (has_value
== TRUE
&& (!*(p
+1) || !*p
[0]))
117 if (valid_xml_element((xmlChar
*)*p
) == FALSE
)
124 gpg_error_t
new_account(xmlDocPtr doc
, gchar
*name
)
126 xmlNodePtr root
= xmlDocGetRootElement(doc
);
132 return EPWMD_LIBXML_ERROR
;
134 if (is_literal_element_str(p
))
137 n
= xmlNewNode(NULL
, (xmlChar
*)"account");
138 n
= xmlAddChild(root
, n
);
139 a
= xmlNewProp(n
, (xmlChar
*)"name", (xmlChar
*)p
);
143 xmlChar
*new_document()
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"
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
, t
;
168 for (t
= doc
->children
; t
; t
= t
->next
) {
169 if (t
->type
== XML_ELEMENT_NODE
) {
170 if (xmlStrEqual(t
->name
, (xmlChar
*)"accounts")) {
177 if (!n
|| !n
->children
)
178 return EPWMD_EMPTY_ELEMENT
;
180 for (n
= n
->children
; n
; n
= n
->next
) {
185 if (n
->type
!= XML_ELEMENT_NODE
)
188 a
= xmlHasProp(n
, (xmlChar
*)"name");
190 if (!a
|| !a
->children
->content
)
193 tmp
= g_strdup((gchar
*)a
->children
->content
);
197 return gpg_error_from_errno(ENOMEM
);
200 tlist
= g_slist_append(list
, tmp
);
204 return gpg_error_from_errno(ENOMEM
);
210 total
= g_slist_length(list
);
213 return EPWMD_EMPTY_ELEMENT
;
215 string
= g_string_new(NULL
);
219 return gpg_error_from_errno(ENOMEM
);
222 for (i
= 0; i
< total
; i
++) {
223 gchar
*tmp
= g_slist_nth_data(list
, i
);
225 g_string_append_printf(string
, "%s\n", tmp
);
229 string
= g_string_truncate(string
, string
->len
- 1);
235 gchar
**split_input_line(gchar
*str
, gchar
*delim
, gint n
)
240 return g_strsplit(str
, delim
, n
);
243 static gchar
**append_element_path(gchar
**dst
, gchar
**src
)
257 i
= g_strv_length(d
);
259 for (p
= src
; *p
; p
++) {
262 pa
= g_realloc(d
, (i
+ 2) * sizeof(gchar
*));
283 static xmlNodePtr
find_stop_node(xmlNodePtr node
)
287 for (n
= node
->parent
->children
; n
; n
= n
->next
) {
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
)
305 if (xmlStrEqual(node
->name
, (xmlChar
*)*req
))
308 for (i
= 0; req
[i
]; i
++) {
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
]);
317 *error
= gpg_error_from_errno(ENOMEM
);
321 node
= xmlAddChild(node
, n
);
324 *error
= gpg_error_from_errno(ENOMEM
);
335 xmlNodePtr
find_text_node(xmlNodePtr node
)
339 if (n
&& n
->type
== XML_TEXT_NODE
)
342 for (n
= node
; n
; n
= n
->next
) {
343 if (n
->type
== XML_TEXT_NODE
)
350 xmlNodePtr
create_elements_cb(xmlNodePtr node
, gchar
**elements
,
351 gpg_error_t
*error
, void *data
)
354 gchar
**req
= elements
;
356 if (node
->type
== XML_TEXT_NODE
)
359 if (node
->name
&& xmlStrEqual(node
->name
, (xmlChar
*)*req
))
362 for (i
= 0; req
[i
]; i
++) {
367 * Strip the first '!' if needed. If there's another, it's an
368 * error. The syntax has already been checked before calling this
371 is_literal_element(&req
[i
]);
375 * The value of the element tree.
378 n
= find_text_node(node
->children
);
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
);
386 xmlNodeSetContent(n
, (xmlChar
*)req
[i
]);
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
)
402 n
= xmlNewNode(NULL
, (xmlChar
*)req
[i
]);
405 *error
= gpg_error_from_errno(ENOMEM
);
409 node
= xmlAddChild(node
, n
);
412 *error
= gpg_error_from_errno(ENOMEM
);
423 xmlNodePtr
find_account(xmlDocPtr doc
, gchar
***req
, gpg_error_t
*error
,
424 gboolean
*target
, gint recursion_depth
)
428 gchar
*account
= g_strdup(*req
[0]);
429 gboolean literal
= is_literal_element(&account
);
432 *error
= gpg_error_from_errno(ENOMEM
);
439 if (max_recursion_depth
>= 1 && recursion_depth
> max_recursion_depth
) {
444 for (n
= doc
->children
; n
;) {
445 if (n
->type
== XML_ELEMENT_NODE
) {
446 if (depth
== 0 && xmlStrEqual(n
->name
, (xmlChar
*)"accounts")) {
452 if (depth
== 1 && xmlStrEqual(n
->name
, (xmlChar
*)"account")) {
453 xmlChar
*content
= node_has_attribute(n
, (xmlChar
*)"name");
458 if (xmlStrEqual(content
, (xmlChar
*)account
)) {
459 gchar
**nreq
, **tmp
= NULL
;
461 if (literal
== TRUE
) {
466 content
= node_has_attribute(n
, (xmlChar
*)"target");
473 if (strchr((gchar
*)content
, '\t')) {
474 nreq
= split_input_line((gchar
*)content
, "\t", 0);
481 *error
= gpg_error_from_errno(ENOMEM
);
487 tmp
= append_element_path(nreq
, tmp
+1);
491 *error
= gpg_error_from_errno(ENOMEM
);
499 if (strv_printf(&tmp
, "%s", content
) == FALSE
) {
500 *error
= gpg_error_from_errno(ENOMEM
);
505 nreq
= append_element_path(tmp
, nreq
+1);
509 *error
= gpg_error_from_errno(ENOMEM
);
521 n
= find_account(doc
, req
, error
, target
, recursion_depth
);
531 *error
= EPWMD_ELEMENT_NOT_FOUND
;
535 xmlNodePtr
find_sibling(xmlNodePtr node
, gchar
*element
, xmlNodePtr stop
)
539 if (!node
|| !element
)
542 for (n
= node
; n
; n
= n
->next
) {
543 if (n
->type
!= XML_ELEMENT_NODE
)
549 if (xmlStrEqual(n
->name
, (xmlChar
*)element
))
556 xmlNodePtr
find_element(xmlNodePtr node
, gchar
*element
, xmlNodePtr stop
)
558 if (!node
|| !element
)
561 return find_sibling(node
, element
, stop
);
564 xmlChar
*node_has_attribute(xmlNodePtr n
, xmlChar
*attr
)
566 xmlAttrPtr a
= xmlHasProp(n
, attr
);
571 if (!a
->children
|| !a
->children
->content
)
574 return a
->children
->content
;
577 static gboolean
element_to_literal(gchar
**element
)
579 gchar
*p
= g_strdup_printf("!%s", *element
);
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
;
602 if (max_recursion_depth
>= 1 && recursion_depth
> max_recursion_depth
) {
608 for (last_node
= last
= n
= node
, p
= req
; *p
; p
++) {
610 gchar
*t
= g_strdup(*p
);
614 *error
= gpg_error_from_errno(ENOMEM
);
618 literal
= is_literal_element(&t
);
619 n
= find_element(last
, t
, NULL
);
624 return not_found_fn(found
? last_node
: last_node
->parent
, p
, error
, data
);
626 *error
= EPWMD_ELEMENT_NOT_FOUND
;
634 if (literal
== FALSE
) {
635 xmlChar
*content
= node_has_attribute(n
, (xmlChar
*)"target");
636 gchar
**nreq
= NULL
, **nnreq
;
639 if (is_list_command
== TRUE
) {
640 if (element_to_literal(&(*p
)) == FALSE
) {
641 *error
= gpg_error_from_errno(ENOMEM
);
649 if (strchr((gchar
*)content
, '\t') != NULL
) {
650 if ((nreq
= split_input_line((gchar
*)content
, "\t", 0)) == NULL
) {
651 *error
= EPWMD_INVALID_ELEMENT
;
656 if ((nreq
= split_input_line((gchar
*)content
, " ", 0)) == NULL
) {
657 *error
= EPWMD_INVALID_ELEMENT
;
662 tmp
= find_account(doc
, &nreq
, error
, target
, 0);
670 found_fn(n
, nreq
, error
, data
);
672 nnreq
= append_element_path(nreq
+1, p
+1);
676 if (!nnreq
|| !*nnreq
) {
686 n
= find_elements(doc
, tmp
->children
, nnreq
, error
, NULL
, found_fn
,
687 not_found_fn
, is_list_command
, recursion_depth
, data
);
690 gchar
**zz
= p
+1, **qq
= nnreq
;
692 if (g_strv_length(nnreq
) > g_strv_length(p
+1))
695 for (; *qq
&& *zz
; zz
++) {
697 *zz
= g_strdup(*qq
++);
700 *error
= gpg_error_from_errno(ENOMEM
);
715 gboolean
node_has_child_element(xmlNodePtr node
)
722 for (n
= node
; n
; n
= n
->next
) {
723 if (n
->type
== XML_ELEMENT_NODE
)
727 return node_has_child_element(n
->children
);