~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/stub_sftp.py

(gz) Backslash escape selftest output when printing to non-unicode consoles
 (Martin [gz])

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005 Robey Pointer <robey@lag.net>, Canonical Ltd
 
1
# Copyright (C) 2005, 2006, 2008, 2009, 2010 Robey Pointer <robey@lag.net>, Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
20
20
"""
21
21
 
22
22
import os
23
 
from paramiko import ServerInterface, SFTPServerInterface, SFTPServer, SFTPAttributes, \
24
 
    SFTPHandle, SFTP_OK, AUTH_SUCCESSFUL, OPEN_SUCCEEDED
 
23
import paramiko
 
24
import select
 
25
import socket
 
26
import SocketServer
25
27
import sys
26
 
 
27
 
from bzrlib.osutils import pathjoin
28
 
from bzrlib.trace import mutter
29
 
 
30
 
 
31
 
class StubServer (ServerInterface):
32
 
 
33
 
    def __init__(self, test_case):
34
 
        ServerInterface.__init__(self)
35
 
        self._test_case = test_case
 
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
36
47
 
37
48
    def check_auth_password(self, username, password):
38
49
        # all are allowed
39
 
        self._test_case.log('sftpserver - authorizing: %s' % (username,))
40
 
        return AUTH_SUCCESSFUL
 
50
        self.log('sftpserver - authorizing: %s' % (username,))
 
51
        return paramiko.AUTH_SUCCESSFUL
41
52
 
42
53
    def check_channel_request(self, kind, chanid):
43
 
        self._test_case.log('sftpserver - channel request: %s, %s' % (kind, chanid))
44
 
        return OPEN_SUCCEEDED
45
 
 
46
 
 
47
 
class StubSFTPHandle (SFTPHandle):
 
54
        self.log('sftpserver - channel request: %s, %s' % (kind, chanid))
 
55
        return paramiko.OPEN_SUCCEEDED
 
56
 
 
57
 
 
58
class StubSFTPHandle(paramiko.SFTPHandle):
 
59
 
48
60
    def stat(self):
49
61
        try:
50
 
            return SFTPAttributes.from_stat(os.fstat(self.readfile.fileno()))
 
62
            return paramiko.SFTPAttributes.from_stat(
 
63
                os.fstat(self.readfile.fileno()))
51
64
        except OSError, e:
52
 
            return SFTPServer.convert_errno(e.errno)
 
65
            return paramiko.SFTPServer.convert_errno(e.errno)
53
66
 
54
67
    def chattr(self, attr):
55
68
        # python doesn't have equivalents to fchown or fchmod, so we have to
56
69
        # use the stored filename
57
 
        mutter('Changing permissions on %s to %s', self.filename, attr)
 
70
        trace.mutter('Changing permissions on %s to %s', self.filename, attr)
58
71
        try:
59
 
            SFTPServer.set_file_attr(self.filename, attr)
 
72
            paramiko.SFTPServer.set_file_attr(self.filename, attr)
60
73
        except OSError, e:
61
 
            return SFTPServer.convert_errno(e.errno)
62
 
 
63
 
 
64
 
class StubSFTPServer (SFTPServerInterface):
 
74
            return paramiko.SFTPServer.convert_errno(e.errno)
 
75
 
 
76
 
 
77
class StubSFTPServer(paramiko.SFTPServerInterface):
65
78
 
66
79
    def __init__(self, server, root, home=None):
67
 
        SFTPServerInterface.__init__(self, server)
 
80
        paramiko.SFTPServerInterface.__init__(self, server)
68
81
        # All paths are actually relative to 'root'.
69
82
        # this is like implementing chroot().
70
83
        self.root = root
78
91
            self.home = home[len(self.root):]
79
92
        if self.home.startswith('/'):
80
93
            self.home = self.home[1:]
81
 
        server._test_case.log('sftpserver - new connection')
 
94
        server.log('sftpserver - new connection')
82
95
 
83
96
    def _realpath(self, path):
84
97
        # paths returned from self.canonicalize() always start with
113
126
 
114
127
    def chattr(self, path, attr):
115
128
        try:
116
 
            SFTPServer.set_file_attr(path, attr)
 
129
            paramiko.SFTPServer.set_file_attr(path, attr)
117
130
        except OSError, e:
118
 
            return SFTPServer.convert_errno(e.errno)
119
 
        return SFTP_OK
 
131
            return paramiko.SFTPServer.convert_errno(e.errno)
 
132
        return paramiko.SFTP_OK
120
133
 
121
134
    def list_folder(self, path):
122
135
        path = self._realpath(path)
123
136
        try:
124
137
            out = [ ]
125
138
            # TODO: win32 incorrectly lists paths with non-ascii if path is not
126
 
            # unicode. However on Linux the server should only deal with
 
139
            # unicode. However on unix the server should only deal with
127
140
            # bytestreams and posix.listdir does the right thing
128
141
            if sys.platform == 'win32':
129
142
                flist = [f.encode('utf8') for f in os.listdir(path)]
130
143
            else:
131
144
                flist = os.listdir(path)
132
145
            for fname in flist:
133
 
                attr = SFTPAttributes.from_stat(os.stat(pathjoin(path, fname)))
 
146
                attr = paramiko.SFTPAttributes.from_stat(
 
147
                    os.stat(osutils.pathjoin(path, fname)))
134
148
                attr.filename = fname
135
149
                out.append(attr)
136
150
            return out
137
151
        except OSError, e:
138
 
            return SFTPServer.convert_errno(e.errno)
 
152
            return paramiko.SFTPServer.convert_errno(e.errno)
139
153
 
140
154
    def stat(self, path):
141
155
        path = self._realpath(path)
142
156
        try:
143
 
            return SFTPAttributes.from_stat(os.stat(path))
 
157
            return paramiko.SFTPAttributes.from_stat(os.stat(path))
144
158
        except OSError, e:
145
 
            return SFTPServer.convert_errno(e.errno)
 
159
            return paramiko.SFTPServer.convert_errno(e.errno)
146
160
 
147
161
    def lstat(self, path):
148
162
        path = self._realpath(path)
149
163
        try:
150
 
            return SFTPAttributes.from_stat(os.lstat(path))
 
164
            return paramiko.SFTPAttributes.from_stat(os.lstat(path))
151
165
        except OSError, e:
152
 
            return SFTPServer.convert_errno(e.errno)
 
166
            return paramiko.SFTPServer.convert_errno(e.errno)
153
167
 
154
168
    def open(self, path, flags, attr):
155
169
        path = self._realpath(path)
162
176
                # an odd default mode for files
163
177
                fd = os.open(path, flags, 0666)
164
178
        except OSError, e:
165
 
            return SFTPServer.convert_errno(e.errno)
 
179
            return paramiko.SFTPServer.convert_errno(e.errno)
166
180
 
167
181
        if (flags & os.O_CREAT) and (attr is not None):
168
182
            attr._flags &= ~attr.FLAG_PERMISSIONS
169
 
            SFTPServer.set_file_attr(path, attr)
 
183
            paramiko.SFTPServer.set_file_attr(path, attr)
170
184
        if flags & os.O_WRONLY:
171
185
            fstr = 'wb'
172
186
        elif flags & os.O_RDWR:
177
191
        try:
178
192
            f = os.fdopen(fd, fstr)
179
193
        except (IOError, OSError), e:
180
 
            return SFTPServer.convert_errno(e.errno)
 
194
            return paramiko.SFTPServer.convert_errno(e.errno)
181
195
        fobj = StubSFTPHandle()
182
196
        fobj.filename = path
183
197
        fobj.readfile = f
189
203
        try:
190
204
            os.remove(path)
191
205
        except OSError, e:
192
 
            return SFTPServer.convert_errno(e.errno)
193
 
        return SFTP_OK
 
206
            return paramiko.SFTPServer.convert_errno(e.errno)
 
207
        return paramiko.SFTP_OK
194
208
 
195
209
    def rename(self, oldpath, newpath):
196
210
        oldpath = self._realpath(oldpath)
198
212
        try:
199
213
            os.rename(oldpath, newpath)
200
214
        except OSError, e:
201
 
            return SFTPServer.convert_errno(e.errno)
202
 
        return SFTP_OK
 
215
            return paramiko.SFTPServer.convert_errno(e.errno)
 
216
        return paramiko.SFTP_OK
203
217
 
204
218
    def mkdir(self, path, attr):
205
219
        path = self._realpath(path)
212
226
                os.mkdir(path)
213
227
            if attr is not None:
214
228
                attr._flags &= ~attr.FLAG_PERMISSIONS
215
 
                SFTPServer.set_file_attr(path, attr)
 
229
                paramiko.SFTPServer.set_file_attr(path, attr)
216
230
        except OSError, e:
217
 
            return SFTPServer.convert_errno(e.errno)
218
 
        return SFTP_OK
 
231
            return paramiko.SFTPServer.convert_errno(e.errno)
 
232
        return paramiko.SFTP_OK
219
233
 
220
234
    def rmdir(self, path):
221
235
        path = self._realpath(path)
222
236
        try:
223
237
            os.rmdir(path)
224
238
        except OSError, e:
225
 
            return SFTPServer.convert_errno(e.errno)
226
 
        return SFTP_OK
 
239
            return paramiko.SFTPServer.convert_errno(e.errno)
 
240
        return paramiko.SFTP_OK
227
241
 
228
242
    # removed: chattr, symlink, readlink
229
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