You've already forked cpp-httplib
Merge commit from fork
* Fix "Untrusted HTTP Header Handling (REMOTE*/LOCAL*)" * Fix "Untrusted HTTP Header Handling (X-Forwarded-For)" * Fix security problems in docker/main.cc
This commit is contained in:
@@ -48,32 +48,10 @@ std::string get_error_time_format() {
|
|||||||
return ss.str();
|
return ss.str();
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string get_client_ip(const Request &req) {
|
|
||||||
// Check for X-Forwarded-For header first (common in reverse proxy setups)
|
|
||||||
auto forwarded_for = req.get_header_value("X-Forwarded-For");
|
|
||||||
if (!forwarded_for.empty()) {
|
|
||||||
// Get the first IP if there are multiple
|
|
||||||
auto comma_pos = forwarded_for.find(',');
|
|
||||||
if (comma_pos != std::string::npos) {
|
|
||||||
return forwarded_for.substr(0, comma_pos);
|
|
||||||
}
|
|
||||||
return forwarded_for;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for X-Real-IP header
|
|
||||||
auto real_ip = req.get_header_value("X-Real-IP");
|
|
||||||
if (!real_ip.empty()) { return real_ip; }
|
|
||||||
|
|
||||||
// Fallback to remote address (though cpp-httplib doesn't provide this
|
|
||||||
// directly) For demonstration, we'll use a placeholder
|
|
||||||
return "127.0.0.1";
|
|
||||||
}
|
|
||||||
|
|
||||||
// NGINX Combined log format:
|
// NGINX Combined log format:
|
||||||
// $remote_addr - $remote_user [$time_local] "$request" $status $body_bytes_sent
|
// $remote_addr - $remote_user [$time_local] "$request" $status $body_bytes_sent
|
||||||
// "$http_referer" "$http_user_agent"
|
// "$http_referer" "$http_user_agent"
|
||||||
void nginx_access_logger(const Request &req, const Response &res) {
|
void nginx_access_logger(const Request &req, const Response &res) {
|
||||||
auto remote_addr = get_client_ip(req);
|
|
||||||
std::string remote_user =
|
std::string remote_user =
|
||||||
"-"; // cpp-httplib doesn't have built-in auth user tracking
|
"-"; // cpp-httplib doesn't have built-in auth user tracking
|
||||||
auto time_local = get_time_format();
|
auto time_local = get_time_format();
|
||||||
@@ -86,7 +64,7 @@ void nginx_access_logger(const Request &req, const Response &res) {
|
|||||||
if (http_user_agent.empty()) http_user_agent = "-";
|
if (http_user_agent.empty()) http_user_agent = "-";
|
||||||
|
|
||||||
std::cout << std::format("{} - {} [{}] \"{}\" {} {} \"{}\" \"{}\"",
|
std::cout << std::format("{} - {} [{}] \"{}\" {} {} \"{}\" \"{}\"",
|
||||||
remote_addr, remote_user, time_local, request,
|
req.remote_addr, remote_user, time_local, request,
|
||||||
status, body_bytes_sent, http_referer,
|
status, body_bytes_sent, http_referer,
|
||||||
http_user_agent)
|
http_user_agent)
|
||||||
<< std::endl;
|
<< std::endl;
|
||||||
@@ -100,7 +78,6 @@ void nginx_error_logger(const Error &err, const Request *req) {
|
|||||||
std::string level = "error";
|
std::string level = "error";
|
||||||
|
|
||||||
if (req) {
|
if (req) {
|
||||||
auto client_ip = get_client_ip(*req);
|
|
||||||
auto request =
|
auto request =
|
||||||
std::format("{} {} {}", req->method, req->path, req->version);
|
std::format("{} {} {}", req->method, req->path, req->version);
|
||||||
auto host = req->get_header_value("Host");
|
auto host = req->get_header_value("Host");
|
||||||
@@ -108,8 +85,8 @@ void nginx_error_logger(const Error &err, const Request *req) {
|
|||||||
|
|
||||||
std::cerr << std::format("{} [{}] {}, client: {}, request: "
|
std::cerr << std::format("{} [{}] {}, client: {}, request: "
|
||||||
"\"{}\", host: \"{}\"",
|
"\"{}\", host: \"{}\"",
|
||||||
time_local, level, to_string(err), client_ip,
|
time_local, level, to_string(err),
|
||||||
request, host)
|
req->remote_addr, request, host)
|
||||||
<< std::endl;
|
<< std::endl;
|
||||||
} else {
|
} else {
|
||||||
// If no request context, just log the error
|
// If no request context, just log the error
|
||||||
@@ -131,6 +108,10 @@ void print_usage(const char *program_name) {
|
|||||||
std::cout << " Format: mount_point:document_root"
|
std::cout << " Format: mount_point:document_root"
|
||||||
<< std::endl;
|
<< std::endl;
|
||||||
std::cout << " (default: /:./html)" << std::endl;
|
std::cout << " (default: /:./html)" << std::endl;
|
||||||
|
std::cout << " --trusted-proxy <ip> Add trusted proxy IP address"
|
||||||
|
<< std::endl;
|
||||||
|
std::cout << " (can be specified multiple times)"
|
||||||
|
<< std::endl;
|
||||||
std::cout << " --version Show version information"
|
std::cout << " --version Show version information"
|
||||||
<< std::endl;
|
<< std::endl;
|
||||||
std::cout << " --help Show this help message" << std::endl;
|
std::cout << " --help Show this help message" << std::endl;
|
||||||
@@ -140,6 +121,9 @@ void print_usage(const char *program_name) {
|
|||||||
<< " --host localhost --port 8080 --mount /:./html" << std::endl;
|
<< " --host localhost --port 8080 --mount /:./html" << std::endl;
|
||||||
std::cout << " " << program_name
|
std::cout << " " << program_name
|
||||||
<< " --host 0.0.0.0 --port 3000 --mount /api:./api" << std::endl;
|
<< " --host 0.0.0.0 --port 3000 --mount /api:./api" << std::endl;
|
||||||
|
std::cout << " " << program_name
|
||||||
|
<< " --trusted-proxy 192.168.1.100 --trusted-proxy 10.0.0.1"
|
||||||
|
<< std::endl;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ServerConfig {
|
struct ServerConfig {
|
||||||
@@ -147,6 +131,7 @@ struct ServerConfig {
|
|||||||
int port = 8080;
|
int port = 8080;
|
||||||
std::string mount_point = "/";
|
std::string mount_point = "/";
|
||||||
std::string document_root = "./html";
|
std::string document_root = "./html";
|
||||||
|
std::vector<std::string> trusted_proxies;
|
||||||
};
|
};
|
||||||
|
|
||||||
enum class ParseResult { SUCCESS, HELP_REQUESTED, VERSION_REQUESTED, ERROR };
|
enum class ParseResult { SUCCESS, HELP_REQUESTED, VERSION_REQUESTED, ERROR };
|
||||||
@@ -205,6 +190,14 @@ ParseResult parse_command_line(int argc, char *argv[], ServerConfig &config) {
|
|||||||
} else if (strcmp(argv[i], "--version") == 0) {
|
} else if (strcmp(argv[i], "--version") == 0) {
|
||||||
std::cout << CPPHTTPLIB_VERSION << std::endl;
|
std::cout << CPPHTTPLIB_VERSION << std::endl;
|
||||||
return ParseResult::VERSION_REQUESTED;
|
return ParseResult::VERSION_REQUESTED;
|
||||||
|
} else if (strcmp(argv[i], "--trusted-proxy") == 0) {
|
||||||
|
if (i + 1 >= argc) {
|
||||||
|
std::cerr << "Error: --trusted-proxy requires an IP address argument"
|
||||||
|
<< std::endl;
|
||||||
|
print_usage(argv[0]);
|
||||||
|
return ParseResult::ERROR;
|
||||||
|
}
|
||||||
|
config.trusted_proxies.push_back(argv[++i]);
|
||||||
} else {
|
} else {
|
||||||
std::cerr << "Error: Unknown option '" << argv[i] << "'" << std::endl;
|
std::cerr << "Error: Unknown option '" << argv[i] << "'" << std::endl;
|
||||||
print_usage(argv[0]);
|
print_usage(argv[0]);
|
||||||
@@ -218,6 +211,11 @@ bool setup_server(Server &svr, const ServerConfig &config) {
|
|||||||
svr.set_logger(nginx_access_logger);
|
svr.set_logger(nginx_access_logger);
|
||||||
svr.set_error_logger(nginx_error_logger);
|
svr.set_error_logger(nginx_error_logger);
|
||||||
|
|
||||||
|
// Set trusted proxies if specified
|
||||||
|
if (!config.trusted_proxies.empty()) {
|
||||||
|
svr.set_trusted_proxies(config.trusted_proxies);
|
||||||
|
}
|
||||||
|
|
||||||
auto ret = svr.set_mount_point(config.mount_point, config.document_root);
|
auto ret = svr.set_mount_point(config.mount_point, config.document_root);
|
||||||
if (!ret) {
|
if (!ret) {
|
||||||
std::cerr
|
std::cerr
|
||||||
@@ -285,6 +283,16 @@ int main(int argc, char *argv[]) {
|
|||||||
<< std::endl;
|
<< std::endl;
|
||||||
std::cout << "Mount point: " << config.mount_point << " -> "
|
std::cout << "Mount point: " << config.mount_point << " -> "
|
||||||
<< config.document_root << std::endl;
|
<< config.document_root << std::endl;
|
||||||
|
|
||||||
|
if (!config.trusted_proxies.empty()) {
|
||||||
|
std::cout << "Trusted proxies: ";
|
||||||
|
for (size_t i = 0; i < config.trusted_proxies.size(); ++i) {
|
||||||
|
if (i > 0) std::cout << ", ";
|
||||||
|
std::cout << config.trusted_proxies[i];
|
||||||
|
}
|
||||||
|
std::cout << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
std::cout << "Press Ctrl+C to shutdown gracefully..." << std::endl;
|
std::cout << "Press Ctrl+C to shutdown gracefully..." << std::endl;
|
||||||
|
|
||||||
auto ret = svr.listen(config.hostname, config.port);
|
auto ret = svr.listen(config.hostname, config.port);
|
||||||
|
|||||||
76
httplib.h
76
httplib.h
@@ -1132,6 +1132,8 @@ public:
|
|||||||
Server &
|
Server &
|
||||||
set_header_writer(std::function<ssize_t(Stream &, Headers &)> const &writer);
|
set_header_writer(std::function<ssize_t(Stream &, Headers &)> const &writer);
|
||||||
|
|
||||||
|
Server &set_trusted_proxies(const std::vector<std::string> &proxies);
|
||||||
|
|
||||||
Server &set_keep_alive_max_count(size_t count);
|
Server &set_keep_alive_max_count(size_t count);
|
||||||
Server &set_keep_alive_timeout(time_t sec);
|
Server &set_keep_alive_timeout(time_t sec);
|
||||||
|
|
||||||
@@ -1170,6 +1172,9 @@ protected:
|
|||||||
const std::function<void(Request &)> &setup_request);
|
const std::function<void(Request &)> &setup_request);
|
||||||
|
|
||||||
std::atomic<socket_t> svr_sock_{INVALID_SOCKET};
|
std::atomic<socket_t> svr_sock_{INVALID_SOCKET};
|
||||||
|
|
||||||
|
std::vector<std::string> trusted_proxies_;
|
||||||
|
|
||||||
size_t keep_alive_max_count_ = CPPHTTPLIB_KEEPALIVE_MAX_COUNT;
|
size_t keep_alive_max_count_ = CPPHTTPLIB_KEEPALIVE_MAX_COUNT;
|
||||||
time_t keep_alive_timeout_sec_ = CPPHTTPLIB_KEEPALIVE_TIMEOUT_SECOND;
|
time_t keep_alive_timeout_sec_ = CPPHTTPLIB_KEEPALIVE_TIMEOUT_SECOND;
|
||||||
time_t read_timeout_sec_ = CPPHTTPLIB_SERVER_READ_TIMEOUT_SECOND;
|
time_t read_timeout_sec_ = CPPHTTPLIB_SERVER_READ_TIMEOUT_SECOND;
|
||||||
@@ -4600,13 +4605,35 @@ inline bool zstd_decompressor::decompress(const char *data, size_t data_length,
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
inline bool is_prohibited_header_name(const std::string &name) {
|
||||||
|
using udl::operator""_t;
|
||||||
|
|
||||||
|
switch (str2tag(name)) {
|
||||||
|
case "REMOTE_ADDR"_t:
|
||||||
|
case "REMOTE_PORT"_t:
|
||||||
|
case "LOCAL_ADDR"_t:
|
||||||
|
case "LOCAL_PORT"_t: return true;
|
||||||
|
default: return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
inline bool has_header(const Headers &headers, const std::string &key) {
|
inline bool has_header(const Headers &headers, const std::string &key) {
|
||||||
|
if (is_prohibited_header_name(key)) { return false; }
|
||||||
return headers.find(key) != headers.end();
|
return headers.find(key) != headers.end();
|
||||||
}
|
}
|
||||||
|
|
||||||
inline const char *get_header_value(const Headers &headers,
|
inline const char *get_header_value(const Headers &headers,
|
||||||
const std::string &key, const char *def,
|
const std::string &key, const char *def,
|
||||||
size_t id) {
|
size_t id) {
|
||||||
|
if (is_prohibited_header_name(key)) {
|
||||||
|
#ifndef CPPHTTPLIB_NO_EXCEPTIONS
|
||||||
|
std::string msg = "Prohibited header name '" + key + "' is specified.";
|
||||||
|
throw std::invalid_argument(msg);
|
||||||
|
#else
|
||||||
|
return "";
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
auto rng = headers.equal_range(key);
|
auto rng = headers.equal_range(key);
|
||||||
auto it = rng.first;
|
auto it = rng.first;
|
||||||
std::advance(it, static_cast<ssize_t>(id));
|
std::advance(it, static_cast<ssize_t>(id));
|
||||||
@@ -7501,6 +7528,12 @@ inline Server &Server::set_header_writer(
|
|||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline Server &
|
||||||
|
Server::set_trusted_proxies(const std::vector<std::string> &proxies) {
|
||||||
|
trusted_proxies_ = proxies;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
inline Server &Server::set_keep_alive_max_count(size_t count) {
|
inline Server &Server::set_keep_alive_max_count(size_t count) {
|
||||||
keep_alive_max_count_ = count;
|
keep_alive_max_count_ = count;
|
||||||
return *this;
|
return *this;
|
||||||
@@ -8289,6 +8322,40 @@ inline bool Server::dispatch_request_for_content_reader(
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline std::string
|
||||||
|
get_client_ip(const std::string &x_forwarded_for,
|
||||||
|
const std::vector<std::string> &trusted_proxies) {
|
||||||
|
// X-Forwarded-For is a comma-separated list per RFC 7239
|
||||||
|
std::vector<std::string> ip_list;
|
||||||
|
detail::split(x_forwarded_for.data(),
|
||||||
|
x_forwarded_for.data() + x_forwarded_for.size(), ',',
|
||||||
|
[&](const char *b, const char *e) {
|
||||||
|
auto r = detail::trim(b, e, 0, static_cast<size_t>(e - b));
|
||||||
|
ip_list.emplace_back(std::string(b + r.first, b + r.second));
|
||||||
|
});
|
||||||
|
|
||||||
|
for (size_t i = 0; i < ip_list.size(); ++i) {
|
||||||
|
auto ip = ip_list[i];
|
||||||
|
|
||||||
|
auto is_trusted_proxy =
|
||||||
|
std::any_of(trusted_proxies.begin(), trusted_proxies.end(),
|
||||||
|
[&](const std::string &proxy) { return ip == proxy; });
|
||||||
|
|
||||||
|
if (is_trusted_proxy) {
|
||||||
|
if (i == 0) {
|
||||||
|
// If the trusted proxy is the first IP, there's no preceding client IP
|
||||||
|
return ip;
|
||||||
|
} else {
|
||||||
|
// Return the IP immediately before the trusted proxy
|
||||||
|
return ip_list[i - 1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no trusted proxy is found, return the first IP in the list
|
||||||
|
return ip_list.front();
|
||||||
|
}
|
||||||
|
|
||||||
inline bool
|
inline bool
|
||||||
Server::process_request(Stream &strm, const std::string &remote_addr,
|
Server::process_request(Stream &strm, const std::string &remote_addr,
|
||||||
int remote_port, const std::string &local_addr,
|
int remote_port, const std::string &local_addr,
|
||||||
@@ -8352,15 +8419,16 @@ Server::process_request(Stream &strm, const std::string &remote_addr,
|
|||||||
connection_closed = true;
|
connection_closed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!trusted_proxies_.empty() && req.has_header("X-Forwarded-For")) {
|
||||||
|
auto x_forwarded_for = req.get_header_value("X-Forwarded-For");
|
||||||
|
req.remote_addr = get_client_ip(x_forwarded_for, trusted_proxies_);
|
||||||
|
} else {
|
||||||
req.remote_addr = remote_addr;
|
req.remote_addr = remote_addr;
|
||||||
|
}
|
||||||
req.remote_port = remote_port;
|
req.remote_port = remote_port;
|
||||||
req.set_header("REMOTE_ADDR", req.remote_addr);
|
|
||||||
req.set_header("REMOTE_PORT", std::to_string(req.remote_port));
|
|
||||||
|
|
||||||
req.local_addr = local_addr;
|
req.local_addr = local_addr;
|
||||||
req.local_port = local_port;
|
req.local_port = local_port;
|
||||||
req.set_header("LOCAL_ADDR", req.local_addr);
|
|
||||||
req.set_header("LOCAL_PORT", std::to_string(req.local_port));
|
|
||||||
|
|
||||||
if (req.has_header("Accept")) {
|
if (req.has_header("Accept")) {
|
||||||
const auto &accept_header = req.get_header_value("Accept");
|
const auto &accept_header = req.get_header_value("Accept");
|
||||||
|
|||||||
260
test/test.cc
260
test/test.cc
@@ -3100,21 +3100,20 @@ protected:
|
|||||||
#endif
|
#endif
|
||||||
.Get("/remote_addr",
|
.Get("/remote_addr",
|
||||||
[&](const Request &req, Response &res) {
|
[&](const Request &req, Response &res) {
|
||||||
auto remote_addr = req.headers.find("REMOTE_ADDR")->second;
|
ASSERT_FALSE(req.has_header("REMOTE_ADDR"));
|
||||||
EXPECT_TRUE(req.has_header("REMOTE_PORT"));
|
ASSERT_FALSE(req.has_header("REMOTE_PORT"));
|
||||||
EXPECT_EQ(req.remote_addr, req.get_header_value("REMOTE_ADDR"));
|
ASSERT_ANY_THROW(req.get_header_value("REMOTE_ADDR"));
|
||||||
EXPECT_EQ(req.remote_port,
|
ASSERT_ANY_THROW(req.get_header_value("REMOTE_PORT"));
|
||||||
std::stoi(req.get_header_value("REMOTE_PORT")));
|
res.set_content(req.remote_addr, "text/plain");
|
||||||
res.set_content(remote_addr.c_str(), "text/plain");
|
|
||||||
})
|
})
|
||||||
.Get("/local_addr",
|
.Get("/local_addr",
|
||||||
[&](const Request &req, Response &res) {
|
[&](const Request &req, Response &res) {
|
||||||
EXPECT_TRUE(req.has_header("LOCAL_PORT"));
|
ASSERT_FALSE(req.has_header("LOCAL_ADDR"));
|
||||||
EXPECT_TRUE(req.has_header("LOCAL_ADDR"));
|
ASSERT_FALSE(req.has_header("LOCAL_PORT"));
|
||||||
auto local_addr = req.get_header_value("LOCAL_ADDR");
|
ASSERT_ANY_THROW(req.get_header_value("LOCAL_ADDR"));
|
||||||
auto local_port = req.get_header_value("LOCAL_PORT");
|
ASSERT_ANY_THROW(req.get_header_value("LOCAL_PORT"));
|
||||||
EXPECT_EQ(req.local_addr, local_addr);
|
auto local_addr = req.local_addr;
|
||||||
EXPECT_EQ(req.local_port, std::stoi(local_port));
|
auto local_port = std::to_string(req.local_port);
|
||||||
res.set_content(local_addr.append(":").append(local_port),
|
res.set_content(local_addr.append(":").append(local_port),
|
||||||
"text/plain");
|
"text/plain");
|
||||||
})
|
})
|
||||||
@@ -11208,3 +11207,240 @@ TEST(HeaderSmugglingTest, ChunkedTrailerHeadersMerged) {
|
|||||||
std::string res;
|
std::string res;
|
||||||
ASSERT_TRUE(send_request(1, req, &res));
|
ASSERT_TRUE(send_request(1, req, &res));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(ForwardedHeadersTest, NoProxiesSetting) {
|
||||||
|
Server svr;
|
||||||
|
|
||||||
|
std::string observed_remote_addr;
|
||||||
|
std::string observed_xff;
|
||||||
|
|
||||||
|
svr.Get("/ip", [&](const Request &req, Response &res) {
|
||||||
|
observed_remote_addr = req.remote_addr;
|
||||||
|
observed_xff = req.get_header_value("X-Forwarded-For");
|
||||||
|
res.set_content("ok", "text/plain");
|
||||||
|
});
|
||||||
|
|
||||||
|
thread t = thread([&]() { svr.listen(HOST, PORT); });
|
||||||
|
auto se = detail::scope_exit([&] {
|
||||||
|
svr.stop();
|
||||||
|
t.join();
|
||||||
|
ASSERT_FALSE(svr.is_running());
|
||||||
|
});
|
||||||
|
|
||||||
|
svr.wait_until_ready();
|
||||||
|
|
||||||
|
Client cli(HOST, PORT);
|
||||||
|
auto res = cli.Get("/ip", {{"X-Forwarded-For", "203.0.113.66"}});
|
||||||
|
|
||||||
|
ASSERT_TRUE(res);
|
||||||
|
EXPECT_EQ(StatusCode::OK_200, res->status);
|
||||||
|
|
||||||
|
EXPECT_EQ(observed_xff, "203.0.113.66");
|
||||||
|
EXPECT_TRUE(observed_remote_addr == "::1" || observed_remote_addr == "127.0.0.1");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ForwardedHeadersTest, NoForwardedHeaders) {
|
||||||
|
Server svr;
|
||||||
|
|
||||||
|
svr.set_trusted_proxies({"203.0.113.66"});
|
||||||
|
|
||||||
|
std::string observed_remote_addr;
|
||||||
|
std::string observed_xff;
|
||||||
|
|
||||||
|
svr.Get("/ip", [&](const Request &req, Response &res) {
|
||||||
|
observed_remote_addr = req.remote_addr;
|
||||||
|
observed_xff = req.get_header_value("X-Forwarded-For");
|
||||||
|
res.set_content("ok", "text/plain");
|
||||||
|
});
|
||||||
|
|
||||||
|
thread t = thread([&]() { svr.listen(HOST, PORT); });
|
||||||
|
auto se = detail::scope_exit([&] {
|
||||||
|
svr.stop();
|
||||||
|
t.join();
|
||||||
|
ASSERT_FALSE(svr.is_running());
|
||||||
|
});
|
||||||
|
|
||||||
|
svr.wait_until_ready();
|
||||||
|
|
||||||
|
Client cli(HOST, PORT);
|
||||||
|
auto res = cli.Get("/ip");
|
||||||
|
|
||||||
|
ASSERT_TRUE(res);
|
||||||
|
EXPECT_EQ(StatusCode::OK_200, res->status);
|
||||||
|
|
||||||
|
EXPECT_EQ(observed_xff, "");
|
||||||
|
EXPECT_TRUE(observed_remote_addr == "::1" || observed_remote_addr == "127.0.0.1");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ForwardedHeadersTest, SingleTrustedProxy_UsesIPBeforeTrusted) {
|
||||||
|
Server svr;
|
||||||
|
|
||||||
|
svr.set_trusted_proxies({"203.0.113.66"});
|
||||||
|
|
||||||
|
std::string observed_remote_addr;
|
||||||
|
std::string observed_xff;
|
||||||
|
|
||||||
|
svr.Get("/ip", [&](const Request &req, Response &res) {
|
||||||
|
observed_remote_addr = req.remote_addr;
|
||||||
|
observed_xff = req.get_header_value("X-Forwarded-For");
|
||||||
|
res.set_content("ok", "text/plain");
|
||||||
|
});
|
||||||
|
|
||||||
|
thread t = thread([&]() { svr.listen(HOST, PORT); });
|
||||||
|
auto se = detail::scope_exit([&] {
|
||||||
|
svr.stop();
|
||||||
|
t.join();
|
||||||
|
ASSERT_FALSE(svr.is_running());
|
||||||
|
});
|
||||||
|
|
||||||
|
svr.wait_until_ready();
|
||||||
|
|
||||||
|
Client cli(HOST, PORT);
|
||||||
|
auto res = cli.Get("/ip", {{"X-Forwarded-For", "198.51.100.23, 203.0.113.66"}});
|
||||||
|
|
||||||
|
ASSERT_TRUE(res);
|
||||||
|
EXPECT_EQ(StatusCode::OK_200, res->status);
|
||||||
|
|
||||||
|
EXPECT_EQ(observed_xff, "198.51.100.23, 203.0.113.66");
|
||||||
|
EXPECT_EQ(observed_remote_addr, "198.51.100.23");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ForwardedHeadersTest, MultipleTrustedProxies_UsesClientIP) {
|
||||||
|
Server svr;
|
||||||
|
|
||||||
|
svr.set_trusted_proxies({"203.0.113.66", "192.0.2.45"});
|
||||||
|
|
||||||
|
std::string observed_remote_addr;
|
||||||
|
std::string observed_xff;
|
||||||
|
|
||||||
|
svr.Get("/ip", [&](const Request &req, Response &res) {
|
||||||
|
observed_remote_addr = req.remote_addr;
|
||||||
|
observed_xff = req.get_header_value("X-Forwarded-For");
|
||||||
|
res.set_content("ok", "text/plain");
|
||||||
|
});
|
||||||
|
|
||||||
|
thread t = thread([&]() { svr.listen(HOST, PORT); });
|
||||||
|
auto se = detail::scope_exit([&] {
|
||||||
|
svr.stop();
|
||||||
|
t.join();
|
||||||
|
ASSERT_FALSE(svr.is_running());
|
||||||
|
});
|
||||||
|
|
||||||
|
svr.wait_until_ready();
|
||||||
|
|
||||||
|
Client cli(HOST, PORT);
|
||||||
|
auto res = cli.Get(
|
||||||
|
"/ip",
|
||||||
|
{{"X-Forwarded-For", "198.51.100.23, 203.0.113.66, 192.0.2.45"}});
|
||||||
|
|
||||||
|
ASSERT_TRUE(res);
|
||||||
|
EXPECT_EQ(StatusCode::OK_200, res->status);
|
||||||
|
|
||||||
|
EXPECT_EQ(observed_xff, "198.51.100.23, 203.0.113.66, 192.0.2.45");
|
||||||
|
EXPECT_EQ(observed_remote_addr, "198.51.100.23");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ForwardedHeadersTest, TrustedProxyNotInHeader_UsesFirstFromXFF) {
|
||||||
|
Server svr;
|
||||||
|
|
||||||
|
svr.set_trusted_proxies({"192.0.2.45"});
|
||||||
|
|
||||||
|
std::string observed_remote_addr;
|
||||||
|
std::string observed_xff;
|
||||||
|
|
||||||
|
svr.Get("/ip", [&](const Request &req, Response &res) {
|
||||||
|
observed_remote_addr = req.remote_addr;
|
||||||
|
observed_xff = req.get_header_value("X-Forwarded-For");
|
||||||
|
res.set_content("ok", "text/plain");
|
||||||
|
});
|
||||||
|
|
||||||
|
thread t = thread([&]() { svr.listen(HOST, PORT); });
|
||||||
|
auto se = detail::scope_exit([&] {
|
||||||
|
svr.stop();
|
||||||
|
t.join();
|
||||||
|
ASSERT_FALSE(svr.is_running());
|
||||||
|
});
|
||||||
|
|
||||||
|
svr.wait_until_ready();
|
||||||
|
|
||||||
|
Client cli(HOST, PORT);
|
||||||
|
auto res = cli.Get("/ip",
|
||||||
|
{{"X-Forwarded-For", "198.51.100.23, 198.51.100.24"}});
|
||||||
|
|
||||||
|
ASSERT_TRUE(res);
|
||||||
|
EXPECT_EQ(StatusCode::OK_200, res->status);
|
||||||
|
|
||||||
|
EXPECT_EQ(observed_xff, "198.51.100.23, 198.51.100.24");
|
||||||
|
EXPECT_EQ(observed_remote_addr, "198.51.100.23");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ForwardedHeadersTest, LastHopTrusted_SelectsImmediateLeftIP) {
|
||||||
|
Server svr;
|
||||||
|
|
||||||
|
svr.set_trusted_proxies({"192.0.2.45"});
|
||||||
|
|
||||||
|
std::string observed_remote_addr;
|
||||||
|
std::string observed_xff;
|
||||||
|
|
||||||
|
svr.Get("/ip", [&](const Request &req, Response &res) {
|
||||||
|
observed_remote_addr = req.remote_addr;
|
||||||
|
observed_xff = req.get_header_value("X-Forwarded-For");
|
||||||
|
res.set_content("ok", "text/plain");
|
||||||
|
});
|
||||||
|
|
||||||
|
thread t = thread([&]() { svr.listen(HOST, PORT); });
|
||||||
|
auto se = detail::scope_exit([&] {
|
||||||
|
svr.stop();
|
||||||
|
t.join();
|
||||||
|
ASSERT_FALSE(svr.is_running());
|
||||||
|
});
|
||||||
|
|
||||||
|
svr.wait_until_ready();
|
||||||
|
|
||||||
|
Client cli(HOST, PORT);
|
||||||
|
auto res = cli.Get(
|
||||||
|
"/ip",
|
||||||
|
{{"X-Forwarded-For", "198.51.100.23, 203.0.113.66, 192.0.2.45"}});
|
||||||
|
|
||||||
|
ASSERT_TRUE(res);
|
||||||
|
EXPECT_EQ(StatusCode::OK_200, res->status);
|
||||||
|
|
||||||
|
EXPECT_EQ(observed_xff, "198.51.100.23, 203.0.113.66, 192.0.2.45");
|
||||||
|
EXPECT_EQ(observed_remote_addr, "203.0.113.66");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ForwardedHeadersTest, HandlesWhitespaceAroundIPs) {
|
||||||
|
Server svr;
|
||||||
|
|
||||||
|
svr.set_trusted_proxies({"192.0.2.45"});
|
||||||
|
|
||||||
|
std::string observed_remote_addr;
|
||||||
|
std::string observed_xff;
|
||||||
|
|
||||||
|
svr.Get("/ip", [&](const Request &req, Response &res) {
|
||||||
|
observed_remote_addr = req.remote_addr;
|
||||||
|
observed_xff = req.get_header_value("X-Forwarded-For");
|
||||||
|
res.set_content("ok", "text/plain");
|
||||||
|
});
|
||||||
|
|
||||||
|
thread t = thread([&]() { svr.listen(HOST, PORT); });
|
||||||
|
auto se = detail::scope_exit([&] {
|
||||||
|
svr.stop();
|
||||||
|
t.join();
|
||||||
|
ASSERT_FALSE(svr.is_running());
|
||||||
|
});
|
||||||
|
|
||||||
|
svr.wait_until_ready();
|
||||||
|
|
||||||
|
Client cli(HOST, PORT);
|
||||||
|
auto res = cli.Get(
|
||||||
|
"/ip",
|
||||||
|
{{"X-Forwarded-For", " 198.51.100.23 , 203.0.113.66 , 192.0.2.45 "}});
|
||||||
|
|
||||||
|
ASSERT_TRUE(res);
|
||||||
|
EXPECT_EQ(StatusCode::OK_200, res->status);
|
||||||
|
|
||||||
|
// Header parser trims surrounding whitespace of the header value
|
||||||
|
EXPECT_EQ(observed_xff, "198.51.100.23 , 203.0.113.66 , 192.0.2.45");
|
||||||
|
EXPECT_EQ(observed_remote_addr, "203.0.113.66");
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user