| #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("httpbin"); |
| ProxyTest(cli, true); |
| } |
| |
| #ifdef CPPHTTPLIB_SSL_ENABLED |
| TEST(ProxyTest, SSLBasic) { |
| SSLClient cli("httpbin"); |
| cli.enable_server_certificate_verification(false); |
| ProxyTest(cli, true); |
| } |
| |
| TEST(ProxyTest, NoSSLDigest) { |
| Client cli("httpbin"); |
| ProxyTest(cli, false); |
| } |
| |
| TEST(ProxyTest, SSLDigest) { |
| SSLClient cli("httpbin"); |
| cli.enable_server_certificate_verification(false); |
| 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("httpbin"); |
| RedirectProxyText(cli, "/redirect/2", true); |
| } |
| |
| #ifdef CPPHTTPLIB_SSL_ENABLED |
| TEST(RedirectTest, HTTPBinNoSSLDigest) { |
| Client cli("httpbin"); |
| RedirectProxyText(cli, "/redirect/2", false); |
| } |
| |
| TEST(RedirectTest, HTTPBinSSLBasic) { |
| SSLClient cli("httpbin"); |
| cli.enable_server_certificate_verification(false); |
| RedirectProxyText(cli, "/redirect/2", true); |
| } |
| |
| TEST(RedirectTest, HTTPBinSSLDigest) { |
| SSLClient cli("httpbin"); |
| cli.enable_server_certificate_verification(false); |
| 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", |
| Headers{make_basic_authentication_header("hello", "world")}); |
| ASSERT_TRUE(res != nullptr); |
| EXPECT_EQ(normalizeJson("{\"authenticated\":true,\"user\":\"hello\"," |
| "\"authorized\":true}\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\"," |
| "\"authorized\":true}\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("httpbin"); |
| BaseAuthTestFromHTTPWatch(cli); |
| } |
| |
| #ifdef CPPHTTPLIB_SSL_ENABLED |
| TEST(BaseAuthTest, SSL) { |
| SSLClient cli("httpbin"); |
| cli.enable_server_certificate_verification(false); |
| 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); |
| } |
| |
| { |
| // go-httpbin (the "httpbin" test double) only implements MD5 and |
| // SHA-256 for digest auth, so SHA-256 is as far as this can exercise |
| // the client's digest-auth algorithm selection end-to-end. |
| std::vector<std::string> paths = { |
| "/digest-auth/auth/hello/world/MD5", |
| "/digest-auth/auth/hello/world/SHA-256", |
| }; |
| |
| cli.set_digest_auth("hello", "world"); |
| for (auto path : paths) { |
| auto res = cli.Get(path.c_str()); |
| ASSERT_TRUE(res != nullptr); |
| EXPECT_EQ(normalizeJson("{\"authenticated\":true,\"user\":\"hello\"," |
| "\"authorized\":true}\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("httpbin"); |
| cli.enable_server_certificate_verification(false); |
| DigestAuthTestFromHTTPWatch(cli); |
| } |
| |
| TEST(DigestAuthTest, NoSSL) { |
| Client cli("httpbin"); |
| 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"); |
| ASSERT_TRUE(res != nullptr); |
| EXPECT_EQ(StatusCode::OK_200, res->status); |
| } |
| { |
| auto res = cli.Get("/redirect/2"); |
| ASSERT_TRUE(res != nullptr); |
| 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()); |
| ASSERT_TRUE(res != nullptr); |
| 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"); |
| ASSERT_TRUE(res != nullptr); |
| EXPECT_EQ(StatusCode::OK_200, res->status); |
| } |
| } |
| } |
| |
| #ifdef CPPHTTPLIB_SSL_ENABLED |
| TEST(KeepAliveTest, NoSSLWithBasic) { |
| Client cli("httpbin"); |
| KeepAliveTest(cli, true); |
| } |
| |
| TEST(KeepAliveTest, SSLWithBasic) { |
| SSLClient cli("httpbin"); |
| cli.enable_server_certificate_verification(false); |
| KeepAliveTest(cli, true); |
| } |
| |
| TEST(KeepAliveTest, NoSSLWithDigest) { |
| Client cli("httpbin"); |
| KeepAliveTest(cli, false); |
| } |
| |
| TEST(KeepAliveTest, SSLWithDigest) { |
| SSLClient cli("httpbin"); |
| cli.enable_server_certificate_verification(false); |
| KeepAliveTest(cli, false); |
| } |
| #endif |
| |
| // ---------------------------------------------------------------------------- |
| |
| #ifdef CPPHTTPLIB_SSL_ENABLED |
| TEST(ProxyTest, SSLOpenStream) { |
| SSLClient cli("httpbin"); |
| cli.enable_server_certificate_verification(false); |
| 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 |