~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/stub_sftp.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2010-08-30 22:49:20 UTC
  • mfrom: (5397.1.6 jam-integration)
  • Revision ID: pqm@pqm.ubuntu.com-20100830224920-w9zw1vhsd5oiyljv
(vila, jam) Get PQM running correctly again (bug #626667),
        skip test_bzr_connect_to_bzr_ssh (bug #626876)

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2005, 2006, 2008, 2009, 2010 Robey Pointer <robey@lag.net>, Canonical Ltd
 
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
 
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 
16
 
 
17
"""
 
18
A stub SFTP server for loopback SFTP testing.
 
19
Adapted from the one in paramiko's unit tests.
 
20
"""
 
21
 
 
22
import os
 
23
import paramiko
 
24
import select
 
25
import socket
 
26
import SocketServer
 
27
import sys
 
28
import threading
 
29
import time
 
30
 
 
31
from bzrlib import (
 
32
    osutils,
 
33
    trace,
 
34
    urlutils,
 
35
    )
 
36
from bzrlib.transport import (
 
37
    ssh,
 
38
    )
 
39
from bzrlib.tests import test_server
 
40
 
 
41
 
 
42
class StubServer(paramiko.ServerInterface):
 
43
 
 
44
    def __init__(self, test_case_server):
 
45
        paramiko.ServerInterface.__init__(self)
 
46
        self.log = test_case_server.log
 
47
 
 
48
    def check_auth_password(self, username, password):
 
49
        # all are allowed
 
50
        self.log('sftpserver - authorizing: %s' % (username,))
 
51
        return paramiko.AUTH_SUCCESSFUL
 
52
 
 
53
    def check_channel_request(self, kind, chanid):
 
54
        self.log('sftpserver - channel request: %s, %s' % (kind, chanid))
 
55
        return paramiko.OPEN_SUCCEEDED
 
56
 
 
57
 
 
58
class StubSFTPHandle(paramiko.SFTPHandle):
 
59
 
 
60
    def stat(self):
 
61
        try:
 
62
            return paramiko.SFTPAttributes.from_stat(
 
63
                os.fstat(self.readfile.fileno()))
 
64
        except OSError, e:
 
65
            return paramiko.SFTPServer.convert_errno(e.errno)
 
66
 
 
67
    def chattr(self, attr):
 
68
        # python doesn't have equivalents to fchown or fchmod, so we have to
 
69
        # use the stored filename
 
70
        trace.mutter('Changing permissions on %s to %s', self.filename, attr)
 
71
        try:
 
72
            paramiko.SFTPServer.set_file_attr(self.filename, attr)
 
73
        except OSError, e:
 
74
            return paramiko.SFTPServer.convert_errno(e.errno)
 
75
 
 
76
 
 
77
class StubSFTPServer(paramiko.SFTPServerInterface):
 
78
 
 
79
    def __init__(self, server, root, home=None):
 
80
        paramiko.SFTPServerInterface.__init__(self, server)
 
81
        # All paths are actually relative to 'root'.
 
82
        # this is like implementing chroot().
 
83
        self.root = root
 
84
        if home is None:
 
85
            self.home = ''
 
86
        else:
 
87
            if not home.startswith(self.root):
 
88
                raise AssertionError(
 
89
                    "home must be a subdirectory of root (%s vs %s)"
 
90
                    % (home, root))
 
91
            self.home = home[len(self.root):]
 
92
        if self.home.startswith('/'):
 
93
            self.home = self.home[1:]
 
94
        server.log('sftpserver - new connection')
 
95
 
 
96
    def _realpath(self, path):
 
97
        # paths returned from self.canonicalize() always start with
 
98
        # a path separator. So if 'root' is just '/', this would cause
 
99
        # a double slash at the beginning '//home/dir'.
 
100
        if self.root == '/':
 
101
            return self.canonicalize(path)
 
102
        return self.root + self.canonicalize(path)
 
103
 
 
104
    if sys.platform == 'win32':
 
105
        def canonicalize(self, path):
 
106
            # Win32 sftp paths end up looking like
 
107
            #     sftp://host@foo/h:/foo/bar
 
108
            # which means absolute paths look like:
 
109
            #     /h:/foo/bar
 
110
            # and relative paths stay the same:
 
111
            #     foo/bar
 
112
            # win32 needs to use the Unicode APIs. so we require the
 
113
            # paths to be utf8 (Linux just uses bytestreams)
 
114
            thispath = path.decode('utf8')
 
115
            if path.startswith('/'):
 
116
                # Abspath H:/foo/bar
 
117
                return os.path.normpath(thispath[1:])
 
118
            else:
 
119
                return os.path.normpath(os.path.join(self.home, thispath))
 
120
    else:
 
121
        def canonicalize(self, path):
 
122
            if os.path.isabs(path):
 
123
                return os.path.normpath(path)
 
124
            else:
 
125
                return os.path.normpath('/' + os.path.join(self.home, path))
 
126
 
 
127
    def chattr(self, path, attr):
 
128
        try:
 
129
            paramiko.SFTPServer.set_file_attr(path, attr)
 
130
        except OSError, e:
 
131
            return paramiko.SFTPServer.convert_errno(e.errno)
 
132
        return paramiko.SFTP_OK
 
133
 
 
134
    def list_folder(self, path):
 
135
        path = self._realpath(path)
 
136
        try:
 
137
            out = [ ]
 
138
            # TODO: win32 incorrectly lists paths with non-ascii if path is not
 
139
            # unicode. However on unix the server should only deal with
 
140
            # bytestreams and posix.listdir does the right thing
 
141
            if sys.platform == 'win32':
 
142
                flist = [f.encode('utf8') for f in os.listdir(path)]
 
143
            else:
 
144
                flist = os.listdir(path)
 
145
            for fname in flist:
 
146
                attr = paramiko.SFTPAttributes.from_stat(
 
147
                    os.stat(osutils.pathjoin(path, fname)))
 
148
                attr.filename = fname
 
149
                out.append(attr)
 
150
            return out
 
151
        except OSError, e:
 
152
            return paramiko.SFTPServer.convert_errno(e.errno)
 
153
 
 
154
    def stat(self, path):
 
155
        path = self._realpath(path)
 
156
        try:
 
157
            return paramiko.SFTPAttributes.from_stat(os.stat(path))
 
158
        except OSError, e:
 
159
            return paramiko.SFTPServer.convert_errno(e.errno)
 
160
 
 
161
    def lstat(self, path):
 
162
        path = self._realpath(path)
 
163
        try:
 
164
            return paramiko.SFTPAttributes.from_stat(os.lstat(path))
 
165
        except OSError, e:
 
166
            return paramiko.SFTPServer.convert_errno(e.errno)
 
167
 
 
168
    def open(self, path, flags, attr):
 
169
        path = self._realpath(path)
 
170
        try:
 
171
            flags |= getattr(os, 'O_BINARY', 0)
 
172
            if getattr(attr, 'st_mode', None):
 
173
                fd = os.open(path, flags, attr.st_mode)
 
174
            else:
 
175
                # os.open() defaults to 0777 which is
 
176
                # an odd default mode for files
 
177
                fd = os.open(path, flags, 0666)
 
178
        except OSError, e:
 
179
            return paramiko.SFTPServer.convert_errno(e.errno)
 
180
 
 
181
        if (flags & os.O_CREAT) and (attr is not None):
 
182
            attr._flags &= ~attr.FLAG_PERMISSIONS
 
183
            paramiko.SFTPServer.set_file_attr(path, attr)
 
184
        if flags & os.O_WRONLY:
 
185
            fstr = 'wb'
 
186
        elif flags & os.O_RDWR:
 
187
            fstr = 'rb+'
 
188
        else:
 
189
            # O_RDONLY (== 0)
 
190
            fstr = 'rb'
 
191
        try:
 
192
            f = os.fdopen(fd, fstr)
 
193
        except (IOError, OSError), e:
 
194
            return paramiko.SFTPServer.convert_errno(e.errno)
 
195
        fobj = StubSFTPHandle()
 
196
        fobj.filename = path
 
197
        fobj.readfile = f
 
198
        fobj.writefile = f
 
199
        return fobj
 
200
 
 
201
    def remove(self, path):
 
202
        path = self._realpath(path)
 
203
        try:
 
204
            os.remove(path)
 
205
        except OSError, e:
 
206
            return paramiko.SFTPServer.convert_errno(e.errno)
 
207
        return paramiko.SFTP_OK
 
208
 
 
209
    def rename(self, oldpath, newpath):
 
210
        oldpath = self._realpath(oldpath)
 
211
        newpath = self._realpath(newpath)
 
212
        try:
 
213
            os.rename(oldpath, newpath)
 
214
        except OSError, e:
 
215
            return paramiko.SFTPServer.convert_errno(e.errno)
 
216
        return paramiko.SFTP_OK
 
217
 
 
218
    def mkdir(self, path, attr):
 
219
        path = self._realpath(path)
 
220
        try:
 
221
            # Using getattr() in case st_mode is None or 0
 
222
            # both evaluate to False
 
223
            if getattr(attr, 'st_mode', None):
 
224
                os.mkdir(path, attr.st_mode)
 
225
            else:
 
226
                os.mkdir(path)
 
227
            if attr is not None:
 
228
                attr._flags &= ~attr.FLAG_PERMISSIONS
 
229
                paramiko.SFTPServer.set_file_attr(path, attr)
 
230
        except OSError, e:
 
231
            return paramiko.SFTPServer.convert_errno(e.errno)
 
232
        return paramiko.SFTP_OK
 
233
 
 
234
    def rmdir(self, path):
 
235
        path = self._realpath(path)
 
236
        try:
 
237
            os.rmdir(path)
 
238
        except OSError, e:
 
239
            return paramiko.SFTPServer.convert_errno(e.errno)
 
240
        return paramiko.SFTP_OK
 
241
 
 
242
    # removed: chattr, symlink, readlink
 
243
    # (nothing in bzr's sftp transport uses those)
 
244
 
 
245
 
 
246
# ------------- server test implementation --------------
 
247
 
 
248
STUB_SERVER_KEY = """
 
249
-----BEGIN RSA PRIVATE KEY-----
 
250
MIICWgIBAAKBgQDTj1bqB4WmayWNPB+8jVSYpZYk80Ujvj680pOTh2bORBjbIAyz
 
251
oWGW+GUjzKxTiiPvVmxFgx5wdsFvF03v34lEVVhMpouqPAYQ15N37K/ir5XY+9m/
 
252
d8ufMCkjeXsQkKqFbAlQcnWMCRnOoPHS3I4vi6hmnDDeeYTSRvfLbW0fhwIBIwKB
 
253
gBIiOqZYaoqbeD9OS9z2K9KR2atlTxGxOJPXiP4ESqP3NVScWNwyZ3NXHpyrJLa0
 
254
EbVtzsQhLn6rF+TzXnOlcipFvjsem3iYzCpuChfGQ6SovTcOjHV9z+hnpXvQ/fon
 
255
soVRZY65wKnF7IAoUwTmJS9opqgrN6kRgCd3DASAMd1bAkEA96SBVWFt/fJBNJ9H
 
256
tYnBKZGw0VeHOYmVYbvMSstssn8un+pQpUm9vlG/bp7Oxd/m+b9KWEh2xPfv6zqU
 
257
avNwHwJBANqzGZa/EpzF4J8pGti7oIAPUIDGMtfIcmqNXVMckrmzQ2vTfqtkEZsA
 
258
4rE1IERRyiJQx6EJsz21wJmGV9WJQ5kCQQDwkS0uXqVdFzgHO6S++tjmjYcxwr3g
 
259
H0CoFYSgbddOT6miqRskOQF3DZVkJT3kyuBgU2zKygz52ukQZMqxCb1fAkASvuTv
 
260
qfpH87Qq5kQhNKdbbwbmd2NxlNabazPijWuphGTdW0VfJdWfklyS2Kr+iqrs/5wV
 
261
HhathJt636Eg7oIjAkA8ht3MQ+XSl9yIJIS8gVpbPxSw5OMfw0PjVE7tBdQruiSc
 
262
nvuQES5C9BMHjF39LZiGH1iLQy7FgdHyoP+eodI7
 
263
-----END RSA PRIVATE KEY-----
 
264
"""
 
265
 
 
266
 
 
267
class SocketDelay(object):
 
268
    """A socket decorator to make TCP appear slower.
 
269
 
 
270
    This changes recv, send, and sendall to add a fixed latency to each python
 
271
    call if a new roundtrip is detected. That is, when a recv is called and the
 
272
    flag new_roundtrip is set, latency is charged. Every send and send_all
 
273
    sets this flag.
 
274
 
 
275
    In addition every send, sendall and recv sleeps a bit per character send to
 
276
    simulate bandwidth.
 
277
 
 
278
    Not all methods are implemented, this is deliberate as this class is not a
 
279
    replacement for the builtin sockets layer. fileno is not implemented to
 
280
    prevent the proxy being bypassed.
 
281
    """
 
282
 
 
283
    simulated_time = 0
 
284
    _proxied_arguments = dict.fromkeys([
 
285
        "close", "getpeername", "getsockname", "getsockopt", "gettimeout",
 
286
        "setblocking", "setsockopt", "settimeout", "shutdown"])
 
287
 
 
288
    def __init__(self, sock, latency, bandwidth=1.0,
 
289
                 really_sleep=True):
 
290
        """
 
291
        :param bandwith: simulated bandwith (MegaBit)
 
292
        :param really_sleep: If set to false, the SocketDelay will just
 
293
        increase a counter, instead of calling time.sleep. This is useful for
 
294
        unittesting the SocketDelay.
 
295
        """
 
296
        self.sock = sock
 
297
        self.latency = latency
 
298
        self.really_sleep = really_sleep
 
299
        self.time_per_byte = 1 / (bandwidth / 8.0 * 1024 * 1024)
 
300
        self.new_roundtrip = False
 
301
 
 
302
    def sleep(self, s):
 
303
        if self.really_sleep:
 
304
            time.sleep(s)
 
305
        else:
 
306
            SocketDelay.simulated_time += s
 
307
 
 
308
    def __getattr__(self, attr):
 
309
        if attr in SocketDelay._proxied_arguments:
 
310
            return getattr(self.sock, attr)
 
311
        raise AttributeError("'SocketDelay' object has no attribute %r" %
 
312
                             attr)
 
313
 
 
314
    def dup(self):
 
315
        return SocketDelay(self.sock.dup(), self.latency, self.time_per_byte,
 
316
                           self._sleep)
 
317
 
 
318
    def recv(self, *args):
 
319
        data = self.sock.recv(*args)
 
320
        if data and self.new_roundtrip:
 
321
            self.new_roundtrip = False
 
322
            self.sleep(self.latency)
 
323
        self.sleep(len(data) * self.time_per_byte)
 
324
        return data
 
325
 
 
326
    def sendall(self, data, flags=0):
 
327
        if not self.new_roundtrip:
 
328
            self.new_roundtrip = True
 
329
            self.sleep(self.latency)
 
330
        self.sleep(len(data) * self.time_per_byte)
 
331
        return self.sock.sendall(data, flags)
 
332
 
 
333
    def send(self, data, flags=0):
 
334
        if not self.new_roundtrip:
 
335
            self.new_roundtrip = True
 
336
            self.sleep(self.latency)
 
337
        bytes_sent = self.sock.send(data, flags)
 
338
        self.sleep(bytes_sent * self.time_per_byte)
 
339
        return bytes_sent
 
340
 
 
341
 
 
342
class TestingSFTPConnectionHandler(SocketServer.BaseRequestHandler):
 
343
 
 
344
    def setup(self):
 
345
        self.wrap_for_latency()
 
346
        tcs = self.server.test_case_server
 
347
        ssh_server = paramiko.Transport(self.request)
 
348
        ssh_server.add_server_key(tcs.get_host_key())
 
349
        ssh_server.set_subsystem_handler('sftp', paramiko.SFTPServer,
 
350
                                         StubSFTPServer, root=tcs._root,
 
351
                                         home=tcs._server_homedir)
 
352
        server = tcs._server_interface(tcs)
 
353
        ssh_server.start_server(None, server)
 
354
        # FIXME: Long story short:
 
355
        # bt.test_transport.TestSSHConnections.test_bzr_connect_to_bzr_ssh
 
356
        # fails if we wait less than 0.2 seconds... paramiko uses a lot of
 
357
        # timeouts internally which probably mask a synchronisation
 
358
        # problem. Note that this is the only test that requires this hack and
 
359
        # the test may need to be fixed instead, but it's late and the test is
 
360
        # horrible as mentioned in its comments :) -- vila 20100623
 
361
        import time
 
362
        time.sleep(0.2)
 
363
 
 
364
    def wrap_for_latency(self):
 
365
        tcs = self.server.test_case_server
 
366
        if tcs.add_latency:
 
367
            # Give the socket (which the request really is) a latency adding
 
368
            # decorator.
 
369
            self.request = SocketDelay(self.request, tcs.add_latency)
 
370
 
 
371
 
 
372
class TestingSFTPWithoutSSHConnectionHandler(TestingSFTPConnectionHandler):
 
373
 
 
374
    def setup(self):
 
375
        self.wrap_for_latency()
 
376
        # Re-import these as locals, so that they're still accessible during
 
377
        # interpreter shutdown (when all module globals get set to None, leading
 
378
        # to confusing errors like "'NoneType' object has no attribute 'error'".
 
379
        class FakeChannel(object):
 
380
            def get_transport(self):
 
381
                return self
 
382
            def get_log_channel(self):
 
383
                return 'paramiko'
 
384
            def get_name(self):
 
385
                return '1'
 
386
            def get_hexdump(self):
 
387
                return False
 
388
            def close(self):
 
389
                pass
 
390
 
 
391
        tcs = self.server.test_case_server
 
392
        server = paramiko.SFTPServer(
 
393
            FakeChannel(), 'sftp', StubServer(tcs), StubSFTPServer,
 
394
            root=tcs._root, home=tcs._server_homedir)
 
395
        try:
 
396
            server.start_subsystem(
 
397
                'sftp', None, ssh.SocketAsChannelAdapter(self.request))
 
398
        except socket.error, e:
 
399
            if (len(e.args) > 0) and (e.args[0] == errno.EPIPE):
 
400
                # it's okay for the client to disconnect abruptly
 
401
                # (bug in paramiko 1.6: it should absorb this exception)
 
402
                pass
 
403
            else:
 
404
                raise
 
405
        except Exception, e:
 
406
            # This typically seems to happen during interpreter shutdown, so
 
407
            # most of the useful ways to report this error won't work.
 
408
            # Writing the exception type, and then the text of the exception,
 
409
            # seems to be the best we can do.
 
410
            # FIXME: All interpreter shutdown errors should have been related
 
411
            # to daemon threads, cleanup needed -- vila 20100623
 
412
            import sys
 
413
            sys.stderr.write('\nEXCEPTION %r: ' % (e.__class__,))
 
414
            sys.stderr.write('%s\n\n' % (e,))
 
415
        server.finish_subsystem()
 
416
 
 
417
 
 
418
class TestingSFTPServer(test_server.TestingThreadingTCPServer):
 
419
 
 
420
    def __init__(self, server_address, request_handler_class, test_case_server):
 
421
        test_server.TestingThreadingTCPServer.__init__(
 
422
            self, server_address, request_handler_class)
 
423
        self.test_case_server = test_case_server
 
424
 
 
425
 
 
426
class SFTPServer(test_server.TestingTCPServerInAThread):
 
427
    """Common code for SFTP server facilities."""
 
428
 
 
429
    def __init__(self, server_interface=StubServer):
 
430
        self.host = '127.0.0.1'
 
431
        self.port = 0
 
432
        super(SFTPServer, self).__init__((self.host, self.port),
 
433
                                         TestingSFTPServer,
 
434
                                         TestingSFTPConnectionHandler)
 
435
        self._original_vendor = None
 
436
        self._vendor = ssh.ParamikoVendor()
 
437
        self._server_interface = server_interface
 
438
        self._host_key = None
 
439
        self.logs = []
 
440
        self.add_latency = 0
 
441
        self._homedir = None
 
442
        self._server_homedir = None
 
443
        self._root = None
 
444
 
 
445
    def _get_sftp_url(self, path):
 
446
        """Calculate an sftp url to this server for path."""
 
447
        return "sftp://foo:bar@%s:%s/%s" % (self.host, self.port, path)
 
448
 
 
449
    def log(self, message):
 
450
        """StubServer uses this to log when a new server is created."""
 
451
        self.logs.append(message)
 
452
 
 
453
    def create_server(self):
 
454
        server = self.server_class((self.host, self.port),
 
455
                                   self.request_handler_class,
 
456
                                   self)
 
457
        return server
 
458
 
 
459
    def get_host_key(self):
 
460
        if self._host_key is None:
 
461
            key_file = osutils.pathjoin(self._homedir, 'test_rsa.key')
 
462
            f = open(key_file, 'w')
 
463
            try:
 
464
                f.write(STUB_SERVER_KEY)
 
465
            finally:
 
466
                f.close()
 
467
            self._host_key = paramiko.RSAKey.from_private_key_file(key_file)
 
468
        return self._host_key
 
469
 
 
470
    def start_server(self, backing_server=None):
 
471
        # XXX: TODO: make sftpserver back onto backing_server rather than local
 
472
        # disk.
 
473
        if not (backing_server is None or
 
474
                isinstance(backing_server, test_server.LocalURLServer)):
 
475
            raise AssertionError(
 
476
                'backing_server should not be %r, because this can only serve '
 
477
                'the local current working directory.' % (backing_server,))
 
478
        self._original_vendor = ssh._ssh_vendor_manager._cached_ssh_vendor
 
479
        ssh._ssh_vendor_manager._cached_ssh_vendor = self._vendor
 
480
        if sys.platform == 'win32':
 
481
            # Win32 needs to use the UNICODE api
 
482
            self._homedir = os.getcwdu()
 
483
            # Normalize the path or it will be wrongly escaped
 
484
            self._homedir = osutils.normpath(self._homedir)
 
485
        else:
 
486
            # But unix SFTP servers should just deal in bytestreams
 
487
            self._homedir = os.getcwd()
 
488
        if self._server_homedir is None:
 
489
            self._server_homedir = self._homedir
 
490
        self._root = '/'
 
491
        if sys.platform == 'win32':
 
492
            self._root = ''
 
493
        super(SFTPServer, self).start_server()
 
494
 
 
495
    def stop_server(self):
 
496
        try:
 
497
            super(SFTPServer, self).stop_server()
 
498
        finally:
 
499
            ssh._ssh_vendor_manager._cached_ssh_vendor = self._original_vendor
 
500
 
 
501
    def get_bogus_url(self):
 
502
        """See bzrlib.transport.Server.get_bogus_url."""
 
503
        # this is chosen to try to prevent trouble with proxies, weird dns, etc
 
504
        # we bind a random socket, so that we get a guaranteed unused port
 
505
        # we just never listen on that port
 
506
        s = socket.socket()
 
507
        s.bind(('localhost', 0))
 
508
        return 'sftp://%s:%s/' % s.getsockname()
 
509
 
 
510
 
 
511
class SFTPFullAbsoluteServer(SFTPServer):
 
512
    """A test server for sftp transports, using absolute urls and ssh."""
 
513
 
 
514
    def get_url(self):
 
515
        """See bzrlib.transport.Server.get_url."""
 
516
        homedir = self._homedir
 
517
        if sys.platform != 'win32':
 
518
            # Remove the initial '/' on all platforms but win32
 
519
            homedir = homedir[1:]
 
520
        return self._get_sftp_url(urlutils.escape(homedir))
 
521
 
 
522
 
 
523
class SFTPServerWithoutSSH(SFTPServer):
 
524
    """An SFTP server that uses a simple TCP socket pair rather than SSH."""
 
525
 
 
526
    def __init__(self):
 
527
        super(SFTPServerWithoutSSH, self).__init__()
 
528
        self._vendor = ssh.LoopbackVendor()
 
529
        self.request_handler_class = TestingSFTPWithoutSSHConnectionHandler
 
530
 
 
531
    def get_host_key():
 
532
        return None
 
533
 
 
534
 
 
535
class SFTPAbsoluteServer(SFTPServerWithoutSSH):
 
536
    """A test server for sftp transports, using absolute urls."""
 
537
 
 
538
    def get_url(self):
 
539
        """See bzrlib.transport.Server.get_url."""
 
540
        homedir = self._homedir
 
541
        if sys.platform != 'win32':
 
542
            # Remove the initial '/' on all platforms but win32
 
543
            homedir = homedir[1:]
 
544
        return self._get_sftp_url(urlutils.escape(homedir))
 
545
 
 
546
 
 
547
class SFTPHomeDirServer(SFTPServerWithoutSSH):
 
548
    """A test server for sftp transports, using homedir relative urls."""
 
549
 
 
550
    def get_url(self):
 
551
        """See bzrlib.transport.Server.get_url."""
 
552
        return self._get_sftp_url("~/")
 
553
 
 
554
 
 
555
class SFTPSiblingAbsoluteServer(SFTPAbsoluteServer):
 
556
    """A test server for sftp transports where only absolute paths will work.
 
557
 
 
558
    It does this by serving from a deeply-nested directory that doesn't exist.
 
559
    """
 
560
 
 
561
    def create_server(self):
 
562
        # FIXME: Can't we do that in a cleaner way ? -- vila 20100623
 
563
        server = super(SFTPSiblingAbsoluteServer, self).create_server()
 
564
        server._server_homedir = '/dev/noone/runs/tests/here'
 
565
        return server
 
566