diff --git a/httplib.h b/httplib.h index abb39ca..09d3225 100644 --- a/httplib.h +++ b/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; - 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; - 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) @@ -942,7 +981,7 @@ inline bool Server::read_request_line(Stream& strm, Request& req) 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; if (!path.empty() && path.back() == '/') { diff --git a/test/test.cc b/test/test.cc index b27e72b..e2deb5d 100644 --- a/test/test.cc +++ b/test/test.cc @@ -293,6 +293,36 @@ TEST_F(ServerTest, GetMethodDirTest) 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) { EXPECT_EQ(false, svr_.set_base_dir("invalid_dir"));