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