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