فهرست منبع

Revert "Encode the characters RFC 3986 doesn't allow in a client path"

This reverts commit 28f3922. Keep the path encoding settled in #2082
(#2170) and #2608 until a concrete report needs more: user input
belongs in encode_path_component(), and an embedded NUL is invalid
input.
yhirose 3 روز پیش
والد
کامیت
e4b3865387
3فایلهای تغییر یافته به همراه24 افزوده شده و 84 حذف شده
  1. 1 1
      README.md
  2. 23 31
      httplib.h
  3. 0 52
      test/test.cc

+ 1 - 1
README.md

@@ -1471,7 +1471,7 @@ auto res = cli.Get("/already%20encoded/path"); // Use pre-encoded paths
 ```
 
 - `set_path_encode(bool on)` - Controls automatic encoding of special characters in URL paths
-  - `true` (default): Percent-encodes characters not allowed in a URL path (spaces, control characters, `<`, `|`, non-ASCII bytes, etc.) and `+`, `,`, `;`, `'`. `%` is kept, so existing escapes aren't encoded twice
+  - `true` (default): Automatically encodes spaces, plus signs, newlines, and other special characters
   - `false`: Sends paths as-is without encoding (useful for pre-encoded URLs)
 
 ### Performance Note for Local Connections

+ 23 - 31
httplib.h

@@ -5999,24 +5999,32 @@ inline size_t FileStat::size() const {
   return ret_ >= 0 ? static_cast<size_t>(st_.st_size) : 0;
 }
 
-// Percent-encode every byte outside RFC 3986 pchar, keeping '/' and '%' so that
-// escapes pass through. '+', ',', ';' and '\'' are pchar but encoded too, as
-// some servers give '+' and ';' a special meaning.
 inline std::string encode_path(const std::string &s) {
-  static const char hex[] = "0123456789ABCDEF";
   std::string result;
   result.reserve(s.size());
 
-  for (auto c : s) {
-    // The '\0' check keeps strchr from matching the terminator.
-    if (is_ascii_alnum(c) ||
-        (c != '\0' && std::strchr("-._~!$&()*=:@/%", c) != nullptr)) {
-      result += c;
-    } else {
-      auto uc = static_cast<unsigned char>(c);
-      result += '%';
-      result += hex[uc >> 4];
-      result += hex[uc & 0x0f];
+  for (size_t i = 0; s[i]; i++) {
+    switch (s[i]) {
+    case ' ': result += "%20"; break;
+    case '+': result += "%2B"; break;
+    case '\'': result += "%27"; break;
+    case ',': result += "%2C"; break;
+    // case ':': result += "%3A"; break; // ok? probably...
+    case ';': result += "%3B"; break;
+    default:
+      auto c = static_cast<uint8_t>(s[i]);
+      // Control characters (incl. CR/LF) and non-ASCII bytes are not allowed
+      // in a request-target as-is.
+      if (c < 0x20 || c == 0x7f || c >= 0x80) {
+        result += '%';
+        char hex[4];
+        auto len = snprintf(hex, sizeof(hex) - 1, "%02X", c);
+        assert(len == 2);
+        result.append(hex, static_cast<size_t>(len));
+      } else {
+        result += s[i];
+      }
+      break;
     }
   }
 
@@ -9315,23 +9323,7 @@ inline std::string encode_request_target(const std::string &uri,
     query_part = target.substr(query_pos + 1);
   }
 
-  std::string result;
-  if (path_encode) {
-    // Encode only the path: an absolute-form target (sent to a proxy) keeps
-    // its "http://host", and a CONNECT "host:port" has no path.
-    size_t path_pos = 0;
-    if (!path_part.empty() && path_part[0] != '/') {
-      auto scheme_end = path_part.find("://");
-      path_pos = scheme_end == std::string::npos
-                     ? path_part.size()
-                     : (std::min)(path_part.find('/', scheme_end + 3),
-                                  path_part.size());
-    }
-    result = path_part.substr(0, path_pos);
-    result += encode_path(path_part.substr(path_pos));
-  } else {
-    result = std::move(path_part);
-  }
+  auto result = path_encode ? encode_path(path_part) : std::move(path_part);
 
   if (!query_part.empty()) {
     // When path encoding is disabled the caller has supplied an already-encoded

+ 0 - 52
test/test.cc

@@ -4457,10 +4457,6 @@ TEST(PathUrlEncodeTest, StreamingMatchesBufferedTarget) {
       {"/a b?x=1", "/a%20b?x=1"},
       {"/\xE6\x97\xA5\xE6\x9C\xAC", "/%E6%97%A5%E6%9C%AC"},
       {"/a,b;c+d", "/a%2Cb%3Bc%2Bd"},
-      // Characters RFC 3986 doesn't allow in a path.
-      {"/\"<>\\^`{|}[]", "/%22%3C%3E%5C%5E%60%7B%7C%7D%5B%5D"},
-      // Escapes and the rest of pchar are kept.
-      {"/a%20b/!$&()*=:@~", "/a%20b/!$&()*=:@~"},
   };
 
   for (const auto &c : cases) {
@@ -4576,55 +4572,7 @@ TEST(PathUrlEncodeTest, ControlCharsInPathAreEncoded) {
     auto res = cli.Get("/a\tb\x1b\x7f");
     ASSERT_TRUE(res) << "Error: " << to_string(res.error());
     EXPECT_EQ("/a%09b%1B%7F", target);
-
-    // An embedded NUL doesn't cut the path short.
-    res = cli.Get(std::string("/a\0b", 4));
-    ASSERT_TRUE(res) << "Error: " << to_string(res.error());
-    EXPECT_EQ("/a%00b", target);
-  }
-}
-
-TEST(PathUrlEncodeTest, HostInProxyTargetIsNotEncoded) {
-  // Only the path of a target sent to a proxy is encoded, so the brackets of
-  // an IPv6 host stay.
-  Server proxy;
-
-  std::string target;
-  proxy.set_pre_routing_handler([&](const Request &req, Response &res) {
-    target = req.target;
-    res.status = StatusCode::Forbidden_403;
-    return Server::HandlerResponse::Handled;
-  });
-
-  auto port = proxy.bind_to_any_port(HOST);
-  auto thread = std::thread([&]() { proxy.listen_after_bind(); });
-  auto se = detail::scope_exit([&] {
-    proxy.stop();
-    thread.join();
-    ASSERT_FALSE(proxy.is_running());
-  });
-
-  proxy.wait_until_ready();
-
-  {
-    Client cli("::1", 8080);
-    cli.set_proxy(HOST, port);
-
-    auto res = cli.Get("/a b");
-    ASSERT_TRUE(res) << "Error: " << to_string(res.error());
-    EXPECT_EQ("http://[::1]:8080/a%20b", target);
   }
-
-#ifdef CPPHTTPLIB_SSL_ENABLED
-  {
-    SSLClient cli("::1", 8443);
-    cli.set_proxy(HOST, port);
-
-    auto res = cli.Get("/");
-    EXPECT_EQ(Error::ProxyConnection, res.error());
-    EXPECT_EQ("[::1]:8443", target);
-  }
-#endif
 }
 
 TEST(PathUrlEncodeTest, StreamingCRLFRejectedWhenPathEncodeDisabled) {