test_10_proxy.py 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370
  1. #!/usr/bin/env python3
  2. # -*- coding: utf-8 -*-
  3. #***************************************************************************
  4. # _ _ ____ _
  5. # Project ___| | | | _ \| |
  6. # / __| | | | |_) | |
  7. # | (__| |_| | _ <| |___
  8. # \___|\___/|_| \_\_____|
  9. #
  10. # Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  11. #
  12. # This software is licensed as described in the file COPYING, which
  13. # you should have received as part of this distribution. The terms
  14. # are also available at https://curl.se/docs/copyright.html.
  15. #
  16. # You may opt to use, copy, modify, merge, publish, distribute and/or sell
  17. # copies of the Software, and permit persons to whom the Software is
  18. # furnished to do so, under the terms of the COPYING file.
  19. #
  20. # This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
  21. # KIND, either express or implied.
  22. #
  23. # SPDX-License-Identifier: curl
  24. #
  25. ###########################################################################
  26. #
  27. import filecmp
  28. import logging
  29. import os
  30. import re
  31. import pytest
  32. from testenv import Env, CurlClient, ExecResult
  33. log = logging.getLogger(__name__)
  34. class TestProxy:
  35. @pytest.fixture(autouse=True, scope='class')
  36. def _class_scope(self, env, httpd, nghttpx_fwd):
  37. push_dir = os.path.join(httpd.docs_dir, 'push')
  38. if not os.path.exists(push_dir):
  39. os.makedirs(push_dir)
  40. if env.have_nghttpx():
  41. nghttpx_fwd.start_if_needed()
  42. env.make_data_file(indir=env.gen_dir, fname="data-100k", fsize=100*1024)
  43. env.make_data_file(indir=env.gen_dir, fname="data-10m", fsize=10*1024*1024)
  44. httpd.clear_extra_configs()
  45. httpd.reload()
  46. def get_tunnel_proto_used(self, r: ExecResult):
  47. for l in r.trace_lines:
  48. m = re.match(r'.* CONNECT tunnel: (\S+) negotiated$', l)
  49. if m:
  50. return m.group(1)
  51. assert False, f'tunnel protocol not found in:\n{"".join(r.trace_lines)}'
  52. return None
  53. # download via http: proxy (no tunnel)
  54. def test_10_01_proxy_http(self, env: Env, httpd, repeat):
  55. curl = CurlClient(env=env)
  56. url = f'http://localhost:{env.http_port}/data.json'
  57. r = curl.http_download(urls=[url], alpn_proto='http/1.1', with_stats=True,
  58. extra_args=curl.get_proxy_args(proxys=False))
  59. r.check_response(count=1, http_status=200)
  60. # download via https: proxy (no tunnel)
  61. @pytest.mark.skipif(condition=not Env.curl_has_feature('HTTPS-proxy'),
  62. reason='curl lacks HTTPS-proxy support')
  63. @pytest.mark.parametrize("proto", ['http/1.1', 'h2'])
  64. def test_10_02_proxys_down(self, env: Env, httpd, proto, repeat):
  65. if proto == 'h2' and not env.curl_uses_lib('nghttp2'):
  66. pytest.skip('only supported with nghttp2')
  67. curl = CurlClient(env=env)
  68. url = f'http://localhost:{env.http_port}/data.json'
  69. xargs = curl.get_proxy_args(proto=proto)
  70. r = curl.http_download(urls=[url], alpn_proto='http/1.1', with_stats=True,
  71. extra_args=xargs)
  72. r.check_response(count=1, http_status=200,
  73. protocol='HTTP/2' if proto == 'h2' else 'HTTP/1.1')
  74. # upload via https: with proto (no tunnel)
  75. @pytest.mark.skipif(condition=not Env.have_ssl_curl(), reason=f"curl without SSL")
  76. @pytest.mark.parametrize("proto", ['http/1.1', 'h2'])
  77. @pytest.mark.parametrize("fname, fcount", [
  78. ['data.json', 5],
  79. ['data-100k', 5],
  80. ['data-1m', 2]
  81. ])
  82. @pytest.mark.skipif(condition=not Env.have_nghttpx(),
  83. reason="no nghttpx available")
  84. def test_10_02_proxys_up(self, env: Env, httpd, nghttpx, proto,
  85. fname, fcount, repeat):
  86. if proto == 'h2' and not env.curl_uses_lib('nghttp2'):
  87. pytest.skip('only supported with nghttp2')
  88. count = fcount
  89. srcfile = os.path.join(httpd.docs_dir, fname)
  90. curl = CurlClient(env=env)
  91. url = f'http://localhost:{env.http_port}/curltest/echo?id=[0-{count-1}]'
  92. xargs = curl.get_proxy_args(proto=proto)
  93. r = curl.http_upload(urls=[url], data=f'@{srcfile}', alpn_proto=proto,
  94. extra_args=xargs)
  95. r.check_response(count=count, http_status=200,
  96. protocol='HTTP/2' if proto == 'h2' else 'HTTP/1.1')
  97. indata = open(srcfile).readlines()
  98. for i in range(count):
  99. respdata = open(curl.response_file(i)).readlines()
  100. assert respdata == indata
  101. # download http: via http: proxytunnel
  102. def test_10_03_proxytunnel_http(self, env: Env, httpd, repeat):
  103. curl = CurlClient(env=env)
  104. url = f'http://localhost:{env.http_port}/data.json'
  105. xargs = curl.get_proxy_args(proxys=False, tunnel=True)
  106. r = curl.http_download(urls=[url], alpn_proto='http/1.1', with_stats=True,
  107. extra_args=xargs)
  108. r.check_response(count=1, http_status=200)
  109. # download http: via https: proxytunnel
  110. @pytest.mark.skipif(condition=not Env.curl_has_feature('HTTPS-proxy'),
  111. reason='curl lacks HTTPS-proxy support')
  112. @pytest.mark.skipif(condition=not Env.have_nghttpx(), reason="no nghttpx available")
  113. def test_10_04_proxy_https(self, env: Env, httpd, nghttpx_fwd, repeat):
  114. curl = CurlClient(env=env)
  115. url = f'http://localhost:{env.http_port}/data.json'
  116. xargs = curl.get_proxy_args(tunnel=True)
  117. r = curl.http_download(urls=[url], alpn_proto='http/1.1', with_stats=True,
  118. extra_args=xargs)
  119. r.check_response(count=1, http_status=200)
  120. # download https: with proto via http: proxytunnel
  121. @pytest.mark.parametrize("proto", ['http/1.1', 'h2'])
  122. @pytest.mark.skipif(condition=not Env.have_ssl_curl(), reason=f"curl without SSL")
  123. def test_10_05_proxytunnel_http(self, env: Env, httpd, proto, repeat):
  124. curl = CurlClient(env=env)
  125. url = f'https://localhost:{env.https_port}/data.json'
  126. xargs = curl.get_proxy_args(proxys=False, tunnel=True)
  127. r = curl.http_download(urls=[url], alpn_proto=proto, with_stats=True,
  128. extra_args=xargs)
  129. r.check_response(count=1, http_status=200,
  130. protocol='HTTP/2' if proto == 'h2' else 'HTTP/1.1')
  131. # download https: with proto via https: proxytunnel
  132. @pytest.mark.skipif(condition=not Env.curl_has_feature('HTTPS-proxy'),
  133. reason='curl lacks HTTPS-proxy support')
  134. @pytest.mark.parametrize("proto", ['http/1.1', 'h2'])
  135. @pytest.mark.parametrize("tunnel", ['http/1.1', 'h2'])
  136. @pytest.mark.skipif(condition=not Env.have_nghttpx(), reason="no nghttpx available")
  137. def test_10_06_proxytunnel_https(self, env: Env, httpd, nghttpx_fwd, proto, tunnel, repeat):
  138. if tunnel == 'h2' and not env.curl_uses_lib('nghttp2'):
  139. pytest.skip('only supported with nghttp2')
  140. curl = CurlClient(env=env)
  141. url = f'https://localhost:{env.https_port}/data.json?[0-0]'
  142. xargs = curl.get_proxy_args(tunnel=True, proto=tunnel)
  143. r = curl.http_download(urls=[url], alpn_proto=proto, with_stats=True,
  144. extra_args=xargs)
  145. r.check_response(count=1, http_status=200,
  146. protocol='HTTP/2' if proto == 'h2' else 'HTTP/1.1')
  147. assert self.get_tunnel_proto_used(r) == 'HTTP/2' \
  148. if tunnel == 'h2' else 'HTTP/1.1'
  149. srcfile = os.path.join(httpd.docs_dir, 'data.json')
  150. dfile = curl.download_file(0)
  151. assert filecmp.cmp(srcfile, dfile, shallow=False)
  152. # download many https: with proto via https: proxytunnel
  153. @pytest.mark.skipif(condition=not Env.have_ssl_curl(), reason=f"curl without SSL")
  154. @pytest.mark.parametrize("proto", ['http/1.1', 'h2'])
  155. @pytest.mark.parametrize("tunnel", ['http/1.1', 'h2'])
  156. @pytest.mark.parametrize("fname, fcount", [
  157. ['data.json', 100],
  158. ['data-100k', 20],
  159. ['data-1m', 5]
  160. ])
  161. @pytest.mark.skipif(condition=not Env.have_nghttpx(), reason="no nghttpx available")
  162. def test_10_07_pts_down_small(self, env: Env, httpd, nghttpx_fwd, proto,
  163. tunnel, fname, fcount, repeat):
  164. if tunnel == 'h2' and not env.curl_uses_lib('nghttp2'):
  165. pytest.skip('only supported with nghttp2')
  166. count = fcount
  167. curl = CurlClient(env=env)
  168. url = f'https://localhost:{env.https_port}/{fname}?[0-{count-1}]'
  169. xargs = curl.get_proxy_args(tunnel=True, proto=tunnel)
  170. r = curl.http_download(urls=[url], alpn_proto=proto, with_stats=True,
  171. extra_args=xargs)
  172. r.check_response(count=count, http_status=200,
  173. protocol='HTTP/2' if proto == 'h2' else 'HTTP/1.1')
  174. assert self.get_tunnel_proto_used(r) == 'HTTP/2' \
  175. if tunnel == 'h2' else 'HTTP/1.1'
  176. srcfile = os.path.join(httpd.docs_dir, fname)
  177. for i in range(count):
  178. dfile = curl.download_file(i)
  179. assert filecmp.cmp(srcfile, dfile, shallow=False)
  180. assert r.total_connects == 1, r.dump_logs()
  181. # upload many https: with proto via https: proxytunnel
  182. @pytest.mark.skipif(condition=not Env.have_ssl_curl(), reason=f"curl without SSL")
  183. @pytest.mark.parametrize("proto", ['http/1.1', 'h2'])
  184. @pytest.mark.parametrize("tunnel", ['http/1.1', 'h2'])
  185. @pytest.mark.parametrize("fname, fcount", [
  186. ['data.json', 50],
  187. ['data-100k', 20],
  188. ['data-1m', 5]
  189. ])
  190. @pytest.mark.skipif(condition=not Env.have_nghttpx(), reason="no nghttpx available")
  191. def test_10_08_upload_seq_large(self, env: Env, httpd, nghttpx, proto,
  192. tunnel, fname, fcount, repeat):
  193. if tunnel == 'h2' and not env.curl_uses_lib('nghttp2'):
  194. pytest.skip('only supported with nghttp2')
  195. count = fcount
  196. srcfile = os.path.join(httpd.docs_dir, fname)
  197. curl = CurlClient(env=env)
  198. url = f'https://localhost:{env.https_port}/curltest/echo?id=[0-{count-1}]'
  199. xargs = curl.get_proxy_args(tunnel=True, proto=tunnel)
  200. r = curl.http_upload(urls=[url], data=f'@{srcfile}', alpn_proto=proto,
  201. extra_args=xargs)
  202. assert self.get_tunnel_proto_used(r) == 'HTTP/2' \
  203. if tunnel == 'h2' else 'HTTP/1.1'
  204. r.check_response(count=count, http_status=200)
  205. indata = open(srcfile).readlines()
  206. for i in range(count):
  207. respdata = open(curl.response_file(i)).readlines()
  208. assert respdata == indata
  209. assert r.total_connects == 1, r.dump_logs()
  210. @pytest.mark.skipif(condition=not Env.have_ssl_curl(), reason=f"curl without SSL")
  211. @pytest.mark.parametrize("tunnel", ['http/1.1', 'h2'])
  212. @pytest.mark.skipif(condition=not Env.have_nghttpx(), reason="no nghttpx available")
  213. def test_10_09_reuse_ser(self, env: Env, httpd, nghttpx_fwd, tunnel, repeat):
  214. if tunnel == 'h2' and not env.curl_uses_lib('nghttp2'):
  215. pytest.skip('only supported with nghttp2')
  216. curl = CurlClient(env=env)
  217. url1 = f'https://localhost:{env.https_port}/data.json'
  218. url2 = f'http://localhost:{env.http_port}/data.json'
  219. xargs = curl.get_proxy_args(tunnel=True, proto=tunnel)
  220. r = curl.http_download(urls=[url1, url2], alpn_proto='http/1.1', with_stats=True,
  221. extra_args=xargs)
  222. r.check_response(count=2, http_status=200)
  223. assert self.get_tunnel_proto_used(r) == 'HTTP/2' \
  224. if tunnel == 'h2' else 'HTTP/1.1'
  225. if tunnel == 'h2':
  226. # TODO: we would like to reuse the first connection for the
  227. # second URL, but this is currently not possible
  228. # assert r.total_connects == 1
  229. assert r.total_connects == 2
  230. else:
  231. assert r.total_connects == 2
  232. @pytest.mark.skipif(condition=not Env.have_ssl_curl(), reason=f"curl without SSL")
  233. @pytest.mark.parametrize("tunnel", ['http/1.1', 'h2'])
  234. @pytest.mark.skipif(condition=not Env.have_nghttpx(), reason="no nghttpx available")
  235. def test_10_10_reuse_proxy(self, env: Env, httpd, nghttpx_fwd, tunnel, repeat):
  236. # url twice via https: proxy separated with '--next', will reuse
  237. if tunnel == 'h2' and not env.curl_uses_lib('nghttp2'):
  238. pytest.skip('only supported with nghttp2')
  239. curl = CurlClient(env=env)
  240. url = f'https://localhost:{env.https_port}/data.json'
  241. proxy_args = curl.get_proxy_args(tunnel=True, proto=tunnel)
  242. r1 = curl.http_download(urls=[url], alpn_proto='http/1.1', with_stats=True,
  243. extra_args=proxy_args)
  244. r1.check_response(count=1, http_status=200)
  245. assert self.get_tunnel_proto_used(r1) == 'HTTP/2' \
  246. if tunnel == 'h2' else 'HTTP/1.1'
  247. # get the args, duplicate separated with '--next'
  248. x2_args = r1.args[1:]
  249. x2_args.append('--next')
  250. x2_args.extend(proxy_args)
  251. r2 = curl.http_download(urls=[url], alpn_proto='http/1.1', with_stats=True,
  252. extra_args=x2_args)
  253. r2.check_response(count=2, http_status=200)
  254. assert r2.total_connects == 1
  255. @pytest.mark.skipif(condition=not Env.have_ssl_curl(), reason=f"curl without SSL")
  256. @pytest.mark.parametrize("tunnel", ['http/1.1', 'h2'])
  257. @pytest.mark.skipif(condition=not Env.have_nghttpx(), reason="no nghttpx available")
  258. @pytest.mark.skipif(condition=not Env.curl_uses_lib('openssl'), reason="tls13-ciphers not supported")
  259. def test_10_11_noreuse_proxy_https(self, env: Env, httpd, nghttpx_fwd, tunnel, repeat):
  260. # different --proxy-tls13-ciphers, no reuse of connection for https:
  261. curl = CurlClient(env=env)
  262. if tunnel == 'h2' and not env.curl_uses_lib('nghttp2'):
  263. pytest.skip('only supported with nghttp2')
  264. url = f'https://localhost:{env.https_port}/data.json'
  265. proxy_args = curl.get_proxy_args(tunnel=True, proto=tunnel)
  266. r1 = curl.http_download(urls=[url], alpn_proto='http/1.1', with_stats=True,
  267. extra_args=proxy_args)
  268. r1.check_response(count=1, http_status=200)
  269. assert self.get_tunnel_proto_used(r1) == 'HTTP/2' \
  270. if tunnel == 'h2' else 'HTTP/1.1'
  271. # get the args, duplicate separated with '--next'
  272. x2_args = r1.args[1:]
  273. x2_args.append('--next')
  274. x2_args.extend(proxy_args)
  275. x2_args.extend(['--proxy-tls13-ciphers', 'TLS_AES_128_GCM_SHA256'])
  276. r2 = curl.http_download(urls=[url], alpn_proto='http/1.1', with_stats=True,
  277. extra_args=x2_args)
  278. r2.check_response(count=2, http_status=200)
  279. assert r2.total_connects == 2
  280. @pytest.mark.skipif(condition=not Env.have_ssl_curl(), reason=f"curl without SSL")
  281. @pytest.mark.parametrize("tunnel", ['http/1.1', 'h2'])
  282. @pytest.mark.skipif(condition=not Env.have_nghttpx(), reason="no nghttpx available")
  283. @pytest.mark.skipif(condition=not Env.curl_uses_lib('openssl'), reason="tls13-ciphers not supported")
  284. def test_10_12_noreuse_proxy_http(self, env: Env, httpd, nghttpx_fwd, tunnel, repeat):
  285. # different --proxy-tls13-ciphers, no reuse of connection for http:
  286. if tunnel == 'h2' and not env.curl_uses_lib('nghttp2'):
  287. pytest.skip('only supported with nghttp2')
  288. curl = CurlClient(env=env)
  289. url = f'http://localhost:{env.http_port}/data.json'
  290. proxy_args = curl.get_proxy_args(tunnel=True, proto=tunnel)
  291. r1 = curl.http_download(urls=[url], alpn_proto='http/1.1', with_stats=True,
  292. extra_args=proxy_args)
  293. r1.check_response(count=1, http_status=200)
  294. assert self.get_tunnel_proto_used(r1) == 'HTTP/2' \
  295. if tunnel == 'h2' else 'HTTP/1.1'
  296. # get the args, duplicate separated with '--next'
  297. x2_args = r1.args[1:]
  298. x2_args.append('--next')
  299. x2_args.extend(proxy_args)
  300. x2_args.extend(['--proxy-tls13-ciphers', 'TLS_AES_128_GCM_SHA256'])
  301. r2 = curl.http_download(urls=[url], alpn_proto='http/1.1', with_stats=True,
  302. extra_args=x2_args)
  303. r2.check_response(count=2, http_status=200)
  304. assert r2.total_connects == 2
  305. @pytest.mark.skipif(condition=not Env.have_ssl_curl(), reason=f"curl without SSL")
  306. @pytest.mark.parametrize("tunnel", ['http/1.1', 'h2'])
  307. @pytest.mark.skipif(condition=not Env.have_nghttpx(), reason="no nghttpx available")
  308. @pytest.mark.skipif(condition=not Env.curl_uses_lib('openssl'), reason="tls13-ciphers not supported")
  309. def test_10_13_noreuse_https(self, env: Env, httpd, nghttpx_fwd, tunnel, repeat):
  310. # different --tls13-ciphers on https: same proxy config
  311. if tunnel == 'h2' and not env.curl_uses_lib('nghttp2'):
  312. pytest.skip('only supported with nghttp2')
  313. curl = CurlClient(env=env)
  314. url = f'https://localhost:{env.https_port}/data.json'
  315. proxy_args = curl.get_proxy_args(tunnel=True, proto=tunnel)
  316. r1 = curl.http_download(urls=[url], alpn_proto='http/1.1', with_stats=True,
  317. extra_args=proxy_args)
  318. r1.check_response(count=1, http_status=200)
  319. assert self.get_tunnel_proto_used(r1) == 'HTTP/2' \
  320. if tunnel == 'h2' else 'HTTP/1.1'
  321. # get the args, duplicate separated with '--next'
  322. x2_args = r1.args[1:]
  323. x2_args.append('--next')
  324. x2_args.extend(proxy_args)
  325. x2_args.extend(['--tls13-ciphers', 'TLS_AES_128_GCM_SHA256'])
  326. r2 = curl.http_download(urls=[url], alpn_proto='http/1.1', with_stats=True,
  327. extra_args=x2_args)
  328. r2.check_response(count=2, http_status=200)
  329. assert r2.total_connects == 2
  330. # download via https: proxy (no tunnel) using IP address
  331. @pytest.mark.skipif(condition=not Env.curl_has_feature('HTTPS-proxy'),
  332. reason='curl lacks HTTPS-proxy support')
  333. @pytest.mark.skipif(condition=Env.curl_uses_lib('bearssl'), reason="ip address cert verification not supported")
  334. @pytest.mark.parametrize("proto", ['http/1.1', 'h2'])
  335. def test_10_14_proxys_ip_addr(self, env: Env, httpd, proto, repeat):
  336. if proto == 'h2' and not env.curl_uses_lib('nghttp2'):
  337. pytest.skip('only supported with nghttp2')
  338. curl = CurlClient(env=env)
  339. url = f'http://localhost:{env.http_port}/data.json'
  340. xargs = curl.get_proxy_args(proto=proto, use_ip=True)
  341. r = curl.http_download(urls=[url], alpn_proto='http/1.1', with_stats=True,
  342. extra_args=xargs)
  343. if env.curl_uses_lib('mbedtls') and \
  344. not env.curl_lib_version_at_least('mbedtls', '3.5.0'):
  345. r.check_exit_code(60) # CURLE_PEER_FAILED_VERIFICATION
  346. else:
  347. r.check_response(count=1, http_status=200,
  348. protocol='HTTP/2' if proto == 'h2' else 'HTTP/1.1')