Browse Source

Bound the number of interim responses a client skips

A server sending "102 Processing" without end kept the client reading
them unless max_timeout was set. Skip at most
CPPHTTPLIB_INTERIM_RESPONSE_MAX_COUNT (100) and fail with Error::Read
past it.
yhirose 2 days ago
parent
commit
8ca46a4db5
2 changed files with 32 additions and 1 deletions
  1. 10 1
      httplib.h
  2. 22 0
      test/test.cc

+ 10 - 1
httplib.h

@@ -130,6 +130,10 @@
 #define CPPHTTPLIB_REDIRECT_MAX_COUNT 20
 #endif
 
+#ifndef CPPHTTPLIB_INTERIM_RESPONSE_MAX_COUNT
+#define CPPHTTPLIB_INTERIM_RESPONSE_MAX_COUNT 100
+#endif
+
 #ifndef CPPHTTPLIB_MULTIPART_FORM_DATA_FILE_MAX_COUNT
 #define CPPHTTPLIB_MULTIPART_FORM_DATA_FILE_MAX_COUNT 1024
 #endif
@@ -15438,10 +15442,15 @@ inline bool ClientImpl::read_response_line(Stream &strm, const Request &req,
 
   // Skip interim (1xx) responses along with their header fields, except a 100
   // Continue the caller handles itself for Expect: 100-continue. 101 ends
-  // HTTP on the connection, so it is final.
+  // HTTP on the connection, so it is final. A server sending them without end
+  // would hold the client, so there may be only so many.
+  size_t interim_count = 0;
   while (100 <= res.status && res.status < 200 &&
          res.status != StatusCode::SwitchingProtocol_101 &&
          (skip_100_continue || res.status != StatusCode::Continue_100)) {
+    if (++interim_count > CPPHTTPLIB_INTERIM_RESPONSE_MAX_COUNT) {
+      return false;
+    }
     Headers interim_headers;
     if (!detail::read_headers(strm, interim_headers)) { return false; }
     if (!line_reader.getline()) { return false; } // next response line

+ 22 - 0
test/test.cc

@@ -23031,6 +23031,28 @@ TEST(InterimResponseTest, SkippedWithTheirHeaderFields) {
   }
 }
 
+TEST(InterimResponseTest, OnlySoManyAreSkipped) {
+  const std::string interim = "HTTP/1.1 102 Processing\r\n\r\n";
+  const std::string final_response =
+      "HTTP/1.1 200 OK\r\nContent-Length: 2\r\n\r\nok";
+  std::string interims;
+  for (auto i = 0; i < CPPHTTPLIB_INTERIM_RESPONSE_MAX_COUNT; i++) {
+    interims += interim;
+  }
+
+  with_single_response(interims + final_response, [](Client &cli) {
+    auto res = cli.Get("/");
+    ASSERT_TRUE(res) << "Error: " << to_string(res.error());
+    EXPECT_EQ("ok", res->body);
+  });
+
+  with_single_response(interims + interim + final_response, [](Client &cli) {
+    auto res = cli.Get("/");
+    ASSERT_FALSE(res);
+    EXPECT_EQ(Error::Read, res.error());
+  });
+}
+
 TEST(ExpectContinueTest, EarlyFinalResponseClosesConnection) {
 #ifndef _WIN32
   signal(SIGPIPE, SIG_IGN);