xref: /openbmc/bmcweb/include/openbmc_dbus_rest.hpp (revision 4859bdbafbb2b78ae414fb050ad197db2f2cb28b)
1 #include <crow/app.h>
2 
3 #include <tinyxml2.h>
4 #include <dbus_singleton.hpp>
5 #include <experimental/filesystem>
6 #include <fstream>
7 #include <boost/algorithm/string.hpp>
8 #include <boost/container/flat_set.hpp>
9 
10 namespace crow {
11 namespace openbmc_mapper {
12 
13 void introspectObjects(crow::Response &res, std::string process_name,
14                        std::string path,
15                        std::shared_ptr<nlohmann::json> transaction) {
16   crow::connections::systemBus->async_method_call(
17       [
18         &res, transaction, processName{std::move(process_name)},
19         objectPath{std::move(path)}
20       ](const boost::system::error_code ec, const std::string &introspect_xml) {
21         if (ec) {
22           BMCWEB_LOG_ERROR << "Introspect call failed with error: "
23                            << ec.message() << " on process: " << processName
24                            << " path: " << objectPath << "\n";
25 
26         } else {
27           transaction->push_back({{"path", objectPath}});
28 
29           tinyxml2::XMLDocument doc;
30 
31           doc.Parse(introspect_xml.c_str());
32           tinyxml2::XMLNode *pRoot = doc.FirstChildElement("node");
33           if (pRoot == nullptr) {
34             BMCWEB_LOG_ERROR << "XML document failed to parse " << processName
35                              << " " << objectPath << "\n";
36 
37           } else {
38             tinyxml2::XMLElement *node = pRoot->FirstChildElement("node");
39             while (node != nullptr) {
40               std::string childPath = node->Attribute("name");
41               std::string newpath;
42               if (objectPath != "/") {
43                 newpath += objectPath;
44               }
45               newpath += "/" + childPath;
46               // introspect the subobjects as well
47               introspectObjects(res, processName, newpath, transaction);
48 
49               node = node->NextSiblingElement("node");
50             }
51           }
52         }
53         // if we're the last outstanding caller, finish the request
54         if (transaction.use_count() == 1) {
55           res.jsonValue = {{"status", "ok"},
56                            {"bus_name", processName},
57                            {"objects", std::move(*transaction)}};
58           res.end();
59         }
60       },
61       process_name, path, "org.freedesktop.DBus.Introspectable", "Introspect");
62 }
63 
64 // A smattering of common types to unpack.  TODO(ed) this should really iterate
65 // the sdbusplus object directly and build the json response
66 using DbusRestVariantType = sdbusplus::message::variant<
67     std::vector<std::tuple<std::string, std::string, std::string>>, std::string,
68     int64_t, uint64_t, double, int32_t, uint32_t, int16_t, uint16_t, uint8_t,
69     bool>;
70 
71 using ManagedObjectType = std::vector<std::pair<
72     sdbusplus::message::object_path,
73     boost::container::flat_map<
74         std::string,
75         boost::container::flat_map<std::string, DbusRestVariantType>>>>;
76 
77 void getManagedObjectsForEnumerate(
78     const std::string &object_name, const std::string &connection_name,
79     crow::Response &res, std::shared_ptr<nlohmann::json> transaction) {
80   crow::connections::systemBus->async_method_call(
81       [&res, transaction](const boost::system::error_code ec,
82                           const ManagedObjectType &objects) {
83         if (ec) {
84           BMCWEB_LOG_ERROR << ec;
85         } else {
86           nlohmann::json &dataJson = *transaction;
87 
88           for (auto &objectPath : objects) {
89             BMCWEB_LOG_DEBUG
90                 << "Reading object "
91                 << static_cast<const std::string &>(objectPath.first);
92             nlohmann::json &objectJson =
93                 dataJson[static_cast<const std::string &>(objectPath.first)];
94             if (objectJson.is_null()) {
95               objectJson = nlohmann::json::object();
96             }
97             for (const auto &interface : objectPath.second) {
98               for (const auto &property : interface.second) {
99                 nlohmann::json &propertyJson = objectJson[property.first];
100                 mapbox::util::apply_visitor(
101                     [&propertyJson](auto &&val) { propertyJson = val; },
102                     property.second);
103 
104                 // dbus-rest represents booleans as 1 or 0, implement to match
105                 // TODO(ed) see if dbus-rest should be changed
106                 const bool *propertyBool = propertyJson.get_ptr<const bool *>();
107                 if (propertyBool != nullptr) {
108                   propertyJson = *propertyBool ? 1 : 0;
109                 }
110               }
111             }
112           }
113         }
114 
115         if (transaction.use_count() == 1) {
116           res.jsonValue = {{"message", "200 OK"},
117                            {"status", "ok"},
118                            {"data", std::move(*transaction)}};
119           res.end();
120         }
121       },
122       connection_name, object_name, "org.freedesktop.DBus.ObjectManager",
123       "GetManagedObjects");
124 }
125 
126 using GetSubTreeType = std::vector<
127     std::pair<std::string,
128               std::vector<std::pair<std::string, std::vector<std::string>>>>>;
129 
130 // Structure for storing data on an in progress action
131 struct InProgressActionData {
132   InProgressActionData(crow::Response &res) : res(res){};
133   ~InProgressActionData() {
134     if (res.result() == boost::beast::http::status::internal_server_error) {
135       // Reset the json object to clear out any data that made it in before the
136       // error happened
137       // todo(ed) handle error condition with proper code
138       res.jsonValue = nlohmann::json::object();
139     }
140     res.end();
141   }
142 
143   void setErrorStatus() {
144     res.result(boost::beast::http::status::internal_server_error);
145   }
146   crow::Response &res;
147   std::string path;
148   std::string methodName;
149   nlohmann::json arguments;
150 };
151 
152 std::vector<std::string> dbusArgSplit(const std::string &string) {
153   std::vector<std::string> ret;
154   if (string.empty()) {
155     return ret;
156   }
157   ret.push_back("");
158   int containerDepth = 0;
159 
160   for (std::string::const_iterator character = string.begin();
161        character != string.end(); character++) {
162     ret.back() += *character;
163     switch (*character) {
164       case ('a'):
165         break;
166       case ('('):
167       case ('{'):
168         containerDepth++;
169         break;
170       case ('}'):
171       case (')'):
172         containerDepth--;
173         if (containerDepth == 0) {
174           if (character + 1 != string.end()) {
175             ret.push_back("");
176           }
177         }
178         break;
179       default:
180         if (containerDepth == 0) {
181           if (character + 1 != string.end()) {
182             ret.push_back("");
183           }
184         }
185         break;
186     }
187   }
188 }
189 
190 int convertJsonToDbus(sd_bus_message *m, const std::string &arg_type,
191                       const nlohmann::json &input_json) {
192   int r = 0;
193   BMCWEB_LOG_DEBUG << "Converting " << input_json.dump()
194                    << " to type: " << arg_type;
195   const std::vector<std::string> argTypes = dbusArgSplit(arg_type);
196 
197   // Assume a single object for now.
198   const nlohmann::json *j = &input_json;
199   nlohmann::json::const_iterator jIt = input_json.begin();
200 
201   for (const std::string &arg_code : argTypes) {
202     // If we are decoding multiple objects, grab the pointer to the iterator,
203     // and increment it for the next loop
204     if (argTypes.size() > 1) {
205       if (jIt == input_json.end()) {
206         return -2;
207       }
208       j = &*jIt;
209       jIt++;
210     }
211     const int64_t *int_value = j->get_ptr<const int64_t *>();
212     const uint64_t *uint_value = j->get_ptr<const uint64_t *>();
213     const std::string *string_value = j->get_ptr<const std::string *>();
214     const double *double_value = j->get_ptr<const double *>();
215     const bool *b = j->get_ptr<const bool *>();
216     int64_t v = 0;
217     double d = 0.0;
218 
219     // Do some basic type conversions that make sense.  uint can be converted to
220     // int.  int and uint can be converted to double
221     if (uint_value != nullptr && int_value == nullptr) {
222       v = static_cast<int64_t>(*uint_value);
223       int_value = &v;
224     }
225     if (uint_value != nullptr && double_value == nullptr) {
226       d = static_cast<double>(*uint_value);
227       double_value = &d;
228     }
229     if (int_value != nullptr && double_value == nullptr) {
230       d = static_cast<double>(*int_value);
231       double_value = &d;
232     }
233 
234     if (arg_code == "s") {
235       if (string_value == nullptr) {
236         return -1;
237       }
238       r = sd_bus_message_append_basic(m, arg_code[0],
239                                       (void *)string_value->c_str());
240       if (r < 0) {
241         return r;
242       }
243     } else if (arg_code == "i") {
244       if (int_value == nullptr) {
245         return -1;
246       }
247       int32_t i = static_cast<int32_t>(*int_value);
248       r = sd_bus_message_append_basic(m, arg_code[0], &i);
249       if (r < 0) {
250         return r;
251       }
252     } else if (arg_code == "b") {
253       // lots of ways bool could be represented here.  Try them all
254       int bool_int = false;
255       if (int_value != nullptr) {
256         bool_int = *int_value > 0 ? 1 : 0;
257       } else if (b != nullptr) {
258         bool_int = b ? 1 : 0;
259       } else if (string_value != nullptr) {
260         bool_int = boost::istarts_with(*string_value, "t") ? 1 : 0;
261       } else {
262         return -1;
263       }
264       r = sd_bus_message_append_basic(m, arg_code[0], &bool_int);
265       if (r < 0) {
266         return r;
267       }
268     } else if (arg_code == "n") {
269       if (int_value == nullptr) {
270         return -1;
271       }
272       int16_t n = static_cast<int16_t>(*int_value);
273       r = sd_bus_message_append_basic(m, arg_code[0], &n);
274       if (r < 0) {
275         return r;
276       }
277     } else if (arg_code == "x") {
278       if (int_value == nullptr) {
279         return -1;
280       }
281       r = sd_bus_message_append_basic(m, arg_code[0], int_value);
282       if (r < 0) {
283         return r;
284       }
285     } else if (arg_code == "y") {
286       if (uint_value == nullptr) {
287         return -1;
288       }
289       uint8_t y = static_cast<uint8_t>(*uint_value);
290       r = sd_bus_message_append_basic(m, arg_code[0], &y);
291     } else if (arg_code == "q") {
292       if (uint_value == nullptr) {
293         return -1;
294       }
295       uint16_t q = static_cast<uint16_t>(*uint_value);
296       r = sd_bus_message_append_basic(m, arg_code[0], &q);
297     } else if (arg_code == "u") {
298       if (uint_value == nullptr) {
299         return -1;
300       }
301       uint32_t u = static_cast<uint32_t>(*uint_value);
302       r = sd_bus_message_append_basic(m, arg_code[0], &u);
303     } else if (arg_code == "t") {
304       if (uint_value == nullptr) {
305         return -1;
306       }
307       r = sd_bus_message_append_basic(m, arg_code[0], uint_value);
308     } else if (arg_code == "d") {
309       sd_bus_message_append_basic(m, arg_code[0], double_value);
310     } else if (boost::starts_with(arg_code, "a")) {
311       std::string contained_type = arg_code.substr(1);
312       r = sd_bus_message_open_container(m, SD_BUS_TYPE_ARRAY,
313                                         contained_type.c_str());
314       if (r < 0) {
315         return r;
316       }
317 
318       for (nlohmann::json::const_iterator it = j->begin(); it != j->end();
319            ++it) {
320         r = convertJsonToDbus(m, contained_type, *it);
321         if (r < 0) {
322           return r;
323         }
324 
325         it++;
326       }
327       sd_bus_message_close_container(m);
328     } else if (boost::starts_with(arg_code, "v")) {
329       std::string contained_type = arg_code.substr(1);
330       BMCWEB_LOG_DEBUG << "variant type: " << arg_code
331                        << " appending variant of type: " << contained_type;
332       r = sd_bus_message_open_container(m, SD_BUS_TYPE_VARIANT,
333                                         contained_type.c_str());
334       if (r < 0) {
335         return r;
336       }
337 
338       r = convertJsonToDbus(m, contained_type, input_json);
339       if (r < 0) {
340         return r;
341       }
342 
343       r = sd_bus_message_close_container(m);
344       if (r < 0) {
345         return r;
346       }
347     } else if (boost::starts_with(arg_code, "(") &&
348                boost::ends_with(arg_code, ")")) {
349       std::string contained_type = arg_code.substr(1, arg_code.size() - 1);
350       r = sd_bus_message_open_container(m, SD_BUS_TYPE_STRUCT,
351                                         contained_type.c_str());
352       nlohmann::json::const_iterator it = j->begin();
353       for (const std::string &arg_code : dbusArgSplit(arg_type)) {
354         if (it == j->end()) {
355           return -1;
356         }
357         r = convertJsonToDbus(m, arg_code, *it);
358         if (r < 0) {
359           return r;
360         }
361         it++;
362       }
363       r = sd_bus_message_close_container(m);
364     } else if (boost::starts_with(arg_code, "{") &&
365                boost::ends_with(arg_code, "}")) {
366       std::string contained_type = arg_code.substr(1, arg_code.size() - 1);
367       r = sd_bus_message_open_container(m, SD_BUS_TYPE_DICT_ENTRY,
368                                         contained_type.c_str());
369       std::vector<std::string> codes = dbusArgSplit(contained_type);
370       if (codes.size() != 2) {
371         return -1;
372       }
373       const std::string &key_type = codes[0];
374       const std::string &value_type = codes[1];
375       for (auto it : j->items()) {
376         r = convertJsonToDbus(m, key_type, it.key());
377         if (r < 0) {
378           return r;
379         }
380 
381         r = convertJsonToDbus(m, value_type, it.value());
382         if (r < 0) {
383           return r;
384         }
385       }
386       r = sd_bus_message_close_container(m);
387     } else {
388       return -2;
389     }
390     if (r < 0) {
391       return r;
392     }
393 
394     if (argTypes.size() > 1) {
395       jIt++;
396     }
397   }
398 }
399 
400 void findActionOnInterface(std::shared_ptr<InProgressActionData> transaction,
401                            const std::string &connectionName) {
402   BMCWEB_LOG_DEBUG << "findActionOnInterface for connection " << connectionName;
403   crow::connections::systemBus->async_method_call(
404       [
405         transaction, connectionName{std::string(connectionName)}
406       ](const boost::system::error_code ec, const std::string &introspect_xml) {
407         BMCWEB_LOG_DEBUG << "got xml:\n " << introspect_xml;
408         if (ec) {
409           BMCWEB_LOG_ERROR << "Introspect call failed with error: "
410                            << ec.message() << " on process: " << connectionName
411                            << "\n";
412         } else {
413           tinyxml2::XMLDocument doc;
414 
415           doc.Parse(introspect_xml.c_str());
416           tinyxml2::XMLNode *pRoot = doc.FirstChildElement("node");
417           if (pRoot == nullptr) {
418             BMCWEB_LOG_ERROR << "XML document failed to parse "
419                              << connectionName << "\n";
420 
421           } else {
422             tinyxml2::XMLElement *interface_node =
423                 pRoot->FirstChildElement("interface");
424             while (interface_node != nullptr) {
425               std::string this_interface_name =
426                   interface_node->Attribute("name");
427               tinyxml2::XMLElement *method_node =
428                   interface_node->FirstChildElement("method");
429               while (method_node != nullptr) {
430                 std::string this_methodName = method_node->Attribute("name");
431                 BMCWEB_LOG_DEBUG << "Found method: " << this_methodName;
432                 if (this_methodName == transaction->methodName) {
433                   sdbusplus::message::message m =
434                       crow::connections::systemBus->new_method_call(
435                           connectionName.c_str(), transaction->path.c_str(),
436                           this_interface_name.c_str(),
437                           transaction->methodName.c_str());
438 
439                   tinyxml2::XMLElement *argument_node =
440                       method_node->FirstChildElement("arg");
441 
442                   nlohmann::json::const_iterator arg_it =
443                       transaction->arguments.begin();
444 
445                   while (argument_node != nullptr) {
446                     std::string arg_direction =
447                         argument_node->Attribute("direction");
448                     if (arg_direction == "in") {
449                       std::string arg_type = argument_node->Attribute("type");
450                       if (arg_it == transaction->arguments.end()) {
451                         transaction->setErrorStatus();
452                         return;
453                       }
454                       if (convertJsonToDbus(m.get(), arg_type, *arg_it) < 0) {
455                         transaction->setErrorStatus();
456                         return;
457                       }
458 
459                       arg_it++;
460                     }
461                     argument_node = method_node->NextSiblingElement("arg");
462                   }
463                   crow::connections::systemBus->async_send(
464                       m, [transaction](boost::system::error_code ec,
465                                        sdbusplus::message::message &m) {
466                         if (ec) {
467                           transaction->setErrorStatus();
468                           return;
469                         }
470                         transaction->res.jsonValue = {{"status", "ok"},
471                                                       {"message", "200 OK"},
472                                                       {"data", nullptr}};
473                       });
474                   break;
475                 }
476                 method_node = method_node->NextSiblingElement("method");
477               }
478               interface_node = interface_node->NextSiblingElement("interface");
479             }
480           }
481         }
482       },
483       connectionName, transaction->path, "org.freedesktop.DBus.Introspectable",
484       "Introspect");
485 }
486 
487 void handle_action(const crow::Request &req, crow::Response &res,
488                    const std::string &objectPath,
489                    const std::string &methodName) {
490   nlohmann::json requestDbusData =
491       nlohmann::json::parse(req.body, nullptr, false);
492 
493   if (requestDbusData.is_discarded()) {
494     res.result(boost::beast::http::status::bad_request);
495     res.end();
496     return;
497   }
498   if (!requestDbusData.is_array()) {
499     res.result(boost::beast::http::status::bad_request);
500     res.end();
501     return;
502   }
503   auto transaction = std::make_shared<InProgressActionData>(res);
504 
505   transaction->path = objectPath;
506   transaction->methodName = methodName;
507   transaction->arguments = std::move(requestDbusData);
508   crow::connections::systemBus->async_method_call(
509       [transaction](
510           const boost::system::error_code ec,
511           const std::vector<std::pair<std::string, std::vector<std::string>>>
512               &interface_names) {
513         if (ec || interface_names.size() <= 0) {
514           transaction->setErrorStatus();
515           return;
516         }
517 
518         BMCWEB_LOG_DEBUG << "GetObject returned objects "
519                          << interface_names.size();
520 
521         for (const std::pair<std::string, std::vector<std::string>> &object :
522              interface_names) {
523           findActionOnInterface(transaction, object.first);
524         }
525       },
526       "xyz.openbmc_project.ObjectMapper", "/xyz/openbmc_project/object_mapper",
527       "xyz.openbmc_project.ObjectMapper", "GetObject", objectPath,
528       std::array<std::string, 0>());
529 }
530 
531 void handle_list(crow::Response &res, const std::string &objectPath) {
532   crow::connections::systemBus->async_method_call(
533       [&res](const boost::system::error_code ec,
534              std::vector<std::string> &objectPaths) {
535         if (ec) {
536           res.result(boost::beast::http::status::internal_server_error);
537         } else {
538           res.jsonValue = {{"status", "ok"},
539                            {"message", "200 OK"},
540                            {"data", std::move(objectPaths)}};
541         }
542         res.end();
543       },
544       "xyz.openbmc_project.ObjectMapper", "/xyz/openbmc_project/object_mapper",
545       "xyz.openbmc_project.ObjectMapper", "GetSubTreePaths", objectPath,
546       static_cast<int32_t>(99), std::array<std::string, 0>());
547 }
548 
549 void handle_enumerate(crow::Response &res, const std::string &objectPath) {
550   crow::connections::systemBus->async_method_call(
551       [&res, objectPath{std::string(objectPath)} ](
552           const boost::system::error_code ec,
553           const GetSubTreeType &object_names) {
554         if (ec) {
555           res.jsonValue = {{"message", "200 OK"},
556                            {"status", "ok"},
557                            {"data", nlohmann::json::object()}};
558 
559           res.end();
560           return;
561         }
562 
563         boost::container::flat_set<std::string> connections;
564 
565         for (const auto &object : object_names) {
566           for (const auto &Connection : object.second) {
567             connections.insert(Connection.first);
568           }
569         }
570 
571         if (connections.size() <= 0) {
572           res.result(boost::beast::http::status::not_found);
573           res.end();
574           return;
575         }
576         auto transaction =
577             std::make_shared<nlohmann::json>(nlohmann::json::object());
578         for (const std::string &Connection : connections) {
579           getManagedObjectsForEnumerate(objectPath, Connection, res,
580                                         transaction);
581         }
582       },
583       "xyz.openbmc_project.ObjectMapper", "/xyz/openbmc_project/object_mapper",
584       "xyz.openbmc_project.ObjectMapper", "GetSubTree", objectPath, (int32_t)0,
585       std::array<std::string, 0>());
586 }
587 
588 void handle_get(crow::Response &res, std::string &objectPath,
589                 std::string &destProperty) {
590   BMCWEB_LOG_DEBUG << "handle_get: " << objectPath << " prop:" << destProperty;
591   std::shared_ptr<std::string> property_name =
592       std::make_shared<std::string>(std::move(destProperty));
593 
594   std::shared_ptr<std::string> path =
595       std::make_shared<std::string>(std::move(objectPath));
596 
597   using GetObjectType =
598       std::vector<std::pair<std::string, std::vector<std::string>>>;
599   crow::connections::systemBus->async_method_call(
600       [&res, path, property_name](const boost::system::error_code ec,
601                                   const GetObjectType &object_names) {
602         if (ec || object_names.size() <= 0) {
603           res.result(boost::beast::http::status::not_found);
604           res.end();
605           return;
606         }
607         std::shared_ptr<nlohmann::json> response =
608             std::make_shared<nlohmann::json>(nlohmann::json::object());
609         // The mapper should never give us an empty interface names list, but
610         // check anyway
611         for (const std::pair<std::string, std::vector<std::string>> connection :
612              object_names) {
613           const std::vector<std::string> &interfaceNames = connection.second;
614 
615           if (interfaceNames.size() <= 0) {
616             res.result(boost::beast::http::status::not_found);
617             res.end();
618             return;
619           }
620 
621           for (const std::string &interface : interfaceNames) {
622             crow::connections::systemBus->async_method_call(
623                 [&res, response, property_name](
624                     const boost::system::error_code ec,
625                     const std::vector<std::pair<
626                         std::string, DbusRestVariantType>> &properties) {
627                   if (ec) {
628                     BMCWEB_LOG_ERROR << "Bad dbus request error: " << ec;
629                   } else {
630                     for (const std::pair<std::string, DbusRestVariantType>
631                              &property : properties) {
632                       // if property name is empty, or matches our search query,
633                       // add it to the response json
634 
635                       if (property_name->empty()) {
636                         mapbox::util::apply_visitor(
637                             [&response, &property](auto &&val) {
638                               (*response)[property.first] = val;
639                             },
640                             property.second);
641                       } else if (property.first == *property_name) {
642                         mapbox::util::apply_visitor(
643                             [&response](auto &&val) { (*response) = val; },
644                             property.second);
645                       }
646                     }
647                   }
648                   if (response.use_count() == 1) {
649                     res.jsonValue = {{"status", "ok"},
650                                      {"message", "200 OK"},
651                                      {"data", *response}};
652 
653                     res.end();
654                   }
655                 },
656                 connection.first, *path, "org.freedesktop.DBus.Properties",
657                 "GetAll", interface);
658           }
659         }
660       },
661       "xyz.openbmc_project.ObjectMapper", "/xyz/openbmc_project/object_mapper",
662       "xyz.openbmc_project.ObjectMapper", "GetObject", *path,
663       std::array<std::string, 0>());
664 }
665 
666 struct AsyncPutRequest {
667   AsyncPutRequest(crow::Response &res) : res(res) {
668     res.jsonValue = {
669         {"status", "ok"}, {"message", "200 OK"}, {"data", nullptr}};
670   }
671   ~AsyncPutRequest() {
672     if (res.result() == boost::beast::http::status::internal_server_error) {
673       // Reset the json object to clear out any data that made it in before the
674       // error happened
675       // todo(ed) handle error condition with proper code
676       res.jsonValue = nlohmann::json::object();
677     }
678 
679     if (res.jsonValue.empty()) {
680       res.result(boost::beast::http::status::forbidden);
681       res.jsonValue = {
682           {"status", "error"},
683           {"message", "403 Forbidden"},
684           {"data",
685            {{"message",
686              "The specified property cannot be created: " + propertyName}}}};
687     }
688 
689     res.end();
690   }
691 
692   void setErrorStatus() {
693     res.result(boost::beast::http::status::internal_server_error);
694   }
695 
696   crow::Response &res;
697   std::string objectPath;
698   std::string propertyName;
699   nlohmann::json propertyValue;
700 };
701 
702 void handlePut(const crow::Request &req, crow::Response &res,
703                const std::string &objectPath, const std::string &destProperty) {
704   nlohmann::json requestDbusData =
705       nlohmann::json::parse(req.body, nullptr, false);
706 
707   if (requestDbusData.is_discarded()) {
708     res.result(boost::beast::http::status::bad_request);
709     res.end();
710     return;
711   }
712 
713   nlohmann::json::const_iterator propertyIt = requestDbusData.find("data");
714   if (propertyIt == requestDbusData.end()) {
715     res.result(boost::beast::http::status::bad_request);
716     res.end();
717     return;
718   }
719   const nlohmann::json &propertySetValue = *propertyIt;
720   auto transaction = std::make_shared<AsyncPutRequest>(res);
721   transaction->objectPath = objectPath;
722   transaction->propertyName = destProperty;
723   transaction->propertyValue = propertySetValue;
724 
725   using GetObjectType =
726       std::vector<std::pair<std::string, std::vector<std::string>>>;
727 
728   crow::connections::systemBus->async_method_call(
729       [transaction](const boost::system::error_code ec,
730                     const GetObjectType &object_names) {
731         if (!ec && object_names.size() <= 0) {
732           transaction->res.result(boost::beast::http::status::not_found);
733           return;
734         }
735 
736         for (const std::pair<std::string, std::vector<std::string>> connection :
737              object_names) {
738           const std::string &connectionName = connection.first;
739 
740           crow::connections::systemBus->async_method_call(
741               [ connectionName{std::string(connectionName)}, transaction ](
742                   const boost::system::error_code ec,
743                   const std::string &introspectXml) {
744                 if (ec) {
745                   BMCWEB_LOG_ERROR
746                       << "Introspect call failed with error: " << ec.message()
747                       << " on process: " << connectionName;
748                   transaction->setErrorStatus();
749                   return;
750                 }
751                 tinyxml2::XMLDocument doc;
752 
753                 doc.Parse(introspectXml.c_str());
754                 tinyxml2::XMLNode *pRoot = doc.FirstChildElement("node");
755                 if (pRoot == nullptr) {
756                   BMCWEB_LOG_ERROR << "XML document failed to parse: "
757                                    << introspectXml;
758                   transaction->setErrorStatus();
759                   return;
760                 }
761                 tinyxml2::XMLElement *ifaceNode =
762                     pRoot->FirstChildElement("interface");
763                 while (ifaceNode != nullptr) {
764                   const char *interfaceName = ifaceNode->Attribute("name");
765                   BMCWEB_LOG_DEBUG << "found interface " << interfaceName;
766                   tinyxml2::XMLElement *propNode =
767                       ifaceNode->FirstChildElement("property");
768                   while (propNode != nullptr) {
769                     const char *propertyName = propNode->Attribute("name");
770                     BMCWEB_LOG_DEBUG << "Found property " << propertyName;
771                     if (propertyName == transaction->propertyName) {
772                       const char *argType = propNode->Attribute("type");
773                       if (argType != nullptr) {
774                         sdbusplus::message::message m =
775                             crow::connections::systemBus->new_method_call(
776                                 connectionName.c_str(),
777                                 transaction->objectPath.c_str(),
778                                 "org.freedesktop.DBus.Properties", "Set");
779                         m.append(interfaceName, transaction->propertyName);
780                         int r = sd_bus_message_open_container(
781                             m.get(), SD_BUS_TYPE_VARIANT, argType);
782                         if (r < 0) {
783                           transaction->setErrorStatus();
784                           return;
785                         }
786                         r = convertJsonToDbus(m.get(), argType,
787                                               transaction->propertyValue);
788                         if (r < 0) {
789                           transaction->setErrorStatus();
790                           return;
791                         }
792                         r = sd_bus_message_close_container(m.get());
793                         if (r < 0) {
794                           transaction->setErrorStatus();
795                           return;
796                         }
797 
798                         crow::connections::systemBus->async_send(
799                             m, [transaction](boost::system::error_code ec,
800                                              sdbusplus::message::message &m) {
801                               BMCWEB_LOG_DEBUG << "sent";
802                               if (ec) {
803                                 transaction->res.jsonValue["status"] = "error";
804                                 transaction->res.jsonValue["message"] =
805                                     ec.message();
806                               }
807                             });
808                       }
809                     }
810                     propNode = propNode->NextSiblingElement("property");
811                   }
812                   ifaceNode = ifaceNode->NextSiblingElement("interface");
813                 }
814               },
815               connectionName, transaction->objectPath,
816               "org.freedesktop.DBus.Introspectable", "Introspect");
817         }
818       },
819       "xyz.openbmc_project.ObjectMapper", "/xyz/openbmc_project/object_mapper",
820       "xyz.openbmc_project.ObjectMapper", "GetObject", transaction->objectPath,
821       std::array<std::string, 0>());
822 }
823 
824 template <typename... Middlewares>
825 void requestRoutes(Crow<Middlewares...> &app) {
826   BMCWEB_ROUTE(app, "/bus/")
827       .methods("GET"_method)([](const crow::Request &req, crow::Response &res) {
828         res.jsonValue = {{"busses", {{{"name", "system"}}}}, {"status", "ok"}};
829       });
830 
831   BMCWEB_ROUTE(app, "/bus/system/")
832       .methods("GET"_method)([](const crow::Request &req, crow::Response &res) {
833 
834         auto myCallback = [&res](const boost::system::error_code ec,
835                                  std::vector<std::string> &names) {
836           if (ec) {
837             BMCWEB_LOG_ERROR << "Dbus call failed with code " << ec;
838             res.result(boost::beast::http::status::internal_server_error);
839           } else {
840             std::sort(names.begin(), names.end());
841             nlohmann::json j{{"status", "ok"}};
842             auto &objectsSub = j["objects"];
843             for (auto &name : names) {
844               objectsSub.push_back({{"name", name}});
845             }
846             res.jsonValue = std::move(j);
847           }
848           res.end();
849         };
850         crow::connections::systemBus->async_method_call(
851             std::move(myCallback), "org.freedesktop.DBus", "/",
852             "org.freedesktop.DBus", "ListNames");
853       });
854 
855   BMCWEB_ROUTE(app, "/list/")
856       .methods("GET"_method)([](const crow::Request &req, crow::Response &res) {
857         handle_list(res, "/");
858       });
859 
860   BMCWEB_ROUTE(app, "/xyz/<path>")
861       .methods("GET"_method, "PUT"_method,
862                "POST"_method)([](const crow::Request &req, crow::Response &res,
863                                  const std::string &path) {
864         std::string objectPath = "/xyz/" + path;
865 
866         // Trim any trailing "/" at the end
867         if (boost::ends_with(objectPath, "/")) {
868           objectPath.pop_back();
869         }
870 
871         // If accessing a single attribute, fill in and update objectPath,
872         // otherwise leave destProperty blank
873         std::string destProperty = "";
874         const char *attrSeperator = "/attr/";
875         size_t attrPosition = path.find(attrSeperator);
876         if (attrPosition != path.npos) {
877           objectPath = "/xyz/" + path.substr(0, attrPosition);
878           destProperty =
879               path.substr(attrPosition + strlen(attrSeperator), path.length());
880         }
881 
882         if (req.method() == "POST"_method) {
883           constexpr const char *action_seperator = "/action/";
884           size_t action_position = path.find(action_seperator);
885           if (action_position != path.npos) {
886             objectPath = "/xyz/" + path.substr(0, action_position);
887             std::string post_property = path.substr(
888                 (action_position + strlen(action_seperator)), path.length());
889             handle_action(req, res, objectPath, post_property);
890             return;
891           }
892         } else if (req.method() == "GET"_method) {
893           if (boost::ends_with(objectPath, "/enumerate")) {
894             objectPath.erase(objectPath.end() - 10, objectPath.end());
895             handle_enumerate(res, objectPath);
896           } else if (boost::ends_with(objectPath, "/list")) {
897             objectPath.erase(objectPath.end() - 5, objectPath.end());
898             handle_list(res, objectPath);
899           } else {
900             handle_get(res, objectPath, destProperty);
901           }
902           return;
903         } else if (req.method() == "PUT"_method) {
904           handlePut(req, res, objectPath, destProperty);
905           return;
906         }
907 
908         res.result(boost::beast::http::status::method_not_allowed);
909         res.end();
910       });
911 
912   BMCWEB_ROUTE(app, "/bus/system/<str>/")
913       .methods("GET"_method)([](const crow::Request &req, crow::Response &res,
914                                 const std::string &Connection) {
915         std::shared_ptr<nlohmann::json> transaction;
916         introspectObjects(res, Connection, "/", transaction);
917       });
918 
919   BMCWEB_ROUTE(app, "/download/dump/<str>/")
920       .methods("GET"_method)([](const crow::Request &req, crow::Response &res,
921                                 const std::string &dumpId) {
922         std::regex validFilename("^[\\w\\- ]+(\\.?[\\w\\- ]+)$");
923         if (!std::regex_match(dumpId, validFilename)) {
924           res.result(boost::beast::http::status::not_found);
925           res.end();
926           return;
927         }
928         std::experimental::filesystem::path loc(
929             "/var/lib/phosphor-debug-collector/dumps");
930 
931         loc += dumpId;
932 
933         if (!std::experimental::filesystem::exists(loc) ||
934             !std::experimental::filesystem::is_directory(loc)) {
935           res.result(boost::beast::http::status::not_found);
936           res.end();
937           return;
938         }
939         std::experimental::filesystem::directory_iterator files(loc);
940         for (auto &file : files) {
941           std::ifstream readFile(file.path());
942           if (readFile.good()) {
943             continue;
944           }
945           res.addHeader("Content-Type", "application/octet-stream");
946           res.body() = {std::istreambuf_iterator<char>(readFile),
947                         std::istreambuf_iterator<char>()};
948           res.end();
949         }
950         res.result(boost::beast::http::status::not_found);
951         res.end();
952         return;
953       });
954 
955   BMCWEB_ROUTE(app, "/bus/system/<str>/<path>")
956       .methods("GET"_method)([](const crow::Request &req, crow::Response &res,
957                                 const std::string &processName,
958                                 const std::string &requestedPath) {
959         std::vector<std::string> strs;
960         boost::split(strs, requestedPath, boost::is_any_of("/"));
961         std::string objectPath;
962         std::string interfaceName;
963         std::string methodName;
964         auto it = strs.begin();
965         if (it == strs.end()) {
966           objectPath = "/";
967         }
968         while (it != strs.end()) {
969           // Check if segment contains ".".  If it does, it must be an
970           // interface
971           if (it->find(".") != std::string::npos) {
972             break;
973             // THis check is neccesary as the trailing slash gets parsed as
974             // part of our <path> specifier above, which causes the normal
975             // trailing backslash redirector to fail.
976           } else if (!it->empty()) {
977             objectPath += "/" + *it;
978           }
979           it++;
980         }
981         if (it != strs.end()) {
982           interfaceName = *it;
983           it++;
984 
985           // after interface, we might have a method name
986           if (it != strs.end()) {
987             methodName = *it;
988             it++;
989           }
990         }
991         if (it != strs.end()) {
992           // if there is more levels past the method name, something went
993           // wrong, return not found
994           res.result(boost::beast::http::status::not_found);
995           res.end();
996           return;
997         }
998         if (interfaceName.empty()) {
999           crow::connections::systemBus->async_method_call(
1000               [&, processName, objectPath](const boost::system::error_code ec,
1001                                            const std::string &introspect_xml) {
1002                 if (ec) {
1003                   BMCWEB_LOG_ERROR
1004                       << "Introspect call failed with error: " << ec.message()
1005                       << " on process: " << processName
1006                       << " path: " << objectPath << "\n";
1007 
1008                 } else {
1009                   tinyxml2::XMLDocument doc;
1010 
1011                   doc.Parse(introspect_xml.c_str());
1012                   tinyxml2::XMLNode *pRoot = doc.FirstChildElement("node");
1013                   if (pRoot == nullptr) {
1014                     BMCWEB_LOG_ERROR << "XML document failed to parse "
1015                                      << processName << " " << objectPath
1016                                      << "\n";
1017                     res.jsonValue = {{"status", "XML parse error"}};
1018                     res.result(
1019                         boost::beast::http::status::internal_server_error);
1020                   } else {
1021                     nlohmann::json interfacesArray = nlohmann::json::array();
1022                     tinyxml2::XMLElement *interface =
1023                         pRoot->FirstChildElement("interface");
1024 
1025                     while (interface != nullptr) {
1026                       std::string ifaceName = interface->Attribute("name");
1027                       interfacesArray.push_back({{"name", ifaceName}});
1028 
1029                       interface = interface->NextSiblingElement("interface");
1030                     }
1031                     res.jsonValue = {{"status", "ok"},
1032                                      {"bus_name", processName},
1033                                      {"interfaces", interfacesArray},
1034                                      {"objectPath", objectPath}};
1035                   }
1036                 }
1037                 res.end();
1038               },
1039               processName, objectPath, "org.freedesktop.DBus.Introspectable",
1040               "Introspect");
1041         } else {
1042           crow::connections::systemBus->async_method_call(
1043               [
1044                     &, processName, objectPath,
1045                     interface_name{std::move(interfaceName)}
1046               ](const boost::system::error_code ec,
1047                 const std::string &introspect_xml) {
1048                 if (ec) {
1049                   BMCWEB_LOG_ERROR
1050                       << "Introspect call failed with error: " << ec.message()
1051                       << " on process: " << processName
1052                       << " path: " << objectPath << "\n";
1053 
1054                 } else {
1055                   tinyxml2::XMLDocument doc;
1056 
1057                   doc.Parse(introspect_xml.c_str());
1058                   tinyxml2::XMLNode *pRoot = doc.FirstChildElement("node");
1059                   if (pRoot == nullptr) {
1060                     BMCWEB_LOG_ERROR << "XML document failed to parse "
1061                                      << processName << " " << objectPath
1062                                      << "\n";
1063                     res.result(
1064                         boost::beast::http::status::internal_server_error);
1065 
1066                   } else {
1067                     tinyxml2::XMLElement *node =
1068                         pRoot->FirstChildElement("node");
1069 
1070                     // if we know we're the only call, build the json directly
1071                     nlohmann::json methodsArray = nlohmann::json::array();
1072                     nlohmann::json signalsArray = nlohmann::json::array();
1073                     tinyxml2::XMLElement *interface =
1074                         pRoot->FirstChildElement("interface");
1075 
1076                     while (interface != nullptr) {
1077                       std::string ifaceName = interface->Attribute("name");
1078 
1079                       if (ifaceName == interfaceName) {
1080                         tinyxml2::XMLElement *methods =
1081                             interface->FirstChildElement("method");
1082                         while (methods != nullptr) {
1083                           nlohmann::json argsArray = nlohmann::json::array();
1084                           tinyxml2::XMLElement *arg =
1085                               methods->FirstChildElement("arg");
1086                           while (arg != nullptr) {
1087                             argsArray.push_back(
1088                                 {{"name", arg->Attribute("name")},
1089                                  {"type", arg->Attribute("type")},
1090                                  {"direction", arg->Attribute("direction")}});
1091                             arg = arg->NextSiblingElement("arg");
1092                           }
1093                           methodsArray.push_back(
1094                               {{"name", methods->Attribute("name")},
1095                                {"uri", "/bus/system/" + processName +
1096                                            objectPath + "/" + interfaceName +
1097                                            "/" + methods->Attribute("name")},
1098                                {"args", argsArray}});
1099                           methods = methods->NextSiblingElement("method");
1100                         }
1101                         tinyxml2::XMLElement *signals =
1102                             interface->FirstChildElement("signal");
1103                         while (signals != nullptr) {
1104                           nlohmann::json argsArray = nlohmann::json::array();
1105 
1106                           tinyxml2::XMLElement *arg =
1107                               signals->FirstChildElement("arg");
1108                           while (arg != nullptr) {
1109                             std::string name = arg->Attribute("name");
1110                             std::string type = arg->Attribute("type");
1111                             argsArray.push_back({
1112                                 {"name", name},
1113                                 {"type", type},
1114                             });
1115                             arg = arg->NextSiblingElement("arg");
1116                           }
1117                           signalsArray.push_back(
1118                               {{"name", signals->Attribute("name")},
1119                                {"args", argsArray}});
1120                           signals = signals->NextSiblingElement("signal");
1121                         }
1122 
1123                         res.jsonValue = {
1124                             {"status", "ok"},
1125                             {"bus_name", processName},
1126                             {"interface", interfaceName},
1127                             {"methods", methodsArray},
1128                             {"objectPath", objectPath},
1129                             {"properties", nlohmann::json::object()},
1130                             {"signals", signalsArray}};
1131 
1132                         break;
1133                       }
1134 
1135                       interface = interface->NextSiblingElement("interface");
1136                     }
1137                     if (interface == nullptr) {
1138                       // if we got to the end of the list and never found a
1139                       // match, throw 404
1140                       res.result(boost::beast::http::status::not_found);
1141                     }
1142                   }
1143                 }
1144                 res.end();
1145               },
1146               processName, objectPath, "org.freedesktop.DBus.Introspectable",
1147               "Introspect");
1148         }
1149       });
1150 }
1151 }  // namespace openbmc_mapper
1152 }  // namespace crow
1153