Files
cpp-httplib/test/test_proxy.cc
2026-03-27 22:26:14 -04:00

368 lines
9.5 KiB
C++

#include <chrono>
#include <future>
#include <gtest/gtest.h>
#include <httplib.h>
using namespace std;
using namespace httplib;
std::string normalizeJson(const std::string &json) {
std::string result;
for (char c : json) {
if (c != ' ' && c != '\t' && c != '\n' && c != '\r') { result += c; }
}
return result;
}
template <typename T> void ProxyTest(T &cli, bool basic) {
cli.set_proxy("localhost", basic ? 3128 : 3129);
auto res = cli.Get("/get");
ASSERT_TRUE(res != nullptr);
EXPECT_EQ(StatusCode::ProxyAuthenticationRequired_407, res->status);
}
TEST(ProxyTest, NoSSLBasic) {
Client cli("httpbingo.org");
ProxyTest(cli, true);
}
#ifdef CPPHTTPLIB_SSL_ENABLED
TEST(ProxyTest, SSLBasic) {
SSLClient cli("httpbingo.org");
ProxyTest(cli, true);
}
TEST(ProxyTest, NoSSLDigest) {
Client cli("httpbingo.org");
ProxyTest(cli, false);
}
TEST(ProxyTest, SSLDigest) {
SSLClient cli("httpbingo.org");
ProxyTest(cli, false);
}
#endif
// ----------------------------------------------------------------------------
template <typename T>
void RedirectProxyText(T &cli, const char *path, bool basic) {
cli.set_proxy("localhost", basic ? 3128 : 3129);
if (basic) {
cli.set_proxy_basic_auth("hello", "world");
} else {
#ifdef CPPHTTPLIB_SSL_ENABLED
cli.set_proxy_digest_auth("hello", "world");
#endif
}
cli.set_follow_location(true);
auto res = cli.Get(path);
ASSERT_TRUE(res != nullptr);
EXPECT_EQ(StatusCode::OK_200, res->status);
}
TEST(RedirectTest, HTTPBinNoSSLBasic) {
Client cli("httpbingo.org");
RedirectProxyText(cli, "/redirect/2", true);
}
#ifdef CPPHTTPLIB_SSL_ENABLED
TEST(RedirectTest, HTTPBinNoSSLDigest) {
Client cli("httpbingo.org");
RedirectProxyText(cli, "/redirect/2", false);
}
TEST(RedirectTest, HTTPBinSSLBasic) {
SSLClient cli("httpbingo.org");
RedirectProxyText(cli, "/redirect/2", true);
}
TEST(RedirectTest, HTTPBinSSLDigest) {
SSLClient cli("httpbingo.org");
RedirectProxyText(cli, "/redirect/2", false);
}
#endif
#ifdef CPPHTTPLIB_SSL_ENABLED
TEST(RedirectTest, YouTubeNoSSLBasic) {
Client cli("youtube.com");
RedirectProxyText(cli, "/", true);
}
TEST(RedirectTest, YouTubeNoSSLDigest) {
Client cli("youtube.com");
RedirectProxyText(cli, "/", false);
}
TEST(RedirectTest, YouTubeSSLBasic) {
SSLClient cli("youtube.com");
RedirectProxyText(cli, "/", true);
}
TEST(RedirectTest, YouTubeSSLDigest) {
std::this_thread::sleep_for(std::chrono::seconds(3));
SSLClient cli("youtube.com");
RedirectProxyText(cli, "/", false);
}
#endif
// ----------------------------------------------------------------------------
#ifdef CPPHTTPLIB_SSL_ENABLED
TEST(RedirectTest, TLSVerificationOnProxyRedirect) {
// Untrusted HTTPS server with self-signed cert
SSLServer untrusted_svr("cert.pem", "key.pem");
untrusted_svr.Get("/", [](const Request &, Response &res) {
res.set_content("MITM'd", "text/plain");
});
auto untrusted_port = untrusted_svr.bind_to_any_port("0.0.0.0");
auto t1 = thread([&]() { untrusted_svr.listen_after_bind(); });
auto se1 = detail::scope_exit([&] {
untrusted_svr.stop();
t1.join();
});
// HTTP server that redirects to the untrusted HTTPS server
// Use host.docker.internal so the proxy container can reach the host
Server redirect_svr;
redirect_svr.Get("/", [&](const Request &, Response &res) {
res.set_redirect(
"https://host.docker.internal:" + to_string(untrusted_port) + "/");
});
auto redirect_port = redirect_svr.bind_to_any_port("0.0.0.0");
auto t2 = thread([&]() { redirect_svr.listen_after_bind(); });
auto se2 = detail::scope_exit([&] {
redirect_svr.stop();
t2.join();
});
// Wait until servers are up
untrusted_svr.wait_until_ready();
redirect_svr.wait_until_ready();
// Client with proxy + follow_location, verification ON (default)
Client cli("host.docker.internal", redirect_port);
cli.set_proxy("localhost", 3128);
cli.set_proxy_basic_auth("hello", "world");
cli.set_follow_location(true);
auto res = cli.Get("/");
// Self-signed cert must be rejected
ASSERT_TRUE(res == nullptr);
}
#endif
// ----------------------------------------------------------------------------
template <typename T> void BaseAuthTestFromHTTPWatch(T &cli) {
cli.set_proxy("localhost", 3128);
cli.set_proxy_basic_auth("hello", "world");
{
auto res = cli.Get("/basic-auth/hello/world");
ASSERT_TRUE(res != nullptr);
EXPECT_EQ(StatusCode::Unauthorized_401, res->status);
}
{
auto res = cli.Get("/basic-auth/hello/world",
{make_basic_authentication_header("hello", "world")});
ASSERT_TRUE(res != nullptr);
EXPECT_EQ(normalizeJson("{\"authenticated\":true,\"user\":\"hello\"}\n"),
normalizeJson(res->body));
EXPECT_EQ(StatusCode::OK_200, res->status);
}
{
cli.set_basic_auth("hello", "world");
auto res = cli.Get("/basic-auth/hello/world");
ASSERT_TRUE(res != nullptr);
EXPECT_EQ(normalizeJson("{\"authenticated\":true,\"user\":\"hello\"}\n"),
normalizeJson(res->body));
EXPECT_EQ(StatusCode::OK_200, res->status);
}
{
cli.set_basic_auth("hello", "bad");
auto res = cli.Get("/basic-auth/hello/world");
ASSERT_TRUE(res != nullptr);
EXPECT_EQ(StatusCode::Unauthorized_401, res->status);
}
{
cli.set_basic_auth("bad", "world");
auto res = cli.Get("/basic-auth/hello/world");
ASSERT_TRUE(res != nullptr);
EXPECT_EQ(StatusCode::Unauthorized_401, res->status);
}
}
TEST(BaseAuthTest, NoSSL) {
Client cli("httpcan.org");
BaseAuthTestFromHTTPWatch(cli);
}
#ifdef CPPHTTPLIB_SSL_ENABLED
TEST(BaseAuthTest, SSL) {
SSLClient cli("httpcan.org");
BaseAuthTestFromHTTPWatch(cli);
}
#endif
// ----------------------------------------------------------------------------
#ifdef CPPHTTPLIB_SSL_ENABLED
template <typename T> void DigestAuthTestFromHTTPWatch(T &cli) {
cli.set_proxy("localhost", 3129);
cli.set_proxy_digest_auth("hello", "world");
{
auto res = cli.Get("/digest-auth/auth/hello/world");
ASSERT_TRUE(res != nullptr);
EXPECT_EQ(StatusCode::Unauthorized_401, res->status);
}
{
std::vector<std::string> paths = {
"/digest-auth/auth/hello/world/MD5",
"/digest-auth/auth/hello/world/SHA-256",
"/digest-auth/auth/hello/world/SHA-512",
};
cli.set_digest_auth("hello", "world");
for (auto path : paths) {
auto res = cli.Get(path.c_str());
ASSERT_TRUE(res != nullptr);
std::string algo(path.substr(path.rfind('/') + 1));
EXPECT_EQ(
normalizeJson("{\"algorithm\":\"" + algo +
"\",\"authenticated\":true,\"user\":\"hello\"}\n"),
normalizeJson(res->body));
EXPECT_EQ(StatusCode::OK_200, res->status);
}
cli.set_digest_auth("hello", "bad");
for (auto path : paths) {
auto res = cli.Get(path.c_str());
ASSERT_TRUE(res != nullptr);
EXPECT_EQ(StatusCode::Unauthorized_401, res->status);
}
cli.set_digest_auth("bad", "world");
for (auto path : paths) {
auto res = cli.Get(path.c_str());
ASSERT_TRUE(res != nullptr);
EXPECT_EQ(StatusCode::Unauthorized_401, res->status);
}
}
}
TEST(DigestAuthTest, SSL) {
SSLClient cli("httpcan.org");
DigestAuthTestFromHTTPWatch(cli);
}
TEST(DigestAuthTest, NoSSL) {
Client cli("httpcan.org");
DigestAuthTestFromHTTPWatch(cli);
}
#endif
// ----------------------------------------------------------------------------
template <typename T> void KeepAliveTest(T &cli, bool basic) {
cli.set_proxy("localhost", basic ? 3128 : 3129);
if (basic) {
cli.set_proxy_basic_auth("hello", "world");
} else {
#ifdef CPPHTTPLIB_SSL_ENABLED
cli.set_proxy_digest_auth("hello", "world");
#endif
}
cli.set_follow_location(true);
#ifdef CPPHTTPLIB_SSL_ENABLED
cli.set_digest_auth("hello", "world");
#endif
{
auto res = cli.Get("/get");
EXPECT_EQ(StatusCode::OK_200, res->status);
}
{
auto res = cli.Get("/redirect/2");
EXPECT_EQ(StatusCode::OK_200, res->status);
}
{
std::vector<std::string> paths = {
"/digest-auth/auth/hello/world/MD5",
"/digest-auth/auth/hello/world/SHA-256",
};
for (auto path : paths) {
auto res = cli.Get(path.c_str());
auto body = normalizeJson(res->body);
EXPECT_TRUE(body.find("\"authenticated\":true") != std::string::npos);
EXPECT_TRUE(body.find("\"user\":\"hello\"") != std::string::npos);
EXPECT_EQ(StatusCode::OK_200, res->status);
}
}
{
int count = 10;
while (count--) {
auto res = cli.Get("/get");
EXPECT_EQ(StatusCode::OK_200, res->status);
}
}
}
#ifdef CPPHTTPLIB_SSL_ENABLED
TEST(KeepAliveTest, NoSSLWithBasic) {
Client cli("httpbingo.org");
KeepAliveTest(cli, true);
}
TEST(KeepAliveTest, SSLWithBasic) {
SSLClient cli("httpbingo.org");
KeepAliveTest(cli, true);
}
TEST(KeepAliveTest, NoSSLWithDigest) {
Client cli("httpbingo.org");
KeepAliveTest(cli, false);
}
TEST(KeepAliveTest, SSLWithDigest) {
SSLClient cli("httpbingo.org");
KeepAliveTest(cli, false);
}
#endif
// ----------------------------------------------------------------------------
#ifdef CPPHTTPLIB_SSL_ENABLED
TEST(ProxyTest, SSLOpenStream) {
SSLClient cli("httpbingo.org");
cli.set_proxy("localhost", 3128);
cli.set_proxy_basic_auth("hello", "world");
auto handle = cli.open_stream("GET", "/get");
ASSERT_TRUE(handle.response);
EXPECT_EQ(StatusCode::OK_200, handle.response->status);
std::string body;
char buf[8192];
ssize_t n;
while ((n = handle.read(buf, sizeof(buf))) > 0) {
body.append(buf, static_cast<size_t>(n));
}
EXPECT_FALSE(body.empty());
}
#endif