// Copyright Epic Games, Inc. All Rights Reserved. #include "httpparser.h" #include #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) { const size_t RemainingBufferSpace = std::numeric_limits::max() - m_HeaderData.size(); if (RemainingBufferSpace < Bytes) { ZEN_WARN("HTTP parser does not have enough space for incoming request headers, need {} more bytes", Bytes - RemainingBufferSpace); return 1; } if (m_UrlRange.Length == 0) { ZEN_ASSERT_SLOW(m_UrlRange.Offset == 0); m_UrlRange.Offset = (uint32_t)m_HeaderData.size(); } m_HeaderData.insert(m_HeaderData.end(), Data, &Data[Bytes]); m_UrlRange.Length += (uint32_t)Bytes; return 0; } int HttpRequestParser::OnHeader(const char* Data, size_t Bytes) { const size_t RemainingBufferSpace = std::numeric_limits::max() - m_HeaderData.size(); if (RemainingBufferSpace < Bytes) { ZEN_WARN("HTTP parser does not have enough space for incoming request headers, need {} more bytes", Bytes - RemainingBufferSpace); return 1; } if (m_HeaderEntries.empty()) { m_HeaderEntries.resize(1); } HeaderEntry* CurrentHeaderEntry = &m_HeaderEntries.back(); if (CurrentHeaderEntry->ValueRange.Length) { ParseCurrentHeader(); m_HeaderEntries.emplace_back(HeaderEntry{.NameRange = {.Offset = (uint32_t)m_HeaderData.size()}}); CurrentHeaderEntry = &m_HeaderEntries.back(); } else if (CurrentHeaderEntry->NameRange.Length == 0) { m_HeaderEntries.emplace_back(HeaderEntry{.NameRange = {.Offset = (uint32_t)m_HeaderData.size()}}); CurrentHeaderEntry = &m_HeaderEntries.back(); } m_HeaderData.insert(m_HeaderData.end(), Data, &Data[Bytes]); CurrentHeaderEntry->NameRange.Length += (uint32_t)Bytes; return 0; } void HttpRequestParser::ParseCurrentHeader() { ZEN_ASSERT_SLOW(!m_HeaderEntries.empty()); const HeaderEntry& CurrentHeaderEntry = m_HeaderEntries.back(); const size_t CurrentHeaderCount = m_HeaderEntries.size(); const std::string_view HeaderName(GetHeaderSubString(CurrentHeaderEntry.NameRange)); if (CurrentHeaderCount > 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; } const std::string_view HeaderValue(GetHeaderSubString(CurrentHeaderEntry.ValueRange)); const uint32_t HeaderHash = HashStringAsLowerDjb2(HeaderName); const int8_t CurrentHeaderIndex = int8_t(CurrentHeaderCount - 1); if (HeaderHash == HashContentLength) { m_ContentLengthHeaderIndex = CurrentHeaderIndex; } else if (HeaderHash == HashAccept) { m_AcceptHeaderIndex = CurrentHeaderIndex; } else if (HeaderHash == HashContentType) { m_ContentTypeHeaderIndex = CurrentHeaderIndex; } else if (HeaderHash == HashSession) { m_SessionId = Oid::TryFromHexString(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 = CurrentHeaderIndex; } } int HttpRequestParser::OnHeaderValue(const char* Data, size_t Bytes) { const size_t RemainingBufferSpace = std::numeric_limits::max() - m_HeaderData.size(); if (RemainingBufferSpace < Bytes) { ZEN_WARN("HTTP parser does not have enough space for incoming request headers, need {} more bytes", Bytes - RemainingBufferSpace); return 1; } ZEN_ASSERT_SLOW(!m_HeaderEntries.empty()); HeaderEntry& CurrentHeaderEntry = m_HeaderEntries.back(); if (CurrentHeaderEntry.ValueRange.Length == 0) { CurrentHeaderEntry.ValueRange.Offset = (uint32_t)m_HeaderData.size(); } m_HeaderData.insert(m_HeaderData.end(), Data, &Data[Bytes]); CurrentHeaderEntry.ValueRange.Length += (uint32_t)Bytes; return 0; } static void NormalizeUrlPath(std::string_view InUrl, std::string& NormalizedUrl) { bool LastCharWasSeparator = false; const char* Url = InUrl.data(); const size_t UrlLength = InUrl.length(); 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_HeaderEntries.empty()) { HeaderEntry& CurrentHeaderEntry = m_HeaderEntries.back(); if (CurrentHeaderEntry.NameRange.Length) { ParseCurrentHeader(); } } 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 FullUrl(GetHeaderSubString(m_UrlRange)); if (auto QuerySplit = FullUrl.find_first_of('?'); QuerySplit != std::string_view::npos) { m_UrlRange.Length = uint32_t(QuerySplit); m_QueryStringRange = {.Offset = uint32_t(m_UrlRange.Offset + QuerySplit + 1), .Length = uint32_t(FullUrl.size() - QuerySplit - 1)}; } NormalizeUrlPath(FullUrl, m_NormalizedUrl); std::string_view Value = GetHeaderValue(m_ContentLengthHeaderIndex); if (!Value.empty()) { 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_UrlRange = {}; m_QueryStringRange = {}; m_HeaderEntries.clear(); m_ContentLengthHeaderIndex = -1; m_AcceptHeaderIndex = -1; m_ContentTypeHeaderIndex = -1; m_RangeHeaderIndex = -1; m_Expect100Continue = false; m_BodyBuffer = {}; m_BodyPosition = 0; m_HeaderData.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(), SystemError.code().value()); } 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