Просмотр исходного кода

Send query string verbatim when path encoding is disabled (#2479)

set_path_encode(false) only suppressed encoding of the path part. The
query part was always run through normalize_query_string(), which
decodes then re-encodes each key/value pair regardless of the flag.

That round-trip is lossy for pre-encoded payloads: re-encoding emits
sub-delimiters literally (%2C->",", %24->"$", %3B->";", ...) and turns
%20 into "+", which a strict RFC 3986 server decodes back as "+"
(0x2B) rather than a space (0x20), corrupting binary query data.

Honor path_encode_ for the query as well: when disabled, append the
caller-supplied query verbatim. Add a regression test asserting on the
raw request target, since the server decodes "+" as space and would
otherwise mask the difference.
Saber Haj Rabiee 1 месяц назад
Родитель
Сommit
3fe32b63b4
2 измененных файлов с 46 добавлено и 3 удалено
  1. 12 3
      httplib.h
  2. 34 0
      test/test.cc

+ 12 - 3
httplib.h

@@ -13661,9 +13661,18 @@ inline bool ClientImpl::write_request(Stream &strm, Request &req,
 
     if (!query_part.empty()) {
       // Normalize the query string (decode then re-encode) while preserving
-      // the original parameter order.
-      auto normalized = detail::normalize_query_string(query_part);
-      if (!normalized.empty()) { path_with_query += '?' + normalized; }
+      // the original parameter order. When path encoding is disabled the
+      // caller has supplied an already-encoded target and expects the exact
+      // bytes to be sent on the wire, so skip normalization for the query
+      // too. Normalizing here would decode-then-re-encode the query and
+      // corrupt pre-encoded binary payloads (e.g. turning `%20` into `+`,
+      // which a strict RFC 3986 server decodes back as `+`, not a space).
+      if (path_encode_) {
+        auto normalized = detail::normalize_query_string(query_part);
+        if (!normalized.empty()) { path_with_query += '?' + normalized; }
+      } else {
+        path_with_query += '?' + query_part;
+      }
 
       // Still populate req.params for handlers/users who read them.
       detail::parse_query_text(query_part, req.params);

+ 34 - 0
test/test.cc

@@ -2929,6 +2929,40 @@ TEST(PathUrlEncodeTest, PathUrlEncode) {
   }
 }
 
+TEST(PathUrlEncodeTest, PreEncodedQueryNotReencoded) {
+  // When path encoding is disabled the client must transmit the supplied
+  // query verbatim. Decoding-then-re-encoding it (the previous behavior)
+  // corrupts pre-encoded binary payloads: e.g. `%20` would be turned into
+  // `+`, which a strict RFC 3986 server decodes back as `+` (0x2B) rather
+  // than a space (0x20). Assert on the raw wire target to catch this.
+  Server svr;
+
+  const std::string expected_target = "/foo?q=a%20b%2Cc%24d%3Bx&a=%00%FF";
+
+  svr.Get("/foo", [&](const Request &req, Response &res) {
+    EXPECT_EQ(expected_target, req.target);
+    res.status = StatusCode::OK_200;
+  });
+
+  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);
+    cli.set_path_encode(false);
+
+    auto res = cli.Get(expected_target.c_str());
+    ASSERT_TRUE(res);
+    EXPECT_EQ(StatusCode::OK_200, res->status);
+  }
+}
+
 TEST(PathUrlEncodeTest, IncludePercentEncodingLF) {
   Server svr;