1 //===-- XML.cpp -----------------------------------------------------------===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8 
9 #include "lldb/Host/Config.h"
10 #include "lldb/Host/XML.h"
11 
12 using namespace lldb;
13 using namespace lldb_private;
14 
15 #pragma mark-- XMLDocument
16 
17 XMLDocument::XMLDocument() = default;
18 
19 XMLDocument::~XMLDocument() { Clear(); }
20 
21 void XMLDocument::Clear() {
22 #if LLDB_ENABLE_LIBXML2
23   if (m_document) {
24     xmlDocPtr doc = m_document;
25     m_document = nullptr;
26     xmlFreeDoc(doc);
27   }
28 #endif
29 }
30 
31 bool XMLDocument::IsValid() const { return m_document != nullptr; }
32 
33 void XMLDocument::ErrorCallback(void *ctx, const char *format, ...) {
34   XMLDocument *document = (XMLDocument *)ctx;
35   va_list args;
36   va_start(args, format);
37   document->m_errors.PrintfVarArg(format, args);
38   document->m_errors.EOL();
39   va_end(args);
40 }
41 
42 bool XMLDocument::ParseFile(const char *path) {
43 #if LLDB_ENABLE_LIBXML2
44   Clear();
45   xmlSetGenericErrorFunc((void *)this, XMLDocument::ErrorCallback);
46   m_document = xmlParseFile(path);
47   xmlSetGenericErrorFunc(nullptr, nullptr);
48 #endif
49   return IsValid();
50 }
51 
52 bool XMLDocument::ParseMemory(const char *xml, size_t xml_length,
53                               const char *url) {
54 #if LLDB_ENABLE_LIBXML2
55   Clear();
56   xmlSetGenericErrorFunc((void *)this, XMLDocument::ErrorCallback);
57   m_document = xmlReadMemory(xml, (int)xml_length, url, nullptr, 0);
58   xmlSetGenericErrorFunc(nullptr, nullptr);
59 #endif
60   return IsValid();
61 }
62 
63 XMLNode XMLDocument::GetRootElement(const char *required_name) {
64 #if LLDB_ENABLE_LIBXML2
65   if (IsValid()) {
66     XMLNode root_node(xmlDocGetRootElement(m_document));
67     if (required_name) {
68       llvm::StringRef actual_name = root_node.GetName();
69       if (actual_name == required_name)
70         return root_node;
71     } else {
72       return root_node;
73     }
74   }
75 #endif
76   return XMLNode();
77 }
78 
79 llvm::StringRef XMLDocument::GetErrors() const { return m_errors.GetString(); }
80 
81 bool XMLDocument::XMLEnabled() {
82 #if LLDB_ENABLE_LIBXML2
83   return true;
84 #else
85   return false;
86 #endif
87 }
88 
89 #pragma mark-- XMLNode
90 
91 XMLNode::XMLNode() = default;
92 
93 XMLNode::XMLNode(XMLNodeImpl node) : m_node(node) {}
94 
95 XMLNode::~XMLNode() = default;
96 
97 void XMLNode::Clear() { m_node = nullptr; }
98 
99 XMLNode XMLNode::GetParent() const {
100 #if LLDB_ENABLE_LIBXML2
101   if (IsValid())
102     return XMLNode(m_node->parent);
103   else
104     return XMLNode();
105 #else
106   return XMLNode();
107 #endif
108 }
109 
110 XMLNode XMLNode::GetSibling() const {
111 #if LLDB_ENABLE_LIBXML2
112   if (IsValid())
113     return XMLNode(m_node->next);
114   else
115     return XMLNode();
116 #else
117   return XMLNode();
118 #endif
119 }
120 
121 XMLNode XMLNode::GetChild() const {
122 #if LLDB_ENABLE_LIBXML2
123 
124   if (IsValid())
125     return XMLNode(m_node->children);
126   else
127     return XMLNode();
128 #else
129   return XMLNode();
130 #endif
131 }
132 
133 std::string XMLNode::GetAttributeValue(const char *name,
134                                        const char *fail_value) const {
135   std::string attr_value;
136 #if LLDB_ENABLE_LIBXML2
137   if (IsValid()) {
138     xmlChar *value = xmlGetProp(m_node, (const xmlChar *)name);
139     if (value) {
140       attr_value = (const char *)value;
141       xmlFree(value);
142     }
143   } else {
144     if (fail_value)
145       attr_value = fail_value;
146   }
147 #else
148   if (fail_value)
149     attr_value = fail_value;
150 #endif
151   return attr_value;
152 }
153 
154 bool XMLNode::GetAttributeValueAsUnsigned(const char *name, uint64_t &value,
155                                           uint64_t fail_value, int base) const {
156   value = fail_value;
157   return llvm::to_integer(GetAttributeValue(name, ""), value, base);
158 }
159 
160 void XMLNode::ForEachChildNode(NodeCallback const &callback) const {
161 #if LLDB_ENABLE_LIBXML2
162   if (IsValid())
163     GetChild().ForEachSiblingNode(callback);
164 #endif
165 }
166 
167 void XMLNode::ForEachChildElement(NodeCallback const &callback) const {
168 #if LLDB_ENABLE_LIBXML2
169   XMLNode child = GetChild();
170   if (child)
171     child.ForEachSiblingElement(callback);
172 #endif
173 }
174 
175 void XMLNode::ForEachChildElementWithName(const char *name,
176                                           NodeCallback const &callback) const {
177 #if LLDB_ENABLE_LIBXML2
178   XMLNode child = GetChild();
179   if (child)
180     child.ForEachSiblingElementWithName(name, callback);
181 #endif
182 }
183 
184 void XMLNode::ForEachAttribute(AttributeCallback const &callback) const {
185 #if LLDB_ENABLE_LIBXML2
186 
187   if (IsValid()) {
188     for (xmlAttrPtr attr = m_node->properties; attr != nullptr;
189          attr = attr->next) {
190       // check if name matches
191       if (attr->name) {
192         // check child is a text node
193         xmlNodePtr child = attr->children;
194         if (child->type == XML_TEXT_NODE) {
195           llvm::StringRef attr_value;
196           if (child->content)
197             attr_value = llvm::StringRef((const char *)child->content);
198           if (!callback(llvm::StringRef((const char *)attr->name), attr_value))
199             return;
200         }
201       }
202     }
203   }
204 #endif
205 }
206 
207 void XMLNode::ForEachSiblingNode(NodeCallback const &callback) const {
208 #if LLDB_ENABLE_LIBXML2
209 
210   if (IsValid()) {
211     // iterate through all siblings
212     for (xmlNodePtr node = m_node; node; node = node->next) {
213       if (!callback(XMLNode(node)))
214         return;
215     }
216   }
217 #endif
218 }
219 
220 void XMLNode::ForEachSiblingElement(NodeCallback const &callback) const {
221 #if LLDB_ENABLE_LIBXML2
222 
223   if (IsValid()) {
224     // iterate through all siblings
225     for (xmlNodePtr node = m_node; node; node = node->next) {
226       // we are looking for element nodes only
227       if (node->type != XML_ELEMENT_NODE)
228         continue;
229 
230       if (!callback(XMLNode(node)))
231         return;
232     }
233   }
234 #endif
235 }
236 
237 void XMLNode::ForEachSiblingElementWithName(
238     const char *name, NodeCallback const &callback) const {
239 #if LLDB_ENABLE_LIBXML2
240 
241   if (IsValid()) {
242     // iterate through all siblings
243     for (xmlNodePtr node = m_node; node; node = node->next) {
244       // we are looking for element nodes only
245       if (node->type != XML_ELEMENT_NODE)
246         continue;
247 
248       // If name is nullptr, we take all nodes of type "t", else just the ones
249       // whose name matches
250       if (name) {
251         if (strcmp((const char *)node->name, name) != 0)
252           continue; // Name mismatch, ignore this one
253       } else {
254         if (node->name)
255           continue; // nullptr name specified and this element has a name,
256                     // ignore this one
257       }
258 
259       if (!callback(XMLNode(node)))
260         return;
261     }
262   }
263 #endif
264 }
265 
266 llvm::StringRef XMLNode::GetName() const {
267 #if LLDB_ENABLE_LIBXML2
268   if (IsValid()) {
269     if (m_node->name)
270       return llvm::StringRef((const char *)m_node->name);
271   }
272 #endif
273   return llvm::StringRef();
274 }
275 
276 bool XMLNode::GetElementText(std::string &text) const {
277   text.clear();
278 #if LLDB_ENABLE_LIBXML2
279   if (IsValid()) {
280     bool success = false;
281     if (m_node->type == XML_ELEMENT_NODE) {
282       // check child is a text node
283       for (xmlNodePtr node = m_node->children; node != nullptr;
284            node = node->next) {
285         if (node->type == XML_TEXT_NODE) {
286           text.append((const char *)node->content);
287           success = true;
288         }
289       }
290     }
291     return success;
292   }
293 #endif
294   return false;
295 }
296 
297 bool XMLNode::GetElementTextAsUnsigned(uint64_t &value, uint64_t fail_value,
298                                        int base) const {
299   std::string text;
300 
301   value = fail_value;
302   return GetElementText(text) && llvm::to_integer(text, value, base);
303 }
304 
305 bool XMLNode::GetElementTextAsFloat(double &value, double fail_value) const {
306   std::string text;
307 
308   value = fail_value;
309   return GetElementText(text) && llvm::to_float(text, value);
310 }
311 
312 bool XMLNode::NameIs(const char *name) const {
313 #if LLDB_ENABLE_LIBXML2
314 
315   if (IsValid()) {
316     // In case we are looking for a nullptr name or an exact pointer match
317     if (m_node->name == (const xmlChar *)name)
318       return true;
319     if (m_node->name)
320       return strcmp((const char *)m_node->name, name) == 0;
321   }
322 #endif
323   return false;
324 }
325 
326 XMLNode XMLNode::FindFirstChildElementWithName(const char *name) const {
327   XMLNode result_node;
328 
329 #if LLDB_ENABLE_LIBXML2
330   ForEachChildElementWithName(
331       name, [&result_node](const XMLNode &node) -> bool {
332         result_node = node;
333         // Stop iterating, we found the node we wanted
334         return false;
335       });
336 #endif
337 
338   return result_node;
339 }
340 
341 bool XMLNode::IsValid() const { return m_node != nullptr; }
342 
343 bool XMLNode::IsElement() const {
344 #if LLDB_ENABLE_LIBXML2
345   if (IsValid())
346     return m_node->type == XML_ELEMENT_NODE;
347 #endif
348   return false;
349 }
350 
351 XMLNode XMLNode::GetElementForPath(const NamePath &path) {
352 #if LLDB_ENABLE_LIBXML2
353 
354   if (IsValid()) {
355     if (path.empty())
356       return *this;
357     else {
358       XMLNode node = FindFirstChildElementWithName(path[0].c_str());
359       const size_t n = path.size();
360       for (size_t i = 1; node && i < n; ++i)
361         node = node.FindFirstChildElementWithName(path[i].c_str());
362       return node;
363     }
364   }
365 #endif
366 
367   return XMLNode();
368 }
369 
370 #pragma mark-- ApplePropertyList
371 
372 ApplePropertyList::ApplePropertyList() : m_xml_doc(), m_dict_node() {}
373 
374 ApplePropertyList::ApplePropertyList(const char *path)
375     : m_xml_doc(), m_dict_node() {
376   ParseFile(path);
377 }
378 
379 ApplePropertyList::~ApplePropertyList() = default;
380 
381 llvm::StringRef ApplePropertyList::GetErrors() const {
382   return m_xml_doc.GetErrors();
383 }
384 
385 bool ApplePropertyList::ParseFile(const char *path) {
386   if (m_xml_doc.ParseFile(path)) {
387     XMLNode plist = m_xml_doc.GetRootElement("plist");
388     if (plist) {
389       plist.ForEachChildElementWithName("dict",
390                                         [this](const XMLNode &dict) -> bool {
391                                           this->m_dict_node = dict;
392                                           return false; // Stop iterating
393                                         });
394       return (bool)m_dict_node;
395     }
396   }
397   return false;
398 }
399 
400 bool ApplePropertyList::IsValid() const { return (bool)m_dict_node; }
401 
402 bool ApplePropertyList::GetValueAsString(const char *key,
403                                          std::string &value) const {
404   XMLNode value_node = GetValueNode(key);
405   if (value_node)
406     return ApplePropertyList::ExtractStringFromValueNode(value_node, value);
407   return false;
408 }
409 
410 XMLNode ApplePropertyList::GetValueNode(const char *key) const {
411   XMLNode value_node;
412 #if LLDB_ENABLE_LIBXML2
413 
414   if (IsValid()) {
415     m_dict_node.ForEachChildElementWithName(
416         "key", [key, &value_node](const XMLNode &key_node) -> bool {
417           std::string key_name;
418           if (key_node.GetElementText(key_name)) {
419             if (key_name == key) {
420               value_node = key_node.GetSibling();
421               while (value_node && !value_node.IsElement())
422                 value_node = value_node.GetSibling();
423               return false; // Stop iterating
424             }
425           }
426           return true; // Keep iterating
427         });
428   }
429 #endif
430   return value_node;
431 }
432 
433 bool ApplePropertyList::ExtractStringFromValueNode(const XMLNode &node,
434                                                    std::string &value) {
435   value.clear();
436 #if LLDB_ENABLE_LIBXML2
437   if (node.IsValid()) {
438     llvm::StringRef element_name = node.GetName();
439     if (element_name == "true" || element_name == "false") {
440       // The text value _is_ the element name itself...
441       value = element_name.str();
442       return true;
443     } else if (element_name == "dict" || element_name == "array")
444       return false; // dictionaries and arrays have no text value, so we fail
445     else
446       return node.GetElementText(value);
447   }
448 #endif
449   return false;
450 }
451 
452 #if LLDB_ENABLE_LIBXML2
453 
454 static StructuredData::ObjectSP CreatePlistValue(XMLNode node) {
455   llvm::StringRef element_name = node.GetName();
456   if (element_name == "array") {
457     std::shared_ptr<StructuredData::Array> array_sp(
458         new StructuredData::Array());
459     node.ForEachChildElement([&array_sp](const XMLNode &node) -> bool {
460       array_sp->AddItem(CreatePlistValue(node));
461       return true; // Keep iterating through all child elements of the array
462     });
463     return array_sp;
464   } else if (element_name == "dict") {
465     XMLNode key_node;
466     std::shared_ptr<StructuredData::Dictionary> dict_sp(
467         new StructuredData::Dictionary());
468     node.ForEachChildElement(
469         [&key_node, &dict_sp](const XMLNode &node) -> bool {
470           if (node.NameIs("key")) {
471             // This is a "key" element node
472             key_node = node;
473           } else {
474             // This is a value node
475             if (key_node) {
476               std::string key_name;
477               key_node.GetElementText(key_name);
478               dict_sp->AddItem(key_name, CreatePlistValue(node));
479               key_node.Clear();
480             }
481           }
482           return true; // Keep iterating through all child elements of the
483                        // dictionary
484         });
485     return dict_sp;
486   } else if (element_name == "real") {
487     double value = 0.0;
488     node.GetElementTextAsFloat(value);
489     return StructuredData::ObjectSP(new StructuredData::Float(value));
490   } else if (element_name == "integer") {
491     uint64_t value = 0;
492     node.GetElementTextAsUnsigned(value, 0, 0);
493     return StructuredData::ObjectSP(new StructuredData::Integer(value));
494   } else if ((element_name == "string") || (element_name == "data") ||
495              (element_name == "date")) {
496     std::string text;
497     node.GetElementText(text);
498     return StructuredData::ObjectSP(
499         new StructuredData::String(std::move(text)));
500   } else if (element_name == "true") {
501     return StructuredData::ObjectSP(new StructuredData::Boolean(true));
502   } else if (element_name == "false") {
503     return StructuredData::ObjectSP(new StructuredData::Boolean(false));
504   }
505   return StructuredData::ObjectSP(new StructuredData::Null());
506 }
507 #endif
508 
509 StructuredData::ObjectSP ApplePropertyList::GetStructuredData() {
510   StructuredData::ObjectSP root_sp;
511 #if LLDB_ENABLE_LIBXML2
512   if (IsValid()) {
513     return CreatePlistValue(m_dict_node);
514   }
515 #endif
516   return root_sp;
517 }
518