xref: /openbmc/bmcweb/include/webassets.hpp (revision 1c74de8786b2e16de8281abf4d427644fc426ee8)
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(std::string(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