From 40a99f1caab614b74d1af38421feab6403f4703a Mon Sep 17 00:00:00 2001 From: Yangmin Zhu Date: Tue, 11 Feb 2020 20:00:45 -0800 Subject: [PATCH] add ignore_case to StringMatcher that allows case insensitive exact/prefix/suffix string matching Signed-off-by: Yangmin Zhu --- api/envoy/type/matcher/string.proto | 7 +- api/envoy/type/matcher/v3/string.proto | 7 +- .../envoy/type/matcher/string.proto | 7 +- .../envoy/type/matcher/v3/string.proto | 7 +- source/common/common/matchers.cc | 15 ++++- test/common/common/BUILD | 1 + test/common/common/matchers_test.cc | 65 +++++++++++++++++++ 7 files changed, 102 insertions(+), 7 deletions(-) diff --git a/api/envoy/type/matcher/string.proto b/api/envoy/type/matcher/string.proto index 325bb986eb06..056dab271d69 100644 --- a/api/envoy/type/matcher/string.proto +++ b/api/envoy/type/matcher/string.proto @@ -14,7 +14,7 @@ option java_multiple_files = true; // [#protodoc-title: StringMatcher] // Specifies the way to match a string. -// [#next-free-field: 6] +// [#next-free-field: 7] message StringMatcher { oneof match_pattern { option (validate.required) = true; @@ -64,6 +64,11 @@ message StringMatcher { // The input string must match the regular expression specified here. RegexMatcher safe_regex = 5 [(validate.rules).message = {required: true}]; } + + // If true, indicates the exact/prefix/suffix matching should be case insensitive. This has no + // effect for the safe_regex match. + // For example, the matcher *data* will match both input string *Data* and *data* if set to true. + bool ignore_case = 6; } // Specifies a list of ways to match a string. diff --git a/api/envoy/type/matcher/v3/string.proto b/api/envoy/type/matcher/v3/string.proto index 5266ce44033c..c3c71b61a397 100644 --- a/api/envoy/type/matcher/v3/string.proto +++ b/api/envoy/type/matcher/v3/string.proto @@ -16,7 +16,7 @@ option java_multiple_files = true; // [#protodoc-title: StringMatcher] // Specifies the way to match a string. -// [#next-free-field: 6] +// [#next-free-field: 7] message StringMatcher { option (udpa.annotations.versioning).previous_message_type = "envoy.type.matcher.StringMatcher"; @@ -53,6 +53,11 @@ message StringMatcher { // The input string must match the regular expression specified here. RegexMatcher safe_regex = 5 [(validate.rules).message = {required: true}]; } + + // If true, indicates the exact/prefix/suffix matching should be case insensitive. This has no + // effect for the safe_regex match. + // For example, the matcher *data* will match both input string *Data* and *data* if set to true. + bool ignore_case = 6; } // Specifies a list of ways to match a string. diff --git a/generated_api_shadow/envoy/type/matcher/string.proto b/generated_api_shadow/envoy/type/matcher/string.proto index 325bb986eb06..056dab271d69 100644 --- a/generated_api_shadow/envoy/type/matcher/string.proto +++ b/generated_api_shadow/envoy/type/matcher/string.proto @@ -14,7 +14,7 @@ option java_multiple_files = true; // [#protodoc-title: StringMatcher] // Specifies the way to match a string. -// [#next-free-field: 6] +// [#next-free-field: 7] message StringMatcher { oneof match_pattern { option (validate.required) = true; @@ -64,6 +64,11 @@ message StringMatcher { // The input string must match the regular expression specified here. RegexMatcher safe_regex = 5 [(validate.rules).message = {required: true}]; } + + // If true, indicates the exact/prefix/suffix matching should be case insensitive. This has no + // effect for the safe_regex match. + // For example, the matcher *data* will match both input string *Data* and *data* if set to true. + bool ignore_case = 6; } // Specifies a list of ways to match a string. diff --git a/generated_api_shadow/envoy/type/matcher/v3/string.proto b/generated_api_shadow/envoy/type/matcher/v3/string.proto index af37e9a3269d..ef31323af81b 100644 --- a/generated_api_shadow/envoy/type/matcher/v3/string.proto +++ b/generated_api_shadow/envoy/type/matcher/v3/string.proto @@ -16,7 +16,7 @@ option java_multiple_files = true; // [#protodoc-title: StringMatcher] // Specifies the way to match a string. -// [#next-free-field: 6] +// [#next-free-field: 7] message StringMatcher { option (udpa.annotations.versioning).previous_message_type = "envoy.type.matcher.StringMatcher"; @@ -68,6 +68,11 @@ message StringMatcher { // The input string must match the regular expression specified here. RegexMatcher safe_regex = 5 [(validate.rules).message = {required: true}]; } + + // If true, indicates the exact/prefix/suffix matching should be case insensitive. This has no + // effect for the safe_regex match. + // For example, the matcher *data* will match both input string *Data* and *data* if set to true. + bool ignore_case = 6; } // Specifies a list of ways to match a string. diff --git a/source/common/common/matchers.cc b/source/common/common/matchers.cc index 61303846555c..11fa907cc9c1 100644 --- a/source/common/common/matchers.cc +++ b/source/common/common/matchers.cc @@ -65,10 +65,16 @@ StringMatcherImpl::StringMatcherImpl(const envoy::type::matcher::v3::StringMatch : matcher_(matcher) { if (matcher.match_pattern_case() == envoy::type::matcher::v3::StringMatcher::MatchPatternCase::kHiddenEnvoyDeprecatedRegex) { + if (matcher.ignore_case()) { + throw EnvoyException("ignore_case has no effect for regex."); + } regex_ = Regex::Utility::parseStdRegexAsCompiledMatcher(matcher_.hidden_envoy_deprecated_regex()); } else if (matcher.match_pattern_case() == envoy::type::matcher::v3::StringMatcher::MatchPatternCase::kSafeRegex) { + if (matcher.ignore_case()) { + throw EnvoyException("ignore_case has no effect for safe_regex."); + } regex_ = Regex::Utility::parseRegex(matcher_.safe_regex()); } } @@ -84,11 +90,14 @@ bool StringMatcherImpl::match(const ProtobufWkt::Value& value) const { bool StringMatcherImpl::match(const absl::string_view value) const { switch (matcher_.match_pattern_case()) { case envoy::type::matcher::v3::StringMatcher::MatchPatternCase::kExact: - return matcher_.exact() == value; + return matcher_.ignore_case() ? absl::EqualsIgnoreCase(value, matcher_.exact()) + : value == matcher_.exact(); case envoy::type::matcher::v3::StringMatcher::MatchPatternCase::kPrefix: - return absl::StartsWith(value, matcher_.prefix()); + return matcher_.ignore_case() ? absl::StartsWithIgnoreCase(value, matcher_.prefix()) + : absl::StartsWith(value, matcher_.prefix()); case envoy::type::matcher::v3::StringMatcher::MatchPatternCase::kSuffix: - return absl::EndsWith(value, matcher_.suffix()); + return matcher_.ignore_case() ? absl::EndsWithIgnoreCase(value, matcher_.suffix()) + : absl::EndsWith(value, matcher_.suffix()); case envoy::type::matcher::v3::StringMatcher::MatchPatternCase::kHiddenEnvoyDeprecatedRegex: case envoy::type::matcher::v3::StringMatcher::MatchPatternCase::kSafeRegex: return regex_->match(value); diff --git a/test/common/common/BUILD b/test/common/common/BUILD index 38d95c0bce32..71acbacc4ea3 100644 --- a/test/common/common/BUILD +++ b/test/common/common/BUILD @@ -136,6 +136,7 @@ envoy_cc_test( deps = [ "//source/common/common:matchers_lib", "//source/common/config:metadata_lib", + "//test/test_common:utility_lib", "//source/common/protobuf:utility_lib", "@envoy_api//envoy/config/core/v3:pkg_cc_proto", "@envoy_api//envoy/type/matcher/v3:pkg_cc_proto", diff --git a/test/common/common/matchers_test.cc b/test/common/common/matchers_test.cc index 7028930fd644..3dc918bc7b5b 100644 --- a/test/common/common/matchers_test.cc +++ b/test/common/common/matchers_test.cc @@ -1,3 +1,4 @@ +#include "envoy/common/exception.h" #include "envoy/config/core/v3/base.pb.h" #include "envoy/type/matcher/v3/metadata.pb.h" #include "envoy/type/matcher/v3/string.pb.h" @@ -7,6 +8,8 @@ #include "common/config/metadata.h" #include "common/protobuf/protobuf.h" +#include "test/test_common/utility.h" + #include "gtest/gtest.h" namespace Envoy { @@ -258,6 +261,51 @@ TEST(MetadataTest, MatchDoubleListValue) { metadataValue.Clear(); } +TEST(StringMatcher, ExactMatchIgnoreCase) { + envoy::type::matcher::v3::StringMatcher matcher; + matcher.set_exact("exact"); + EXPECT_TRUE(Matchers::StringMatcherImpl(matcher).match("exact")); + EXPECT_FALSE(Matchers::StringMatcherImpl(matcher).match("EXACT")); + EXPECT_FALSE(Matchers::StringMatcherImpl(matcher).match("exacz")); + EXPECT_FALSE(Matchers::StringMatcherImpl(matcher).match("other")); + + matcher.set_ignore_case(true); + EXPECT_TRUE(Matchers::StringMatcherImpl(matcher).match("exact")); + EXPECT_TRUE(Matchers::StringMatcherImpl(matcher).match("EXACT")); + EXPECT_FALSE(Matchers::StringMatcherImpl(matcher).match("exacz")); + EXPECT_FALSE(Matchers::StringMatcherImpl(matcher).match("other")); +} + +TEST(StringMatcher, PrefixMatchIgnoreCase) { + envoy::type::matcher::v3::StringMatcher matcher; + matcher.set_prefix("prefix"); + EXPECT_TRUE(Matchers::StringMatcherImpl(matcher).match("prefix-abc")); + EXPECT_FALSE(Matchers::StringMatcherImpl(matcher).match("PREFIX-ABC")); + EXPECT_FALSE(Matchers::StringMatcherImpl(matcher).match("prefiz-abc")); + EXPECT_FALSE(Matchers::StringMatcherImpl(matcher).match("other")); + + matcher.set_ignore_case(true); + EXPECT_TRUE(Matchers::StringMatcherImpl(matcher).match("prefix-abc")); + EXPECT_TRUE(Matchers::StringMatcherImpl(matcher).match("PREFIX-ABC")); + EXPECT_FALSE(Matchers::StringMatcherImpl(matcher).match("prefiz-abc")); + EXPECT_FALSE(Matchers::StringMatcherImpl(matcher).match("other")); +} + +TEST(StringMatcher, SuffixMatchIgnoreCase) { + envoy::type::matcher::v3::StringMatcher matcher; + matcher.set_suffix("suffix"); + EXPECT_TRUE(Matchers::StringMatcherImpl(matcher).match("abc-suffix")); + EXPECT_FALSE(Matchers::StringMatcherImpl(matcher).match("ABC-SUFFIX")); + EXPECT_FALSE(Matchers::StringMatcherImpl(matcher).match("abc-suffiz")); + EXPECT_FALSE(Matchers::StringMatcherImpl(matcher).match("other")); + + matcher.set_ignore_case(true); + EXPECT_TRUE(Matchers::StringMatcherImpl(matcher).match("abc-suffix")); + EXPECT_TRUE(Matchers::StringMatcherImpl(matcher).match("ABC-SUFFIX")); + EXPECT_FALSE(Matchers::StringMatcherImpl(matcher).match("abc-suffiz")); + EXPECT_FALSE(Matchers::StringMatcherImpl(matcher).match("other")); +} + TEST(StringMatcher, SafeRegexValue) { envoy::type::matcher::v3::StringMatcher matcher; matcher.mutable_safe_regex()->mutable_google_re2(); @@ -267,6 +315,23 @@ TEST(StringMatcher, SafeRegexValue) { EXPECT_FALSE(Matchers::StringMatcherImpl(matcher).match("bar")); } +TEST(StringMatcher, RegexValueIgnoreCase) { + envoy::type::matcher::v3::StringMatcher matcher; + matcher.set_ignore_case(true); + matcher.set_hidden_envoy_deprecated_regex("foo"); + EXPECT_THROW_WITH_MESSAGE(Matchers::StringMatcherImpl(matcher).match("foo"), EnvoyException, + "ignore_case has no effect for regex."); +} + +TEST(StringMatcher, SafeRegexValueIgnoreCase) { + envoy::type::matcher::v3::StringMatcher matcher; + matcher.set_ignore_case(true); + matcher.mutable_safe_regex()->mutable_google_re2(); + matcher.mutable_safe_regex()->set_regex("foo"); + EXPECT_THROW_WITH_MESSAGE(Matchers::StringMatcherImpl(matcher).match("foo"), EnvoyException, + "ignore_case has no effect for safe_regex."); +} + TEST(LowerCaseStringMatcher, MatchExactValue) { envoy::type::matcher::v3::StringMatcher matcher; matcher.set_exact("Foo");