test_07_upload.py 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572
  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 difflib
  28. import filecmp
  29. import logging
  30. import os
  31. import time
  32. import pytest
  33. from testenv import Env, CurlClient, LocalClient
  34. log = logging.getLogger(__name__)
  35. class TestUpload:
  36. @pytest.fixture(autouse=True, scope='class')
  37. def _class_scope(self, env, httpd, nghttpx):
  38. if env.have_h3():
  39. nghttpx.start_if_needed()
  40. env.make_data_file(indir=env.gen_dir, fname="data-63k", fsize=63*1024)
  41. env.make_data_file(indir=env.gen_dir, fname="data-64k", fsize=64*1024)
  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-1m+", fsize=(1024*1024)+1)
  44. env.make_data_file(indir=env.gen_dir, fname="data-10m", fsize=10*1024*1024)
  45. httpd.clear_extra_configs()
  46. httpd.reload()
  47. # upload small data, check that this is what was echoed
  48. @pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3'])
  49. def test_07_01_upload_1_small(self, env: Env, httpd, nghttpx, repeat, proto):
  50. if proto == 'h3' and not env.have_h3():
  51. pytest.skip("h3 not supported")
  52. if proto == 'h3' and env.curl_uses_lib('msh3'):
  53. pytest.skip("msh3 fails here")
  54. data = '0123456789'
  55. curl = CurlClient(env=env)
  56. url = f'https://{env.authority_for(env.domain1, proto)}/curltest/echo?id=[0-0]'
  57. r = curl.http_upload(urls=[url], data=data, alpn_proto=proto)
  58. r.check_stats(count=1, http_status=200, exitcode=0)
  59. respdata = open(curl.response_file(0)).readlines()
  60. assert respdata == [data]
  61. # upload large data, check that this is what was echoed
  62. @pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3'])
  63. def test_07_02_upload_1_large(self, env: Env, httpd, nghttpx, repeat, proto):
  64. if proto == 'h3' and not env.have_h3():
  65. pytest.skip("h3 not supported")
  66. if proto == 'h3' and env.curl_uses_lib('msh3'):
  67. pytest.skip("msh3 fails here")
  68. fdata = os.path.join(env.gen_dir, 'data-100k')
  69. curl = CurlClient(env=env)
  70. url = f'https://{env.authority_for(env.domain1, proto)}/curltest/echo?id=[0-0]'
  71. r = curl.http_upload(urls=[url], data=f'@{fdata}', alpn_proto=proto)
  72. r.check_stats(count=1, http_status=200, exitcode=0)
  73. indata = open(fdata).readlines()
  74. respdata = open(curl.response_file(0)).readlines()
  75. assert respdata == indata
  76. # upload data sequentially, check that they were echoed
  77. @pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3'])
  78. def test_07_10_upload_sequential(self, env: Env, httpd, nghttpx, repeat, proto):
  79. if proto == 'h3' and not env.have_h3():
  80. pytest.skip("h3 not supported")
  81. if proto == 'h3' and env.curl_uses_lib('msh3'):
  82. pytest.skip("msh3 stalls here")
  83. count = 50
  84. data = '0123456789'
  85. curl = CurlClient(env=env)
  86. url = f'https://{env.authority_for(env.domain1, proto)}/curltest/echo?id=[0-{count-1}]'
  87. r = curl.http_upload(urls=[url], data=data, alpn_proto=proto)
  88. r.check_stats(count=count, http_status=200, exitcode=0)
  89. for i in range(count):
  90. respdata = open(curl.response_file(i)).readlines()
  91. assert respdata == [data]
  92. # upload data parallel, check that they were echoed
  93. @pytest.mark.parametrize("proto", ['h2', 'h3'])
  94. def test_07_11_upload_parallel(self, env: Env, httpd, nghttpx, repeat, proto):
  95. if proto == 'h3' and not env.have_h3():
  96. pytest.skip("h3 not supported")
  97. if proto == 'h3' and env.curl_uses_lib('msh3'):
  98. pytest.skip("msh3 stalls here")
  99. # limit since we use a separate connection in h1
  100. count = 50
  101. data = '0123456789'
  102. curl = CurlClient(env=env)
  103. url = f'https://{env.authority_for(env.domain1, proto)}/curltest/echo?id=[0-{count-1}]'
  104. r = curl.http_upload(urls=[url], data=data, alpn_proto=proto,
  105. extra_args=['--parallel'])
  106. r.check_stats(count=count, http_status=200, exitcode=0)
  107. for i in range(count):
  108. respdata = open(curl.response_file(i)).readlines()
  109. assert respdata == [data]
  110. # upload large data sequentially, check that this is what was echoed
  111. @pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3'])
  112. def test_07_12_upload_seq_large(self, env: Env, httpd, nghttpx, repeat, proto):
  113. if proto == 'h3' and not env.have_h3():
  114. pytest.skip("h3 not supported")
  115. if proto == 'h3' and env.curl_uses_lib('msh3'):
  116. pytest.skip("msh3 stalls here")
  117. fdata = os.path.join(env.gen_dir, 'data-100k')
  118. count = 20
  119. curl = CurlClient(env=env)
  120. url = f'https://{env.authority_for(env.domain1, proto)}/curltest/echo?id=[0-{count-1}]'
  121. r = curl.http_upload(urls=[url], data=f'@{fdata}', alpn_proto=proto)
  122. r.check_response(count=count, http_status=200)
  123. indata = open(fdata).readlines()
  124. r.check_stats(count=count, http_status=200, exitcode=0)
  125. for i in range(count):
  126. respdata = open(curl.response_file(i)).readlines()
  127. assert respdata == indata
  128. # upload very large data sequentially, check that this is what was echoed
  129. @pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3'])
  130. def test_07_13_upload_seq_large(self, env: Env, httpd, nghttpx, repeat, proto):
  131. if proto == 'h3' and not env.have_h3():
  132. pytest.skip("h3 not supported")
  133. if proto == 'h3' and env.curl_uses_lib('msh3'):
  134. pytest.skip("msh3 stalls here")
  135. fdata = os.path.join(env.gen_dir, 'data-10m')
  136. count = 2
  137. curl = CurlClient(env=env)
  138. url = f'https://{env.authority_for(env.domain1, proto)}/curltest/echo?id=[0-{count-1}]'
  139. r = curl.http_upload(urls=[url], data=f'@{fdata}', alpn_proto=proto)
  140. r.check_stats(count=count, http_status=200, exitcode=0)
  141. indata = open(fdata).readlines()
  142. for i in range(count):
  143. respdata = open(curl.response_file(i)).readlines()
  144. assert respdata == indata
  145. # upload data parallel, check that they were echoed
  146. @pytest.mark.parametrize("proto", ['h2', 'h3'])
  147. def test_07_20_upload_parallel(self, env: Env, httpd, nghttpx, repeat, proto):
  148. if proto == 'h3' and not env.have_h3():
  149. pytest.skip("h3 not supported")
  150. if proto == 'h3' and env.curl_uses_lib('msh3'):
  151. pytest.skip("msh3 stalls here")
  152. # limit since we use a separate connection in h1
  153. count = 20
  154. data = '0123456789'
  155. curl = CurlClient(env=env)
  156. url = f'https://{env.authority_for(env.domain1, proto)}/curltest/echo?id=[0-{count-1}]'
  157. r = curl.http_upload(urls=[url], data=data, alpn_proto=proto,
  158. extra_args=['--parallel'])
  159. r.check_stats(count=count, http_status=200, exitcode=0)
  160. for i in range(count):
  161. respdata = open(curl.response_file(i)).readlines()
  162. assert respdata == [data]
  163. # upload large data parallel, check that this is what was echoed
  164. @pytest.mark.parametrize("proto", ['h2', 'h3'])
  165. def test_07_21_upload_parallel_large(self, env: Env, httpd, nghttpx, repeat, proto):
  166. if proto == 'h3' and not env.have_h3():
  167. pytest.skip("h3 not supported")
  168. if proto == 'h3' and env.curl_uses_lib('msh3'):
  169. pytest.skip("msh3 stalls here")
  170. fdata = os.path.join(env.gen_dir, 'data-100k')
  171. # limit since we use a separate connection in h1
  172. count = 20
  173. curl = CurlClient(env=env)
  174. url = f'https://{env.authority_for(env.domain1, proto)}/curltest/echo?id=[0-{count-1}]'
  175. r = curl.http_upload(urls=[url], data=f'@{fdata}', alpn_proto=proto,
  176. extra_args=['--parallel'])
  177. r.check_response(count=count, http_status=200)
  178. self.check_download(count, fdata, curl)
  179. # upload large data parallel to a URL that denies uploads
  180. @pytest.mark.parametrize("proto", ['h2', 'h3'])
  181. def test_07_22_upload_parallel_fail(self, env: Env, httpd, nghttpx, repeat, proto):
  182. if proto == 'h3' and not env.have_h3():
  183. pytest.skip("h3 not supported")
  184. if proto == 'h3' and env.curl_uses_lib('msh3'):
  185. pytest.skip("msh3 stalls here")
  186. fdata = os.path.join(env.gen_dir, 'data-10m')
  187. count = 100
  188. curl = CurlClient(env=env)
  189. url = f'https://{env.authority_for(env.domain1, proto)}'\
  190. f'/curltest/tweak?status=400&delay=5ms&chunks=1&body_error=reset&id=[0-{count-1}]'
  191. r = curl.http_upload(urls=[url], data=f'@{fdata}', alpn_proto=proto,
  192. extra_args=['--parallel'])
  193. exp_exit = 92 if proto == 'h2' else 95
  194. r.check_stats(count=count, exitcode=exp_exit)
  195. # PUT 100k
  196. @pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3'])
  197. def test_07_30_put_100k(self, env: Env, httpd, nghttpx, repeat, proto):
  198. if proto == 'h3' and not env.have_h3():
  199. pytest.skip("h3 not supported")
  200. if proto == 'h3' and env.curl_uses_lib('msh3'):
  201. pytest.skip("msh3 fails here")
  202. fdata = os.path.join(env.gen_dir, 'data-100k')
  203. count = 1
  204. curl = CurlClient(env=env)
  205. url = f'https://{env.authority_for(env.domain1, proto)}/curltest/put?id=[0-{count-1}]'
  206. r = curl.http_put(urls=[url], fdata=fdata, alpn_proto=proto,
  207. extra_args=['--parallel'])
  208. r.check_stats(count=count, http_status=200, exitcode=0)
  209. exp_data = [f'{os.path.getsize(fdata)}']
  210. r.check_response(count=count, http_status=200)
  211. for i in range(count):
  212. respdata = open(curl.response_file(i)).readlines()
  213. assert respdata == exp_data
  214. # PUT 10m
  215. @pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3'])
  216. def test_07_31_put_10m(self, env: Env, httpd, nghttpx, repeat, proto):
  217. if proto == 'h3' and not env.have_h3():
  218. pytest.skip("h3 not supported")
  219. if proto == 'h3' and env.curl_uses_lib('msh3'):
  220. pytest.skip("msh3 fails here")
  221. fdata = os.path.join(env.gen_dir, 'data-10m')
  222. count = 1
  223. curl = CurlClient(env=env)
  224. url = f'https://{env.authority_for(env.domain1, proto)}/curltest/put?id=[0-{count-1}]&chunk_delay=2ms'
  225. r = curl.http_put(urls=[url], fdata=fdata, alpn_proto=proto,
  226. extra_args=['--parallel'])
  227. r.check_stats(count=count, http_status=200, exitcode=0)
  228. exp_data = [f'{os.path.getsize(fdata)}']
  229. r.check_response(count=count, http_status=200)
  230. for i in range(count):
  231. respdata = open(curl.response_file(i)).readlines()
  232. assert respdata == exp_data
  233. # issue #10591
  234. @pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3'])
  235. def test_07_32_issue_10591(self, env: Env, httpd, nghttpx, repeat, proto):
  236. if proto == 'h3' and not env.have_h3():
  237. pytest.skip("h3 not supported")
  238. if proto == 'h3' and env.curl_uses_lib('msh3'):
  239. pytest.skip("msh3 fails here")
  240. fdata = os.path.join(env.gen_dir, 'data-10m')
  241. count = 1
  242. curl = CurlClient(env=env)
  243. url = f'https://{env.authority_for(env.domain1, proto)}/curltest/put?id=[0-{count-1}]'
  244. r = curl.http_put(urls=[url], fdata=fdata, alpn_proto=proto)
  245. r.check_stats(count=count, http_status=200, exitcode=0)
  246. # issue #11157, upload that is 404'ed by server, needs to terminate
  247. # correctly and not time out on sending
  248. def test_07_33_issue_11157a(self, env: Env, httpd, nghttpx, repeat):
  249. proto = 'h2'
  250. fdata = os.path.join(env.gen_dir, 'data-10m')
  251. # send a POST to our PUT handler which will send immediately a 404 back
  252. url = f'https://{env.authority_for(env.domain1, proto)}/curltest/put'
  253. curl = CurlClient(env=env)
  254. r = curl.run_direct(with_stats=True, args=[
  255. '--resolve', f'{env.authority_for(env.domain1, proto)}:127.0.0.1',
  256. '--cacert', env.ca.cert_file,
  257. '--request', 'POST',
  258. '--max-time', '5', '-v',
  259. '--url', url,
  260. '--form', 'idList=12345678',
  261. '--form', 'pos=top',
  262. '--form', 'name=mr_test',
  263. '--form', f'fileSource=@{fdata};type=application/pdf',
  264. ])
  265. assert r.exit_code == 0, f'{r}'
  266. r.check_stats(1, 404)
  267. # issue #11157, send upload that is slowly read in
  268. def test_07_33_issue_11157b(self, env: Env, httpd, nghttpx, repeat):
  269. proto = 'h2'
  270. fdata = os.path.join(env.gen_dir, 'data-10m')
  271. # tell our test PUT handler to read the upload more slowly, so
  272. # that the send buffering and transfer loop needs to wait
  273. url = f'https://{env.authority_for(env.domain1, proto)}/curltest/put?chunk_delay=2ms'
  274. curl = CurlClient(env=env)
  275. r = curl.run_direct(with_stats=True, args=[
  276. '--verbose', '--trace-config', 'ids,time',
  277. '--resolve', f'{env.authority_for(env.domain1, proto)}:127.0.0.1',
  278. '--cacert', env.ca.cert_file,
  279. '--request', 'PUT',
  280. '--max-time', '10', '-v',
  281. '--url', url,
  282. '--form', 'idList=12345678',
  283. '--form', 'pos=top',
  284. '--form', 'name=mr_test',
  285. '--form', f'fileSource=@{fdata};type=application/pdf',
  286. ])
  287. assert r.exit_code == 0, r.dump_logs()
  288. r.check_stats(1, 200)
  289. def test_07_34_issue_11194(self, env: Env, httpd, nghttpx, repeat):
  290. proto = 'h2'
  291. # tell our test PUT handler to read the upload more slowly, so
  292. # that the send buffering and transfer loop needs to wait
  293. fdata = os.path.join(env.gen_dir, 'data-100k')
  294. url = f'https://{env.authority_for(env.domain1, proto)}/curltest/put'
  295. curl = CurlClient(env=env)
  296. r = curl.run_direct(with_stats=True, args=[
  297. '--verbose', '--trace-config', 'ids,time',
  298. '--resolve', f'{env.authority_for(env.domain1, proto)}:127.0.0.1',
  299. '--cacert', env.ca.cert_file,
  300. '--request', 'PUT',
  301. '--digest', '--user', 'test:test',
  302. '--data-binary', f'@{fdata}',
  303. '--url', url,
  304. ])
  305. assert r.exit_code == 0, r.dump_logs()
  306. r.check_stats(1, 200)
  307. # upload large data on a h1 to h2 upgrade
  308. def test_07_35_h1_h2_upgrade_upload(self, env: Env, httpd, nghttpx, repeat):
  309. fdata = os.path.join(env.gen_dir, 'data-100k')
  310. curl = CurlClient(env=env)
  311. url = f'http://{env.domain1}:{env.http_port}/curltest/echo?id=[0-0]'
  312. r = curl.http_upload(urls=[url], data=f'@{fdata}', extra_args=[
  313. '--http2'
  314. ])
  315. r.check_response(count=1, http_status=200)
  316. # apache does not Upgrade on request with a body
  317. assert r.stats[0]['http_version'] == '1.1', f'{r}'
  318. indata = open(fdata).readlines()
  319. respdata = open(curl.response_file(0)).readlines()
  320. assert respdata == indata
  321. # upload to a 301,302,303 response
  322. @pytest.mark.parametrize("redir", ['301', '302', '303'])
  323. @pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3'])
  324. def test_07_36_upload_30x(self, env: Env, httpd, nghttpx, repeat, redir, proto):
  325. if proto == 'h3' and not env.have_h3():
  326. pytest.skip("h3 not supported")
  327. if proto == 'h3' and env.curl_uses_lib('msh3'):
  328. pytest.skip("msh3 fails here")
  329. data = '0123456789' * 10
  330. curl = CurlClient(env=env)
  331. url = f'https://{env.authority_for(env.domain1, proto)}/curltest/echo{redir}?id=[0-0]'
  332. r = curl.http_upload(urls=[url], data=data, alpn_proto=proto, extra_args=[
  333. '-L', '--trace-config', 'http/2,http/3'
  334. ])
  335. r.check_response(count=1, http_status=200)
  336. respdata = open(curl.response_file(0)).readlines()
  337. assert respdata == [] # was transformed to a GET
  338. # upload to a 307 response
  339. @pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3'])
  340. def test_07_37_upload_307(self, env: Env, httpd, nghttpx, repeat, proto):
  341. if proto == 'h3' and not env.have_h3():
  342. pytest.skip("h3 not supported")
  343. if proto == 'h3' and env.curl_uses_lib('msh3'):
  344. pytest.skip("msh3 fails here")
  345. data = '0123456789' * 10
  346. curl = CurlClient(env=env)
  347. url = f'https://{env.authority_for(env.domain1, proto)}/curltest/echo307?id=[0-0]'
  348. r = curl.http_upload(urls=[url], data=data, alpn_proto=proto, extra_args=[
  349. '-L', '--trace-config', 'http/2,http/3'
  350. ])
  351. r.check_response(count=1, http_status=200)
  352. respdata = open(curl.response_file(0)).readlines()
  353. assert respdata == [data] # was POST again
  354. # POST form data, yet another code path in transfer
  355. @pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3'])
  356. def test_07_38_form_small(self, env: Env, httpd, nghttpx, repeat, proto):
  357. if proto == 'h3' and not env.have_h3():
  358. pytest.skip("h3 not supported")
  359. if proto == 'h3' and env.curl_uses_lib('msh3'):
  360. pytest.skip("msh3 fails here")
  361. curl = CurlClient(env=env)
  362. url = f'https://{env.authority_for(env.domain1, proto)}/curltest/echo?id=[0-0]'
  363. r = curl.http_form(urls=[url], alpn_proto=proto, form={
  364. 'name1': 'value1',
  365. })
  366. r.check_stats(count=1, http_status=200, exitcode=0)
  367. # POST data urlencoded, small enough to be sent with request headers
  368. @pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3'])
  369. def test_07_39_post_urlenc_small(self, env: Env, httpd, nghttpx, repeat, proto):
  370. if proto == 'h3' and not env.have_h3():
  371. pytest.skip("h3 not supported")
  372. if proto == 'h3' and env.curl_uses_lib('msh3'):
  373. pytest.skip("msh3 fails here")
  374. fdata = os.path.join(env.gen_dir, 'data-63k')
  375. curl = CurlClient(env=env)
  376. url = f'https://{env.authority_for(env.domain1, proto)}/curltest/echo?id=[0-0]'
  377. r = curl.http_upload(urls=[url], data=f'@{fdata}', alpn_proto=proto, extra_args=[
  378. '--trace-config', 'http/2,http/3'
  379. ])
  380. r.check_stats(count=1, http_status=200, exitcode=0)
  381. indata = open(fdata).readlines()
  382. respdata = open(curl.response_file(0)).readlines()
  383. assert respdata == indata
  384. # POST data urlencoded, large enough to be sent separate from request headers
  385. @pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3'])
  386. def test_07_40_post_urlenc_large(self, env: Env, httpd, nghttpx, repeat, proto):
  387. if proto == 'h3' and not env.have_h3():
  388. pytest.skip("h3 not supported")
  389. if proto == 'h3' and env.curl_uses_lib('msh3'):
  390. pytest.skip("msh3 fails here")
  391. fdata = os.path.join(env.gen_dir, 'data-64k')
  392. curl = CurlClient(env=env)
  393. url = f'https://{env.authority_for(env.domain1, proto)}/curltest/echo?id=[0-0]'
  394. r = curl.http_upload(urls=[url], data=f'@{fdata}', alpn_proto=proto, extra_args=[
  395. '--trace-config', 'http/2,http/3'
  396. ])
  397. r.check_stats(count=1, http_status=200, exitcode=0)
  398. indata = open(fdata).readlines()
  399. respdata = open(curl.response_file(0)).readlines()
  400. assert respdata == indata
  401. # POST data urlencoded, small enough to be sent with request headers
  402. # and request headers are so large that the first send is larger
  403. # than our default upload buffer length (64KB).
  404. # Unfixed, this will fail when run with CURL_DBG_SOCK_WBLOCK=80 most
  405. # of the time
  406. @pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3'])
  407. def test_07_41_post_urlenc_small(self, env: Env, httpd, nghttpx, repeat, proto):
  408. if proto == 'h3' and not env.have_h3():
  409. pytest.skip("h3 not supported")
  410. if proto == 'h3' and env.curl_uses_lib('msh3'):
  411. pytest.skip("msh3 fails here")
  412. if proto == 'h3' and env.curl_uses_lib('quiche'):
  413. pytest.skip("quiche has CWND issues with large requests")
  414. fdata = os.path.join(env.gen_dir, 'data-63k')
  415. curl = CurlClient(env=env)
  416. extra_args = ['--trace-config', 'http/2,http/3']
  417. # add enough headers so that the first send chunk is > 64KB
  418. for i in range(63):
  419. extra_args.extend(['-H', f'x{i:02d}: {"y"*1019}'])
  420. url = f'https://{env.authority_for(env.domain1, proto)}/curltest/echo?id=[0-0]'
  421. r = curl.http_upload(urls=[url], data=f'@{fdata}', alpn_proto=proto, extra_args=extra_args)
  422. r.check_stats(count=1, http_status=200, exitcode=0)
  423. indata = open(fdata).readlines()
  424. respdata = open(curl.response_file(0)).readlines()
  425. assert respdata == indata
  426. def check_download(self, count, srcfile, curl):
  427. for i in range(count):
  428. dfile = curl.download_file(i)
  429. assert os.path.exists(dfile)
  430. if not filecmp.cmp(srcfile, dfile, shallow=False):
  431. diff = "".join(difflib.unified_diff(a=open(srcfile).readlines(),
  432. b=open(dfile).readlines(),
  433. fromfile=srcfile,
  434. tofile=dfile,
  435. n=1))
  436. assert False, f'download {dfile} differs:\n{diff}'
  437. # upload data, pause, let connection die with an incomplete response
  438. # issues #11769 #13260
  439. @pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3'])
  440. def test_07_42a_upload_disconnect(self, env: Env, httpd, nghttpx, repeat, proto):
  441. if proto == 'h3' and not env.have_h3():
  442. pytest.skip("h3 not supported")
  443. if proto == 'h3' and env.curl_uses_lib('msh3'):
  444. pytest.skip("msh3 fails here")
  445. client = LocalClient(name='upload-pausing', env=env, timeout=60)
  446. if not client.exists():
  447. pytest.skip(f'example client not built: {client.name}')
  448. url = f'http://{env.domain1}:{env.http_port}/curltest/echo?id=[0-0]&die_after=0'
  449. r = client.run([url])
  450. r.check_exit_code(18) # PARTIAL_FILE
  451. # upload data, pause, let connection die without any response at all
  452. @pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3'])
  453. def test_07_42b_upload_disconnect(self, env: Env, httpd, nghttpx, repeat, proto):
  454. if proto == 'h3' and not env.have_h3():
  455. pytest.skip("h3 not supported")
  456. if proto == 'h3' and env.curl_uses_lib('msh3'):
  457. pytest.skip("msh3 fails here")
  458. client = LocalClient(name='upload-pausing', env=env, timeout=60)
  459. if not client.exists():
  460. pytest.skip(f'example client not built: {client.name}')
  461. url = f'http://{env.domain1}:{env.http_port}/curltest/echo?id=[0-0]&just_die=1'
  462. r = client.run([url])
  463. r.check_exit_code(52) # GOT_NOTHING
  464. # upload data, pause, let connection die after 100 continue
  465. @pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3'])
  466. def test_07_42c_upload_disconnect(self, env: Env, httpd, nghttpx, repeat, proto):
  467. if proto == 'h3' and not env.have_h3():
  468. pytest.skip("h3 not supported")
  469. if proto == 'h3' and env.curl_uses_lib('msh3'):
  470. pytest.skip("msh3 fails here")
  471. client = LocalClient(name='upload-pausing', env=env, timeout=60)
  472. if not client.exists():
  473. pytest.skip(f'example client not built: {client.name}')
  474. url = f'http://{env.domain1}:{env.http_port}/curltest/echo?id=[0-0]&die_after_100=1'
  475. r = client.run([url])
  476. r.check_exit_code(52) # GOT_NOTHING
  477. # speed limited on put handler
  478. @pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3'])
  479. def test_07_50_put_speed_limit(self, env: Env, httpd, nghttpx, proto, repeat):
  480. if proto == 'h3' and not env.have_h3():
  481. pytest.skip("h3 not supported")
  482. count = 1
  483. fdata = os.path.join(env.gen_dir, 'data-100k')
  484. up_len = 100 * 1024
  485. speed_limit = 20 * 1024
  486. curl = CurlClient(env=env)
  487. url = f'https://{env.authority_for(env.domain1, proto)}/curltest/put?id=[0-0]'
  488. r = curl.http_put(urls=[url], fdata=fdata, alpn_proto=proto,
  489. with_headers=True, extra_args=[
  490. '--limit-rate', f'{speed_limit}'
  491. ])
  492. r.check_response(count=count, http_status=200)
  493. assert r.responses[0]['header']['received-length'] == f'{up_len}', f'{r.responses[0]}'
  494. up_speed = r.stats[0]['speed_upload']
  495. assert (speed_limit * 0.5) <= up_speed <= (speed_limit * 1.5), f'{r.stats[0]}'
  496. # speed limited on echo handler
  497. @pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3'])
  498. def test_07_51_echo_speed_limit(self, env: Env, httpd, nghttpx, proto, repeat):
  499. if proto == 'h3' and not env.have_h3():
  500. pytest.skip("h3 not supported")
  501. count = 1
  502. fdata = os.path.join(env.gen_dir, 'data-100k')
  503. speed_limit = 20 * 1024
  504. curl = CurlClient(env=env)
  505. url = f'https://{env.authority_for(env.domain1, proto)}/curltest/echo?id=[0-0]'
  506. r = curl.http_upload(urls=[url], data=f'@{fdata}', alpn_proto=proto,
  507. with_headers=True, extra_args=[
  508. '--limit-rate', f'{speed_limit}'
  509. ])
  510. r.check_response(count=count, http_status=200)
  511. up_speed = r.stats[0]['speed_upload']
  512. assert (speed_limit * 0.5) <= up_speed <= (speed_limit * 1.5), f'{r.stats[0]}'
  513. # upload larger data, triggering "Expect: 100-continue" code paths
  514. @pytest.mark.parametrize("proto", ['http/1.1'])
  515. def test_07_60_upload_exp100(self, env: Env, httpd, nghttpx, repeat, proto):
  516. fdata = os.path.join(env.gen_dir, 'data-1m+')
  517. read_delay = 1
  518. curl = CurlClient(env=env)
  519. url = f'https://{env.authority_for(env.domain1, proto)}/curltest/put?id=[0-0]'\
  520. f'&read_delay={read_delay}s'
  521. r = curl.http_put(urls=[url], fdata=fdata, alpn_proto=proto, extra_args=[
  522. '--expect100-timeout', f'{read_delay+1}'
  523. ])
  524. r.check_stats(count=1, http_status=200, exitcode=0)
  525. # upload larger data, triggering "Expect: 100-continue" code paths
  526. @pytest.mark.parametrize("proto", ['http/1.1'])
  527. def test_07_61_upload_exp100_timeout(self, env: Env, httpd, nghttpx, repeat, proto):
  528. fdata = os.path.join(env.gen_dir, 'data-1m+')
  529. read_delay = 2
  530. curl = CurlClient(env=env)
  531. url = f'https://{env.authority_for(env.domain1, proto)}/curltest/put?id=[0-0]'\
  532. f'&read_delay={read_delay}s'
  533. r = curl.http_put(urls=[url], fdata=fdata, alpn_proto=proto, extra_args=[
  534. '--expect100-timeout', f'{read_delay-1}'
  535. ])
  536. r.check_stats(count=1, http_status=200, exitcode=0)