diff --git a/scripts/sync_vendor.py b/scripts/sync_vendor.py index 4fcfd5267..4ecf50f90 100755 --- a/scripts/sync_vendor.py +++ b/scripts/sync_vendor.py @@ -5,7 +5,7 @@ import os import sys import subprocess -HTTPLIB_VERSION = "refs/tags/v0.53.0" +HTTPLIB_VERSION = "refs/tags/v0.53.1" vendor = { "https://github.com/nlohmann/json/releases/latest/download/json.hpp": "vendor/nlohmann/json.hpp", diff --git a/vendor/cpp-httplib/httplib.cpp b/vendor/cpp-httplib/httplib.cpp index 3b687ff4f..81cdcfe3a 100644 --- a/vendor/cpp-httplib/httplib.cpp +++ b/vendor/cpp-httplib/httplib.cpp @@ -7146,6 +7146,10 @@ PathParamsMatcher::PathParamsMatcher(const std::string &pattern) bool PathParamsMatcher::match(Request &request) const { request.matches = std::smatch(); request.path_params.clear(); + + // A pattern without parameters is just a literal path to compare against + if (param_names_.empty()) { return request.path == pattern(); } + request.path_params.reserve(param_names_.size()); // One past the position at which the path matched the pattern last time @@ -7188,6 +7192,11 @@ bool PathParamsMatcher::match(Request &request) const { bool RegexMatcher::match(Request &request) const { request.path_params.clear(); + // See CPPHTTPLIB_REGEX_ROUTE_PATH_MAX_LENGTH: an overlong path is treated as + // a non-match rather than risking a stack overflow in std::regex_match. + if (request.path.length() > CPPHTTPLIB_REGEX_ROUTE_PATH_MAX_LENGTH) { + return false; + } return std::regex_match(request.path, request.matches, regex_); } @@ -7613,11 +7622,21 @@ Server::~Server() = default; std::unique_ptr Server::make_matcher(const std::string &pattern) { + // Path params take precedence, so "/users/:id/(.*)" keeps being matched as + // a path params pattern if (pattern.find("/:") != std::string::npos) { return detail::make_unique(pattern); - } else { - return detail::make_unique(pattern); } + + // A pattern with no regex metacharacter only has to be compared literally, + // which is what PathParamsMatcher already does when it captures no + // parameter, so std::regex is only worth building for the patterns that + // actually need it + if (pattern.find_first_of(".^$|()[]{}*+?\\") == std::string::npos) { + return detail::make_unique(pattern); + } + + return detail::make_unique(pattern); } Server &Server::Get(const std::string &pattern, Handler handler) { @@ -8259,15 +8278,12 @@ bool Server::read_content_core( } } if (has_data) { - auto result = - detail::read_content_without_length(strm, payload_max_length_, out); - if (result == detail::ReadContentResult::PayloadTooLarge) { - res.status = StatusCode::PayloadTooLarge_413; - return false; - } else if (result != detail::ReadContentResult::Success) { - return false; - } - return true; + // Route through the same decompressing reader used by the + // length-framed and chunked paths below, so payload_max_length_ is + // enforced on the decompressed size here too instead of only on the + // compressed wire bytes. + return detail::read_content(strm, req, payload_max_length_, res.status, + nullptr, out, true); } } return true; diff --git a/vendor/cpp-httplib/httplib.h b/vendor/cpp-httplib/httplib.h index f82e58e49..6fc86c7c7 100644 --- a/vendor/cpp-httplib/httplib.h +++ b/vendor/cpp-httplib/httplib.h @@ -8,8 +8,8 @@ #ifndef CPPHTTPLIB_HTTPLIB_H #define CPPHTTPLIB_HTTPLIB_H -#define CPPHTTPLIB_VERSION "0.53.0" -#define CPPHTTPLIB_VERSION_NUM "0x003500" +#define CPPHTTPLIB_VERSION "0.53.1" +#define CPPHTTPLIB_VERSION_NUM "0x003501" #ifdef _WIN32 #if defined(_WIN32_WINNT) && _WIN32_WINNT < 0x0A00 @@ -138,6 +138,18 @@ #define CPPHTTPLIB_RANGE_MAX_COUNT 1024 #endif +// std::regex_match's backtracking implementation (most acutely on libstdc++) +// recurses roughly once per matched character for quantified patterns such +// as "(.*)", so a long enough path can exhaust the calling thread's stack; on +// a default ~8MB thread stack that has been observed to take on the order of +// a couple thousand characters for a simple pattern. 256 leaves a wide safety +// margin below that (well under the 8192-byte request URI limit) while still +// fitting any realistic route segment; raise it if a route legitimately needs +// longer paths. Regex routes are never applied to paths longer than this. +#ifndef CPPHTTPLIB_REGEX_ROUTE_PATH_MAX_LENGTH +#define CPPHTTPLIB_REGEX_ROUTE_PATH_MAX_LENGTH 256 +#endif + #ifndef CPPHTTPLIB_TCP_NODELAY #define CPPHTTPLIB_TCP_NODELAY false #endif @@ -839,6 +851,15 @@ inline bool parse_url(const std::string &url, UrlComponents &uc) { } pos = close + 1; + + // The IPv6 literal is the whole host, so ']' must be followed by a port, + // path, query or fragment delimiter (or the end of input). Otherwise the + // trailing bytes would be folded into the path while the connection + // still targets the bracketed address. + if (pos < url.size()) { + auto c = url[pos]; + if (c != ':' && c != '/' && c != '?' && c != '#') { return false; } + } } else { auto end = url.find_first_of(":/?#", pos); if (end == std::string::npos) { end = url.size(); }