aboutsummaryrefslogtreecommitdiff
path: root/src/zenserver/proxy/httptrafficinspector.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/zenserver/proxy/httptrafficinspector.cpp')
-rw-r--r--src/zenserver/proxy/httptrafficinspector.cpp197
1 files changed, 197 insertions, 0 deletions
diff --git a/src/zenserver/proxy/httptrafficinspector.cpp b/src/zenserver/proxy/httptrafficinspector.cpp
new file mode 100644
index 000000000..74ecbfd48
--- /dev/null
+++ b/src/zenserver/proxy/httptrafficinspector.cpp
@@ -0,0 +1,197 @@
+// Copyright Epic Games, Inc. All Rights Reserved.
+
+#include "httptrafficinspector.h"
+
+#include <zencore/logging.h>
+#include <zencore/string.h>
+
+#include <charconv>
+
+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<http_method>(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