mirror of
https://github.com/yhirose/cpp-httplib.git
synced 2026-11-03 13:38:36 +00:00
keep a relative Location on its host in resolve_relative_location (#2654)
* keep a relative Location on its host in resolve_relative_location * Shorten the comment on on_this_host in resolve_relative_location --------- Co-authored-by: yhirose <yuji.hirose.bug@gmail.com>
This commit is contained in:
1 parent
a18895f42e
commit
453b1b8613
2 files changed
+68
-4
No files matched your search
@@ -1029,18 +1029,27 @@ inline std::string resolve_relative_location(const std::string &location,
|
|||||||
// A ':' in the first segment means the value has a scheme.
|
// A ':' in the first segment means the value has a scheme.
|
||||||
if (location.find(':') < location.find_first_of("/?#")) { return location; }
|
if (location.find(':') < location.find_first_of("/?#")) { return location; }
|
||||||
|
|
||||||
if (location[0] == '#') { return base.substr(0, base.find('#')) + location; }
|
// A path starting with "//" would be re-parsed as an authority. Prefix "/."
|
||||||
|
// (as the URL Standard does); remove_dot_segments strips it later.
|
||||||
|
auto on_this_host = [](std::string path) -> std::string {
|
||||||
|
if (path.compare(0, 2, "//") == 0) { path.insert(0, "/."); }
|
||||||
|
return path;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (location[0] == '#') {
|
||||||
|
return on_this_host(base.substr(0, base.find('#')) + location);
|
||||||
|
}
|
||||||
|
|
||||||
auto base_path = base.substr(0, base.find_first_of("?#"));
|
auto base_path = base.substr(0, base.find_first_of("?#"));
|
||||||
if (location[0] == '?') { return base_path + location; }
|
if (location[0] == '?') { return on_this_host(base_path + location); }
|
||||||
|
|
||||||
if (base_path.empty() || base_path[0] != '/') { base_path = "/"; }
|
if (base_path.empty() || base_path[0] != '/') { base_path = "/"; }
|
||||||
auto merged = base_path.substr(0, base_path.rfind('/') + 1) + location;
|
auto merged = base_path.substr(0, base_path.rfind('/') + 1) + location;
|
||||||
|
|
||||||
// Remove "." and ".." segments from the merged path.
|
// Remove "." and ".." segments from the merged path.
|
||||||
auto path_end = (std::min)(merged.find_first_of("?#"), merged.size());
|
auto path_end = (std::min)(merged.find_first_of("?#"), merged.size());
|
||||||
return remove_dot_segments(merged.substr(0, path_end)) +
|
return on_this_host(remove_dot_segments(merged.substr(0, path_end)) +
|
||||||
merged.substr(path_end);
|
merged.substr(path_end));
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace detail
|
} // namespace detail
|
||||||
|
|||||||
@@ -20725,6 +20725,13 @@ TEST(RedirectTest, ResolveRelativeLocation) {
|
|||||||
detail::resolve_relative_location(c.first, "/b/c/d;p?q"))
|
detail::resolve_relative_location(c.first, "/b/c/d;p?q"))
|
||||||
<< c.first;
|
<< c.first;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A result starting with "//" is still a path, not a network-path reference
|
||||||
|
EXPECT_EQ("/.//g",
|
||||||
|
detail::resolve_relative_location("../../..//g", "/b/c/d;p?q"));
|
||||||
|
EXPECT_EQ("/.//g/x", detail::resolve_relative_location("x", "//g/h"));
|
||||||
|
EXPECT_EQ("/.//g/h?y", detail::resolve_relative_location("?y", "//g/h"));
|
||||||
|
EXPECT_EQ("/.//g/h#s", detail::resolve_relative_location("#s", "//g/h"));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(RedirectTest, RemoveDotSegments) {
|
TEST(RedirectTest, RemoveDotSegments) {
|
||||||
@@ -20812,6 +20819,54 @@ TEST(RedirectTest, RelativeLocationWithoutLeadingSlash) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// "..//host:port/x" resolves to the path "//host:port/x" on the same host
|
||||||
|
// (RFC 3986 5.2), as curl and the URL Standard have it, not to another host.
|
||||||
|
TEST(RedirectTest, RelativeLocationStaysOnHost) {
|
||||||
|
Server other;
|
||||||
|
std::atomic<int> other_hits{0};
|
||||||
|
other.Get(".*", [&](const Request & /*req*/, Response &res) {
|
||||||
|
other_hits++;
|
||||||
|
res.set_content("other", "text/plain");
|
||||||
|
});
|
||||||
|
auto other_port = other.bind_to_any_port("127.0.0.1");
|
||||||
|
auto other_thread = std::thread([&]() { other.listen_after_bind(); });
|
||||||
|
|
||||||
|
Server svr;
|
||||||
|
svr.Get("/dir/page", [](const Request &req, Response &res) {
|
||||||
|
res.set_redirect(req.get_param_value("to"));
|
||||||
|
});
|
||||||
|
svr.Get(".*", [](const Request &req, Response &res) {
|
||||||
|
res.set_content(req.path, "text/plain");
|
||||||
|
});
|
||||||
|
auto port = svr.bind_to_any_port(HOST);
|
||||||
|
auto thread = std::thread([&]() { svr.listen_after_bind(); });
|
||||||
|
|
||||||
|
auto se = detail::scope_exit([&] {
|
||||||
|
svr.stop();
|
||||||
|
thread.join();
|
||||||
|
other.stop();
|
||||||
|
other_thread.join();
|
||||||
|
ASSERT_FALSE(svr.is_running());
|
||||||
|
ASSERT_FALSE(other.is_running());
|
||||||
|
});
|
||||||
|
|
||||||
|
svr.wait_until_ready();
|
||||||
|
other.wait_until_ready();
|
||||||
|
|
||||||
|
auto authority = "127.0.0.1:" + std::to_string(other_port);
|
||||||
|
for (const auto &to :
|
||||||
|
{"..//" + authority + "/x", "../..//" + authority + "/x"}) {
|
||||||
|
Client cli(HOST, port);
|
||||||
|
cli.set_follow_location(true);
|
||||||
|
|
||||||
|
auto res = cli.Get("/dir/page?to=" + encode_query_component(to));
|
||||||
|
ASSERT_TRUE(res) << to << ": " << to_string(res.error());
|
||||||
|
EXPECT_EQ(StatusCode::OK_200, res->status) << to;
|
||||||
|
EXPECT_EQ("//" + authority + "/x", res->body) << to;
|
||||||
|
}
|
||||||
|
EXPECT_EQ(0, other_hits.load());
|
||||||
|
}
|
||||||
|
|
||||||
#ifdef CPPHTTPLIB_SSL_ENABLED
|
#ifdef CPPHTTPLIB_SSL_ENABLED
|
||||||
TEST(RedirectTest, Issue2185_Online) {
|
TEST(RedirectTest, Issue2185_Online) {
|
||||||
SSLClient client("github.com");
|
SSLClient client("github.com");
|
||||||
|
|||||||
Reference in new issue
Block a user