diff --git a/httplib.h b/httplib.h index 37491fdf..a2ca27f4 100644 --- a/httplib.h +++ b/httplib.h @@ -13340,16 +13340,19 @@ inline bool Server::set_mount_point(const std::string &mount_point, if (stat.is_dir()) { std::string mnt = !mount_point.empty() ? mount_point : "/"; if (!mnt.empty() && mnt[0] == '/') { + // Without the resolved directory, a file could not be checked to be + // inside it. std::string resolved_base; - if (detail::canonicalize_path(dir.c_str(), resolved_base)) { -#if defined(_WIN32) - if (resolved_base.back() != '\\' && resolved_base.back() != '/') { - resolved_base += '\\'; - } -#else - if (resolved_base.back() != '/') { resolved_base += '/'; } -#endif + if (!detail::canonicalize_path(dir.c_str(), resolved_base)) { + return false; } +#if defined(_WIN32) + if (resolved_base.back() != '\\' && resolved_base.back() != '/') { + resolved_base += '\\'; + } +#else + if (resolved_base.back() != '/') { resolved_base += '/'; } +#endif base_dirs_.push_back( {std::move(mnt), dir, std::move(resolved_base), std::move(headers)}); return true; @@ -14017,15 +14020,17 @@ inline bool Server::handle_file_request(Request &req, Response &res) { if (path.back() == '/') { path += "index.html"; } // Defense-in-depth: is_valid_path blocks ".." traversal in the URL, - // but symlinks/junctions can still escape the base directory. - if (!entry.resolved_base_dir.empty()) { - std::string resolved_path; - if (detail::canonicalize_path(path.c_str(), resolved_path) && - !detail::is_path_within_base(resolved_path, - entry.resolved_base_dir)) { - res.status = StatusCode::Forbidden_403; - return true; - } + // but symlinks/junctions can still escape the base directory. A path + // that cannot be resolved, which includes one that does not exist, is + // not served from here. + std::string resolved_path; + if (!detail::canonicalize_path(path.c_str(), resolved_path)) { + continue; + } + if (!detail::is_path_within_base(resolved_path, + entry.resolved_base_dir)) { + res.status = StatusCode::Forbidden_403; + return true; } detail::FileStat stat(path); diff --git a/test/test.cc b/test/test.cc index 9c9b9e44..4a55e44d 100644 --- a/test/test.cc +++ b/test/test.cc @@ -11720,6 +11720,32 @@ TEST(MountTest, Unmount) { EXPECT_EQ(StatusCode::NotFound_404, res->status); } +TEST(MountTest, MissingFileFallsThroughToHandlers) { + Server svr; + ASSERT_TRUE(svr.set_mount_point("/", "./www")); + svr.Get("/api/hello", [](const Request &, Response &res) { + res.set_content("api", "text/plain"); + }); + + auto listen_thread = std::thread([&svr]() { svr.listen(HOST, PORT); }); + auto se = detail::scope_exit([&] { + svr.stop(); + listen_thread.join(); + ASSERT_FALSE(svr.is_running()); + }); + svr.wait_until_ready(); + + Client cli(HOST, PORT); + auto res = cli.Get("/dir/test.html"); + ASSERT_TRUE(res) << "Error: " << to_string(res.error()); + EXPECT_EQ(StatusCode::OK_200, res->status); + + res = cli.Get("/api/hello"); + ASSERT_TRUE(res) << "Error: " << to_string(res.error()); + EXPECT_EQ(StatusCode::OK_200, res->status); + EXPECT_EQ("api", res->body); +} + TEST(MountTest, PathSegmentBoundary) { Server svr;