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