yhirose 8 years ago
parent
commit
90f9cd40f9
2 changed files with 74 additions and 5 deletions
  1. 44 5
      httplib.h
  2. 30 0
      test/test.cc

+ 44 - 5
httplib.h

@@ -393,16 +393,55 @@ inline socket_t create_client_socket(const char* host, int port)
     });
     });
 }
 }
 
 
-inline bool is_file(const std::string& s)
+inline bool is_file(const std::string& path)
 {
 {
     struct stat st;
     struct stat st;
-    return stat(s.c_str(), &st) >= 0 && S_ISREG(st.st_mode);
+    return stat(path.c_str(), &st) >= 0 && S_ISREG(st.st_mode);
 }
 }
 
 
-inline bool is_dir(const std::string& s)
+inline bool is_dir(const std::string& path)
 {
 {
     struct stat st;
     struct stat st;
-    return stat(s.c_str(), &st) >= 0 && S_ISDIR(st.st_mode);
+    return stat(path.c_str(), &st) >= 0 && S_ISDIR(st.st_mode);
+}
+
+inline bool is_valid_path(const std::string& path) {
+    size_t level = 0;
+    size_t i = 0;
+
+    // Skip slash
+    while (i < path.size() && path[i] == '/') {
+        i++;
+    }
+
+    while (i < path.size()) {
+        // Read component
+        auto beg = i;
+        while (i < path.size() && path[i] != '/') {
+            i++;
+        }
+
+        auto len = i - beg;
+        assert(len > 0);
+
+        if (!path.compare(beg, len, ".")) {
+            ;
+        } else if (!path.compare(beg, len, "..")) {
+            if (level == 0) {
+                return false;
+            }
+            level--;
+        } else {
+            level++;
+        }
+
+        // Skip slash
+        while (i < path.size() && path[i] == '/') {
+            i++;
+        }
+    }
+
+    return true;
 }
 }
 
 
 inline void read_file(const std::string& path, std::string& out)
 inline void read_file(const std::string& path, std::string& out)
@@ -942,7 +981,7 @@ inline bool Server::read_request_line(Stream& strm, Request& req)
 
 
 inline bool Server::handle_file_request(Request& req, Response& res)
 inline bool Server::handle_file_request(Request& req, Response& res)
 {
 {
-    if (!base_dir_.empty()) {
+    if (!base_dir_.empty() && detail::is_valid_path(req.path)) {
         std::string path = base_dir_ + req.path;
         std::string path = base_dir_ + req.path;
 
 
         if (!path.empty() && path.back() == '/') {
         if (!path.empty() && path.back() == '/') {

+ 30 - 0
test/test.cc

@@ -293,6 +293,36 @@ TEST_F(ServerTest, GetMethodDirTest)
 	EXPECT_EQ("test.html", res->body);
 	EXPECT_EQ("test.html", res->body);
 }
 }
 
 
+TEST_F(ServerTest, GetMethodDirTestWithDoubleDots)
+{
+	auto res = cli_.get("/dir/../dir/test.html");
+	ASSERT_TRUE(res != nullptr);
+	EXPECT_EQ(200, res->status);
+	EXPECT_EQ("text/html", res->get_header_value("Content-Type"));
+	EXPECT_EQ("test.html", res->body);
+}
+
+TEST_F(ServerTest, GetMethodInvalidPath)
+{
+	auto res = cli_.get("/dir/../test.html");
+	ASSERT_TRUE(res != nullptr);
+	EXPECT_EQ(404, res->status);
+}
+
+TEST_F(ServerTest, GetMethodOutOfBaseDir)
+{
+	auto res = cli_.get("/../www/dir/test.html");
+	ASSERT_TRUE(res != nullptr);
+	EXPECT_EQ(404, res->status);
+}
+
+TEST_F(ServerTest, GetMethodOutOfBaseDir2)
+{
+	auto res = cli_.get("/dir/../../www/dir/test.html");
+	ASSERT_TRUE(res != nullptr);
+	EXPECT_EQ(404, res->status);
+}
+
 TEST_F(ServerTest, InvalidBaseDir)
 TEST_F(ServerTest, InvalidBaseDir)
 {
 {
 	EXPECT_EQ(false, svr_.set_base_dir("invalid_dir"));
 	EXPECT_EQ(false, svr_.set_base_dir("invalid_dir"));