diff --git a/api/envoy/config/route/v3/route_components.proto b/api/envoy/config/route/v3/route_components.proto index 58ac31d41515d..ee82e8f732261 100644 --- a/api/envoy/config/route/v3/route_components.proto +++ b/api/envoy/config/route/v3/route_components.proto @@ -1885,8 +1885,8 @@ message HeaderMatcher { // "-1somestring" type.v3.Int64Range range_match = 6; - // If specified, header match will be performed based on whether the header is in the - // request. + // If specified as true, header match will be performed based on whether the header is in the + // request. If specified as false, header match will be performed based on whether the header is absent. bool present_match = 7; // If specified, header match will be performed based on the prefix of the header value. diff --git a/api/envoy/config/route/v4alpha/route_components.proto b/api/envoy/config/route/v4alpha/route_components.proto index ce3b1c479969a..256a3c742ff3a 100644 --- a/api/envoy/config/route/v4alpha/route_components.proto +++ b/api/envoy/config/route/v4alpha/route_components.proto @@ -1837,8 +1837,8 @@ message HeaderMatcher { // "-1somestring" type.v3.Int64Range range_match = 6; - // If specified, header match will be performed based on whether the header is in the - // request. + // If specified as true, header match will be performed based on whether the header is in the + // request. If specified as false, header match will be performed based on whether the header is absent. bool present_match = 7; // If specified, header match will be performed based on the prefix of the header value. diff --git a/docs/root/version_history/current.rst b/docs/root/version_history/current.rst index bd265aaccff6b..577d2baaa9222 100644 --- a/docs/root/version_history/current.rst +++ b/docs/root/version_history/current.rst @@ -29,11 +29,12 @@ Minor Behavior Changes ``envoy.reloadable_features.send_strict_1xx_and_204_response_headers`` (do not send 1xx or 204 responses with these headers). Both are true by default. * http: serve HEAD requests from cache. +* http: the behavior of the *present_match* in route header matcher changed. The value of *present_match* is ignored in the past. The new behavior is *present_match* performed when value is true. absent match performed when the value is false. Please reference :ref:`present_match + `. * listener: respect the :ref:`connection balance config ` defined within the listener where the sockets are redirected to. Clear that field to restore the previous behavior. * tcp: switched to the new connection pool by default. Any unexpected behavioral changes can be reverted by setting runtime guard ``envoy.reloadable_features.new_tcp_connection_pool`` to false. - Bug Fixes --------- *Changes expected to improve the state of the world and are unlikely to have negative effects* diff --git a/generated_api_shadow/envoy/config/route/v3/route_components.proto b/generated_api_shadow/envoy/config/route/v3/route_components.proto index 1b70f539c2c0a..b8f03cde3a9dd 100644 --- a/generated_api_shadow/envoy/config/route/v3/route_components.proto +++ b/generated_api_shadow/envoy/config/route/v3/route_components.proto @@ -1916,8 +1916,8 @@ message HeaderMatcher { // "-1somestring" type.v3.Int64Range range_match = 6; - // If specified, header match will be performed based on whether the header is in the - // request. + // If specified as true, header match will be performed based on whether the header is in the + // request. If specified as false, header match will be performed based on whether the header is absent. bool present_match = 7; // If specified, header match will be performed based on the prefix of the header value. diff --git a/generated_api_shadow/envoy/config/route/v4alpha/route_components.proto b/generated_api_shadow/envoy/config/route/v4alpha/route_components.proto index 34d3762bfdde9..6b8c146582a33 100644 --- a/generated_api_shadow/envoy/config/route/v4alpha/route_components.proto +++ b/generated_api_shadow/envoy/config/route/v4alpha/route_components.proto @@ -1915,8 +1915,8 @@ message HeaderMatcher { // "-1somestring" type.v3.Int64Range range_match = 6; - // If specified, header match will be performed based on whether the header is in the - // request. + // If specified as true, header match will be performed based on whether the header is in the + // request. If specified as false, header match will be performed based on whether the header is absent. bool present_match = 7; // If specified, header match will be performed based on the prefix of the header value. diff --git a/source/common/http/header_utility.cc b/source/common/http/header_utility.cc index e504a21ad8a23..13b16c0841e4b 100644 --- a/source/common/http/header_utility.cc +++ b/source/common/http/header_utility.cc @@ -58,6 +58,7 @@ HeaderUtility::HeaderData::HeaderData(const envoy::config::route::v3::HeaderMatc break; case envoy::config::route::v3::HeaderMatcher::HeaderMatchSpecifierCase::kPresentMatch: header_match_type_ = HeaderMatchType::Present; + present_ = config.present_match(); break; case envoy::config::route::v3::HeaderMatcher::HeaderMatchSpecifierCase::kPrefixMatch: header_match_type_ = HeaderMatchType::Prefix; @@ -76,6 +77,7 @@ HeaderUtility::HeaderData::HeaderData(const envoy::config::route::v3::HeaderMatc FALLTHRU; default: header_match_type_ = HeaderMatchType::Present; + present_ = true; break; } } @@ -135,7 +137,11 @@ bool HeaderUtility::matchHeaders(const HeaderMap& request_headers, const HeaderD const auto header_value = getAllOfHeaderAsString(request_headers, header_data.name_); if (!header_value.result().has_value()) { - return header_data.invert_match_ && header_data.header_match_type_ == HeaderMatchType::Present; + if (header_data.invert_match_) { + return header_data.header_match_type_ == HeaderMatchType::Present && header_data.present_; + } else { + return header_data.header_match_type_ == HeaderMatchType::Present && !header_data.present_; + } } bool match; @@ -154,7 +160,7 @@ bool HeaderUtility::matchHeaders(const HeaderMap& request_headers, const HeaderD break; } case HeaderMatchType::Present: - match = true; + match = header_data.present_; break; case HeaderMatchType::Prefix: match = absl::StartsWith(header_value.result().value(), header_data.value_); diff --git a/source/common/http/header_utility.h b/source/common/http/header_utility.h index 20719003733c2..fe034a8c1d21f 100644 --- a/source/common/http/header_utility.h +++ b/source/common/http/header_utility.h @@ -66,6 +66,7 @@ class HeaderUtility { Regex::CompiledMatcherPtr regex_; envoy::type::v3::Int64Range range_; const bool invert_match_; + bool present_; // HeaderMatcher bool matchesHeaders(const HeaderMap& headers) const override { diff --git a/test/common/http/header_utility_test.cc b/test/common/http/header_utility_test.cc index a529f7011b010..6abeac050b317 100644 --- a/test/common/http/header_utility_test.cc +++ b/test/common/http/header_utility_test.cc @@ -495,7 +495,9 @@ invert_match: true EXPECT_FALSE(HeaderUtility::matchHeaders(unmatching_headers, header_data)); } -TEST(MatchHeadersTest, HeaderPresentMatch) { +// Test the case present_match is true. Expected true when +// header matched, expected false when no header matched. +TEST(MatchHeadersTest, HeaderPresentMatchWithTrueValue) { TestRequestHeaderMapImpl matching_headers{{"match-header", "123"}}; TestRequestHeaderMapImpl unmatching_headers{{"nonmatch-header", "1234"}, {"other-nonmatch-header", "123.456"}}; @@ -512,7 +514,28 @@ present_match: true EXPECT_FALSE(HeaderUtility::matchHeaders(unmatching_headers, header_data)); } -TEST(MatchHeadersTest, HeaderPresentInverseMatch) { +// Test the case present_match is false. Expected false when +// header matched, expected true when no header matched. +TEST(MatchHeadersTest, HeaderPresentMatchWithFalseValue) { + TestRequestHeaderMapImpl matching_headers{{"match-header", "123"}}; + TestRequestHeaderMapImpl unmatching_headers{{"nonmatch-header", "1234"}, + {"other-nonmatch-header", "123.456"}}; + + const std::string yaml = R"EOF( +name: match-header +present_match: false + )EOF"; + + std::vector header_data; + header_data.push_back( + std::make_unique(parseHeaderMatcherFromYaml(yaml))); + EXPECT_FALSE(HeaderUtility::matchHeaders(matching_headers, header_data)); + EXPECT_TRUE(HeaderUtility::matchHeaders(unmatching_headers, header_data)); +} + +// Test the case present_match is true and invert_match is true. Expected true when +// no header matched, expected false when header matched. +TEST(MatchHeadersTest, HeaderPresentInverseMatchWithTrueValue) { TestRequestHeaderMapImpl unmatching_headers{{"match-header", "123"}}; TestRequestHeaderMapImpl matching_headers{{"nonmatch-header", "1234"}, {"other-nonmatch-header", "123.456"}}; @@ -530,6 +553,26 @@ invert_match: true EXPECT_FALSE(HeaderUtility::matchHeaders(unmatching_headers, header_data)); } +// Test the case present_match is true and invert_match is true. Expected false when +// no header matched, expected true when header matched. +TEST(MatchHeadersTest, HeaderPresentInverseMatchWithFalseValue) { + TestRequestHeaderMapImpl unmatching_headers{{"match-header", "123"}}; + TestRequestHeaderMapImpl matching_headers{{"nonmatch-header", "1234"}, + {"other-nonmatch-header", "123.456"}}; + + const std::string yaml = R"EOF( +name: match-header +present_match: false +invert_match: true + )EOF"; + + std::vector header_data; + header_data.push_back( + std::make_unique(parseHeaderMatcherFromYaml(yaml))); + EXPECT_FALSE(HeaderUtility::matchHeaders(matching_headers, header_data)); + EXPECT_TRUE(HeaderUtility::matchHeaders(unmatching_headers, header_data)); +} + TEST(MatchHeadersTest, HeaderPrefixMatch) { TestRequestHeaderMapImpl matching_headers{{"match-header", "value123"}}; TestRequestHeaderMapImpl unmatching_headers{{"match-header", "123value"}};