test_proxy.cc 10.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376
  1. #include <chrono>
  2. #include <future>
  3. #include <gtest/gtest.h>
  4. #include <httplib.h>
  5. using namespace std;
  6. using namespace httplib;
  7. std::string normalizeJson(const std::string &json) {
  8. std::string result;
  9. for (char c : json) {
  10. if (c != ' ' && c != '\t' && c != '\n' && c != '\r') { result += c; }
  11. }
  12. return result;
  13. }
  14. template <typename T> void ProxyTest(T &cli, bool basic) {
  15. cli.set_proxy("localhost", basic ? 3128 : 3129);
  16. auto res = cli.Get("/get");
  17. ASSERT_TRUE(res != nullptr);
  18. EXPECT_EQ(StatusCode::ProxyAuthenticationRequired_407, res->status);
  19. }
  20. TEST(ProxyTest, NoSSLBasic) {
  21. Client cli("httpbingo.org");
  22. ProxyTest(cli, true);
  23. }
  24. #ifdef CPPHTTPLIB_SSL_ENABLED
  25. TEST(ProxyTest, SSLBasic) {
  26. SSLClient cli("httpbingo.org");
  27. ProxyTest(cli, true);
  28. }
  29. TEST(ProxyTest, NoSSLDigest) {
  30. Client cli("httpbingo.org");
  31. ProxyTest(cli, false);
  32. }
  33. TEST(ProxyTest, SSLDigest) {
  34. SSLClient cli("httpbingo.org");
  35. ProxyTest(cli, false);
  36. }
  37. #endif
  38. // ----------------------------------------------------------------------------
  39. template <typename T>
  40. void RedirectProxyText(T &cli, const char *path, bool basic) {
  41. cli.set_proxy("localhost", basic ? 3128 : 3129);
  42. if (basic) {
  43. cli.set_proxy_basic_auth("hello", "world");
  44. } else {
  45. #ifdef CPPHTTPLIB_SSL_ENABLED
  46. cli.set_proxy_digest_auth("hello", "world");
  47. #endif
  48. }
  49. cli.set_follow_location(true);
  50. auto res = cli.Get(path);
  51. ASSERT_TRUE(res != nullptr);
  52. EXPECT_EQ(StatusCode::OK_200, res->status);
  53. }
  54. TEST(RedirectTest, HTTPBinNoSSLBasic) {
  55. Client cli("httpbingo.org");
  56. RedirectProxyText(cli, "/redirect/2", true);
  57. }
  58. #ifdef CPPHTTPLIB_SSL_ENABLED
  59. TEST(RedirectTest, HTTPBinNoSSLDigest) {
  60. Client cli("httpbingo.org");
  61. RedirectProxyText(cli, "/redirect/2", false);
  62. }
  63. TEST(RedirectTest, HTTPBinSSLBasic) {
  64. SSLClient cli("httpbingo.org");
  65. RedirectProxyText(cli, "/redirect/2", true);
  66. }
  67. TEST(RedirectTest, HTTPBinSSLDigest) {
  68. SSLClient cli("httpbingo.org");
  69. RedirectProxyText(cli, "/redirect/2", false);
  70. }
  71. #endif
  72. #ifdef CPPHTTPLIB_SSL_ENABLED
  73. TEST(RedirectTest, YouTubeNoSSLBasic) {
  74. Client cli("youtube.com");
  75. RedirectProxyText(cli, "/", true);
  76. }
  77. TEST(RedirectTest, YouTubeNoSSLDigest) {
  78. Client cli("youtube.com");
  79. RedirectProxyText(cli, "/", false);
  80. }
  81. TEST(RedirectTest, YouTubeSSLBasic) {
  82. SSLClient cli("youtube.com");
  83. RedirectProxyText(cli, "/", true);
  84. }
  85. TEST(RedirectTest, YouTubeSSLDigest) {
  86. std::this_thread::sleep_for(std::chrono::seconds(3));
  87. SSLClient cli("youtube.com");
  88. RedirectProxyText(cli, "/", false);
  89. }
  90. #endif
  91. // ----------------------------------------------------------------------------
  92. #ifdef CPPHTTPLIB_SSL_ENABLED
  93. TEST(RedirectTest, TLSVerificationOnProxyRedirect) {
  94. // Untrusted HTTPS server with self-signed cert
  95. SSLServer untrusted_svr("cert.pem", "key.pem");
  96. untrusted_svr.Get("/", [](const Request &, Response &res) {
  97. res.set_content("MITM'd", "text/plain");
  98. });
  99. auto untrusted_port = untrusted_svr.bind_to_any_port("0.0.0.0");
  100. auto t1 = thread([&]() { untrusted_svr.listen_after_bind(); });
  101. auto se1 = detail::scope_exit([&] {
  102. untrusted_svr.stop();
  103. t1.join();
  104. });
  105. // HTTP server that redirects to the untrusted HTTPS server
  106. // Use host.docker.internal so the proxy container can reach the host
  107. Server redirect_svr;
  108. redirect_svr.Get("/", [&](const Request &, Response &res) {
  109. res.set_redirect(
  110. "https://host.docker.internal:" + to_string(untrusted_port) + "/");
  111. });
  112. auto redirect_port = redirect_svr.bind_to_any_port("0.0.0.0");
  113. auto t2 = thread([&]() { redirect_svr.listen_after_bind(); });
  114. auto se2 = detail::scope_exit([&] {
  115. redirect_svr.stop();
  116. t2.join();
  117. });
  118. // Wait until servers are up
  119. untrusted_svr.wait_until_ready();
  120. redirect_svr.wait_until_ready();
  121. // Client with proxy + follow_location, verification ON (default)
  122. Client cli("host.docker.internal", redirect_port);
  123. cli.set_proxy("localhost", 3128);
  124. cli.set_proxy_basic_auth("hello", "world");
  125. cli.set_follow_location(true);
  126. auto res = cli.Get("/");
  127. // Self-signed cert must be rejected
  128. ASSERT_TRUE(res == nullptr);
  129. }
  130. #endif
  131. // ----------------------------------------------------------------------------
  132. template <typename T> void BaseAuthTestFromHTTPWatch(T &cli) {
  133. cli.set_proxy("localhost", 3128);
  134. cli.set_proxy_basic_auth("hello", "world");
  135. {
  136. auto res = cli.Get("/basic-auth/hello/world");
  137. ASSERT_TRUE(res != nullptr);
  138. EXPECT_EQ(StatusCode::Unauthorized_401, res->status);
  139. }
  140. {
  141. auto res =
  142. cli.Get("/basic-auth/hello/world",
  143. Headers{make_basic_authentication_header("hello", "world")});
  144. ASSERT_TRUE(res != nullptr);
  145. EXPECT_EQ(normalizeJson("{\"authenticated\":true,\"user\":\"hello\","
  146. "\"authorized\":true}\n"),
  147. normalizeJson(res->body));
  148. EXPECT_EQ(StatusCode::OK_200, res->status);
  149. }
  150. {
  151. cli.set_basic_auth("hello", "world");
  152. auto res = cli.Get("/basic-auth/hello/world");
  153. ASSERT_TRUE(res != nullptr);
  154. EXPECT_EQ(normalizeJson("{\"authenticated\":true,\"user\":\"hello\","
  155. "\"authorized\":true}\n"),
  156. normalizeJson(res->body));
  157. EXPECT_EQ(StatusCode::OK_200, res->status);
  158. }
  159. {
  160. cli.set_basic_auth("hello", "bad");
  161. auto res = cli.Get("/basic-auth/hello/world");
  162. ASSERT_TRUE(res != nullptr);
  163. EXPECT_EQ(StatusCode::Unauthorized_401, res->status);
  164. }
  165. {
  166. cli.set_basic_auth("bad", "world");
  167. auto res = cli.Get("/basic-auth/hello/world");
  168. ASSERT_TRUE(res != nullptr);
  169. EXPECT_EQ(StatusCode::Unauthorized_401, res->status);
  170. }
  171. }
  172. TEST(BaseAuthTest, NoSSL) {
  173. Client cli("httpbin");
  174. BaseAuthTestFromHTTPWatch(cli);
  175. }
  176. #ifdef CPPHTTPLIB_SSL_ENABLED
  177. TEST(BaseAuthTest, SSL) {
  178. SSLClient cli("httpbin");
  179. cli.enable_server_certificate_verification(false);
  180. BaseAuthTestFromHTTPWatch(cli);
  181. }
  182. #endif
  183. // ----------------------------------------------------------------------------
  184. #ifdef CPPHTTPLIB_SSL_ENABLED
  185. template <typename T> void DigestAuthTestFromHTTPWatch(T &cli) {
  186. cli.set_proxy("localhost", 3129);
  187. cli.set_proxy_digest_auth("hello", "world");
  188. {
  189. auto res = cli.Get("/digest-auth/auth/hello/world");
  190. ASSERT_TRUE(res != nullptr);
  191. EXPECT_EQ(StatusCode::Unauthorized_401, res->status);
  192. }
  193. {
  194. // go-httpbin (the "httpbin" test double) only implements MD5 and
  195. // SHA-256 for digest auth, so SHA-256 is as far as this can exercise
  196. // the client's digest-auth algorithm selection end-to-end.
  197. std::vector<std::string> paths = {
  198. "/digest-auth/auth/hello/world/MD5",
  199. "/digest-auth/auth/hello/world/SHA-256",
  200. };
  201. cli.set_digest_auth("hello", "world");
  202. for (auto path : paths) {
  203. auto res = cli.Get(path.c_str());
  204. ASSERT_TRUE(res != nullptr);
  205. EXPECT_EQ(normalizeJson("{\"authenticated\":true,\"user\":\"hello\","
  206. "\"authorized\":true}\n"),
  207. normalizeJson(res->body));
  208. EXPECT_EQ(StatusCode::OK_200, res->status);
  209. }
  210. cli.set_digest_auth("hello", "bad");
  211. for (auto path : paths) {
  212. auto res = cli.Get(path.c_str());
  213. ASSERT_TRUE(res != nullptr);
  214. EXPECT_EQ(StatusCode::Unauthorized_401, res->status);
  215. }
  216. cli.set_digest_auth("bad", "world");
  217. for (auto path : paths) {
  218. auto res = cli.Get(path.c_str());
  219. ASSERT_TRUE(res != nullptr);
  220. EXPECT_EQ(StatusCode::Unauthorized_401, res->status);
  221. }
  222. }
  223. }
  224. TEST(DigestAuthTest, SSL) {
  225. SSLClient cli("httpbin");
  226. cli.enable_server_certificate_verification(false);
  227. DigestAuthTestFromHTTPWatch(cli);
  228. }
  229. TEST(DigestAuthTest, NoSSL) {
  230. Client cli("httpbin");
  231. DigestAuthTestFromHTTPWatch(cli);
  232. }
  233. #endif
  234. // ----------------------------------------------------------------------------
  235. template <typename T> void KeepAliveTest(T &cli, bool basic) {
  236. cli.set_proxy("localhost", basic ? 3128 : 3129);
  237. if (basic) {
  238. cli.set_proxy_basic_auth("hello", "world");
  239. } else {
  240. #ifdef CPPHTTPLIB_SSL_ENABLED
  241. cli.set_proxy_digest_auth("hello", "world");
  242. #endif
  243. }
  244. cli.set_follow_location(true);
  245. #ifdef CPPHTTPLIB_SSL_ENABLED
  246. cli.set_digest_auth("hello", "world");
  247. #endif
  248. {
  249. auto res = cli.Get("/get");
  250. ASSERT_TRUE(res != nullptr);
  251. EXPECT_EQ(StatusCode::OK_200, res->status);
  252. }
  253. {
  254. auto res = cli.Get("/redirect/2");
  255. ASSERT_TRUE(res != nullptr);
  256. EXPECT_EQ(StatusCode::OK_200, res->status);
  257. }
  258. {
  259. std::vector<std::string> paths = {
  260. "/digest-auth/auth/hello/world/MD5",
  261. "/digest-auth/auth/hello/world/SHA-256",
  262. };
  263. for (auto path : paths) {
  264. auto res = cli.Get(path.c_str());
  265. ASSERT_TRUE(res != nullptr);
  266. auto body = normalizeJson(res->body);
  267. EXPECT_TRUE(body.find("\"authenticated\":true") != std::string::npos);
  268. EXPECT_TRUE(body.find("\"user\":\"hello\"") != std::string::npos);
  269. EXPECT_EQ(StatusCode::OK_200, res->status);
  270. }
  271. }
  272. {
  273. int count = 10;
  274. while (count--) {
  275. auto res = cli.Get("/get");
  276. ASSERT_TRUE(res != nullptr);
  277. EXPECT_EQ(StatusCode::OK_200, res->status);
  278. }
  279. }
  280. }
  281. #ifdef CPPHTTPLIB_SSL_ENABLED
  282. TEST(KeepAliveTest, NoSSLWithBasic) {
  283. Client cli("httpbingo.org");
  284. KeepAliveTest(cli, true);
  285. }
  286. TEST(KeepAliveTest, SSLWithBasic) {
  287. SSLClient cli("httpbingo.org");
  288. KeepAliveTest(cli, true);
  289. }
  290. TEST(KeepAliveTest, NoSSLWithDigest) {
  291. Client cli("httpbingo.org");
  292. KeepAliveTest(cli, false);
  293. }
  294. TEST(KeepAliveTest, SSLWithDigest) {
  295. SSLClient cli("httpbingo.org");
  296. KeepAliveTest(cli, false);
  297. }
  298. #endif
  299. // ----------------------------------------------------------------------------
  300. #ifdef CPPHTTPLIB_SSL_ENABLED
  301. TEST(ProxyTest, SSLOpenStream) {
  302. SSLClient cli("httpbingo.org");
  303. cli.set_proxy("localhost", 3128);
  304. cli.set_proxy_basic_auth("hello", "world");
  305. auto handle = cli.open_stream("GET", "/get");
  306. ASSERT_TRUE(handle.response);
  307. EXPECT_EQ(StatusCode::OK_200, handle.response->status);
  308. std::string body;
  309. char buf[8192];
  310. ssize_t n;
  311. while ((n = handle.read(buf, sizeof(buf))) > 0) {
  312. body.append(buf, static_cast<size_t>(n));
  313. }
  314. EXPECT_FALSE(body.empty());
  315. }
  316. #endif