~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/stub_sftp.py

  • Committer: Aaron Bentley
  • Date: 2007-06-11 14:59:52 UTC
  • mto: (2520.5.2 bzr.mpbundle)
  • mto: This revision was merged to the branch mainline in revision 2631.
  • Revision ID: abentley@panoramicfeedback.com-20070611145952-cwt4480gphdhen6l
Get installation started

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