test_31_vsftpds.py 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199
  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 shutil
  32. import pytest
  33. from testenv import Env, CurlClient, VsFTPD
  34. log = logging.getLogger(__name__)
  35. @pytest.mark.skipif(condition=not Env.has_vsftpd(), reason=f"missing vsftpd")
  36. class TestVsFTPD:
  37. SUPPORTS_SSL = True
  38. @pytest.fixture(autouse=True, scope='class')
  39. def vsftpds(self, env):
  40. if not TestVsFTPD.SUPPORTS_SSL:
  41. pytest.skip('vsftpd does not seem to support SSL')
  42. vsftpds = VsFTPD(env=env, with_ssl=True)
  43. if not vsftpds.start():
  44. vsftpds.stop()
  45. TestVsFTPD.SUPPORTS_SSL = False
  46. pytest.skip('vsftpd does not seem to support SSL')
  47. yield vsftpds
  48. vsftpds.stop()
  49. def _make_docs_file(self, docs_dir: str, fname: str, fsize: int):
  50. fpath = os.path.join(docs_dir, fname)
  51. data1k = 1024*'x'
  52. flen = 0
  53. with open(fpath, 'w') as fd:
  54. while flen < fsize:
  55. fd.write(data1k)
  56. flen += len(data1k)
  57. return flen
  58. @pytest.fixture(autouse=True, scope='class')
  59. def _class_scope(self, env, vsftpds):
  60. if os.path.exists(vsftpds.docs_dir):
  61. shutil.rmtree(vsftpds.docs_dir)
  62. if not os.path.exists(vsftpds.docs_dir):
  63. os.makedirs(vsftpds.docs_dir)
  64. self._make_docs_file(docs_dir=vsftpds.docs_dir, fname='data-1k', fsize=1024)
  65. self._make_docs_file(docs_dir=vsftpds.docs_dir, fname='data-10k', fsize=10*1024)
  66. self._make_docs_file(docs_dir=vsftpds.docs_dir, fname='data-1m', fsize=1024*1024)
  67. self._make_docs_file(docs_dir=vsftpds.docs_dir, fname='data-10m', fsize=10*1024*1024)
  68. env.make_data_file(indir=env.gen_dir, fname="upload-1k", fsize=1024)
  69. env.make_data_file(indir=env.gen_dir, fname="upload-100k", fsize=100*1024)
  70. env.make_data_file(indir=env.gen_dir, fname="upload-1m", fsize=1024*1024)
  71. def test_31_01_list_dir(self, env: Env, vsftpds: VsFTPD, repeat):
  72. curl = CurlClient(env=env)
  73. url = f'ftp://{env.ftp_domain}:{vsftpds.port}/'
  74. r = curl.ftp_ssl_get(urls=[url], with_stats=True)
  75. r.check_stats(count=1, http_status=226)
  76. lines = open(os.path.join(curl.run_dir, 'download_#1.data')).readlines()
  77. assert len(lines) == 4, f'list: {lines}'
  78. # download 1 file, no SSL
  79. @pytest.mark.parametrize("docname", [
  80. 'data-1k', 'data-1m', 'data-10m'
  81. ])
  82. def test_31_02_download_1(self, env: Env, vsftpds: VsFTPD, docname, repeat):
  83. curl = CurlClient(env=env)
  84. srcfile = os.path.join(vsftpds.docs_dir, f'{docname}')
  85. count = 1
  86. url = f'ftp://{env.ftp_domain}:{vsftpds.port}/{docname}?[0-{count-1}]'
  87. r = curl.ftp_ssl_get(urls=[url], with_stats=True)
  88. r.check_stats(count=count, http_status=226)
  89. self.check_downloads(curl, srcfile, count)
  90. @pytest.mark.parametrize("docname", [
  91. 'data-1k', 'data-1m', 'data-10m'
  92. ])
  93. def test_31_03_download_10_serial(self, env: Env, vsftpds: VsFTPD, docname, repeat):
  94. curl = CurlClient(env=env)
  95. srcfile = os.path.join(vsftpds.docs_dir, f'{docname}')
  96. count = 10
  97. url = f'ftp://{env.ftp_domain}:{vsftpds.port}/{docname}?[0-{count-1}]'
  98. r = curl.ftp_ssl_get(urls=[url], with_stats=True)
  99. r.check_stats(count=count, http_status=226)
  100. self.check_downloads(curl, srcfile, count)
  101. @pytest.mark.parametrize("docname", [
  102. 'data-1k', 'data-1m', 'data-10m'
  103. ])
  104. def test_31_04_download_10_parallel(self, env: Env, vsftpds: VsFTPD, docname, repeat):
  105. curl = CurlClient(env=env)
  106. srcfile = os.path.join(vsftpds.docs_dir, f'{docname}')
  107. count = 10
  108. url = f'ftp://{env.ftp_domain}:{vsftpds.port}/{docname}?[0-{count-1}]'
  109. r = curl.ftp_ssl_get(urls=[url], with_stats=True, extra_args=[
  110. '--parallel'
  111. ])
  112. r.check_stats(count=count, http_status=226)
  113. self.check_downloads(curl, srcfile, count)
  114. @pytest.mark.parametrize("docname", [
  115. 'upload-1k', 'upload-100k', 'upload-1m'
  116. ])
  117. def test_31_05_upload_1(self, env: Env, vsftpds: VsFTPD, docname, repeat):
  118. curl = CurlClient(env=env)
  119. srcfile = os.path.join(env.gen_dir, docname)
  120. dstfile = os.path.join(vsftpds.docs_dir, docname)
  121. self._rmf(dstfile)
  122. count = 1
  123. url = f'ftp://{env.ftp_domain}:{vsftpds.port}/'
  124. r = curl.ftp_ssl_upload(urls=[url], fupload=f'{srcfile}', with_stats=True)
  125. r.check_stats(count=count, http_status=226)
  126. self.check_upload(env, vsftpds, docname=docname)
  127. def _rmf(self, path):
  128. if os.path.exists(path):
  129. return os.remove(path)
  130. # check with `tcpdump` if curl causes any TCP RST packets
  131. @pytest.mark.skipif(condition=not Env.tcpdump(), reason="tcpdump not available")
  132. def test_31_06_shutdownh_download(self, env: Env, vsftpds: VsFTPD, repeat):
  133. docname = 'data-1k'
  134. curl = CurlClient(env=env)
  135. count = 1
  136. url = f'ftp://{env.ftp_domain}:{vsftpds.port}/{docname}?[0-{count-1}]'
  137. r = curl.ftp_ssl_get(urls=[url], with_stats=True, with_tcpdump=True)
  138. r.check_stats(count=count, http_status=226)
  139. # vsftp closes control connection without niceties,
  140. # disregard RST packets it sent from its port to curl
  141. assert len(r.tcpdump.stats_excluding(src_port=env.ftps_port)) == 0, f'Unexpected TCP RSTs packets'
  142. # check with `tcpdump` if curl causes any TCP RST packets
  143. @pytest.mark.skipif(condition=not Env.tcpdump(), reason="tcpdump not available")
  144. def test_31_07_shutdownh_upload(self, env: Env, vsftpds: VsFTPD, repeat):
  145. docname = 'upload-1k'
  146. curl = CurlClient(env=env)
  147. srcfile = os.path.join(env.gen_dir, docname)
  148. dstfile = os.path.join(vsftpds.docs_dir, docname)
  149. self._rmf(dstfile)
  150. count = 1
  151. url = f'ftp://{env.ftp_domain}:{vsftpds.port}/'
  152. r = curl.ftp_ssl_upload(urls=[url], fupload=f'{srcfile}', with_stats=True, with_tcpdump=True)
  153. r.check_stats(count=count, http_status=226)
  154. # vsftp closes control connection without niceties,
  155. # disregard RST packets it sent from its port to curl
  156. assert len(r.tcpdump.stats_excluding(src_port=env.ftps_port)) == 0, f'Unexpected TCP RSTs packets'
  157. def check_downloads(self, client, srcfile: str, count: int,
  158. complete: bool = True):
  159. for i in range(count):
  160. dfile = client.download_file(i)
  161. assert os.path.exists(dfile)
  162. if complete and not filecmp.cmp(srcfile, dfile, shallow=False):
  163. diff = "".join(difflib.unified_diff(a=open(srcfile).readlines(),
  164. b=open(dfile).readlines(),
  165. fromfile=srcfile,
  166. tofile=dfile,
  167. n=1))
  168. assert False, f'download {dfile} differs:\n{diff}'
  169. def check_upload(self, env, vsftpd: VsFTPD, docname):
  170. srcfile = os.path.join(env.gen_dir, docname)
  171. dstfile = os.path.join(vsftpd.docs_dir, docname)
  172. assert os.path.exists(srcfile)
  173. assert os.path.exists(dstfile)
  174. if not filecmp.cmp(srcfile, dstfile, shallow=False):
  175. diff = "".join(difflib.unified_diff(a=open(srcfile).readlines(),
  176. b=open(dstfile).readlines(),
  177. fromfile=srcfile,
  178. tofile=dstfile,
  179. n=1))
  180. assert False, f'upload {dstfile} differs:\n{diff}'