mirror of
https://github.com/yhirose/cpp-httplib.git
synced 2026-10-19 11:19:36 +00:00
Serve no file whose path cannot be resolved
When the real path of a file under a mount point could not be resolved, the check that it stays inside the mount point was skipped and the file was served. Do not serve it from there, and refuse a mount point whose directory cannot be resolved.
This commit is contained in:
1 parent
455166314b
commit
eeaae4bc3c
2 files changed
+48
-17
No files matched your search
@@ -13340,16 +13340,19 @@ inline bool Server::set_mount_point(const std::string &mount_point,
|
|||||||
if (stat.is_dir()) {
|
if (stat.is_dir()) {
|
||||||
std::string mnt = !mount_point.empty() ? mount_point : "/";
|
std::string mnt = !mount_point.empty() ? mount_point : "/";
|
||||||
if (!mnt.empty() && mnt[0] == '/') {
|
if (!mnt.empty() && mnt[0] == '/') {
|
||||||
|
// Without the resolved directory, a file could not be checked to be
|
||||||
|
// inside it.
|
||||||
std::string resolved_base;
|
std::string resolved_base;
|
||||||
if (detail::canonicalize_path(dir.c_str(), resolved_base)) {
|
if (!detail::canonicalize_path(dir.c_str(), resolved_base)) {
|
||||||
#if defined(_WIN32)
|
return false;
|
||||||
if (resolved_base.back() != '\\' && resolved_base.back() != '/') {
|
|
||||||
resolved_base += '\\';
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
if (resolved_base.back() != '/') { resolved_base += '/'; }
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
#if defined(_WIN32)
|
||||||
|
if (resolved_base.back() != '\\' && resolved_base.back() != '/') {
|
||||||
|
resolved_base += '\\';
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
if (resolved_base.back() != '/') { resolved_base += '/'; }
|
||||||
|
#endif
|
||||||
base_dirs_.push_back(
|
base_dirs_.push_back(
|
||||||
{std::move(mnt), dir, std::move(resolved_base), std::move(headers)});
|
{std::move(mnt), dir, std::move(resolved_base), std::move(headers)});
|
||||||
return true;
|
return true;
|
||||||
@@ -14017,15 +14020,17 @@ inline bool Server::handle_file_request(Request &req, Response &res) {
|
|||||||
if (path.back() == '/') { path += "index.html"; }
|
if (path.back() == '/') { path += "index.html"; }
|
||||||
|
|
||||||
// Defense-in-depth: is_valid_path blocks ".." traversal in the URL,
|
// Defense-in-depth: is_valid_path blocks ".." traversal in the URL,
|
||||||
// but symlinks/junctions can still escape the base directory.
|
// but symlinks/junctions can still escape the base directory. A path
|
||||||
if (!entry.resolved_base_dir.empty()) {
|
// that cannot be resolved, which includes one that does not exist, is
|
||||||
std::string resolved_path;
|
// not served from here.
|
||||||
if (detail::canonicalize_path(path.c_str(), resolved_path) &&
|
std::string resolved_path;
|
||||||
!detail::is_path_within_base(resolved_path,
|
if (!detail::canonicalize_path(path.c_str(), resolved_path)) {
|
||||||
entry.resolved_base_dir)) {
|
continue;
|
||||||
res.status = StatusCode::Forbidden_403;
|
}
|
||||||
return true;
|
if (!detail::is_path_within_base(resolved_path,
|
||||||
}
|
entry.resolved_base_dir)) {
|
||||||
|
res.status = StatusCode::Forbidden_403;
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
detail::FileStat stat(path);
|
detail::FileStat stat(path);
|
||||||
|
|||||||
@@ -11720,6 +11720,32 @@ TEST(MountTest, Unmount) {
|
|||||||
EXPECT_EQ(StatusCode::NotFound_404, res->status);
|
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) {
|
TEST(MountTest, PathSegmentBoundary) {
|
||||||
Server svr;
|
Server svr;
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user