Bläddra i källkod

Replace the Content-Type along with the content

set_content_provider() and file content added their Content-Type next to
the one an earlier set_content() had set, so the response went out with
two, and clients took the first. Each content setter now replaces it. A
Content-Type set after set_file_content() is used for the file.
yhirose 4 dagar sedan
förälder
incheckning
be5043a240
2 ändrade filer med 37 tillägg och 19 borttagningar
  1. 15 10
      httplib.h
  2. 22 9
      test/test.cc

+ 15 - 10
httplib.h

@@ -12068,6 +12068,12 @@ inline void clear_content(Response &res) {
   clear_content_provider(res);
 }
 
+// The Content-Type of the old content goes with it.
+inline void set_content_type(Response &res, const std::string &content_type) {
+  res.headers.erase("Content-Type");
+  res.set_header("Content-Type", content_type);
+}
+
 // `body` is public, so it can be assigned directly after a provider or a file
 // was set. It came last, so it wins.
 inline void prefer_assigned_body(Response &res) {
@@ -12189,10 +12195,7 @@ inline void Response::set_content(const char *s, size_t n,
                                   const std::string &content_type) {
   body.assign(s, n); // Before the releaser runs; it may own `s`.
   detail::clear_content_provider(*this);
-
-  auto rng = headers.equal_range("Content-Type");
-  headers.erase(rng.first, rng.second);
-  set_header("Content-Type", content_type);
+  detail::set_content_type(*this, content_type);
 }
 
 inline void Response::set_content(const std::string &s,
@@ -12204,18 +12207,15 @@ inline void Response::set_content(std::string &&s,
                                   const std::string &content_type) {
   body = std::move(s);
   detail::clear_content_provider(*this);
-
-  auto rng = headers.equal_range("Content-Type");
-  headers.erase(rng.first, rng.second);
-  set_header("Content-Type", content_type);
+  detail::set_content_type(*this, content_type);
 }
 
 inline void Response::set_content_provider(
     size_t in_length, const std::string &content_type, ContentProvider provider,
     ContentProviderResourceReleaser resource_releaser) {
   detail::clear_content(*this);
+  detail::set_content_type(*this, content_type);
 
-  set_header("Content-Type", content_type);
   content_length_ = in_length;
   if (in_length > 0) { content_provider_ = std::move(provider); }
   content_provider_resource_releaser_ = std::move(resource_releaser);
@@ -12225,8 +12225,8 @@ inline void Response::set_content_provider(
     const std::string &content_type, ContentProviderWithoutLength provider,
     ContentProviderResourceReleaser resource_releaser) {
   detail::clear_content(*this);
+  detail::set_content_type(*this, content_type);
 
-  set_header("Content-Type", content_type);
   content_provider_ = detail::ContentProviderAdapter(std::move(provider));
   content_provider_resource_releaser_ = std::move(resource_releaser);
 }
@@ -12242,6 +12242,7 @@ inline void Response::set_chunked_content_provider(
 inline void Response::set_file_content(const std::string &path,
                                        const std::string &content_type) {
   detail::clear_content(*this);
+  headers.erase("Content-Type");
 
   file_content_path_ = path;
   file_content_content_type_ = content_type;
@@ -15191,6 +15192,10 @@ Server::process_request(Stream &strm, const std::string &remote_addr,
         file_open_error = true;
       } else {
         auto content_type = res.file_content_content_type_;
+        if (content_type.empty()) {
+          // One the handler set after set_file_content()
+          content_type = res.get_header_value("Content-Type");
+        }
         if (content_type.empty()) {
           content_type = detail::find_content_type(
               path, file_extension_and_mimetype_map_, default_file_mimetype_);

+ 22 - 9
test/test.cc

@@ -13463,12 +13463,20 @@ TEST(ResponseContentSourceTest, LastContentSetterWins) {
   });
   svr.Get("/body-then-provider", [&](const Request &, Response &res) {
     res.set_content(std::string(100, 'B'), "text/plain");
-    res.set_content_provider(3, "text/plain", abc);
+    res.set_content_provider(3, "text/x-provider", abc);
   });
   svr.Get("/provider-then-body", [&](const Request &, Response &res) {
-    res.set_content_provider(3, "text/plain", abc);
+    res.set_content_provider(3, "text/x-provider", abc);
     res.set_content(std::string(100, 'B'), "text/plain");
   });
+  svr.Get("/provider-then-file", [&](const Request &, Response &res) {
+    res.set_content_provider(3, "text/x-provider", abc);
+    res.set_file_content("./www/dir/test.html", "text/x-file");
+  });
+  svr.Get("/file-then-header", [](const Request &, Response &res) {
+    res.set_file_content("./www/dir/test.html");
+    res.set_header("Content-Type", "text/x-handler");
+  });
   svr.Get("/provider-then-direct-body", [&](const Request &, Response &res) {
     res.set_content_provider(3, "text/plain", abc);
     res.body = "direct";
@@ -13489,7 +13497,8 @@ TEST(ResponseContentSourceTest, LastContentSetterWins) {
 
   Client cli(HOST, port);
 
-  auto expect_body = [&](const char *path, const std::string &body) {
+  auto expect_body = [&](const char *path, const std::string &body,
+                         const std::string &type) {
     // Uncompressed, so Content-Length can be compared with the body.
     auto res = cli.Get(path, Headers{{"Accept-Encoding", ""}});
     ASSERT_TRUE(res) << path << " Error: " << to_string(res.error());
@@ -13498,14 +13507,18 @@ TEST(ResponseContentSourceTest, LastContentSetterWins) {
     EXPECT_EQ(std::to_string(body.size()),
               res->get_header_value("Content-Length"))
         << path;
+    EXPECT_EQ(1u, res->get_header_value_count("Content-Type")) << path;
+    EXPECT_EQ(type, res->get_header_value("Content-Type")) << path;
   };
 
-  expect_body("/file-then-body", "body");
-  expect_body("/body-then-file", "test.html");
-  expect_body("/body-then-provider", "abc");
-  expect_body("/provider-then-body", std::string(100, 'B'));
-  expect_body("/provider-then-direct-body", "direct");
-  expect_body("/file-then-direct-body", "direct");
+  expect_body("/file-then-body", "body", "text/plain");
+  expect_body("/body-then-file", "test.html", "text/html");
+  expect_body("/body-then-provider", "abc", "text/x-provider");
+  expect_body("/provider-then-body", std::string(100, 'B'), "text/plain");
+  expect_body("/provider-then-file", "test.html", "text/x-file");
+  expect_body("/file-then-header", "test.html", "text/x-handler");
+  expect_body("/provider-then-direct-body", "direct", "text/plain");
+  expect_body("/file-then-direct-body", "direct", "text/plain");
 
   // Ranges are checked against the body, not the dropped provider.
   {