Преглед изворни кода

Treat ':' as a path separator in sanitize_filename on Windows (#2651)

On Windows a ':' ends a drive prefix or starts an NTFS stream name, so a filename such as "C:.." or "a.txt:stream" survived sanitize_filename and could replace the upload directory or write to an alternate data stream. Cut at ':' on Windows as well.
metsw24-max пре 1 дан
родитељ
комит
578e4815b7
3 измењених фајлова са 21 додато и 2 уклоњено
  1. 1 1
      README.md
  2. 6 1
      httplib.h
  3. 14 0
      test/test.cc

+ 1 - 1
README.md

@@ -742,7 +742,7 @@ svr.Post("/multipart", [&](const Request& req, Response& res) {
 auto safe = httplib::sanitize_filename(file.filename);
 auto safe = httplib::sanitize_filename(file.filename);
 ```
 ```
 
 
-This function keeps only the part after the last `/` or `\`, removes null bytes, and trims leading and trailing spaces and tabs. It returns an empty string if what is left is empty, `.` or `..`.
+This function keeps only the part after the last `/` or `\` (or, on Windows, `:`, which starts a drive prefix such as `C:` or an NTFS stream name), removes null bytes, and trims leading and trailing spaces and tabs. It returns an empty string if what is left is empty, `.` or `..`.
 
 
 > [!NOTE]
 > [!NOTE]
 > `name` and `filename` are read as quoted-strings (RFC 9110 §5.6.4), where `\"` and `\\` stand for `"` and `\`. Browsers send a backslash as it is, so from a browser, a value with `\\` in it arrives with a single `\`, and one that ends in `\` is not read correctly.
 > `name` and `filename` are read as quoted-strings (RFC 9110 §5.6.4), where `\"` and `\\` stand for `"` and `\`. Browsers send a backslash as it is, so from a browser, a value with `\\` in it arrives with a single `\`, and one that ends in `\` is not read correctly.

+ 6 - 1
httplib.h

@@ -11975,8 +11975,13 @@ inline std::string decode_query_component(const std::string &component,
 }
 }
 
 
 inline std::string sanitize_filename(const std::string &filename) {
 inline std::string sanitize_filename(const std::string &filename) {
-  // Extract basename: find the last path separator (/ or \)
+  // Extract basename: find the last path separator (/ or \) and, on Windows,
+  // ':', which ends a drive prefix ("C:..") or names an NTFS stream ("a:b")
+#ifdef _WIN32
+  auto pos = filename.find_last_of("/\\:");
+#else
   auto pos = filename.find_last_of("/\\");
   auto pos = filename.find_last_of("/\\");
+#endif
   auto result =
   auto result =
       (pos != std::string::npos) ? filename.substr(pos + 1) : filename;
       (pos != std::string::npos) ? filename.substr(pos + 1) : filename;
 
 

+ 14 - 0
test/test.cc

@@ -508,6 +508,20 @@ TEST(SanitizeFilenameTest, VariousPatterns) {
   EXPECT_EQ("", httplib::sanitize_filename("   "));
   EXPECT_EQ("", httplib::sanitize_filename("   "));
 }
 }
 
 
+// On Windows a ':' ends a drive prefix or names an NTFS stream, so what comes
+// before it is dropped; anywhere else it is an ordinary file name character.
+TEST(SanitizeFilenameTest, ColonEndsPrefixOnlyOnWindows) {
+#ifdef _WIN32
+  EXPECT_EQ("", httplib::sanitize_filename("C:.."));
+  EXPECT_EQ("evil.dll", httplib::sanitize_filename("C:evil.dll"));
+  EXPECT_EQ("stream", httplib::sanitize_filename("a.txt:stream"));
+#else
+  EXPECT_EQ("C:..", httplib::sanitize_filename("C:.."));
+  EXPECT_EQ("C:evil.dll", httplib::sanitize_filename("C:evil.dll"));
+  EXPECT_EQ("a.txt:stream", httplib::sanitize_filename("a.txt:stream"));
+#endif
+}
+
 // Forward declaration (see the base64_encode note below): split builds move the
 // Forward declaration (see the base64_encode note below): split builds move the
 // definition into httplib.cc, so re-declaring it here lets the test link.
 // definition into httplib.cc, so re-declaring it here lets the test link.
 namespace httplib {
 namespace httplib {