~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,
3508.1.10 by Vincent Ladeuil
Start supporting pyftpdlib as an ftp test server.
31
    trace,
32
    )
5017.3.16 by Vincent Ladeuil
Fix imports for ftp_server/pyftpdlib_based.py
33
from bzrlib.tests import test_server
3508.1.10 by Vincent Ladeuil
Start supporting pyftpdlib as an ftp test server.
34
35
36
class AnonymousWithWriteAccessAuthorizer(ftpserver.DummyAuthorizer):
37
38
    def _check_permissions(self, username, perm):
39
        # Like base implementation but don't warn about write permissions
3508.1.18 by Vincent Ladeuil
Final tweaks.
40
        # assigned to anonymous, since that's exactly our purpose.
3508.1.10 by Vincent Ladeuil
Start supporting pyftpdlib as an ftp test server.
41
        for p in perm:
42
            if p not in self.read_perms + self.write_perms:
43
                raise ftpserver.AuthorizerError('No such permission "%s"' %p)
44
45
46
class BzrConformingFS(ftpserver.AbstractedFS):
47
3508.1.20 by Vincent Ladeuil
Test passing for python2.5 and 2.6.
48
    def chmod(self, path, mode):
49
        return os.chmod(path, mode)
50
3508.1.10 by Vincent Ladeuil
Start supporting pyftpdlib as an ftp test server.
51
    def listdir(self, path):
52
        """List the content of a directory."""
3508.1.18 by Vincent Ladeuil
Final tweaks.
53
        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.
54
3508.1.11 by Vincent Ladeuil
Tests passing for python2.5 and python2.6 (and python2.4 :).
55
    def fs2ftp(self, fspath):
4935.1.3 by Vincent Ladeuil
Better fix for fancy_rename respecting callers file encoding.
56
        p = ftpserver.AbstractedFS.fs2ftp(self, osutils.safe_unicode(fspath))
3508.1.18 by Vincent Ladeuil
Final tweaks.
57
        return osutils.safe_utf8(p)
58
4935.1.1 by Vincent Ladeuil
Support Unicode paths for ftp transport (encoded as utf8).
59
    def ftp2fs(self, ftppath):
60
        p = osutils.safe_unicode(ftppath)
61
        return ftpserver.AbstractedFS.ftp2fs(self, p)
3508.1.10 by Vincent Ladeuil
Start supporting pyftpdlib as an ftp test server.
62
3508.1.23 by Vincent Ladeuil
Fix as per Martin's review.
63
class BzrConformingFTPHandler(ftpserver.FTPHandler):
3508.1.10 by Vincent Ladeuil
Start supporting pyftpdlib as an ftp test server.
64
65
    abstracted_fs = BzrConformingFS
66
67
    def __init__(self, conn, server):
68
        ftpserver.FTPHandler.__init__(self, conn, server)
69
        self.authorizer = server.authorizer
70
71
    def ftp_SIZE(self, path):
72
        # bzr is overly picky here, but we want to make the test suite pass
73
        # first. This may need to be revisited -- vila 20090226
74
        line = self.fs.fs2ftp(path)
75
        if self.fs.isdir(self.fs.realpath(path)):
76
            why = "%s is a directory" % line
77
            self.log('FAIL SIZE "%s". %s.' % (line, why))
78
            self.respond("550 %s."  %why)
79
        else:
80
            ftpserver.FTPHandler.ftp_SIZE(self, path)
81
82
    def ftp_NLST(self, path):
83
        # bzr is overly picky here, but we want to make the test suite pass
84
        # first. This may need to be revisited -- vila 20090226
85
        line = self.fs.fs2ftp(path)
86
        if self.fs.isfile(self.fs.realpath(path)):
87
            why = "Not a directory: %s" % line
4725.3.3 by Vincent Ladeuil
Fix test failure at the root without cleaning up ftp APPE.
88
            self.log('FAIL NLST "%s". %s.' % (line, why))
3508.1.10 by Vincent Ladeuil
Start supporting pyftpdlib as an ftp test server.
89
            self.respond("550 %s."  %why)
90
        else:
91
            ftpserver.FTPHandler.ftp_NLST(self, path)
92
93
    def ftp_SITE_CHMOD(self, line):
94
        try:
3508.1.14 by Vincent Ladeuil
Tweak SITE_CHMOD server command.
95
            mode, path = line.split(None, 1)
3508.1.10 by Vincent Ladeuil
Start supporting pyftpdlib as an ftp test server.
96
            mode = int(mode, 8)
97
        except ValueError:
98
            # We catch both malformed line and malformed mode with the same
99
            # ValueError.
100
            self.respond("500 'SITE CHMOD %s': command not understood."
101
                         % line)
3508.1.18 by Vincent Ladeuil
Final tweaks.
102
            self.log('FAIL SITE CHMOD ' % line)
3508.1.10 by Vincent Ladeuil
Start supporting pyftpdlib as an ftp test server.
103
            return
104
        ftp_path = self.fs.fs2ftp(path)
105
        try:
106
            self.run_as_current_user(self.fs.chmod, self.fs.ftp2fs(path), mode)
107
        except OSError, err:
108
            why = ftpserver._strerror(err)
109
            self.log('FAIL SITE CHMOD 0%03o "%s". %s.' % (mode, ftp_path, why))
110
            self.respond('550 %s.' % why)
111
        else:
112
            self.log('OK SITE CHMOD 0%03o "%s".' % (mode, ftp_path))
113
            self.respond('200 SITE CHMOD succesful.')
114
115
116
# pyftpdlib says to define SITE commands by declaring ftp_SITE_<CMD> methods,
117
# but fails to recognize them.
118
ftpserver.proto_cmds['SITE CHMOD'] = ftpserver._CommandProperty(
119
    perm='w', # Best fit choice even if not exactly right (can be d, f or m too)
120
    auth_needed=True, arg_needed=True, check_path=False,
121
    help='Syntax: SITE CHMOD <SP>  octal_mode_bits file-name (chmod file)',
122
    )
3508.1.18 by Vincent Ladeuil
Final tweaks.
123
# An empty password is valid, hence the arg is neither mandatory not forbidden
124
ftpserver.proto_cmds['PASS'].arg_needed = None
3508.1.10 by Vincent Ladeuil
Start supporting pyftpdlib as an ftp test server.
125
126
127
class ftp_server(ftpserver.FTPServer):
128
129
    def __init__(self, address, handler, authorizer):
130
        ftpserver.FTPServer.__init__(self, address, handler)
131
        self.authorizer = authorizer
3508.1.17 by Vincent Ladeuil
Allows empty passwords with pyftpdlib ftp test server.
132
        # Worth backporting upstream ?
3508.1.11 by Vincent Ladeuil
Tests passing for python2.5 and python2.6 (and python2.4 :).
133
        self.addr = self.socket.getsockname()
3508.1.10 by Vincent Ladeuil
Start supporting pyftpdlib as an ftp test server.
134
135
5017.3.16 by Vincent Ladeuil
Fix imports for ftp_server/pyftpdlib_based.py
136
class FTPTestServer(test_server.TestServer):
3508.1.10 by Vincent Ladeuil
Start supporting pyftpdlib as an ftp test server.
137
    """Common code for FTP server facilities."""
138
139
    def __init__(self):
140
        self._root = None
141
        self._ftp_server = None
142
        self._port = None
143
        self._async_thread = None
144
        # ftp server logs
145
        self.logs = []
3508.1.11 by Vincent Ladeuil
Tests passing for python2.5 and python2.6 (and python2.4 :).
146
        self._ftpd_running = False
3508.1.10 by Vincent Ladeuil
Start supporting pyftpdlib as an ftp test server.
147
148
    def get_url(self):
149
        """Calculate an ftp url to this server."""
3508.1.13 by Vincent Ladeuil
Fix last failing tests under python2.6.
150
        return 'ftp://anonymous@localhost:%d/' % (self._port)
3508.1.10 by Vincent Ladeuil
Start supporting pyftpdlib as an ftp test server.
151
152
    def get_bogus_url(self):
153
        """Return a URL which cannot be connected to."""
154
        return 'ftp://127.0.0.1:1/'
155
156
    def log(self, message):
3508.1.18 by Vincent Ladeuil
Final tweaks.
157
        """This is used by ftp_server to log connections, etc."""
3508.1.10 by Vincent Ladeuil
Start supporting pyftpdlib as an ftp test server.
158
        self.logs.append(message)
159
4934.3.3 by Martin Pool
Rename Server.setUp to Server.start_server
160
    def start_server(self, vfs_server=None):
5017.3.16 by Vincent Ladeuil
Fix imports for ftp_server/pyftpdlib_based.py
161
        if not (vfs_server is None or isinstance(vfs_server,
162
                                                 test_server.LocalURLServer)):
3508.1.10 by Vincent Ladeuil
Start supporting pyftpdlib as an ftp test server.
163
            raise AssertionError(
164
                "FTPServer currently assumes local transport, got %s"
165
                % vfs_server)
166
        self._root = os.getcwdu()
167
168
        address = ('localhost', 0) # bind to a random port
169
        authorizer = AnonymousWithWriteAccessAuthorizer()
3508.1.13 by Vincent Ladeuil
Fix last failing tests under python2.6.
170
        authorizer.add_anonymous(self._root, perm='elradfmw')
3508.1.23 by Vincent Ladeuil
Fix as per Martin's review.
171
        self._ftp_server = ftp_server(address, BzrConformingFTPHandler,
3508.1.10 by Vincent Ladeuil
Start supporting pyftpdlib as an ftp test server.
172
                                      authorizer)
173
        # This is hacky as hell, will not work if we need two servers working
174
        # at the same time, but that's the best we can do so far...
175
        # FIXME: At least log and logline could be overriden in the handler ?
176
        # -- vila 20090227
177
        ftpserver.log = self.log
178
        ftpserver.logline = self.log
179
        ftpserver.logerror = self.log
180
181
        self._port = self._ftp_server.socket.getsockname()[1]
3508.1.12 by Vincent Ladeuil
Don't require patched version for pyftpdlib.
182
        self._ftpd_starting = threading.Lock()
183
        self._ftpd_starting.acquire() # So it can be released by the server
4725.3.2 by Vincent Ladeuil
Cosmetic change.
184
        self._ftpd_thread = threading.Thread(target=self._run_server,)
3508.1.12 by Vincent Ladeuil
Don't require patched version for pyftpdlib.
185
        self._ftpd_thread.start()
186
        # Wait for the server thread to start (i.e release the lock)
187
        self._ftpd_starting.acquire()
188
        self._ftpd_starting.release()
3508.1.10 by Vincent Ladeuil
Start supporting pyftpdlib as an ftp test server.
189
4934.3.1 by Martin Pool
Rename Server.tearDown to .stop_server
190
    def stop_server(self):
191
        """See bzrlib.transport.Server.stop_server."""
3508.1.12 by Vincent Ladeuil
Don't require patched version for pyftpdlib.
192
        # Tell the server to stop, but also close the server socket for tests
193
        # that start the server but never initiate a connection. Closing the
194
        # 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 :).
195
        self._ftp_server.close()
3508.1.12 by Vincent Ladeuil
Don't require patched version for pyftpdlib.
196
        self._ftpd_running = False
197
        self._ftpd_thread.join()
3508.1.10 by Vincent Ladeuil
Start supporting pyftpdlib as an ftp test server.
198
3508.1.11 by Vincent Ladeuil
Tests passing for python2.5 and python2.6 (and python2.4 :).
199
    def _run_server(self):
4934.3.1 by Martin Pool
Rename Server.tearDown to .stop_server
200
        """Run the server until stop_server is called, shut it down properly then.
3508.1.10 by Vincent Ladeuil
Start supporting pyftpdlib as an ftp test server.
201
        """
3508.1.11 by Vincent Ladeuil
Tests passing for python2.5 and python2.6 (and python2.4 :).
202
        self._ftpd_running = True
3508.1.12 by Vincent Ladeuil
Don't require patched version for pyftpdlib.
203
        self._ftpd_starting.release()
204
        while self._ftpd_running:
4725.3.1 by Vincent Ladeuil
Fix test failure and clean up ftp APPE.
205
            try:
206
                self._ftp_server.serve_forever(timeout=0.1, count=1)
207
            except select.error, e:
208
                if e.args[0] != errno.EBADF:
209
                    raise
3508.1.12 by Vincent Ladeuil
Don't require patched version for pyftpdlib.
210
        self._ftp_server.close_all(ignore_all=True)
3508.1.13 by Vincent Ladeuil
Fix last failing tests under python2.6.
211
212
    def add_user(self, user, password):
213
        """Add a user with write access."""
214
        self._ftp_server.authorizer.add_user(user, password, self._root,
215
                                             perm='elradfmw')