From bf7700d1924e12a27faa5d346a5b8a9f8e163b61 Mon Sep 17 00:00:00 2001 From: Matthew DeVore Date: Fri, 28 Feb 2020 03:31:39 -0800 Subject: [PATCH] Fix exception that occurs with libc++ regex engine (#368) The regex that parses header lines potentially causes an unlimited amount of backtracking, which can cause an exception in the libc++ regex engine. The exception that occurs looks like this and is identical to the message of the exception fixed in https://github.com/yhirose/cpp-httplib/pull/280: libc++abi.dylib: terminating with uncaught exception of type std::__1::regex_error: The complexity of an attempted match against a regular expression exceeded a pre-set level. This commit eliminates the problematic backtracking. --- httplib.h | 2 +- test/test.cc | 58 ++++++++++++++++++++++++++++++++++++++++++++-------- 2 files changed, 51 insertions(+), 9 deletions(-) diff --git a/httplib.h b/httplib.h index 8982d88..dffc58a 100644 --- a/httplib.h +++ b/httplib.h @@ -1764,7 +1764,7 @@ inline bool read_headers(Stream &strm, Headers &headers) { // the left or right side of the header value: // - https://stackoverflow.com/questions/50179659/ // - https://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html - static const std::regex re(R"((.+?):[\t ]*(.+))"); + static const std::regex re(R"(([^:]+):[\t ]*(.+))"); std::cmatch m; if (std::regex_match(line_reader.ptr(), end, m, re)) { diff --git a/test/test.cc b/test/test.cc index 51d3f05..6bdd56c 100644 --- a/test/test.cc +++ b/test/test.cc @@ -2049,7 +2049,8 @@ TEST(ServerRequestParsingTest, TrimWhitespaceFromHeaderValues) { EXPECT_EQ(header_value, "\v bar \e"); } -TEST(ServerRequestParsingTest, ReadHeadersRegexComplexity) { +// Sends a raw request and verifies that there isn't a crash or exception. +static void test_raw_request(const std::string& req) { Server svr; svr.Get("/hi", [&](const Request & /*req*/, Response &res) { res.set_content("ok", "text/plain"); @@ -2066,19 +2067,60 @@ TEST(ServerRequestParsingTest, ReadHeadersRegexComplexity) { std::this_thread::sleep_for(std::chrono::milliseconds(1)); } - // A certain header line causes an exception if the header property is parsed - // naively with a single regex. This occurs with libc++ but not libstdc++. - const std::string req = - "GET /hi HTTP/1.1\r\n" - " : " - " "; - ASSERT_TRUE(send_request(client_read_timeout_sec, req)); svr.stop(); t.join(); EXPECT_TRUE(listen_thread_ok); } +TEST(ServerRequestParsingTest, ReadHeadersRegexComplexity) { + // A certain header line causes an exception if the header property is parsed + // naively with a single regex. This occurs with libc++ but not libstdc++. + test_raw_request( + "GET /hi HTTP/1.1\r\n" + " : " + " " + ); +} + +TEST(ServerRequestParsingTest, ReadHeadersRegexComplexity2) { + // A certain header line causes an exception if the header property *name* is + // parsed with a regular expression starting with "(.+?):" - this is a non- + // greedy matcher and requires backtracking when there are a lot of ":" + // characters. + // This occurs with libc++ but not libstdc++. + test_raw_request( + "GET /hi HTTP/1.1\r\n" + ":-:::::::::::::::::::::::::::-::::::::::::::::::::::::@-&&&&&&&&&&&" + "--:::::::-:::::::::::::::::::::::::::::-:::::::::::::::::@-&&&&&&&&" + "&&&--:::::::-:::::::::::::::::::::::::::::-:::::::::::::::::@-:::::" + "::-:::::::::::::::::@-&&&&&&&&&&&--:::::::-::::::::::::::::::::::::" + ":::::-:::::::::::::::::@-&&&&&&&&&&&--:::::::-:::::::::::::::::::::" + "::::::::-:::::::::::::::::@-&&&&&&&--:::::::-::::::::::::::::::::::" + ":::::::-:::::::::::::::::@-&&&&&&&&&&&--:::::::-:::::::::::::::::::" + "::::::::::-:::::::::::::::::@-&&&&&::::::::::::-:::::::::::::::::@-" + "&&&&&&&&&&&--:::::::-:::::::::::::::::::::::::::::-::::::::::::::::" + ":@-&&&&&&&&&&&--:::::::-:::::::::::::::::::::::::::::-:::::::::::::" + "::::@-&&&&&&&&&&&--:::::::-:::::::::::::::::::::::::::::-::::::@-&&" + "&&&&&&&&&--:::::::-:::::::::::::::::::::::::::::-:::::::::::::::::@" + "::::::-:::::::::::::::::::::::::::::-:::::::::::::::::@-&&&&&&&&&&&" + "--:::::::-:::::::::::::::::::::::::::::-:::::::::::::::::@-&&&&&&&&" + "&&&--:::::::-:::::::::::::::::::::::::::::-:::::::::::::::::@-&&&&&" + "&&&&&&--:::::::-:::::::::::::::::::::::::::::-:::::::::::::::::@-&&" + "&&&&&&&&&--:::::::-:::::::::::::::::::::::::::::-:::::::::::::::::@" + "-&&&&&&&&&&&--:::::::-:::::::::::::::::::::::::::::-:::::::::::::::" + "::@-&&&&&&&&&&&--:::::::-:::::::::::::::::::::::::::::-::::::::::::" + ":::::@-&&&&&&&&&&&::-:::::::::::::::::@-&&&&&&&&&&&--:::::::-::::::" + ":::::::::::::::::::::::-:::::::::::::::::@-&&&&&&&&&&&--:::::::-:::" + "::::::::::::::::::::::::::-:::::::::::::::::@-&&&&&&&&&&&--:::::::-" + ":::::::::::::::::::::::::::::-:::::::::::::::::@-&&&&&&&&&&&---&&:&" + "&&.0------------:-:::::::::::::::::::::::::::::-:::::::::::::::::@-" + "&&&&&&&&&&&--:::::::-:::::::::::::::::::::::::::::-::::::::::::::::" + ":@-&&&&&&&&&&&--:::::::-:::::::::::::::::::::::::::::-:::::::::::::" + "::::@-&&&&&&&&&&&---&&:&&&.0------------O--------\rH PUTHTTP/1.1\r\n" + "&&&%%%"); +} + TEST(ServerStopTest, StopServerWithChunkedTransmission) { Server svr;