Generate HTML

This commit is contained in:
blankie 2023-04-03 16:32:26 +07:00
parent 167d87b41e
commit 419c5e573c
Signed by: blankie
GPG Key ID: CC15FC822C7F61F5
10 changed files with 197 additions and 5 deletions

View File

@ -23,7 +23,8 @@ list(APPEND FLAGS -Werror -Wall -Wextra -Wshadow -Wpedantic -Wno-gnu-anonymous-s
add_link_options(${FLAGS}) add_link_options(${FLAGS})
add_executable(${PROJECT_NAME} main.cpp misc.cpp config.cpp routes/home.cpp) add_executable(${PROJECT_NAME} main.cpp misc.cpp config.cpp servehelper.cpp blankie/serializer.cpp blankie/escape.cpp
routes/home.cpp)
set_target_properties(${PROJECT_NAME} set_target_properties(${PROJECT_NAME}
PROPERTIES PROPERTIES
CXX_STANDARD 20 CXX_STANDARD 20

42
blankie/escape.cpp Normal file
View File

@ -0,0 +1,42 @@
#include <stdexcept>
#include "escape.h"
static inline const char* get_replacement(char c);
namespace blankie {
namespace html {
std::string escape(const std::string& in) {
std::string out;
size_t pos = 0;
size_t last_pos = 0;
out.reserve(in.size());
while ((pos = in.find_first_of("&<>\"'", pos)) != std::string::npos) {
out.append(in, last_pos, pos - last_pos);
out.append(get_replacement(in[pos]));
pos++;
last_pos = pos;
}
if (in.size() > last_pos) {
out.append(in, last_pos);
}
return out;
}
}; // namespace html
}; // namespace blankie
static inline const char* get_replacement(char c) {
switch (c) {
case '&': return "&amp;";
case '<': return "&lt;";
case '>': return "&gt;";
case '"': return "&quot;";
case '\'': return "&#x27;";
default: throw std::runtime_error("Encountered unknown replacement character");
}
}

11
blankie/escape.h Normal file
View File

@ -0,0 +1,11 @@
#pragma once
#include <string>
namespace blankie {
namespace html {
std::string escape(const std::string& in);
}; // namespace html
}; // namespace blankie

54
blankie/serializer.cpp Normal file
View File

@ -0,0 +1,54 @@
#include <cstring>
#include <stdexcept>
#include "escape.h"
#include "serializer.h"
static inline bool should_not_close_tag(const char* tag);
namespace blankie {
namespace html {
std::string Element::serialize() const {
std::string out;
out += '<';
out += this->tag;
for (const auto &[key, value] : this->attributes) {
out += ' ';
out += key;
if (!value.empty()) {
out += "=\"";
out += escape(value);
out += '"';
}
}
out += '>';
for (const Node& node : this->nodes) {
if (const Element* element = std::get_if<Element>(&node)) {
out += element->serialize();
} else if (const char* const* text = std::get_if<const char*>(&node)) {
out += escape(*text);
} else if (const std::string* str = std::get_if<std::string>(&node)) {
out += escape(*str);
} else {
throw std::runtime_error("Encountered unknown node");
}
}
if (!should_not_close_tag(this->tag)) {
out += "</";
out += this->tag;
out += '>';
}
return out;
}
}; // namespace html
}; // namespace blankie
static inline bool should_not_close_tag(const char* tag) {
return !strncmp(tag, "link", 5);
}

30
blankie/serializer.h Normal file
View File

@ -0,0 +1,30 @@
#pragma once
#include <string>
#include <vector>
#include <variant>
#include <utility>
namespace blankie {
namespace html {
struct Element;
typedef std::pair<const char*, std::string> Attribute;
typedef std::variant<Element, const char*, std::string> Node;
struct Element {
const char* tag;
std::vector<Attribute> attributes;
std::vector<Node> nodes;
Element(const char* tag_) : tag(tag_) {}
Element(const char* tag_, std::vector<Node> nodes_)
: tag(tag_), nodes(std::move(nodes_)) {}
Element(const char* tag_, std::vector<Attribute> attributes_, std::vector<Node> nodes_)
: tag(tag_), attributes(std::move(attributes_)), nodes(std::move(nodes_)) {}
std::string serialize() const;
};
}; // namespace html
}; // namespace blankie

View File

@ -20,7 +20,9 @@ int main(int argc, char** argv) {
} }
httplib::Server server; httplib::Server server;
server.Get("/", home_route); server.Get("/", [&](const httplib::Request& req, httplib::Response& res) {
home_route(req, res, config);
});
if (config.bind_port != 0) { if (config.bind_port != 0) {
if (!server.bind_to_port(config.bind_host, config.bind_port)) { if (!server.bind_to_port(config.bind_host, config.bind_port)) {

View File

@ -1,5 +1,8 @@
#include "home.h" #include "home.h"
#include "../servehelper.h"
void home_route(const httplib::Request& req, httplib::Response& res) { void home_route(const httplib::Request& req, httplib::Response& res, const Config& config) {
res.set_content("awoo", "text/plain"); Element body("body");
body.nodes.push_back(Element("h1", {"awoo"}));
serve(req, res, config, "Pixwhile", std::move(body));
} }

View File

@ -2,4 +2,6 @@
#include <httplib/httplib.h> #include <httplib/httplib.h>
void home_route(const httplib::Request& req, httplib::Response& res); struct Config; // forward declaration from config.h
void home_route(const httplib::Request& req, httplib::Response& res, const Config& config);

38
servehelper.cpp Normal file
View File

@ -0,0 +1,38 @@
#include "config.h"
#include "servehelper.h"
static inline std::string get_origin(const httplib::Request& req, const Config& config);
void serve(const httplib::Request& req, httplib::Response& res, const Config& config, std::string title, Element element) {
using namespace std::string_literals;
std::string origin = get_origin(req, config);
std::string css_url = origin + "/style.css";
res.set_header("Content-Security-Policy", "default-src 'none'; img-src 'self'; style-src "s + css_url);
Element html("html", {
Element("head", {
Element("title", {std::move(title)}),
Element("link", {{"rel", "stylesheet"}, {"href", std::move(css_url)}}, {})
}),
std::move(element)
});
res.set_content("<!DOCTYPE html>"s + html.serialize(), "text/html");
}
static inline std::string get_origin(const httplib::Request& req, const Config& config) {
if (req.has_header("X-Canonical-Origin")) {
return req.get_header_value("X-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;
}

9
servehelper.h Normal file
View File

@ -0,0 +1,9 @@
#pragma once
#include <httplib/httplib.h>
#include "blankie/serializer.h"
struct Config; // forward declaration from config.h
using Element = blankie::html::Element;
void serve(const httplib::Request& req, httplib::Response& res, const Config& config, std::string title, Element element);