test_proxy.cc 10 KB

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