1 // Copyright 2014 The Chromium OS Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "chromeos-dbus-bindings/xml_interface_parser.h"
6 
7 #include <utility>
8 
9 #include <base/files/file_path.h>
10 #include <base/files/file_util.h>
11 #include <base/logging.h>
12 #include <base/strings/string_util.h>
13 #include <brillo/strings/string_utils.h>
14 
15 using std::string;
16 using std::vector;
17 
18 namespace chromeos_dbus_bindings {
19 
20 // static
21 const char XmlInterfaceParser::kArgumentTag[] = "arg";
22 const char XmlInterfaceParser::kInterfaceTag[] = "interface";
23 const char XmlInterfaceParser::kMethodTag[] = "method";
24 const char XmlInterfaceParser::kNodeTag[] = "node";
25 const char XmlInterfaceParser::kSignalTag[] = "signal";
26 const char XmlInterfaceParser::kPropertyTag[] = "property";
27 const char XmlInterfaceParser::kAnnotationTag[] = "annotation";
28 const char XmlInterfaceParser::kDocStringTag[] = "tp:docstring";
29 const char XmlInterfaceParser::kNameAttribute[] = "name";
30 const char XmlInterfaceParser::kTypeAttribute[] = "type";
31 const char XmlInterfaceParser::kValueAttribute[] = "value";
32 const char XmlInterfaceParser::kDirectionAttribute[] = "direction";
33 const char XmlInterfaceParser::kAccessAttribute[] = "access";
34 const char XmlInterfaceParser::kArgumentDirectionIn[] = "in";
35 const char XmlInterfaceParser::kArgumentDirectionOut[] = "out";
36 
37 const char XmlInterfaceParser::kTrue[] = "true";
38 const char XmlInterfaceParser::kFalse[] = "false";
39 
40 const char XmlInterfaceParser::kMethodConst[] =
41     "org.chromium.DBus.Method.Const";
42 const char XmlInterfaceParser::kMethodAsync[] =
43     "org.freedesktop.DBus.GLib.Async";
44 const char XmlInterfaceParser::kMethodIncludeDBusMessage[] =
45     "org.chromium.DBus.Method.IncludeDBusMessage";
46 
47 const char XmlInterfaceParser::kMethodKind[] = "org.chromium.DBus.Method.Kind";
48 const char XmlInterfaceParser::kMethodKindSimple[] = "simple";
49 const char XmlInterfaceParser::kMethodKindNormal[] = "normal";
50 const char XmlInterfaceParser::kMethodKindAsync[] = "async";
51 const char XmlInterfaceParser::kMethodKindRaw[] = "raw";
52 
53 namespace {
54 
GetElementPath(const vector<string> & path)55 string GetElementPath(const vector<string>& path) {
56   return brillo::string_utils::Join("/", path);
57 }
58 
59 }  // anonymous namespace
60 
ParseXmlInterfaceFile(const std::string & contents,const std::vector<std::string> & ignore_interfaces)61 bool XmlInterfaceParser::ParseXmlInterfaceFile(
62     const std::string& contents,
63     const std::vector<std::string>& ignore_interfaces) {
64   auto parser = XML_ParserCreate(nullptr);
65   XML_SetUserData(parser, this);
66   XML_SetElementHandler(parser,
67                         &XmlInterfaceParser::HandleElementStart,
68                         &XmlInterfaceParser::HandleElementEnd);
69   XML_SetCharacterDataHandler(parser, &XmlInterfaceParser::HandleCharData);
70   const int kIsFinal = XML_TRUE;
71 
72   element_path_.clear();
73   XML_Status res = XML_Parse(parser,
74                              contents.c_str(),
75                              contents.size(),
76                              kIsFinal);
77   XML_ParserFree(parser);
78 
79   if (res != XML_STATUS_OK) {
80     LOG(ERROR) << "XML parse failure";
81     return false;
82   }
83 
84   CHECK(element_path_.empty());
85 
86   if (!ignore_interfaces.empty()) {
87     // Remove interfaces whose names are in |ignore_interfaces| list.
88     auto condition = [&ignore_interfaces](const Interface& itf) {
89       return std::find(ignore_interfaces.begin(), ignore_interfaces.end(),
90                        itf.name) != ignore_interfaces.end();
91     };
92     auto p = std::remove_if(interfaces_.begin(), interfaces_.end(), condition);
93     interfaces_.erase(p, interfaces_.end());
94   }
95   return true;
96 }
97 
OnOpenElement(const string & element_name,const XmlAttributeMap & attributes)98 void XmlInterfaceParser::OnOpenElement(
99     const string& element_name, const XmlAttributeMap& attributes) {
100   string prev_element;
101   if (!element_path_.empty())
102     prev_element = element_path_.back();
103   element_path_.push_back(element_name);
104   if (element_name == kNodeTag) {
105     CHECK(prev_element.empty() || prev_element == kNodeTag)
106         << "Unexpected tag " << element_name << " inside " << prev_element;
107     // 'name' attribute is optional for <node> element.
108     string name;
109     GetElementAttribute(attributes, element_path_, kNameAttribute, &name);
110     base::TrimWhitespaceASCII(name, base::TRIM_ALL, &name);
111     node_names_.push_back(name);
112   } else if (element_name == kInterfaceTag) {
113     CHECK_EQ(kNodeTag, prev_element)
114         << "Unexpected tag " << element_name << " inside " << prev_element;
115     string interface_name = GetValidatedElementName(attributes, element_path_);
116     Interface itf(interface_name,
117                   std::vector<Interface::Method>{},
118                   std::vector<Interface::Signal>{},
119                   std::vector<Interface::Property>{});
120     itf.path = node_names_.back();
121     interfaces_.push_back(std::move(itf));
122   } else if (element_name == kMethodTag) {
123     CHECK_EQ(kInterfaceTag, prev_element)
124         << "Unexpected tag " << element_name << " inside " << prev_element;
125     interfaces_.back().methods.push_back(
126         Interface::Method(GetValidatedElementName(attributes, element_path_)));
127   } else if (element_name == kSignalTag) {
128     CHECK_EQ(kInterfaceTag, prev_element)
129         << "Unexpected tag " << element_name << " inside " << prev_element;
130     interfaces_.back().signals.push_back(
131         Interface::Signal(GetValidatedElementName(attributes, element_path_)));
132   } else if (element_name == kPropertyTag) {
133     CHECK_EQ(kInterfaceTag, prev_element)
134         << "Unexpected tag " << element_name << " inside " << prev_element;
135     interfaces_.back().properties.push_back(ParseProperty(attributes,
136                                                           element_path_));
137   } else if (element_name == kArgumentTag) {
138     if (prev_element == kMethodTag) {
139       AddMethodArgument(attributes);
140     } else if (prev_element == kSignalTag) {
141       AddSignalArgument(attributes);
142     } else {
143       LOG(FATAL) << "Unexpected tag " << element_name
144                  << " inside " << prev_element;
145     }
146   } else if (element_name == kAnnotationTag) {
147     string name = GetValidatedElementAttribute(attributes, element_path_,
148                                                kNameAttribute);
149     // Value is optional. Default to empty string if omitted.
150     string value;
151     GetElementAttribute(attributes, element_path_, kValueAttribute, &value);
152     if (prev_element == kInterfaceTag) {
153       // Parse interface annotations...
154     } else if (prev_element == kMethodTag) {
155       // Parse method annotations...
156       Interface::Method& method = interfaces_.back().methods.back();
157       if (name == kMethodConst) {
158         CHECK(value == kTrue || value == kFalse);
159         method.is_const = (value == kTrue);
160       } else if (name == kMethodIncludeDBusMessage) {
161         CHECK(value == kTrue || value == kFalse);
162         method.include_dbus_message = (value == kTrue);
163       } else if (name == kMethodAsync) {
164         // Support GLib.Async annotation as well.
165         method.kind = Interface::Method::Kind::kAsync;
166       } else if (name == kMethodKind) {
167         if (value == kMethodKindSimple) {
168           method.kind = Interface::Method::Kind::kSimple;
169         } else if (value == kMethodKindNormal) {
170           method.kind = Interface::Method::Kind::kNormal;
171         } else if (value == kMethodKindAsync) {
172           method.kind = Interface::Method::Kind::kAsync;
173         } else if (value == kMethodKindRaw) {
174           method.kind = Interface::Method::Kind::kRaw;
175         } else {
176           LOG(FATAL) << "Invalid method kind: " << value;
177         }
178       }
179     } else if (prev_element == kSignalTag) {
180       // Parse signal annotations...
181     } else if (prev_element == kPropertyTag) {
182       // Parse property annotations...
183     } else {
184       LOG(FATAL) << "Unexpected tag " << element_name
185                  << " inside " << prev_element;
186     }
187   } else if (element_name == kDocStringTag) {
188     CHECK(!prev_element.empty() && prev_element != kNodeTag)
189         << "Unexpected tag " << element_name << " inside " << prev_element;
190   }
191 }
192 
OnCharData(const std::string & content)193 void XmlInterfaceParser::OnCharData(const std::string& content) {
194   // Handle the text data only for <tp:docstring> element.
195   if (element_path_.back() != kDocStringTag)
196     return;
197 
198   CHECK_GT(element_path_.size(), 1u);
199   string* doc_string_ptr = nullptr;
200   string target_element = element_path_[element_path_.size() - 2];
201   if (target_element == kInterfaceTag) {
202     doc_string_ptr = &(interfaces_.back().doc_string);
203   } else if (target_element == kMethodTag) {
204     doc_string_ptr = &(interfaces_.back().methods.back().doc_string);
205   } else if (target_element == kSignalTag) {
206     doc_string_ptr = &(interfaces_.back().signals.back().doc_string);
207   } else if (target_element == kPropertyTag) {
208     doc_string_ptr = &(interfaces_.back().properties.back().doc_string);
209   }
210 
211   // If <tp:docstring> is attached to elements we don't care about, do nothing.
212   if (doc_string_ptr == nullptr)
213     return;
214 
215   (*doc_string_ptr) += content;
216 }
217 
218 
AddMethodArgument(const XmlAttributeMap & attributes)219 void XmlInterfaceParser::AddMethodArgument(const XmlAttributeMap& attributes) {
220   string argument_direction;
221   vector<string> path = element_path_;
222   path.push_back(kArgumentTag);
223   bool is_direction_paramter_present = GetElementAttribute(
224       attributes, path, kDirectionAttribute, &argument_direction);
225   vector<Interface::Argument>* argument_list = nullptr;
226   if (!is_direction_paramter_present ||
227       argument_direction == kArgumentDirectionIn) {
228     argument_list = &interfaces_.back().methods.back().input_arguments;
229   } else if (argument_direction == kArgumentDirectionOut) {
230     argument_list = &interfaces_.back().methods.back().output_arguments;
231   } else {
232     LOG(FATAL) << "Unknown method argument direction " << argument_direction;
233   }
234   argument_list->push_back(ParseArgument(attributes, element_path_));
235 }
236 
AddSignalArgument(const XmlAttributeMap & attributes)237 void XmlInterfaceParser::AddSignalArgument(const XmlAttributeMap& attributes) {
238   interfaces_.back().signals.back().arguments.push_back(
239       ParseArgument(attributes, element_path_));
240 }
241 
OnCloseElement(const string & element_name)242 void XmlInterfaceParser::OnCloseElement(const string& element_name) {
243   VLOG(1) << "Close Element " << element_name;
244   CHECK(!element_path_.empty());
245   CHECK_EQ(element_path_.back(), element_name);
246   element_path_.pop_back();
247   if (element_name == kNodeTag) {
248     CHECK(!node_names_.empty());
249     node_names_.pop_back();
250   }
251 }
252 
253 // static
GetElementAttribute(const XmlAttributeMap & attributes,const vector<string> & element_path,const string & element_key,string * element_value)254 bool XmlInterfaceParser::GetElementAttribute(
255     const XmlAttributeMap& attributes,
256     const vector<string>& element_path,
257     const string& element_key,
258     string* element_value) {
259   if (attributes.find(element_key) == attributes.end()) {
260     return false;
261   }
262   *element_value = attributes.find(element_key)->second;
263   VLOG(1) << "Got " << GetElementPath(element_path) << " element with "
264           << element_key << " = " << *element_value;
265   return true;
266 }
267 
268 // static
GetValidatedElementAttribute(const XmlAttributeMap & attributes,const vector<string> & element_path,const string & element_key)269 string XmlInterfaceParser::GetValidatedElementAttribute(
270     const XmlAttributeMap& attributes,
271     const vector<string>& element_path,
272     const string& element_key) {
273   string element_value;
274   CHECK(GetElementAttribute(attributes,
275                             element_path,
276                             element_key,
277                             &element_value))
278       << GetElementPath(element_path) << " does not contain a " << element_key
279       << " attribute";
280   CHECK(!element_value.empty()) << GetElementPath(element_path) << " "
281                                 << element_key << " attribute is empty";
282   return element_value;
283 }
284 
285 // static
GetValidatedElementName(const XmlAttributeMap & attributes,const vector<string> & element_path)286 string XmlInterfaceParser::GetValidatedElementName(
287     const XmlAttributeMap& attributes,
288     const vector<string>& element_path) {
289   return GetValidatedElementAttribute(attributes, element_path, kNameAttribute);
290 }
291 
292 // static
ParseArgument(const XmlAttributeMap & attributes,const vector<string> & element_path)293 Interface::Argument XmlInterfaceParser::ParseArgument(
294     const XmlAttributeMap& attributes, const vector<string>& element_path) {
295   vector<string> path = element_path;
296   path.push_back(kArgumentTag);
297   string argument_name;
298   // Since the "name" field is optional, use the un-validated variant.
299   GetElementAttribute(attributes, path, kNameAttribute, &argument_name);
300 
301   string argument_type = GetValidatedElementAttribute(
302       attributes, path, kTypeAttribute);
303   return Interface::Argument(argument_name, argument_type);
304 }
305 
306 // static
ParseProperty(const XmlAttributeMap & attributes,const std::vector<std::string> & element_path)307 Interface::Property XmlInterfaceParser::ParseProperty(
308     const XmlAttributeMap& attributes,
309     const std::vector<std::string>& element_path) {
310   vector<string> path = element_path;
311   path.push_back(kPropertyTag);
312   string property_name = GetValidatedElementName(attributes, path);
313   string property_type = GetValidatedElementAttribute(attributes, path,
314                                                       kTypeAttribute);
315   string property_access = GetValidatedElementAttribute(attributes, path,
316                                                         kAccessAttribute);
317   return Interface::Property(property_name, property_type, property_access);
318 }
319 
320 // static
HandleElementStart(void * user_data,const XML_Char * element,const XML_Char ** attr)321 void XmlInterfaceParser::HandleElementStart(void* user_data,
322                                             const XML_Char* element,
323                                             const XML_Char** attr) {
324   XmlAttributeMap attributes;
325   if (attr != nullptr) {
326     for (size_t n = 0; attr[n] != nullptr && attr[n+1] != nullptr; n += 2) {
327       auto key = attr[n];
328       auto value = attr[n + 1];
329       attributes.insert(std::make_pair(key, value));
330     }
331   }
332   auto parser = reinterpret_cast<XmlInterfaceParser*>(user_data);
333   parser->OnOpenElement(element, attributes);
334 }
335 
336 // static
HandleElementEnd(void * user_data,const XML_Char * element)337 void XmlInterfaceParser::HandleElementEnd(void* user_data,
338                                           const XML_Char* element) {
339   auto parser = reinterpret_cast<XmlInterfaceParser*>(user_data);
340   parser->OnCloseElement(element);
341 }
342 
343 // static
HandleCharData(void * user_data,const char * content,int length)344 void XmlInterfaceParser::HandleCharData(void* user_data,
345                                         const char *content,
346                                         int length) {
347   auto parser = reinterpret_cast<XmlInterfaceParser*>(user_data);
348   parser->OnCharData(string(content, length));
349 }
350 
351 }  // namespace chromeos_dbus_bindings
352