Bladeren bron

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>
metsw24-max 5 dagen geleden
bovenliggende
commit
453b1b8613
2 gewijzigde bestanden met toevoegingen van 68 en 4 verwijderingen
  1. 13 4
      httplib.h
  2. 55 0
      test/test.cc

+ 13 - 4
httplib.h

@@ -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.
   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("?#"));
-  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 = "/"; }
   auto merged = base_path.substr(0, base_path.rfind('/') + 1) + location;
 
   // Remove "." and ".." segments from the merged path.
   auto path_end = (std::min)(merged.find_first_of("?#"), merged.size());
-  return remove_dot_segments(merged.substr(0, path_end)) +
-         merged.substr(path_end);
+  return on_this_host(remove_dot_segments(merged.substr(0, path_end)) +
+                      merged.substr(path_end));
 }
 
 } // namespace detail

+ 55 - 0
test/test.cc

@@ -20725,6 +20725,13 @@ TEST(RedirectTest, ResolveRelativeLocation) {
               detail::resolve_relative_location(c.first, "/b/c/d;p?q"))
         << 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) {
@@ -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
 TEST(RedirectTest, Issue2185_Online) {
   SSLClient client("github.com");