diff --git a/source/common/formatter/substitution_formatter.cc b/source/common/formatter/substitution_formatter.cc index 1072ae132f6f..bd2dacc2f93a 100644 --- a/source/common/formatter/substitution_formatter.cc +++ b/source/common/formatter/substitution_formatter.cc @@ -300,6 +300,15 @@ std::vector SubstitutionFormatParser::parse(const std::str current_token = ""; } + // escape '%%' + if (format.length() > pos+1) { + if (format[pos+1] == '%') { + current_token += '%'; + pos++; + continue; + } + } + std::smatch m; const std::string search_space = format.substr(pos); if (!std::regex_search(search_space, m, command_w_args_regex)) { diff --git a/test/common/formatter/substitution_formatter_test.cc b/test/common/formatter/substitution_formatter_test.cc index 1c3d9edd35b5..0a7ee48082a9 100644 --- a/test/common/formatter/substitution_formatter_test.cc +++ b/test/common/formatter/substitution_formatter_test.cc @@ -2405,8 +2405,7 @@ TEST(SubstitutionFormatterTest, ParserFailures) { "RESP(FIRST)%", "%REQ(valid)% %NOT_VALID%", "%REQ(FIRST?SECOND%", - "%%", - "%%HOSTNAME%PROTOCOL%", + "%HOSTNAME%PROTOCOL%", "%protocol%", "%REQ(TEST):%", "%REQ(TEST):3q4%", @@ -2449,6 +2448,51 @@ TEST(SubstitutionFormatterTest, ParserSuccesses) { } } +TEST(SubstitutionFormatterTest, EmptyFormatParse) { + Http::TestRequestHeaderMapImpl request_headers{{":method", "GET"}, {":path", "/"}}; + Http::TestResponseHeaderMapImpl response_headers; + Http::TestResponseTrailerMapImpl response_trailers; + StreamInfo::MockStreamInfo stream_info; + std::string body; + + auto providers = SubstitutionFormatParser::parse(""); + + EXPECT_EQ(providers.size(), 1); + EXPECT_EQ("", providers[0]->format(request_headers, response_headers, response_trailers, + stream_info, body)); +} + +TEST(SubstitutionFormatterTest, EscapingFormatParse) { + Http::TestRequestHeaderMapImpl request_headers{{":method", "GET"}, {":path", "/"}}; + Http::TestResponseHeaderMapImpl response_headers; + Http::TestResponseTrailerMapImpl response_trailers; + StreamInfo::MockStreamInfo stream_info; + std::string body; + + auto providers = SubstitutionFormatParser::parse("%%"); + + EXPECT_EQ(providers.size(), 1); + EXPECT_EQ("%", providers[0]->format(request_headers, response_headers, response_trailers, + stream_info, body)); +} + +TEST(SubstitutionFormatterTest, FormatterExtension) { + Http::TestRequestHeaderMapImpl request_headers{{":method", "GET"}, {":path", "/"}}; + Http::TestResponseHeaderMapImpl response_headers; + Http::TestResponseTrailerMapImpl response_trailers; + StreamInfo::MockStreamInfo stream_info; + std::string body; + + std::vector commands; + commands.push_back(std::make_unique()); + + auto providers = SubstitutionFormatParser::parse("foo %COMMAND_EXTENSION(x)%", commands); + + EXPECT_EQ(providers.size(), 2); + EXPECT_EQ("TestFormatter", providers[1]->format(request_headers, response_headers, + response_trailers, stream_info, body)); +} + } // namespace } // namespace Formatter } // namespace Envoy