xref: /openbmc/bmcweb/include/webassets.hpp (revision e617c9ab09c70c45ad098c68e709787d0783f908)
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        } else {
82          if (webpath.string() == "$metadata"){
83            content_type_it = content_types.find(".xml");
84            // should always be true
85            if (content_type_it != content_types.end()) {
86              content_type = content_type_it->second;
87            }
88          }
89        }
90        app.route_dynamic(webpath.string())(
91            [is_gzip, absolute_path_str, content_type](const crow::request& req,
92                                                       crow::response& res) {
93              static const char* content_type_string = "Content-Type";
94              // std::string sha1("a576dc96a5c605b28afb032f3103630d61ac1068");
95              // res.add_header("ETag", sha1);
96  
97              // if (req.get_header_value("If-None-Match") == sha1) {
98              //  res.code = 304;
99              //} else {
100              //  res.code = 200;
101              // TODO, if you have a browser from the dark ages that doesn't
102              // support
103              // gzip, unzip it before sending based on Accept-Encoding header
104              //  res.add_header("Content-Encoding", gzip_string);
105              if (content_type != nullptr) {
106                res.add_header(content_type_string, content_type);
107              }
108  
109              if (is_gzip) {
110                res.add_header("Content-Encoding", "gzip");
111              }
112  
113              // res.set_header("Cache-Control", "public, max-age=86400");
114              std::ifstream inf(absolute_path_str);
115              if (!inf) {
116                CROW_LOG_DEBUG << "failed to read file";
117                res.code = 400;
118                res.end();
119                return;
120              }
121  
122              std::string body{std::istreambuf_iterator<char>(inf),
123                               std::istreambuf_iterator<char>()};
124  
125              res.body = body;
126              res.end();
127            });
128      }
129    }
130  }
131  }  // namespace webassets
132  }  // namespace crow