From 5b2744584aab12699e8ef08377f42461caae7d70 Mon Sep 17 00:00:00 2001 From: catfromplan9 <104175360+catfromplan9@users.noreply.github.com> Date: Mon, 21 Apr 2025 19:28:31 +0000 Subject: [PATCH] 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 --- CMakeLists.txt | 2 +- README.md | 2 +- src/core/Handler.cpp | 45 +++++++++++++++++++++++++++++++++++++------- 3 files changed, 40 insertions(+), 9 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 4d5f704..47f520e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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 diff --git a/README.md b/README.md index ca122c1..9202a46 100644 --- a/README.md +++ b/README.md @@ -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`. diff --git a/src/core/Handler.cpp b/src/core/Handler.cpp index 50cf42f..5f8e111 100644 --- a/src/core/Handler.cpp +++ b/src/core/Handler.cpp @@ -23,6 +23,7 @@ #include #include #include +#include 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(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); }