mirror of
https://github.com/yhirose/cpp-httplib.git
synced 2026-10-25 12:14:41 +00:00
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.
This commit is contained in:
1 parent
28f39225fc
commit
e4b3865387
3 files changed
+24
-84
No files matched your search
@@ -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
|
- `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)
|
- `false`: Sends paths as-is without encoding (useful for pre-encoded URLs)
|
||||||
|
|
||||||
### Performance Note for Local Connections
|
### Performance Note for Local Connections
|
||||||
|
|||||||
@@ -5999,24 +5999,32 @@ inline size_t FileStat::size() const {
|
|||||||
return ret_ >= 0 ? static_cast<size_t>(st_.st_size) : 0;
|
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) {
|
inline std::string encode_path(const std::string &s) {
|
||||||
static const char hex[] = "0123456789ABCDEF";
|
|
||||||
std::string result;
|
std::string result;
|
||||||
result.reserve(s.size());
|
result.reserve(s.size());
|
||||||
|
|
||||||
for (auto c : s) {
|
for (size_t i = 0; s[i]; i++) {
|
||||||
// The '\0' check keeps strchr from matching the terminator.
|
switch (s[i]) {
|
||||||
if (is_ascii_alnum(c) ||
|
case ' ': result += "%20"; break;
|
||||||
(c != '\0' && std::strchr("-._~!$&()*=:@/%", c) != nullptr)) {
|
case '+': result += "%2B"; break;
|
||||||
result += c;
|
case '\'': result += "%27"; break;
|
||||||
} else {
|
case ',': result += "%2C"; break;
|
||||||
auto uc = static_cast<unsigned char>(c);
|
// case ':': result += "%3A"; break; // ok? probably...
|
||||||
result += '%';
|
case ';': result += "%3B"; break;
|
||||||
result += hex[uc >> 4];
|
default:
|
||||||
result += hex[uc & 0x0f];
|
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);
|
query_part = target.substr(query_pos + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string result;
|
auto result = path_encode ? encode_path(path_part) : std::move(path_part);
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!query_part.empty()) {
|
if (!query_part.empty()) {
|
||||||
// When path encoding is disabled the caller has supplied an already-encoded
|
// When path encoding is disabled the caller has supplied an already-encoded
|
||||||
|
|||||||
@@ -4457,10 +4457,6 @@ TEST(PathUrlEncodeTest, StreamingMatchesBufferedTarget) {
|
|||||||
{"/a b?x=1", "/a%20b?x=1"},
|
{"/a b?x=1", "/a%20b?x=1"},
|
||||||
{"/\xE6\x97\xA5\xE6\x9C\xAC", "/%E6%97%A5%E6%9C%AC"},
|
{"/\xE6\x97\xA5\xE6\x9C\xAC", "/%E6%97%A5%E6%9C%AC"},
|
||||||
{"/a,b;c+d", "/a%2Cb%3Bc%2Bd"},
|
{"/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) {
|
for (const auto &c : cases) {
|
||||||
@@ -4576,57 +4572,9 @@ TEST(PathUrlEncodeTest, ControlCharsInPathAreEncoded) {
|
|||||||
auto res = cli.Get("/a\tb\x1b\x7f");
|
auto res = cli.Get("/a\tb\x1b\x7f");
|
||||||
ASSERT_TRUE(res) << "Error: " << to_string(res.error());
|
ASSERT_TRUE(res) << "Error: " << to_string(res.error());
|
||||||
EXPECT_EQ("/a%09b%1B%7F", target);
|
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) {
|
TEST(PathUrlEncodeTest, StreamingCRLFRejectedWhenPathEncodeDisabled) {
|
||||||
// Nothing may reach the wire: a raw CR/LF target would split the request
|
// Nothing may reach the wire: a raw CR/LF target would split the request
|
||||||
// line and inject headers.
|
// line and inject headers.
|
||||||
|
|||||||
Reference in new issue
Block a user