// Copyright Epic Games, Inc. All Rights Reserved. #include "httpparser.h" #include #include #include namespace zen { using namespace std::literals; static constinit uint32_t HashContentLength = HashStringAsLowerDjb2("Content-Length"sv); static constinit uint32_t HashContentType = HashStringAsLowerDjb2("Content-Type"sv); static constinit uint32_t HashAccept = HashStringAsLowerDjb2("Accept"sv); static constinit uint32_t HashExpect = HashStringAsLowerDjb2("Expect"sv); static constinit uint32_t HashSession = HashStringAsLowerDjb2("UE-Session"sv); static constinit uint32_t HashRequest = HashStringAsLowerDjb2("UE-Request"sv); static constinit uint32_t HashRange = HashStringAsLowerDjb2("Range"sv); ////////////////////////////////////////////////////////////////////////// // // HttpRequestParser // http_parser_settings HttpRequestParser::s_ParserSettings{ .on_message_begin = [](http_parser* p) { return GetThis(p)->OnMessageBegin(); }, .on_url = [](http_parser* p, const char* Data, size_t ByteCount) { return GetThis(p)->OnUrl(Data, ByteCount); }, .on_status = [](http_parser* p, const char* Data, size_t ByteCount) { ZEN_UNUSED(p, Data, ByteCount); return 0; }, .on_header_field = [](http_parser* p, const char* Data, size_t ByteCount) { return GetThis(p)->OnHeader(Data, ByteCount); }, .on_header_value = [](http_parser* p, const char* Data, size_t ByteCount) { return GetThis(p)->OnHeaderValue(Data, ByteCount); }, .on_headers_complete = [](http_parser* p) { return GetThis(p)->OnHeadersComplete(); }, .on_body = [](http_parser* p, const char* Data, size_t ByteCount) { return GetThis(p)->OnBody(Data, ByteCount); }, .on_message_complete = [](http_parser* p) { return GetThis(p)->OnMessageComplete(); }, .on_chunk_header{}, .on_chunk_complete{}}; HttpRequestParser::HttpRequestParser(HttpRequestParserCallbacks& Connection) : m_Connection(Connection) { http_parser_init(&m_Parser, HTTP_REQUEST); m_Parser.data = this; ResetState(); } HttpRequestParser::~HttpRequestParser() { } size_t HttpRequestParser::ConsumeData(const char* InputData, size_t DataSize) { const size_t ConsumedBytes = http_parser_execute(&m_Parser, &s_ParserSettings, InputData, DataSize); http_errno HttpErrno = HTTP_PARSER_ERRNO((&m_Parser)); if (HttpErrno && HttpErrno != HPE_INVALID_EOF_STATE) { ZEN_WARN("HTTP parser error {} ('{}'). Closing connection", http_errno_name(HttpErrno), http_errno_description(HttpErrno)); return ~0ull; } return ConsumedBytes; } int HttpRequestParser::OnUrl(const char* Data, size_t Bytes) { if (!m_Url) { ZEN_ASSERT_SLOW(m_UrlLength == 0); m_Url = m_HeaderCursor; } const size_t RemainingBufferSpace = sizeof m_HeaderBuffer + m_HeaderBuffer - m_HeaderCursor; if (RemainingBufferSpace < Bytes) { ZEN_WARN("HTTP parser does not have enough space for incoming request, need {} more bytes", Bytes - RemainingBufferSpace); return 1; } memcpy(m_HeaderCursor, Data, Bytes); m_HeaderCursor += Bytes; m_UrlLength += Bytes; return 0; } int HttpRequestParser::OnHeader(const char* Data, size_t Bytes) { if (m_CurrentHeaderValueLength) { AppendCurrentHeader(); m_CurrentHeaderNameLength = 0; m_CurrentHeaderValueLength = 0; m_CurrentHeaderName = m_HeaderCursor; } else if (m_CurrentHeaderName == nullptr) { m_CurrentHeaderName = m_HeaderCursor; } const size_t RemainingBufferSpace = sizeof m_HeaderBuffer + m_HeaderBuffer - m_HeaderCursor; if (RemainingBufferSpace < Bytes) { ZEN_WARN("HTTP parser does not have enough space for incoming header name, need {} more bytes", Bytes - RemainingBufferSpace); return 1; } memcpy(m_HeaderCursor, Data, Bytes); m_HeaderCursor += Bytes; m_CurrentHeaderNameLength += Bytes; return 0; } void HttpRequestParser::AppendCurrentHeader() { std::string_view HeaderName(m_CurrentHeaderName, m_CurrentHeaderNameLength); if (m_Headers.size() == std::numeric_limits::max()) { ZEN_WARN("HttpRequestParser parser only supports up to {} headers, can't store header '{}'. Dropping it.", std::numeric_limits::max(), HeaderName); return; } std::string_view HeaderValue(m_CurrentHeaderValue, m_CurrentHeaderValueLength); const uint32_t HeaderHash = HashStringAsLowerDjb2(HeaderName); if (HeaderHash == HashContentLength) { m_ContentLengthHeaderIndex = (int8_t)m_Headers.size(); } else if (HeaderHash == HashAccept) { m_AcceptHeaderIndex = (int8_t)m_Headers.size(); } else if (HeaderHash == HashContentType) { m_ContentTypeHeaderIndex = (int8_t)m_Headers.size(); } else if (HeaderHash == HashSession) { m_SessionId = Oid::FromHexString(HeaderValue); } else if (HeaderHash == HashRequest) { std::from_chars(HeaderValue.data(), HeaderValue.data() + HeaderValue.size(), m_RequestId); } else if (HeaderHash == HashExpect) { if (HeaderValue == "100-continue"sv) { // We don't currently do anything with this m_Expect100Continue = true; } else { ZEN_INFO("Unexpected expect - Expect: {}", HeaderValue); } } else if (HeaderHash == HashRange) { m_RangeHeaderIndex = (int8_t)m_Headers.size(); } m_Headers.emplace_back(HeaderName, HeaderValue); } int HttpRequestParser::OnHeaderValue(const char* Data, size_t Bytes) { if (m_CurrentHeaderValueLength == 0) { m_CurrentHeaderValue = m_HeaderCursor; } const size_t RemainingBufferSpace = sizeof m_HeaderBuffer + m_HeaderBuffer - m_HeaderCursor; if (RemainingBufferSpace < Bytes) { ZEN_WARN("HTTP parser does not have enough space for incoming header value, need {} more bytes", Bytes - RemainingBufferSpace); return 1; } memcpy(m_HeaderCursor, Data, Bytes); m_HeaderCursor += Bytes; m_CurrentHeaderValueLength += Bytes; return 0; } static void NormalizeUrlPath(const char* Url, size_t UrlLength, std::string& NormalizedUrl) { bool LastCharWasSeparator = false; for (std::string_view::size_type UrlIndex = 0; UrlIndex < UrlLength; ++UrlIndex) { const char UrlChar = Url[UrlIndex]; const bool IsSeparator = (UrlChar == '/'); if (IsSeparator && LastCharWasSeparator) { if (NormalizedUrl.empty()) { NormalizedUrl.reserve(UrlLength); NormalizedUrl.append(Url, UrlIndex); } if (!LastCharWasSeparator) { NormalizedUrl.push_back('/'); } } else if (!NormalizedUrl.empty()) { NormalizedUrl.push_back(UrlChar); } LastCharWasSeparator = IsSeparator; } } int HttpRequestParser::OnHeadersComplete() { try { if (m_CurrentHeaderValueLength) { AppendCurrentHeader(); } m_KeepAlive = !!http_should_keep_alive(&m_Parser); switch (m_Parser.method) { case HTTP_GET: m_RequestVerb = HttpVerb::kGet; break; case HTTP_POST: m_RequestVerb = HttpVerb::kPost; break; case HTTP_PUT: m_RequestVerb = HttpVerb::kPut; break; case HTTP_DELETE: m_RequestVerb = HttpVerb::kDelete; break; case HTTP_HEAD: m_RequestVerb = HttpVerb::kHead; break; case HTTP_COPY: m_RequestVerb = HttpVerb::kCopy; break; case HTTP_OPTIONS: m_RequestVerb = HttpVerb::kOptions; break; default: ZEN_WARN("invalid HTTP method: '{}'", http_method_str((http_method)m_Parser.method)); break; } std::string_view Url(m_Url, m_UrlLength); if (auto QuerySplit = Url.find_first_of('?'); QuerySplit != std::string_view::npos) { m_UrlLength = QuerySplit; m_QueryString = m_Url + QuerySplit + 1; m_QueryLength = Url.size() - QuerySplit - 1; } NormalizeUrlPath(m_Url, m_UrlLength, m_NormalizedUrl); if (m_ContentLengthHeaderIndex >= 0) { std::string_view& Value = m_Headers[m_ContentLengthHeaderIndex].Value; uint64_t ContentLength = 0; std::from_chars(Value.data(), Value.data() + Value.size(), ContentLength); if (ContentLength) { m_BodyBuffer = IoBuffer(ContentLength); } m_BodyBuffer.SetContentType(ContentType()); m_BodyPosition = 0; } } catch (const std::exception& Ex) { ZEN_WARN("HttpRequestParser::OnHeadersComplete failed. Reason '{}'", Ex.what()); return -1; } return 0; } int HttpRequestParser::OnBody(const char* Data, size_t Bytes) { if (m_BodyPosition + Bytes > m_BodyBuffer.Size()) { ZEN_WARN("HTTP parser incoming body is larger than content size, need {} more bytes", (m_BodyPosition + Bytes) - m_BodyBuffer.Size()); return 1; } memcpy(reinterpret_cast(m_BodyBuffer.MutableData()) + m_BodyPosition, Data, Bytes); m_BodyPosition += Bytes; if (http_body_is_final(&m_Parser)) { if (m_BodyPosition != m_BodyBuffer.Size()) { ZEN_WARN("Body mismatch! {} != {}", m_BodyPosition, m_BodyBuffer.Size()); return 1; } } return 0; } void HttpRequestParser::ResetState() { m_HeaderCursor = m_HeaderBuffer; m_CurrentHeaderName = nullptr; m_CurrentHeaderNameLength = 0; m_CurrentHeaderValue = nullptr; m_CurrentHeaderValueLength = 0; m_Url = nullptr; m_UrlLength = 0; m_QueryString = nullptr; m_QueryLength = 0; m_ContentLengthHeaderIndex = -1; m_AcceptHeaderIndex = -1; m_ContentTypeHeaderIndex = -1; m_RangeHeaderIndex = -1; m_Expect100Continue = false; m_BodyBuffer = {}; m_BodyPosition = 0; m_Headers.clear(); m_NormalizedUrl.clear(); } int HttpRequestParser::OnMessageBegin() { return 0; } int HttpRequestParser::OnMessageComplete() { try { m_Connection.HandleRequest(); ResetState(); return 0; } catch (const AssertException& AssertEx) { ZEN_WARN("Assert caught when processing http request: {}", AssertEx.FullDescription()); return 1; } catch (const std::system_error& SystemError) { if (IsOOM(SystemError.code())) { ZEN_WARN("out of memory when processing http request: '{}'", SystemError.what()); } else if (IsOOD(SystemError.code())) { ZEN_WARN("out of disk space when processing http request: '{}'", SystemError.what()); } else { ZEN_ERROR("failed processing http request: '{}'", SystemError.what()); } ResetState(); return 1; } catch (const std::bad_alloc& BadAlloc) { ZEN_WARN("out of memory when processing http request: '{}'", BadAlloc.what()); ResetState(); return 1; } catch (const std::exception& Ex) { ZEN_ERROR("failed processing http request: '{}'", Ex.what()); ResetState(); return 1; } } } // namespace zen