~bzr-pqm/bzr/bzr.dev

4763.2.4 by John Arbash Meinel
merge bzr.2.1 in preparation for NEWS entry.
1
# Copyright (C) 2009, 2010 Canonical Ltd
3508.1.10 by Vincent Ladeuil
Start supporting pyftpdlib as an ftp test server.
2
#
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
7
#
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
# GNU General Public License for more details.
12
#
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
4183.7.1 by Sabin Iacob
update FSF mailing address
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
3508.1.10 by Vincent Ladeuil
Start supporting pyftpdlib as an ftp test server.
16
"""
17
FTP test server.
18
19
Based on pyftpdlib: http://code.google.com/p/pyftpdlib/
20
"""
21
22
import errno
23
import os
24
from pyftpdlib import ftpserver
25
import select
26
import threading
27
28
29
from bzrlib import (
3508.1.18 by Vincent Ladeuil
Final tweaks.
30
    osutils,
4731.2.9 by Vincent Ladeuil
Implement a new -Ethreads to better track the leaks.
31
    tests,
3508.1.10 by Vincent Ladeuil
Start supporting pyftpdlib as an ftp test server.
32
    trace,
33
    )
5017.3.16 by Vincent Ladeuil
Fix imports for ftp_server/pyftpdlib_based.py
34
from bzrlib.tests import test_server
3508.1.10 by Vincent Ladeuil
Start supporting pyftpdlib as an ftp test server.
35
36
5947.1.1 by Vincent Ladeuil
Support pyftplib-0.6.0 as an ftp test server
37
# Convert the pyftplib string version into a tuple to avoid traps in string
38
# comparison.
39
pyftplib_version = tuple(map(int, ftpserver.__ver__.split('.')))
40
41
3508.1.10 by Vincent Ladeuil
Start supporting pyftpdlib as an ftp test server.
42
class AnonymousWithWriteAccessAuthorizer(ftpserver.DummyAuthorizer):
43
44
    def _check_permissions(self, username, perm):
45
        # Like base implementation but don't warn about write permissions
3508.1.18 by Vincent Ladeuil
Final tweaks.
46
        # assigned to anonymous, since that's exactly our purpose.
3508.1.10 by Vincent Ladeuil
Start supporting pyftpdlib as an ftp test server.
47
        for p in perm:
48
            if p not in self.read_perms + self.write_perms:
49
                raise ftpserver.AuthorizerError('No such permission "%s"' %p)
50
51
52
class BzrConformingFS(ftpserver.AbstractedFS):
53
3508.1.20 by Vincent Ladeuil
Test passing for python2.5 and 2.6.
54
    def chmod(self, path, mode):
55
        return os.chmod(path, mode)
56
3508.1.10 by Vincent Ladeuil
Start supporting pyftpdlib as an ftp test server.
57
    def listdir(self, path):
58
        """List the content of a directory."""
3508.1.18 by Vincent Ladeuil
Final tweaks.
59
        return [osutils.safe_utf8(s) for s in os.listdir(path)]
3508.1.10 by Vincent Ladeuil
Start supporting pyftpdlib as an ftp test server.
60
3508.1.11 by Vincent Ladeuil
Tests passing for python2.5 and python2.6 (and python2.4 :).
61
    def fs2ftp(self, fspath):
4935.1.3 by Vincent Ladeuil
Better fix for fancy_rename respecting callers file encoding.
62
        p = ftpserver.AbstractedFS.fs2ftp(self, osutils.safe_unicode(fspath))
3508.1.18 by Vincent Ladeuil
Final tweaks.
63
        return osutils.safe_utf8(p)
64
4935.1.1 by Vincent Ladeuil
Support Unicode paths for ftp transport (encoded as utf8).
65
    def ftp2fs(self, ftppath):
66
        p = osutils.safe_unicode(ftppath)
67
        return ftpserver.AbstractedFS.ftp2fs(self, p)
3508.1.10 by Vincent Ladeuil
Start supporting pyftpdlib as an ftp test server.
68
3508.1.23 by Vincent Ladeuil
Fix as per Martin's review.
69
class BzrConformingFTPHandler(ftpserver.FTPHandler):
3508.1.10 by Vincent Ladeuil
Start supporting pyftpdlib as an ftp test server.
70
71
    abstracted_fs = BzrConformingFS
72
73
    def __init__(self, conn, server):
74
        ftpserver.FTPHandler.__init__(self, conn, server)
75
        self.authorizer = server.authorizer
76
77
    def ftp_SIZE(self, path):
78
        # bzr is overly picky here, but we want to make the test suite pass
79
        # first. This may need to be revisited -- vila 20090226
80
        line = self.fs.fs2ftp(path)
81
        if self.fs.isdir(self.fs.realpath(path)):
82
            why = "%s is a directory" % line
83
            self.log('FAIL SIZE "%s". %s.' % (line, why))
84
            self.respond("550 %s."  %why)
85
        else:
86
            ftpserver.FTPHandler.ftp_SIZE(self, path)
87
88
    def ftp_NLST(self, path):
89
        # bzr is overly picky here, but we want to make the test suite pass
90
        # first. This may need to be revisited -- vila 20090226
91
        line = self.fs.fs2ftp(path)
92
        if self.fs.isfile(self.fs.realpath(path)):
93
            why = "Not a directory: %s" % line
4725.3.3 by Vincent Ladeuil
Fix test failure at the root without cleaning up ftp APPE.
94
            self.log('FAIL NLST "%s". %s.' % (line, why))
3508.1.10 by Vincent Ladeuil
Start supporting pyftpdlib as an ftp test server.
95
            self.respond("550 %s."  %why)
96
        else:
97
            ftpserver.FTPHandler.ftp_NLST(self, path)
98
6437.51.1 by Vincent Ladeuil
Support pyftpdlib >= 0.7.0
99
    def log_cmd(self, cmd, arg, respcode, respstr):
100
        # base class version choke on unicode, the alternative is to just
101
        # provide an empty implementation and relies on the client to do
102
        # the logging for debugging purposes. Not worth the trouble so far
103
        # -- vila 20110607
104
        if cmd in ("DELE", "RMD", "RNFR", "RNTO", "MKD"):
105
            line = '"%s" %s' % (' '.join([cmd, unicode(arg)]).strip(), respcode)
106
            self.log(line)
107
108
109
# An empty password is valid, hence the arg is neither mandatory nor forbidden
110
ftpserver.proto_cmds['PASS']['arg'] = None
3508.1.10 by Vincent Ladeuil
Start supporting pyftpdlib as an ftp test server.
111
112
class ftp_server(ftpserver.FTPServer):
113
114
    def __init__(self, address, handler, authorizer):
115
        ftpserver.FTPServer.__init__(self, address, handler)
116
        self.authorizer = authorizer
3508.1.17 by Vincent Ladeuil
Allows empty passwords with pyftpdlib ftp test server.
117
        # Worth backporting upstream ?
3508.1.11 by Vincent Ladeuil
Tests passing for python2.5 and python2.6 (and python2.4 :).
118
        self.addr = self.socket.getsockname()
3508.1.10 by Vincent Ladeuil
Start supporting pyftpdlib as an ftp test server.
119
120
5017.3.16 by Vincent Ladeuil
Fix imports for ftp_server/pyftpdlib_based.py
121
class FTPTestServer(test_server.TestServer):
3508.1.10 by Vincent Ladeuil
Start supporting pyftpdlib as an ftp test server.
122
    """Common code for FTP server facilities."""
123
124
    def __init__(self):
125
        self._root = None
126
        self._ftp_server = None
127
        self._port = None
128
        self._async_thread = None
129
        # ftp server logs
130
        self.logs = []
3508.1.11 by Vincent Ladeuil
Tests passing for python2.5 and python2.6 (and python2.4 :).
131
        self._ftpd_running = False
3508.1.10 by Vincent Ladeuil
Start supporting pyftpdlib as an ftp test server.
132
133
    def get_url(self):
134
        """Calculate an ftp url to this server."""
3508.1.13 by Vincent Ladeuil
Fix last failing tests under python2.6.
135
        return 'ftp://anonymous@localhost:%d/' % (self._port)
3508.1.10 by Vincent Ladeuil
Start supporting pyftpdlib as an ftp test server.
136
137
    def get_bogus_url(self):
138
        """Return a URL which cannot be connected to."""
139
        return 'ftp://127.0.0.1:1/'
140
141
    def log(self, message):
3508.1.18 by Vincent Ladeuil
Final tweaks.
142
        """This is used by ftp_server to log connections, etc."""
3508.1.10 by Vincent Ladeuil
Start supporting pyftpdlib as an ftp test server.
143
        self.logs.append(message)
144
4934.3.3 by Martin Pool
Rename Server.setUp to Server.start_server
145
    def start_server(self, vfs_server=None):
5017.3.16 by Vincent Ladeuil
Fix imports for ftp_server/pyftpdlib_based.py
146
        if not (vfs_server is None or isinstance(vfs_server,
147
                                                 test_server.LocalURLServer)):
3508.1.10 by Vincent Ladeuil
Start supporting pyftpdlib as an ftp test server.
148
            raise AssertionError(
149
                "FTPServer currently assumes local transport, got %s"
150
                % vfs_server)
151
        self._root = os.getcwdu()
152
153
        address = ('localhost', 0) # bind to a random port
154
        authorizer = AnonymousWithWriteAccessAuthorizer()
6437.51.1 by Vincent Ladeuil
Support pyftpdlib >= 0.7.0
155
        authorizer.add_anonymous(self._root, perm='elradfmwM')
3508.1.23 by Vincent Ladeuil
Fix as per Martin's review.
156
        self._ftp_server = ftp_server(address, BzrConformingFTPHandler,
3508.1.10 by Vincent Ladeuil
Start supporting pyftpdlib as an ftp test server.
157
                                      authorizer)
158
        # This is hacky as hell, will not work if we need two servers working
159
        # at the same time, but that's the best we can do so far...
160
        # FIXME: At least log and logline could be overriden in the handler ?
161
        # -- vila 20090227
162
        ftpserver.log = self.log
163
        ftpserver.logline = self.log
164
        ftpserver.logerror = self.log
165
166
        self._port = self._ftp_server.socket.getsockname()[1]
3508.1.12 by Vincent Ladeuil
Don't require patched version for pyftpdlib.
167
        self._ftpd_starting = threading.Lock()
168
        self._ftpd_starting.acquire() # So it can be released by the server
4725.3.2 by Vincent Ladeuil
Cosmetic change.
169
        self._ftpd_thread = threading.Thread(target=self._run_server,)
3508.1.12 by Vincent Ladeuil
Don't require patched version for pyftpdlib.
170
        self._ftpd_thread.start()
4731.2.9 by Vincent Ladeuil
Implement a new -Ethreads to better track the leaks.
171
        if 'threads' in tests.selftest_debug_flags:
5247.5.29 by Vincent Ladeuil
Fixed as per jam's review.
172
            sys.stderr.write('Thread started: %s\n'
173
                             % (self._ftpd_thread.ident,))
3508.1.12 by Vincent Ladeuil
Don't require patched version for pyftpdlib.
174
        # Wait for the server thread to start (i.e release the lock)
175
        self._ftpd_starting.acquire()
176
        self._ftpd_starting.release()
3508.1.10 by Vincent Ladeuil
Start supporting pyftpdlib as an ftp test server.
177
4934.3.1 by Martin Pool
Rename Server.tearDown to .stop_server
178
    def stop_server(self):
179
        """See bzrlib.transport.Server.stop_server."""
3508.1.12 by Vincent Ladeuil
Don't require patched version for pyftpdlib.
180
        # Tell the server to stop, but also close the server socket for tests
181
        # that start the server but never initiate a connection. Closing the
182
        # socket should be done first though, to avoid further connections.
3508.1.11 by Vincent Ladeuil
Tests passing for python2.5 and python2.6 (and python2.4 :).
183
        self._ftp_server.close()
3508.1.12 by Vincent Ladeuil
Don't require patched version for pyftpdlib.
184
        self._ftpd_running = False
185
        self._ftpd_thread.join()
4731.2.9 by Vincent Ladeuil
Implement a new -Ethreads to better track the leaks.
186
        if 'threads' in tests.selftest_debug_flags:
5247.5.29 by Vincent Ladeuil
Fixed as per jam's review.
187
            sys.stderr.write('Thread  joined: %s\n'
188
                             % (self._ftpd_thread.ident,))
3508.1.10 by Vincent Ladeuil
Start supporting pyftpdlib as an ftp test server.
189
3508.1.11 by Vincent Ladeuil
Tests passing for python2.5 and python2.6 (and python2.4 :).
190
    def _run_server(self):
6437.51.1 by Vincent Ladeuil
Support pyftpdlib >= 0.7.0
191
        """Run the server until stop_server is called.
192
193
        Shut it down properly then.
3508.1.10 by Vincent Ladeuil
Start supporting pyftpdlib as an ftp test server.
194
        """
3508.1.11 by Vincent Ladeuil
Tests passing for python2.5 and python2.6 (and python2.4 :).
195
        self._ftpd_running = True
3508.1.12 by Vincent Ladeuil
Don't require patched version for pyftpdlib.
196
        self._ftpd_starting.release()
197
        while self._ftpd_running:
4725.3.1 by Vincent Ladeuil
Fix test failure and clean up ftp APPE.
198
            try:
199
                self._ftp_server.serve_forever(timeout=0.1, count=1)
200
            except select.error, e:
201
                if e.args[0] != errno.EBADF:
202
                    raise
3508.1.12 by Vincent Ladeuil
Don't require patched version for pyftpdlib.
203
        self._ftp_server.close_all(ignore_all=True)
3508.1.13 by Vincent Ladeuil
Fix last failing tests under python2.6.
204
205
    def add_user(self, user, password):
206
        """Add a user with write access."""
207
        self._ftp_server.authorizer.add_user(user, password, self._root,
6437.51.1 by Vincent Ladeuil
Support pyftpdlib >= 0.7.0
208
                                             perm='elradfmwM')