~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/stub_sftp.py

  • Committer: Patch Queue Manager
  • Date: 2016-04-21 04:10:52 UTC
  • mfrom: (6616.1.1 fix-en-user-guide)
  • Revision ID: pqm@pqm.ubuntu.com-20160421041052-clcye7ns1qcl2n7w
(richard-wilbur) Ensure build of English use guide always uses English text
 even when user's locale specifies a different language. (Jelmer Vernooij)

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-2011 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
16
 
17
17
"""
18
18
A stub SFTP server for loopback SFTP testing.
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 socket
 
25
import SocketServer
25
26
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
 
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
36
45
 
37
46
    def check_auth_password(self, username, password):
38
47
        # all are allowed
39
 
        self._test_case.log('sftpserver - authorizing: %s' % (username,))
40
 
        return AUTH_SUCCESSFUL
 
48
        self.log('sftpserver - authorizing: %s' % (username,))
 
49
        return paramiko.AUTH_SUCCESSFUL
41
50
 
42
51
    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):
 
52
        self.log('sftpserver - channel request: %s, %s' % (kind, chanid))
 
53
        return paramiko.OPEN_SUCCEEDED
 
54
 
 
55
 
 
56
class StubSFTPHandle(paramiko.SFTPHandle):
 
57
 
48
58
    def stat(self):
49
59
        try:
50
 
            return SFTPAttributes.from_stat(os.fstat(self.readfile.fileno()))
 
60
            return paramiko.SFTPAttributes.from_stat(
 
61
                os.fstat(self.readfile.fileno()))
51
62
        except OSError, e:
52
 
            return SFTPServer.convert_errno(e.errno)
 
63
            return paramiko.SFTPServer.convert_errno(e.errno)
53
64
 
54
65
    def chattr(self, attr):
55
66
        # python doesn't have equivalents to fchown or fchmod, so we have to
56
67
        # use the stored filename
57
 
        mutter('Changing permissions on %s to %s', self.filename, attr)
 
68
        trace.mutter('Changing permissions on %s to %s', self.filename, attr)
58
69
        try:
59
 
            SFTPServer.set_file_attr(self.filename, attr)
 
70
            paramiko.SFTPServer.set_file_attr(self.filename, attr)
60
71
        except OSError, e:
61
 
            return SFTPServer.convert_errno(e.errno)
62
 
 
63
 
 
64
 
class StubSFTPServer (SFTPServerInterface):
 
72
            return paramiko.SFTPServer.convert_errno(e.errno)
 
73
 
 
74
 
 
75
class StubSFTPServer(paramiko.SFTPServerInterface):
65
76
 
66
77
    def __init__(self, server, root, home=None):
67
 
        SFTPServerInterface.__init__(self, server)
 
78
        paramiko.SFTPServerInterface.__init__(self, server)
68
79
        # All paths are actually relative to 'root'.
69
80
        # this is like implementing chroot().
70
81
        self.root = root
71
82
        if home is None:
72
83
            self.home = ''
73
84
        else:
74
 
            assert home.startswith(self.root), \
75
 
                    "home must be a subdirectory of root (%s vs %s)" \
76
 
                    % (home, root)
 
85
            if not home.startswith(self.root):
 
86
                raise AssertionError(
 
87
                    "home must be a subdirectory of root (%s vs %s)"
 
88
                    % (home, root))
77
89
            self.home = home[len(self.root):]
78
90
        if self.home.startswith('/'):
79
91
            self.home = self.home[1:]
80
 
        server._test_case.log('sftpserver - new connection')
 
92
        server.log('sftpserver - new connection')
81
93
 
82
94
    def _realpath(self, path):
83
95
        # paths returned from self.canonicalize() always start with
84
96
        # a path separator. So if 'root' is just '/', this would cause
85
 
        # a double slash at the beginning '//home/dir'. 
 
97
        # a double slash at the beginning '//home/dir'.
86
98
        if self.root == '/':
87
99
            return self.canonicalize(path)
88
100
        return self.root + self.canonicalize(path)
95
107
            #     /h:/foo/bar
96
108
            # and relative paths stay the same:
97
109
            #     foo/bar
98
 
            # win32 needs to use the Unicode APIs. so we require the 
 
110
            # win32 needs to use the Unicode APIs. so we require the
99
111
            # paths to be utf8 (Linux just uses bytestreams)
100
112
            thispath = path.decode('utf8')
101
113
            if path.startswith('/'):
106
118
    else:
107
119
        def canonicalize(self, path):
108
120
            if os.path.isabs(path):
109
 
                return os.path.normpath(path)
 
121
                return osutils.normpath(path)
110
122
            else:
111
 
                return os.path.normpath('/' + os.path.join(self.home, path))
 
123
                return osutils.normpath('/' + os.path.join(self.home, path))
112
124
 
113
125
    def chattr(self, path, attr):
114
126
        try:
115
 
            SFTPServer.set_file_attr(path, attr)
 
127
            paramiko.SFTPServer.set_file_attr(path, attr)
116
128
        except OSError, e:
117
 
            return SFTPServer.convert_errno(e.errno)
118
 
        return SFTP_OK
 
129
            return paramiko.SFTPServer.convert_errno(e.errno)
 
130
        return paramiko.SFTP_OK
119
131
 
120
132
    def list_folder(self, path):
121
133
        path = self._realpath(path)
122
134
        try:
123
135
            out = [ ]
124
136
            # TODO: win32 incorrectly lists paths with non-ascii if path is not
125
 
            # unicode. However on Linux the server should only deal with
126
 
            # bytestreams and posix.listdir does the right thing 
 
137
            # unicode. However on unix the server should only deal with
 
138
            # bytestreams and posix.listdir does the right thing
127
139
            if sys.platform == 'win32':
128
140
                flist = [f.encode('utf8') for f in os.listdir(path)]
129
141
            else:
130
142
                flist = os.listdir(path)
131
143
            for fname in flist:
132
 
                attr = SFTPAttributes.from_stat(os.stat(pathjoin(path, fname)))
 
144
                attr = paramiko.SFTPAttributes.from_stat(
 
145
                    os.stat(osutils.pathjoin(path, fname)))
133
146
                attr.filename = fname
134
147
                out.append(attr)
135
148
            return out
136
149
        except OSError, e:
137
 
            return SFTPServer.convert_errno(e.errno)
 
150
            return paramiko.SFTPServer.convert_errno(e.errno)
138
151
 
139
152
    def stat(self, path):
140
153
        path = self._realpath(path)
141
154
        try:
142
 
            return SFTPAttributes.from_stat(os.stat(path))
 
155
            return paramiko.SFTPAttributes.from_stat(os.stat(path))
143
156
        except OSError, e:
144
 
            return SFTPServer.convert_errno(e.errno)
 
157
            return paramiko.SFTPServer.convert_errno(e.errno)
145
158
 
146
159
    def lstat(self, path):
147
160
        path = self._realpath(path)
148
161
        try:
149
 
            return SFTPAttributes.from_stat(os.lstat(path))
 
162
            return paramiko.SFTPAttributes.from_stat(os.lstat(path))
150
163
        except OSError, e:
151
 
            return SFTPServer.convert_errno(e.errno)
 
164
            return paramiko.SFTPServer.convert_errno(e.errno)
152
165
 
153
166
    def open(self, path, flags, attr):
154
167
        path = self._realpath(path)
161
174
                # an odd default mode for files
162
175
                fd = os.open(path, flags, 0666)
163
176
        except OSError, e:
164
 
            return SFTPServer.convert_errno(e.errno)
 
177
            return paramiko.SFTPServer.convert_errno(e.errno)
165
178
 
166
179
        if (flags & os.O_CREAT) and (attr is not None):
167
180
            attr._flags &= ~attr.FLAG_PERMISSIONS
168
 
            SFTPServer.set_file_attr(path, attr)
 
181
            paramiko.SFTPServer.set_file_attr(path, attr)
169
182
        if flags & os.O_WRONLY:
170
183
            fstr = 'wb'
171
184
        elif flags & os.O_RDWR:
176
189
        try:
177
190
            f = os.fdopen(fd, fstr)
178
191
        except (IOError, OSError), e:
179
 
            return SFTPServer.convert_errno(e.errno)
 
192
            return paramiko.SFTPServer.convert_errno(e.errno)
180
193
        fobj = StubSFTPHandle()
181
194
        fobj.filename = path
182
195
        fobj.readfile = f
188
201
        try:
189
202
            os.remove(path)
190
203
        except OSError, e:
191
 
            return SFTPServer.convert_errno(e.errno)
192
 
        return SFTP_OK
 
204
            return paramiko.SFTPServer.convert_errno(e.errno)
 
205
        return paramiko.SFTP_OK
193
206
 
194
207
    def rename(self, oldpath, newpath):
195
208
        oldpath = self._realpath(oldpath)
197
210
        try:
198
211
            os.rename(oldpath, newpath)
199
212
        except OSError, e:
200
 
            return SFTPServer.convert_errno(e.errno)
201
 
        return SFTP_OK
 
213
            return paramiko.SFTPServer.convert_errno(e.errno)
 
214
        return paramiko.SFTP_OK
202
215
 
203
216
    def mkdir(self, path, attr):
204
217
        path = self._realpath(path)
211
224
                os.mkdir(path)
212
225
            if attr is not None:
213
226
                attr._flags &= ~attr.FLAG_PERMISSIONS
214
 
                SFTPServer.set_file_attr(path, attr)
 
227
                paramiko.SFTPServer.set_file_attr(path, attr)
215
228
        except OSError, e:
216
 
            return SFTPServer.convert_errno(e.errno)
217
 
        return SFTP_OK
 
229
            return paramiko.SFTPServer.convert_errno(e.errno)
 
230
        return paramiko.SFTP_OK
218
231
 
219
232
    def rmdir(self, path):
220
233
        path = self._realpath(path)
221
234
        try:
222
235
            os.rmdir(path)
223
236
        except OSError, e:
224
 
            return SFTPServer.convert_errno(e.errno)
225
 
        return SFTP_OK
 
237
            return paramiko.SFTPServer.convert_errno(e.errno)
 
238
        return paramiko.SFTP_OK
226
239
 
227
240
    # removed: chattr, symlink, readlink
228
241
    # (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