// Copyright Epic Games, Inc. All Rights Reserved. #include "httptrafficinspector.h" #include #include #include namespace zen { // clang-format off http_parser_settings HttpTrafficInspector::s_RequestSettings{ .on_message_begin = [](http_parser*) { return 0; }, .on_url = [](http_parser* p, const char* Data, size_t Len) { return GetThis(p)->OnUrl(Data, Len); }, .on_status = [](http_parser*, const char*, size_t) { return 0; }, .on_header_field = [](http_parser* p, const char* Data, size_t Len) { return GetThis(p)->OnHeaderField(Data, Len); }, .on_header_value = [](http_parser* p, const char* Data, size_t Len) { return GetThis(p)->OnHeaderValue(Data, Len); }, .on_headers_complete = [](http_parser* p) { return GetThis(p)->OnHeadersComplete(); }, .on_body = [](http_parser*, const char*, size_t) { return 0; }, .on_message_complete = [](http_parser* p) { return GetThis(p)->OnMessageComplete(); }, .on_chunk_header{}, .on_chunk_complete{}}; http_parser_settings HttpTrafficInspector::s_ResponseSettings{ .on_message_begin = [](http_parser*) { return 0; }, .on_url = [](http_parser*, const char*, size_t) { return 0; }, .on_status = [](http_parser*, const char*, size_t) { return 0; }, .on_header_field = [](http_parser* p, const char* Data, size_t Len) { return GetThis(p)->OnHeaderField(Data, Len); }, .on_header_value = [](http_parser* p, const char* Data, size_t Len) { return GetThis(p)->OnHeaderValue(Data, Len); }, .on_headers_complete = [](http_parser* p) { return GetThis(p)->OnHeadersComplete(); }, .on_body = [](http_parser*, const char*, size_t) { return 0; }, .on_message_complete = [](http_parser* p) { return GetThis(p)->OnMessageComplete(); }, .on_chunk_header{}, .on_chunk_complete{}}; // clang-format on HttpTrafficInspector::HttpTrafficInspector(Direction Dir, std::string_view SessionLabel) : m_Log(logging::Get("proxy.http")) , m_Direction(Dir) , m_SessionLabel(SessionLabel) { http_parser_init(&m_Parser, Dir == Direction::Request ? HTTP_REQUEST : HTTP_RESPONSE); m_Parser.data = this; } void HttpTrafficInspector::Inspect(const char* Data, size_t Length) { if (m_Disabled) { return; } http_parser_settings* Settings = (m_Direction == Direction::Request) ? &s_RequestSettings : &s_ResponseSettings; size_t Parsed = http_parser_execute(&m_Parser, Settings, Data, Length); if (m_Parser.upgrade) { if (m_Direction == Direction::Request) { ZEN_DEBUG("[{}] >> {} {} (upgrade to WebSocket)", m_SessionLabel, m_Method, m_Url); } else { ZEN_DEBUG("[{}] << {} (upgrade to WebSocket)", m_SessionLabel, m_StatusCode); } ResetMessageState(); m_Upgraded.store(true, std::memory_order_relaxed); m_Disabled = true; return; } http_errno Error = HTTP_PARSER_ERRNO(&m_Parser); if (Error != HPE_OK) { ZEN_DEBUG("[{}] non-HTTP traffic detected ({}), disabling inspection", m_SessionLabel, http_errno_name(Error)); m_Disabled = true; } else if (Parsed != Length) { ZEN_DEBUG("[{}] parser consumed {}/{} bytes, disabling inspection", m_SessionLabel, Parsed, Length); m_Disabled = true; } } int HttpTrafficInspector::OnUrl(const char* Data, size_t Length) { m_Url.append(Data, Length); return 0; } int HttpTrafficInspector::OnHeaderField(const char* Data, size_t Length) { m_CurrentHeaderField.assign(Data, Length); return 0; } int HttpTrafficInspector::OnHeaderValue(const char* Data, size_t Length) { if (m_CurrentHeaderField.size() == 14 && StrCaseCompare(m_CurrentHeaderField.c_str(), "Content-Length", 14) == 0) { int64_t Value = 0; std::from_chars(Data, Data + Length, Value); m_ContentLength = Value; } else if (!m_SessionIdCaptured && m_CurrentHeaderField.size() == 10 && StrCaseCompare(m_CurrentHeaderField.c_str(), "UE-Session", 10) == 0) { Oid Parsed; if (Oid::TryParse(std::string_view(Data, Length), Parsed)) { m_SessionId = Parsed; m_SessionIdCaptured = true; } } m_CurrentHeaderField.clear(); return 0; } int HttpTrafficInspector::OnHeadersComplete() { if (m_Direction == Direction::Request) { m_Method = http_method_str(static_cast(m_Parser.method)); } else { m_StatusCode = m_Parser.status_code; } return 0; } int HttpTrafficInspector::OnMessageComplete() { if (m_Direction == Direction::Request) { if (m_ContentLength >= 0) { ZEN_DEBUG("[{}] >> {} {} (content-length: {})", m_SessionLabel, m_Method, m_Url, m_ContentLength); } else { ZEN_DEBUG("[{}] >> {} {}", m_SessionLabel, m_Method, m_Url); } } else { if (m_ContentLength >= 0) { ZEN_DEBUG("[{}] << {} (content-length: {})", m_SessionLabel, m_StatusCode, m_ContentLength); } else { ZEN_DEBUG("[{}] << {}", m_SessionLabel, m_StatusCode); } } if (m_Observer) { m_Observer->OnMessageComplete(m_Direction, m_Method, m_Url, m_StatusCode, m_ContentLength); } m_MessageCount.fetch_add(1, std::memory_order_relaxed); ResetMessageState(); return 0; } Oid HttpTrafficInspector::GetSessionId() const { return m_SessionId; } bool HttpTrafficInspector::HasSessionId() const { return m_SessionIdCaptured; } void HttpTrafficInspector::ResetMessageState() { m_Url.clear(); m_Method.clear(); m_StatusCode = 0; m_ContentLength = -1; m_CurrentHeaderField.clear(); } } // namespace zen