Просмотр исходного кода

Normalize the extension of a user MIME type mapping when it is set

set_file_extension_and_mimetype_mapping() kept the extension as given,
and a file's extension was looked up as it was, then in lowercase, so a
mapping given as ".pdf" never matched and one given as "PDF" matched
only files ending in ".PDF". The extension is now lowercased and loses
a leading '.' when it is set, as Apache's AddType does, and a file's
extension is looked up in lowercase only.
yhirose 5 дней назад
Родитель
Сommit
5db950e1e1
3 измененных файлов с 34 добавлено и 8 удалено
  1. 1 1
      README.md
  2. 7 7
      httplib.h
  3. 26 0
      test/test.cc

+ 1 - 1
README.md

@@ -415,7 +415,7 @@ svr.set_file_extension_and_mimetype_mapping("cpp", "text/x-c");
 svr.set_file_extension_and_mimetype_mapping("hh", "text/x-h");
 svr.set_file_extension_and_mimetype_mapping("hh", "text/x-h");
 ```
 ```
 
 
-Extensions match regardless of case, so `photo.JPG` is served as `image/jpeg`. A user mapping is looked up by the exact extension first, then by the extension in lowercase.
+Extensions match regardless of case, so `photo.JPG` is served as `image/jpeg`. The extension of a user mapping can be given in any case, with or without a leading `.`.
 
 
 The following are built-in mappings:
 The following are built-in mappings:
 
 

+ 7 - 7
httplib.h

@@ -7748,13 +7748,10 @@ inline std::string
 find_content_type(const std::string &path,
 find_content_type(const std::string &path,
                   const std::map<std::string, std::string> &user_data,
                   const std::map<std::string, std::string> &user_data,
                   const std::string &default_content_type) {
                   const std::string &default_content_type) {
-  auto ext = file_extension(path);
+  // Extensions are matched regardless of case, as web servers do
+  auto ext = case_ignore::to_lower(file_extension(path));
 
 
-  // Extensions are matched regardless of case, as web servers do, with an
-  // exact match to a user mapping first
-  auto lower_ext = case_ignore::to_lower(ext);
   auto it = user_data.find(ext);
   auto it = user_data.find(ext);
-  if (it == user_data.end()) { it = user_data.find(lower_ext); }
   if (it != user_data.end()) { return it->second; }
   if (it != user_data.end()) { return it->second; }
 
 
   static const struct {
   static const struct {
@@ -7813,7 +7810,7 @@ find_content_type(const std::string &path,
   };
   };
 
 
   for (const auto &t : types) {
   for (const auto &t : types) {
-    if (lower_ext == t.ext) { return t.type; }
+    if (ext == t.ext) { return t.type; }
   }
   }
   return default_content_type;
   return default_content_type;
 }
 }
@@ -13736,7 +13733,10 @@ inline bool Server::remove_mount_point(const std::string &mount_point) {
 inline Server &
 inline Server &
 Server::set_file_extension_and_mimetype_mapping(const std::string &ext,
 Server::set_file_extension_and_mimetype_mapping(const std::string &ext,
                                                 const std::string &mime) {
                                                 const std::string &mime) {
-  file_extension_and_mimetype_map_[ext] = mime;
+  // Keyed as find_content_type() looks it up
+  auto key = detail::case_ignore::to_lower(ext);
+  if (!key.empty() && key[0] == '.') { key.erase(0, 1); }
+  file_extension_and_mimetype_map_[key] = mime;
   return *this;
   return *this;
 }
 }
 
 

+ 26 - 0
test/test.cc

@@ -12641,6 +12641,32 @@ TEST(MountTest, AliasedPathDoesNotPassPreRoutingGuard) {
   }
   }
 }
 }
 
 
+TEST(UserDefinedMIMETypeMappingTest, IgnoresCaseAndLeadingDot) {
+  Server svr;
+  svr.set_mount_point("/", "./www");
+  svr.set_file_extension_and_mimetype_mapping(".abcde", "text/abcde");
+  svr.set_file_extension_and_mimetype_mapping("HTML", "text/x-html");
+
+  auto port = svr.bind_to_any_port(HOST);
+  ASSERT_GT(port, 0);
+  thread t = thread([&] { svr.listen_after_bind(); });
+  auto se = detail::scope_exit([&] {
+    svr.stop();
+    t.join();
+    ASSERT_FALSE(svr.is_running());
+  });
+  svr.wait_until_ready();
+
+  Client cli(HOST, port);
+  auto res = cli.Get("/dir/test.abcde");
+  ASSERT_TRUE(res) << "Error: " << to_string(res.error());
+  EXPECT_EQ("text/abcde", res->get_header_value("Content-Type"));
+
+  res = cli.Get("/dir/test.html");
+  ASSERT_TRUE(res) << "Error: " << to_string(res.error());
+  EXPECT_EQ("text/x-html", res->get_header_value("Content-Type"));
+}
+
 #ifdef _WIN32
 #ifdef _WIN32
 // Issue #2435: mmap::open() must succeed even when another handle holds
 // Issue #2435: mmap::open() must succeed even when another handle holds
 // the file open for writing (e.g. an active log file).
 // the file open for writing (e.g. an active log file).