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/App.h b/src/App.h index 7fa9319a6..9b49b3f80 100644 --- a/src/App.h +++ b/src/App.h @@ -147,6 +147,13 @@ struct TemplatedApp { return std::move(static_cast(*this)); } + /* Attaches a log handler for HTTP parsing errors */ + TemplatedApp &&log(MoveOnlyFunction &&logHandler) { + httpContext->log(std::move(logHandler)); + + 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..d6c58d41d 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,12 @@ 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 */ + httpContextData->httpParsingErrorHandler(httpRequest, httpErrorStatusCodes[errorCode], httpErrorResponses[errorCode]); + } }); /* Mark that we are no longer parsing Http */ @@ -421,6 +428,11 @@ struct HttpContext { getSocketContextData()->filterHandlers.emplace_back(std::move(filterHandler)); } + /* Register an HTTP parsing error handler */ + void log(MoveOnlyFunction &&logHandler) { + getSocketContextData()->httpParsingErrorHandler = std::move(logHandler); + } + /* 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/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 7a4c563b6..5ee3355bd 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) { /* 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,7 @@ struct HttpParser { dataHandler(user, chunk, chunk.length() == 0); } if (isParsingInvalidChunkedEncoding(remainingStreamingBytes)) { + errorHandler(&req, HTTP_ERROR_400_BAD_REQUEST); return {HTTP_ERROR_400_BAD_REQUEST, FULLPTR}; } data = (char *) dataToConsume.data(); @@ -667,6 +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) { + errorHandler(&req, consumed.first); return consumed; } @@ -687,6 +688,7 @@ struct HttpParser { dataHandler(user, chunk, chunk.length() == 0); } if (isParsingInvalidChunkedEncoding(remainingStreamingBytes)) { + errorHandler(&req, HTTP_ERROR_400_BAD_REQUEST); return {HTTP_ERROR_400_BAD_REQUEST, FULLPTR}; } data = (char *) dataToConsume.data(); @@ -714,6 +716,7 @@ struct HttpParser { } else { if (fallback.length() == MAX_FALLBACK_SIZE) { + 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 +725,7 @@ struct HttpParser { std::pair consumed = fenceAndConsumePostPadded(data, length, user, reserved, &req, requestHandler, dataHandler); if (consumed.second != user) { + errorHandler(&req, consumed.first); return consumed; } @@ -732,6 +736,7 @@ struct HttpParser { if (length < MAX_FALLBACK_SIZE) { fallback.append(data, length); } else { + 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;