#include "crow.h" #include "routes.h" #include #include #include #include #include namespace routes { namespace { std::string static_dir() { if (const char* env = std::getenv("CXWEBAPP_STATIC_DIR")) { return std::string(env); } #ifdef CXWEBAPP_STATIC_DIR_DEFAULT return std::string(CXWEBAPP_STATIC_DIR_DEFAULT); #else return "static"; #endif } std::string read_file(const std::string& path) { std::ifstream ifs(path, std::ios::binary); if (!ifs.is_open()) return ""; std::ostringstream ss; ss << ifs.rdbuf(); return ss.str(); } std::string mime_for(const std::string& path) { auto ends = [&](const char* s) { size_t n = std::char_traits::length(s); return path.size() >= n && path.compare(path.size() - n, n, s) == 0; }; if (ends(".html")) return "text/html; charset=utf-8"; if (ends(".css")) return "text/css; charset=utf-8"; if (ends(".js")) return "application/javascript; charset=utf-8"; if (ends(".mjs")) return "application/javascript; charset=utf-8"; if (ends(".worker.js")) return "application/javascript; charset=utf-8"; if (ends(".wasm")) return "application/wasm"; if (ends(".json")) return "application/json; charset=utf-8"; if (ends(".data")) return "application/octet-stream"; if (ends(".svg")) return "image/svg+xml"; if (ends(".png")) return "image/png"; if (ends(".jpg") || ends(".jpeg")) return "image/jpeg"; if (ends(".gif")) return "image/gif"; if (ends(".ico")) return "image/x-icon"; if (ends(".woff2")) return "font/woff2"; if (ends(".woff")) return "font/woff"; if (ends(".map")) return "application/json; charset=utf-8"; if (ends(".txt")) return "text/plain; charset=utf-8"; return "application/octet-stream"; } bool is_safe(const std::string& path) { if (path.empty()) return false; if (path.front() == '/') return false; if (path.find("..") != std::string::npos) return false; if (path.find('\0') != std::string::npos) return false; return true; } bool has_segment(const std::string& path, const std::string& segment) { return path == segment || path.rfind(segment + "/", 0) == 0 || path.find("/" + segment + "/") != std::string::npos; } std::string cache_for(const std::string& rel) { if (rel == "index.html" || rel == "service-catalog.json" || rel == "wasm/modules.json") { return "no-cache, max-age=0, must-revalidate"; } if (has_segment(rel, "assets") || has_segment(rel, "wasm")) { return "public, max-age=31536000, immutable"; } return "public, max-age=300"; } std::string join_path(std::initializer_list parts) { std::string joined; for (const auto& part : parts) { if (joined.empty()) { joined = part; } else { joined += "/" + part; } } return joined; } crow::response serve_static(const std::string& rel) { if (!is_safe(rel)) return crow::response(400, "bad path"); const std::string full = static_dir() + "/" + rel; auto body = read_file(full); if (body.empty()) return crow::response(404, "not found"); crow::response resp(body); resp.add_header("Content-Type", mime_for(rel)); resp.add_header("Cache-Control", cache_for(rel)); return resp; } } // anonymous namespace void register_pages(crow::SimpleApp& app) { CROW_ROUTE(app, "/") ([]() { return serve_static("index.html"); }); CROW_ROUTE(app, "/favicon.ico") ([]() { return serve_static("favicon.svg"); }); // Crow's `` matches a single segment. Register enough depth for Vite and Wasm side files. CROW_ROUTE(app, "/static/") ([](const std::string& a) { return serve_static(a); }); CROW_ROUTE(app, "/static//") ([](const std::string& a, const std::string& b) { return serve_static(a + "/" + b); }); CROW_ROUTE(app, "/static///") ([](const std::string& a, const std::string& b, const std::string& c) { return serve_static(join_path({a, b, c})); }); CROW_ROUTE(app, "/static////") ([](const std::string& a, const std::string& b, const std::string& c, const std::string& d) { return serve_static(join_path({a, b, c, d})); }); CROW_ROUTE(app, "/static/////") ([](const std::string& a, const std::string& b, const std::string& c, const std::string& d, const std::string& e) { return serve_static(join_path({a, b, c, d, e})); }); CROW_ROUTE(app, "/static//////") ([](const std::string& a, const std::string& b, const std::string& c, const std::string& d, const std::string& e, const std::string& f) { return serve_static(join_path({a, b, c, d, e, f})); }); } } // namespace routes