瀏覽代碼

Skip every interim response, with its header fields

Only 100 Continue was skipped, and only without header fields, so a 103
Early Hints was taken for the final response and a 100 with fields threw
the parsing off. Skip every 1xx but 101, along with its header fields.
yhirose 1 天之前
父節點
當前提交
ce7ecb633b
共有 2 個文件被更改,包括 67 次插入 和 3 次删除
  1. 7 3
      httplib.h
  2. 60 0
      test/test.cc

+ 7 - 3
httplib.h

@@ -15280,9 +15280,13 @@ inline bool ClientImpl::read_response_line(Stream &strm, const Request &req,
     return req.method == "CONNECT";
   }
 
-  // Ignore '100 Continue' (only when not using Expect: 100-continue explicitly)
-  while (skip_100_continue && res.status == StatusCode::Continue_100) {
-    if (!line_reader.getline()) { return false; } // CRLF
+  // Skip interim responses, such as 100 Continue (unless Expect:
+  // 100-continue is handled by the caller) and 103 Early Hints, along with
+  // their header fields. 101 ends HTTP on the connection, so it is final.
+  while (skip_100_continue && 100 <= res.status && res.status < 200 &&
+         res.status != StatusCode::SwitchingProtocol_101) {
+    Headers interim_headers;
+    if (!detail::read_headers(strm, interim_headers)) { return false; }
     if (!line_reader.getline()) { return false; } // next response line
 
     if (!detail::parse_status_line(line_reader.ptr(), res.version, res.status,

+ 60 - 0
test/test.cc

@@ -13535,6 +13535,66 @@ TEST(ClientPayloadTest, DoesNotReserveTheClaimedLength) {
   EXPECT_FALSE(cli.Get("/"));
 }
 
+// Accepts one connection on 127.0.0.1:port, reads the request head and sends
+// back the given bytes, which httplib::Server could not be made to send.
+static void serve_raw_once(int port, const std::string &response) {
+  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);
+    std::string head;
+    char c;
+    while (head.find("\r\n\r\n") == std::string::npos &&
+           ::recv(cli, &c, 1, 0) == 1) {
+      head += c;
+    }
+    ::send(cli, response.data(), static_cast<int>(response.size()), 0);
+    detail::close_socket(cli);
+  }
+  detail::close_socket(srv);
+}
+
+TEST(InterimResponseTest, SkippedWithTheirHeaderFields) {
+  const char *responses[] = {
+      "HTTP/1.1 103 Early Hints\r\n"
+      "Link: </style.css>; rel=preload\r\n"
+      "\r\n"
+      "HTTP/1.1 200 OK\r\nContent-Length: 2\r\n\r\nok",
+      "HTTP/1.1 100 Continue\r\n"
+      "X-Note: interim\r\n"
+      "\r\n"
+      "HTTP/1.1 200 OK\r\nContent-Length: 2\r\n\r\nok",
+  };
+
+  for (auto response : responses) {
+    std::thread t([&]() { serve_raw_once(PORT, response); });
+    std::this_thread::sleep_for(std::chrono::milliseconds(100));
+
+    Client cli("127.0.0.1", PORT);
+    auto res = cli.Get("/");
+    t.join();
+    ASSERT_TRUE(res) << "Error: " << to_string(res.error());
+    EXPECT_EQ(StatusCode::OK_200, res->status);
+    EXPECT_EQ("ok", res->body);
+  }
+}
+
 TEST(ExpectContinueTest, EarlyFinalResponseClosesConnection) {
   std::atomic<bool> closed_by_client{false};
   auto server_thread = std::thread([&] {