test_30_vsftpd.py 9.1 KB

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