diff --git a/source/extensions/filters/listener/http_inspector/BUILD b/source/extensions/filters/listener/http_inspector/BUILD index e9d3307bc848f..6a517ad578758 100644 --- a/source/extensions/filters/listener/http_inspector/BUILD +++ b/source/extensions/filters/listener/http_inspector/BUILD @@ -13,10 +13,8 @@ envoy_package() envoy_cc_library( name = "http_inspector_lib", srcs = ["http_inspector.cc"], - hdrs = [ - "http_inspector.h", - "http_protocol_header.h", - ], + hdrs = ["http_inspector.h"], + external_deps = ["http_parser"], deps = [ "//include/envoy/event:dispatcher_interface", "//include/envoy/event:timer_interface", diff --git a/source/extensions/filters/listener/http_inspector/http_inspector.cc b/source/extensions/filters/listener/http_inspector/http_inspector.cc index 087b4f40e430a..bb039f8cfe5d8 100644 --- a/source/extensions/filters/listener/http_inspector/http_inspector.cc +++ b/source/extensions/filters/listener/http_inspector/http_inspector.cc @@ -9,7 +9,6 @@ #include "common/common/macros.h" #include "common/http/headers.h" -#include "extensions/filters/listener/http_inspector/http_protocol_header.h" #include "extensions/transport_sockets/well_known_names.h" #include "absl/strings/match.h" @@ -26,7 +25,13 @@ Config::Config(Stats::Scope& scope) const absl::string_view Filter::HTTP2_CONNECTION_PREFACE = "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n"; thread_local uint8_t Filter::buf_[Config::MAX_INSPECT_SIZE]; -Filter::Filter(const ConfigSharedPtr config) : config_(config) {} +Filter::Filter(const ConfigSharedPtr config) : config_(config) { + http_parser_init(&parser_, HTTP_REQUEST); +} + +http_parser_settings Filter::settings_{ + nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, +}; Network::FilterStatus Filter::onAccept(Network::ListenerFilterCallbacks& cb) { ENVOY_LOG(debug, "http inspector: new connection accepted"); @@ -132,34 +137,39 @@ ParseState Filter::parseHttpHeader(absl::string_view data) { protocol_ = "HTTP/2"; return ParseState::Done; } else { - const size_t pos = data.find_first_of("\r\n"); + absl::string_view new_data = data.substr(parser_.nread); + const size_t pos = new_data.find_first_of("\r\n"); + if (pos != absl::string_view::npos) { - const absl::string_view request_line = data.substr(0, pos); - const std::vector fields = - absl::StrSplit(request_line, absl::MaxSplits(' ', 4)); - - // Method SP Request-URI SP HTTP-Version - if (fields.size() != 3) { - ENVOY_LOG(trace, "http inspector: invalid http1x request line"); - // done(false); + // Include \r or \n + new_data = new_data.substr(0, pos + 1); + ssize_t rc = http_parser_execute(&parser_, &settings_, new_data.data(), new_data.length()); + ENVOY_LOG(trace, "http inspector: http_parser parsed {} chars, error code: {}", rc, + HTTP_PARSER_ERRNO(&parser_)); + + // Errors in parsing HTTP. + if (HTTP_PARSER_ERRNO(&parser_) != HPE_OK && HTTP_PARSER_ERRNO(&parser_) != HPE_PAUSED) { return ParseState::Error; } - if (http1xMethods().count(fields[0]) == 0 || httpProtocols().count(fields[2]) == 0) { - ENVOY_LOG(trace, "http inspector: method: {} or protocol: {} not valid", fields[0], - fields[2]); - // done(false); - return ParseState::Error; + if (parser_.http_major == 1 && parser_.http_minor == 1) { + protocol_ = Http::Headers::get().ProtocolStrings.Http11String; + } else { + // Set other HTTP protocols to HTTP/1.0 + protocol_ = Http::Headers::get().ProtocolStrings.Http10String; } - - ENVOY_LOG(trace, "http inspector: method: {}, request uri: {}, protocol: {}", fields[0], - fields[1], fields[2]); - - protocol_ = fields[2]; - // done(true); return ParseState::Done; } else { - return ParseState::Continue; + ssize_t rc = http_parser_execute(&parser_, &settings_, new_data.data(), new_data.length()); + ENVOY_LOG(trace, "http inspector: http_parser parsed {} chars, error code: {}", rc, + HTTP_PARSER_ERRNO(&parser_)); + + // Errors in parsing HTTP. + if (HTTP_PARSER_ERRNO(&parser_) != HPE_OK && HTTP_PARSER_ERRNO(&parser_) != HPE_PAUSED) { + return ParseState::Error; + } else { + return ParseState::Continue; + } } } } @@ -189,54 +199,6 @@ void Filter::done(bool success) { } } -const absl::flat_hash_set& Filter::httpProtocols() const { - CONSTRUCT_ON_FIRST_USE(absl::flat_hash_set, - Http::Headers::get().ProtocolStrings.Http10String, - Http::Headers::get().ProtocolStrings.Http11String); -} - -const absl::flat_hash_set& Filter::http1xMethods() const { - CONSTRUCT_ON_FIRST_USE(absl::flat_hash_set, - {HttpInspector::ExtendedHeader::get().MethodValues.Acl, - HttpInspector::ExtendedHeader::get().MethodValues.Baseline_Control, - HttpInspector::ExtendedHeader::get().MethodValues.Bind, - HttpInspector::ExtendedHeader::get().MethodValues.Checkin, - HttpInspector::ExtendedHeader::get().MethodValues.Checkout, - HttpInspector::ExtendedHeader::get().MethodValues.Connect, - HttpInspector::ExtendedHeader::get().MethodValues.Copy, - HttpInspector::ExtendedHeader::get().MethodValues.Delete, - HttpInspector::ExtendedHeader::get().MethodValues.Get, - HttpInspector::ExtendedHeader::get().MethodValues.Head, - HttpInspector::ExtendedHeader::get().MethodValues.Label, - HttpInspector::ExtendedHeader::get().MethodValues.Link, - HttpInspector::ExtendedHeader::get().MethodValues.Lock, - HttpInspector::ExtendedHeader::get().MethodValues.Merge, - HttpInspector::ExtendedHeader::get().MethodValues.Mkactivity, - HttpInspector::ExtendedHeader::get().MethodValues.Mkcalendar, - HttpInspector::ExtendedHeader::get().MethodValues.Mkcol, - HttpInspector::ExtendedHeader::get().MethodValues.Mkredirectref, - HttpInspector::ExtendedHeader::get().MethodValues.Mkworkspace, - HttpInspector::ExtendedHeader::get().MethodValues.Move, - HttpInspector::ExtendedHeader::get().MethodValues.Options, - HttpInspector::ExtendedHeader::get().MethodValues.Orderpatch, - HttpInspector::ExtendedHeader::get().MethodValues.Patch, - HttpInspector::ExtendedHeader::get().MethodValues.Post, - HttpInspector::ExtendedHeader::get().MethodValues.Proppatch, - HttpInspector::ExtendedHeader::get().MethodValues.Purge, - HttpInspector::ExtendedHeader::get().MethodValues.Put, - HttpInspector::ExtendedHeader::get().MethodValues.Rebind, - HttpInspector::ExtendedHeader::get().MethodValues.Report, - HttpInspector::ExtendedHeader::get().MethodValues.Search, - HttpInspector::ExtendedHeader::get().MethodValues.Trace, - HttpInspector::ExtendedHeader::get().MethodValues.Unbind, - HttpInspector::ExtendedHeader::get().MethodValues.Uncheckout, - HttpInspector::ExtendedHeader::get().MethodValues.Unlink, - HttpInspector::ExtendedHeader::get().MethodValues.Unlock, - HttpInspector::ExtendedHeader::get().MethodValues.Update, - HttpInspector::ExtendedHeader::get().MethodValues.Updateredirectref, - HttpInspector::ExtendedHeader::get().MethodValues.Version_Control}); -} - } // namespace HttpInspector } // namespace ListenerFilters } // namespace Extensions diff --git a/source/extensions/filters/listener/http_inspector/http_inspector.h b/source/extensions/filters/listener/http_inspector/http_inspector.h index 4c94a94287190..a73beeea11a97 100644 --- a/source/extensions/filters/listener/http_inspector/http_inspector.h +++ b/source/extensions/filters/listener/http_inspector/http_inspector.h @@ -1,5 +1,7 @@ #pragma once +#include + #include "envoy/event/file_event.h" #include "envoy/event/timer.h" #include "envoy/network/filter.h" @@ -82,6 +84,8 @@ class Filter : public Network::ListenerFilter, Logger::Loggable - -#include "common/singleton/const_singleton.h" - -namespace Envoy { -namespace Extensions { -namespace ListenerFilters { -namespace HttpInspector { - -/** - * Class including extended methods. - */ -class ExtendedHeaderValues { -public: - struct { - const std::string Acl{"ACL"}; - const std::string Baseline_Control{"BASELINE-CONTROL"}; - const std::string Bind{"BIND"}; - const std::string Checkin{"CHECKIN"}; - const std::string Checkout{"CHECKOUT"}; - const std::string Connect{"CONNECT"}; - const std::string Copy{"COPY"}; - const std::string Delete{"DELETE"}; - const std::string Get{"GET"}; - const std::string Head{"HEAD"}; - const std::string Label{"LABEL"}; - const std::string Link{"LINK"}; - const std::string Lock{"LOCK"}; - const std::string Merge{"Merge"}; - const std::string Mkactivity{"MKACTIVITY"}; - const std::string Mkcalendar{"MKCALENDAR"}; - const std::string Mkcol{"MKCOL"}; - const std::string Mkredirectref{"MKREDIRECTREF"}; - const std::string Mkworkspace{"MKWORKSPACE"}; - const std::string Move{"MOVE"}; - const std::string Options{"OPTIONS"}; - const std::string Orderpatch{"ORDERPATCH"}; - const std::string Patch{"PATCH"}; - const std::string Post{"POST"}; - const std::string Pri{"PRI"}; - const std::string Proppatch{"PROPPATCH"}; - const std::string Purge{"PURGE"}; - const std::string Put{"PUT"}; - const std::string Rebind{"REBIND"}; - const std::string Report{"REPORT"}; - const std::string Search{"SEARCH"}; - const std::string Trace{"TRACE"}; - const std::string Unbind{"UNBIND"}; - const std::string Uncheckout{"UNCHECKOUT"}; - const std::string Unlink{"UNLINK"}; - const std::string Unlock{"UNLOCK"}; - const std::string Update{"UPDATE"}; - const std::string Updateredirectref{"UPDATEREDIRECTREF"}; - const std::string Version_Control{"VERSION-CONTROL"}; - } MethodValues; -}; - -using ExtendedHeader = ConstSingleton; - -} // namespace HttpInspector -} // namespace ListenerFilters -} // namespace Extensions -} // namespace Envoy diff --git a/test/extensions/filters/listener/http_inspector/http_inspector_test.cc b/test/extensions/filters/listener/http_inspector/http_inspector_test.cc index 1ecf224aa6dfc..b028974fcb8d5 100644 --- a/test/extensions/filters/listener/http_inspector/http_inspector_test.cc +++ b/test/extensions/filters/listener/http_inspector/http_inspector_test.cc @@ -180,9 +180,52 @@ TEST_F(HttpInspectorTest, InspectHttp11) { EXPECT_EQ(1, cfg_->stats().http11_found_.value()); } +TEST_F(HttpInspectorTest, InspectHttp11WithNonEmptyRequestBody) { + init(); + const absl::string_view header = + "GET /anything HTTP/1.1\r\nhost: google.com\r\nuser-agent: curl/7.64.0\r\naccept: " + "*/*\r\nx-forwarded-proto: http\r\nx-request-id: " + "a52df4a0-ed00-4a19-86a7-80e5049c6c84\r\nx-envoy-expected-rq-timeout-ms: " + "15000\r\ncontent-length: 3\r\n\r\nfoo"; + + EXPECT_CALL(os_sys_calls_, recv(42, _, _, MSG_PEEK)) + .WillOnce(Invoke([&header](int, void* buffer, size_t length, int) -> Api::SysCallSizeResult { + ASSERT(length >= header.size()); + memcpy(buffer, header.data(), header.size()); + return Api::SysCallSizeResult{ssize_t(header.size()), 0}; + })); + + const std::vector alpn_protos{absl::string_view("http/1.1")}; + + EXPECT_CALL(socket_, setRequestedApplicationProtocols(alpn_protos)); + EXPECT_CALL(cb_, continueFilterChain(true)); + file_event_callback_(Event::FileReadyType::Read); + EXPECT_EQ(1, cfg_->stats().http11_found_.value()); +} + +TEST_F(HttpInspectorTest, ExtraSpaceInRequestLine) { + init(); + const absl::string_view header = "GET /anything HTTP/1.1\r\n\r\n"; + // ^^ ^^ + + EXPECT_CALL(os_sys_calls_, recv(42, _, _, MSG_PEEK)) + .WillOnce(Invoke([&header](int, void* buffer, size_t length, int) -> Api::SysCallSizeResult { + ASSERT(length >= header.size()); + memcpy(buffer, header.data(), header.size()); + return Api::SysCallSizeResult{ssize_t(header.size()), 0}; + })); + + const std::vector alpn_protos{absl::string_view("http/1.1")}; + + EXPECT_CALL(socket_, setRequestedApplicationProtocols(alpn_protos)); + EXPECT_CALL(cb_, continueFilterChain(true)); + file_event_callback_(Event::FileReadyType::Read); + EXPECT_EQ(1, cfg_->stats().http11_found_.value()); +} + TEST_F(HttpInspectorTest, InvalidHttpMethod) { init(); - const absl::string_view header = "BAD /anything HTTP/1.1\r\n"; + const absl::string_view header = "BAD /anything HTTP/1.1"; EXPECT_CALL(os_sys_calls_, recv(42, _, _, MSG_PEEK)) .WillOnce(Invoke([&header](int, void* buffer, size_t length, int) -> Api::SysCallSizeResult { @@ -199,7 +242,7 @@ TEST_F(HttpInspectorTest, InvalidHttpMethod) { TEST_F(HttpInspectorTest, InvalidHttpRequestLine) { init(); - const absl::string_view header = "BAD /anything HTTP/1.1"; + const absl::string_view header = "BAD /anything HTTP/1.1\r\n"; EXPECT_CALL(os_sys_calls_, recv(42, _, _, MSG_PEEK)) .WillOnce(Invoke([&header](int, void* buffer, size_t length, int) -> Api::SysCallSizeResult { @@ -209,11 +252,12 @@ TEST_F(HttpInspectorTest, InvalidHttpRequestLine) { })); EXPECT_CALL(socket_, setRequestedApplicationProtocols(_)).Times(0); - EXPECT_CALL(cb_, continueFilterChain(_)).Times(0); + EXPECT_CALL(cb_, continueFilterChain(_)); file_event_callback_(Event::FileReadyType::Read); + EXPECT_EQ(1, cfg_->stats().http_not_found_.value()); } -TEST_F(HttpInspectorTest, UnsupportedHttpProtocol) { +TEST_F(HttpInspectorTest, OldHttpProtocol) { init(); const absl::string_view header = "GET /anything HTTP/0.9\r\n"; @@ -224,10 +268,11 @@ TEST_F(HttpInspectorTest, UnsupportedHttpProtocol) { return Api::SysCallSizeResult{ssize_t(header.size()), 0}; })); - EXPECT_CALL(socket_, setRequestedApplicationProtocols(_)).Times(0); + const std::vector alpn_protos{absl::string_view("http/1.0")}; + EXPECT_CALL(socket_, setRequestedApplicationProtocols(alpn_protos)); EXPECT_CALL(cb_, continueFilterChain(true)); file_event_callback_(Event::FileReadyType::Read); - EXPECT_EQ(1, cfg_->stats().http_not_found_.value()); + EXPECT_EQ(1, cfg_->stats().http10_found_.value()); } TEST_F(HttpInspectorTest, InvalidRequestLine) { @@ -304,7 +349,6 @@ TEST_F(HttpInspectorTest, ReadError) { } TEST_F(HttpInspectorTest, MultipleReadsHttp2) { - init(); const std::vector alpn_protos = {absl::string_view("h2c")}; @@ -326,8 +370,8 @@ TEST_F(HttpInspectorTest, MultipleReadsHttp2) { EXPECT_CALL(os_sys_calls_, recv(42, _, _, MSG_PEEK)) .WillOnce( Invoke([&data, i](int, void* buffer, size_t length, int) -> Api::SysCallSizeResult { - ASSERT(length >= data.size()); - memcpy(buffer, data.data(), data.size()); + ASSERT(length >= i); + memcpy(buffer, data.data(), i); return Api::SysCallSizeResult{ssize_t(i), 0}; })); } @@ -345,7 +389,6 @@ TEST_F(HttpInspectorTest, MultipleReadsHttp2) { } TEST_F(HttpInspectorTest, MultipleReadsHttp2BadPreface) { - init(); const std::string header = "505249202a20485454502f322e300d0a0d0c"; const std::vector data = Hex::decode(header); @@ -360,8 +403,8 @@ TEST_F(HttpInspectorTest, MultipleReadsHttp2BadPreface) { EXPECT_CALL(os_sys_calls_, recv(42, _, _, MSG_PEEK)) .WillOnce( Invoke([&data, i](int, void* buffer, size_t length, int) -> Api::SysCallSizeResult { - ASSERT(length >= data.size()); - memcpy(buffer, data.data(), data.size()); + ASSERT(length >= i); + memcpy(buffer, data.data(), i); return Api::SysCallSizeResult{ssize_t(i), 0}; })); } @@ -379,9 +422,7 @@ TEST_F(HttpInspectorTest, MultipleReadsHttp2BadPreface) { } TEST_F(HttpInspectorTest, MultipleReadsHttp1) { - init(); - const absl::string_view data = "GET /anything HTTP/1.0\r"; { InSequence s; @@ -390,12 +431,12 @@ TEST_F(HttpInspectorTest, MultipleReadsHttp1) { return Api::SysCallSizeResult{ssize_t(-1), EAGAIN}; })); - for (size_t i = 1; i <= data.length(); i++) { + for (size_t i = 1; i <= data.size(); i++) { EXPECT_CALL(os_sys_calls_, recv(42, _, _, MSG_PEEK)) .WillOnce( Invoke([&data, i](int, void* buffer, size_t length, int) -> Api::SysCallSizeResult { - ASSERT(length >= data.size()); - memcpy(buffer, data.data(), data.size()); + ASSERT(length >= i); + memcpy(buffer, data.data(), i); return Api::SysCallSizeResult{ssize_t(i), 0}; })); } @@ -414,12 +455,9 @@ TEST_F(HttpInspectorTest, MultipleReadsHttp1) { } TEST_F(HttpInspectorTest, MultipleReadsHttp1IncompleteHeader) { - init(); - const absl::string_view data = "GE"; bool end_stream = false; - { InSequence s; @@ -427,13 +465,13 @@ TEST_F(HttpInspectorTest, MultipleReadsHttp1IncompleteHeader) { return Api::SysCallSizeResult{ssize_t(-1), EAGAIN}; })); - for (size_t i = 1; i <= data.length(); i++) { + for (size_t i = 1; i <= data.size(); i++) { EXPECT_CALL(os_sys_calls_, recv(42, _, _, MSG_PEEK)) .WillOnce(Invoke([&data, &end_stream, i](int, void* buffer, size_t length, int) -> Api::SysCallSizeResult { - ASSERT(length >= data.size()); - memcpy(buffer, data.data(), data.size()); - if (i == data.length()) { + ASSERT(length >= i); + memcpy(buffer, data.data(), i); + if (i == data.size()) { end_stream = true; } @@ -448,14 +486,44 @@ TEST_F(HttpInspectorTest, MultipleReadsHttp1IncompleteHeader) { file_event_callback_(Event::FileReadyType::Read); } } -TEST_F(HttpInspectorTest, MultipleReadsHttp1BadProtocol) { +TEST_F(HttpInspectorTest, MultipleReadsHttp1IncompleteBadHeader) { init(); + const absl::string_view data = "X"; + { + InSequence s; + + EXPECT_CALL(os_sys_calls_, recv(42, _, _, MSG_PEEK)).WillOnce(InvokeWithoutArgs([]() { + return Api::SysCallSizeResult{ssize_t(-1), EAGAIN}; + })); + for (size_t i = 1; i <= data.size(); i++) { + EXPECT_CALL(os_sys_calls_, recv(42, _, _, MSG_PEEK)) + .WillOnce( + Invoke([&data, i](int, void* buffer, size_t length, int) -> Api::SysCallSizeResult { + ASSERT(length >= i); + memcpy(buffer, data.data(), i); + return Api::SysCallSizeResult{ssize_t(i), 0}; + })); + } + } + + bool got_continue = false; + EXPECT_CALL(socket_, setRequestedApplicationProtocols(_)).Times(0); + EXPECT_CALL(cb_, continueFilterChain(true)).WillOnce(InvokeWithoutArgs([&got_continue]() { + got_continue = true; + })); + while (!got_continue) { + file_event_callback_(Event::FileReadyType::Read); + } + EXPECT_EQ(1, cfg_->stats().http_not_found_.value()); +} + +TEST_F(HttpInspectorTest, MultipleReadsHttp1BadProtocol) { + init(); const std::string valid_header = "GET /index HTTP/1.1\r"; // offset: 0 10 const std::string truncate_header = valid_header.substr(0, 14).append("\r"); - { InSequence s; @@ -463,7 +531,7 @@ TEST_F(HttpInspectorTest, MultipleReadsHttp1BadProtocol) { return Api::SysCallSizeResult{ssize_t(-1), EAGAIN}; })); - for (size_t i = 1; i <= truncate_header.length(); i++) { + for (size_t i = 1; i <= truncate_header.size(); i++) { EXPECT_CALL(os_sys_calls_, recv(42, _, _, MSG_PEEK)) .WillOnce(Invoke([&truncate_header, i](int, void* buffer, size_t length, int) -> Api::SysCallSizeResult { @@ -485,6 +553,87 @@ TEST_F(HttpInspectorTest, MultipleReadsHttp1BadProtocol) { EXPECT_EQ(1, cfg_->stats().http_not_found_.value()); } +TEST_F(HttpInspectorTest, Http1WithLargeRequestLine) { + init(); + absl::string_view method = "GET", http = "/index HTTP/1.0\r"; + std::string spaces(Config::MAX_INSPECT_SIZE - method.size() - http.size(), ' '); + const std::string data = absl::StrCat(method, spaces, http); + { + InSequence s; + + EXPECT_CALL(os_sys_calls_, recv(42, _, _, MSG_PEEK)).WillOnce(InvokeWithoutArgs([]() { + return Api::SysCallSizeResult{ssize_t(-1), EAGAIN}; + })); + + uint64_t num_loops = Config::MAX_INSPECT_SIZE; +#if defined(__has_feature) && \ + ((__has_feature(thread_sanitizer)) || (__has_feature(address_sanitizer))) + num_loops = 2; +#endif + + for (size_t i = 1; i <= num_loops; i++) { + size_t len = i; + if (num_loops == 2) { + len = size_t(Config::MAX_INSPECT_SIZE / (3 - i)); + } + EXPECT_CALL(os_sys_calls_, recv(42, _, _, MSG_PEEK)) + .WillOnce( + Invoke([&data, len](int, void* buffer, size_t length, int) -> Api::SysCallSizeResult { + ASSERT(length >= len); + memcpy(buffer, data.data(), len); + return Api::SysCallSizeResult{ssize_t(len), 0}; + })); + } + } + + bool got_continue = false; + const std::vector alpn_protos = {absl::string_view("http/1.0")}; + EXPECT_CALL(socket_, setRequestedApplicationProtocols(alpn_protos)); + EXPECT_CALL(cb_, continueFilterChain(true)).WillOnce(InvokeWithoutArgs([&got_continue]() { + got_continue = true; + })); + while (!got_continue) { + file_event_callback_(Event::FileReadyType::Read); + } + EXPECT_EQ(1, cfg_->stats().http10_found_.value()); +} + +TEST_F(HttpInspectorTest, Http1WithLargeHeader) { + init(); + absl::string_view request = "GET /index HTTP/1.0\rfield: "; + // 0 20 + std::string value(Config::MAX_INSPECT_SIZE - request.size(), 'a'); + const std::string data = absl::StrCat(request, value); + { + InSequence s; + + EXPECT_CALL(os_sys_calls_, recv(42, _, _, MSG_PEEK)).WillOnce(InvokeWithoutArgs([]() { + return Api::SysCallSizeResult{ssize_t(-1), EAGAIN}; + })); + + for (size_t i = 1; i <= 20; i++) { + EXPECT_CALL(os_sys_calls_, recv(42, _, _, MSG_PEEK)) + .WillOnce( + Invoke([&data, i](int, void* buffer, size_t length, int) -> Api::SysCallSizeResult { + ASSERT(length >= data.size()); + memcpy(buffer, data.data(), i); + return Api::SysCallSizeResult{ssize_t(i), 0}; + })); + } + } + + bool got_continue = false; + const std::vector alpn_protos = {absl::string_view("http/1.0")}; + EXPECT_CALL(socket_, setRequestedApplicationProtocols(alpn_protos)); + EXPECT_CALL(cb_, continueFilterChain(true)).WillOnce(InvokeWithoutArgs([&got_continue]() { + got_continue = true; + })); + while (!got_continue) { + file_event_callback_(Event::FileReadyType::Read); + } + EXPECT_EQ(1, cfg_->stats().http10_found_.value()); +} + } // namespace } // namespace HttpInspector } // namespace ListenerFilters