code: Initial Commit

This commit is contained in:
Vaxry
2025-04-12 20:06:56 +01:00
parent cf925610f4
commit fdc616ac69
23 changed files with 1349 additions and 0 deletions

11
src/GlobalState.hpp Normal file
View File

@@ -0,0 +1,11 @@
#pragma once
#include <string>
#include <memory>
struct SGlobalState {
std::string cwd;
std::string configPath;
};
inline std::unique_ptr<SGlobalState> g_pGlobalState = std::make_unique<SGlobalState>();

22
src/config/Config.cpp Normal file
View File

@@ -0,0 +1,22 @@
#include "Config.hpp"
#include <glaze/glaze.hpp>
#include "../GlobalState.hpp"
static std::string readFileAsText(const std::string& path) {
std::ifstream ifs(path);
auto res = std::string((std::istreambuf_iterator<char>(ifs)), (std::istreambuf_iterator<char>()));
if (res.back() == '\n')
res.pop_back();
return res;
}
CConfig::CConfig() {
auto json = glz::read_jsonc<SConfig>(readFileAsText(g_pGlobalState->cwd + "/" + g_pGlobalState->configPath));
if (!json.has_value())
throw std::runtime_error("No config / bad config format");
m_config = json.value();
}

19
src/config/Config.hpp Normal file
View File

@@ -0,0 +1,19 @@
#pragma once
#include <string>
#include <memory>
class CConfig {
public:
CConfig();
struct SConfig {
int port = 3001;
std::string forward_address = "127.0.0.1:3000";
std::string data_dir = "";
std::string html_dir = "";
unsigned long int max_request_size = 10000000; // 10MB
} m_config;
};
inline std::unique_ptr<CConfig> g_pConfig;

248
src/core/Db.cpp Normal file
View File

@@ -0,0 +1,248 @@
#include "Db.hpp"
#include "../GlobalState.hpp"
#include "../debug/log.hpp"
#include "../config/Config.hpp"
#include <filesystem>
#include <string_view>
#include <algorithm>
constexpr const char* DB_FILE = "data.db";
constexpr const uint64_t DB_TIME_BEFORE_CLEANUP_MS = 1000 * 60 * 10; // 10 mins
constexpr const uint64_t DB_TOKEN_LIFE_LENGTH_S = 60 * 60; // 1hr
constexpr const uint64_t DB_CHALLENGE_LIFE_LENGTH_S = 60 * 10; // 10 mins
//
static std::string dbPath() {
static const std::string path = std::filesystem::canonical(g_pGlobalState->cwd + "/" + g_pConfig->m_config.data_dir).string() + "/" + DB_FILE;
return path;
}
static bool isHashValid(const std::string_view sv) {
return std::all_of(sv.begin(), sv.end(), [](const char& c) { return (c >= 'a' && c <= 'f') || std::isdigit(c); });
}
static bool isIpValid(const std::string_view sv) {
return std::all_of(sv.begin(), sv.end(), [](const char& c) { return c == '.' || c == ':' || std::isdigit(c); });
}
CDatabase::CDatabase() {
if (std::filesystem::exists(dbPath())) {
if (sqlite3_open(dbPath().c_str(), &m_db) != SQLITE_OK)
throw std::runtime_error("failed to open sqlite3 db");
cleanupDb();
return;
}
Debug::log(LOG, "Database not present, creating one");
if (sqlite3_open(dbPath().c_str(), &m_db) != SQLITE_OK)
throw std::runtime_error("failed to open sqlite3 db");
// create db layout
char* errmsg = nullptr;
const char* CHALLENGE_TABLE = R"#(
CREATE TABLE challenges (
nonce TEXT NOT NULL,
ip TEXT NOT NULL,
difficulty INTEGER NOT NULL,
epoch INTEGER NOT NULL,
CONSTRAINT PK PRIMARY KEY (nonce)
);)#";
sqlite3_exec(m_db, CHALLENGE_TABLE, [](void* data, int len, char** a, char** b) -> int { return 0; }, nullptr, &errmsg);
const char* TOKENS_TABLE = R"#(
CREATE TABLE tokens (
token TEXT NOT NULL,
ip TEXT NOT NULL,
epoch INTEGER NOT NULL,
CONSTRAINT PK PRIMARY KEY (token)
);)#";
sqlite3_exec(m_db, TOKENS_TABLE, [](void* data, int len, char** a, char** b) -> int { return 0; }, nullptr, &errmsg);
}
CDatabase::~CDatabase() {
if (m_db)
sqlite3_close(m_db);
}
void CDatabase::addChallenge(const SDatabaseChallengeEntry& entry) {
if (!isHashValid(entry.nonce))
return;
if (!isIpValid(entry.ip))
return;
const std::string CMD = fmt::format(R"#(
INSERT INTO challenges VALUES (
"{}", "{}", {}, {}
);)#",
entry.nonce, entry.ip, entry.difficulty, entry.epoch);
char* errmsg = nullptr;
sqlite3_exec(m_db, CMD.c_str(), nullptr, nullptr, &errmsg);
if (errmsg)
Debug::log(ERR, "sqlite3 error: tried to persist:\n{}\nGot: {}", CMD, errmsg);
}
std::optional<SDatabaseChallengeEntry> CDatabase::getChallenge(const std::string& nonce) {
if (!isHashValid(nonce))
return std::nullopt;
const std::string CMD = fmt::format(R"#(
SELECT * FROM challenges WHERE nonce = "{}";
)#",
nonce);
char* errmsg = nullptr;
CDatabase::SQueryResult result;
sqlite3_exec(
m_db, CMD.c_str(),
[](void* result, int len, char** a, char** b) -> int {
auto res = reinterpret_cast<CDatabase::SQueryResult*>(result);
for (size_t i = 0; i < len; ++i) {
res->result.push_back(a[i]);
res->result2.push_back(b[i]);
}
return 0;
},
&result, &errmsg);
if (errmsg || result.result.size() < 4)
return std::nullopt;
return SDatabaseChallengeEntry{.nonce = nonce, .difficulty = std::stoi(result.result.at(2)), .epoch = std::stoull(result.result.at(3)), .ip = result.result.at(1)};
}
void CDatabase::dropChallenge(const std::string& nonce) {
if (!isHashValid(nonce))
return;
const std::string CMD = fmt::format(R"#(
DELETE FROM challenges WHERE token = "{}"
)#",
nonce);
char* errmsg = nullptr;
sqlite3_exec(m_db, CMD.c_str(), nullptr, nullptr, &errmsg);
if (errmsg)
Debug::log(ERR, "sqlite3 error: tried to persist:\n{}\nGot: {}", CMD, errmsg);
}
void CDatabase::addToken(const SDatabaseTokenEntry& entry) {
if (!isHashValid(entry.token))
return;
if (!isIpValid(entry.ip))
return;
const std::string CMD = fmt::format(R"#(
INSERT INTO tokens VALUES (
"{}", "{}", {}
);)#",
entry.token, entry.ip, entry.epoch);
char* errmsg = nullptr;
sqlite3_exec(m_db, CMD.c_str(), nullptr, nullptr, &errmsg);
if (errmsg)
Debug::log(ERR, "sqlite3 error: tried to persist:\n{}\nGot: {}", CMD, errmsg);
}
void CDatabase::dropToken(const std::string& token) {
if (!isHashValid(token))
return;
const std::string CMD = fmt::format(R"#(
DELETE FROM tokens WHERE token = "{}"
)#",
token);
char* errmsg = nullptr;
sqlite3_exec(m_db, CMD.c_str(), nullptr, nullptr, &errmsg);
if (errmsg)
Debug::log(ERR, "sqlite3 error: tried to persist:\n{}\nGot: {}", CMD, errmsg);
}
std::optional<SDatabaseTokenEntry> CDatabase::getToken(const std::string& token) {
if (!isHashValid(token))
return std::nullopt;
if (shouldCleanupDb())
cleanupDb();
const std::string CMD = fmt::format(R"#(
SELECT * FROM tokens WHERE token = "{}";
)#",
token);
char* errmsg = nullptr;
CDatabase::SQueryResult result;
sqlite3_exec(
m_db, CMD.c_str(),
[](void* result, int len, char** a, char** b) -> int {
auto res = reinterpret_cast<CDatabase::SQueryResult*>(result);
for (size_t i = 0; i < len; ++i) {
res->result.push_back(a[i]);
res->result2.push_back(b[i]);
}
return 0;
},
&result, &errmsg);
if (errmsg || result.result.size() < 3)
return std::nullopt;
return SDatabaseTokenEntry{.token = token, .epoch = std::stoull(result.result.at(2)), .ip = result.result.at(1)};
}
bool CDatabase::shouldCleanupDb() {
const auto TIME = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now().time_since_epoch()).count();
const auto LAST = std::chrono::duration_cast<std::chrono::milliseconds>(m_lastDbCleanup.time_since_epoch()).count();
if (TIME - LAST > DB_TIME_BEFORE_CLEANUP_MS)
return true;
return false;
}
void CDatabase::cleanupDb() {
m_lastDbCleanup = std::chrono::steady_clock::now();
const auto TIME = std::chrono::milliseconds(std::time(nullptr)).count();
std::string CMD = fmt::format(R"#(
DELETE FROM tokens WHERE epoch < {};
)#",
TIME - DB_TOKEN_LIFE_LENGTH_S);
char* errmsg = nullptr;
sqlite3_exec(m_db, CMD.c_str(), nullptr, nullptr, &errmsg);
if (errmsg)
Debug::log(ERR, "sqlite3 error: tried to persist:\n{}\nGot: {}", CMD, errmsg);
CMD = fmt::format(R"#(
DELETE FROM challenges WHERE epoch < {};
)#",
TIME - DB_CHALLENGE_LIFE_LENGTH_S);
sqlite3_exec(m_db, CMD.c_str(), nullptr, nullptr, &errmsg);
if (errmsg)
Debug::log(ERR, "sqlite3 error: tried to persist:\n{}\nGot: {}", CMD, errmsg);
}

50
src/core/Db.hpp Normal file
View File

@@ -0,0 +1,50 @@
#pragma once
#include <sqlite3.h>
#include <string>
#include <chrono>
#include <optional>
#include <memory>
struct SDatabaseChallengeEntry {
std::string nonce = "";
int difficulty = 0;
unsigned long int epoch = std::chrono::system_clock::now().time_since_epoch() / std::chrono::seconds(1);
std::string ip = "";
};
struct SDatabaseTokenEntry {
std::string token = "";
unsigned long int epoch = std::chrono::system_clock::now().time_since_epoch() / std::chrono::seconds(1);
std::string ip = "";
};
class CDatabase {
public:
CDatabase();
~CDatabase();
void addChallenge(const SDatabaseChallengeEntry& entry);
std::optional<SDatabaseChallengeEntry> getChallenge(const std::string& nonce);
void dropChallenge(const std::string& nonce);
void addToken(const SDatabaseTokenEntry& entry);
std::optional<SDatabaseTokenEntry> getToken(const std::string& token);
void dropToken(const std::string& token);
private:
struct SQueryResult {
bool failed = false;
std::string error = "";
std::vector<std::string> result;
std::vector<std::string> result2;
};
sqlite3* m_db = nullptr;
std::chrono::steady_clock::time_point m_lastDbCleanup = std::chrono::steady_clock::now();
void cleanupDb();
bool shouldCleanupDb();
};
inline std::unique_ptr<CDatabase> g_pDB;

330
src/core/Handler.cpp Normal file
View File

@@ -0,0 +1,330 @@
#include "Handler.hpp"
#include "../headers/authorization.hpp"
#include "../headers/cfHeader.hpp"
#include "../headers/xforwardfor.hpp"
#include "../debug/log.hpp"
#include "../GlobalState.hpp"
#include "../config/Config.hpp"
#include "Db.hpp"
#include <fstream>
#include <filesystem>
#include <random>
#include <sstream>
#include <pistache/client.h>
#include <tinylates/tinylates.hpp>
#include <fmt/format.h>
#include <glaze/glaze.hpp>
#include <openssl/evp.h>
constexpr const uint64_t TOKEN_MAX_AGE_MS = 1000 * 60 * 60; // 1hr
//
static std::string readFileAsText(const std::string& path) {
std::ifstream ifs(path);
auto res = std::string((std::istreambuf_iterator<char>(ifs)), (std::istreambuf_iterator<char>()));
if (res.back() == '\n')
res.pop_back();
return res;
}
static std::string generateNonce() {
static std::random_device dev;
std::mt19937 engine(dev());
std::uniform_int_distribution<> distribution(0, INT32_MAX);
std::stringstream ss;
for (size_t i = 0; i < 32; ++i) {
ss << fmt::format("{:08x}", distribution(engine));
}
return ss.str();
}
static std::string generateToken() {
static std::random_device dev;
std::mt19937 engine(dev());
std::uniform_int_distribution<> distribution(0, INT32_MAX);
std::stringstream ss;
for (size_t i = 0; i < 16; ++i) {
ss << fmt::format("{:08x}", distribution(engine));
}
return ss.str();
}
static std::string sha256(const std::string& string) {
EVP_MD_CTX* ctx = EVP_MD_CTX_new();
if (!ctx)
return "";
if (!EVP_DigestInit(ctx, EVP_sha256())) {
EVP_MD_CTX_free(ctx);
return "";
}
if (!EVP_DigestUpdate(ctx, string.c_str(), string.size())) {
EVP_MD_CTX_free(ctx);
return "";
}
uint8_t buf[32];
if (!EVP_DigestFinal(ctx, buf, nullptr)) {
EVP_MD_CTX_free(ctx);
return "";
}
std::stringstream ss;
for (size_t i = 0; i < 32; ++i) {
ss << fmt::format("{:02x}", buf[i]);
}
return ss.str();
}
void CServerHandler::onRequest(const Pistache::Http::Request& req, Pistache::Http::ResponseWriter response) {
const auto HEADERS = req.headers();
std::shared_ptr<const Pistache::Http::Header::Host> hostHeader;
std::shared_ptr<const Pistache::Http::Header::ContentType> contentTypeHeader;
std::shared_ptr<const CFConnectingIPHeader> cfHeader;
std::shared_ptr<const XForwardedForHeader> xForwardedForHeader;
std::shared_ptr<const AuthorizationHeader> authHeader;
try {
hostHeader = Pistache::Http::Header::header_cast<Pistache::Http::Header::Host>(HEADERS.get("Host"));
} catch (std::exception& e) {
Debug::log(ERR, "Request has no Host header?");
response.send(Pistache::Http::Code::Bad_Request, "Bad Request");
return;
}
try {
cfHeader = Pistache::Http::Header::header_cast<CFConnectingIPHeader>(HEADERS.get("cf-connecting-ip"));
} catch (std::exception& e) {
; // silent ignore
}
try {
xForwardedForHeader = Pistache::Http::Header::header_cast<XForwardedForHeader>(HEADERS.get("X-Forwarded-For"));
} catch (std::exception& e) {
; // silent ignore
}
try {
authHeader = Pistache::Http::Header::header_cast<AuthorizationHeader>(HEADERS.get("Authorization"));
} catch (std::exception& e) {
; // silent ignore
}
try {
contentTypeHeader = Pistache::Http::Header::header_cast<Pistache::Http::Header::ContentType>(HEADERS.get("Content-Type"));
} catch (std::exception& e) {
; // silent ignore
}
Debug::log(LOG, "Got request for: {}:{}{}", hostHeader->host(), hostHeader->port().toString(), req.resource());
Debug::log(LOG, "Request author: IP {}", req.address().host());
if (cfHeader)
Debug::log(LOG, "CloudFlare reports IP: {}", cfHeader->ip());
else
Debug::log(WARN, "Connection does not come through CloudFlare");
if (req.resource() == "/checkpoint/challenge") {
if (req.method() == Pistache::Http::Method::Post)
challengeSubmitted(req, response);
else
response.send(Pistache::Http::Code::Bad_Request, "Bad Request");
return;
}
if (req.cookies().has("CheckpointToken")) {
// check the token
const auto TOKEN = g_pDB->getToken(req.cookies().get("CheckpointToken").value);
if (TOKEN) {
const auto AGE = std::chrono::milliseconds(std::time(nullptr)).count() - TOKEN->epoch;
if (AGE <= TOKEN_MAX_AGE_MS && TOKEN->ip == (cfHeader ? cfHeader->ip() : req.address().host())) {
proxyPass(req, response);
return;
} else // token has been used from a different IP or is expired. Nuke it.
g_pDB->dropToken(TOKEN->token);
}
}
serveStop(req, response);
}
void CServerHandler::onTimeout(const Pistache::Http::Request& request, Pistache::Http::ResponseWriter response) {
response.send(Pistache::Http::Code::Request_Timeout, "Timeout").then([=](ssize_t) {}, PrintException());
}
void CServerHandler::challengeSubmitted(const Pistache::Http::Request& req, Pistache::Http::ResponseWriter& response) {
const auto JSON = req.body();
std::shared_ptr<const CFConnectingIPHeader> cfHeader;
try {
cfHeader = Pistache::Http::Header::header_cast<CFConnectingIPHeader>(req.headers().get("cf-connecting-ip"));
} catch (std::exception& e) {
; // silent ignore
}
auto json = glz::read_json<SChallengeResponse>(JSON);
STokenResponse resp;
if (!json) {
resp.error = "bad input";
response.send(Pistache::Http::Code::Bad_Request, glz::write_json(resp).value());
return;
}
auto val = json.value();
const auto CHALLENGE = g_pDB->getChallenge(val.challenge);
if (!CHALLENGE.has_value()) {
resp.error = "bad challenge";
response.send(Pistache::Http::Code::Bad_Request, glz::write_json(resp).value());
return;
}
if (CHALLENGE->ip != req.address().host()) {
resp.error = "bad challenge";
response.send(Pistache::Http::Code::Bad_Request, glz::write_json(resp).value());
return;
}
// drop challenge already.
g_pDB->dropChallenge(val.challenge);
// verify challenge
const auto SHA = sha256(val.challenge + std::to_string(val.solution));
for (int i = 0; i < CHALLENGE->difficulty; ++i) {
if (SHA.at(i) != '0') {
resp.error = "bad solution";
response.send(Pistache::Http::Code::Bad_Request, glz::write_json(resp).value());
return;
}
}
// correct solution, return a token
const auto TOKEN = generateToken();
g_pDB->addToken(SDatabaseTokenEntry{.token = TOKEN, .ip = (cfHeader ? cfHeader->ip() : req.address().host())});
resp.success = true;
resp.token = TOKEN;
response.send(Pistache::Http::Code::Ok, glz::write_json(resp).value());
}
void CServerHandler::serveStop(const Pistache::Http::Request& req, Pistache::Http::ResponseWriter& response) {
static const auto PATH = std::filesystem::canonical(g_pGlobalState->cwd + "/" + g_pConfig->m_config.html_dir).string();
/* static */ const auto PAGE_INDEX = readFileAsText(PATH + "/index.min.html");
CTinylates page(PAGE_INDEX);
page.setTemplateRoot(PATH);
const auto NONCE = generateNonce();
const auto DIFFICULTY = 4;
g_pDB->addChallenge(SDatabaseChallengeEntry{.nonce = NONCE, .difficulty = DIFFICULTY, .ip = req.address().host()});
page.add("challengeDifficulty", CTinylatesProp(std::to_string(DIFFICULTY)));
page.add("challengeNonce", CTinylatesProp(NONCE));
response.send(Pistache::Http::Code::Ok, page.render().value_or("error"));
}
void CServerHandler::proxyPass(const Pistache::Http::Request& req, Pistache::Http::ResponseWriter& response) {
Pistache::Http::Experimental::Client client;
client.init(Pistache::Http::Experimental::Client::options().threads(1).maxConnectionsPerHost(8));
const std::string FORWARD_ADDR = g_pConfig->m_config.forward_address;
switch (req.method()) {
// there are some crazy semantics going on here, idk how to make this cleaner with less c+p
case Pistache::Http::Method::Get: {
Debug::log(LOG, "Get: Forwarding to {}", FORWARD_ADDR + req.resource());
auto builder = client.get(FORWARD_ADDR + req.resource()).body(req.body());
for (auto it = req.cookies().begin(); it != req.cookies().end(); ++it) {
builder.cookie(*it);
}
builder.timeout(std::chrono::milliseconds(10000));
auto resp = builder.send();
resp.then([&](Pistache::Http::Response resp) { response.send(Pistache::Http::Code::Ok, resp.body()); },
[&](std::exception_ptr e) { response.send(Pistache::Http::Code::Internal_Server_Error, "Internal Proxy Error"); });
Pistache::Async::Barrier<Pistache::Http::Response> b(resp);
b.wait_for(std::chrono::seconds(10));
break;
}
case Pistache::Http::Method::Post: {
Debug::log(LOG, "Post: Forwarding to {}", FORWARD_ADDR + req.resource());
auto builder = client.post(FORWARD_ADDR + req.resource()).body(req.body());
for (auto it = req.cookies().begin(); it != req.cookies().end(); ++it) {
builder.cookie(*it);
}
builder.timeout(std::chrono::milliseconds(10000));
auto resp = builder.send();
resp.then([&](Pistache::Http::Response resp) { response.send(Pistache::Http::Code::Ok, resp.body()); },
[&](std::exception_ptr e) { response.send(Pistache::Http::Code::Internal_Server_Error, "Internal Proxy Error"); });
Pistache::Async::Barrier<Pistache::Http::Response> b(resp);
b.wait_for(std::chrono::seconds(10));
break;
}
case Pistache::Http::Method::Put: {
Debug::log(LOG, "Put: Forwarding to {}", FORWARD_ADDR + req.resource());
auto builder = client.put(FORWARD_ADDR + req.resource()).body(req.body());
for (auto it = req.cookies().begin(); it != req.cookies().end(); ++it) {
builder.cookie(*it);
}
builder.timeout(std::chrono::milliseconds(10000));
auto resp = builder.send();
resp.then([&](Pistache::Http::Response resp) { response.send(Pistache::Http::Code::Ok, resp.body()); },
[&](std::exception_ptr e) { response.send(Pistache::Http::Code::Internal_Server_Error, "Internal Proxy Error"); });
Pistache::Async::Barrier<Pistache::Http::Response> b(resp);
b.wait_for(std::chrono::seconds(10));
break;
}
case Pistache::Http::Method::Delete: {
Debug::log(LOG, "Delete: Forwarding to {}", FORWARD_ADDR + req.resource());
auto builder = client.del(FORWARD_ADDR + req.resource()).body(req.body());
for (auto it = req.cookies().begin(); it != req.cookies().end(); ++it) {
builder.cookie(*it);
}
builder.timeout(std::chrono::milliseconds(10000));
auto resp = builder.send();
resp.then([&](Pistache::Http::Response resp) { response.send(Pistache::Http::Code::Ok, resp.body()); },
[&](std::exception_ptr e) { response.send(Pistache::Http::Code::Internal_Server_Error, "Internal Proxy Error"); });
Pistache::Async::Barrier<Pistache::Http::Response> b(resp);
b.wait_for(std::chrono::seconds(10));
break;
}
case Pistache::Http::Method::Patch: {
Debug::log(LOG, "Patch: Forwarding to {}", FORWARD_ADDR + req.resource());
auto builder = client.patch(FORWARD_ADDR + req.resource()).body(req.body());
for (auto it = req.cookies().begin(); it != req.cookies().end(); ++it) {
builder.cookie(*it);
}
builder.timeout(std::chrono::milliseconds(10000));
auto resp = builder.send();
resp.then([&](Pistache::Http::Response resp) { response.send(Pistache::Http::Code::Ok, resp.body()); },
[&](std::exception_ptr e) { response.send(Pistache::Http::Code::Internal_Server_Error, "Internal Proxy Error"); });
Pistache::Async::Barrier<Pistache::Http::Response> b(resp);
b.wait_for(std::chrono::seconds(10));
break;
}
default: {
response.send(Pistache::Http::Code::Internal_Server_Error, "Invalid request type for proxy");
}
}
client.shutdown();
}

28
src/core/Handler.hpp Normal file
View File

@@ -0,0 +1,28 @@
#pragma once
#include <pistache/http.h>
class CServerHandler : public Pistache::Http::Handler {
HTTP_PROTOTYPE(CServerHandler)
void onRequest(const Pistache::Http::Request& req, Pistache::Http::ResponseWriter response);
void onTimeout(const Pistache::Http::Request& request, Pistache::Http::ResponseWriter response);
private:
void serveStop(const Pistache::Http::Request& req, Pistache::Http::ResponseWriter& response);
void proxyPass(const Pistache::Http::Request& req, Pistache::Http::ResponseWriter& response);
void challengeSubmitted(const Pistache::Http::Request& req, Pistache::Http::ResponseWriter& response);
struct SChallengeResponse {
std::string challenge;
unsigned long int solution = 0;
};
struct STokenResponse {
bool success = false;
std::string token = "";
std::string error = "";
};
};

36
src/debug/log.hpp Normal file
View File

@@ -0,0 +1,36 @@
#pragma once
#include <string>
#include <fmt/format.h>
#include <iostream>
enum LogLevel {
NONE = -1,
LOG = 0,
WARN,
ERR,
CRIT,
INFO,
TRACE
};
namespace Debug {
template <typename... Args>
void log(LogLevel level, fmt::format_string<Args...> fmt, Args&&... args) {
std::string logMsg = "";
switch (level) {
case LOG: logMsg += "[LOG] "; break;
case WARN: logMsg += "[WARN] "; break;
case ERR: logMsg += "[ERR] "; break;
case CRIT: logMsg += "[CRITICAL] "; break;
case INFO: logMsg += "[INFO] "; break;
case TRACE: logMsg += "[TRACE] "; break;
default: break;
}
logMsg += fmt::vformat(fmt.get(), fmt::make_format_args(args...));
std::cout << logMsg << "\n";
}
};

View File

@@ -0,0 +1,24 @@
#include <pistache/http_headers.h>
#include <pistache/net.h>
class AuthorizationHeader : public Pistache::Http::Header::Header {
public:
NAME("Authorization");
AuthorizationHeader() = default;
void parse(const std::string& str) override {
m_token = str;
}
void write(std::ostream& os) const override {
os << m_token;
}
std::string token() const {
return m_token;
}
private:
std::string m_token = "";
};

24
src/headers/cfHeader.hpp Normal file
View File

@@ -0,0 +1,24 @@
#include <pistache/http_headers.h>
#include <pistache/net.h>
class CFConnectingIPHeader : public Pistache::Http::Header::Header {
public:
NAME("cf-connecting-ip");
CFConnectingIPHeader() = default;
void parse(const std::string& str) override {
m_ip = str;
}
void write(std::ostream& os) const override {
os << m_ip;
}
std::string ip() const {
return m_ip;
}
private:
std::string m_ip = "";
};

View File

@@ -0,0 +1,24 @@
#include <pistache/http_headers.h>
#include <pistache/net.h>
class XForwardedForHeader : public Pistache::Http::Header::Header {
public:
NAME("X-Forwarded-For");
XForwardedForHeader() = default;
void parse(const std::string& str) override {
m_for = str;
}
void write(std::ostream& os) const override {
os << m_for;
}
std::string from() const {
return m_for;
}
private:
std::string m_for = "";
};

115
src/main.cpp Normal file
View File

@@ -0,0 +1,115 @@
#include <iostream>
#include <filesystem>
#include <pistache/common.h>
#include <pistache/cookie.h>
#include <pistache/endpoint.h>
#include <pistache/http.h>
#include <pistache/http_headers.h>
#include <pistache/net.h>
#include <pistache/peer.h>
#include "headers/authorization.hpp"
#include "headers/xforwardfor.hpp"
#include "headers/cfHeader.hpp"
#include "debug/log.hpp"
#include "core/Handler.hpp"
#include "core/Db.hpp"
#include "config/Config.hpp"
#include "GlobalState.hpp"
#include <signal.h>
int main(int argc, char** argv, char** envp) {
if (argc < 2) {
Debug::log(CRIT, "Missing param for websites storage");
return 1;
}
std::vector<std::string> ARGS{};
ARGS.resize(argc);
for (int i = 0; i < argc; ++i) {
ARGS[i] = std::string{argv[i]};
}
std::vector<std::string> command;
g_pGlobalState->cwd = std::filesystem::current_path();
for (int i = 1; i < argc; ++i) {
if (ARGS[i].starts_with("-")) {
if (ARGS[i] == "--help" || ARGS[i] == "-h") {
std::cout << "-h [html_root] -p [port]\n";
return 0;
} else if ((ARGS[i] == "--config" || ARGS[i] == "-c") && i + 1 < argc) {
g_pGlobalState->configPath = ARGS[i + 1];
i++;
} else {
std::cerr << "Unrecognized / invalid use of option " << ARGS[i] << "\nContinuing...\n";
continue;
}
} else
command.push_back(ARGS[i]);
}
g_pConfig = std::make_unique<CConfig>();
if (g_pConfig->m_config.html_dir.empty() || g_pConfig->m_config.data_dir.empty())
return 1;
sigset_t signals;
if (sigemptyset(&signals) != 0 || sigaddset(&signals, SIGTERM) != 0 || sigaddset(&signals, SIGINT) != 0 || sigaddset(&signals, SIGQUIT) != 0 ||
sigaddset(&signals, SIGPIPE) != 0 || sigaddset(&signals, SIGALRM) != 0 || sigprocmask(SIG_BLOCK, &signals, nullptr) != 0)
return 1;
int threads = 1;
Pistache::Address address = {Pistache::Ipv4::any(), (uint16_t)g_pConfig->m_config.port};
Debug::log(LOG, "Starting the server on {}:{}\n", address.host(), address.port().toString());
Pistache::Http::Header::Registry::instance().registerHeader<CFConnectingIPHeader>();
Pistache::Http::Header::Registry::instance().registerHeader<XForwardedForHeader>();
g_pDB = std::make_unique<CDatabase>();
auto endpoint = std::make_unique<Pistache::Http::Endpoint>(address);
auto opts = Pistache::Http::Endpoint::options().threads(threads).flags(Pistache::Tcp::Options::ReuseAddr | Pistache::Tcp::Options::ReusePort);
opts.maxRequestSize(g_pConfig->m_config.max_request_size); // 150MB TODO: configurable
endpoint->init(opts);
auto handler = Pistache::Http::make_handler<CServerHandler>();
endpoint->setHandler(handler);
endpoint->serveThreaded();
bool terminate = false;
while (!terminate) {
int number = 0;
int status = sigwait(&signals, &number);
if (status != 0) {
Debug::log(CRIT, "sigwait threw {} :(", status);
break;
}
Debug::log(LOG, "Caught signal {}", number);
switch (number) {
case SIGINT: terminate = true; break;
case SIGTERM: terminate = true; break;
case SIGQUIT: terminate = true; break;
case SIGPIPE: break;
case SIGALRM: break;
}
}
sigprocmask(SIG_UNBLOCK, &signals, nullptr);
Debug::log(LOG, "Shutting down, bye!");
endpoint->shutdown();
endpoint = nullptr;
return 0;
}