diff --git a/bazel/external/llhttp.BUILD b/bazel/external/llhttp.BUILD new file mode 100644 index 0000000000000..c125bce7baf0d --- /dev/null +++ b/bazel/external/llhttp.BUILD @@ -0,0 +1,13 @@ +licenses(["notice"]) # Apache 2 + +cc_library( + name = "llhttp", + srcs = [ + "src/api.c", + "src/http.c", + "src/llhttp.c", + ], + hdrs = ["include/llhttp.h"], + includes = ["include"], + visibility = ["//visibility:public"], +) diff --git a/bazel/repositories.bzl b/bazel/repositories.bzl index 18230f79d9545..62f0bed44f72d 100644 --- a/bazel/repositories.bzl +++ b/bazel/repositories.bzl @@ -168,6 +168,7 @@ def envoy_dependencies(skip_targets = []): _com_github_moonjit_moonjit() _com_github_nghttp2_nghttp2() _com_github_nodejs_http_parser() + _com_github_nodejs_llhttp() _com_github_tencent_rapidjson() _com_google_absl() _com_google_googletest() @@ -439,6 +440,16 @@ def _com_github_nodejs_http_parser(): actual = "@com_github_nodejs_http_parser//:http_parser", ) +def _com_github_nodejs_llhttp(): + _repository_impl( + name = "com_github_nodejs_llhttp", + build_file = "@envoy//bazel/external:llhttp.BUILD", + ) + native.bind( + name = "llhttp", + actual = "@com_github_nodejs_llhttp//:llhttp", + ) + def _com_google_googletest(): _repository_impl("com_google_googletest") native.bind( diff --git a/bazel/repository_locations.bzl b/bazel/repository_locations.bzl index 9561aae50d6f1..52fc2a96d8d43 100644 --- a/bazel/repository_locations.bzl +++ b/bazel/repository_locations.bzl @@ -290,6 +290,13 @@ DEPENDENCY_REPOSITORIES = dict( use_category = ["dataplane"], cpe = "N/A", ), + com_github_nodejs_llhttp = dict( + sha256 = "48f882f0b6cecc48aec8f81072ee4d80fe9a4b5e1bce009e3cf8aecbe5892c1a", + strip_prefix = "llhttp-release-v2.0.5", + urls = ["https://github.com/nodejs/llhttp/archive/release/v2.0.5.tar.gz"], + use_category = ["dataplane"], + cpe = "N/A", + ), com_github_pallets_jinja = dict( sha256 = "db49236731373e4f3118af880eb91bb0aa6978bc0cf8b35760f6a026f1a9ffc4", strip_prefix = "jinja-2.10.3", diff --git a/docs/root/version_history/current.rst b/docs/root/version_history/current.rst index a8b6e0f694c32..47a43924a8903 100644 --- a/docs/root/version_history/current.rst +++ b/docs/root/version_history/current.rst @@ -24,6 +24,7 @@ Changes Can be reverted temporarily by setting runtime feature `envoy.reloadable_features.fix_upgrade_response` to false. * http: remove legacy connection pool code and their runtime features: `envoy.reloadable_features.new_http1_connection_pool_behavior` and `envoy.reloadable_features.new_http2_connection_pool_behavior`. +* http: add llhttp as an alternative http parser * listener: added in place filter chain update flow for tcp listener update which doesn't close connections if the corresponding network filter chain is equivalent during the listener update. Can be disabled by setting runtime feature `envoy.reloadable_features.listener_in_place_filterchain_update` to false. Also added additional draining filter chain stat for :ref:`listener manager ` to track the number of draining filter chains and the number of in place update attempts. diff --git a/include/envoy/server/options.h b/include/envoy/server/options.h index 96baa7fbdfef8..2ac2d74638cef 100644 --- a/include/envoy/server/options.h +++ b/include/envoy/server/options.h @@ -195,6 +195,11 @@ class Options { */ virtual bool fakeSymbolTableEnabled() const PURE; + /** + * @return whether to use the legacy HTTP/1.x parser. + */ + virtual bool legacyHttpParserEnabled() const PURE; + /** * @return bool indicating whether cpuset size should determine the number of worker threads. */ diff --git a/source/common/http/http1/BUILD b/source/common/http/http1/BUILD index d9b00a3171578..1c0dda069e439 100644 --- a/source/common/http/http1/BUILD +++ b/source/common/http/http1/BUILD @@ -18,8 +18,9 @@ envoy_cc_library( name = "codec_lib", srcs = ["codec_impl.cc"], hdrs = ["codec_impl.h"], - external_deps = ["http_parser"], deps = [ + ":parser_factory_lib", + ":parser_interface", "//include/envoy/buffer:buffer_interface", "//include/envoy/http:codec_interface", "//include/envoy/http:header_map_interface", @@ -66,3 +67,45 @@ envoy_cc_library( "//source/common/upstream:upstream_lib", ], ) + +envoy_cc_library( + name = "parser_interface", + hdrs = ["parser.h"], +) + +envoy_cc_library( + name = "llhttp_lib", + srcs = ["llhttp_parser.cc"], + hdrs = ["llhttp_parser.h"], + external_deps = [ + "llhttp", + ], + deps = [ + ":parser_interface", + "//source/common/common:assert_lib", + ], +) + +envoy_cc_library( + name = "legacy_http_parser_lib", + srcs = ["legacy_http_parser.cc"], + hdrs = ["legacy_http_parser.h"], + external_deps = [ + "http_parser", + ], + deps = [ + ":parser_interface", + "//source/common/common:assert_lib", + ], +) + +envoy_cc_library( + name = "parser_factory_lib", + srcs = ["parser_factory.cc"], + hdrs = ["parser_factory.h"], + deps = [ + ":legacy_http_parser_lib", + ":llhttp_lib", + ":parser_interface", + ], +) diff --git a/source/common/http/http1/codec_impl.cc b/source/common/http/http1/codec_impl.cc index d4aacd3a8ccc6..92d8077846683 100644 --- a/source/common/http/http1/codec_impl.cc +++ b/source/common/http/http1/codec_impl.cc @@ -389,50 +389,13 @@ void RequestEncoderImpl::encodeHeaders(const RequestHeaderMap& headers, bool end encodeHeadersBase(headers, end_stream); } -http_parser_settings ConnectionImpl::settings_{ - [](http_parser* parser) -> int { - static_cast(parser->data)->onMessageBeginBase(); - return 0; - }, - [](http_parser* parser, const char* at, size_t length) -> int { - static_cast(parser->data)->onUrl(at, length); - return 0; - }, - nullptr, // on_status - [](http_parser* parser, const char* at, size_t length) -> int { - static_cast(parser->data)->onHeaderField(at, length); - return 0; - }, - [](http_parser* parser, const char* at, size_t length) -> int { - static_cast(parser->data)->onHeaderValue(at, length); - return 0; - }, - [](http_parser* parser) -> int { - return static_cast(parser->data)->onHeadersCompleteBase(); - }, - [](http_parser* parser, const char* at, size_t length) -> int { - static_cast(parser->data)->bufferBody(at, length); - return 0; - }, - [](http_parser* parser) -> int { - static_cast(parser->data)->onMessageCompleteBase(); - return 0; - }, - [](http_parser* parser) -> int { - // A 0-byte chunk header is used to signal the end of the chunked body. - // When this function is called, http-parser holds the size of the chunk in - // parser->content_length. See - // https://github.com/nodejs/http-parser/blob/v2.9.3/http_parser.h#L336 - const bool is_final_chunk = (parser->content_length == 0); - static_cast(parser->data)->onChunkHeader(is_final_chunk); - return 0; - }, - nullptr // on_chunk_complete -}; +const ToLowerTable& ConnectionImpl::toLowerTable() { + static auto* table = new ToLowerTable(); + return *table; +} ConnectionImpl::ConnectionImpl(Network::Connection& connection, Stats::Scope& stats, - http_parser_type type, uint32_t max_headers_kb, - const uint32_t max_headers_count, + uint32_t max_headers_kb, const uint32_t max_headers_count, HeaderKeyFormatterPtr&& header_key_formatter, bool enable_trailers) : connection_(connection), stats_{ALL_HTTP1_CODEC_STATS(POOL_COUNTER_PREFIX(stats, "http1."))}, header_key_formatter_(std::move(header_key_formatter)), processing_trailers_(false), @@ -448,8 +411,6 @@ ConnectionImpl::ConnectionImpl(Network::Connection& connection, Stats::Scope& st [&]() -> void { this->onAboveHighWatermark(); }), max_headers_kb_(max_headers_kb), max_headers_count_(max_headers_count) { output_buffer_.setWatermarks(connection.bufferLimit()); - http_parser_init(&parser_, type); - parser_.data = this; } void ConnectionImpl::completeLastHeader() { @@ -511,7 +472,7 @@ Http::Status ConnectionImpl::innerDispatch(Buffer::Instance& data) { } // Always unpause before dispatch. - http_parser_pause(&parser_, 0); + parser_->resume(); ssize_t total_parsed = 0; if (data.length() > 0) { @@ -540,14 +501,16 @@ Http::Status ConnectionImpl::innerDispatch(Buffer::Instance& data) { } size_t ConnectionImpl::dispatchSlice(const char* slice, size_t len) { - ssize_t rc = http_parser_execute(&parser_, &settings_, slice, len); - if (HTTP_PARSER_ERRNO(&parser_) != HPE_OK && HTTP_PARSER_ERRNO(&parser_) != HPE_PAUSED) { + ASSERT(parser_ != nullptr); + const size_t bytes_read = parser_->execute(slice, len); + + if (parser_->getErrno() != static_cast(ParserStatus::Ok) && + parser_->getErrno() != static_cast(ParserStatus::Paused)) { sendProtocolError(Http1ResponseCodeDetails::get().HttpCodecError); - throw CodecProtocolException("http/1.1 protocol error: " + - std::string(http_errno_name(HTTP_PARSER_ERRNO(&parser_)))); + throw CodecProtocolException("http/1.1 protocol error: " + std::string(parser_->errnoName())); } - return rc; + return bytes_read; } void ConnectionImpl::onHeaderField(const char* data, size_t length) { @@ -614,8 +577,11 @@ int ConnectionImpl::onHeadersCompleteBase() { ASSERT(!processing_trailers_); ENVOY_CONN_LOG(trace, "onHeadersCompleteBase", connection_); completeLastHeader(); - - if (!(parser_.http_major == 1 && parser_.http_minor == 1)) { + // Validate that the completed HeaderMap's cached byte size exists and is correct. + // This assert iterates over the HeaderMap. + ASSERT(current_header_map_->byteSize().has_value() && + current_header_map_->byteSize() == current_header_map_->byteSizeInternal()); + if (!(parser_->httpMajor() == 1 && parser_->httpMinor() == 1)) { // This is not necessarily true, but it's good enough since higher layers only care if this is // HTTP/1.1 or not. protocol_ = Protocol::Http10; @@ -664,6 +630,7 @@ int ConnectionImpl::onHeadersCompleteBase() { } } + seen_content_length_ = current_header_map_->ContentLength() != nullptr; int rc = onHeadersComplete(); header_parsing_state_ = HeaderParsingState::Done; @@ -691,7 +658,7 @@ void ConnectionImpl::onChunkHeader(bool is_final_chunk) { } } -void ConnectionImpl::onMessageCompleteBase() { +int ConnectionImpl::onMessageCompleteBase() { ENVOY_CONN_LOG(trace, "message complete", connection_); dispatchBufferedBody(); @@ -701,8 +668,7 @@ void ConnectionImpl::onMessageCompleteBase() { // upgrade payload will be treated as stream body. ASSERT(!deferred_end_stream_headers_); ENVOY_CONN_LOG(trace, "Pausing parser due to upgrade.", connection_); - http_parser_pause(&parser_, 1); - return; + return parser_->pause(); } // If true, this indicates we were processing trailers and must @@ -711,7 +677,7 @@ void ConnectionImpl::onMessageCompleteBase() { completeLastHeader(); } - onMessageComplete(); + return onMessageComplete(); } void ConnectionImpl::onMessageBeginBase() { @@ -738,10 +704,9 @@ ServerConnectionImpl::ServerConnectionImpl( const uint32_t max_request_headers_count, envoy::config::core::v3::HttpProtocolOptions::HeadersWithUnderscoresAction headers_with_underscores_action) - : ConnectionImpl(connection, stats, HTTP_REQUEST, max_request_headers_kb, + : ConnectionImpl(connection, stats, MessageType::Request, max_request_headers_kb, max_request_headers_count, formatter(settings), settings.enable_trailers_), - callbacks_(callbacks), codec_settings_(settings), - response_buffer_releasor_([this](const Buffer::OwnedBufferFragmentImpl* fragment) { + callbacks_(callbacks), codec_settings_(settings), response_buffer_releasor_([this](const Buffer::OwnedBufferFragmentImpl* fragment) { releaseOutboundResponse(fragment); }), // Pipelining is generally not well supported on the internet and has a series of dangerous @@ -752,7 +717,9 @@ ServerConnectionImpl::ServerConnectionImpl( Runtime::getInteger("envoy.do_not_use_going_away_max_http2_outbound_responses", 2)), flood_protection_( Runtime::runtimeFeatureEnabled("envoy.reloadable_features.http1_flood_protection")), - headers_with_underscores_action_(headers_with_underscores_action) {} + headers_with_underscores_action_(headers_with_underscores_action) { + parser_ = ParserFactory::create(MessageType::Request, this); +} void ServerConnectionImpl::onEncodeComplete() { if (active_request_.value().remote_complete_) { @@ -766,13 +733,14 @@ void ServerConnectionImpl::onEncodeComplete() { void ServerConnectionImpl::handlePath(RequestHeaderMap& headers, unsigned int method) { HeaderString path(Headers::get().Path); - bool is_connect = (method == HTTP_CONNECT); + bool is_connect = (method == static_cast(Method::Connect)); // The url is relative or a wildcard when the method is OPTIONS. Nothing to do here. auto& active_request = active_request_.value(); if (!is_connect && !active_request.request_url_.getStringView().empty() && (active_request.request_url_.getStringView()[0] == '/' || - ((method == HTTP_OPTIONS) && active_request.request_url_.getStringView()[0] == '*'))) { + ((method == static_cast(Method::Options)) && + active_request.request_url_.getStringView()[0] == '*'))) { headers.addViaMove(std::move(path), std::move(active_request.request_url_)); return; } @@ -807,6 +775,9 @@ void ServerConnectionImpl::handlePath(RequestHeaderMap& headers, unsigned int me } int ServerConnectionImpl::onHeadersComplete() { + onHeadersCompleteBase(); + + int rc = 0; // Handle the case where response happens prior to request complete. It's up to upper layer code // to disconnect the connection but we shouldn't fire any more events since it doesn't make // sense. @@ -814,7 +785,7 @@ int ServerConnectionImpl::onHeadersComplete() { auto& active_request = active_request_.value(); auto& headers = absl::get(headers_or_trailers_); ENVOY_CONN_LOG(trace, "Server: onHeadersComplete size={}", connection_, headers->size()); - const char* method_string = http_method_str(static_cast(parser_.method)); + const char* method_string = parser_->methodName(); if (!handling_upgrade_ && connection_header_sanitization_ && headers->Connection()) { // If we fail to sanitize the request, return a 400 to the client @@ -830,13 +801,15 @@ int ServerConnectionImpl::onHeadersComplete() { // Inform the response encoder about any HEAD method, so it can set content // length and transfer encoding headers correctly. - active_request.response_encoder_.setIsResponseToHeadRequest(parser_.method == HTTP_HEAD); - active_request.response_encoder_.setIsResponseToConnectRequest(parser_.method == HTTP_CONNECT); + active_request.response_encoder_.setIsResponseToHeadRequest(parser_->method() == + static_cast(Method::Head)); + active_request.response_encoder_.setIsResponseToConnectRequest(parser_->method() == + static_cast(Method::Connect)); - handlePath(*headers, parser_.method); + handlePath(*headers, parser_->method()); ASSERT(active_request.request_url_.empty()); - headers->setMethod(method_string); + current_header_map_->setMethod(method_string); // Make sure the host is valid. auto details = HeaderUtility::requestHeadersValid(*headers); @@ -852,24 +825,32 @@ int ServerConnectionImpl::onHeadersComplete() { // with message complete. This allows upper layers to behave like HTTP/2 and prevents a proxy // scenario where the higher layers stream through and implicitly switch to chunked transfer // encoding because end stream with zero body length has not yet been indicated. - if (parser_.flags & F_CHUNKED || - (parser_.content_length > 0 && parser_.content_length != ULLONG_MAX) || handling_upgrade_) { + if (parser_->flags() & static_cast(Flags::Chunked) || + (parser_->contentLength() > 0 && parser_->contentLength() != ULLONG_MAX) || + handling_upgrade_) { active_request.request_decoder_->decodeHeaders(std::move(headers), false); // If the connection has been closed (or is closing) after decoding headers, pause the parser // so we return control to the caller. if (connection_.state() != Network::Connection::State::Open) { - http_parser_pause(&parser_, 1); + rc = parser_->pause(); } } else { deferred_end_stream_headers_ = true; } } - return 0; + // TODO(dereka) share below with ClientConnectionImpl + current_header_map_.reset(); + header_parsing_state_ = HeaderParsingState::Done; + + // Returning 2 informs llhttp to not expect a body or further data on this connection. + return handling_upgrade_ ? 2 : rc; } -void ServerConnectionImpl::onMessageBegin() { +int ServerConnectionImpl::onMessageBegin() { + onMessageBeginBase(); + if (!resetStreamCalled()) { ASSERT(!active_request_.has_value()); active_request_.emplace(*this, header_key_formatter_.get()); @@ -881,24 +862,41 @@ void ServerConnectionImpl::onMessageBegin() { // possible to overflow output buffers with early parse errors. doFloodProtectionChecks(); } + + return 0; } -void ServerConnectionImpl::onUrl(const char* data, size_t length) { +int ServerConnectionImpl::onUrl(const char* data, size_t length) { if (active_request_.has_value()) { active_request_.value().request_url_.append(data, length); } + + return 0; +} + +int ServerConnectionImpl::onHeaderField(const char* data, size_t length) { + onHeaderFieldBase(data, length); + return 0; +} + +int ServerConnectionImpl::onHeaderValue(const char* data, size_t length) { + onHeaderValueBase(data, length); + return 0; } -void ServerConnectionImpl::onBody(Buffer::Instance& data) { +int ServerConnectionImpl::onBody(const char* data, size_t length) { ASSERT(!deferred_end_stream_headers_); if (active_request_.has_value()) { ENVOY_CONN_LOG(trace, "body size={}", connection_, data.length()); active_request_.value().request_decoder_->decodeData(data, false); } + + return 0; } -void ServerConnectionImpl::onMessageComplete() { +int ServerConnectionImpl::onMessageComplete() { ASSERT(!handling_upgrade_); + onMessageCompleteBase(); if (active_request_.has_value()) { auto& active_request = active_request_.value(); active_request.remote_complete_ = true; @@ -921,7 +919,7 @@ void ServerConnectionImpl::onMessageComplete() { // Always pause the parser so that the calling code can process 1 request at a time and apply // back pressure. However this means that the calling code needs to detect if there is more data // in the buffer and dispatch it again. - http_parser_pause(&parser_, 1); + return parser_->pause(); } void ServerConnectionImpl::onResetStream(StreamResetReason reason) { @@ -958,6 +956,10 @@ void ServerConnectionImpl::onBelowLowWatermark() { } } +void ServerConnectionImpl::processBody(const Buffer::RawSlice& slice) { + onBody(static_cast(slice.mem_), slice.len_); +} + void ServerConnectionImpl::releaseOutboundResponse( const Buffer::OwnedBufferFragmentImpl* fragment) { ASSERT(outbound_responses_ >= 1); @@ -989,15 +991,17 @@ void ServerConnectionImpl::checkHeaderNameForUnderscores() { ClientConnectionImpl::ClientConnectionImpl(Network::Connection& connection, Stats::Scope& stats, ConnectionCallbacks&, const Http1Settings& settings, const uint32_t max_response_headers_count) - : ConnectionImpl(connection, stats, HTTP_RESPONSE, MAX_RESPONSE_HEADERS_KB, - max_response_headers_count, formatter(settings), settings.enable_trailers_) {} + : ConnectionImpl(connection, stats, MessageType::Response, MAX_RESPONSE_HEADERS_KB, + max_response_headers_count, formatter(settings), settings.enable_trailers_) { + parser_ = ParserFactory::create(MessageType::Response, this); +} bool ClientConnectionImpl::cannotHaveBody() { if (pending_response_.has_value() && pending_response_.value().encoder_.headRequest()) { ASSERT(!pending_response_done_); return true; - } else if (parser_.status_code == 204 || parser_.status_code == 304 || - (parser_.status_code >= 200 && parser_.content_length == 0)) { + } else if(parser_->statusCode() == 204 || parser_->statusCode() == 304 || + (parser_->statusCode() >= 200 && (seen_content_length_ && parser_->contentLength() == 0))) { return true; } else { return false; @@ -1020,7 +1024,23 @@ RequestEncoder& ClientConnectionImpl::newStream(ResponseDecoder& response_decode return pending_response_.value().encoder_; } +int ClientConnectionImpl::onMessageBegin() { + onMessageBeginBase(); + return 0; +} + +int ClientConnectionImpl::onHeaderField(const char* data, size_t length) { + onHeaderFieldBase(data, length); + return 0; +} + +int ClientConnectionImpl::onHeaderValue(const char* data, size_t length) { + onHeaderValueBase(data, length); + return 0; +} + int ClientConnectionImpl::onHeadersComplete() { + onHeadersCompleteBase(); // Handle the case where the client is closing a kept alive connection (by sending a 408 // with a 'Connection: close' header). In this case we just let response flush out followed // by the remote close. @@ -1054,9 +1074,16 @@ int ClientConnectionImpl::onHeadersComplete() { } } - // Here we deal with cases where the response cannot have a body, but http_parser does not deal + // Here we deal with cases where the response cannot have a body, but llhttp does not deal // with it for us. - return cannotHaveBody() ? 1 : 0; + const int rc = cannotHaveBody() ? 1 : 0; + + // TODO(dereka) share below with ServerConnectionImpl + current_header_map_.reset(); + header_parsing_state_ = HeaderParsingState::Done; + + // Returning 2 informs llhttp to not expect a body or further data on this connection. + return handling_upgrade_ ? 2 : rc; } bool ClientConnectionImpl::upgradeAllowed() const { @@ -1066,19 +1093,23 @@ bool ClientConnectionImpl::upgradeAllowed() const { return false; } -void ClientConnectionImpl::onBody(Buffer::Instance& data) { +int ClientConnectionImpl::onBody(Buffer::Instance& data) { ASSERT(!deferred_end_stream_headers_); if (pending_response_.has_value()) { ASSERT(!pending_response_done_); pending_response_.value().decoder_->decodeData(data, false); } + + return 0; } -void ClientConnectionImpl::onMessageComplete() { +int ClientConnectionImpl::onMessageComplete() { + onMessageCompleteBase(); + ENVOY_CONN_LOG(trace, "message complete", connection_); if (ignore_message_complete_for_100_continue_) { ignore_message_complete_for_100_continue_ = false; - return; + return 0; } if (pending_response_.has_value()) { ASSERT(!pending_response_done_); @@ -1115,6 +1146,8 @@ void ClientConnectionImpl::onMessageComplete() { pending_response_.reset(); headers_or_trailers_.emplace(nullptr); } + + return 0; } void ClientConnectionImpl::onResetStream(StreamResetReason reason) { @@ -1146,6 +1179,10 @@ void ClientConnectionImpl::onBelowLowWatermark() { } } +void ClientConnectionImpl::processBody(const Buffer::RawSlice& slice) { + onBody(static_cast(slice.mem_), slice.len_); +} + } // namespace Http1 } // namespace Http } // namespace Envoy diff --git a/source/common/http/http1/codec_impl.h b/source/common/http/http1/codec_impl.h index d3fa827e029ae..40c2931c8dad4 100644 --- a/source/common/http/http1/codec_impl.h +++ b/source/common/http/http1/codec_impl.h @@ -1,7 +1,5 @@ #pragma once -#include - #include #include #include @@ -20,6 +18,8 @@ #include "common/http/codes.h" #include "common/http/header_map_impl.h" #include "common/http/http1/header_formatter.h" +#include "common/http/http1/parser.h" +#include "common/http/http1/parser_factory.h" #include "common/http/status.h" namespace Envoy { @@ -215,8 +215,47 @@ class ConnectionImpl : public virtual Connection, protected Logger::Loggable parser_; + HeaderMapPtr deferred_end_stream_headers_; Http::Code error_code_{Http::Code::BadRequest}; const HeaderKeyFormatterPtr header_key_formatter_; + HeaderMapImplPtr current_header_map_; HeaderString current_header_field_; HeaderString current_header_value_; bool processing_trailers_ : 1; bool handling_upgrade_ : 1; bool reset_stream_called_ : 1; + bool seen_content_length_ : 1; + Http::Code error_code_{Http::Code::BadRequest}; + bool processing_trailers_ : 1; // Deferred end stream headers indicate that we are not going to raise headers until the full // HTTP/1 message has been flushed from the parser. This allows raising an HTTP/2 style headers // block with end stream set to true with no further protocol data remaining. @@ -382,13 +426,18 @@ class ConnectionImpl : public virtual Connection, protected Logger::Loggable(headers_or_trailers_)) { return *absl::get(headers_or_trailers_); @@ -500,7 +565,9 @@ class ServerConnectionImpl : public ServerConnection, public ConnectionImpl { /** * Implementation of Http::ClientConnection for HTTP/1.1. */ -class ClientConnectionImpl : public ClientConnection, public ConnectionImpl { +class ClientConnectionImpl : public ClientConnection, + public ConnectionImpl, + public ParserCallbacks { public: ClientConnectionImpl(Network::Connection& connection, Stats::Scope& stats, ConnectionCallbacks& callbacks, const Http1Settings& settings, @@ -509,6 +576,18 @@ class ClientConnectionImpl : public ClientConnection, public ConnectionImpl { // Http::ClientConnection RequestEncoder& newStream(ResponseDecoder& response_decoder) override; + // ParserCallbacks + int onMessageBegin() override; + int onUrl(const char*, size_t) override { return 0; } + int onStatus() override { return 0; } + int onHeaderField(const char* data, size_t length) override; + int onHeaderValue(const char* data, size_t length) override; + int onHeadersComplete() override; + int onBody(const char* data, size_t length) override; + int onMessageComplete() override; + int onChunkHeader() override { return 0; } + int onChunkComplete() override { return 0; } + private: struct PendingResponse { PendingResponse(ConnectionImpl& connection, HeaderKeyFormatter* header_key_formatter, @@ -523,6 +602,7 @@ class ClientConnectionImpl : public ClientConnection, public ConnectionImpl { // ConnectionImpl void onEncodeComplete() override {} + void onEncodeHeaders(const HeaderMap& headers) override; void onMessageBegin() override {} void onUrl(const char*, size_t) override { NOT_IMPLEMENTED_GCOVR_EXCL_LINE; } int onHeadersComplete() override; @@ -533,6 +613,10 @@ class ClientConnectionImpl : public ClientConnection, public ConnectionImpl { void sendProtocolError(absl::string_view details) override; void onAboveHighWatermark() override; void onBelowLowWatermark() override; + void processBody(const Buffer::RawSlice& slice) override; + + std::unique_ptr request_encoder_; + std::list pending_responses_; HeaderMap& headersOrTrailers() override { if (absl::holds_alternative(headers_or_trailers_)) { return *absl::get(headers_or_trailers_); @@ -571,7 +655,7 @@ class ClientConnectionImpl : public ClientConnection, public ConnectionImpl { // purposes. absl::variant headers_or_trailers_; - // The default limit of 80 KiB is the vanilla http_parser behaviour. + // The default limit of 80 KiB is the vanilla llhttp behaviour. static constexpr uint32_t MAX_RESPONSE_HEADERS_KB = 80; }; diff --git a/source/common/http/http1/legacy_http_parser.cc b/source/common/http/http1/legacy_http_parser.cc new file mode 100644 index 0000000000000..7c87b4e4bb216 --- /dev/null +++ b/source/common/http/http1/legacy_http_parser.cc @@ -0,0 +1,131 @@ +#include "common/http/http1/legacy_http_parser.h" + +#include + +#include + +#include "common/common/assert.h" +#include "common/http/http1/parser.h" + +namespace Envoy { +namespace Http { +namespace Http1 { + +class LegacyHttpParserImpl::Impl { +public: + Impl(http_parser_type type) { http_parser_init(&parser_, type); } + + Impl(http_parser_type type, void* data) : Impl(type) { + parser_.data = data; + settings_ = { + [](http_parser* parser) -> int { + std::cout << "message begin callback" << std::endl; + return static_cast(parser->data)->onMessageBegin(); + }, + [](http_parser* parser, const char* at, size_t length) -> int { + return static_cast(parser->data)->onUrl(at, length); + }, + // TODO(dereka) onStatus + nullptr, + [](http_parser* parser, const char* at, size_t length) -> int { + return static_cast(parser->data)->onHeaderField(at, length); + }, + [](http_parser* parser, const char* at, size_t length) -> int { + return static_cast(parser->data)->onHeaderValue(at, length); + }, + [](http_parser* parser) -> int { + return static_cast(parser->data)->onHeadersComplete(); + }, + [](http_parser* parser, const char* at, size_t length) -> int { + return static_cast(parser->data)->onBody(at, length); + }, + [](http_parser* parser) -> int { + return static_cast(parser->data)->onMessageComplete(); + }, + nullptr, // TODO(dereka) onChunkHeader + nullptr // TODO(dereka) onChunkComplete + }; + } + + size_t execute(const char* slice, int len) { + return http_parser_execute(&parser_, &settings_, slice, len); + } + + void resume() { http_parser_pause(&parser_, 0); } + + int pause() { + http_parser_pause(&parser_, 1); + return HPE_PAUSED; + } + + int getErrno() { return HTTP_PARSER_ERRNO(&parser_); } + + int statusCode() const { return parser_.status_code; } + + int httpMajor() const { return parser_.http_major; } + + int httpMinor() const { return parser_.http_minor; } + + uint64_t contentLength() const { return parser_.content_length; } + + int flags() const { return parser_.flags; } + + uint16_t method() const { return parser_.method; } + + const char* methodName() const { + return http_method_str(static_cast(parser_.method)); + } + +private: + http_parser parser_; + http_parser_settings settings_; +}; + +LegacyHttpParserImpl::LegacyHttpParserImpl(MessageType type, void* data) { + http_parser_type parser_type; + switch (type) { + case MessageType::Request: + parser_type = HTTP_REQUEST; + break; + case MessageType::Response: + parser_type = HTTP_RESPONSE; + default: + NOT_IMPLEMENTED_GCOVR_EXCL_LINE; + } + + impl_ = std::make_unique(parser_type, data); +} + +// Because we have a pointer-to-impl using std::unique_ptr, we must place the destructor in the +// same compilation unit so that the destructor has a complete definition of Impl. +LegacyHttpParserImpl::~LegacyHttpParserImpl() = default; + +int LegacyHttpParserImpl::execute(const char* slice, int len) { return impl_->execute(slice, len); } + +void LegacyHttpParserImpl::resume() { impl_->resume(); } + +int LegacyHttpParserImpl::pause() { return impl_->pause(); } + +int LegacyHttpParserImpl::getErrno() { return impl_->getErrno(); } + +int LegacyHttpParserImpl::statusCode() const { return impl_->statusCode(); } + +int LegacyHttpParserImpl::httpMajor() const { return impl_->httpMajor(); } + +int LegacyHttpParserImpl::httpMinor() const { return impl_->httpMinor(); } + +uint64_t LegacyHttpParserImpl::contentLength() const { return impl_->contentLength(); } + +int LegacyHttpParserImpl::flags() const { return impl_->flags(); } + +uint16_t LegacyHttpParserImpl::method() const { return impl_->method(); } + +const char* LegacyHttpParserImpl::methodName() const { return impl_->methodName(); } + +const char* LegacyHttpParserImpl::errnoName() { + return http_errno_name(static_cast(impl_->getErrno())); +} + +} // namespace Http1 +} // namespace Http +} // namespace Envoy diff --git a/source/common/http/http1/legacy_http_parser.h b/source/common/http/http1/legacy_http_parser.h new file mode 100644 index 0000000000000..4742c507bba68 --- /dev/null +++ b/source/common/http/http1/legacy_http_parser.h @@ -0,0 +1,36 @@ +#pragma once + +#include + +#include "common/http/http1/parser.h" + +namespace Envoy { +namespace Http { +namespace Http1 { + +class LegacyHttpParserImpl : public Parser { +public: + LegacyHttpParserImpl(MessageType type, void* data); + ~LegacyHttpParserImpl(); + int execute(const char* data, int len) override; + void resume() override; + int pause() override; + int getErrno() override; + int statusCode() const override; + int httpMajor() const override; + int httpMinor() const override; + uint64_t contentLength() const override; + int flags() const override; + uint16_t method() const override; + const char* methodName() const override; + const char* errnoName() override; + bool usesOldImpl() const override { return true; } + +private: + class Impl; + std::unique_ptr impl_; +}; + +} // namespace Http1 +} // namespace Http +} // namespace Envoy diff --git a/source/common/http/http1/llhttp_parser.cc b/source/common/http/http1/llhttp_parser.cc new file mode 100644 index 0000000000000..1df6ca673366e --- /dev/null +++ b/source/common/http/http1/llhttp_parser.cc @@ -0,0 +1,114 @@ +#include "common/http/http1/llhttp_parser.h" + +#include + +#include "common/common/assert.h" +#include "common/http/http1/parser.h" + +namespace Envoy { +namespace Http { +namespace Http1 { + +class LlHttpParserImpl::Impl { +public: + Impl(llhttp_type_t type, void* data) { + llhttp_init(&parser_, type, &settings_); + parser_.data = data; + } + + size_t execute(const char* slice, int len) { + llhttp_errno_t err; + if (slice == nullptr || len == 0) { + err = llhttp_finish(&parser_); + } else { + err = llhttp_execute(&parser_, slice, len); + } + + size_t nread = len; + if (err != HPE_OK) { + nread = llhttp_get_error_pos(&parser_) - slice; + if (err == HPE_PAUSED_UPGRADE) { + err = HPE_OK; + llhttp_resume_after_upgrade(&parser_); + } + } + + return nread; + } + + void resume() { llhttp_resume(&parser_); } + + int getErrno() { return llhttp_get_errno(&parser_); } + + int statusCode() const { return parser_.status_code; } + + int httpMajor() const { return parser_.http_major; } + + int httpMinor() const { return parser_.http_minor; } + + uint64_t contentLength() const { return parser_.content_length; } + + int flags() const { return parser_.flags; } + + uint16_t method() const { return parser_.method; } + + const char* methodName() const { + return llhttp_method_name(static_cast(parser_.method)); + } + +private: + llhttp_t parser_; + llhttp_settings_s settings_; +}; + +LlHttpParserImpl::LlHttpParserImpl(MessageType type, void* data) { + llhttp_type_t llhttp_type; + switch (type) { + case MessageType::Request: + llhttp_type = HTTP_REQUEST; + break; + case MessageType::Response: + llhttp_type = HTTP_RESPONSE; + break; + default: + // We strictly use the parser for either request or response, not both. + NOT_IMPLEMENTED_GCOVR_EXCL_LINE; + } + + impl_ = std::make_unique(llhttp_type, data); +} + +LlHttpParserImpl::~LlHttpParserImpl() = default; + +int LlHttpParserImpl::execute(const char* slice, int len) { return impl_->execute(slice, len); } + +void LlHttpParserImpl::resume() { impl_->resume(); } + +int LlHttpParserImpl::pause() { + // TODO(dereka) do we actually need to call llhttp_pause(&parser_); ? + return HPE_PAUSED; +} + +int LlHttpParserImpl::getErrno() { return impl_->getErrno(); } + +int LlHttpParserImpl::statusCode() const { return impl_->statusCode(); } + +int LlHttpParserImpl::httpMajor() const { return impl_->httpMajor(); } + +int LlHttpParserImpl::httpMinor() const { return impl_->httpMinor(); } + +uint64_t LlHttpParserImpl::contentLength() const { return impl_->contentLength(); } + +int LlHttpParserImpl::flags() const { return impl_->flags(); } + +uint16_t LlHttpParserImpl::method() const { return impl_->method(); } + +const char* LlHttpParserImpl::methodName() const { return impl_->methodName(); } + +const char* LlHttpParserImpl::errnoName() { + return llhttp_errno_name(static_cast(impl_->getErrno())); +} + +} // namespace Http1 +} // namespace Http +} // namespace Envoy diff --git a/source/common/http/http1/llhttp_parser.h b/source/common/http/http1/llhttp_parser.h new file mode 100644 index 0000000000000..e49a0b3e92e5c --- /dev/null +++ b/source/common/http/http1/llhttp_parser.h @@ -0,0 +1,37 @@ +#pragma once + +#include + +#include "common/http/http1/parser.h" + +namespace Envoy { +namespace Http { +namespace Http1 { + +class LlHttpParserImpl : public Parser { +public: + LlHttpParserImpl(MessageType type, void* data); + ~LlHttpParserImpl(); + int execute(const char* data, int len) override; + void resume() override; + int pause() override; + int getErrno() override; + int statusCode() const override; + int httpMajor() const override; + int httpMinor() const override; + uint64_t contentLength() const override; + int flags() const override; + uint16_t method() const override; + const char* methodName() const override; + const char* errnoName() override; + bool usesOldImpl() const override { return false; } + +private: + // PImpl pattern is used to contain the llhttp library to avoid global namespace collisions. + class Impl; + std::unique_ptr impl_; +}; + +} // namespace Http1 +} // namespace Http +} // namespace Envoy diff --git a/source/common/http/http1/parser.h b/source/common/http/http1/parser.h new file mode 100644 index 0000000000000..d3c1a6c4fcc71 --- /dev/null +++ b/source/common/http/http1/parser.h @@ -0,0 +1,110 @@ +#pragma once + +#include + +#include "envoy/common/pure.h" + +namespace Envoy { +namespace Http { +namespace Http1 { + +enum class ParserType { Legacy, LlHttp }; + +enum class MessageType { Request, Response }; + +/** + * Callbacks base class. Any users of the HTTP1 parser who want to use the parsed data needs to + * implement this interface and pass the object to the `data` parameter of ParserFactory::create. + */ +class ParserCallbacks { +public: + virtual ~ParserCallbacks() = default; + /** + * Called when a request/response is beginning. + */ + virtual int onMessageBegin() PURE; + + /** + * Called when URL data is received. + * @param data supplies the start address. + * @param length supplies the length. + */ + virtual int onUrl(const char* data, size_t length) PURE; + virtual int onStatus() PURE; + + /** + * Called when header field data is received. + * @param data supplies the start address. + * @param length supplies the length. + */ + virtual int onHeaderField(const char* data, size_t length) PURE; + + /** + * Called when header value data is received. + * @param data supplies the start address. + * @param length supplies the length. + */ + virtual int onHeaderValue(const char* data, size_t length) PURE; + + /** + * Called when headers are complete. + * @return 0 if no error, 1 if there should be no body. + */ + virtual int onHeadersComplete() PURE; + + /** + * Called when body data is received. + * @param data supplies the start address. + * @param length supplies the length. + */ + virtual int onBody(const char* data, size_t length) PURE; + + /** + * Called when the request/response is complete. + */ + virtual int onMessageComplete() PURE; + virtual int onChunkHeader() PURE; // shrug + virtual int onChunkComplete() PURE; // shrug +}; + +/** + * Parser interface. + */ +class Parser { +public: + virtual ~Parser() = default; + virtual int execute(const char* slice, int len) PURE; + virtual void resume() PURE; + virtual int pause() PURE; + virtual int getErrno() PURE; + virtual int statusCode() const PURE; + virtual int httpMajor() const PURE; + virtual int httpMinor() const PURE; + virtual uint64_t contentLength() const PURE; + virtual int flags() const PURE; + virtual uint16_t method() const PURE; + virtual const char* methodName() const PURE; + virtual const char* errnoName() PURE; + virtual bool usesOldImpl() const PURE; +}; + +enum class Flags { + Chunked = 1, +}; + +enum class ParserStatus { + Ok = 0, + Paused = 31, +}; + +enum class Method { + Head = 2, + Connect = 5, + Options = 6, +}; + +using ParserPtr = std::unique_ptr; + +} // namespace Http1 +} // namespace Http +} // namespace Envoy diff --git a/source/common/http/http1/parser_factory.cc b/source/common/http/http1/parser_factory.cc new file mode 100644 index 0000000000000..4a5c95af8a5fa --- /dev/null +++ b/source/common/http/http1/parser_factory.cc @@ -0,0 +1,28 @@ +#include "common/http/http1/parser_factory.h" + +#include + +#include "common/http/http1/legacy_http_parser.h" +#include "common/http/http1/llhttp_parser.h" + +namespace Envoy { +namespace Http { +namespace Http1 { + +bool ParserFactory::use_legacy_parser_ = false; + +ParserPtr ParserFactory::create(MessageType type, void* data) { + if (usesLegacyParser()) { + return std::make_unique(type, data); + } + + return std::make_unique(type, data); +} + +bool ParserFactory::usesLegacyParser() { return use_legacy_parser_; } + +void ParserFactory::useLegacy(bool use_legacy_parser) { use_legacy_parser_ = use_legacy_parser; } + +} // namespace Http1 +} // namespace Http +} // namespace Envoy diff --git a/source/common/http/http1/parser_factory.h b/source/common/http/http1/parser_factory.h new file mode 100644 index 0000000000000..d8a6e1e189e02 --- /dev/null +++ b/source/common/http/http1/parser_factory.h @@ -0,0 +1,36 @@ +#pragma once + +#include "common/http/http1/parser.h" + +namespace Envoy { +namespace Http { +namespace Http1 { + +/** + * A temporary factory class to allow switching between constructing a parser using the legacy + * http-parser library or llhttp. + */ +class ParserFactory { +public: + /** + * Creates a new parser implementation. + */ + static ParserPtr create(MessageType type, void* data); + + /** + * @return whether the factory is configured to return the legacy HTTP parser. + */ + static bool usesLegacyParser(); + + /** + * Sets whether to construct the legacy HTTP parser or newer llhttp parser. + */ + static void useLegacy(bool use_legacy_parser); + +private: + static bool use_legacy_parser_; +}; + +} // namespace Http1 +} // namespace Http +} // namespace Envoy diff --git a/source/server/BUILD b/source/server/BUILD index 3611191f990a1..dc27ec9b585c1 100644 --- a/source/server/BUILD +++ b/source/server/BUILD @@ -469,6 +469,7 @@ envoy_cc_library( "//source/common/grpc:context_lib", "//source/common/http:codes_lib", "//source/common/http:context_lib", + "//source/common/http/http1:parser_factory_lib", "//source/common/init:manager_lib", "//source/common/local_info:local_info_lib", "//source/common/memory:heap_shrinker_lib", diff --git a/source/server/options_impl.cc b/source/server/options_impl.cc index f3b7d67db6a61..54ea276874c7b 100644 --- a/source/server/options_impl.cc +++ b/source/server/options_impl.cc @@ -145,6 +145,10 @@ OptionsImpl::OptionsImpl(std::vector args, "Use fake symbol table implementation", false, true, "bool", cmd); + TCLAP::ValueArg use_legacy_http_parser("", "use-legacy-http-parser", + "Use the legacy Node HTTP parser implementation", + false, true, "bool", cmd); + TCLAP::ValueArg disable_extensions("", "disable-extensions", "Comma-separated list of extensions to disable", false, "", "string", cmd); @@ -170,6 +174,7 @@ OptionsImpl::OptionsImpl(std::vector args, hot_restart_disabled_ = disable_hot_restart.getValue(); mutex_tracing_enabled_ = enable_mutex_tracing.getValue(); fake_symbol_table_enabled_ = use_fake_symbol_table.getValue(); + legacy_http_parser_enabled_ = use_legacy_http_parser.getValue(); cpuset_threads_ = cpuset_threads.getValue(); if (log_level.isSet()) { @@ -368,7 +373,7 @@ OptionsImpl::OptionsImpl(const std::string& service_cluster, const std::string& service_zone_(service_zone), file_flush_interval_msec_(10000), drain_time_(600), parent_shutdown_time_(900), mode_(Server::Mode::Serve), hot_restart_disabled_(false), signal_handling_enabled_(true), mutex_tracing_enabled_(false), cpuset_threads_(false), - fake_symbol_table_enabled_(false) {} + fake_symbol_table_enabled_(false), legacy_http_parser_enabled_(false) {} void OptionsImpl::disableExtensions(const std::vector& names) { for (const auto& name : names) { diff --git a/source/server/options_impl.h b/source/server/options_impl.h index fb6bd08dfdb9e..49191baf59f83 100644 --- a/source/server/options_impl.h +++ b/source/server/options_impl.h @@ -133,6 +133,7 @@ class OptionsImpl : public Server::Options, protected Logger::Loggable disabled_extensions_; uint32_t count_; }; diff --git a/source/server/server.cc b/source/server/server.cc index 02de2aa305d2d..892e0985a29d4 100644 --- a/source/server/server.cc +++ b/source/server/server.cc @@ -30,6 +30,7 @@ #include "common/config/utility.h" #include "common/config/version_converter.h" #include "common/http/codes.h" +#include "common/http/http1/parser_factory.h" #include "common/local_info/local_info_impl.h" #include "common/memory/stats.h" #include "common/network/address_impl.h" @@ -296,6 +297,10 @@ void InstanceImpl::initialize(const Options& options, ENVOY_LOG(info, " {}: {}", ext.first, absl::StrJoin(ext.second->registeredNames(), ", ")); } + Http::Http1::ParserFactory::useLegacy(options.legacyHttpParserEnabled()); + ENVOY_LOG(info, "http implementation: {}", + Http::Http1::ParserFactory::usesLegacyParser() ? "old (http-parser" : "new (llhttp)"); + // Handle configuration that needs to take place prior to the main configuration load. InstanceUtil::loadBootstrapConfig(bootstrap_, options, messageValidationContext().staticValidationVisitor(), *api_); diff --git a/test/common/http/http1/codec_fuzz_test.cc b/test/common/http/http1/codec_fuzz_test.cc new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/test/common/http/http1/codec_impl_test.cc b/test/common/http/http1/codec_impl_test.cc index b07b812df6502..744158bdf0402 100644 --- a/test/common/http/http1/codec_impl_test.cc +++ b/test/common/http/http1/codec_impl_test.cc @@ -55,8 +55,32 @@ Buffer::OwnedImpl createBufferWithNByteSlices(absl::string_view input, size_t ma } } // namespace -class Http1ServerConnectionImplTest : public testing::Test { +enum class CodecImplementation { + Old, // original node http-parser + New // llhttp +}; + +class Http1CodecParamTest : public testing::TestWithParam { +protected: + Http1CodecParamTest() { ParserFactory::useLegacy(GetParam() == CodecImplementation::Old); } + ~Http1CodecParamTest() override = default; + + /** Verify that a parser has been constructed using the expected implementation. */ + void verifyImplementation(const Parser& parser) { + switch (GetParam()) { + case CodecImplementation::Old: + ASSERT_TRUE(parser.usesOldImpl()); + break; + case CodecImplementation::New: + ASSERT_FALSE(parser.usesOldImpl()); + break; + } + } +}; + +class Http1ServerConnectionImplTest : public Http1CodecParamTest { public: + ~Http1ServerConnectionImplTest() override = default; void initialize() { codec_ = std::make_unique( connection_, store_, callbacks_, codec_settings_, max_request_headers_kb_, @@ -285,7 +309,10 @@ void Http1ServerConnectionImplTest::testRequestHeadersAccepted(std::string heade EXPECT_TRUE(status.ok()); } -TEST_F(Http1ServerConnectionImplTest, EmptyHeader) { +INSTANTIATE_TEST_SUITE_P(Http1ServerConnectionImplTest, Http1ServerConnectionImplTest, + testing::ValuesIn({CodecImplementation::Old, CodecImplementation::New})); + +TEST_P(Http1ServerConnectionImplTest, EmptyHeader) { initialize(); InSequence sequence; @@ -436,7 +463,7 @@ TEST_F(Http1ServerConnectionImplTest, ChunkedBodyFragmentedBuffer) { EXPECT_EQ(0U, buffer.length()); } -TEST_F(Http1ServerConnectionImplTest, ChunkedBodyCase) { +TEST_P(Http1ServerConnectionImplTest, ChunkedBodyCase) { initialize(); InSequence sequence; @@ -504,7 +531,7 @@ TEST_F(Http1ServerConnectionImplTest, IdentityAndChunkedBody) { EXPECT_EQ(status.message(), "http/1.1 protocol error: unsupported transfer encoding"); } -TEST_F(Http1ServerConnectionImplTest, HostWithLWS) { +TEST_P(Http1ServerConnectionImplTest, HostWithLWS) { initialize(); TestHeaderMapImpl expected_headers{{":authority", "host"}, {":path", "/"}, {":method", "GET"}}; @@ -575,7 +602,7 @@ TEST_F(Http1ServerConnectionImplTest, Http10) { EXPECT_EQ(Protocol::Http10, codec_->protocol()); } -TEST_F(Http1ServerConnectionImplTest, Http10AbsoluteNoOp) { +TEST_P(Http1ServerConnectionImplTest, Http10AbsoluteNoOp) { initialize(); TestHeaderMapImpl expected_headers{{":path", "/"}, {":method", "GET"}}; @@ -583,7 +610,7 @@ TEST_F(Http1ServerConnectionImplTest, Http10AbsoluteNoOp) { expectHeadersTest(Protocol::Http10, true, buffer, expected_headers); } -TEST_F(Http1ServerConnectionImplTest, Http10Absolute) { +TEST_P(Http1ServerConnectionImplTest, Http10Absolute) { initialize(); TestHeaderMapImpl expected_headers{ @@ -592,7 +619,7 @@ TEST_F(Http1ServerConnectionImplTest, Http10Absolute) { expectHeadersTest(Protocol::Http10, true, buffer, expected_headers); } -TEST_F(Http1ServerConnectionImplTest, Http10MultipleResponses) { +TEST_P(Http1ServerConnectionImplTest, Http10MultipleResponses) { initialize(); MockRequestDecoder decoder; @@ -638,7 +665,7 @@ TEST_F(Http1ServerConnectionImplTest, Http10MultipleResponses) { } } -TEST_F(Http1ServerConnectionImplTest, Http11AbsolutePath1) { +TEST_P(Http1ServerConnectionImplTest, Http11AbsolutePath1) { initialize(); TestHeaderMapImpl expected_headers{ @@ -647,7 +674,7 @@ TEST_F(Http1ServerConnectionImplTest, Http11AbsolutePath1) { expectHeadersTest(Protocol::Http11, true, buffer, expected_headers); } -TEST_F(Http1ServerConnectionImplTest, Http11AbsolutePath2) { +TEST_P(Http1ServerConnectionImplTest, Http11AbsolutePath2) { initialize(); TestHeaderMapImpl expected_headers{ @@ -656,7 +683,7 @@ TEST_F(Http1ServerConnectionImplTest, Http11AbsolutePath2) { expectHeadersTest(Protocol::Http11, true, buffer, expected_headers); } -TEST_F(Http1ServerConnectionImplTest, Http11AbsolutePathWithPort) { +TEST_P(Http1ServerConnectionImplTest, Http11AbsolutePathWithPort) { initialize(); TestHeaderMapImpl expected_headers{ @@ -666,7 +693,7 @@ TEST_F(Http1ServerConnectionImplTest, Http11AbsolutePathWithPort) { expectHeadersTest(Protocol::Http11, true, buffer, expected_headers); } -TEST_F(Http1ServerConnectionImplTest, Http11AbsoluteEnabledNoOp) { +TEST_P(Http1ServerConnectionImplTest, Http11AbsoluteEnabledNoOp) { initialize(); TestHeaderMapImpl expected_headers{ @@ -675,7 +702,7 @@ TEST_F(Http1ServerConnectionImplTest, Http11AbsoluteEnabledNoOp) { expectHeadersTest(Protocol::Http11, true, buffer, expected_headers); } -TEST_F(Http1ServerConnectionImplTest, Http11InvalidRequest) { +TEST_P(Http1ServerConnectionImplTest, Http11InvalidRequest) { initialize(); // Invalid because www.somewhere.com is not an absolute path nor an absolute url @@ -711,7 +738,7 @@ TEST_F(Http1ServerConnectionImplTest, Http11InvalidTrailerPost) { EXPECT_EQ("HTTP/1.1 400 Bad Request\r\ncontent-length: 0\r\nconnection: close\r\n\r\n", output); } -TEST_F(Http1ServerConnectionImplTest, Http11AbsolutePathNoSlash) { +TEST_P(Http1ServerConnectionImplTest, Http11AbsolutePathNoSlash) { initialize(); TestHeaderMapImpl expected_headers{ @@ -720,21 +747,21 @@ TEST_F(Http1ServerConnectionImplTest, Http11AbsolutePathNoSlash) { expectHeadersTest(Protocol::Http11, true, buffer, expected_headers); } -TEST_F(Http1ServerConnectionImplTest, Http11AbsolutePathBad) { +TEST_P(Http1ServerConnectionImplTest, Http11AbsolutePathBad) { initialize(); Buffer::OwnedImpl buffer("GET * HTTP/1.1\r\nHost: bah\r\n\r\n"); expect400(Protocol::Http11, true, buffer, "http1.invalid_url"); } -TEST_F(Http1ServerConnectionImplTest, Http11AbsolutePortTooLarge) { +TEST_P(Http1ServerConnectionImplTest, Http11AbsolutePortTooLarge) { initialize(); Buffer::OwnedImpl buffer("GET http://foobar.com:1000000 HTTP/1.1\r\nHost: bah\r\n\r\n"); expect400(Protocol::Http11, true, buffer); } -TEST_F(Http1ServerConnectionImplTest, SketchyConnectionHeader) { +TEST_P(Http1ServerConnectionImplTest, SketchyConnectionHeader) { initialize(); Buffer::OwnedImpl buffer( @@ -742,7 +769,7 @@ TEST_F(Http1ServerConnectionImplTest, SketchyConnectionHeader) { expect400(Protocol::Http11, true, buffer, "http1.connection_header_rejected"); } -TEST_F(Http1ServerConnectionImplTest, Http11RelativeOnly) { +TEST_P(Http1ServerConnectionImplTest, Http11RelativeOnly) { initialize(); TestHeaderMapImpl expected_headers{ @@ -751,7 +778,7 @@ TEST_F(Http1ServerConnectionImplTest, Http11RelativeOnly) { expectHeadersTest(Protocol::Http11, false, buffer, expected_headers); } -TEST_F(Http1ServerConnectionImplTest, Http11Options) { +TEST_P(Http1ServerConnectionImplTest, Http11Options) { initialize(); TestHeaderMapImpl expected_headers{ @@ -760,7 +787,7 @@ TEST_F(Http1ServerConnectionImplTest, Http11Options) { expectHeadersTest(Protocol::Http11, true, buffer, expected_headers); } -TEST_F(Http1ServerConnectionImplTest, SimpleGet) { +TEST_P(Http1ServerConnectionImplTest, SimpleGet) { initialize(); InSequence sequence; @@ -777,13 +804,19 @@ TEST_F(Http1ServerConnectionImplTest, SimpleGet) { EXPECT_EQ(0U, buffer.length()); } -TEST_F(Http1ServerConnectionImplTest, BadRequestNoStream) { +TEST_P(Http1ServerConnectionImplTest, BadRequestNoStream) { initialize(); std::string output; ON_CALL(connection_, write(_, _)).WillByDefault(AddBufferToString(&output)); Buffer::OwnedImpl buffer("bad"); +#ifndef ENVOY_ENABLE_LEGACY_HTTP_PARSER + // TODO(dereka) fixme + Http::MockStreamDecoder decoder; + EXPECT_CALL(callbacks_, newStream(_, _)).WillOnce(ReturnRef(decoder)); +#endif + auto status = codec_->dispatch(buffer); EXPECT_TRUE(isCodecProtocolError(status)); EXPECT_EQ("HTTP/1.1 400 Bad Request\r\ncontent-length: 0\r\nconnection: close\r\n\r\n", output); @@ -791,7 +824,7 @@ TEST_F(Http1ServerConnectionImplTest, BadRequestNoStream) { // This behavior was observed during CVE-2019-18801 and helped to limit the // scope of affected Envoy configurations. -TEST_F(Http1ServerConnectionImplTest, RejectInvalidMethod) { +TEST_P(Http1ServerConnectionImplTest, RejectInvalidMethod) { initialize(); MockRequestDecoder decoder; @@ -806,7 +839,7 @@ TEST_F(Http1ServerConnectionImplTest, RejectInvalidMethod) { EXPECT_EQ("HTTP/1.1 400 Bad Request\r\ncontent-length: 0\r\nconnection: close\r\n\r\n", output); } -TEST_F(Http1ServerConnectionImplTest, BadRequestStartedStream) { +TEST_P(Http1ServerConnectionImplTest, BadRequestStartedStream) { initialize(); std::string output; @@ -825,7 +858,7 @@ TEST_F(Http1ServerConnectionImplTest, BadRequestStartedStream) { EXPECT_EQ("HTTP/1.1 400 Bad Request\r\ncontent-length: 0\r\nconnection: close\r\n\r\n", output); } -TEST_F(Http1ServerConnectionImplTest, FloodProtection) { +TEST_P(Http1ServerConnectionImplTest, FloodProtection) { initialize(); NiceMock decoder; @@ -876,7 +909,7 @@ TEST_F(Http1ServerConnectionImplTest, FloodProtection) { } } -TEST_F(Http1ServerConnectionImplTest, FloodProtectionOff) { +TEST_P(Http1ServerConnectionImplTest, FloodProtectionOff) { TestScopedRuntime scoped_runtime; Runtime::LoaderSingleton::getExisting()->mergeValues( {{"envoy.reloadable_features.http1_flood_protection", "false"}}); @@ -912,7 +945,7 @@ TEST_F(Http1ServerConnectionImplTest, FloodProtectionOff) { } } -TEST_F(Http1ServerConnectionImplTest, HostHeaderTranslation) { +TEST_P(Http1ServerConnectionImplTest, HostHeaderTranslation) { initialize(); InSequence sequence; @@ -929,9 +962,10 @@ TEST_F(Http1ServerConnectionImplTest, HostHeaderTranslation) { EXPECT_EQ(0U, buffer.length()); } +#ifdef ENVOY_ENABLE_LEGACY_HTTP_PARSER // Ensures that requests with invalid HTTP header values are not rejected // when the runtime guard is not enabled for the feature. -TEST_F(Http1ServerConnectionImplTest, HeaderInvalidCharsRuntimeGuard) { +TEST_P(Http1ServerConnectionImplTest, HeaderInvalidCharsRuntimeGuard) { TestScopedRuntime scoped_runtime; // When the runtime-guarded feature is NOT enabled, invalid header values // should be accepted by the codec. @@ -948,10 +982,11 @@ TEST_F(Http1ServerConnectionImplTest, HeaderInvalidCharsRuntimeGuard) { auto status = codec_->dispatch(buffer); EXPECT_TRUE(status.ok()); } +#endif // Ensures that requests with invalid HTTP header values are properly rejected // when the runtime guard is enabled for the feature. -TEST_F(Http1ServerConnectionImplTest, HeaderInvalidCharsRejection) { +TEST_P(Http1ServerConnectionImplTest, HeaderInvalidCharsRejection) { TestScopedRuntime scoped_runtime; // When the runtime-guarded feature is enabled, invalid header values // should result in a rejection. @@ -969,9 +1004,17 @@ TEST_F(Http1ServerConnectionImplTest, HeaderInvalidCharsRejection) { })); Buffer::OwnedImpl buffer( absl::StrCat("GET / HTTP/1.1\r\nHOST: h.com\r\nfoo: ", std::string(1, 3), "\r\n")); + auto status = codec_->dispatch(buffer); EXPECT_TRUE(isCodecProtocolError(status)); EXPECT_EQ(status.message(), "http/1.1 protocol error: header value contains invalid chars"); +#ifndef ENVOY_ENABLE_LEGACY_HTTP_PARSER + EXPECT_EQ(status.message(), + "http/1.1 protocol error: HPE_INVALID_HEADER_TOKEN"); +#else + EXPECT_EQ(status.message(), + "http/1.1 protocol error: header value contains invalid chars"); +#endif EXPECT_EQ("http1.invalid_characters", response_encoder->getStream().responseDetails()); } @@ -1040,11 +1083,12 @@ TEST_F(Http1ServerConnectionImplTest, HeaderNameWithUnderscoreCauseRequestReject auto status = codec_->dispatch(buffer); EXPECT_TRUE(isCodecProtocolError(status)); EXPECT_EQ(status.message(), "http/1.1 protocol error: header name contains underscores"); +>>>>>>> master EXPECT_EQ("http1.invalid_characters", response_encoder->getStream().responseDetails()); EXPECT_EQ(1, store_.counter("http1.requests_rejected_with_underscores_in_headers").value()); } -TEST_F(Http1ServerConnectionImplTest, HeaderInvalidAuthority) { +TEST_P(Http1ServerConnectionImplTest, HeaderInvalidAuthority) { TestScopedRuntime scoped_runtime; initialize(); @@ -1066,7 +1110,7 @@ TEST_F(Http1ServerConnectionImplTest, HeaderInvalidAuthority) { // Regression test for http-parser allowing embedded NULs in header values, // verify we reject them. -TEST_F(Http1ServerConnectionImplTest, HeaderEmbeddedNulRejection) { +TEST_P(Http1ServerConnectionImplTest, HeaderEmbeddedNulRejection) { TestScopedRuntime scoped_runtime; Runtime::LoaderSingleton::getExisting()->mergeValues( {{"envoy.reloadable_features.strict_header_validation", "false"}}); @@ -1081,12 +1125,18 @@ TEST_F(Http1ServerConnectionImplTest, HeaderEmbeddedNulRejection) { absl::StrCat("GET / HTTP/1.1\r\nHOST: h.com\r\nfoo: bar", std::string(1, '\0'), "baz\r\n")); auto status = codec_->dispatch(buffer); EXPECT_TRUE(isCodecProtocolError(status)); - EXPECT_EQ(status.message(), "http/1.1 protocol error: HPE_INVALID_HEADER_TOKEN"); +#ifndef ENVOY_ENABLE_LEGACY_HTTP_PARSER + EXPECT_EQ(status.message(), + "http/1.1 protocol error: HPE_INVALID_HEADER_TOKEN"); +#else + EXPECT_EQ(status.message(), + "http/1.1 protocol error: header value contains NUL"); +#endif } // Mutate an HTTP GET with embedded NULs, this should always be rejected in some // way (not necessarily with "head value contains NUL" though). -TEST_F(Http1ServerConnectionImplTest, HeaderMutateEmbeddedNul) { +TEST_P(Http1ServerConnectionImplTest, HeaderMutateEmbeddedNul) { const std::string example_input = "GET / HTTP/1.1\r\nHOST: h.com\r\nfoo: barbaz\r\n"; for (size_t n = 1; n < example_input.size(); ++n) { @@ -1109,7 +1159,7 @@ TEST_F(Http1ServerConnectionImplTest, HeaderMutateEmbeddedNul) { // Mutate an HTTP GET with CR or LF. These can cause an error status or maybe // result in a valid decodeHeaders(). In any case, the validHeaderString() // ASSERTs should validate we never have any embedded CR or LF. -TEST_F(Http1ServerConnectionImplTest, HeaderMutateEmbeddedCRLF) { +TEST_P(Http1ServerConnectionImplTest, HeaderMutateEmbeddedCRLF) { const std::string example_input = "GET / HTTP/1.1\r\nHOST: h.com\r\nfoo: barbaz\r\n"; for (const char c : {'\r', '\n'}) { @@ -1129,7 +1179,7 @@ TEST_F(Http1ServerConnectionImplTest, HeaderMutateEmbeddedCRLF) { } } -TEST_F(Http1ServerConnectionImplTest, CloseDuringHeadersComplete) { +TEST_P(Http1ServerConnectionImplTest, CloseDuringHeadersComplete) { initialize(); InSequence sequence; @@ -1151,7 +1201,7 @@ TEST_F(Http1ServerConnectionImplTest, CloseDuringHeadersComplete) { EXPECT_NE(0U, buffer.length()); } -TEST_F(Http1ServerConnectionImplTest, PostWithContentLength) { +TEST_P(Http1ServerConnectionImplTest, PostWithContentLength) { initialize(); InSequence sequence; @@ -1200,7 +1250,7 @@ TEST_F(Http1ServerConnectionImplTest, PostWithContentLengthFragmentedBuffer) { EXPECT_EQ(0U, buffer.length()); } -TEST_F(Http1ServerConnectionImplTest, HeaderOnlyResponse) { +TEST_P(Http1ServerConnectionImplTest, HeaderOnlyResponse) { initialize(); NiceMock decoder; @@ -1226,7 +1276,7 @@ TEST_F(Http1ServerConnectionImplTest, HeaderOnlyResponse) { // As with Http1ClientConnectionImplTest.LargeHeaderRequestEncode but validate // the response encoder instead of request encoder. -TEST_F(Http1ServerConnectionImplTest, LargeHeaderResponseEncode) { +TEST_P(Http1ServerConnectionImplTest, LargeHeaderResponseEncode) { initialize(); NiceMock decoder; @@ -1252,7 +1302,7 @@ TEST_F(Http1ServerConnectionImplTest, LargeHeaderResponseEncode) { output); } -TEST_F(Http1ServerConnectionImplTest, HeaderOnlyResponseTrainProperHeaders) { +TEST_P(Http1ServerConnectionImplTest, HeaderOnlyResponseTrainProperHeaders) { codec_settings_.header_key_format_ = Http1Settings::HeaderKeyFormat::ProperCase; initialize(); @@ -1279,7 +1329,7 @@ TEST_F(Http1ServerConnectionImplTest, HeaderOnlyResponseTrainProperHeaders) { output); } -TEST_F(Http1ServerConnectionImplTest, HeaderOnlyResponseWith204) { +TEST_P(Http1ServerConnectionImplTest, HeaderOnlyResponseWith204) { initialize(); NiceMock decoder; @@ -1303,7 +1353,7 @@ TEST_F(Http1ServerConnectionImplTest, HeaderOnlyResponseWith204) { EXPECT_EQ("HTTP/1.1 204 No Content\r\n\r\n", output); } -TEST_F(Http1ServerConnectionImplTest, HeaderOnlyResponseWith100Then200) { +TEST_P(Http1ServerConnectionImplTest, HeaderOnlyResponseWith100Then200) { initialize(); NiceMock decoder; @@ -1334,7 +1384,7 @@ TEST_F(Http1ServerConnectionImplTest, HeaderOnlyResponseWith100Then200) { EXPECT_EQ("HTTP/1.1 200 OK\r\ncontent-length: 0\r\n\r\n", output); } -TEST_F(Http1ServerConnectionImplTest, MetadataTest) { +TEST_P(Http1ServerConnectionImplTest, MetadataTest) { initialize(); NiceMock decoder; @@ -1357,7 +1407,7 @@ TEST_F(Http1ServerConnectionImplTest, MetadataTest) { EXPECT_EQ(1, store_.counter("http1.metadata_not_supported_error").value()); } -TEST_F(Http1ServerConnectionImplTest, ChunkedResponse) { +TEST_P(Http1ServerConnectionImplTest, ChunkedResponse) { initialize(); NiceMock decoder; @@ -1426,7 +1476,7 @@ TEST_F(Http1ServerConnectionImplTest, ChunkedResponseWithTrailers) { output); } -TEST_F(Http1ServerConnectionImplTest, ContentLengthResponse) { +TEST_P(Http1ServerConnectionImplTest, ContentLengthResponse) { initialize(); NiceMock decoder; @@ -1453,7 +1503,7 @@ TEST_F(Http1ServerConnectionImplTest, ContentLengthResponse) { EXPECT_EQ("HTTP/1.1 200 OK\r\ncontent-length: 11\r\n\r\nHello World", output); } -TEST_F(Http1ServerConnectionImplTest, HeadRequestResponse) { +TEST_P(Http1ServerConnectionImplTest, HeadRequestResponse) { initialize(); NiceMock decoder; @@ -1477,7 +1527,7 @@ TEST_F(Http1ServerConnectionImplTest, HeadRequestResponse) { EXPECT_EQ("HTTP/1.1 200 OK\r\ncontent-length: 5\r\n\r\n", output); } -TEST_F(Http1ServerConnectionImplTest, HeadChunkedRequestResponse) { +TEST_P(Http1ServerConnectionImplTest, HeadChunkedRequestResponse) { initialize(); NiceMock decoder; @@ -1501,7 +1551,7 @@ TEST_F(Http1ServerConnectionImplTest, HeadChunkedRequestResponse) { EXPECT_EQ("HTTP/1.1 200 OK\r\ntransfer-encoding: chunked\r\n\r\n", output); } -TEST_F(Http1ServerConnectionImplTest, DoubleRequest) { +TEST_P(Http1ServerConnectionImplTest, DoubleRequest) { initialize(); NiceMock decoder; @@ -1527,11 +1577,11 @@ TEST_F(Http1ServerConnectionImplTest, DoubleRequest) { EXPECT_EQ(0U, buffer.length()); } -TEST_F(Http1ServerConnectionImplTest, RequestWithTrailersDropped) { expectTrailersTest(false); } +TEST_P(Http1ServerConnectionImplTest, RequestWithTrailersDropped) { expectTrailersTest(false); } -TEST_F(Http1ServerConnectionImplTest, RequestWithTrailersKept) { expectTrailersTest(true); } +TEST_P(Http1ServerConnectionImplTest, RequestWithTrailersKept) { expectTrailersTest(true); } -TEST_F(Http1ServerConnectionImplTest, IgnoreUpgradeH2c) { +TEST_P(Http1ServerConnectionImplTest, IgnoreUpgradeH2c) { initialize(); TestHeaderMapImpl expected_headers{ @@ -1542,7 +1592,7 @@ TEST_F(Http1ServerConnectionImplTest, IgnoreUpgradeH2c) { expectHeadersTest(Protocol::Http11, true, buffer, expected_headers); } -TEST_F(Http1ServerConnectionImplTest, IgnoreUpgradeH2cClose) { +TEST_P(Http1ServerConnectionImplTest, IgnoreUpgradeH2cClose) { initialize(); TestHeaderMapImpl expected_headers{{":authority", "www.somewhere.com"}, @@ -1555,7 +1605,7 @@ TEST_F(Http1ServerConnectionImplTest, IgnoreUpgradeH2cClose) { expectHeadersTest(Protocol::Http11, true, buffer, expected_headers); } -TEST_F(Http1ServerConnectionImplTest, IgnoreUpgradeH2cCloseEtc) { +TEST_P(Http1ServerConnectionImplTest, IgnoreUpgradeH2cCloseEtc) { initialize(); TestHeaderMapImpl expected_headers{{":authority", "www.somewhere.com"}, @@ -1568,7 +1618,7 @@ TEST_F(Http1ServerConnectionImplTest, IgnoreUpgradeH2cCloseEtc) { expectHeadersTest(Protocol::Http11, true, buffer, expected_headers); } -TEST_F(Http1ServerConnectionImplTest, UpgradeRequest) { +TEST_P(Http1ServerConnectionImplTest, UpgradeRequest) { initialize(); InSequence sequence; @@ -1592,7 +1642,7 @@ TEST_F(Http1ServerConnectionImplTest, UpgradeRequest) { EXPECT_TRUE(status.ok()); } -TEST_F(Http1ServerConnectionImplTest, UpgradeRequestWithEarlyData) { +TEST_P(Http1ServerConnectionImplTest, UpgradeRequestWithEarlyData) { initialize(); InSequence sequence; @@ -1608,7 +1658,7 @@ TEST_F(Http1ServerConnectionImplTest, UpgradeRequestWithEarlyData) { EXPECT_TRUE(status.ok()); } -TEST_F(Http1ServerConnectionImplTest, UpgradeRequestWithTEChunked) { +TEST_P(Http1ServerConnectionImplTest, UpgradeRequestWithTEChunked) { initialize(); InSequence sequence; @@ -1626,7 +1676,7 @@ TEST_F(Http1ServerConnectionImplTest, UpgradeRequestWithTEChunked) { EXPECT_TRUE(status.ok()); } -TEST_F(Http1ServerConnectionImplTest, UpgradeRequestWithNoBody) { +TEST_P(Http1ServerConnectionImplTest, UpgradeRequestWithNoBody) { initialize(); InSequence sequence; @@ -1766,8 +1816,9 @@ TEST_F(Http1ServerConnectionImplTest, WatermarkTest) { ->onUnderlyingConnectionBelowWriteBufferLowWatermark(); } -class Http1ClientConnectionImplTest : public testing::Test { +class Http1ClientConnectionImplTest : public Http1CodecParamTest { public: + ~Http1ClientConnectionImplTest() override = default; void initialize() { codec_ = std::make_unique(connection_, store_, callbacks_, codec_settings_, max_response_headers_count_); @@ -1783,7 +1834,10 @@ class Http1ClientConnectionImplTest : public testing::Test { uint32_t max_response_headers_count_{Http::DEFAULT_MAX_HEADERS_COUNT}; }; -TEST_F(Http1ClientConnectionImplTest, SimpleGet) { +INSTANTIATE_TEST_SUITE_P(Http1ClientConnectionImplTest, Http1ClientConnectionImplTest, + testing::ValuesIn({CodecImplementation::Old, CodecImplementation::New})); + +TEST_P(Http1ClientConnectionImplTest, SimpleGet) { initialize(); MockResponseDecoder response_decoder; @@ -1797,7 +1851,7 @@ TEST_F(Http1ClientConnectionImplTest, SimpleGet) { EXPECT_EQ("GET / HTTP/1.1\r\ncontent-length: 0\r\n\r\n", output); } -TEST_F(Http1ClientConnectionImplTest, SimpleGetWithHeaderCasing) { +TEST_P(Http1ClientConnectionImplTest, SimpleGetWithHeaderCasing) { codec_settings_.header_key_format_ = Http1Settings::HeaderKeyFormat::ProperCase; initialize(); @@ -1813,7 +1867,7 @@ TEST_F(Http1ClientConnectionImplTest, SimpleGetWithHeaderCasing) { EXPECT_EQ("GET / HTTP/1.1\r\nMy-Custom-Header: hey\r\nContent-Length: 0\r\n\r\n", output); } -TEST_F(Http1ClientConnectionImplTest, HostHeaderTranslate) { +TEST_P(Http1ClientConnectionImplTest, HostHeaderTranslate) { initialize(); MockResponseDecoder response_decoder; @@ -1827,7 +1881,7 @@ TEST_F(Http1ClientConnectionImplTest, HostHeaderTranslate) { EXPECT_EQ("GET / HTTP/1.1\r\nhost: host\r\ncontent-length: 0\r\n\r\n", output); } -TEST_F(Http1ClientConnectionImplTest, Reset) { +TEST_P(Http1ClientConnectionImplTest, Reset) { initialize(); MockResponseDecoder response_decoder; @@ -1841,7 +1895,7 @@ TEST_F(Http1ClientConnectionImplTest, Reset) { // Verify that we correctly enable reads on the connection when the final response is // received. -TEST_F(Http1ClientConnectionImplTest, FlowControlReadDisabledReenable) { +TEST_P(Http1ClientConnectionImplTest, FlowControlReadDisabledReenable) { initialize(); MockResponseDecoder response_decoder; @@ -1871,7 +1925,7 @@ TEST_F(Http1ClientConnectionImplTest, FlowControlReadDisabledReenable) { EXPECT_TRUE(status.ok()); } -TEST_F(Http1ClientConnectionImplTest, PrematureResponse) { +TEST_P(Http1ClientConnectionImplTest, PrematureResponse) { initialize(); Buffer::OwnedImpl response("HTTP/1.1 408 Request Timeout\r\nConnection: Close\r\n\r\n"); @@ -1879,7 +1933,7 @@ TEST_F(Http1ClientConnectionImplTest, PrematureResponse) { EXPECT_TRUE(isPrematureResponseError(status)); } -TEST_F(Http1ClientConnectionImplTest, EmptyBodyResponse503) { +TEST_P(Http1ClientConnectionImplTest, EmptyBodyResponse503) { initialize(); NiceMock response_decoder; @@ -1893,7 +1947,7 @@ TEST_F(Http1ClientConnectionImplTest, EmptyBodyResponse503) { EXPECT_TRUE(status.ok()); } -TEST_F(Http1ClientConnectionImplTest, EmptyBodyResponse200) { +TEST_P(Http1ClientConnectionImplTest, EmptyBodyResponse200) { initialize(); NiceMock response_decoder; @@ -1907,7 +1961,7 @@ TEST_F(Http1ClientConnectionImplTest, EmptyBodyResponse200) { EXPECT_TRUE(status.ok()); } -TEST_F(Http1ClientConnectionImplTest, HeadRequest) { +TEST_P(Http1ClientConnectionImplTest, HeadRequest) { initialize(); NiceMock response_decoder; @@ -1921,7 +1975,7 @@ TEST_F(Http1ClientConnectionImplTest, HeadRequest) { EXPECT_TRUE(status.ok()); } -TEST_F(Http1ClientConnectionImplTest, 204Response) { +TEST_P(Http1ClientConnectionImplTest, 204Response) { initialize(); NiceMock response_decoder; @@ -1935,7 +1989,7 @@ TEST_F(Http1ClientConnectionImplTest, 204Response) { EXPECT_TRUE(status.ok()); } -TEST_F(Http1ClientConnectionImplTest, 100Response) { +TEST_P(Http1ClientConnectionImplTest, 100Response) { initialize(); NiceMock response_decoder; @@ -1955,7 +2009,7 @@ TEST_F(Http1ClientConnectionImplTest, 100Response) { EXPECT_TRUE(status.ok()); } -TEST_F(Http1ClientConnectionImplTest, BadEncodeParams) { +TEST_P(Http1ClientConnectionImplTest, BadEncodeParams) { initialize(); NiceMock response_decoder; @@ -1968,7 +2022,7 @@ TEST_F(Http1ClientConnectionImplTest, BadEncodeParams) { CodecClientException); } -TEST_F(Http1ClientConnectionImplTest, NoContentLengthResponse) { +TEST_P(Http1ClientConnectionImplTest, NoContentLengthResponse) { initialize(); NiceMock response_decoder; @@ -1990,7 +2044,7 @@ TEST_F(Http1ClientConnectionImplTest, NoContentLengthResponse) { EXPECT_TRUE(status.ok()); } -TEST_F(Http1ClientConnectionImplTest, ResponseWithTrailers) { +TEST_P(Http1ClientConnectionImplTest, ResponseWithTrailers) { initialize(); NiceMock response_decoder; @@ -2005,7 +2059,7 @@ TEST_F(Http1ClientConnectionImplTest, ResponseWithTrailers) { EXPECT_TRUE(status.ok()); } -TEST_F(Http1ClientConnectionImplTest, GiantPath) { +TEST_P(Http1ClientConnectionImplTest, GiantPath) { initialize(); NiceMock response_decoder; @@ -2030,7 +2084,7 @@ TEST_F(Http1ClientConnectionImplTest, PrematureUpgradeResponse) { EXPECT_TRUE(isPrematureResponseError(status)); } -TEST_F(Http1ClientConnectionImplTest, UpgradeResponse) { +TEST_P(Http1ClientConnectionImplTest, UpgradeResponse) { initialize(); InSequence s; @@ -2066,7 +2120,7 @@ TEST_F(Http1ClientConnectionImplTest, UpgradeResponse) { // Same data as above, but make sure directDispatch immediately hands off any // outstanding data. -TEST_F(Http1ClientConnectionImplTest, UpgradeResponseWithEarlyData) { +TEST_P(Http1ClientConnectionImplTest, UpgradeResponseWithEarlyData) { initialize(); InSequence s; @@ -2193,7 +2247,7 @@ TEST_F(Http1ClientConnectionImplTest, WatermarkTest) { // caller attempts to close the connection. This causes the network connection to attempt to write // pending data, even in the no flush scenario, which can cause us to go below low watermark // which then raises callbacks for a stream that no longer exists. -TEST_F(Http1ClientConnectionImplTest, HighwatermarkMultipleResponses) { +TEST_P(Http1ClientConnectionImplTest, HighwatermarkMultipleResponses) { initialize(); InSequence s; @@ -2264,36 +2318,36 @@ TEST_F(Http1ServerConnectionImplTest, LargeTrailersRejected) { } // Tests that the default limit for the number of request headers is 100. -TEST_F(Http1ServerConnectionImplTest, ManyTrailersRejected) { +TEST_P(Http1ServerConnectionImplTest, ManyTrailersRejected) { // Send a request with 101 headers. testTrailersExceedLimit(createHeaderFragment(101), true); } -TEST_F(Http1ServerConnectionImplTest, LargeTrailersRejectedIgnored) { +TEST_P(Http1ServerConnectionImplTest, LargeTrailersRejectedIgnored) { // Default limit of 60 KiB std::string long_string = "big: " + std::string(60 * 1024, 'q') + "\r\n"; testTrailersExceedLimit(long_string, false); } // Tests that the default limit for the number of request headers is 100. -TEST_F(Http1ServerConnectionImplTest, ManyTrailersIgnored) { +TEST_P(Http1ServerConnectionImplTest, ManyTrailersIgnored) { // Send a request with 101 headers. testTrailersExceedLimit(createHeaderFragment(101), false); } -TEST_F(Http1ServerConnectionImplTest, LargeRequestHeadersRejected) { +TEST_P(Http1ServerConnectionImplTest, LargeRequestHeadersRejected) { // Default limit of 60 KiB std::string long_string = "big: " + std::string(60 * 1024, 'q') + "\r\n"; testRequestHeadersExceedLimit(long_string, ""); } // Tests that the default limit for the number of request headers is 100. -TEST_F(Http1ServerConnectionImplTest, ManyRequestHeadersRejected) { +TEST_P(Http1ServerConnectionImplTest, ManyRequestHeadersRejected) { // Send a request with 101 headers. testRequestHeadersExceedLimit(createHeaderFragment(101), "http1.too_many_headers"); } -TEST_F(Http1ServerConnectionImplTest, LargeRequestHeadersSplitRejected) { +TEST_P(Http1ServerConnectionImplTest, LargeRequestHeadersSplitRejected) { // Default limit of 60 KiB initialize(); @@ -2323,7 +2377,7 @@ TEST_F(Http1ServerConnectionImplTest, LargeRequestHeadersSplitRejected) { // Tests that the 101th request header causes overflow with the default max number of request // headers. -TEST_F(Http1ServerConnectionImplTest, ManyRequestHeadersSplitRejected) { +TEST_P(Http1ServerConnectionImplTest, ManyRequestHeadersSplitRejected) { // Default limit of 100. initialize(); @@ -2349,27 +2403,27 @@ TEST_F(Http1ServerConnectionImplTest, ManyRequestHeadersSplitRejected) { EXPECT_EQ(status.message(), "headers size exceeds limit"); } -TEST_F(Http1ServerConnectionImplTest, LargeRequestHeadersAccepted) { +TEST_P(Http1ServerConnectionImplTest, LargeRequestHeadersAccepted) { max_request_headers_kb_ = 65; std::string long_string = "big: " + std::string(64 * 1024, 'q') + "\r\n"; testRequestHeadersAccepted(long_string); } -TEST_F(Http1ServerConnectionImplTest, LargeRequestHeadersAcceptedMaxConfigurable) { +TEST_P(Http1ServerConnectionImplTest, LargeRequestHeadersAcceptedMaxConfigurable) { max_request_headers_kb_ = 96; std::string long_string = "big: " + std::string(95 * 1024, 'q') + "\r\n"; testRequestHeadersAccepted(long_string); } // Tests that the number of request headers is configurable. -TEST_F(Http1ServerConnectionImplTest, ManyRequestHeadersAccepted) { +TEST_P(Http1ServerConnectionImplTest, ManyRequestHeadersAccepted) { max_request_headers_count_ = 150; // Create a request with 150 headers. testRequestHeadersAccepted(createHeaderFragment(150)); } // Tests that response headers of 80 kB fails. -TEST_F(Http1ClientConnectionImplTest, LargeResponseHeadersRejected) { +TEST_P(Http1ClientConnectionImplTest, LargeResponseHeadersRejected) { initialize(); NiceMock response_decoder; @@ -2388,7 +2442,7 @@ TEST_F(Http1ClientConnectionImplTest, LargeResponseHeadersRejected) { } // Tests that the size of response headers for HTTP/1 must be under 80 kB. -TEST_F(Http1ClientConnectionImplTest, LargeResponseHeadersAccepted) { +TEST_P(Http1ClientConnectionImplTest, LargeResponseHeadersAccepted) { initialize(); NiceMock response_decoder; @@ -2457,7 +2511,7 @@ TEST_F(Http1ClientConnectionImplTest, LargeHeaderRequestEncode) { } // Exception called when the number of response headers exceeds the default value of 100. -TEST_F(Http1ClientConnectionImplTest, ManyResponseHeadersRejected) { +TEST_P(Http1ClientConnectionImplTest, ManyResponseHeadersRejected) { initialize(); NiceMock response_decoder; @@ -2474,7 +2528,7 @@ TEST_F(Http1ClientConnectionImplTest, ManyResponseHeadersRejected) { } // Tests that the number of response headers is configurable. -TEST_F(Http1ClientConnectionImplTest, ManyResponseHeadersAccepted) { +TEST_P(Http1ClientConnectionImplTest, ManyResponseHeadersAccepted) { max_response_headers_count_ = 152; initialize(); diff --git a/tools/spelling/spelling_dictionary.txt b/tools/spelling/spelling_dictionary.txt index e5af042bf9cd1..52b93232332ec 100644 --- a/tools/spelling/spelling_dictionary.txt +++ b/tools/spelling/spelling_dictionary.txt @@ -167,6 +167,7 @@ LDS LEV LF LHS +LLHTTP LLVM LPT LRS