handler: Handle any checkpoint resource, determine Content-Type with libmagic (#22)

* Handle any resource

* Determine Content-Type for static files with libmagic

---------

Co-authored-by: Vaxry <vaxry@vaxry.net>
This commit is contained in:
catfromplan9
2025-04-21 19:28:31 +00:00
committed by GitHub
parent f199569ff8
commit 5b2744584a
3 changed files with 40 additions and 9 deletions

View File

@@ -29,7 +29,7 @@ add_compile_definitions(CHECKPOINT_VERSION="${CHECKPOINT_VERSION}")
find_package(PkgConfig REQUIRED)
pkg_check_modules(deps IMPORTED_TARGET openssl re2)
pkg_check_modules(deps IMPORTED_TARGET openssl re2 libmagic)
target_include_directories(checkpoint
PRIVATE

View File

@@ -26,7 +26,7 @@ If you are using this, it's almost certain search engines will stop indexing you
## Setup guide
1. Clone and build this repo. You will need `openssl`, `g++>=12`, `re2`, and deps for `pistache`, `fmt` and `tinylates`.
1. Clone and build this repo. You will need `openssl`, `g++>=12`, `re2`, `libmagic`, and deps for `pistache`, `fmt` and `tinylates`.
2. Create a `config.jsonc`. An example one is in `example/`.
3. Adjust the config to your needs. Options are documented with comments in the example config.
4. Set up your IP rules if you want. These allow you to set up IPs that are automatically blocked, or allowed to access without a challenge. This is useful for e.g. search engine scrapers. Some IP ranges can be found in `example/index_bots.jsonc`.

View File

@@ -23,6 +23,7 @@
#include <fmt/format.h>
#include <glaze/glaze.hpp>
#include <openssl/evp.h>
#include <magic.h>
constexpr const uint64_t TOKEN_MAX_AGE_MS = 1000 * 60 * 60; // 1hr
constexpr const char* TOKEN_COOKIE_NAME = "checkpoint-token";
@@ -94,7 +95,7 @@ std::string CServerHandler::fingerprintForRequest(const Pistache::Http::Request&
}
bool CServerHandler::isResourceCheckpoint(const std::string_view& res) {
return res == "/checkpoint/NotoSans.woff";
return res.starts_with("/checkpoint/");
}
std::string CServerHandler::ipForRequest(const Pistache::Http::Request& req) {
@@ -200,12 +201,6 @@ void CServerHandler::onRequest(const Pistache::Http::Request& req, Pistache::Htt
return;
}
if (isResourceCheckpoint(req.resource())) {
// no directory traversal is possible when resource is checkpoint
response.send(Pistache::Http::Code::Ok, NFsUtils::readFileAsString(NFsUtils::htmlPath(req.resource().substr(req.resource().find("checkpoint/") + 11))).value());
return;
}
if (g_pConfig->m_config.git_host) {
// TODO: ratelimit this, probably.
@@ -289,6 +284,42 @@ void CServerHandler::onRequest(const Pistache::Http::Request& req, Pistache::Htt
} else
Debug::log(LOG, " | Action: CHALLENGE (no token)");
if (isResourceCheckpoint(req.resource())) {
static const auto HTML_ROOT = std::filesystem::canonical(NFsUtils::htmlPath("")).string();
const auto RESOURCE_PATH = req.resource().substr(req.resource().find("checkpoint/") + 11);
const auto PATH_RAW = NFsUtils::htmlPath(RESOURCE_PATH);
std::error_code ec;
const auto PATH_ABSOLUTE = std::filesystem::canonical(PATH_RAW, ec);
if (ec) {
// bad resource
response.send(Pistache::Http::Code::Bad_Request, "Bad Request");
return;
}
if (!PATH_ABSOLUTE.string().starts_with(HTML_ROOT)) {
// directory traversal
response.send(Pistache::Http::Code::Bad_Request, "Bad Request");
return;
}
// attempt to handle mime
magic_t magic = magic_open(MAGIC_MIME_TYPE);
if (magic && magic_load(magic, nullptr) == 0) {
const char* m = magic_file(magic, PATH_ABSOLUTE.c_str());
auto mimeType = Pistache::Http::Mime::MediaType::fromString(m ? std::string(m) : std::string("application/octet-stream"));
response.headers().add<Pistache::Http::Header::ContentType>(mimeType);
}
if (magic)
magic_close(magic);
auto body = NFsUtils::readFileAsString(PATH_ABSOLUTE).value_or("");
response.send(body.empty() ? Pistache::Http::Code::Internal_Server_Error : Pistache::Http::Code::Ok, body);
return;
}
serveStop(req, response, challengeDifficulty);
}