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();
|
||||
}
|
||||
|
||||
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:
|
||||
// $remote_addr - $remote_user [$time_local] "$request" $status $body_bytes_sent
|
||||
// "$http_referer" "$http_user_agent"
|
||||
void nginx_access_logger(const Request &req, const Response &res) {
|
||||
auto remote_addr = get_client_ip(req);
|
||||
std::string remote_user =
|
||||
"-"; // cpp-httplib doesn't have built-in auth user tracking
|
||||
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 = "-";
|
||||
|
||||
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,
|
||||
http_user_agent)
|
||||
<< std::endl;
|
||||
@@ -100,7 +78,6 @@ void nginx_error_logger(const Error &err, const Request *req) {
|
||||
std::string level = "error";
|
||||
|
||||
if (req) {
|
||||
auto client_ip = get_client_ip(*req);
|
||||
auto request =
|
||||
std::format("{} {} {}", req->method, req->path, req->version);
|
||||
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: "
|
||||
"\"{}\", host: \"{}\"",
|
||||
time_local, level, to_string(err), client_ip,
|
||||
request, host)
|
||||
time_local, level, to_string(err),
|
||||
req->remote_addr, request, host)
|
||||
<< std::endl;
|
||||
} else {
|
||||
// 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::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::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;
|
||||
std::cout << " " << program_name
|
||||
<< " --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 {
|
||||
@@ -147,6 +131,7 @@ struct ServerConfig {
|
||||
int port = 8080;
|
||||
std::string mount_point = "/";
|
||||
std::string document_root = "./html";
|
||||
std::vector<std::string> trusted_proxies;
|
||||
};
|
||||
|
||||
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) {
|
||||
std::cout << CPPHTTPLIB_VERSION << std::endl;
|
||||
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 {
|
||||
std::cerr << "Error: Unknown option '" << argv[i] << "'" << std::endl;
|
||||
print_usage(argv[0]);
|
||||
@@ -218,6 +211,11 @@ bool setup_server(Server &svr, const ServerConfig &config) {
|
||||
svr.set_logger(nginx_access_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);
|
||||
if (!ret) {
|
||||
std::cerr
|
||||
@@ -285,6 +283,16 @@ int main(int argc, char *argv[]) {
|
||||
<< std::endl;
|
||||
std::cout << "Mount point: " << config.mount_point << " -> "
|
||||
<< 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;
|
||||
|
||||
auto ret = svr.listen(config.hostname, config.port);
|
||||
|
||||
78
httplib.h
78
httplib.h
@@ -1132,6 +1132,8 @@ public:
|
||||
Server &
|
||||
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_timeout(time_t sec);
|
||||
|
||||
@@ -1170,6 +1172,9 @@ protected:
|
||||
const std::function<void(Request &)> &setup_request);
|
||||
|
||||
std::atomic<socket_t> svr_sock_{INVALID_SOCKET};
|
||||
|
||||
std::vector<std::string> trusted_proxies_;
|
||||
|
||||
size_t keep_alive_max_count_ = CPPHTTPLIB_KEEPALIVE_MAX_COUNT;
|
||||
time_t keep_alive_timeout_sec_ = CPPHTTPLIB_KEEPALIVE_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
|
||||
|
||||
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) {
|
||||
if (is_prohibited_header_name(key)) { return false; }
|
||||
return headers.find(key) != headers.end();
|
||||
}
|
||||
|
||||
inline const char *get_header_value(const Headers &headers,
|
||||
const std::string &key, const char *def,
|
||||
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 it = rng.first;
|
||||
std::advance(it, static_cast<ssize_t>(id));
|
||||
@@ -7501,6 +7528,12 @@ inline Server &Server::set_header_writer(
|
||||
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) {
|
||||
keep_alive_max_count_ = count;
|
||||
return *this;
|
||||
@@ -8289,6 +8322,40 @@ inline bool Server::dispatch_request_for_content_reader(
|
||||
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
|
||||
Server::process_request(Stream &strm, const std::string &remote_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;
|
||||
}
|
||||
|
||||
req.remote_addr = remote_addr;
|
||||
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_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_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")) {
|
||||
const auto &accept_header = req.get_header_value("Accept");
|
||||
|
||||
260
test/test.cc
260
test/test.cc
@@ -3100,21 +3100,20 @@ protected:
|
||||
#endif
|
||||
.Get("/remote_addr",
|
||||
[&](const Request &req, Response &res) {
|
||||
auto remote_addr = req.headers.find("REMOTE_ADDR")->second;
|
||||
EXPECT_TRUE(req.has_header("REMOTE_PORT"));
|
||||
EXPECT_EQ(req.remote_addr, req.get_header_value("REMOTE_ADDR"));
|
||||
EXPECT_EQ(req.remote_port,
|
||||
std::stoi(req.get_header_value("REMOTE_PORT")));
|
||||
res.set_content(remote_addr.c_str(), "text/plain");
|
||||
ASSERT_FALSE(req.has_header("REMOTE_ADDR"));
|
||||
ASSERT_FALSE(req.has_header("REMOTE_PORT"));
|
||||
ASSERT_ANY_THROW(req.get_header_value("REMOTE_ADDR"));
|
||||
ASSERT_ANY_THROW(req.get_header_value("REMOTE_PORT"));
|
||||
res.set_content(req.remote_addr, "text/plain");
|
||||
})
|
||||
.Get("/local_addr",
|
||||
[&](const Request &req, Response &res) {
|
||||
EXPECT_TRUE(req.has_header("LOCAL_PORT"));
|
||||
EXPECT_TRUE(req.has_header("LOCAL_ADDR"));
|
||||
auto local_addr = req.get_header_value("LOCAL_ADDR");
|
||||
auto local_port = req.get_header_value("LOCAL_PORT");
|
||||
EXPECT_EQ(req.local_addr, local_addr);
|
||||
EXPECT_EQ(req.local_port, std::stoi(local_port));
|
||||
ASSERT_FALSE(req.has_header("LOCAL_ADDR"));
|
||||
ASSERT_FALSE(req.has_header("LOCAL_PORT"));
|
||||
ASSERT_ANY_THROW(req.get_header_value("LOCAL_ADDR"));
|
||||
ASSERT_ANY_THROW(req.get_header_value("LOCAL_PORT"));
|
||||
auto local_addr = req.local_addr;
|
||||
auto local_port = std::to_string(req.local_port);
|
||||
res.set_content(local_addr.append(":").append(local_port),
|
||||
"text/plain");
|
||||
})
|
||||
@@ -11208,3 +11207,240 @@ TEST(HeaderSmugglingTest, ChunkedTrailerHeadersMerged) {
|
||||
std::string 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