~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/stub_sftp.py

  • Committer: Martin Pool
  • Date: 2005-07-28 11:56:24 UTC
  • Revision ID: mbp@sourcefrog.net-20050728115624-93c11c2b1e399023
- note changes to command line parsing

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
 
        ptrans = paramiko.Transport(self.request)
348
 
        self.paramiko_transport = ptrans
349
 
        # Set it to a channel under 'bzr' so that we get debug info
350
 
        ptrans.set_log_channel('bzr.paramiko.transport')
351
 
        ptrans.add_server_key(tcs.get_host_key())
352
 
        ptrans.set_subsystem_handler('sftp', paramiko.SFTPServer,
353
 
                                     StubSFTPServer, root=tcs._root,
354
 
                                     home=tcs._server_homedir)
355
 
        server = tcs._server_interface(tcs)
356
 
        # This blocks until the key exchange has been done
357
 
        ptrans.start_server(None, server)
358
 
 
359
 
    def finish(self):
360
 
        # Wait for the conversation to finish, when the paramiko.Transport
361
 
        # thread finishes
362
 
        # TODO: Consider timing out after XX seconds rather than hanging.
363
 
        #       Also we could check paramiko_transport.active and possibly
364
 
        #       paramiko_transport.getException().
365
 
        self.paramiko_transport.join()
366
 
 
367
 
    def wrap_for_latency(self):
368
 
        tcs = self.server.test_case_server
369
 
        if tcs.add_latency:
370
 
            # Give the socket (which the request really is) a latency adding
371
 
            # decorator.
372
 
            self.request = SocketDelay(self.request, tcs.add_latency)
373
 
 
374
 
 
375
 
class TestingSFTPWithoutSSHConnectionHandler(TestingSFTPConnectionHandler):
376
 
 
377
 
    def setup(self):
378
 
        self.wrap_for_latency()
379
 
        # Re-import these as locals, so that they're still accessible during
380
 
        # interpreter shutdown (when all module globals get set to None, leading
381
 
        # to confusing errors like "'NoneType' object has no attribute 'error'".
382
 
        class FakeChannel(object):
383
 
            def get_transport(self):
384
 
                return self
385
 
            def get_log_channel(self):
386
 
                return 'bzr.paramiko'
387
 
            def get_name(self):
388
 
                return '1'
389
 
            def get_hexdump(self):
390
 
                return False
391
 
            def close(self):
392
 
                pass
393
 
 
394
 
        tcs = self.server.test_case_server
395
 
        sftp_server = paramiko.SFTPServer(
396
 
            FakeChannel(), 'sftp', StubServer(tcs), StubSFTPServer,
397
 
            root=tcs._root, home=tcs._server_homedir)
398
 
        self.sftp_server = sftp_server
399
 
        sys_stderr = sys.stderr # Used in error reporting during shutdown
400
 
        try:
401
 
            sftp_server.start_subsystem(
402
 
                'sftp', None, ssh.SocketAsChannelAdapter(self.request))
403
 
        except socket.error, e:
404
 
            if (len(e.args) > 0) and (e.args[0] == errno.EPIPE):
405
 
                # it's okay for the client to disconnect abruptly
406
 
                # (bug in paramiko 1.6: it should absorb this exception)
407
 
                pass
408
 
            else:
409
 
                raise
410
 
        except Exception, e:
411
 
            # This typically seems to happen during interpreter shutdown, so
412
 
            # most of the useful ways to report this error won't work.
413
 
            # Writing the exception type, and then the text of the exception,
414
 
            # seems to be the best we can do.
415
 
            # FIXME: All interpreter shutdown errors should have been related
416
 
            # to daemon threads, cleanup needed -- vila 20100623
417
 
            sys_stderr.write('\nEXCEPTION %r: ' % (e.__class__,))
418
 
            sys_stderr.write('%s\n\n' % (e,))
419
 
 
420
 
    def finish(self):
421
 
        self.sftp_server.finish_subsystem()
422
 
 
423
 
 
424
 
class TestingSFTPServer(test_server.TestingThreadingTCPServer):
425
 
 
426
 
    def __init__(self, server_address, request_handler_class, test_case_server):
427
 
        test_server.TestingThreadingTCPServer.__init__(
428
 
            self, server_address, request_handler_class)
429
 
        self.test_case_server = test_case_server
430
 
 
431
 
 
432
 
class SFTPServer(test_server.TestingTCPServerInAThread):
433
 
    """Common code for SFTP server facilities."""
434
 
 
435
 
    def __init__(self, server_interface=StubServer):
436
 
        self.host = '127.0.0.1'
437
 
        self.port = 0
438
 
        super(SFTPServer, self).__init__((self.host, self.port),
439
 
                                         TestingSFTPServer,
440
 
                                         TestingSFTPConnectionHandler)
441
 
        self._original_vendor = None
442
 
        self._vendor = ssh.ParamikoVendor()
443
 
        self._server_interface = server_interface
444
 
        self._host_key = None
445
 
        self.logs = []
446
 
        self.add_latency = 0
447
 
        self._homedir = None
448
 
        self._server_homedir = None
449
 
        self._root = None
450
 
 
451
 
    def _get_sftp_url(self, path):
452
 
        """Calculate an sftp url to this server for path."""
453
 
        return "sftp://foo:bar@%s:%s/%s" % (self.host, self.port, path)
454
 
 
455
 
    def log(self, message):
456
 
        """StubServer uses this to log when a new server is created."""
457
 
        self.logs.append(message)
458
 
 
459
 
    def create_server(self):
460
 
        server = self.server_class((self.host, self.port),
461
 
                                   self.request_handler_class,
462
 
                                   self)
463
 
        return server
464
 
 
465
 
    def get_host_key(self):
466
 
        if self._host_key is None:
467
 
            key_file = osutils.pathjoin(self._homedir, 'test_rsa.key')
468
 
            f = open(key_file, 'w')
469
 
            try:
470
 
                f.write(STUB_SERVER_KEY)
471
 
            finally:
472
 
                f.close()
473
 
            self._host_key = paramiko.RSAKey.from_private_key_file(key_file)
474
 
        return self._host_key
475
 
 
476
 
    def start_server(self, backing_server=None):
477
 
        # XXX: TODO: make sftpserver back onto backing_server rather than local
478
 
        # disk.
479
 
        if not (backing_server is None or
480
 
                isinstance(backing_server, test_server.LocalURLServer)):
481
 
            raise AssertionError(
482
 
                'backing_server should not be %r, because this can only serve '
483
 
                'the local current working directory.' % (backing_server,))
484
 
        self._original_vendor = ssh._ssh_vendor_manager._cached_ssh_vendor
485
 
        ssh._ssh_vendor_manager._cached_ssh_vendor = self._vendor
486
 
        if sys.platform == 'win32':
487
 
            # Win32 needs to use the UNICODE api
488
 
            self._homedir = os.getcwdu()
489
 
            # Normalize the path or it will be wrongly escaped
490
 
            self._homedir = osutils.normpath(self._homedir)
491
 
        else:
492
 
            # But unix SFTP servers should just deal in bytestreams
493
 
            self._homedir = os.getcwd()
494
 
        if self._server_homedir is None:
495
 
            self._server_homedir = self._homedir
496
 
        self._root = '/'
497
 
        if sys.platform == 'win32':
498
 
            self._root = ''
499
 
        super(SFTPServer, self).start_server()
500
 
 
501
 
    def stop_server(self):
502
 
        try:
503
 
            super(SFTPServer, self).stop_server()
504
 
        finally:
505
 
            ssh._ssh_vendor_manager._cached_ssh_vendor = self._original_vendor
506
 
 
507
 
    def get_bogus_url(self):
508
 
        """See bzrlib.transport.Server.get_bogus_url."""
509
 
        # this is chosen to try to prevent trouble with proxies, weird dns, etc
510
 
        # we bind a random socket, so that we get a guaranteed unused port
511
 
        # we just never listen on that port
512
 
        s = socket.socket()
513
 
        s.bind(('localhost', 0))
514
 
        return 'sftp://%s:%s/' % s.getsockname()
515
 
 
516
 
 
517
 
class SFTPFullAbsoluteServer(SFTPServer):
518
 
    """A test server for sftp transports, using absolute urls and ssh."""
519
 
 
520
 
    def get_url(self):
521
 
        """See bzrlib.transport.Server.get_url."""
522
 
        homedir = self._homedir
523
 
        if sys.platform != 'win32':
524
 
            # Remove the initial '/' on all platforms but win32
525
 
            homedir = homedir[1:]
526
 
        return self._get_sftp_url(urlutils.escape(homedir))
527
 
 
528
 
 
529
 
class SFTPServerWithoutSSH(SFTPServer):
530
 
    """An SFTP server that uses a simple TCP socket pair rather than SSH."""
531
 
 
532
 
    def __init__(self):
533
 
        super(SFTPServerWithoutSSH, self).__init__()
534
 
        self._vendor = ssh.LoopbackVendor()
535
 
        self.request_handler_class = TestingSFTPWithoutSSHConnectionHandler
536
 
 
537
 
    def get_host_key():
538
 
        return None
539
 
 
540
 
 
541
 
class SFTPAbsoluteServer(SFTPServerWithoutSSH):
542
 
    """A test server for sftp transports, using absolute urls."""
543
 
 
544
 
    def get_url(self):
545
 
        """See bzrlib.transport.Server.get_url."""
546
 
        homedir = self._homedir
547
 
        if sys.platform != 'win32':
548
 
            # Remove the initial '/' on all platforms but win32
549
 
            homedir = homedir[1:]
550
 
        return self._get_sftp_url(urlutils.escape(homedir))
551
 
 
552
 
 
553
 
class SFTPHomeDirServer(SFTPServerWithoutSSH):
554
 
    """A test server for sftp transports, using homedir relative urls."""
555
 
 
556
 
    def get_url(self):
557
 
        """See bzrlib.transport.Server.get_url."""
558
 
        return self._get_sftp_url("~/")
559
 
 
560
 
 
561
 
class SFTPSiblingAbsoluteServer(SFTPAbsoluteServer):
562
 
    """A test server for sftp transports where only absolute paths will work.
563
 
 
564
 
    It does this by serving from a deeply-nested directory that doesn't exist.
565
 
    """
566
 
 
567
 
    def create_server(self):
568
 
        # FIXME: Can't we do that in a cleaner way ? -- vila 20100623
569
 
        server = super(SFTPSiblingAbsoluteServer, self).create_server()
570
 
        server._server_homedir = '/dev/noone/runs/tests/here'
571
 
        return server
572