瀏覽代碼

Put Params before the fragment of a client path

append_query_params() added the parameters after a '#', where the
fragment, which is not sent, took them along, and write_request() read a
query that came after one. Insert them before the fragment, and leave
the fragment out of the query.
yhirose 2 天之前
父節點
當前提交
079f2176af
共有 2 個文件被更改,包括 49 次插入 和 4 次删除
  1. 11 4
      httplib.h
  2. 38 0
      test/test.cc

+ 11 - 4
httplib.h

@@ -11843,9 +11843,14 @@ inline std::string sanitize_filename(const std::string &filename) {
 
 inline std::string append_query_params(const std::string &path,
                                        const Params &params) {
-  std::string path_with_query = path;
-  path_with_query += path.find('?') == std::string::npos ? '?' : '&';
+  // The query ends at the fragment (RFC 3986 3.5), so the params go before it.
+  auto fragment_pos = path.find('#');
+  auto path_with_query = path.substr(0, fragment_pos);
+  path_with_query += path_with_query.find('?') == std::string::npos ? '?' : '&';
   path_with_query += detail::params_to_query_str(params);
+  if (fragment_pos != std::string::npos) {
+    path_with_query.append(path, fragment_pos, std::string::npos);
+  }
   return path_with_query;
 }
 
@@ -16370,10 +16375,12 @@ inline bool ClientImpl::write_request(Stream &strm, Request &req,
     // `encode_request_target`; the raw query is still needed here to decide
     // between populating `req.params` from it and falling back to building a
     // query out of caller-supplied `req.params`.
-    auto query_pos = req.path.find('?');
+    // The fragment, which is not sent, is not part of the query.
+    auto target = req.path.substr(0, req.path.find('#'));
+    auto query_pos = target.find('?');
     auto query_part = query_pos == std::string::npos
                           ? std::string()
-                          : req.path.substr(query_pos + 1);
+                          : target.substr(query_pos + 1);
 
     auto path_with_query =
         detail::encode_request_target(req.path, path_encode_);

+ 38 - 0
test/test.cc

@@ -5812,6 +5812,44 @@ TEST(URLFragmentTest, WithFragment) {
   }
 }
 
+TEST(URLFragmentTest, ParamsGoBeforeTheFragment) {
+  EXPECT_EQ("/a?b=1#f", append_query_params("/a#f", {{"b", "1"}}));
+  EXPECT_EQ("/a?q=0&b=1#f", append_query_params("/a?q=0#f", {{"b", "1"}}));
+
+  Server svr;
+  std::vector<std::string> targets;
+  svr.Get("/hi", [&](const Request &req, Response & /*res*/) {
+    targets.push_back(req.target);
+  });
+
+  auto thread = std::thread([&]() { svr.listen(HOST, PORT); });
+  auto se = detail::scope_exit([&] {
+    svr.stop();
+    thread.join();
+    ASSERT_FALSE(svr.is_running());
+  });
+
+  svr.wait_until_ready();
+
+  Client cli(HOST, PORT);
+  EXPECT_TRUE(cli.Get("/hi#f", Params{{"a", "1"}}, Headers{}));
+  EXPECT_TRUE(cli.Get("/hi?q=0#f", Params{{"a", "1"}}, Headers{}));
+  EXPECT_TRUE(cli.open_stream("GET", "/hi#f", {{"a", "1"}}).is_valid());
+
+  // A '?' after the '#' belongs to the fragment, not to the query.
+  Request req;
+  req.method = "GET";
+  req.path = "/hi#f?x=1";
+  req.params.emplace("a", "1");
+  Response res;
+  auto error = Error::Success;
+  EXPECT_TRUE(cli.send(req, res, error));
+
+  EXPECT_EQ((std::vector<std::string>{"/hi?a=1", "/hi?q=0&a=1", "/hi?a=1",
+                                      "/hi?a=1"}),
+            targets);
+}
+
 TEST(HeaderWriter, SetHeaderWriter) {
   Server svr;