LCOV - code coverage report
Current view: top level - source/server/admin - admin_html_util.cc (source / functions) Hit Total Coverage
Test: coverage.dat Lines: 0 114 0.0 %
Date: 2024-01-05 06:35:25 Functions: 0 11 0.0 %

          Line data    Source code
       1             : #include "source/server/admin/admin_html_util.h"
       2             : 
       3             : #include "source/common/html/utility.h"
       4             : #include "source/common/http/headers.h"
       5             : #include "source/server/admin/html/admin_html_gen.h"
       6             : 
       7             : #include "absl/strings/str_replace.h"
       8             : 
       9             : namespace {
      10             : 
      11             : const char AdminHtmlTableBegin[] = R"(
      12             :   <table class='home-table'>
      13             :     <thead>
      14             :       <tr>
      15             :         <th class='home-data'>Command</th>
      16             :         <th class='home-data'>Description</th>
      17             :       </tr>
      18             :     </thead>
      19             :     <tbody>
      20             : )";
      21             : 
      22             : const char AdminHtmlTableEnd[] = R"(
      23             :     </tbody>
      24             :   </table>
      25             : )";
      26             : 
      27             : /**
      28             :  * Favicon base64 image was harvested by screen-capturing the favicon from a Chrome tab
      29             :  * while visiting https://www.envoyproxy.io/. The resulting PNG was translated to base64
      30             :  * by dropping it into https://www.base64-image.de/ and then pasting the resulting string
      31             :  * below.
      32             :  *
      33             :  * The actual favicon source for that, https://www.envoyproxy.io/img/favicon.ico is nicer
      34             :  * because it's transparent, but is also 67646 bytes, which is annoying to inline. We could
      35             :  * just reference that rather than inlining it, but then the favicon won't work when visiting
      36             :  * the admin page from a network that can't see the internet.
      37             :  */
      38             : const char EnvoyFavicon[] =
      39             :     "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1"
      40             :     "BAACxjwv8YQUAAAAJcEhZcwAAEnQAABJ0Ad5mH3gAAAH9SURBVEhL7ZRdTttAFIUrUFaAX5w9gIhgUfzshFRK+gIbaVbA"
      41             :     "zwaqCly1dSpKk5A485/YCdXpHTB4BsdgVe0bD0cZ3Xsm38yZ8byTUuJ/6g3wqqoBrBhPTzmmLfptMbAzttJTpTKAF2MWC"
      42             :     "7ADCdNIwXZpvMMwayiIwwS874CcOc9VuQPR1dBBChPMITpFXXU45hukIIH6kHhzVqkEYB8F5HYGvZ5B7EvwmHt9K/59Cr"
      43             :     "U3QbY2RNYaQPYmJc+jPIBICNCcg20ZsAsCPfbcrFlRF+cJZpvXSJt9yMTxO/IAzJrCOfhJXiOgFEX/SbZmezTWxyNk4Q9"
      44             :     "anHMmjnzAhEyhAW8LCE6wl26J7ZFHH1FMYQxh567weQBOO1AW8D7P/UXAQySq/QvL8Fu9HfCEw4SKALm5BkC3bwjwhSKr"
      45             :     "A5hYAMXTJnPNiMyRBVzVjcgCyHiSm+8P+WGlnmwtP2RzbCMiQJ0d2KtmmmPorRHEhfMROVfTG5/fYrF5iWXzE80tfy9WP"
      46             :     "sCqx5Buj7FYH0LvDyHiqd+3otpsr4/fa5+xbEVQPfrYnntylQG5VGeMLBhgEfyE7o6e6qYzwHIjwl0QwXSvvTmrVAY4D5"
      47             :     "ddvT64wV0jRrr7FekO/XEjwuwwhuw7Ef7NY+dlfXpLb06EtHUJdVbsxvNUqBrwj/QGeEUSfwBAkmWHn5Bb/gAAAABJRU5";
      48             : 
      49             : } // namespace
      50             : 
      51             : namespace Envoy {
      52             : namespace Server {
      53             : 
      54             : namespace {
      55             : class BuiltinResourceProvider : public AdminHtmlUtil::ResourceProvider {
      56             : public:
      57           0 :   BuiltinResourceProvider() : histogram_js_(absl::StrCat(HistogramsJs1, HistogramsJs2)) {
      58           0 :     map_["admin_head_start.html"] = AdminHtmlStart;
      59           0 :     map_["admin.css"] = AdminCss;
      60           0 :     map_["active_stats.js"] = AdminActiveStatsJs;
      61           0 :     map_["histograms.js"] = histogram_js_;
      62           0 :     map_["active_params.html"] = AdminActiveParamsHtml;
      63           0 :   }
      64             : 
      65           0 :   absl::string_view getResource(absl::string_view resource_name, std::string&) override {
      66           0 :     return map_[resource_name];
      67           0 :   }
      68             : 
      69             : private:
      70             :   const std::string histogram_js_;
      71             :   absl::flat_hash_map<absl::string_view, absl::string_view> map_;
      72             : };
      73             : 
      74             : // This is a hack to make a lazy-constructed holder for a pointer to the
      75             : // resource provider.
      76             : //
      77             : // We use a mutable singleton rather than plumbing in the resource
      78             : // provider. This is because the HTML features in the admin console can be
      79             : // compiled out of Envoy. It's awkward to plumb the resource provider through
      80             : // multiple layers that do not want to compile in the class definition. There
      81             : // would be many `ifdefs` through constructors, initializers, etc.
      82             : struct ProviderContainer {
      83             :   std::unique_ptr<AdminHtmlUtil::ResourceProvider> provider_{
      84             :       std::make_unique<BuiltinResourceProvider>()};
      85             : };
      86             : 
      87           0 : ProviderContainer& getProviderContainer() { MUTABLE_CONSTRUCT_ON_FIRST_USE(ProviderContainer); }
      88             : 
      89             : } // namespace
      90             : 
      91           0 : absl::string_view AdminHtmlUtil::getResource(absl::string_view resource_name, std::string& buf) {
      92           0 :   return getProviderContainer().provider_->getResource(resource_name, buf);
      93           0 : }
      94             : 
      95             : std::unique_ptr<AdminHtmlUtil::ResourceProvider>
      96           0 : AdminHtmlUtil::setResourceProvider(std::unique_ptr<ResourceProvider> resource_provider) {
      97           0 :   ProviderContainer& container = getProviderContainer();
      98           0 :   std::unique_ptr<ResourceProvider> prev = std::move(container.provider_);
      99           0 :   container.provider_ = std::move(resource_provider);
     100           0 :   return prev;
     101           0 : }
     102             : 
     103             : void AdminHtmlUtil::renderHead(Http::ResponseHeaderMap& response_headers,
     104           0 :                                Buffer::Instance& response) {
     105           0 :   response_headers.setReferenceContentType(Http::Headers::get().ContentTypeValues.Html);
     106           0 :   std::string buf1, buf2, buf3;
     107           0 :   response.addFragments({"<!DOCTYPE html>\n"
     108           0 :                          "<html lang='en'>\n"
     109           0 :                          "<head>\n",
     110           0 :                          absl::StrReplaceAll(getResource("admin_head_start.html", buf1),
     111           0 :                                              {{"@FAVICON@", EnvoyFavicon}}),
     112           0 :                          "<style>\n", getResource("admin.css", buf2), "</style>\n"});
     113           0 :   response.add("</head>\n<body>\n");
     114           0 : }
     115             : 
     116           0 : void AdminHtmlUtil::renderTableBegin(Buffer::Instance& response) {
     117           0 :   response.add(AdminHtmlTableBegin);
     118           0 : }
     119             : 
     120           0 : void AdminHtmlUtil::renderTableEnd(Buffer::Instance& response) { response.add(AdminHtmlTableEnd); }
     121             : 
     122           0 : void AdminHtmlUtil::finalize(Buffer::Instance& response) { response.add("</body>\n</html>\n"); }
     123             : 
     124             : void AdminHtmlUtil::renderHandlerParam(Buffer::Instance& response, absl::string_view id,
     125             :                                        absl::string_view name, absl::string_view path,
     126             :                                        Admin::ParamDescriptor::Type type,
     127             :                                        OptRef<const Http::Utility::QueryParamsMulti> query,
     128             :                                        const std::vector<absl::string_view>& enum_choices,
     129           0 :                                        bool submit_on_change) {
     130           0 :   std::string value;
     131           0 :   if (query.has_value()) {
     132           0 :     auto result = query->getFirstValue(name);
     133           0 :     if (result.has_value()) {
     134           0 :       value = result.value();
     135           0 :     }
     136           0 :   }
     137             : 
     138           0 :   std::string on_change;
     139           0 :   if (submit_on_change) {
     140           0 :     on_change = absl::StrCat(" onchange='", path, ".submit()'");
     141           0 :   }
     142             : 
     143           0 :   switch (type) {
     144           0 :   case Admin::ParamDescriptor::Type::Boolean:
     145           0 :     response.addFragments({"<input type='checkbox' name='", name, "' id='", id, "' form='", path,
     146           0 :                            "'", on_change, value.empty() ? ">" : " checked/>"});
     147           0 :     break;
     148           0 :   case Admin::ParamDescriptor::Type::String: {
     149           0 :     std::string sanitized;
     150           0 :     if (!value.empty()) {
     151           0 :       sanitized = absl::StrCat(" value='", Html::Utility::sanitize(value), "'");
     152           0 :     }
     153           0 :     response.addFragments({"<input type='text' name='", name, "' id='", id, "' form='", path, "'",
     154           0 :                            on_change, sanitized, " />"});
     155           0 :     break;
     156           0 :   }
     157           0 :   case Admin::ParamDescriptor::Type::Enum:
     158           0 :     response.addFragments(
     159           0 :         {"\n    <select name='", name, "' id='", id, "' form='", path, "'", on_change, ">\n"});
     160           0 :     for (absl::string_view choice : enum_choices) {
     161           0 :       std::string sanitized_choice = Html::Utility::sanitize(choice);
     162           0 :       std::string sanitized_value = Html::Utility::sanitize(value);
     163           0 :       absl::string_view selected = (sanitized_value == sanitized_choice) ? " selected" : "";
     164           0 :       response.addFragments({"      <option value='", sanitized_choice, "'", selected, ">",
     165           0 :                              sanitized_choice, "</option>\n"});
     166           0 :     }
     167           0 :     response.add("    </select>\n  ");
     168           0 :     break;
     169           0 :   }
     170           0 : }
     171             : 
     172             : void AdminHtmlUtil::renderEndpointTableRow(Buffer::Instance& response,
     173             :                                            const Admin::UrlHandler& handler,
     174             :                                            OptRef<const Http::Utility::QueryParamsMulti> query,
     175           0 :                                            int index, bool submit_on_change, bool active) {
     176           0 :   absl::string_view path = handler.prefix_;
     177             : 
     178           0 :   if (path == "/") {
     179           0 :     return; // No need to print self-link to index page.
     180           0 :   }
     181             : 
     182             :   // Remove the leading slash from the link, so that the admin page can be
     183             :   // rendered as part of another console, on a sub-path.
     184             :   //
     185             :   // E.g. consider a downstream dashboard that embeds the Envoy admin console.
     186             :   // In that case, the "/stats" endpoint would be at
     187             :   // https://DASHBOARD/envoy_admin/stats. If the links we present on the home
     188             :   // page are absolute (e.g. "/stats") they won't work in the context of the
     189             :   // dashboard. Removing the leading slash, they will work properly in both
     190             :   // the raw admin console and when embedded in another page and URL
     191             :   // hierarchy.
     192           0 :   ASSERT(!path.empty());
     193           0 :   ASSERT(path[0] == '/');
     194           0 :   std::string sanitized_path = Html::Utility::sanitize(path.substr(1));
     195           0 :   path = sanitized_path;
     196             : 
     197             :   // Alternate gray and white param-blocks. The pure CSS way of coloring based
     198             :   // on row index doesn't work correctly for us as we are using a row for each
     199             :   // parameter, and we want each endpoint/option-block to be colored the same.
     200           0 :   const char* row_class = (index & 1) ? " class='gray'" : "";
     201             : 
     202             :   // For handlers that mutate state, render the link as a button in a POST form,
     203             :   // rather than an anchor tag. This should discourage crawlers that find the /
     204             :   // page from accidentally mutating all the server state by GETting all the hrefs.
     205           0 :   const char* method = handler.mutates_server_state_ ? "post" : "get";
     206           0 :   if (submit_on_change) {
     207           0 :     response.addFragments({"\n<tr><td><form action='", path, "' method='", method, "' id='", path,
     208           0 :                            "' class='home-form'></form></td><td></td></tr>\n"});
     209           0 :   } else {
     210           0 :     response.addFragments({"\n<tr class='vert-space'><td></td><td></td></tr>\n<tr", row_class,
     211           0 :                            ">\n  <td class='home-data'>"});
     212           0 :     if (!handler.mutates_server_state_ && handler.params_.empty()) {
     213             :       // GET requests without parameters can be simple links rather than forms with
     214             :       // buttons that are rendered as links. This simplification improves the
     215             :       // usability of the page with screen-readers.
     216           0 :       response.addFragments({"<a href='", path, "'>", path, "</a>"});
     217           0 :     } else {
     218             :       // Render an explicit visible submit as a link (for GET) or button (for POST).
     219           0 :       const char* button_style = handler.mutates_server_state_ ? "" : " class='button-as-link'";
     220           0 :       response.addFragments({"<form action='", path, "' method='", method, "' id='", path,
     221           0 :                              "' class='home-form'>\n    <button", button_style, ">", path,
     222           0 :                              "</button>\n  </form>"});
     223           0 :     }
     224           0 :     response.addFragments({"</td>\n  <td class='home-data'>",
     225           0 :                            Html::Utility::sanitize(handler.help_text_), "</td>\n</tr>\n"});
     226           0 :   }
     227             : 
     228           0 :   for (const Admin::ParamDescriptor& param : handler.params_) {
     229             :     // Give each parameter a unique number. Note that this naming is also referenced in
     230             :     // active_stats.js which looks directly at the parameter widgets to find the
     231             :     // current values during JavaScript-driven active updates.
     232           0 :     std::string id =
     233           0 :         absl::StrCat("param-", index, "-", absl::StrReplaceAll(path, {{"/", "-"}}), "-", param.id_);
     234           0 :     response.addFragments({"<tr", row_class, ">\n  <td class='option'>"});
     235           0 :     renderHandlerParam(response, id, param.id_, path, param.type_, query, param.enum_choices_,
     236           0 :                        submit_on_change && (!active || param.id_ == "format"));
     237           0 :     response.addFragments({"</td>\n  <td class='home-data'>", "<label for='", id, "'>",
     238           0 :                            Html::Utility::sanitize(param.help_), "</label></td>\n</tr>\n"});
     239           0 :   }
     240           0 : }
     241             : 
     242             : } // namespace Server
     243             : } // namespace Envoy

Generated by: LCOV version 1.15