test_proxy.cc 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368
  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("/httpbin/get");
  17. ASSERT_TRUE(res != nullptr);
  18. EXPECT_EQ(StatusCode::ProxyAuthenticationRequired_407, res->status);
  19. }
  20. TEST(ProxyTest, NoSSLBasic) {
  21. Client cli("nghttp2.org");
  22. ProxyTest(cli, true);
  23. }
  24. #ifdef CPPHTTPLIB_SSL_ENABLED
  25. TEST(ProxyTest, SSLBasic) {
  26. SSLClient cli("nghttp2.org");
  27. ProxyTest(cli, true);
  28. }
  29. TEST(ProxyTest, NoSSLDigest) {
  30. Client cli("nghttp2.org");
  31. ProxyTest(cli, false);
  32. }
  33. TEST(ProxyTest, SSLDigest) {
  34. SSLClient cli("nghttp2.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("nghttp2.org");
  56. RedirectProxyText(cli, "/httpbin/redirect/2", true);
  57. }
  58. #ifdef CPPHTTPLIB_SSL_ENABLED
  59. TEST(RedirectTest, HTTPBinNoSSLDigest) {
  60. Client cli("nghttp2.org");
  61. RedirectProxyText(cli, "/httpbin/redirect/2", false);
  62. }
  63. TEST(RedirectTest, HTTPBinSSLBasic) {
  64. SSLClient cli("nghttp2.org");
  65. RedirectProxyText(cli, "/httpbin/redirect/2", true);
  66. }
  67. TEST(RedirectTest, HTTPBinSSLDigest) {
  68. SSLClient cli("nghttp2.org");
  69. RedirectProxyText(cli, "/httpbin/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 = cli.Get("/basic-auth/hello/world",
  142. {make_basic_authentication_header("hello", "world")});
  143. ASSERT_TRUE(res != nullptr);
  144. EXPECT_EQ(normalizeJson("{\"authenticated\":true,\"user\":\"hello\"}\n"),
  145. normalizeJson(res->body));
  146. EXPECT_EQ(StatusCode::OK_200, res->status);
  147. }
  148. {
  149. cli.set_basic_auth("hello", "world");
  150. auto res = cli.Get("/basic-auth/hello/world");
  151. ASSERT_TRUE(res != nullptr);
  152. EXPECT_EQ(normalizeJson("{\"authenticated\":true,\"user\":\"hello\"}\n"),
  153. normalizeJson(res->body));
  154. EXPECT_EQ(StatusCode::OK_200, res->status);
  155. }
  156. {
  157. cli.set_basic_auth("hello", "bad");
  158. auto res = cli.Get("/basic-auth/hello/world");
  159. ASSERT_TRUE(res != nullptr);
  160. EXPECT_EQ(StatusCode::Unauthorized_401, res->status);
  161. }
  162. {
  163. cli.set_basic_auth("bad", "world");
  164. auto res = cli.Get("/basic-auth/hello/world");
  165. ASSERT_TRUE(res != nullptr);
  166. EXPECT_EQ(StatusCode::Unauthorized_401, res->status);
  167. }
  168. }
  169. TEST(BaseAuthTest, NoSSL) {
  170. Client cli("httpcan.org");
  171. BaseAuthTestFromHTTPWatch(cli);
  172. }
  173. #ifdef CPPHTTPLIB_SSL_ENABLED
  174. TEST(BaseAuthTest, SSL) {
  175. SSLClient cli("httpcan.org");
  176. BaseAuthTestFromHTTPWatch(cli);
  177. }
  178. #endif
  179. // ----------------------------------------------------------------------------
  180. #ifdef CPPHTTPLIB_SSL_ENABLED
  181. template <typename T> void DigestAuthTestFromHTTPWatch(T &cli) {
  182. cli.set_proxy("localhost", 3129);
  183. cli.set_proxy_digest_auth("hello", "world");
  184. {
  185. auto res = cli.Get("/digest-auth/auth/hello/world");
  186. ASSERT_TRUE(res != nullptr);
  187. EXPECT_EQ(StatusCode::Unauthorized_401, res->status);
  188. }
  189. {
  190. std::vector<std::string> paths = {
  191. "/digest-auth/auth/hello/world/MD5",
  192. "/digest-auth/auth/hello/world/SHA-256",
  193. "/digest-auth/auth/hello/world/SHA-512",
  194. };
  195. cli.set_digest_auth("hello", "world");
  196. for (auto path : paths) {
  197. auto res = cli.Get(path.c_str());
  198. ASSERT_TRUE(res != nullptr);
  199. std::string algo(path.substr(path.rfind('/') + 1));
  200. EXPECT_EQ(
  201. normalizeJson("{\"algorithm\":\"" + algo +
  202. "\",\"authenticated\":true,\"user\":\"hello\"}\n"),
  203. normalizeJson(res->body));
  204. EXPECT_EQ(StatusCode::OK_200, res->status);
  205. }
  206. cli.set_digest_auth("hello", "bad");
  207. for (auto path : paths) {
  208. auto res = cli.Get(path.c_str());
  209. ASSERT_TRUE(res != nullptr);
  210. EXPECT_EQ(StatusCode::Unauthorized_401, res->status);
  211. }
  212. cli.set_digest_auth("bad", "world");
  213. for (auto path : paths) {
  214. auto res = cli.Get(path.c_str());
  215. ASSERT_TRUE(res != nullptr);
  216. EXPECT_EQ(StatusCode::Unauthorized_401, res->status);
  217. }
  218. }
  219. }
  220. TEST(DigestAuthTest, SSL) {
  221. SSLClient cli("httpcan.org");
  222. DigestAuthTestFromHTTPWatch(cli);
  223. }
  224. TEST(DigestAuthTest, NoSSL) {
  225. Client cli("httpcan.org");
  226. DigestAuthTestFromHTTPWatch(cli);
  227. }
  228. #endif
  229. // ----------------------------------------------------------------------------
  230. template <typename T> void KeepAliveTest(T &cli, bool basic) {
  231. cli.set_proxy("localhost", basic ? 3128 : 3129);
  232. if (basic) {
  233. cli.set_proxy_basic_auth("hello", "world");
  234. } else {
  235. #ifdef CPPHTTPLIB_SSL_ENABLED
  236. cli.set_proxy_digest_auth("hello", "world");
  237. #endif
  238. }
  239. cli.set_follow_location(true);
  240. #ifdef CPPHTTPLIB_SSL_ENABLED
  241. cli.set_digest_auth("hello", "world");
  242. #endif
  243. {
  244. auto res = cli.Get("/httpbin/get");
  245. EXPECT_EQ(StatusCode::OK_200, res->status);
  246. }
  247. {
  248. auto res = cli.Get("/httpbin/redirect/2");
  249. EXPECT_EQ(StatusCode::OK_200, res->status);
  250. }
  251. {
  252. std::vector<std::string> paths = {
  253. "/httpbin/digest-auth/auth/hello/world/MD5",
  254. "/httpbin/digest-auth/auth/hello/world/SHA-256",
  255. "/httpbin/digest-auth/auth/hello/world/SHA-512",
  256. "/httpbin/digest-auth/auth-int/hello/world/MD5",
  257. };
  258. for (auto path : paths) {
  259. auto res = cli.Get(path.c_str());
  260. EXPECT_EQ(normalizeJson("{\"authenticated\":true,\"user\":\"hello\"}\n"),
  261. normalizeJson(res->body));
  262. EXPECT_EQ(StatusCode::OK_200, res->status);
  263. }
  264. }
  265. {
  266. int count = 10;
  267. while (count--) {
  268. auto res = cli.Get("/httpbin/get");
  269. EXPECT_EQ(StatusCode::OK_200, res->status);
  270. }
  271. }
  272. }
  273. #ifdef CPPHTTPLIB_SSL_ENABLED
  274. TEST(KeepAliveTest, NoSSLWithBasic) {
  275. Client cli("nghttp2.org");
  276. KeepAliveTest(cli, true);
  277. }
  278. TEST(KeepAliveTest, SSLWithBasic) {
  279. SSLClient cli("nghttp2.org");
  280. KeepAliveTest(cli, true);
  281. }
  282. TEST(KeepAliveTest, NoSSLWithDigest) {
  283. Client cli("nghttp2.org");
  284. KeepAliveTest(cli, false);
  285. }
  286. TEST(KeepAliveTest, SSLWithDigest) {
  287. SSLClient cli("nghttp2.org");
  288. KeepAliveTest(cli, false);
  289. }
  290. #endif
  291. // ----------------------------------------------------------------------------
  292. #ifdef CPPHTTPLIB_SSL_ENABLED
  293. TEST(ProxyTest, SSLOpenStream) {
  294. SSLClient cli("nghttp2.org");
  295. cli.set_proxy("localhost", 3128);
  296. cli.set_proxy_basic_auth("hello", "world");
  297. auto handle = cli.open_stream("GET", "/httpbin/get");
  298. ASSERT_TRUE(handle.response);
  299. EXPECT_EQ(StatusCode::OK_200, handle.response->status);
  300. std::string body;
  301. char buf[8192];
  302. ssize_t n;
  303. while ((n = handle.read(buf, sizeof(buf))) > 0) {
  304. body.append(buf, static_cast<size_t>(n));
  305. }
  306. EXPECT_FALSE(body.empty());
  307. }
  308. #endif