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