From e9b8c436f95d9e81f6fe3f371e12d916b72f5382 Mon Sep 17 00:00:00 2001 From: Philip Deltour Date: Thu, 2 Oct 2025 08:38:09 +0000 Subject: [PATCH 1/3] Add callback handler to report internal HTTP parsing errors(431, 400, 505) --- src/App.h | 7 +++++++ src/HttpContext.h | 25 +++++++++++++++++++++++++ src/HttpContextData.h | 3 +++ src/HttpParser.h | 27 ++++++++++++++++++++++----- 4 files changed, 57 insertions(+), 5 deletions(-) diff --git a/src/App.h b/src/App.h index 7fa9319a6..61860f456 100644 --- a/src/App.h +++ b/src/App.h @@ -147,6 +147,13 @@ struct TemplatedApp { return std::move(static_cast(*this)); } + /* Attaches an error handler for internal HTTP parsing errors */ + TemplatedApp &&onHttpParsingError(MoveOnlyFunction &&errorHandler) { + httpContext->onHttpParsingError(std::move(errorHandler)); + + return std::move(static_cast(*this)); + } + /* Same as publish, but takes a prepared message */ bool publishPrepared(std::string_view topic, PreparedMessage &preparedMessage) { /* It is assumed by heuristics that a prepared message ought to be big, diff --git a/src/HttpContext.h b/src/HttpContext.h index 118bca56a..ee6875693 100644 --- a/src/HttpContext.h +++ b/src/HttpContext.h @@ -25,6 +25,7 @@ #include "HttpResponseData.h" #include "AsyncSocket.h" #include "WebSocketData.h" +#include "HttpErrors.h" #include #include @@ -251,6 +252,25 @@ struct HttpContext { } } return user; + }, [httpContextData](/*void *s, */HttpRequest *httpRequest, unsigned int errorCode) -> void { + /* Call the high-level error handler if one is registered */ + if (httpContextData->httpParsingErrorHandler) { + /* Map internal error codes to HTTP status codes and response bodies */ + int statusCode; + switch (errorCode) { + case HTTP_ERROR_505_HTTP_VERSION_NOT_SUPPORTED: + statusCode = 505; + break; + case HTTP_ERROR_431_REQUEST_HEADER_FIELDS_TOO_LARGE: + statusCode = 431; + break; + case HTTP_ERROR_400_BAD_REQUEST: + default: + statusCode = 400; + break; + } + httpContextData->httpParsingErrorHandler(httpRequest, statusCode, httpErrorResponses[errorCode]); + } }); /* Mark that we are no longer parsing Http */ @@ -421,6 +441,11 @@ struct HttpContext { getSocketContextData()->filterHandlers.emplace_back(std::move(filterHandler)); } + /* Register an HTTP parsing error handler */ + void onHttpParsingError(MoveOnlyFunction &&errorHandler) { + getSocketContextData()->httpParsingErrorHandler = std::move(errorHandler); + } + /* Register an HTTP route handler acording to URL pattern */ void onHttp(std::string method, std::string pattern, MoveOnlyFunction *, HttpRequest *)> &&handler, bool upgrade = false) { HttpContextData *httpContextData = getSocketContextData(); diff --git a/src/HttpContextData.h b/src/HttpContextData.h index 8337a3a9f..f7885ff82 100644 --- a/src/HttpContextData.h +++ b/src/HttpContextData.h @@ -36,6 +36,9 @@ struct alignas(16) HttpContextData { std::vector *, int)>> filterHandlers; MoveOnlyFunction missingServerNameHandler; + + /* HTTP parsing error handler */ + MoveOnlyFunction httpParsingErrorHandler; struct RouterData { HttpResponse *httpResponse; diff --git a/src/HttpParser.h b/src/HttpParser.h index 7a4c563b6..2b2a6465c 100644 --- a/src/HttpParser.h +++ b/src/HttpParser.h @@ -489,6 +489,9 @@ struct HttpParser { data += consumed; length -= consumed; consumedTotal += consumed; + /* Parse query */ + const char *querySeparatorPtr = (const char *) memchr(req->headers->value.data(), '?', req->headers->value.length()); + req->querySeparator = (unsigned int) ((querySeparatorPtr ? querySeparatorPtr : req->headers->value.data() + req->headers->value.length()) - req->headers->value.data()); /* Even if we could parse it, check for length here as well */ if (consumed > MAX_FALLBACK_SIZE) { @@ -529,10 +532,6 @@ struct HttpParser { return {HTTP_ERROR_400_BAD_REQUEST, FULLPTR}; } - /* Parse query */ - const char *querySeparatorPtr = (const char *) memchr(req->headers->value.data(), '?', req->headers->value.length()); - req->querySeparator = (unsigned int) ((querySeparatorPtr ? querySeparatorPtr : req->headers->value.data() + req->headers->value.length()) - req->headers->value.data()); - /* If returned socket is not what we put in we need * to break here as we either have upgraded to * WebSockets or otherwise closed the socket. */ @@ -615,7 +614,7 @@ struct HttpParser { } public: - std::pair consumePostPadded(char *data, unsigned int length, void *user, void *reserved, MoveOnlyFunction &&requestHandler, MoveOnlyFunction &&dataHandler) { + std::pair consumePostPadded(char *data, unsigned int length, void *user, void *reserved, MoveOnlyFunction &&requestHandler, MoveOnlyFunction &&dataHandler, MoveOnlyFunction &&errorHandler = nullptr) { /* This resets BloomFilter by construction, but later we also reset it again. * Optimize this to skip resetting twice (req could be made global) */ @@ -630,6 +629,9 @@ struct HttpParser { dataHandler(user, chunk, chunk.length() == 0); } if (isParsingInvalidChunkedEncoding(remainingStreamingBytes)) { + if (errorHandler) { + errorHandler(&req, HTTP_ERROR_400_BAD_REQUEST); + } return {HTTP_ERROR_400_BAD_REQUEST, FULLPTR}; } data = (char *) dataToConsume.data(); @@ -667,6 +669,9 @@ struct HttpParser { // break here on break std::pair consumed = fenceAndConsumePostPadded(fallback.data(), (unsigned int) fallback.length(), user, reserved, &req, requestHandler, dataHandler); if (consumed.second != user) { + if (errorHandler) { + errorHandler(&req, consumed.first); + } return consumed; } @@ -687,6 +692,9 @@ struct HttpParser { dataHandler(user, chunk, chunk.length() == 0); } if (isParsingInvalidChunkedEncoding(remainingStreamingBytes)) { + if (errorHandler) { + errorHandler(&req, HTTP_ERROR_400_BAD_REQUEST); + } return {HTTP_ERROR_400_BAD_REQUEST, FULLPTR}; } data = (char *) dataToConsume.data(); @@ -714,6 +722,9 @@ struct HttpParser { } else { if (fallback.length() == MAX_FALLBACK_SIZE) { + if (errorHandler) { + errorHandler(&req, HTTP_ERROR_431_REQUEST_HEADER_FIELDS_TOO_LARGE); + } return {HTTP_ERROR_431_REQUEST_HEADER_FIELDS_TOO_LARGE, FULLPTR}; } return {0, user}; @@ -722,6 +733,9 @@ struct HttpParser { std::pair consumed = fenceAndConsumePostPadded(data, length, user, reserved, &req, requestHandler, dataHandler); if (consumed.second != user) { + if (errorHandler) { + errorHandler(&req, consumed.first); + } return consumed; } @@ -732,6 +746,9 @@ struct HttpParser { if (length < MAX_FALLBACK_SIZE) { fallback.append(data, length); } else { + if (errorHandler) { + errorHandler(&req, HTTP_ERROR_431_REQUEST_HEADER_FIELDS_TOO_LARGE); + } return {HTTP_ERROR_431_REQUEST_HEADER_FIELDS_TOO_LARGE, FULLPTR}; } } From 44cdf050704255e637d8aae3cb3618e213a091a3 Mon Sep 17 00:00:00 2001 From: Philip Deltour Date: Tue, 7 Oct 2025 08:35:00 +0000 Subject: [PATCH 2/3] renamed callack to log --- src/App.h | 6 +++--- src/HttpContext.h | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/App.h b/src/App.h index 61860f456..9b49b3f80 100644 --- a/src/App.h +++ b/src/App.h @@ -147,9 +147,9 @@ struct TemplatedApp { return std::move(static_cast(*this)); } - /* Attaches an error handler for internal HTTP parsing errors */ - TemplatedApp &&onHttpParsingError(MoveOnlyFunction &&errorHandler) { - httpContext->onHttpParsingError(std::move(errorHandler)); + /* Attaches a log handler for HTTP parsing errors */ + TemplatedApp &&log(MoveOnlyFunction &&logHandler) { + httpContext->log(std::move(logHandler)); return std::move(static_cast(*this)); } diff --git a/src/HttpContext.h b/src/HttpContext.h index ee6875693..65268e42c 100644 --- a/src/HttpContext.h +++ b/src/HttpContext.h @@ -442,8 +442,8 @@ struct HttpContext { } /* Register an HTTP parsing error handler */ - void onHttpParsingError(MoveOnlyFunction &&errorHandler) { - getSocketContextData()->httpParsingErrorHandler = std::move(errorHandler); + void log(MoveOnlyFunction &&logHandler) { + getSocketContextData()->httpParsingErrorHandler = std::move(logHandler); } /* Register an HTTP route handler acording to URL pattern */ From 1a1b8a3ad022db892257bb79f0f76ce4a11ed22f Mon Sep 17 00:00:00 2001 From: Philip Deltour Date: Thu, 11 Dec 2025 09:40:47 +0000 Subject: [PATCH 3/3] performance - remove switch and check fro callback --- fuzzing/Http.cpp | 1 + src/HttpContext.h | 15 +-------------- src/HttpErrors.h | 7 +++++++ src/HttpParser.h | 26 +++++++------------------- tests/HttpParser.cpp | 1 + 5 files changed, 17 insertions(+), 33 deletions(-) diff --git a/fuzzing/Http.cpp b/fuzzing/Http.cpp index a01c7a383..33714bb26 100644 --- a/fuzzing/Http.cpp +++ b/fuzzing/Http.cpp @@ -135,6 +135,7 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { /* Return ok */ return user; + }, [](uWS::HttpRequest */*req*/, unsigned int /*errorCode*/) { }); if (!returnedUser) { diff --git a/src/HttpContext.h b/src/HttpContext.h index 65268e42c..d6c58d41d 100644 --- a/src/HttpContext.h +++ b/src/HttpContext.h @@ -256,20 +256,7 @@ struct HttpContext { /* Call the high-level error handler if one is registered */ if (httpContextData->httpParsingErrorHandler) { /* Map internal error codes to HTTP status codes and response bodies */ - int statusCode; - switch (errorCode) { - case HTTP_ERROR_505_HTTP_VERSION_NOT_SUPPORTED: - statusCode = 505; - break; - case HTTP_ERROR_431_REQUEST_HEADER_FIELDS_TOO_LARGE: - statusCode = 431; - break; - case HTTP_ERROR_400_BAD_REQUEST: - default: - statusCode = 400; - break; - } - httpContextData->httpParsingErrorHandler(httpRequest, statusCode, httpErrorResponses[errorCode]); + httpContextData->httpParsingErrorHandler(httpRequest, httpErrorStatusCodes[errorCode], httpErrorResponses[errorCode]); } }); diff --git a/src/HttpErrors.h b/src/HttpErrors.h index a17a1c737..17aebd386 100644 --- a/src/HttpErrors.h +++ b/src/HttpErrors.h @@ -30,6 +30,13 @@ enum HttpError { #ifndef UWS_HTTPRESPONSE_NO_WRITEMARK +static const int httpErrorStatusCodes[] = { + 400, /* Zeroth place (unused) */ + 505, /* HTTP_ERROR_505_HTTP_VERSION_NOT_SUPPORTED */ + 431, /* HTTP_ERROR_431_REQUEST_HEADER_FIELDS_TOO_LARGE */ + 400 /* HTTP_ERROR_400_BAD_REQUEST */ +}; + /* Returned parser errors match this LUT. */ static const std::string_view httpErrorResponses[] = { "", /* Zeroth place is no error so don't use it */ diff --git a/src/HttpParser.h b/src/HttpParser.h index 2b2a6465c..5ee3355bd 100644 --- a/src/HttpParser.h +++ b/src/HttpParser.h @@ -614,7 +614,7 @@ struct HttpParser { } public: - std::pair consumePostPadded(char *data, unsigned int length, void *user, void *reserved, MoveOnlyFunction &&requestHandler, MoveOnlyFunction &&dataHandler, MoveOnlyFunction &&errorHandler = nullptr) { + std::pair consumePostPadded(char *data, unsigned int length, void *user, void *reserved, MoveOnlyFunction &&requestHandler, MoveOnlyFunction &&dataHandler, MoveOnlyFunction &&errorHandler) { /* This resets BloomFilter by construction, but later we also reset it again. * Optimize this to skip resetting twice (req could be made global) */ @@ -629,9 +629,7 @@ struct HttpParser { dataHandler(user, chunk, chunk.length() == 0); } if (isParsingInvalidChunkedEncoding(remainingStreamingBytes)) { - if (errorHandler) { - errorHandler(&req, HTTP_ERROR_400_BAD_REQUEST); - } + errorHandler(&req, HTTP_ERROR_400_BAD_REQUEST); return {HTTP_ERROR_400_BAD_REQUEST, FULLPTR}; } data = (char *) dataToConsume.data(); @@ -669,9 +667,7 @@ struct HttpParser { // break here on break std::pair consumed = fenceAndConsumePostPadded(fallback.data(), (unsigned int) fallback.length(), user, reserved, &req, requestHandler, dataHandler); if (consumed.second != user) { - if (errorHandler) { - errorHandler(&req, consumed.first); - } + errorHandler(&req, consumed.first); return consumed; } @@ -692,9 +688,7 @@ struct HttpParser { dataHandler(user, chunk, chunk.length() == 0); } if (isParsingInvalidChunkedEncoding(remainingStreamingBytes)) { - if (errorHandler) { - errorHandler(&req, HTTP_ERROR_400_BAD_REQUEST); - } + errorHandler(&req, HTTP_ERROR_400_BAD_REQUEST); return {HTTP_ERROR_400_BAD_REQUEST, FULLPTR}; } data = (char *) dataToConsume.data(); @@ -722,9 +716,7 @@ struct HttpParser { } else { if (fallback.length() == MAX_FALLBACK_SIZE) { - if (errorHandler) { - errorHandler(&req, HTTP_ERROR_431_REQUEST_HEADER_FIELDS_TOO_LARGE); - } + errorHandler(&req, HTTP_ERROR_431_REQUEST_HEADER_FIELDS_TOO_LARGE); return {HTTP_ERROR_431_REQUEST_HEADER_FIELDS_TOO_LARGE, FULLPTR}; } return {0, user}; @@ -733,9 +725,7 @@ struct HttpParser { std::pair consumed = fenceAndConsumePostPadded(data, length, user, reserved, &req, requestHandler, dataHandler); if (consumed.second != user) { - if (errorHandler) { - errorHandler(&req, consumed.first); - } + errorHandler(&req, consumed.first); return consumed; } @@ -746,9 +736,7 @@ struct HttpParser { if (length < MAX_FALLBACK_SIZE) { fallback.append(data, length); } else { - if (errorHandler) { - errorHandler(&req, HTTP_ERROR_431_REQUEST_HEADER_FIELDS_TOO_LARGE); - } + errorHandler(&req, HTTP_ERROR_431_REQUEST_HEADER_FIELDS_TOO_LARGE); return {HTTP_ERROR_431_REQUEST_HEADER_FIELDS_TOO_LARGE, FULLPTR}; } } diff --git a/tests/HttpParser.cpp b/tests/HttpParser.cpp index a1b75317a..69dbde9dc 100644 --- a/tests/HttpParser.cpp +++ b/tests/HttpParser.cpp @@ -31,6 +31,7 @@ int main() { /* Return ok */ return user; + }, [](uWS::HttpRequest */*req*/, unsigned int /*errorCode*/) { }); std::cout << "HTTP DONE" << std::endl;