summaryrefslogtreecommitdiff
path: root/include/webassets.hpp
blob: 7845d712ad95603a0c86539e130166b2f3c43a35 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
#pragma once

#include <experimental/filesystem>
#include <fstream>
#include <string>
#include <crow/app.h>
#include <crow/http_request.h>
#include <crow/http_response.h>
#include <crow/routing.h>
#include <boost/algorithm/string/replace.hpp>
#include <boost/container/flat_set.hpp>

namespace crow {
namespace webassets {

namespace filesystem = std::experimental::filesystem;

struct cmp_str {
  bool operator()(const char* a, const char* b) const {
    return std::strcmp(a, b) < 0;
  }
};

static boost::container::flat_set<std::string> routes;

template <typename... Middlewares>
void request_routes(Crow<Middlewares...>& app) {
  const static boost::container::flat_map<const char*, const char*, cmp_str>
      content_types{
          {{".css", "text/css;charset=UTF-8"},
           {".html", "text/html;charset=UTF-8"},
           {".js", "text/html;charset=UTF-8"},
           {".png", "image/png;charset=UTF-8"},
           {".woff", "application/x-font-woff"},
           {".woff2", "application/x-font-woff2"},
           {".ttf", "application/x-font-ttf"},
           {".svg", "image/svg+xml"},
           {".eot", "application/vnd.ms-fontobject"},
           {".xml", "application/xml"},
           // dev tools don't care about map type, setting to json causes
           // browser to show as text
           // https://stackoverflow.com/questions/19911929/what-mime-type-should-i-use-for-javascript-source-map-files
           {".map", "application/json"}}};
  auto rootpath = filesystem::path("/usr/share/www/");
  auto dir_iter = filesystem::recursive_directory_iterator(rootpath);
  for (auto& dir : dir_iter) {
    auto absolute_path = dir.path();
    auto absolute_path_str = dir.path().string();
    auto relative_path = filesystem::path(
        absolute_path.string().substr(rootpath.string().size() - 1));
    // make sure we don't recurse into certain directories
    // note: maybe check for is_directory() here as well...
    if (filesystem::is_directory(dir)) {
      // don't recurse into hidden directories or symlinks
      if (boost::starts_with(dir.path().filename().string(), ".") ||
          filesystem::is_symlink(dir)) {
        dir_iter.disable_recursion_pending();
      }
    } else if (filesystem::is_regular_file(dir)) {
      auto webpath = relative_path;
      bool is_gzip = false;
      if (relative_path.extension() == ".gz") {
        webpath = webpath.replace_extension("");
        is_gzip = true;
      }

      if (webpath.filename() == "index.html") {
        webpath = webpath.parent_path();
        if (webpath.string() != "/") {
          webpath += "/";
        }
      }

      routes.insert(webpath.string());

      std::string absolute_path_str = absolute_path.string();
      const char* content_type = nullptr;
      auto content_type_it = content_types.find(webpath.extension().c_str());
      if (content_type_it != content_types.end()) {
        content_type = content_type_it->second;
      }
      app.route_dynamic(std::string(webpath.string()))(
          [is_gzip, absolute_path_str, content_type](const crow::request& req,
                                                     crow::response& res) {
            static const char* content_type_string = "Content-Type";
            // std::string sha1("a576dc96a5c605b28afb032f3103630d61ac1068");
            // res.add_header("ETag", sha1);

            // if (req.get_header_value("If-None-Match") == sha1) {
            //  res.code = 304;
            //} else {
            //  res.code = 200;
            // TODO, if you have a browser from the dark ages that doesn't
            // support
            // gzip, unzip it before sending based on Accept-Encoding header
            //  res.add_header("Content-Encoding", gzip_string);
            if (content_type != nullptr) {
              res.add_header(content_type_string, content_type);
            }

            if (is_gzip) {
              res.add_header("Content-Encoding", "gzip");
            }

            // res.set_header("Cache-Control", "public, max-age=86400");
            std::ifstream inf(absolute_path_str);
            if (!inf) {
              CROW_LOG_DEBUG << "failed to read file";
              res.code = 400;
              res.end();
              return;
            }

            std::string body{std::istreambuf_iterator<char>(inf),
                             std::istreambuf_iterator<char>()};

            res.body = body;
            res.end();
          });
    }
  }
}
}  // namespace webassets
}  // namespace crow