/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to breezy/tests/stub_sftp.py

  • Committer: Jelmer Vernooij
  • Date: 2017-08-07 11:49:46 UTC
  • mto: (6747.3.4 avoid-set-revid-3)
  • mto: This revision was merged to the branch mainline in revision 6750.
  • Revision ID: jelmer@jelmer.uk-20170807114946-luclmxuawyzhpiot
Avoid setting revision_ids.

Show diffs side-by-side

added added

removed removed

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