Browse Source

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.
Matthew DeVore 5 years ago
parent
commit
bf7700d192
2 changed files with 50 additions and 8 deletions
  1. 1 1
      httplib.h
  2. 49 7
      test/test.cc

+ 1 - 1
httplib.h

@@ -1764,7 +1764,7 @@ inline bool read_headers(Stream &strm, Headers &headers) {
     // the left or right side of the header value:
     // the left or right side of the header value:
     //  - https://stackoverflow.com/questions/50179659/
     //  - https://stackoverflow.com/questions/50179659/
     //  - https://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html
     //  - 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;
     std::cmatch m;
     if (std::regex_match(line_reader.ptr(), end, m, re)) {
     if (std::regex_match(line_reader.ptr(), end, m, re)) {

+ 49 - 7
test/test.cc

@@ -2049,7 +2049,8 @@ TEST(ServerRequestParsingTest, TrimWhitespaceFromHeaderValues) {
   EXPECT_EQ(header_value, "\v bar \e");
   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;
   Server svr;
   svr.Get("/hi", [&](const Request & /*req*/, Response &res) {
   svr.Get("/hi", [&](const Request & /*req*/, Response &res) {
     res.set_content("ok", "text/plain");
     res.set_content("ok", "text/plain");
@@ -2066,17 +2067,58 @@ TEST(ServerRequestParsingTest, ReadHeadersRegexComplexity) {
     std::this_thread::sleep_for(std::chrono::milliseconds(1));
     std::this_thread::sleep_for(std::chrono::milliseconds(1));
   }
   }
 
 
+  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
   // 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++.
   // naively with a single regex. This occurs with libc++ but not libstdc++.
-  const std::string req =
+  test_raw_request(
       "GET /hi HTTP/1.1\r\n"
       "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, 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) {
 TEST(ServerStopTest, StopServerWithChunkedTransmission) {