• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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#
27import difflib
28import filecmp
29import logging
30import os
31import shutil
32import pytest
33
34from testenv import Env, CurlClient, VsFTPD
35
36
37log = logging.getLogger(__name__)
38
39
40@pytest.mark.skipif(condition=not Env.has_vsftpd(), reason=f"missing vsftpd")
41# rustsl: transfers sometimes fail with "received corrupt message of type InvalidContentType"
42# sporadic, never seen when filter tracing is on
43@pytest.mark.skipif(condition=Env.curl_uses_lib('rustls-ffi'), reason=f"rustls unreliable here")
44class TestVsFTPD:
45
46    SUPPORTS_SSL = True
47
48    @pytest.fixture(autouse=True, scope='class')
49    def vsftpds(self, env):
50        if not TestVsFTPD.SUPPORTS_SSL:
51            pytest.skip('vsftpd does not seem to support SSL')
52        vsftpds = VsFTPD(env=env, with_ssl=True)
53        if not vsftpds.start():
54            vsftpds.stop()
55            TestVsFTPD.SUPPORTS_SSL = False
56            pytest.skip('vsftpd does not seem to support SSL')
57        yield vsftpds
58        vsftpds.stop()
59
60    def _make_docs_file(self, docs_dir: str, fname: str, fsize: int):
61        fpath = os.path.join(docs_dir, fname)
62        data1k = 1024*'x'
63        flen = 0
64        with open(fpath, 'w') as fd:
65            while flen < fsize:
66                fd.write(data1k)
67                flen += len(data1k)
68        return flen
69
70    @pytest.fixture(autouse=True, scope='class')
71    def _class_scope(self, env, vsftpds):
72        if os.path.exists(vsftpds.docs_dir):
73            shutil.rmtree(vsftpds.docs_dir)
74        if not os.path.exists(vsftpds.docs_dir):
75            os.makedirs(vsftpds.docs_dir)
76        self._make_docs_file(docs_dir=vsftpds.docs_dir, fname='data-1k', fsize=1024)
77        self._make_docs_file(docs_dir=vsftpds.docs_dir, fname='data-10k', fsize=10*1024)
78        self._make_docs_file(docs_dir=vsftpds.docs_dir, fname='data-1m', fsize=1024*1024)
79        self._make_docs_file(docs_dir=vsftpds.docs_dir, fname='data-10m', fsize=10*1024*1024)
80
81    def test_31_01_list_dir(self, env: Env, vsftpds: VsFTPD, repeat):
82        curl = CurlClient(env=env)
83        url = f'ftp://{env.ftp_domain}:{vsftpds.port}/'
84        r = curl.ftp_ssl_get(urls=[url], with_stats=True)
85        r.check_stats(count=1, http_status=226)
86        lines = open(os.path.join(curl.run_dir, 'download_#1.data')).readlines()
87        assert len(lines) == 4, f'list: {lines}'
88
89    # download 1 file, no SSL
90    @pytest.mark.parametrize("docname", [
91        'data-1k', 'data-1m', 'data-10m'
92    ])
93    def test_31_02_download_1(self, env: Env, vsftpds: VsFTPD, docname, repeat):
94        curl = CurlClient(env=env)
95        srcfile = os.path.join(vsftpds.docs_dir, f'{docname}')
96        count = 1
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
102    @pytest.mark.parametrize("docname", [
103        'data-1k', 'data-1m', 'data-10m'
104    ])
105    def test_31_03_download_10_serial(self, env: Env, vsftpds: VsFTPD, docname, repeat):
106        curl = CurlClient(env=env)
107        srcfile = os.path.join(vsftpds.docs_dir, f'{docname}')
108        count = 10
109        url = f'ftp://{env.ftp_domain}:{vsftpds.port}/{docname}?[0-{count-1}]'
110        r = curl.ftp_ssl_get(urls=[url], with_stats=True)
111        r.check_stats(count=count, http_status=226)
112        self.check_downloads(curl, srcfile, count)
113
114    @pytest.mark.parametrize("docname", [
115        'data-1k', 'data-1m', 'data-10m'
116    ])
117    def test_31_04_download_10_parallel(self, env: Env, vsftpds: VsFTPD, docname, repeat):
118        curl = CurlClient(env=env)
119        srcfile = os.path.join(vsftpds.docs_dir, f'{docname}')
120        count = 10
121        url = f'ftp://{env.ftp_domain}:{vsftpds.port}/{docname}?[0-{count-1}]'
122        r = curl.ftp_ssl_get(urls=[url], with_stats=True, extra_args=[
123            '--parallel'
124        ])
125        r.check_stats(count=count, http_status=226)
126        self.check_downloads(curl, srcfile, count)
127
128    def check_downloads(self, client, srcfile: str, count: int,
129                        complete: bool = True):
130        for i in range(count):
131            dfile = client.download_file(i)
132            assert os.path.exists(dfile)
133            if complete and not filecmp.cmp(srcfile, dfile, shallow=False):
134                diff = "".join(difflib.unified_diff(a=open(srcfile).readlines(),
135                                                    b=open(dfile).readlines(),
136                                                    fromfile=srcfile,
137                                                    tofile=dfile,
138                                                    n=1))
139                assert False, f'download {dfile} differs:\n{diff}'
140
141
142
143