coyote/servehelper.cpp

156 lines
4.8 KiB
C++
Raw Normal View History

2023-11-22 08:39:24 +00:00
#include <memory>
#include <exception>
#include <stdexcept>
#include <FastHash.h>
#include <curl/curl.h>
#include "config.h"
#include "servehelper.h"
#include "routes/routes.h"
class CurlUrlException : public std::exception {
public:
CurlUrlException(CURLUcode code_) : code(code_) {}
const char* what() const noexcept {
return curl_url_strerror(this->code);
}
CURLUcode code;
};
void serve(const httplib::Request& req, httplib::Response& res, std::string title, Element element, Nodes extra_head) {
using namespace std::string_literals;
std::string css_url = get_origin(req) + "/style.css";
res.set_header("Content-Security-Policy", "default-src 'none'; img-src https:; media-src: https:; style-src "s + css_url);
Element head("head", {
Element("meta", {{"charset", "utf-8"}}, {}),
Element("title", {std::move(title)}),
Element("link", {{"rel", "stylesheet"}, {"href", std::move(css_url) + "?v=" + std::to_string(css_hash)}}, {}),
Element("meta", {{"name", "viewport"}, {"content", "width=device-width,initial-scale=1"}}, {})
});
head.nodes.reserve(head.nodes.size() + extra_head.size());
head.nodes.insert(head.nodes.end(), extra_head.begin(), extra_head.end());
std::string html = "<!DOCTYPE html>"s + Element("html", {
std::move(head),
std::move(element)
}).serialize();
uint64_t hash = FastHash(html.data(), html.size(), 0);
res.set_header("ETag", std::string(1, '"') + std::to_string(hash) + '"');
if (should_send_304(req, hash)) {
res.status = 304;
res.set_header("Content-Length", std::to_string(html.size()));
res.set_header("Content-Type", "text/html");
} else {
res.set_content(std::move(html), "text/html");
}
}
void serve_error(const httplib::Request& req, httplib::Response& res,
std::string title, std::optional<std::string> subtitle, std::optional<std::string> info) {
Element error_div("div", {{"class", "error"}}, {
Element("h2", {title})
});
if (subtitle) {
error_div.nodes.push_back(Element("p", {
std::move(*subtitle)
}));
}
if (info) {
error_div.nodes.push_back(Element("pre", {
Element("code", {std::move(*info)})
}));
}
Element body("body", {std::move(error_div)});
serve(req, res, std::move(title), std::move(body));
}
void serve_redirect(const httplib::Request& req, httplib::Response& res, std::string url, bool permanent) {
using namespace std::string_literals;
Element body("body", {
"Redirecting to ",
Element("a", {{"href", url}}, {url}),
""
});
res.set_redirect(url, permanent ? 301 : 302);
serve(req, res, "Redirecting to "s + std::move(url) + "", std::move(body));
}
std::string get_origin(const httplib::Request& req) {
if (req.has_header("X-Canonical-Origin")) {
return req.get_header_value("X-Canonical-Origin");
}
if (config.canonical_origin) {
return *config.canonical_origin;
}
std::string origin = "http://";
if (req.has_header("Host")) {
origin += req.get_header_value("Host");
} else {
origin += config.bind_host;
if (config.bind_port != 80) {
origin += ':' + std::to_string(config.bind_port);
}
}
return origin;
}
std::string proxy_mastodon_url(const httplib::Request& req, const std::string& url_str) {
using CurlStr = std::unique_ptr<char, decltype(&curl_free)>;
std::unique_ptr<CURLU, decltype(&curl_url_cleanup)> url(curl_url(), curl_url_cleanup);
if (!url) {
throw std::bad_alloc();
}
CURLUcode code = curl_url_set(url.get(), CURLUPART_URL, url_str.c_str(), 0);
if (code) {
throw CurlUrlException(code);
}
auto get_part = [&](CURLUPart part) {
char* content;
CURLUcode code = curl_url_get(url.get(), part, &content, 0);
if (code) {
throw CurlUrlException(code);
}
return CurlStr(content, curl_free);
};
CurlStr host = get_part(CURLUPART_HOST);
CurlStr path = get_part(CURLUPART_PATH);
CurlStr query = get_part(CURLUPART_QUERY);
CurlStr fragment = get_part(CURLUPART_FRAGMENT);
std::string new_url = get_origin(req) + '/' + host.get() + path.get();
if (query) {
new_url += '?';
new_url += query.get();
}
if (fragment) {
new_url += '#';
new_url += fragment.get();
}
return new_url;
}
bool should_send_304(const httplib::Request& req, uint64_t hash) {
std::string header = req.get_header_value("If-None-Match");
if (header == "*") {
return true;
}
size_t pos = header.find(std::string(1, '"') + std::to_string(hash) + '"');
return pos != std::string::npos && (pos == 0 || header[pos - 1] != '/');
}