Ver Fonte

Close the connection after an ambiguous Transfer-Encoding

RFC 9112 6.1 has a server close the connection after answering a
request with both Transfer-Encoding and Content-Length, or with
Transfer-Encoding in HTTP/1.0. Close it, and send Connection: close
whenever the connection closes after a response.
yhirose há 1 dia atrás
pai
commit
58957ae4da
2 ficheiros alterados com 36 adições e 4 exclusões
  1. 13 4
      httplib.h
  2. 23 0
      test/test.cc

+ 13 - 4
httplib.h

@@ -14761,6 +14761,13 @@ Server::process_request(Stream &strm, const std::string &remote_addr,
     return write_response(strm, close_connection, req, res);
   }
 
+  // RFC 9112 §6.1: answer a request that also has a Content-Length, or an
+  // HTTP/1.0 one, by its Transfer-Encoding, but close the connection after.
+  if (req.has_header("Transfer-Encoding") &&
+      (req.has_header("Content-Length") || req.version == "HTTP/1.0")) {
+    connection_closed = true;
+  }
+
   // RFC 9112 §3.2: Reject more than one Host field line or an invalid Host
   // value, so that every recipient of the request sees the same authority.
   if (req.get_header_value_count("Host") > 1 ||
@@ -14990,6 +14997,8 @@ Server::process_request(Stream &strm, const std::string &remote_addr,
     }
   }
 #endif
+  // The response says so when this connection closes after it.
+  auto close = close_connection || connection_closed;
   auto ret = false;
   if (routed) {
     if (res.status == -1) {
@@ -15023,17 +15032,17 @@ Server::process_request(Stream &strm, const std::string &remote_addr,
     }
 
     if (file_open_error) {
-      ret = write_response(strm, close_connection, req, res);
+      ret = write_response(strm, close, req, res);
     } else if (detail::range_error(req, res)) {
       detail::clear_content(res);
       res.status = StatusCode::RangeNotSatisfiable_416;
-      ret = write_response(strm, close_connection, req, res);
+      ret = write_response(strm, close, req, res);
     } else {
-      ret = write_response_with_content(strm, close_connection, req, res);
+      ret = write_response_with_content(strm, close, req, res);
     }
   } else {
     if (res.status == -1) { res.status = StatusCode::NotFound_404; }
-    ret = write_response(strm, close_connection, req, res);
+    ret = write_response(strm, close, req, res);
   }
 
   // Drain any unconsumed framed body to prevent request smuggling on

+ 23 - 0
test/test.cc

@@ -10836,6 +10836,29 @@ TEST(ServerRequestParsingTest, ExcessiveWhitespaceInUnparsableHeaderLine) {
                    "\r\n");
 }
 
+TEST(ServerRequestParsingTest, AmbiguousTransferEncodingClosesConnection) {
+  // RFC 9112 §6.1
+  const char *requests[] = {
+      "PUT /put_hi HTTP/1.1\r\n"
+      "Content-Length: 0\r\n"
+      "Transfer-Encoding: chunked\r\n"
+      "\r\n"
+      "0\r\n\r\n",
+      "PUT /put_hi HTTP/1.0\r\n"
+      "Connection: keep-alive\r\n"
+      "Transfer-Encoding: chunked\r\n"
+      "\r\n"
+      "0\r\n\r\n",
+  };
+
+  for (auto request : requests) {
+    std::string out;
+    test_raw_request(request, &out);
+    EXPECT_EQ("HTTP/1.1 200 OK", out.substr(0, 15)) << request;
+    EXPECT_NE(std::string::npos, out.find("Connection: close")) << request;
+  }
+}
+
 TEST(ServerRequestParsingTest, InvalidFirstChunkLengthInRequest) {
   std::string out;