/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: 2020-01-31 17:43:44 UTC
  • mto: This revision was merged to the branch mainline in revision 7478.
  • Revision ID: jelmer@jelmer.uk-20200131174344-qjhgqm7bdkuqj9sj
Default to running Python 3.

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 symlink(self, target_path, path):
 
220
        path = self._realpath(path)
 
221
        try:
 
222
            os.symlink(target_path, path)
 
223
        except OSError as e:
 
224
            return paramiko.SFTPServer.convert_errno(e.errno)
 
225
        return paramiko.SFTP_OK
 
226
 
 
227
    def readlink(self, path):
 
228
        path = self._realpath(path)
 
229
        try:
 
230
            target_path = os.readlink(path)
 
231
        except OSError as e:
 
232
            return paramiko.SFTPServer.convert_errno(e.errno)
 
233
        return target_path
 
234
 
 
235
    def mkdir(self, path, attr):
 
236
        path = self._realpath(path)
 
237
        try:
 
238
            # Using getattr() in case st_mode is None or 0
 
239
            # both evaluate to False
 
240
            if getattr(attr, 'st_mode', None):
 
241
                os.mkdir(path, attr.st_mode)
 
242
            else:
 
243
                os.mkdir(path)
 
244
            if attr is not None:
 
245
                attr._flags &= ~attr.FLAG_PERMISSIONS
 
246
                paramiko.SFTPServer.set_file_attr(path, attr)
 
247
        except OSError as e:
 
248
            return paramiko.SFTPServer.convert_errno(e.errno)
 
249
        return paramiko.SFTP_OK
 
250
 
 
251
    def rmdir(self, path):
 
252
        path = self._realpath(path)
 
253
        try:
 
254
            os.rmdir(path)
 
255
        except OSError as e:
 
256
            return paramiko.SFTPServer.convert_errno(e.errno)
 
257
        return paramiko.SFTP_OK
 
258
 
 
259
    # removed: chattr
 
260
    # (nothing in bzr's sftp transport uses those)
 
261
 
 
262
 
 
263
# ------------- server test implementation --------------
 
264
 
 
265
STUB_SERVER_KEY = """
 
266
-----BEGIN RSA PRIVATE KEY-----
 
267
MIICWgIBAAKBgQDTj1bqB4WmayWNPB+8jVSYpZYk80Ujvj680pOTh2bORBjbIAyz
 
268
oWGW+GUjzKxTiiPvVmxFgx5wdsFvF03v34lEVVhMpouqPAYQ15N37K/ir5XY+9m/
 
269
d8ufMCkjeXsQkKqFbAlQcnWMCRnOoPHS3I4vi6hmnDDeeYTSRvfLbW0fhwIBIwKB
 
270
gBIiOqZYaoqbeD9OS9z2K9KR2atlTxGxOJPXiP4ESqP3NVScWNwyZ3NXHpyrJLa0
 
271
EbVtzsQhLn6rF+TzXnOlcipFvjsem3iYzCpuChfGQ6SovTcOjHV9z+hnpXvQ/fon
 
272
soVRZY65wKnF7IAoUwTmJS9opqgrN6kRgCd3DASAMd1bAkEA96SBVWFt/fJBNJ9H
 
273
tYnBKZGw0VeHOYmVYbvMSstssn8un+pQpUm9vlG/bp7Oxd/m+b9KWEh2xPfv6zqU
 
274
avNwHwJBANqzGZa/EpzF4J8pGti7oIAPUIDGMtfIcmqNXVMckrmzQ2vTfqtkEZsA
 
275
4rE1IERRyiJQx6EJsz21wJmGV9WJQ5kCQQDwkS0uXqVdFzgHO6S++tjmjYcxwr3g
 
276
H0CoFYSgbddOT6miqRskOQF3DZVkJT3kyuBgU2zKygz52ukQZMqxCb1fAkASvuTv
 
277
qfpH87Qq5kQhNKdbbwbmd2NxlNabazPijWuphGTdW0VfJdWfklyS2Kr+iqrs/5wV
 
278
HhathJt636Eg7oIjAkA8ht3MQ+XSl9yIJIS8gVpbPxSw5OMfw0PjVE7tBdQruiSc
 
279
nvuQES5C9BMHjF39LZiGH1iLQy7FgdHyoP+eodI7
 
280
-----END RSA PRIVATE KEY-----
 
281
"""
 
282
 
 
283
 
 
284
class SocketDelay(object):
 
285
    """A socket decorator to make TCP appear slower.
 
286
 
 
287
    This changes recv, send, and sendall to add a fixed latency to each python
 
288
    call if a new roundtrip is detected. That is, when a recv is called and the
 
289
    flag new_roundtrip is set, latency is charged. Every send and send_all
 
290
    sets this flag.
 
291
 
 
292
    In addition every send, sendall and recv sleeps a bit per character send to
 
293
    simulate bandwidth.
 
294
 
 
295
    Not all methods are implemented, this is deliberate as this class is not a
 
296
    replacement for the builtin sockets layer. fileno is not implemented to
 
297
    prevent the proxy being bypassed.
 
298
    """
 
299
 
 
300
    simulated_time = 0
 
301
    _proxied_arguments = dict.fromkeys([
 
302
        "close", "getpeername", "getsockname", "getsockopt", "gettimeout",
 
303
        "setblocking", "setsockopt", "settimeout", "shutdown"])
 
304
 
 
305
    def __init__(self, sock, latency, bandwidth=1.0,
 
306
                 really_sleep=True):
 
307
        """
 
308
        :param bandwith: simulated bandwith (MegaBit)
 
309
        :param really_sleep: If set to false, the SocketDelay will just
 
310
        increase a counter, instead of calling time.sleep. This is useful for
 
311
        unittesting the SocketDelay.
 
312
        """
 
313
        self.sock = sock
 
314
        self.latency = latency
 
315
        self.really_sleep = really_sleep
 
316
        self.time_per_byte = 1 / (bandwidth / 8.0 * 1024 * 1024)
 
317
        self.new_roundtrip = False
 
318
 
 
319
    def sleep(self, s):
 
320
        if self.really_sleep:
 
321
            time.sleep(s)
 
322
        else:
 
323
            SocketDelay.simulated_time += s
 
324
 
 
325
    def __getattr__(self, attr):
 
326
        if attr in SocketDelay._proxied_arguments:
 
327
            return getattr(self.sock, attr)
 
328
        raise AttributeError("'SocketDelay' object has no attribute %r" %
 
329
                             attr)
 
330
 
 
331
    def dup(self):
 
332
        return SocketDelay(self.sock.dup(), self.latency, self.time_per_byte,
 
333
                           self._sleep)
 
334
 
 
335
    def recv(self, *args):
 
336
        data = self.sock.recv(*args)
 
337
        if data and self.new_roundtrip:
 
338
            self.new_roundtrip = False
 
339
            self.sleep(self.latency)
 
340
        self.sleep(len(data) * self.time_per_byte)
 
341
        return data
 
342
 
 
343
    def sendall(self, data, flags=0):
 
344
        if not self.new_roundtrip:
 
345
            self.new_roundtrip = True
 
346
            self.sleep(self.latency)
 
347
        self.sleep(len(data) * self.time_per_byte)
 
348
        return self.sock.sendall(data, flags)
 
349
 
 
350
    def send(self, data, flags=0):
 
351
        if not self.new_roundtrip:
 
352
            self.new_roundtrip = True
 
353
            self.sleep(self.latency)
 
354
        bytes_sent = self.sock.send(data, flags)
 
355
        self.sleep(bytes_sent * self.time_per_byte)
 
356
        return bytes_sent
 
357
 
 
358
 
 
359
class TestingSFTPConnectionHandler(socketserver.BaseRequestHandler):
 
360
 
 
361
    def setup(self):
 
362
        self.wrap_for_latency()
 
363
        tcs = self.server.test_case_server
 
364
        ptrans = paramiko.Transport(self.request)
 
365
        self.paramiko_transport = ptrans
 
366
        # Set it to a channel under 'bzr' so that we get debug info
 
367
        ptrans.set_log_channel('brz.paramiko.transport')
 
368
        ptrans.add_server_key(tcs.get_host_key())
 
369
        ptrans.set_subsystem_handler('sftp', paramiko.SFTPServer,
 
370
                                     StubSFTPServer, root=tcs._root,
 
371
                                     home=tcs._server_homedir)
 
372
        server = tcs._server_interface(tcs)
 
373
        # This blocks until the key exchange has been done
 
374
        ptrans.start_server(None, server)
 
375
 
 
376
    def finish(self):
 
377
        # Wait for the conversation to finish, when the paramiko.Transport
 
378
        # thread finishes
 
379
        # TODO: Consider timing out after XX seconds rather than hanging.
 
380
        #       Also we could check paramiko_transport.active and possibly
 
381
        #       paramiko_transport.getException().
 
382
        self.paramiko_transport.join()
 
383
 
 
384
    def wrap_for_latency(self):
 
385
        tcs = self.server.test_case_server
 
386
        if tcs.add_latency:
 
387
            # Give the socket (which the request really is) a latency adding
 
388
            # decorator.
 
389
            self.request = SocketDelay(self.request, tcs.add_latency)
 
390
 
 
391
 
 
392
class TestingSFTPWithoutSSHConnectionHandler(TestingSFTPConnectionHandler):
 
393
 
 
394
    def setup(self):
 
395
        self.wrap_for_latency()
 
396
        # Re-import these as locals, so that they're still accessible during
 
397
        # interpreter shutdown (when all module globals get set to None, leading
 
398
        # to confusing errors like "'NoneType' object has no attribute 'error'".
 
399
 
 
400
        class FakeChannel(object):
 
401
            def get_transport(self):
 
402
                return self
 
403
 
 
404
            def get_log_channel(self):
 
405
                return 'brz.paramiko'
 
406
 
 
407
            def get_name(self):
 
408
                return '1'
 
409
 
 
410
            def get_hexdump(self):
 
411
                return False
 
412
 
 
413
            def close(self):
 
414
                pass
 
415
 
 
416
        tcs = self.server.test_case_server
 
417
        sftp_server = paramiko.SFTPServer(
 
418
            FakeChannel(), 'sftp', StubServer(tcs), StubSFTPServer,
 
419
            root=tcs._root, home=tcs._server_homedir)
 
420
        self.sftp_server = sftp_server
 
421
        sys_stderr = sys.stderr  # Used in error reporting during shutdown
 
422
        try:
 
423
            sftp_server.start_subsystem(
 
424
                'sftp', None, ssh.SocketAsChannelAdapter(self.request))
 
425
        except socket.error as e:
 
426
            if (len(e.args) > 0) and (e.args[0] == errno.EPIPE):
 
427
                # it's okay for the client to disconnect abruptly
 
428
                # (bug in paramiko 1.6: it should absorb this exception)
 
429
                pass
 
430
            else:
 
431
                raise
 
432
        except Exception as e:
 
433
            # This typically seems to happen during interpreter shutdown, so
 
434
            # most of the useful ways to report this error won't work.
 
435
            # Writing the exception type, and then the text of the exception,
 
436
            # seems to be the best we can do.
 
437
            # FIXME: All interpreter shutdown errors should have been related
 
438
            # to daemon threads, cleanup needed -- vila 20100623
 
439
            sys_stderr.write('\nEXCEPTION %r: ' % (e.__class__,))
 
440
            sys_stderr.write('%s\n\n' % (e,))
 
441
 
 
442
    def finish(self):
 
443
        self.sftp_server.finish_subsystem()
 
444
 
 
445
 
 
446
class TestingSFTPServer(test_server.TestingThreadingTCPServer):
 
447
 
 
448
    def __init__(self, server_address, request_handler_class, test_case_server):
 
449
        test_server.TestingThreadingTCPServer.__init__(
 
450
            self, server_address, request_handler_class)
 
451
        self.test_case_server = test_case_server
 
452
 
 
453
 
 
454
class SFTPServer(test_server.TestingTCPServerInAThread):
 
455
    """Common code for SFTP server facilities."""
 
456
 
 
457
    def __init__(self, server_interface=StubServer):
 
458
        self.host = '127.0.0.1'
 
459
        self.port = 0
 
460
        super(SFTPServer, self).__init__((self.host, self.port),
 
461
                                         TestingSFTPServer,
 
462
                                         TestingSFTPConnectionHandler)
 
463
        self._original_vendor = None
 
464
        self._vendor = ssh.ParamikoVendor()
 
465
        self._server_interface = server_interface
 
466
        self._host_key = None
 
467
        self.logs = []
 
468
        self.add_latency = 0
 
469
        self._homedir = None
 
470
        self._server_homedir = None
 
471
        self._root = None
 
472
 
 
473
    def _get_sftp_url(self, path):
 
474
        """Calculate an sftp url to this server for path."""
 
475
        return "sftp://foo:bar@%s:%s/%s" % (self.host, self.port, path)
 
476
 
 
477
    def log(self, message):
 
478
        """StubServer uses this to log when a new server is created."""
 
479
        self.logs.append(message)
 
480
 
 
481
    def create_server(self):
 
482
        server = self.server_class((self.host, self.port),
 
483
                                   self.request_handler_class,
 
484
                                   self)
 
485
        return server
 
486
 
 
487
    def get_host_key(self):
 
488
        if self._host_key is None:
 
489
            key_file = osutils.pathjoin(self._homedir, 'test_rsa.key')
 
490
            f = open(key_file, 'w')
 
491
            try:
 
492
                f.write(STUB_SERVER_KEY)
 
493
            finally:
 
494
                f.close()
 
495
            self._host_key = paramiko.RSAKey.from_private_key_file(key_file)
 
496
        return self._host_key
 
497
 
 
498
    def start_server(self, backing_server=None):
 
499
        # XXX: TODO: make sftpserver back onto backing_server rather than local
 
500
        # disk.
 
501
        if not (backing_server is None
 
502
                or isinstance(backing_server, test_server.LocalURLServer)):
 
503
            raise AssertionError(
 
504
                'backing_server should not be %r, because this can only serve '
 
505
                'the local current working directory.' % (backing_server,))
 
506
        self._original_vendor = ssh._ssh_vendor_manager._cached_ssh_vendor
 
507
        ssh._ssh_vendor_manager._cached_ssh_vendor = self._vendor
 
508
        self._homedir = osutils.getcwd()
 
509
        if sys.platform == 'win32':
 
510
            # Normalize the path or it will be wrongly escaped
 
511
            self._homedir = osutils.normpath(self._homedir)
 
512
        else:
 
513
            self._homedir = self._homedir
 
514
        if self._server_homedir is None:
 
515
            self._server_homedir = self._homedir
 
516
        self._root = '/'
 
517
        if sys.platform == 'win32':
 
518
            self._root = ''
 
519
        super(SFTPServer, self).start_server()
 
520
 
 
521
    def stop_server(self):
 
522
        try:
 
523
            super(SFTPServer, self).stop_server()
 
524
        finally:
 
525
            ssh._ssh_vendor_manager._cached_ssh_vendor = self._original_vendor
 
526
 
 
527
    def get_bogus_url(self):
 
528
        """See breezy.transport.Server.get_bogus_url."""
 
529
        # this is chosen to try to prevent trouble with proxies, weird dns, etc
 
530
        # we bind a random socket, so that we get a guaranteed unused port
 
531
        # we just never listen on that port
 
532
        s = socket.socket()
 
533
        s.bind(('localhost', 0))
 
534
        return 'sftp://%s:%s/' % s.getsockname()
 
535
 
 
536
 
 
537
class SFTPFullAbsoluteServer(SFTPServer):
 
538
    """A test server for sftp transports, using absolute urls and ssh."""
 
539
 
 
540
    def get_url(self):
 
541
        """See breezy.transport.Server.get_url."""
 
542
        homedir = self._homedir
 
543
        if sys.platform != 'win32':
 
544
            # Remove the initial '/' on all platforms but win32
 
545
            homedir = homedir[1:]
 
546
        return self._get_sftp_url(urlutils.escape(homedir))
 
547
 
 
548
 
 
549
class SFTPServerWithoutSSH(SFTPServer):
 
550
    """An SFTP server that uses a simple TCP socket pair rather than SSH."""
 
551
 
 
552
    def __init__(self):
 
553
        super(SFTPServerWithoutSSH, self).__init__()
 
554
        self._vendor = ssh.LoopbackVendor()
 
555
        self.request_handler_class = TestingSFTPWithoutSSHConnectionHandler
 
556
 
 
557
    def get_host_key():
 
558
        return None
 
559
 
 
560
 
 
561
class SFTPAbsoluteServer(SFTPServerWithoutSSH):
 
562
    """A test server for sftp transports, using absolute urls."""
 
563
 
 
564
    def get_url(self):
 
565
        """See breezy.transport.Server.get_url."""
 
566
        homedir = self._homedir
 
567
        if sys.platform != 'win32':
 
568
            # Remove the initial '/' on all platforms but win32
 
569
            homedir = homedir[1:]
 
570
        return self._get_sftp_url(urlutils.escape(homedir))
 
571
 
 
572
 
 
573
class SFTPHomeDirServer(SFTPServerWithoutSSH):
 
574
    """A test server for sftp transports, using homedir relative urls."""
 
575
 
 
576
    def get_url(self):
 
577
        """See breezy.transport.Server.get_url."""
 
578
        return self._get_sftp_url("%7E/")
 
579
 
 
580
 
 
581
class SFTPSiblingAbsoluteServer(SFTPAbsoluteServer):
 
582
    """A test server for sftp transports where only absolute paths will work.
 
583
 
 
584
    It does this by serving from a deeply-nested directory that doesn't exist.
 
585
    """
 
586
 
 
587
    def create_server(self):
 
588
        # FIXME: Can't we do that in a cleaner way ? -- vila 20100623
 
589
        server = super(SFTPSiblingAbsoluteServer, self).create_server()
 
590
        server._server_homedir = '/dev/noone/runs/tests/here'
 
591
        return server