1 /*
2 * Copyright 2004-2026 the Pacemaker project contributors
3 *
4 * The version control history for this file may have further details.
5 *
6 * This source code is licensed under the GNU Lesser General Public License
7 * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
8 */
9
10 #include <crm_internal.h>
11
12 #include <stdarg.h>
13 #include <stdbool.h>
14 #include <stdint.h> // uint32_t
15 #include <stdio.h>
16 #include <stdlib.h>
17 #include <string.h>
18 #include <sys/stat.h> // stat(), S_ISREG, etc.
19 #include <sys/types.h>
20
21 #include <glib.h> // gboolean, GString
22 #include <libxml/tree.h> // xmlNode, etc.
23 #include <libxml/xmlstring.h> // xmlChar, xmlGetUTF8Char()
24
25 #include <crm/crm.h>
26 #include <crm/common/xml.h>
27 #include "crmcommon_private.h"
28
29 //! libxml2 supports only XML version 1.0, at least as of libxml2-2.12.5
30 #define XML_VERSION ((const xmlChar *) "1.0")
31
32 /*!
33 * \internal
34 * \brief Get a string representation of an XML element type for logging
35 *
36 * \param[in] type XML element type
37 *
38 * \return String representation of \p type
39 */
40 const char *
41 pcmk__xml_element_type_text(xmlElementType type)
42 {
43 static const char *const element_type_names[] = {
44 [XML_ELEMENT_NODE] = "element",
45 [XML_ATTRIBUTE_NODE] = "attribute",
46 [XML_TEXT_NODE] = "text",
47 [XML_CDATA_SECTION_NODE] = "CDATA section",
48 [XML_ENTITY_REF_NODE] = "entity reference",
49 [XML_ENTITY_NODE] = "entity",
50 [XML_PI_NODE] = "PI",
51 [XML_COMMENT_NODE] = "comment",
52 [XML_DOCUMENT_NODE] = "document",
53 [XML_DOCUMENT_TYPE_NODE] = "document type",
54 [XML_DOCUMENT_FRAG_NODE] = "document fragment",
55 [XML_NOTATION_NODE] = "notation",
56 [XML_HTML_DOCUMENT_NODE] = "HTML document",
57 [XML_DTD_NODE] = "DTD",
58 [XML_ELEMENT_DECL] = "element declaration",
59 [XML_ATTRIBUTE_DECL] = "attribute declaration",
60 [XML_ENTITY_DECL] = "entity declaration",
61 [XML_NAMESPACE_DECL] = "namespace declaration",
62 [XML_XINCLUDE_START] = "XInclude start",
63 [XML_XINCLUDE_END] = "XInclude end",
64 };
65
66 // Assumes the numeric values of the indices are in ascending order
67 if ((type < XML_ELEMENT_NODE) || (type > XML_XINCLUDE_END)) {
68 return "unrecognized type";
69 }
70 return element_type_names[type];
71 }
72
73 /*!
74 * \internal
75 * \brief Apply a function to each XML node in a tree (pre-order, depth-first)
76 *
77 * \param[in,out] xml XML tree to traverse
78 * \param[in,out] fn Function to call for each node (returns \c true to
79 * continue traversing the tree or \c false to stop)
80 * \param[in,out] user_data Argument to \p fn
81 *
82 * \return \c false if any \p fn call returned \c false, or \c true otherwise
83 *
84 * \note This function is recursive.
85 */
86 bool
87 pcmk__xml_tree_foreach(xmlNode *xml, bool (*fn)(xmlNode *, void *),
88 void *user_data)
89 {
90 pcmk__assert(fn != NULL);
91
92 if (xml == NULL) {
93 return true;
94 }
95
96 if (!fn(xml, user_data)) {
97 return false;
98 }
99
100 for (xml = pcmk__xml_first_child(xml); xml != NULL;
101 xml = pcmk__xml_next(xml)) {
102
103 if (!pcmk__xml_tree_foreach(xml, fn, user_data)) {
104 return false;
105 }
106 }
107 return true;
108 }
109
110 void
111 pcmk__xml_set_parent_flags(xmlNode *xml, uint64_t flags)
112 {
113 for (; xml != NULL; xml = xml->parent) {
114 xml_node_private_t *nodepriv = xml->_private;
115
116 if (nodepriv != NULL) {
117 pcmk__set_xml_flags(nodepriv, flags);
118 }
119 }
120 }
121
122 /*!
123 * \internal
124 * \brief Set flags for an XML document
125 *
126 * \param[in,out] doc XML document
127 * \param[in] flags Group of <tt>enum pcmk__xml_flags</tt>
128 */
129 void
130 pcmk__xml_doc_set_flags(xmlDoc *doc, uint32_t flags)
131 {
132 if (doc != NULL) {
133 xml_doc_private_t *docpriv = doc->_private;
134
135 pcmk__set_xml_flags(docpriv, flags);
136 }
137 }
138
139 /*!
140 * \internal
141 * \brief Check whether the given flags are set for an XML document
142 *
143 * \param[in] doc XML document to check
144 * \param[in] flags Group of <tt>enum pcmk__xml_flags</tt>
145 *
146 * \return \c true if all of \p flags are set for \p doc, or \c false otherwise
147 */
148 bool
149 pcmk__xml_doc_all_flags_set(const xmlDoc *doc, uint32_t flags)
150 {
151 if (doc != NULL) {
152 xml_doc_private_t *docpriv = doc->_private;
153
154 return (docpriv != NULL) && pcmk__all_flags_set(docpriv->flags, flags);
155 }
156 return false;
157 }
158
159 // Mark document, element, and all element's parents as changed
160 void
161 pcmk__mark_xml_node_dirty(xmlNode *xml)
162 {
163 if (xml == NULL) {
164 return;
165 }
166 pcmk__xml_doc_set_flags(xml->doc, pcmk__xf_dirty);
167 pcmk__xml_set_parent_flags(xml, pcmk__xf_dirty);
168 }
169
170 /*!
171 * \internal
172 * \brief Clear flags on an XML node
173 *
174 * \param[in,out] xml XML node whose flags to reset
175 * \param[in,out] user_data Ignored
176 *
177 * \return \c true (to continue traversing the tree)
178 *
179 * \note This is compatible with \c pcmk__xml_tree_foreach().
180 */
181 bool
182 pcmk__xml_reset_node_flags(xmlNode *xml, void *user_data)
183 {
184 xml_node_private_t *nodepriv = xml->_private;
185
186 if (nodepriv != NULL) {
187 nodepriv->flags = pcmk__xf_none;
188 }
189 return true;
190 }
191
192 /*!
193 * \internal
194 * \brief Set the \c pcmk__xf_dirty and \c pcmk__xf_created flags on an XML node
195 *
196 * \param[in,out] xml Node whose flags to set
197 * \param[in] user_data Ignored
198 *
199 * \return \c true (to continue traversing the tree)
200 *
201 * \note This is compatible with \c pcmk__xml_tree_foreach().
202 */
203 static bool
204 mark_xml_dirty_created(xmlNode *xml, void *user_data)
205 {
206 xml_node_private_t *nodepriv = xml->_private;
207
208 if (nodepriv != NULL) {
209 pcmk__set_xml_flags(nodepriv, pcmk__xf_dirty|pcmk__xf_created);
210 }
211 return true;
212 }
213
214 /*!
215 * \internal
216 * \brief Mark an XML tree as dirty and created, and mark its parents dirty
217 *
218 * Also mark the document dirty.
219 *
220 * \param[in,out] xml Tree to mark as dirty and created
221 */
222 static void
223 mark_xml_tree_dirty_created(xmlNode *xml)
224 {
225 pcmk__assert(xml != NULL);
226
227 if (!pcmk__xml_doc_all_flags_set(xml->doc, pcmk__xf_tracking)) {
228 // Tracking is disabled for entire document
229 return;
230 }
231
232 // Mark all parents and document dirty
233 pcmk__mark_xml_node_dirty(xml);
234
235 pcmk__xml_tree_foreach(xml, mark_xml_dirty_created, NULL);
236 }
237
238 // Free an XML object previously marked as deleted
239 static void
240 free_deleted_object(void *data)
241 {
242 if(data) {
243 pcmk__deleted_xml_t *deleted_obj = data;
244
245 g_free(deleted_obj->path);
246 free(deleted_obj);
247 }
248 }
249
250 /*!
251 * \internal
252 * \brief Allocate and initialize private data for an XML document
253 *
254 * \param[in,out] doc XML document
255 */
256 static void
257 new_doc_private_data(xmlDoc *doc)
258 {
259 xml_doc_private_t *priv = pcmk__assert_alloc(1, sizeof(xml_doc_private_t));
260
261 priv->check = PCMK__XML_DOC_PRIVATE_MAGIC;
262 doc->_private = priv;
263 }
264
265 /*!
266 * \internal
267 * \brief Allocate and initialize private data for a non-document XML node
268 *
269 * \param[in,out] xml XML node
270 */
271 static void
272 new_node_private_data(xmlNode *xml)
273 {
274 const bool tracking = pcmk__xml_doc_all_flags_set(xml->doc,
275 pcmk__xf_tracking);
276 xml_node_private_t *priv = pcmk__assert_alloc(1,
277 sizeof(xml_node_private_t));
278
279 priv->check = PCMK__XML_NODE_PRIVATE_MAGIC;
280 xml->_private = priv;
281
282 if (tracking) {
283 pcmk__set_xml_flags(priv, pcmk__xf_created);
284 pcmk__mark_xml_node_dirty(xml);
285 }
286 }
287
288 /*!
289 * \internal
290 * \brief Allocate and initialize private data for an XML attribute
291 *
292 * \param[in,out] attr XML attribute
293 * \param[in] user_data Ignored
294 *
295 * \return \c true (to continue iterating)
296 *
297 * \note This is compatible with \c pcmk__xe_foreach_attr().
298 */
299 static bool
300 new_attr_private_data(xmlAttr *attr, void *user_data)
301 {
302 new_node_private_data((xmlNode *) attr);
303 return true;
304 }
305
306 /*!
307 * \internal
308 * \brief Allocate and initialize private data for an XML element
309 *
310 * \param[in,out] xml XML element
311 */
312 static void
313 new_element_private_data(xmlNode *xml)
314 {
315 new_node_private_data(xml);
316 pcmk__xe_foreach_attr(xml, new_attr_private_data, NULL);
317 }
318
319 /*!
320 * \internal
321 * \brief Allocate and initialize private data for an XML node
322 *
323 * \param[in,out] node XML node whose private data to initialize
324 * \param[in] user_data Ignored
325 *
326 * \return \c true (to continue traversing the tree)
327 *
328 * \note This is compatible with \c pcmk__xml_tree_foreach().
329 */
330 static bool
331 new_private_data(xmlNode *node, void *user_data)
332 {
333 CRM_CHECK(node != NULL, return true);
334
335 if (node->_private != NULL) {
336 return true;
337 }
338
339 switch (node->type) {
340 case XML_DOCUMENT_NODE:
341 new_doc_private_data((xmlDoc *) node);
342 return true;
343
344 case XML_ATTRIBUTE_NODE:
345 case XML_COMMENT_NODE:
346 new_node_private_data(node);
347 return true;
348
349 case XML_ELEMENT_NODE:
350 new_element_private_data(node);
351 return true;
352
353 case XML_TEXT_NODE:
354 case XML_DTD_NODE:
355 case XML_CDATA_SECTION_NODE:
356 return true;
357
358 default:
359 CRM_LOG_ASSERT(node->type == XML_ELEMENT_NODE);
360 return true;
361 }
362 }
363
364 /*!
365 * \internal
366 * \brief Free and zero all data fields of an XML document's private data
367 *
368 * This function does not clear the \c check field or free the private data
369 * object itself.
370 *
371 * \param[in,out] docpriv XML document private data
372 */
373 static void
374 reset_doc_private_data(xml_doc_private_t *docpriv)
375 {
376 if (docpriv == NULL) {
377 return;
378 }
379
380 pcmk__assert(docpriv->check == PCMK__XML_DOC_PRIVATE_MAGIC);
381
382 docpriv->flags = pcmk__xf_none;
383
384 g_clear_pointer(&docpriv->acl_user, free);
385 g_clear_pointer(&docpriv->acls, pcmk__free_acls);
386
387 g_list_free_full(docpriv->deleted_objs, free_deleted_object);
388 docpriv->deleted_objs = NULL;
389 }
390
391 /*!
392 * \internal
393 * \brief Free and clear private data for an XML document
394 *
395 * \param[in,out] doc XML document
396 */
397 static void
398 free_doc_private_data(xmlDoc *doc)
399 {
400 reset_doc_private_data(doc->_private);
401 g_clear_pointer(&doc->_private, free);
402 }
403
404 /*!
405 * \internal
406 * \brief Free and clear private data for a non-document XML node
407 *
408 * \param[in,out] xml XML node
409 */
410 static void
411 free_node_private_data(xmlNode *xml)
412 {
413 xml_node_private_t *nodepriv = xml->_private;
414
|
(1) Event path: |
Condition "!(nodepriv->check == 1415803521UL)", taking false branch. |
415 pcmk__assert(nodepriv->check == PCMK__XML_NODE_PRIVATE_MAGIC);
416
|
CID (unavailable; MK=6d97e29487f9596140f3102f6ea91ed3) (#1 of 1): Inconsistent C union access (INCONSISTENT_UNION_ACCESS): |
|
(2) Event assign_union_field: |
The union field "in" of "_pp" is written. |
|
(3) Event inconsistent_union_field_access: |
In "_pp.out", the union field used: "out" is inconsistent with the field most recently stored: "in". |
417 g_clear_pointer(&xml->_private, free);
418 }
419
420 /*!
421 * \internal
422 * \brief Free and clear private data for an XML attribute
423 *
424 * \param[in,out] attr XML attribute
425 * \param[in] user_data Ignored
426 *
427 * \return \c true (to continue iterating)
428 *
429 * \note This is compatible with \c pcmk__xe_foreach_attr().
430 */
431 static bool
432 free_attr_private_data(xmlAttr *xml, void *user_data)
433 {
434 free_node_private_data((xmlNode *) xml);
435 return true;
436 }
437
438 /*!
439 * \internal
440 * \brief Free and clear private data for an XML element and its attributes
441 *
442 * \param[in,out] xml XML element
443 */
444 static void
445 free_element_private_data(xmlNode *xml)
446 {
447 free_node_private_data(xml);
448 pcmk__xe_foreach_attr(xml, free_attr_private_data, NULL);
449 }
450
451 /*!
452 * \internal
453 * \brief Free private data for an XML node
454 *
455 * \param[in,out] node XML node whose private data to free
456 * \param[in] user_data Ignored
457 *
458 * \return \c true (to continue traversing the tree)
459 *
460 * \note This is compatible with \c pcmk__xml_tree_foreach().
461 */
462 static bool
463 free_private_data(xmlNode *node, void *user_data)
464 {
465 CRM_CHECK(node != NULL, return true);
466
467 if (node->_private == NULL) {
468 return true;
469 }
470
471 switch (node->type) {
472 case XML_DOCUMENT_NODE:
473 free_doc_private_data((xmlDoc *) node);
474 return true;
475
476 case XML_ELEMENT_NODE:
477 free_element_private_data(node);
478 return true;
479
480 default:
481 free_node_private_data(node);
482 return true;
483 }
484 }
485
486 /*!
487 * \internal
488 * \brief Allocate and initialize private data recursively for an XML tree
489 *
490 * \param[in,out] node XML node whose private data to initialize
491 */
492 void
493 pcmk__xml_new_private_data(xmlNode *xml)
494 {
495 pcmk__xml_tree_foreach(xml, new_private_data, NULL);
496 }
497
498 /*!
499 * \internal
500 * \brief Free private data recursively for an XML tree
501 *
502 * \param[in,out] node XML node whose private data to free
503 */
504 void
505 pcmk__xml_free_private_data(xmlNode *xml)
506 {
507 pcmk__xml_tree_foreach(xml, free_private_data, NULL);
508 }
509
510 /*!
511 * \internal
512 * \brief Return ordinal position of an XML node among its siblings
513 *
514 * \param[in] xml XML node to check
515 * \param[in] ignore_if_set Don't count siblings with this flag set
516 *
517 * \return Ordinal position of \p xml (starting with 0)
518 */
519 int
520 pcmk__xml_position(const xmlNode *xml, enum pcmk__xml_flags ignore_if_set)
521 {
522 int position = 0;
523
524 for (const xmlNode *cIter = xml; cIter->prev; cIter = cIter->prev) {
525 xml_node_private_t *nodepriv = ((xmlNode*)cIter->prev)->_private;
526
527 if (!pcmk__is_set(nodepriv->flags, ignore_if_set)) {
528 position++;
529 }
530 }
531
532 return position;
533 }
534
535 /*!
536 * \internal
537 * \brief Remove all attributes marked as deleted from an XML node
538 *
539 * \param[in,out] xml XML node whose deleted attributes to remove
540 * \param[in,out] user_data Ignored
541 *
542 * \return \c true (to continue traversing the tree)
543 *
544 * \note This is compatible with \c pcmk__xml_tree_foreach().
545 */
546 static bool
547 commit_attr_deletions(xmlNode *xml, void *user_data)
548 {
549 pcmk__xml_reset_node_flags(xml, NULL);
550 pcmk__xe_remove_matching_attrs(xml, true, pcmk__marked_as_deleted, NULL);
551 return true;
552 }
553
554 /*!
555 * \internal
556 * \brief Finalize all pending changes to an XML document and reset private data
557 *
558 * Clear the ACL user and all flags, unpacked ACLs, and deleted node records for
559 * the document; clear all flags on each node in the tree; and delete any
560 * attributes that are marked for deletion.
561 *
562 * \param[in,out] doc XML document
563 *
564 * \note When change tracking is enabled, "deleting" an attribute simply marks
565 * it for deletion (using \c pcmk__xf_deleted) until changes are
566 * committed. Freeing a node (using \c pcmk__xml_free()) adds a deleted
567 * node record (\c pcmk__deleted_xml_t) to the node's document before
568 * freeing it.
569 * \note This function clears all flags, not just flags that indicate changes.
570 * In particular, note that it clears the \c pcmk__xf_tracking flag, thus
571 * disabling tracking.
572 */
573 void
574 pcmk__xml_commit_changes(xmlDoc *doc)
575 {
576 xml_doc_private_t *docpriv = NULL;
577
578 if (doc == NULL) {
579 return;
580 }
581
582 docpriv = doc->_private;
583 if (docpriv == NULL) {
584 return;
585 }
586
587 if (pcmk__is_set(docpriv->flags, pcmk__xf_dirty)) {
588 pcmk__xml_tree_foreach(xmlDocGetRootElement(doc), commit_attr_deletions,
589 NULL);
590 }
591 reset_doc_private_data(docpriv);
592 }
593
594 /*!
595 * \internal
596 * \brief Create a new XML document
597 *
598 * \return Newly allocated XML document (guaranteed not to be \c NULL)
599 *
600 * \note The caller is responsible for freeing the return value using
601 * \c pcmk__xml_free_doc().
602 */
603 xmlDoc *
604 pcmk__xml_new_doc(void)
605 {
606 xmlDoc *doc = xmlNewDoc(XML_VERSION);
607
608 pcmk__mem_assert(doc);
609 pcmk__xml_new_private_data((xmlNode *) doc);
610 return doc;
611 }
612
613 /*!
614 * \internal
615 * \brief Free a new XML document
616 *
617 * \param[in,out] doc XML document to free
618 */
619 void
620 pcmk__xml_free_doc(xmlDoc *doc)
621 {
622 if (doc != NULL) {
623 pcmk__xml_free_private_data((xmlNode *) doc);
624 xmlFreeDoc(doc);
625 }
626 }
627
628 /*!
629 * \internal
630 * \brief Check whether the first character of a string is an XML NameStartChar
631 *
632 * See https://www.w3.org/TR/xml/#NT-NameStartChar.
633 *
634 * This is almost identical to libxml2's \c xmlIsDocNameStartChar(), but they
635 * don't expose it as part of the public API.
636 *
637 * \param[in] utf8 UTF-8 encoded string
638 * \param[out] len If not \c NULL, where to store size in bytes of first
639 * character in \p utf8
640 *
641 * \return \c true if \p utf8 begins with a valid XML NameStartChar, or \c false
642 * otherwise
643 */
644 bool
645 pcmk__xml_is_name_start_char(const char *utf8, int *len)
646 {
647 int c = 0;
648 int local_len = 0;
649
650 if (len == NULL) {
651 len = &local_len;
652 }
653
654 /* xmlGetUTF8Char() abuses the len argument. At call time, it must be set to
655 * "the minimum number of bytes present in the sequence... to assure the
656 * next character is completely contained within the sequence." It's similar
657 * to the "n" in the strn*() functions. However, this doesn't make any sense
658 * for null-terminated strings, and there's no value that indicates "keep
659 * going until '\0'." So we set it to 4, the max number of bytes in a UTF-8
660 * character.
661 *
662 * At return, it's set to the actual number of bytes in the char, or 0 on
663 * error.
664 */
665 *len = 4;
666
667 // Note: xmlGetUTF8Char() assumes a 32-bit int
668 c = xmlGetUTF8Char((const xmlChar *) utf8, len);
669 if (c < 0) {
670 GString *buf = g_string_sized_new(32);
671
672 for (int i = 0; (i < 4) && (utf8[i] != '\0'); i++) {
673 g_string_append_printf(buf, " 0x%.2X", utf8[i]);
674 }
675 pcmk__info("Invalid UTF-8 character (bytes:%s)",
676 (pcmk__str_empty(buf->str)? " <none>" : buf->str));
677 g_string_free(buf, TRUE);
678 return false;
679 }
680
681 return (c == '_')
682 || (c == ':')
683 || ((c >= 'a') && (c <= 'z'))
684 || ((c >= 'A') && (c <= 'Z'))
685 || ((c >= 0xC0) && (c <= 0xD6))
686 || ((c >= 0xD8) && (c <= 0xF6))
687 || ((c >= 0xF8) && (c <= 0x2FF))
688 || ((c >= 0x370) && (c <= 0x37D))
689 || ((c >= 0x37F) && (c <= 0x1FFF))
690 || ((c >= 0x200C) && (c <= 0x200D))
691 || ((c >= 0x2070) && (c <= 0x218F))
692 || ((c >= 0x2C00) && (c <= 0x2FEF))
693 || ((c >= 0x3001) && (c <= 0xD7FF))
694 || ((c >= 0xF900) && (c <= 0xFDCF))
695 || ((c >= 0xFDF0) && (c <= 0xFFFD))
696 || ((c >= 0x10000) && (c <= 0xEFFFF));
697 }
698
699 /*!
700 * \internal
701 * \brief Check whether the first character of a string is an XML NameChar
702 *
703 * See https://www.w3.org/TR/xml/#NT-NameChar.
704 *
705 * This is almost identical to libxml2's \c xmlIsDocNameChar(), but they don't
706 * expose it as part of the public API.
707 *
708 * \param[in] utf8 UTF-8 encoded string
709 * \param[out] len If not \c NULL, where to store size in bytes of first
710 * character in \p utf8
711 *
712 * \return \c true if \p utf8 begins with a valid XML NameChar, or \c false
713 * otherwise
714 */
715 bool
716 pcmk__xml_is_name_char(const char *utf8, int *len)
717 {
718 int c = 0;
719 int local_len = 0;
720
721 if (len == NULL) {
722 len = &local_len;
723 }
724
725 // See comment regarding len in pcmk__xml_is_name_start_char()
726 *len = 4;
727
728 // Note: xmlGetUTF8Char() assumes a 32-bit int
729 c = xmlGetUTF8Char((const xmlChar *) utf8, len);
730 if (c < 0) {
731 GString *buf = g_string_sized_new(32);
732
733 for (int i = 0; (i < 4) && (utf8[i] != '\0'); i++) {
734 g_string_append_printf(buf, " 0x%.2X", utf8[i]);
735 }
736 pcmk__info("Invalid UTF-8 character (bytes:%s)",
737 (pcmk__str_empty(buf->str)? " <none>" : buf->str));
738 g_string_free(buf, TRUE);
739 return false;
740 }
741
742 return ((c >= 'a') && (c <= 'z'))
743 || ((c >= 'A') && (c <= 'Z'))
744 || ((c >= '0') && (c <= '9'))
745 || (c == '_')
746 || (c == ':')
747 || (c == '-')
748 || (c == '.')
749 || (c == 0xB7)
750 || ((c >= 0xC0) && (c <= 0xD6))
751 || ((c >= 0xD8) && (c <= 0xF6))
752 || ((c >= 0xF8) && (c <= 0x2FF))
753 || ((c >= 0x300) && (c <= 0x36F))
754 || ((c >= 0x370) && (c <= 0x37D))
755 || ((c >= 0x37F) && (c <= 0x1FFF))
756 || ((c >= 0x200C) && (c <= 0x200D))
757 || ((c >= 0x203F) && (c <= 0x2040))
758 || ((c >= 0x2070) && (c <= 0x218F))
759 || ((c >= 0x2C00) && (c <= 0x2FEF))
760 || ((c >= 0x3001) && (c <= 0xD7FF))
761 || ((c >= 0xF900) && (c <= 0xFDCF))
762 || ((c >= 0xFDF0) && (c <= 0xFFFD))
763 || ((c >= 0x10000) && (c <= 0xEFFFF));
764 }
765
766 /*!
767 * \internal
768 * \brief Sanitize a string so it is usable as an XML ID
769 *
770 * An ID must match the Name production as defined here:
771 * https://www.w3.org/TR/xml/#NT-Name.
772 *
773 * Convert an invalid start character to \c '_'. Convert an invalid character
774 * after the start character to \c '.'.
775 *
776 * \param[in,out] id String to sanitize
777 */
778 void
779 pcmk__xml_sanitize_id(char *id)
780 {
781 bool valid = true;
782 int len = 0;
783
784 // If id is empty or NULL, there's no way to make it a valid XML ID
785 pcmk__assert(!pcmk__str_empty(id));
786
787 /* @TODO Suppose there are two strings and each has an invalid ID character
788 * in the same position. The strings are otherwise identical. Both strings
789 * will be sanitized to the same valid ID, which is incorrect.
790 *
791 * The caller is responsible for ensuring the sanitized ID does not already
792 * exist in a given XML document before using it, if uniqueness is desired.
793 */
794 valid = pcmk__xml_is_name_start_char(id, &len);
795 CRM_CHECK(len > 0, return); // UTF-8 encoding error
796 if (!valid) {
797 *id = '_';
798 for (int i = 1; i < len; i++) {
799 id[i] = '.';
800 }
801 }
802
803 for (id += len; *id != '\0'; id += len) {
804 valid = pcmk__xml_is_name_char(id, &len);
805 CRM_CHECK(len > 0, return); // UTF-8 encoding error
806 if (!valid) {
807 for (int i = 0; i < len; i++) {
808 id[i] = '.';
809 }
810 }
811 }
812 }
813
814 /*!
815 * \internal
816 * \brief Free an XML tree without ACL checks or change tracking
817 *
818 * \param[in,out] xml XML node to free
819 */
820 void
821 pcmk__xml_free_node(xmlNode *xml)
822 {
823 pcmk__xml_free_private_data(xml);
824 xmlUnlinkNode(xml);
825 xmlFreeNode(xml);
826 }
827
828 /*!
829 * \internal
830 * \brief Free an XML tree if ACLs allow; track deletion if tracking is enabled
831 *
832 * If \p node is the root of its document, free the entire document.
833 *
834 * \param[in,out] node XML node to free
835 * \param[in] position Position of \p node among its siblings for change
836 * tracking (negative to calculate automatically if
837 * needed)
838 *
839 * \return Standard Pacemaker return code
840 */
841 static int
842 free_xml_with_position(xmlNode *node, int position)
843 {
844 xmlDoc *doc = NULL;
845 xml_node_private_t *nodepriv = NULL;
846 xml_doc_private_t *docpriv = NULL;
847 GString *xpath = NULL;
848
849 if (node == NULL) {
850 return pcmk_rc_ok;
851 }
852 doc = node->doc;
853 nodepriv = node->_private;
854
855 if ((doc != NULL) && (xmlDocGetRootElement(doc) == node)) {
856 /* @TODO Should we check ACLs first? Otherwise it seems like we could
857 * free the root element without write permission.
858 */
859 pcmk__xml_free_doc(doc);
860 return pcmk_rc_ok;
861 }
862
863 if (!pcmk__check_acl(node, NULL, pcmk__xf_acl_write)) {
864 pcmk__if_tracing(
865 {
866 GString *xpath = pcmk__element_xpath(node);
867
868 qb_log_from_external_source(__func__, __FILE__,
869 "Cannot remove %s %x", LOG_TRACE,
870 __LINE__, 0, xpath->str,
871 nodepriv->flags);
872 g_string_free(xpath, TRUE);
873 },
874 {}
875 );
876 return EACCES;
877 }
878
879 if (!pcmk__xml_doc_all_flags_set(node->doc, pcmk__xf_tracking)
880 || pcmk__is_set(nodepriv->flags, pcmk__xf_created)) {
881 pcmk__xml_free_node(node);
882 return pcmk_rc_ok;
883 }
884
885 pcmk__assert(doc != NULL);
886
887 docpriv = doc->_private;
888 xpath = pcmk__element_xpath(node);
889
890 if (xpath != NULL) {
891 pcmk__deleted_xml_t *deleted_obj = NULL;
892
893 pcmk__trace("Deleting %s %p from %p", xpath->str, node, doc);
894
895 deleted_obj = pcmk__assert_alloc(1, sizeof(pcmk__deleted_xml_t));
896 deleted_obj->path = g_string_free(xpath, FALSE);
897 deleted_obj->position = -1;
898
899 // Record the position only for XML comments for now
900 if (node->type == XML_COMMENT_NODE) {
901 if (position >= 0) {
902 deleted_obj->position = position;
903
904 } else {
905 deleted_obj->position = pcmk__xml_position(node, pcmk__xf_skip);
906 }
907 }
908
909 docpriv->deleted_objs = g_list_append(docpriv->deleted_objs,
910 deleted_obj);
911 pcmk__xml_doc_set_flags(node->doc, pcmk__xf_dirty);
912 }
913
914 pcmk__xml_free_node(node);
915 return pcmk_rc_ok;
916 }
917
918 /*!
919 * \internal
920 * \brief Free an XML tree if ACLs allow; track deletion if tracking is enabled
921 *
922 * If \p xml is the root of its document, free the entire document.
923 *
924 * \param[in,out] xml XML node to free
925 */
926 void
927 pcmk__xml_free(xmlNode *xml)
928 {
929 free_xml_with_position(xml, -1);
930 }
931
932 /*!
933 * \internal
934 * \brief Make a deep copy of an XML node under a given parent
935 *
936 * \param[in,out] parent XML element that will be the copy's parent (\c NULL
937 * to create a new XML document with the copy as root)
938 * \param[in] src XML node to copy
939 *
940 * \return Deep copy of \p src, or \c NULL if \p src is \c NULL
941 */
942 xmlNode *
943 pcmk__xml_copy(xmlNode *parent, xmlNode *src)
944 {
945 xmlNode *copy = NULL;
946
947 if (src == NULL) {
948 return NULL;
949 }
950
951 if (parent == NULL) {
952 xmlDoc *doc = NULL;
953
954 // The copy will be the root element of a new document
955 pcmk__assert(src->type == XML_ELEMENT_NODE);
956
957 doc = pcmk__xml_new_doc();
958 copy = xmlDocCopyNode(src, doc, 1);
959 pcmk__mem_assert(copy);
960
961 xmlDocSetRootElement(doc, copy);
962
963 } else {
964 copy = xmlDocCopyNode(src, parent->doc, 1);
965 pcmk__mem_assert(copy);
966
967 xmlAddChild(parent, copy);
968 }
969
970 pcmk__xml_new_private_data(copy);
971 return copy;
972 }
973
974 /*!
975 * \internal
976 * \brief Replace one XML node with a copy of another XML node
977 *
978 * This function handles change tracking and applies ACLs.
979 *
980 * \param[in,out] old XML node to replace
981 * \param[in] new XML node to copy as replacement for \p old
982 *
983 * \return Copy of \p new that replaced \p old
984 *
985 * \note This frees \p old.
986 * \note The caller is responsible for freeing the return value using
987 * \c pcmk__xml_free() (but note that it may be part of a larger XML
988 * tree).
989 */
990 xmlNode *
991 pcmk__xml_replace_with_copy(xmlNode *old, xmlNode *new)
992 {
993 xmlNode *new_copy = NULL;
994
995 pcmk__assert((old != NULL) && (new != NULL));
996
997 /* Pass old to pcmk__xml_copy() so that new_copy gets created within the
998 * same doc. But old won't remain its parent.
999 */
1000 new_copy = pcmk__xml_copy(old, new);
1001 old = xmlReplaceNode(old, new_copy);
1002
1003 // old == NULL means memory allocation error
1004 pcmk__assert(old != NULL);
1005
1006 // May be unnecessary but avoids slight changes to some test outputs
1007 pcmk__xml_tree_foreach(new_copy, pcmk__xml_reset_node_flags, NULL);
1008
1009 if (pcmk__xml_doc_all_flags_set(new_copy->doc, pcmk__xf_tracking)) {
1010 // Replaced sections may have included relevant ACLs
1011 pcmk__apply_acls(new_copy->doc);
1012 }
1013 pcmk__xml_mark_changes(old, new_copy);
1014 pcmk__xml_free_node(old);
1015
1016 return new_copy;
1017 }
1018
1019 /*!
1020 * \internal
1021 * \brief Remove XML text nodes from specified XML and all its children
1022 *
1023 * \param[in,out] xml XML to strip text from
1024 */
1025 void
1026 pcmk__strip_xml_text(xmlNode *xml)
1027 {
1028 xmlNode *iter = xml->children;
1029
1030 while (iter) {
1031 xmlNode *next = iter->next;
1032
1033 switch (iter->type) {
1034 case XML_TEXT_NODE:
1035 pcmk__xml_free_node(iter);
1036 break;
1037
1038 case XML_ELEMENT_NODE:
1039 /* Search it */
1040 pcmk__strip_xml_text(iter);
1041 break;
1042
1043 default:
1044 /* Leave it */
1045 break;
1046 }
1047
1048 iter = next;
1049 }
1050 }
1051
1052 /*!
1053 * \internal
1054 * \brief Check whether a string has XML special characters that must be escaped
1055 *
1056 * See \c pcmk__xml_escape() and \c pcmk__xml_escape_type for more details.
1057 *
1058 * \param[in] text String to check
1059 * \param[in] type Type of escaping
1060 *
1061 * \return \c true if \p text has special characters that need to be escaped, or
1062 * \c false otherwise
1063 */
1064 bool
1065 pcmk__xml_needs_escape(const char *text, enum pcmk__xml_escape_type type)
1066 {
1067 if (text == NULL) {
1068 return false;
1069 }
1070
1071 while (*text != '\0') {
1072 switch (type) {
1073 case pcmk__xml_escape_text:
1074 switch (*text) {
1075 case '<':
1076 case '>':
1077 case '&':
1078 return true;
1079 case '\n':
1080 case '\t':
1081 break;
1082 default:
1083 if (g_ascii_iscntrl(*text)) {
1084 return true;
1085 }
1086 break;
1087 }
1088 break;
1089
1090 case pcmk__xml_escape_attr:
1091 switch (*text) {
1092 case '<':
1093 case '>':
1094 case '&':
1095 case '"':
1096 return true;
1097 default:
1098 if (g_ascii_iscntrl(*text)) {
1099 return true;
1100 }
1101 break;
1102 }
1103 break;
1104
1105 case pcmk__xml_escape_attr_pretty:
1106 switch (*text) {
1107 case '\n':
1108 case '\r':
1109 case '\t':
1110 case '"':
1111 return true;
1112 default:
1113 break;
1114 }
1115 break;
1116
1117 default: // Invalid enum value
1118 pcmk__assert(false);
1119 break;
1120 }
1121
1122 text = g_utf8_next_char(text);
1123 }
1124 return false;
1125 }
1126
1127 /*!
1128 * \internal
1129 * \brief Replace special characters with their XML escape sequences
1130 *
1131 * \param[in] text Text to escape
1132 * \param[in] type Type of escaping
1133 *
1134 * \return Newly allocated string equivalent to \p text but with special
1135 * characters replaced with XML escape sequences (or \c NULL if \p text
1136 * is \c NULL). If \p text is not \c NULL, the return value is
1137 * guaranteed not to be \c NULL.
1138 *
1139 * \note There are libxml functions that purport to do this:
1140 * \c xmlEncodeEntitiesReentrant() and \c xmlEncodeSpecialChars().
1141 * However, their escaping is incomplete. See:
1142 * https://discourse.gnome.org/t/intended-use-of-xmlencodeentitiesreentrant-vs-xmlencodespecialchars/19252
1143 * \note The caller is responsible for freeing the return value using
1144 * \c g_free().
1145 */
1146 gchar *
1147 pcmk__xml_escape(const char *text, enum pcmk__xml_escape_type type)
1148 {
1149 GString *copy = NULL;
1150
1151 if (text == NULL) {
1152 return NULL;
1153 }
1154 copy = g_string_sized_new(strlen(text));
1155
1156 while (*text != '\0') {
1157 // Don't escape any non-ASCII characters
1158 if ((*text & 0x80) != 0) {
1159 size_t bytes = g_utf8_next_char(text) - text;
1160
1161 g_string_append_len(copy, text, bytes);
1162 text += bytes;
1163 continue;
1164 }
1165
1166 switch (type) {
1167 case pcmk__xml_escape_text:
1168 switch (*text) {
1169 case '<':
1170 g_string_append(copy, PCMK__XML_ENTITY_LT);
1171 break;
1172 case '>':
1173 g_string_append(copy, PCMK__XML_ENTITY_GT);
1174 break;
1175 case '&':
1176 g_string_append(copy, PCMK__XML_ENTITY_AMP);
1177 break;
1178 case '\n':
1179 case '\t':
1180 g_string_append_c(copy, *text);
1181 break;
1182 default:
1183 if (g_ascii_iscntrl(*text)) {
1184 g_string_append_printf(copy, "&#x%.2X;", *text);
1185 } else {
1186 g_string_append_c(copy, *text);
1187 }
1188 break;
1189 }
1190 break;
1191
1192 case pcmk__xml_escape_attr:
1193 switch (*text) {
1194 case '<':
1195 g_string_append(copy, PCMK__XML_ENTITY_LT);
1196 break;
1197 case '>':
1198 g_string_append(copy, PCMK__XML_ENTITY_GT);
1199 break;
1200 case '&':
1201 g_string_append(copy, PCMK__XML_ENTITY_AMP);
1202 break;
1203 case '"':
1204 g_string_append(copy, PCMK__XML_ENTITY_QUOT);
1205 break;
1206 default:
1207 if (g_ascii_iscntrl(*text)) {
1208 g_string_append_printf(copy, "&#x%.2X;", *text);
1209 } else {
1210 g_string_append_c(copy, *text);
1211 }
1212 break;
1213 }
1214 break;
1215
1216 case pcmk__xml_escape_attr_pretty:
1217 switch (*text) {
1218 case '"':
1219 g_string_append(copy, "\\\"");
1220 break;
1221 case '\n':
1222 g_string_append(copy, "\\n");
1223 break;
1224 case '\r':
1225 g_string_append(copy, "\\r");
1226 break;
1227 case '\t':
1228 g_string_append(copy, "\\t");
1229 break;
1230 default:
1231 g_string_append_c(copy, *text);
1232 break;
1233 }
1234 break;
1235
1236 default: // Invalid enum value
1237 pcmk__assert(false);
1238 break;
1239 }
1240
1241 text = g_utf8_next_char(text);
1242 }
1243 return g_string_free(copy, FALSE);
1244 }
1245
1246 /*!
1247 * \internal
1248 * \brief Set the \c pcmk__xf_created flag on an attribute
1249 *
1250 * \param[in,out] attr XML attribute
1251 * \param[in] user_data Ignored
1252 *
1253 * \return \c true (to continue iterating)
1254 *
1255 * \note This is compatible with \c pcmk__xe_foreach_attr().
1256 */
1257 static bool
1258 mark_attr_created(xmlAttr *attr, void *user_data)
1259 {
1260 xml_node_private_t *nodepriv = attr->_private;
1261
1262 pcmk__set_xml_flags(nodepriv, pcmk__xf_created);
1263 return true;
1264 }
1265
1266 /*!
1267 * \internal
1268 * \brief Add an XML attribute to a node, marked as deleted
1269 *
1270 * When calculating XML changes, we need to know when an attribute has been
1271 * deleted. Add the attribute back to the new XML, so that we can check the
1272 * removal against ACLs, and mark it as deleted for later removal after
1273 * differences have been calculated.
1274 *
1275 * \param[in,out] new_xml XML to modify
1276 * \param[in] attr_name Name of attribute that was deleted
1277 * \param[in] old_value Value of attribute that was deleted
1278 */
1279 static void
1280 mark_attr_deleted(xmlNode *new_xml, const char *attr_name,
1281 const char *old_value)
1282 {
1283 xml_doc_private_t *docpriv = new_xml->doc->_private;
1284 xmlAttr *attr = NULL;
1285 xml_node_private_t *nodepriv;
1286
1287 /* Restore the old value (without setting dirty flag recursively upwards or
1288 * checking ACLs)
1289 */
1290 pcmk__clear_xml_flags(docpriv, pcmk__xf_tracking);
1291 pcmk__xe_set(new_xml, attr_name, old_value);
1292 pcmk__set_xml_flags(docpriv, pcmk__xf_tracking);
1293
1294 // Reset flags (so the attribute doesn't appear as newly created)
1295 attr = xmlHasProp(new_xml, (const xmlChar *) attr_name);
1296 nodepriv = attr->_private;
1297 nodepriv->flags = 0;
1298
1299 // Check ACLs and mark restored value for later removal
1300 pcmk__xa_remove(attr, false);
1301
1302 pcmk__trace("XML attribute %s=%s was removed from %s", attr_name, old_value,
1303 (const char *) new_xml->name);
1304 }
1305
1306 /*
1307 * \internal
1308 * \brief Check ACLs for a changed XML attribute
1309 */
1310 static void
1311 mark_attr_changed(xmlNode *new_xml, const char *attr_name,
1312 const char *old_value)
1313 {
1314 xml_doc_private_t *docpriv = new_xml->doc->_private;
1315 char *vcopy = pcmk__xe_get_copy(new_xml, attr_name);
1316
1317 pcmk__trace("XML attribute %s was changed from '%s' to '%s' in %s",
1318 attr_name, old_value, vcopy, (const char *) new_xml->name);
1319
1320 // Restore the original value (without checking ACLs)
1321 pcmk__clear_xml_flags(docpriv, pcmk__xf_tracking);
1322 pcmk__xe_set(new_xml, attr_name, old_value);
1323 pcmk__set_xml_flags(docpriv, pcmk__xf_tracking);
1324
1325 // Change it back to the new value, to check ACLs
1326 pcmk__xe_set(new_xml, attr_name, vcopy);
1327 free(vcopy);
1328 }
1329
1330 /*!
1331 * \internal
1332 * \brief Mark an XML attribute as having changed position
1333 *
1334 * \param[in,out] new_xml XML to modify
1335 * \param[in,out] old_attr Attribute that moved, in original XML
1336 * \param[in,out] new_attr Attribute that moved, in \p new_xml
1337 * \param[in] p_old Ordinal position of \p old_attr in original XML
1338 * \param[in] p_new Ordinal position of \p new_attr in \p new_xml
1339 */
1340 static void
1341 mark_attr_moved(xmlNode *new_xml, xmlAttr *old_attr, xmlAttr *new_attr,
1342 int p_old, int p_new)
1343 {
1344 xml_node_private_t *nodepriv = new_attr->_private;
1345
1346 pcmk__trace("XML attribute %s moved from position %d to %d in %s",
1347 old_attr->name, p_old, p_new, (const char *) new_xml->name);
1348
1349 // Mark document, element, and all element's parents as changed
1350 pcmk__mark_xml_node_dirty(new_xml);
1351
1352 // Mark attribute as changed
1353 pcmk__set_xml_flags(nodepriv, pcmk__xf_dirty|pcmk__xf_moved);
1354
1355 nodepriv = (p_old > p_new)? old_attr->_private : new_attr->_private;
1356 pcmk__set_xml_flags(nodepriv, pcmk__xf_skip);
1357 }
1358
1359 /*!
1360 * \internal
1361 * \brief Mark an XML attribute as deleted, changed, or moved if appropriate
1362 *
1363 * Given an attribute (from an old XML element) and a new XML element, check
1364 * whether the attribute has been deleted, changed, or moved between the old and
1365 * new elements. If so, mark the new XML element to indicate what changed.
1366 *
1367 * \param[in,out] old_attr XML attribute from old element
1368 * \param[in,out] user_data New XML element
1369 *
1370 * \return \c true (to continue iterating)
1371 *
1372 * \note This is compatible with \c pcmk__xe_foreach_attr().
1373 */
1374 static bool
1375 mark_attr_diff(xmlAttr *old_attr, void *user_data)
1376 {
1377 xmlNode *new_xml = user_data;
1378
1379 const char *name = (const char *) old_attr->name;
1380
1381 xmlAttr *new_attr = xmlHasProp(new_xml, old_attr->name);
1382 xml_node_private_t *new_priv = NULL;
1383
1384 const char *old_value = pcmk__xml_attr_value(old_attr);
1385 const char *new_value = NULL;
1386
1387 int old_pos = 0;
1388 int new_pos = 0;
1389
1390 if (new_attr == NULL) {
1391 mark_attr_deleted(new_xml, name, old_value);
1392 return true;
1393 }
1394
1395 new_priv = new_attr->_private;
1396 new_value = pcmk__xe_get(new_xml, name);
1397
1398 // This attribute isn't new
1399 pcmk__clear_xml_flags(new_priv, pcmk__xf_created);
1400
1401 if (!pcmk__str_eq(old_value, new_value, pcmk__str_none)) {
1402 mark_attr_changed(new_xml, name, old_value);
1403 return true;
1404 }
1405
1406 old_pos = pcmk__xml_position((xmlNode *) old_attr, pcmk__xf_skip);
1407 new_pos = pcmk__xml_position((xmlNode *) new_attr, pcmk__xf_skip);
1408
1409 if ((old_pos == new_pos)
1410 || pcmk__xml_doc_all_flags_set(new_xml->doc,
1411 pcmk__xf_ignore_attr_pos)) {
1412 return true;
1413 }
1414
1415 mark_attr_moved(new_xml, old_attr, new_attr, old_pos, new_pos);
1416 return true;
1417 }
1418
1419 /*!
1420 * \internal
1421 * \brief Mark a new attribute dirty if ACLs allow creation, or remove otherwise
1422 *
1423 * We set the \c pcmk__xf_created flag on all attributes in the new XML at an
1424 * earlier stage of change calculation. Then we checked whether each attribute
1425 * was present in the old XML, and we cleared the flag if so. If the flag is
1426 * still set, then the attribute is truly new.
1427 *
1428 * Now we check whether ACLs allow the attribute's creation. If so, we "accept"
1429 * it: we mark the attribute as dirty and modified, and we mark all of its
1430 * parents as dirty. Otherwise, we reject it by removing the attribute (ignoring
1431 * ACLs and change tracking for the removal).
1432 *
1433 * \param[in,out] attr XML attribute to mark dirty or remove
1434 * \param[in] user_data Ignored
1435 *
1436 * \return \c true (to continue iterating)
1437 *
1438 * \note This is compatible with \c pcmk__xe_foreach_attr().
1439 */
1440 static bool
1441 check_new_attr_acls(xmlAttr *attr, void *user_data)
1442 {
1443 const char *name = (const char *) attr->name;
1444 const char *value = pcmk__xml_attr_value(attr);
1445 const xml_node_private_t *nodepriv = attr->_private;
1446 xmlNode *new_xml = attr->parent;
1447 const char *new_xml_id = pcmk__s(pcmk__xe_id(new_xml), "without ID");
1448
1449 if (!pcmk__is_set(nodepriv->flags, pcmk__xf_created)) {
1450 return true;
1451 }
1452
1453 /* Check ACLs (we can't use the remove-then-create trick because it
1454 * would modify the attribute position).
1455 */
1456 if (!pcmk__check_acl(new_xml, name, pcmk__xf_acl_write)) {
1457 pcmk__trace("ACLs prevent creation of attribute %s=%s in %s %s", name,
1458 value, (const char *) new_xml->name, new_xml_id);
1459 pcmk__xa_remove(attr, true);
1460 return true;
1461 }
1462
1463 pcmk__trace("Created new attribute %s=%s in %s %s", name, value,
1464 (const char *) new_xml->name, new_xml_id);
1465 pcmk__mark_xml_attr_dirty(attr);
1466 return true;
1467 }
1468
1469 /*!
1470 * \internal
1471 * \brief Calculate differences in attributes between two XML nodes
1472 *
1473 * \param[in,out] old_xml Original XML to compare
1474 * \param[in,out] new_xml New XML to compare
1475 */
1476 static void
1477 xml_diff_attrs(xmlNode *old_xml, xmlNode *new_xml)
1478 {
1479 // Cleared later if attributes are not really new
1480 pcmk__xe_foreach_attr(new_xml, mark_attr_created, NULL);
1481
1482 pcmk__xe_foreach_attr(old_xml, mark_attr_diff, new_xml);
1483 pcmk__xe_foreach_attr(new_xml, check_new_attr_acls, NULL);
1484 }
1485
1486 /*!
1487 * \internal
1488 * \brief Add a deleted object record for an old XML child if ACLs allow
1489 *
1490 * This is intended to be called for a child of an old XML element that is not
1491 * present as a child of a new XML element.
1492 *
1493 * Add a temporary copy of the old child to the new XML. Then check whether ACLs
1494 * would have allowed the deletion of that element. If so, add a deleted object
1495 * record for it to the new XML's document, and set the \c pcmk__xf_skip flag on
1496 * the old child.
1497 *
1498 * The temporary copy is removed before returning. The new XML and all of its
1499 * ancestors will have the \c pcmk__xf_dirty flag set because of the creation,
1500 * however.
1501 *
1502 * \param[in,out] old_child Child of old XML
1503 * \param[in,out] new_parent New XML that does not contain \p old_child
1504 *
1505 * \note The deletion is checked using the new XML's ACLs. The ACLs may have
1506 * also changed between the old and new XML trees. Callers should take
1507 * reasonable action if there were ACL changes that themselves would have
1508 * been denied.
1509 */
1510 static void
1511 mark_child_deleted(xmlNode *old_child, xmlNode *new_parent)
1512 {
1513 int pos = pcmk__xml_position(old_child, pcmk__xf_skip);
1514
1515 // Re-create the child element so we can check ACLs
1516 xmlNode *candidate = pcmk__xml_copy(new_parent, old_child);
1517
1518 // Clear flags on new child and its children
1519 pcmk__xml_tree_foreach(candidate, pcmk__xml_reset_node_flags, NULL);
1520
1521 // free_xml_with_position() will check whether ACLs allow the deletion
1522 pcmk__apply_acls(candidate->doc);
1523
1524 /* Try to remove the child again (which will track it in document's
1525 * deleted_objs on success)
1526 */
1527 if (free_xml_with_position(candidate, pos) != pcmk_rc_ok) {
1528 // ACLs denied deletion in free_xml_with_position. Free candidate here.
1529 pcmk__xml_free_node(candidate);
1530 }
1531
1532 pcmk__set_xml_flags((xml_node_private_t *) old_child->_private,
1533 pcmk__xf_skip);
1534 }
1535
1536 /*!
1537 * \internal
1538 * \brief Mark a new child as moved and set \c pcmk__xf_skip as appropriate
1539 *
1540 * \param[in,out] old_child Child of old XML
1541 * \param[in,out] new_child Child of new XML that matches \p old_child
1542 * \param[in] old_pos Position of \p old_child among its siblings
1543 * \param[in] new_pos Position of \p new_child among its siblings
1544 */
1545 static void
1546 mark_child_moved(xmlNode *old_child, xmlNode *new_child, int old_pos,
1547 int new_pos)
1548 {
1549 const char *id_s = pcmk__s(pcmk__xe_id(new_child), "<no id>");
1550 xmlNode *new_parent = new_child->parent;
1551 xml_node_private_t *nodepriv = new_child->_private;
1552
1553 pcmk__trace("Child element %s with " PCMK_XA_ID "='%s' moved from position "
1554 "%d to %d under %s",
1555 new_child->name, id_s, old_pos, new_pos, new_parent->name);
1556 pcmk__mark_xml_node_dirty(new_parent);
1557 pcmk__set_xml_flags(nodepriv, pcmk__xf_moved);
1558
1559 /* @TODO Figure out and document why we skip the old child in future
1560 * position calculations if the old position is higher, and skip the new
1561 * child in future position calculations if the new position is higher. This
1562 * goes back to d028b52, and there's no explanation in the commit message.
1563 */
1564 if (old_pos > new_pos) {
1565 nodepriv = old_child->_private;
1566 }
1567 pcmk__set_xml_flags(nodepriv, pcmk__xf_skip);
1568 }
1569
1570 /*!
1571 * \internal
1572 * \brief Check whether a new XML child comment matches an old XML child comment
1573 *
1574 * Two comments match if they have the same position among their siblings and
1575 * the same contents.
1576 *
1577 * If \p new_comment has the \c pcmk__xf_skip flag set, then it is automatically
1578 * considered not to match.
1579 *
1580 * \param[in] old_comment Old XML child element
1581 * \param[in] new_comment New XML child element
1582 *
1583 * \retval \c true if \p new_comment matches \p old_comment
1584 * \retval \c false otherwise
1585 */
1586 static bool
1587 new_comment_matches(const xmlNode *old_comment, const xmlNode *new_comment)
1588 {
1589 xml_node_private_t *nodepriv = new_comment->_private;
1590
1591 if (pcmk__is_set(nodepriv->flags, pcmk__xf_skip)) {
1592 /* @TODO Should we also return false if old_comment has pcmk__xf_skip
1593 * set? This preserves existing behavior at time of writing.
1594 */
1595 return false;
1596 }
1597 if (pcmk__xml_position(old_comment, pcmk__xf_skip)
1598 != pcmk__xml_position(new_comment, pcmk__xf_skip)) {
1599 return false;
1600 }
1601 return pcmk__xc_matches(old_comment, new_comment);
1602 }
1603
1604 /*!
1605 * \internal
1606 * \brief Check whether a new XML child element matches an old XML child element
1607 *
1608 * Two elements match if they have the same name and the same ID. (Both IDs can
1609 * be \c NULL.)
1610 *
1611 * For XML attributes other than \c PCMK_XA_ID, we can treat a value change as
1612 * an in-place modification. However, when Pacemaker applies a patchset, it uses
1613 * the \c PCMK_XA_ID attribute to find the node to update (modify, delete, or
1614 * move). If we treat two nodes with different \c PCMK_XA_ID attributes as
1615 * matching and then mark that attribute as changed, it can cause this lookup to
1616 * fail.
1617 *
1618 * There's unlikely to ever be much practical reason to treat elements with
1619 * different IDs as a change. Unless that changes, we'll treat them as a
1620 * mismatch.
1621 *
1622 * \param[in] old_element Old XML child element
1623 * \param[in] new_element New XML child element
1624 *
1625 * \retval \c true if \p new_element matches \p old_element
1626 * \retval \c false otherwise
1627 */
1628 static bool
1629 new_element_matches(const xmlNode *old_element, const xmlNode *new_element)
1630 {
1631 return pcmk__xe_is(new_element, (const char *) old_element->name)
1632 && pcmk__str_eq(pcmk__xe_id(old_element), pcmk__xe_id(new_element),
1633 pcmk__str_none);
1634 }
1635
1636 /*!
1637 * \internal
1638 * \brief Check whether a new XML child node matches an old XML child node
1639 *
1640 * Node types must be the same in order to match.
1641 *
1642 * For comments, a match is a comment at the same position with the same
1643 * content.
1644 *
1645 * For elements, a match is an element with the same name and the same ID. (Both
1646 * IDs can be \c NULL.)
1647 *
1648 * For other node types, there is no match.
1649 *
1650 * \param[in] old_child Child of old XML
1651 * \param[in] new_child Child of new XML
1652 *
1653 * \retval \c true if \p new_child matches \p old_child
1654 * \retval \c false otherwise
1655 */
1656 static bool
1657 new_child_matches(const xmlNode *old_child, const xmlNode *new_child)
1658 {
1659 if (old_child->type != new_child->type) {
1660 return false;
1661 }
1662
1663 switch (old_child->type) {
1664 case XML_COMMENT_NODE:
1665 return new_comment_matches(old_child, new_child);
1666 case XML_ELEMENT_NODE:
1667 return new_element_matches(old_child, new_child);
1668 default:
1669 return false;
1670 }
1671 }
1672
1673 /*!
1674 * \internal
1675 * \brief Find matching XML node pairs between old and new XML's children
1676 *
1677 * A node that is part of a matching pair gets its <tt>_private:match</tt>
1678 * member set to the matching node.
1679 *
1680 * \param[in,out] old_xml Old XML
1681 * \param[in,out] new_xml New XML
1682 */
1683 static void
1684 find_matching_children(xmlNode *old_xml, xmlNode *new_xml)
1685 {
1686 for (xmlNode *old_child = pcmk__xml_first_child(old_xml); old_child != NULL;
1687 old_child = pcmk__xml_next(old_child)) {
1688
1689 xml_node_private_t *old_nodepriv = old_child->_private;
1690
1691 if ((old_nodepriv == NULL) || (old_nodepriv->match != NULL)) {
1692 // Can't process, or we already found a match for this old child
1693 continue;
1694 }
1695
1696 for (xmlNode *new_child = pcmk__xml_first_child(new_xml);
1697 new_child != NULL; new_child = pcmk__xml_next(new_child)) {
1698
1699 xml_node_private_t *new_nodepriv = new_child->_private;
1700
1701 if ((new_nodepriv == NULL) || (new_nodepriv->match != NULL)) {
1702 /* Can't process, or this new child already matched some old
1703 * child
1704 */
1705 continue;
1706 }
1707
1708 if (new_child_matches(old_child, new_child)) {
1709 old_nodepriv->match = new_child;
1710 new_nodepriv->match = old_child;
1711 break;
1712 }
1713 }
1714 }
1715 }
1716
1717 /*!
1718 * \internal
1719 * \brief Mark changes between two XML trees
1720 *
1721 * Set flags in a new XML tree to indicate changes relative to an old XML tree.
1722 *
1723 * \param[in,out] old_xml XML before changes
1724 * \param[in,out] new_xml XML after changes
1725 *
1726 * \note This may set \c pcmk__xf_skip on parts of \p old_xml.
1727 */
1728 void
1729 pcmk__xml_mark_changes(xmlNode *old_xml, xmlNode *new_xml)
1730 {
1731 /* This function may set the xml_node_private_t:match member on children of
1732 * old_xml and new_xml, but it clears that member before returning.
1733 *
1734 * @TODO Ensure we handle (for example, by copying) or reject user-created
1735 * XML that is missing xml_node_private_t at top level or in any children.
1736 * Similarly, check handling of node types for which we don't create private
1737 * data. For now, we'll skip them in the loops below.
1738 */
1739 CRM_CHECK((old_xml != NULL) && (new_xml != NULL), return);
1740 if ((old_xml->_private == NULL) || (new_xml->_private == NULL)) {
1741 return;
1742 }
1743
1744 pcmk__xml_doc_set_flags(new_xml->doc, pcmk__xf_tracking);
1745 xml_diff_attrs(old_xml, new_xml);
1746
1747 find_matching_children(old_xml, new_xml);
1748
1749 // Process matches (changed children) and deletions
1750 for (xmlNode *old_child = pcmk__xml_first_child(old_xml); old_child != NULL;
1751 old_child = pcmk__xml_next(old_child)) {
1752
1753 xml_node_private_t *nodepriv = old_child->_private;
1754 xmlNode *new_child = NULL;
1755
1756 if (nodepriv == NULL) {
1757 continue;
1758 }
1759
1760 if (nodepriv->match == NULL) {
1761 // No match in new XML means the old child was deleted
1762 mark_child_deleted(old_child, new_xml);
1763 continue;
1764 }
1765
1766 /* Fetch the match and clear old_child->_private's match member.
1767 * new_child->_private's match member is handled in the new_xml loop.
1768 */
1769 new_child = nodepriv->match;
1770 nodepriv->match = NULL;
1771
1772 pcmk__assert(old_child->type == new_child->type);
1773
1774 if (old_child->type == XML_COMMENT_NODE) {
1775 // Comments match only if their positions and contents match
1776 continue;
1777 }
1778
1779 pcmk__xml_mark_changes(old_child, new_child);
1780 }
1781
1782 /* Mark unmatched new children as created, and mark matched new children as
1783 * moved if their positions changed. Grab the next new child in advance,
1784 * since new_child may get freed in the loop body.
1785 */
1786 for (xmlNode *new_child = pcmk__xml_first_child(new_xml),
1787 *next = pcmk__xml_next(new_child);
1788 new_child != NULL;
1789 new_child = next, next = pcmk__xml_next(new_child)) {
1790
1791 xml_node_private_t *nodepriv = new_child->_private;
1792
1793 if (nodepriv == NULL) {
1794 continue;
1795 }
1796
1797 if (nodepriv->match != NULL) {
1798 /* Fetch the match and clear new_child->_private's match member. Any
1799 * changes were marked in the old_xml loop. Mark the move.
1800 *
1801 * We might be able to mark the move earlier, when we mark changes
1802 * for matches in the old_xml loop, consolidating both actions. We'd
1803 * have to think about whether the timing of setting the
1804 * pcmk__xf_skip flag makes any difference.
1805 */
1806 xmlNode *old_child = nodepriv->match;
1807 int old_pos = pcmk__xml_position(old_child, pcmk__xf_skip);
1808 int new_pos = pcmk__xml_position(new_child, pcmk__xf_skip);
1809
1810 if (old_pos != new_pos) {
1811 mark_child_moved(old_child, new_child, old_pos, new_pos);
1812 }
1813 nodepriv->match = NULL;
1814 continue;
1815 }
1816
1817 // No match in old XML means the new child is newly created
1818 pcmk__set_xml_flags(nodepriv, pcmk__xf_skip);
1819 mark_xml_tree_dirty_created(new_child);
1820
1821 // Check whether creation was allowed (may free new_child)
1822 pcmk__apply_creation_acl(new_child, true);
1823 }
1824 }
1825
1826 char *
1827 pcmk__xml_artefact_root(enum pcmk__xml_artefact_ns ns)
1828 {
1829 static const char *base = NULL;
1830 char *ret = NULL;
1831
1832 if (base == NULL) {
1833 base = pcmk__env_option(PCMK__ENV_SCHEMA_DIRECTORY);
1834 }
1835 if (pcmk__str_empty(base)) {
1836 base = PCMK_SCHEMA_DIR;
1837 }
1838
1839 switch (ns) {
1840 case pcmk__xml_artefact_ns_legacy_rng:
1841 case pcmk__xml_artefact_ns_legacy_xslt:
1842 ret = strdup(base);
1843 break;
1844 case pcmk__xml_artefact_ns_base_rng:
1845 case pcmk__xml_artefact_ns_base_xslt:
1846 ret = pcmk__assert_asprintf("%s/base", base);
1847 break;
1848 default:
1849 pcmk__err("XML artefact family specified as %u not recognized", ns);
1850 }
1851 return ret;
1852 }
1853
1854 static char *
1855 find_artefact(enum pcmk__xml_artefact_ns ns, const char *path, const char *filespec)
1856 {
1857 char *ret = NULL;
1858
1859 switch (ns) {
1860 case pcmk__xml_artefact_ns_legacy_rng:
1861 case pcmk__xml_artefact_ns_base_rng:
1862 if (g_str_has_suffix(filespec, ".rng")) {
1863 ret = pcmk__assert_asprintf("%s/%s", path, filespec);
1864 } else {
1865 ret = pcmk__assert_asprintf("%s/%s.rng", path, filespec);
1866 }
1867 break;
1868 case pcmk__xml_artefact_ns_legacy_xslt:
1869 case pcmk__xml_artefact_ns_base_xslt:
1870 if (g_str_has_suffix(filespec, ".xsl")) {
1871 ret = pcmk__assert_asprintf("%s/%s", path, filespec);
1872 } else {
1873 ret = pcmk__assert_asprintf("%s/%s.xsl", path, filespec);
1874 }
1875 break;
1876 default:
1877 pcmk__err("XML artefact family specified as %u not recognized", ns);
1878 }
1879
1880 return ret;
1881 }
1882
1883 char *
1884 pcmk__xml_artefact_path(enum pcmk__xml_artefact_ns ns, const char *filespec)
1885 {
1886 struct stat sb;
1887 char *base = pcmk__xml_artefact_root(ns);
1888 char *ret = NULL;
1889
1890 ret = find_artefact(ns, base, filespec);
1891 free(base);
1892
1893 if (stat(ret, &sb) != 0 || !S_ISREG(sb.st_mode)) {
1894 const char *remote_schema_dir = pcmk__remote_schema_dir();
1895
1896 free(ret);
1897 ret = find_artefact(ns, remote_schema_dir, filespec);
1898 }
1899
1900 return ret;
1901 }
1902
1903 // Deprecated functions kept only for backward API compatibility
1904 // LCOV_EXCL_START
1905
1906 #include <libxml/parser.h> // xmlCleanupParser()
1907
1908 #include <crm/common/xml_compat.h>
1909
1910 xmlNode *
1911 copy_xml(xmlNode *src)
1912 {
1913 xmlDoc *doc = pcmk__xml_new_doc();
1914 xmlNode *copy = NULL;
1915
1916 copy = xmlDocCopyNode(src, doc, 1);
1917 pcmk__mem_assert(copy);
1918
1919 xmlDocSetRootElement(doc, copy);
1920 pcmk__xml_new_private_data(copy);
1921 return copy;
1922 }
1923
1924 void
1925 crm_xml_init(void)
1926 {
1927 pcmk__schema_init();
1928 }
1929
1930 void
1931 crm_xml_cleanup(void)
1932 {
1933 pcmk__schema_cleanup();
1934 xmlCleanupParser();
1935 }
1936
1937 void
1938 pcmk_free_xml_subtree(xmlNode *xml)
1939 {
1940 pcmk__xml_free_node(xml);
1941 }
1942
1943 void
1944 free_xml(xmlNode *child)
1945 {
1946 pcmk__xml_free(child);
1947 }
1948
1949 void
1950 crm_xml_sanitize_id(char *id)
1951 {
1952 char *c;
1953
1954 for (c = id; *c; ++c) {
1955 switch (*c) {
1956 case ':':
1957 case '#':
1958 *c = '.';
1959 }
1960 }
1961 }
1962
1963 bool
1964 xml_tracking_changes(xmlNode *xml)
1965 {
1966 return (xml != NULL)
1967 && pcmk__xml_doc_all_flags_set(xml->doc, pcmk__xf_tracking);
1968 }
1969
1970 bool
1971 xml_document_dirty(xmlNode *xml)
1972 {
1973 return (xml != NULL)
1974 && pcmk__xml_doc_all_flags_set(xml->doc, pcmk__xf_dirty);
1975 }
1976
1977 void
1978 xml_accept_changes(xmlNode *xml)
1979 {
1980 if (xml != NULL) {
1981 pcmk__xml_commit_changes(xml->doc);
1982 }
1983 }
1984
1985 void
1986 xml_track_changes(xmlNode *xml, const char *user, xmlNode *acl_source,
1987 bool enforce_acls)
1988 {
1989 if (xml == NULL) {
1990 return;
1991 }
1992
1993 pcmk__xml_commit_changes(xml->doc);
1994 pcmk__trace("Tracking changes%s to %p", (enforce_acls? " with ACLs" : ""),
1995 xml);
1996 pcmk__xml_doc_set_flags(xml->doc, pcmk__xf_tracking);
1997 if (enforce_acls) {
1998 if (acl_source == NULL) {
1999 acl_source = xml;
2000 }
2001 pcmk__xml_doc_set_flags(xml->doc, pcmk__xf_acl_enabled);
2002 pcmk__unpack_acls(acl_source->doc, xml->doc->_private, user);
2003 pcmk__apply_acls(xml->doc);
2004 }
2005 }
2006
2007 void
2008 xml_calculate_changes(xmlNode *old_xml, xmlNode *new_xml)
2009 {
2010 CRM_CHECK((old_xml != NULL) && (new_xml != NULL)
2011 && pcmk__xe_is(old_xml, (const char *) new_xml->name)
2012 && pcmk__str_eq(pcmk__xe_id(old_xml), pcmk__xe_id(new_xml),
2013 pcmk__str_none),
2014 return);
2015
2016 if (!pcmk__xml_doc_all_flags_set(new_xml->doc, pcmk__xf_tracking)) {
2017 // Ensure tracking has a clean start (pcmk__xml_mark_changes() enables)
2018 pcmk__xml_commit_changes(new_xml->doc);
2019 }
2020
2021 pcmk__xml_mark_changes(old_xml, new_xml);
2022 }
2023
2024 void
2025 xml_calculate_significant_changes(xmlNode *old_xml, xmlNode *new_xml)
2026 {
2027 CRM_CHECK((old_xml != NULL) && (new_xml != NULL)
2028 && pcmk__xe_is(old_xml, (const char *) new_xml->name)
2029 && pcmk__str_eq(pcmk__xe_id(old_xml), pcmk__xe_id(new_xml),
2030 pcmk__str_none),
2031 return);
2032
2033 /* BUG: If pcmk__xf_tracking is not set for new_xml when this function is
2034 * called, then we unset pcmk__xf_ignore_attr_pos via
2035 * pcmk__xml_commit_changes(). Since this function is about to be
2036 * deprecated, it's not worth fixing this and changing the user-facing
2037 * behavior.
2038 */
2039 pcmk__xml_doc_set_flags(new_xml->doc, pcmk__xf_ignore_attr_pos);
2040
2041 if (!pcmk__xml_doc_all_flags_set(new_xml->doc, pcmk__xf_tracking)) {
2042 // Ensure tracking has a clean start (pcmk__xml_mark_changes() enables)
2043 pcmk__xml_commit_changes(new_xml->doc);
2044 }
2045
2046 pcmk__xml_mark_changes(old_xml, new_xml);
2047 }
2048
2049 // LCOV_EXCL_STOP
2050 // End deprecated API
2051