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:
@@ -29,7 +29,7 @@ add_compile_definitions(CHECKPOINT_VERSION="${CHECKPOINT_VERSION}")
|
|||||||
|
|
||||||
find_package(PkgConfig REQUIRED)
|
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
|
target_include_directories(checkpoint
|
||||||
PRIVATE
|
PRIVATE
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ If you are using this, it's almost certain search engines will stop indexing you
|
|||||||
|
|
||||||
## Setup guide
|
## 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/`.
|
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.
|
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`.
|
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`.
|
||||||
|
|||||||
@@ -23,6 +23,7 @@
|
|||||||
#include <fmt/format.h>
|
#include <fmt/format.h>
|
||||||
#include <glaze/glaze.hpp>
|
#include <glaze/glaze.hpp>
|
||||||
#include <openssl/evp.h>
|
#include <openssl/evp.h>
|
||||||
|
#include <magic.h>
|
||||||
|
|
||||||
constexpr const uint64_t TOKEN_MAX_AGE_MS = 1000 * 60 * 60; // 1hr
|
constexpr const uint64_t TOKEN_MAX_AGE_MS = 1000 * 60 * 60; // 1hr
|
||||||
constexpr const char* TOKEN_COOKIE_NAME = "checkpoint-token";
|
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) {
|
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) {
|
std::string CServerHandler::ipForRequest(const Pistache::Http::Request& req) {
|
||||||
@@ -200,12 +201,6 @@ void CServerHandler::onRequest(const Pistache::Http::Request& req, Pistache::Htt
|
|||||||
return;
|
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) {
|
if (g_pConfig->m_config.git_host) {
|
||||||
// TODO: ratelimit this, probably.
|
// TODO: ratelimit this, probably.
|
||||||
|
|
||||||
@@ -289,6 +284,42 @@ void CServerHandler::onRequest(const Pistache::Http::Request& req, Pistache::Htt
|
|||||||
} else
|
} else
|
||||||
Debug::log(LOG, " | Action: CHALLENGE (no token)");
|
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);
|
serveStop(req, response, challengeDifficulty);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user