Procházet zdrojové kódy

Close the connection after an early answer to Expect: 100-continue

When the final response came before 100 Continue, the body was never
sent but the connection was kept, so the server could read the next
request as that body. Close it after the response.
yhirose před 6 dny
rodič
revize
5d10305218
2 změnil soubory, kde provedl 63 přidání a 1 odebrání
  1. 6 1
      httplib.h
  2. 57 0
      test/test.cc

+ 6 - 1
httplib.h

@@ -16596,8 +16596,13 @@ inline bool ClientImpl::process_request(Stream &strm, Request &req,
         output_error_log(error, &req);
         return false;
       }
+    } else {
+      // A final response came first, so the body this request declared was
+      // never sent, and the server may still be waiting for it. The
+      // connection cannot carry another request.
+      std::lock_guard<std::mutex> guard(socket_mutex_);
+      socket_should_be_closed_when_request_is_done_ = true;
     }
-    // If not 100 Continue, server returned an error; proceed with that response
   }
 
   // Body

+ 57 - 0
test/test.cc

@@ -13535,6 +13535,63 @@ TEST(ClientPayloadTest, DoesNotReserveTheClaimedLength) {
   EXPECT_FALSE(cli.Get("/"));
 }
 
+TEST(ExpectContinueTest, EarlyFinalResponseClosesConnection) {
+  std::atomic<bool> closed_by_client{false};
+  auto server_thread = std::thread([&] {
+    auto srv = ::socket(AF_INET, SOCK_STREAM, 0);
+    default_socket_options(srv);
+    sockaddr_in addr{};
+    addr.sin_family = AF_INET;
+    addr.sin_port = htons(static_cast<uint16_t>(PORT));
+    ::inet_pton(AF_INET, "127.0.0.1", &addr.sin_addr);
+    int opt = 1;
+    ::setsockopt(srv, SOL_SOCKET, SO_REUSEADDR,
+#ifdef _WIN32
+                 reinterpret_cast<const char *>(&opt),
+#else
+                 &opt,
+#endif
+                 sizeof(opt));
+    ::bind(srv, reinterpret_cast<sockaddr *>(&addr), sizeof(addr));
+    ::listen(srv, 1);
+
+    auto cli = ::accept(srv, nullptr, nullptr);
+    if (cli != INVALID_SOCKET) {
+      detail::set_socket_opt_time(cli, SOL_SOCKET, SO_RCVTIMEO, 3, 0);
+
+      // Answer the head without asking for the body, and keep the connection.
+      std::string head;
+      char c;
+      while (head.find("\r\n\r\n") == std::string::npos &&
+             ::recv(cli, &c, 1, 0) == 1) {
+        head += c;
+      }
+      std::string res =
+          "HTTP/1.1 401 Unauthorized\r\nContent-Length: 0\r\n\r\n";
+      ::send(cli, res.data(), static_cast<int>(res.size()), 0);
+
+      // Bytes here would be read as the body the first request declared.
+      char buf[256];
+      closed_by_client = ::recv(cli, buf, sizeof(buf), 0) == 0;
+      detail::close_socket(cli);
+    }
+    detail::close_socket(srv);
+  });
+  std::this_thread::sleep_for(std::chrono::milliseconds(100));
+
+  Client cli("127.0.0.1", PORT);
+  cli.set_keep_alive(true);
+  cli.set_read_timeout(1, 0);
+  // Long enough for the client to send Expect: 100-continue.
+  auto res = cli.Post("/upload", std::string(2048, 'B'), "text/plain");
+  ASSERT_TRUE(res) << "Error: " << to_string(res.error());
+  EXPECT_EQ(StatusCode::Unauthorized_401, res->status);
+  cli.Get("/second");
+
+  server_thread.join();
+  EXPECT_TRUE(closed_by_client);
+}
+
 #ifdef CPPHTTPLIB_ZLIB_SUPPORT
 // `payload_max_length` is not enforced on decompressed body in ContentReader
 // path.