xref: /openbmc/bmcweb/include/webassets.hpp (revision ba9f9a6cebfbb7a12ecf869afa0cdc5aef9c8372)
1  #pragma once
2  
3  #include <experimental/filesystem>
4  #include <fstream>
5  #include <string>
6  #include <crow/app.h>
7  #include <crow/http_request.h>
8  #include <crow/http_response.h>
9  #include <crow/routing.h>
10  #include <boost/algorithm/string/replace.hpp>
11  #include <boost/container/flat_set.hpp>
12  
13  namespace crow {
14  namespace webassets {
15  
16  namespace filesystem = std::experimental::filesystem;
17  
18  struct cmp_str {
19    bool operator()(const char* a, const char* b) const {
20      return std::strcmp(a, b) < 0;
21    }
22  };
23  
24  static boost::container::flat_set<std::string> routes;
25  
26  template <typename... Middlewares>
27  void request_routes(Crow<Middlewares...>& app) {
28    const static boost::container::flat_map<const char*, const char*, cmp_str>
29        content_types{
30            {{".css", "text/css;charset=UTF-8"},
31             {".html", "text/html;charset=UTF-8"},
32             {".js", "text/html;charset=UTF-8"},
33             {".png", "image/png;charset=UTF-8"},
34             {".woff", "application/x-font-woff"},
35             {".woff2", "application/x-font-woff2"},
36             {".ttf", "application/x-font-ttf"},
37             {".svg", "image/svg+xml"},
38             {".eot", "application/vnd.ms-fontobject"},
39             {".xml", "application/xml"},
40             // dev tools don't care about map type, setting to json causes
41             // browser to show as text
42             // https://stackoverflow.com/questions/19911929/what-mime-type-should-i-use-for-javascript-source-map-files
43             {".map", "application/json"}}};
44    auto rootpath = filesystem::path("/usr/share/www/");
45    auto dir_iter = filesystem::recursive_directory_iterator(rootpath);
46    for (auto& dir : dir_iter) {
47      auto absolute_path = dir.path();
48      auto absolute_path_str = dir.path().string();
49      auto relative_path = filesystem::path(
50          absolute_path.string().substr(rootpath.string().size() - 1));
51      // make sure we don't recurse into certain directories
52      // note: maybe check for is_directory() here as well...
53      if (filesystem::is_directory(dir)) {
54        // don't recurse into hidden directories or symlinks
55        if (boost::starts_with(dir.path().filename().string(), ".") ||
56            filesystem::is_symlink(dir)) {
57          dir_iter.disable_recursion_pending();
58        }
59      } else if (filesystem::is_regular_file(dir)) {
60        auto webpath = relative_path;
61        bool is_gzip = false;
62        if (relative_path.extension() == ".gz") {
63          webpath = webpath.replace_extension("");
64          is_gzip = true;
65        }
66  
67        if (webpath.filename() == "index.html") {
68          webpath = webpath.parent_path();
69          if (webpath.string() != "/") {
70            webpath += "/";
71          }
72        }
73  
74        routes.insert(webpath.string());
75  
76        std::string absolute_path_str = absolute_path.string();
77        const char* content_type = nullptr;
78        auto content_type_it = content_types.find(webpath.extension().c_str());
79        if (content_type_it != content_types.end()) {
80          content_type = content_type_it->second;
81        }
82        app.route_dynamic(std::string(webpath.string()))(
83            [is_gzip, absolute_path_str, content_type](const crow::request& req,
84                                                       crow::response& res) {
85              static const char* content_type_string = "Content-Type";
86              // std::string sha1("a576dc96a5c605b28afb032f3103630d61ac1068");
87              // res.add_header("ETag", sha1);
88  
89              // if (req.get_header_value("If-None-Match") == sha1) {
90              //  res.code = 304;
91              //} else {
92              //  res.code = 200;
93              // TODO, if you have a browser from the dark ages that doesn't
94              // support
95              // gzip, unzip it before sending based on Accept-Encoding header
96              //  res.add_header("Content-Encoding", gzip_string);
97              if (content_type != nullptr) {
98                res.add_header(content_type_string, content_type);
99              }
100  
101              if (is_gzip) {
102                res.add_header("Content-Encoding", "gzip");
103              }
104  
105              // res.set_header("Cache-Control", "public, max-age=86400");
106              std::ifstream inf(absolute_path_str);
107              if (!inf) {
108                CROW_LOG_DEBUG << "failed to read file";
109                res.code = 400;
110                res.end();
111                return;
112              }
113  
114              std::string body{std::istreambuf_iterator<char>(inf),
115                               std::istreambuf_iterator<char>()};
116  
117              res.body = body;
118              res.end();
119            });
120      }
121    }
122  }
123  }  // namespace webassets
124  }  // namespace crow