test_07_upload.py 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318
  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 pytest
  32. from testenv import Env, CurlClient
  33. log = logging.getLogger(__name__)
  34. class TestUpload:
  35. @pytest.fixture(autouse=True, scope='class')
  36. def _class_scope(self, env, httpd, nghttpx):
  37. if env.have_h3():
  38. nghttpx.start_if_needed()
  39. env.make_data_file(indir=env.gen_dir, fname="data-100k", fsize=100*1024)
  40. env.make_data_file(indir=env.gen_dir, fname="data-10m", fsize=10*1024*1024)
  41. httpd.clear_extra_configs()
  42. httpd.reload()
  43. # upload small data, check that this is what was echoed
  44. @pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3'])
  45. def test_07_01_upload_1_small(self, env: Env, httpd, nghttpx, repeat, proto):
  46. if proto == 'h3' and not env.have_h3():
  47. pytest.skip("h3 not supported")
  48. if proto == 'h3' and env.curl_uses_lib('msh3'):
  49. pytest.skip("msh3 fails here")
  50. data = '0123456789'
  51. curl = CurlClient(env=env)
  52. url = f'https://{env.authority_for(env.domain1, proto)}/curltest/echo?id=[0-0]'
  53. r = curl.http_upload(urls=[url], data=data, alpn_proto=proto)
  54. r.check_response(count=1, http_status=200)
  55. respdata = open(curl.response_file(0)).readlines()
  56. assert respdata == [data]
  57. # upload large data, check that this is what was echoed
  58. @pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3'])
  59. def test_07_02_upload_1_large(self, env: Env, httpd, nghttpx, repeat, proto):
  60. if proto == 'h3' and not env.have_h3():
  61. pytest.skip("h3 not supported")
  62. if proto == 'h3' and env.curl_uses_lib('msh3'):
  63. pytest.skip("msh3 fails here")
  64. fdata = os.path.join(env.gen_dir, 'data-100k')
  65. curl = CurlClient(env=env)
  66. url = f'https://{env.authority_for(env.domain1, proto)}/curltest/echo?id=[0-0]'
  67. r = curl.http_upload(urls=[url], data=f'@{fdata}', alpn_proto=proto)
  68. r.check_response(count=1, http_status=200)
  69. indata = open(fdata).readlines()
  70. respdata = open(curl.response_file(0)).readlines()
  71. assert respdata == indata
  72. # upload data sequentially, check that they were echoed
  73. @pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3'])
  74. def test_07_10_upload_sequential(self, env: Env, httpd, nghttpx, repeat, proto):
  75. if proto == 'h3' and not env.have_h3():
  76. pytest.skip("h3 not supported")
  77. if proto == 'h3' and env.curl_uses_lib('msh3'):
  78. pytest.skip("msh3 stalls here")
  79. count = 50
  80. data = '0123456789'
  81. curl = CurlClient(env=env)
  82. url = f'https://{env.authority_for(env.domain1, proto)}/curltest/echo?id=[0-{count-1}]'
  83. r = curl.http_upload(urls=[url], data=data, alpn_proto=proto)
  84. r.check_response(count=count, http_status=200)
  85. for i in range(count):
  86. respdata = open(curl.response_file(i)).readlines()
  87. assert respdata == [data]
  88. # upload data parallel, check that they were echoed
  89. @pytest.mark.parametrize("proto", ['h2', 'h3'])
  90. def test_07_11_upload_parallel(self, env: Env, httpd, nghttpx, repeat, proto):
  91. if proto == 'h3' and not env.have_h3():
  92. pytest.skip("h3 not supported")
  93. if proto == 'h3' and env.curl_uses_lib('msh3'):
  94. pytest.skip("msh3 stalls here")
  95. # limit since we use a separate connection in h1
  96. count = 50
  97. data = '0123456789'
  98. curl = CurlClient(env=env)
  99. url = f'https://{env.authority_for(env.domain1, proto)}/curltest/echo?id=[0-{count-1}]'
  100. r = curl.http_upload(urls=[url], data=data, alpn_proto=proto,
  101. extra_args=['--parallel'])
  102. r.check_response(count=count, http_status=200)
  103. for i in range(count):
  104. respdata = open(curl.response_file(i)).readlines()
  105. assert respdata == [data]
  106. # upload large data sequentially, check that this is what was echoed
  107. @pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3'])
  108. def test_07_20_upload_seq_large(self, env: Env, httpd, nghttpx, repeat, proto):
  109. if proto == 'h3' and not env.have_h3():
  110. pytest.skip("h3 not supported")
  111. if proto == 'h3' and env.curl_uses_lib('msh3'):
  112. pytest.skip("msh3 stalls here")
  113. fdata = os.path.join(env.gen_dir, 'data-100k')
  114. count = 50
  115. curl = CurlClient(env=env)
  116. url = f'https://{env.authority_for(env.domain1, proto)}/curltest/echo?id=[0-{count-1}]'
  117. r = curl.http_upload(urls=[url], data=f'@{fdata}', alpn_proto=proto)
  118. r.check_response(count=count, http_status=200)
  119. indata = open(fdata).readlines()
  120. r.check_response(count=count, http_status=200)
  121. for i in range(count):
  122. respdata = open(curl.response_file(i)).readlines()
  123. assert respdata == indata
  124. # upload very large data sequentially, check that this is what was echoed
  125. @pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3'])
  126. def test_07_12_upload_seq_large(self, env: Env, httpd, nghttpx, repeat, proto):
  127. if proto == 'h3' and not env.have_h3():
  128. pytest.skip("h3 not supported")
  129. if proto == 'h3' and env.curl_uses_lib('msh3'):
  130. pytest.skip("msh3 stalls here")
  131. fdata = os.path.join(env.gen_dir, 'data-10m')
  132. count = 2
  133. curl = CurlClient(env=env)
  134. url = f'https://{env.authority_for(env.domain1, proto)}/curltest/echo?id=[0-{count-1}]'
  135. r = curl.http_upload(urls=[url], data=f'@{fdata}', alpn_proto=proto)
  136. r.check_response(count=count, http_status=200)
  137. indata = open(fdata).readlines()
  138. for i in range(count):
  139. respdata = open(curl.response_file(i)).readlines()
  140. assert respdata == indata
  141. # upload data parallel, check that they were echoed
  142. @pytest.mark.parametrize("proto", ['h2', 'h3'])
  143. def test_07_20_upload_parallel(self, env: Env, httpd, nghttpx, repeat, proto):
  144. if proto == 'h3' and not env.have_h3():
  145. pytest.skip("h3 not supported")
  146. if proto == 'h3' and env.curl_uses_lib('msh3'):
  147. pytest.skip("msh3 stalls here")
  148. # limit since we use a separate connection in h1
  149. count = 50
  150. data = '0123456789'
  151. curl = CurlClient(env=env)
  152. url = f'https://{env.authority_for(env.domain1, proto)}/curltest/echo?id=[0-{count-1}]'
  153. r = curl.http_upload(urls=[url], data=data, alpn_proto=proto,
  154. extra_args=['--parallel'])
  155. r.check_response(count=count, http_status=200)
  156. for i in range(count):
  157. respdata = open(curl.response_file(i)).readlines()
  158. assert respdata == [data]
  159. # upload large data parallel, check that this is what was echoed
  160. @pytest.mark.parametrize("proto", ['h2', 'h3'])
  161. def test_07_21_upload_parallel_large(self, env: Env, httpd, nghttpx, repeat, proto):
  162. if proto == 'h3' and not env.have_h3():
  163. pytest.skip("h3 not supported")
  164. if proto == 'h3' and env.curl_uses_lib('msh3'):
  165. pytest.skip("msh3 stalls here")
  166. fdata = os.path.join(env.gen_dir, 'data-100k')
  167. # limit since we use a separate connection in h1
  168. count = 50
  169. curl = CurlClient(env=env)
  170. url = f'https://{env.authority_for(env.domain1, proto)}/curltest/echo?id=[0-{count-1}]'
  171. r = curl.http_upload(urls=[url], data=f'@{fdata}', alpn_proto=proto,
  172. extra_args=['--parallel'])
  173. r.check_response(count=count, http_status=200)
  174. self.check_download(count, fdata, curl)
  175. # PUT 100k
  176. @pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3'])
  177. def test_07_30_put_100k(self, env: Env, httpd, nghttpx, repeat, proto):
  178. if proto == 'h3' and not env.have_h3():
  179. pytest.skip("h3 not supported")
  180. if proto == 'h3' and env.curl_uses_lib('msh3'):
  181. pytest.skip("msh3 fails here")
  182. fdata = os.path.join(env.gen_dir, 'data-100k')
  183. count = 1
  184. curl = CurlClient(env=env)
  185. url = f'https://{env.authority_for(env.domain1, proto)}/curltest/put?id=[0-{count-1}]'
  186. r = curl.http_put(urls=[url], fdata=fdata, alpn_proto=proto,
  187. extra_args=['--parallel'])
  188. r.check_response(count=count, http_status=200)
  189. exp_data = [f'{os.path.getsize(fdata)}']
  190. r.check_response(count=count, http_status=200)
  191. for i in range(count):
  192. respdata = open(curl.response_file(i)).readlines()
  193. assert respdata == exp_data
  194. # PUT 10m
  195. @pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3'])
  196. def test_07_31_put_10m(self, env: Env, httpd, nghttpx, repeat, proto):
  197. if proto == 'h3' and not env.have_h3():
  198. pytest.skip("h3 not supported")
  199. if proto == 'h3' and env.curl_uses_lib('msh3'):
  200. pytest.skip("msh3 fails here")
  201. fdata = os.path.join(env.gen_dir, 'data-10m')
  202. count = 1
  203. curl = CurlClient(env=env)
  204. url = f'https://{env.authority_for(env.domain1, proto)}/curltest/put?id=[0-{count-1}]&chunk_delay=10ms'
  205. r = curl.http_put(urls=[url], fdata=fdata, alpn_proto=proto,
  206. extra_args=['--parallel'])
  207. r.check_response(count=count, http_status=200)
  208. exp_data = [f'{os.path.getsize(fdata)}']
  209. r.check_response(count=count, http_status=200)
  210. for i in range(count):
  211. respdata = open(curl.response_file(i)).readlines()
  212. assert respdata == exp_data
  213. # issue #10591
  214. @pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3'])
  215. def test_07_32_issue_10591(self, env: Env, httpd, nghttpx, repeat, proto):
  216. if proto == 'h3' and not env.have_h3():
  217. pytest.skip("h3 not supported")
  218. if proto == 'h3' and env.curl_uses_lib('msh3'):
  219. pytest.skip("msh3 fails here")
  220. fdata = os.path.join(env.gen_dir, 'data-10m')
  221. count = 1
  222. curl = CurlClient(env=env)
  223. url = f'https://{env.authority_for(env.domain1, proto)}/curltest/put?id=[0-{count-1}]'
  224. r = curl.http_put(urls=[url], fdata=fdata, alpn_proto=proto)
  225. r.check_response(count=count, http_status=200)
  226. # issue #11157, upload that is 404'ed by server, needs to terminate
  227. # correctly and not time out on sending
  228. def test_07_33_issue_11157a(self, env: Env, httpd, nghttpx, repeat):
  229. proto = 'h2'
  230. fdata = os.path.join(env.gen_dir, 'data-10m')
  231. # send a POST to our PUT handler which will send immediately a 404 back
  232. url = f'https://{env.authority_for(env.domain1, proto)}/curltest/put'
  233. curl = CurlClient(env=env)
  234. r = curl.run_direct(with_stats=True, args=[
  235. '--resolve', f'{env.authority_for(env.domain1, proto)}:127.0.0.1',
  236. '--cacert', env.ca.cert_file,
  237. '--request', 'POST',
  238. '--max-time', '5', '-v',
  239. '--url', url,
  240. '--form', 'idList=12345678',
  241. '--form', 'pos=top',
  242. '--form', 'name=mr_test',
  243. '--form', f'fileSource=@{fdata};type=application/pdf',
  244. ])
  245. assert r.exit_code == 0, f'{r}'
  246. r.check_stats(1, 404)
  247. # issue #11157, send upload that is slowly read in
  248. def test_07_33_issue_11157b(self, env: Env, httpd, nghttpx, repeat):
  249. proto = 'h2'
  250. fdata = os.path.join(env.gen_dir, 'data-10m')
  251. # tell our test PUT handler to read the upload more slowly, so
  252. # that the send buffering and transfer loop needs to wait
  253. url = f'https://{env.authority_for(env.domain1, proto)}/curltest/put?chunk_delay=2ms'
  254. curl = CurlClient(env=env)
  255. r = curl.run_direct(with_stats=True, args=[
  256. '--resolve', f'{env.authority_for(env.domain1, proto)}:127.0.0.1',
  257. '--cacert', env.ca.cert_file,
  258. '--request', 'PUT',
  259. '--max-time', '10', '-v',
  260. '--url', url,
  261. '--form', 'idList=12345678',
  262. '--form', 'pos=top',
  263. '--form', 'name=mr_test',
  264. '--form', f'fileSource=@{fdata};type=application/pdf',
  265. ])
  266. assert r.exit_code == 0, r.dump_logs()
  267. r.check_stats(1, 200)
  268. def test_07_34_issue_11194(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. fdata = os.path.join(env.gen_dir, 'data-100k')
  274. url = f'https://{env.authority_for(env.domain1, proto)}/curltest/put'
  275. curl = CurlClient(env=env)
  276. r = curl.run_direct(with_stats=True, args=[
  277. '--verbose',
  278. '--resolve', f'{env.authority_for(env.domain1, proto)}:127.0.0.1',
  279. '--cacert', env.ca.cert_file,
  280. '--request', 'PUT',
  281. '--digest', '--user', 'test:test',
  282. '--data-binary', f'@{fdata}'
  283. '--url', url,
  284. ])
  285. assert r.exit_code == 0, r.dump_logs()
  286. r.check_stats(1, 200)
  287. def check_download(self, count, srcfile, curl):
  288. for i in range(count):
  289. dfile = curl.download_file(i)
  290. assert os.path.exists(dfile)
  291. if not filecmp.cmp(srcfile, dfile, shallow=False):
  292. diff = "".join(difflib.unified_diff(a=open(srcfile).readlines(),
  293. b=open(dfile).readlines(),
  294. fromfile=srcfile,
  295. tofile=dfile,
  296. n=1))
  297. assert False, f'download {dfile} differs:\n{diff}'