~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/stub_sftp.py

  • Committer: Marius Kruger
  • Date: 2010-07-10 21:28:56 UTC
  • mto: (5384.1.1 integration)
  • mto: This revision was merged to the branch mainline in revision 5385.
  • Revision ID: marius.kruger@enerweb.co.za-20100710212856-uq4ji3go0u5se7hx
* Update documentation
* add NEWS

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005 Robey Pointer <robey@lag.net>, Canonical Ltd
 
1
# Copyright (C) 2005, 2006, 2008, 2009, 2010 Robey Pointer <robey@lag.net>, Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
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 select
 
25
import socket
25
26
import sys
26
 
 
27
 
from bzrlib.osutils import pathjoin
28
 
from bzrlib.trace import mutter
29
 
 
30
 
 
31
 
class StubServer (ServerInterface):
 
27
import threading
 
28
import time
 
29
 
 
30
from bzrlib import (
 
31
    osutils,
 
32
    trace,
 
33
    urlutils,
 
34
    )
 
35
from bzrlib.transport import (
 
36
    ssh,
 
37
    )
 
38
from bzrlib.tests import test_server
 
39
 
 
40
 
 
41
class StubServer (paramiko.ServerInterface):
32
42
 
33
43
    def __init__(self, test_case):
34
 
        ServerInterface.__init__(self)
 
44
        paramiko.ServerInterface.__init__(self)
35
45
        self._test_case = test_case
36
46
 
37
47
    def check_auth_password(self, username, password):
38
48
        # all are allowed
39
49
        self._test_case.log('sftpserver - authorizing: %s' % (username,))
40
 
        return AUTH_SUCCESSFUL
 
50
        return paramiko.AUTH_SUCCESSFUL
41
51
 
42
52
    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):
 
53
        self._test_case.log(
 
54
            'sftpserver - channel request: %s, %s' % (kind, chanid))
 
55
        return paramiko.OPEN_SUCCEEDED
 
56
 
 
57
 
 
58
class StubSFTPHandle (paramiko.SFTPHandle):
48
59
    def stat(self):
49
60
        try:
50
 
            return SFTPAttributes.from_stat(os.fstat(self.readfile.fileno()))
 
61
            return paramiko.SFTPAttributes.from_stat(
 
62
                os.fstat(self.readfile.fileno()))
51
63
        except OSError, e:
52
 
            return SFTPServer.convert_errno(e.errno)
 
64
            return paramiko.SFTPServer.convert_errno(e.errno)
53
65
 
54
66
    def chattr(self, attr):
55
67
        # python doesn't have equivalents to fchown or fchmod, so we have to
56
68
        # use the stored filename
57
 
        mutter('Changing permissions on %s to %s', self.filename, attr)
 
69
        trace.mutter('Changing permissions on %s to %s', self.filename, attr)
58
70
        try:
59
 
            SFTPServer.set_file_attr(self.filename, attr)
 
71
            paramiko.SFTPServer.set_file_attr(self.filename, attr)
60
72
        except OSError, e:
61
 
            return SFTPServer.convert_errno(e.errno)
62
 
 
63
 
 
64
 
class StubSFTPServer (SFTPServerInterface):
 
73
            return paramiko.SFTPServer.convert_errno(e.errno)
 
74
 
 
75
 
 
76
class StubSFTPServer (paramiko.SFTPServerInterface):
65
77
 
66
78
    def __init__(self, server, root, home=None):
67
 
        SFTPServerInterface.__init__(self, server)
 
79
        paramiko.SFTPServerInterface.__init__(self, server)
68
80
        # All paths are actually relative to 'root'.
69
81
        # this is like implementing chroot().
70
82
        self.root = root
71
83
        if home is None:
72
84
            self.home = ''
73
85
        else:
74
 
            assert home.startswith(self.root), \
75
 
                    "home must be a subdirectory of root (%s vs %s)" \
76
 
                    % (home, root)
 
86
            if not home.startswith(self.root):
 
87
                raise AssertionError(
 
88
                    "home must be a subdirectory of root (%s vs %s)"
 
89
                    % (home, root))
77
90
            self.home = home[len(self.root):]
78
91
        if self.home.startswith('/'):
79
92
            self.home = self.home[1:]
82
95
    def _realpath(self, path):
83
96
        # paths returned from self.canonicalize() always start with
84
97
        # a path separator. So if 'root' is just '/', this would cause
85
 
        # a double slash at the beginning '//home/dir'. 
 
98
        # a double slash at the beginning '//home/dir'.
86
99
        if self.root == '/':
87
100
            return self.canonicalize(path)
88
101
        return self.root + self.canonicalize(path)
95
108
            #     /h:/foo/bar
96
109
            # and relative paths stay the same:
97
110
            #     foo/bar
98
 
            # win32 needs to use the Unicode APIs. so we require the 
 
111
            # win32 needs to use the Unicode APIs. so we require the
99
112
            # paths to be utf8 (Linux just uses bytestreams)
100
113
            thispath = path.decode('utf8')
101
114
            if path.startswith('/'):
112
125
 
113
126
    def chattr(self, path, attr):
114
127
        try:
115
 
            SFTPServer.set_file_attr(path, attr)
 
128
            paramiko.SFTPServer.set_file_attr(path, attr)
116
129
        except OSError, e:
117
 
            return SFTPServer.convert_errno(e.errno)
118
 
        return SFTP_OK
 
130
            return paramiko.SFTPServer.convert_errno(e.errno)
 
131
        return paramiko.SFTP_OK
119
132
 
120
133
    def list_folder(self, path):
121
134
        path = self._realpath(path)
122
135
        try:
123
136
            out = [ ]
124
137
            # 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 
 
138
            # unicode. However on unix the server should only deal with
 
139
            # bytestreams and posix.listdir does the right thing
127
140
            if sys.platform == 'win32':
128
141
                flist = [f.encode('utf8') for f in os.listdir(path)]
129
142
            else:
130
143
                flist = os.listdir(path)
131
144
            for fname in flist:
132
 
                attr = SFTPAttributes.from_stat(os.stat(pathjoin(path, fname)))
 
145
                attr = paramiko.SFTPAttributes.from_stat(
 
146
                    os.stat(osutils.pathjoin(path, fname)))
133
147
                attr.filename = fname
134
148
                out.append(attr)
135
149
            return out
136
150
        except OSError, e:
137
 
            return SFTPServer.convert_errno(e.errno)
 
151
            return paramiko.SFTPServer.convert_errno(e.errno)
138
152
 
139
153
    def stat(self, path):
140
154
        path = self._realpath(path)
141
155
        try:
142
 
            return SFTPAttributes.from_stat(os.stat(path))
 
156
            return paramiko.SFTPAttributes.from_stat(os.stat(path))
143
157
        except OSError, e:
144
 
            return SFTPServer.convert_errno(e.errno)
 
158
            return paramiko.SFTPServer.convert_errno(e.errno)
145
159
 
146
160
    def lstat(self, path):
147
161
        path = self._realpath(path)
148
162
        try:
149
 
            return SFTPAttributes.from_stat(os.lstat(path))
 
163
            return paramiko.SFTPAttributes.from_stat(os.lstat(path))
150
164
        except OSError, e:
151
 
            return SFTPServer.convert_errno(e.errno)
 
165
            return paramiko.SFTPServer.convert_errno(e.errno)
152
166
 
153
167
    def open(self, path, flags, attr):
154
168
        path = self._realpath(path)
161
175
                # an odd default mode for files
162
176
                fd = os.open(path, flags, 0666)
163
177
        except OSError, e:
164
 
            return SFTPServer.convert_errno(e.errno)
 
178
            return paramiko.SFTPServer.convert_errno(e.errno)
165
179
 
166
180
        if (flags & os.O_CREAT) and (attr is not None):
167
181
            attr._flags &= ~attr.FLAG_PERMISSIONS
168
 
            SFTPServer.set_file_attr(path, attr)
 
182
            paramiko.SFTPServer.set_file_attr(path, attr)
169
183
        if flags & os.O_WRONLY:
170
184
            fstr = 'wb'
171
185
        elif flags & os.O_RDWR:
176
190
        try:
177
191
            f = os.fdopen(fd, fstr)
178
192
        except (IOError, OSError), e:
179
 
            return SFTPServer.convert_errno(e.errno)
 
193
            return paramiko.SFTPServer.convert_errno(e.errno)
180
194
        fobj = StubSFTPHandle()
181
195
        fobj.filename = path
182
196
        fobj.readfile = f
188
202
        try:
189
203
            os.remove(path)
190
204
        except OSError, e:
191
 
            return SFTPServer.convert_errno(e.errno)
192
 
        return SFTP_OK
 
205
            return paramiko.SFTPServer.convert_errno(e.errno)
 
206
        return paramiko.SFTP_OK
193
207
 
194
208
    def rename(self, oldpath, newpath):
195
209
        oldpath = self._realpath(oldpath)
197
211
        try:
198
212
            os.rename(oldpath, newpath)
199
213
        except OSError, e:
200
 
            return SFTPServer.convert_errno(e.errno)
201
 
        return SFTP_OK
 
214
            return paramiko.SFTPServer.convert_errno(e.errno)
 
215
        return paramiko.SFTP_OK
202
216
 
203
217
    def mkdir(self, path, attr):
204
218
        path = self._realpath(path)
211
225
                os.mkdir(path)
212
226
            if attr is not None:
213
227
                attr._flags &= ~attr.FLAG_PERMISSIONS
214
 
                SFTPServer.set_file_attr(path, attr)
 
228
                paramiko.SFTPServer.set_file_attr(path, attr)
215
229
        except OSError, e:
216
 
            return SFTPServer.convert_errno(e.errno)
217
 
        return SFTP_OK
 
230
            return paramiko.SFTPServer.convert_errno(e.errno)
 
231
        return paramiko.SFTP_OK
218
232
 
219
233
    def rmdir(self, path):
220
234
        path = self._realpath(path)
221
235
        try:
222
236
            os.rmdir(path)
223
237
        except OSError, e:
224
 
            return SFTPServer.convert_errno(e.errno)
225
 
        return SFTP_OK
 
238
            return paramiko.SFTPServer.convert_errno(e.errno)
 
239
        return paramiko.SFTP_OK
226
240
 
227
241
    # removed: chattr, symlink, readlink
228
242
    # (nothing in bzr's sftp transport uses those)
 
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 SocketListener(threading.Thread):
 
266
 
 
267
    def __init__(self, callback):
 
268
        threading.Thread.__init__(self)
 
269
        self._callback = callback
 
270
        self._socket = socket.socket()
 
271
        self._socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
 
272
        self._socket.bind(('localhost', 0))
 
273
        self._socket.listen(1)
 
274
        self.host, self.port = self._socket.getsockname()[:2]
 
275
        self._stop_event = threading.Event()
 
276
 
 
277
    def stop(self):
 
278
        # called from outside this thread
 
279
        self._stop_event.set()
 
280
        # use a timeout here, because if the test fails, the server thread may
 
281
        # never notice the stop_event.
 
282
        self.join(5.0)
 
283
        self._socket.close()
 
284
 
 
285
    def run(self):
 
286
        trace.mutter('SocketListener %r has started', self)
 
287
        while True:
 
288
            readable, writable_unused, exception_unused = \
 
289
                select.select([self._socket], [], [], 0.1)
 
290
            if self._stop_event.isSet():
 
291
                trace.mutter('SocketListener %r has stopped', self)
 
292
                return
 
293
            if len(readable) == 0:
 
294
                continue
 
295
            try:
 
296
                s, addr_unused = self._socket.accept()
 
297
                trace.mutter('SocketListener %r has accepted connection %r',
 
298
                    self, s)
 
299
                # because the loopback socket is inline, and transports are
 
300
                # never explicitly closed, best to launch a new thread.
 
301
                threading.Thread(target=self._callback, args=(s,)).start()
 
302
            except socket.error, x:
 
303
                sys.excepthook(*sys.exc_info())
 
304
                trace.warning('Socket error during accept() '
 
305
                              'within unit test server thread: %r' % x)
 
306
            except Exception, x:
 
307
                # probably a failed test; unit test thread will log the
 
308
                # failure/error
 
309
                sys.excepthook(*sys.exc_info())
 
310
                trace.warning(
 
311
                    'Exception from within unit test server thread: %r' % x)
 
312
 
 
313
 
 
314
class SocketDelay(object):
 
315
    """A socket decorator to make TCP appear slower.
 
316
 
 
317
    This changes recv, send, and sendall to add a fixed latency to each python
 
318
    call if a new roundtrip is detected. That is, when a recv is called and the
 
319
    flag new_roundtrip is set, latency is charged. Every send and send_all
 
320
    sets this flag.
 
321
 
 
322
    In addition every send, sendall and recv sleeps a bit per character send to
 
323
    simulate bandwidth.
 
324
 
 
325
    Not all methods are implemented, this is deliberate as this class is not a
 
326
    replacement for the builtin sockets layer. fileno is not implemented to
 
327
    prevent the proxy being bypassed.
 
328
    """
 
329
 
 
330
    simulated_time = 0
 
331
    _proxied_arguments = dict.fromkeys([
 
332
        "close", "getpeername", "getsockname", "getsockopt", "gettimeout",
 
333
        "setblocking", "setsockopt", "settimeout", "shutdown"])
 
334
 
 
335
    def __init__(self, sock, latency, bandwidth=1.0,
 
336
                 really_sleep=True):
 
337
        """
 
338
        :param bandwith: simulated bandwith (MegaBit)
 
339
        :param really_sleep: If set to false, the SocketDelay will just
 
340
        increase a counter, instead of calling time.sleep. This is useful for
 
341
        unittesting the SocketDelay.
 
342
        """
 
343
        self.sock = sock
 
344
        self.latency = latency
 
345
        self.really_sleep = really_sleep
 
346
        self.time_per_byte = 1 / (bandwidth / 8.0 * 1024 * 1024)
 
347
        self.new_roundtrip = False
 
348
 
 
349
    def sleep(self, s):
 
350
        if self.really_sleep:
 
351
            time.sleep(s)
 
352
        else:
 
353
            SocketDelay.simulated_time += s
 
354
 
 
355
    def __getattr__(self, attr):
 
356
        if attr in SocketDelay._proxied_arguments:
 
357
            return getattr(self.sock, attr)
 
358
        raise AttributeError("'SocketDelay' object has no attribute %r" %
 
359
                             attr)
 
360
 
 
361
    def dup(self):
 
362
        return SocketDelay(self.sock.dup(), self.latency, self.time_per_byte,
 
363
                           self._sleep)
 
364
 
 
365
    def recv(self, *args):
 
366
        data = self.sock.recv(*args)
 
367
        if data and self.new_roundtrip:
 
368
            self.new_roundtrip = False
 
369
            self.sleep(self.latency)
 
370
        self.sleep(len(data) * self.time_per_byte)
 
371
        return data
 
372
 
 
373
    def sendall(self, data, flags=0):
 
374
        if not self.new_roundtrip:
 
375
            self.new_roundtrip = True
 
376
            self.sleep(self.latency)
 
377
        self.sleep(len(data) * self.time_per_byte)
 
378
        return self.sock.sendall(data, flags)
 
379
 
 
380
    def send(self, data, flags=0):
 
381
        if not self.new_roundtrip:
 
382
            self.new_roundtrip = True
 
383
            self.sleep(self.latency)
 
384
        bytes_sent = self.sock.send(data, flags)
 
385
        self.sleep(bytes_sent * self.time_per_byte)
 
386
        return bytes_sent
 
387
 
 
388
 
 
389
class SFTPServer(test_server.TestServer):
 
390
    """Common code for SFTP server facilities."""
 
391
 
 
392
    def __init__(self, server_interface=StubServer):
 
393
        self._original_vendor = None
 
394
        self._homedir = None
 
395
        self._server_homedir = None
 
396
        self._listener = None
 
397
        self._root = None
 
398
        self._vendor = ssh.ParamikoVendor()
 
399
        self._server_interface = server_interface
 
400
        # sftp server logs
 
401
        self.logs = []
 
402
        self.add_latency = 0
 
403
 
 
404
    def _get_sftp_url(self, path):
 
405
        """Calculate an sftp url to this server for path."""
 
406
        return 'sftp://foo:bar@%s:%d/%s' % (self._listener.host,
 
407
                                            self._listener.port, path)
 
408
 
 
409
    def log(self, message):
 
410
        """StubServer uses this to log when a new server is created."""
 
411
        self.logs.append(message)
 
412
 
 
413
    def _run_server_entry(self, sock):
 
414
        """Entry point for all implementations of _run_server.
 
415
 
 
416
        If self.add_latency is > 0.000001 then sock is given a latency adding
 
417
        decorator.
 
418
        """
 
419
        if self.add_latency > 0.000001:
 
420
            sock = SocketDelay(sock, self.add_latency)
 
421
        return self._run_server(sock)
 
422
 
 
423
    def _run_server(self, s):
 
424
        ssh_server = paramiko.Transport(s)
 
425
        key_file = osutils.pathjoin(self._homedir, 'test_rsa.key')
 
426
        f = open(key_file, 'w')
 
427
        f.write(STUB_SERVER_KEY)
 
428
        f.close()
 
429
        host_key = paramiko.RSAKey.from_private_key_file(key_file)
 
430
        ssh_server.add_server_key(host_key)
 
431
        server = self._server_interface(self)
 
432
        ssh_server.set_subsystem_handler('sftp', paramiko.SFTPServer,
 
433
                                         StubSFTPServer, root=self._root,
 
434
                                         home=self._server_homedir)
 
435
        event = threading.Event()
 
436
        ssh_server.start_server(event, server)
 
437
        event.wait(5.0)
 
438
 
 
439
    def start_server(self, backing_server=None):
 
440
        # XXX: TODO: make sftpserver back onto backing_server rather than local
 
441
        # disk.
 
442
        if not (backing_server is None or
 
443
                isinstance(backing_server, test_server.LocalURLServer)):
 
444
            raise AssertionError(
 
445
                'backing_server should not be %r, because this can only serve '
 
446
                'the local current working directory.' % (backing_server,))
 
447
        self._original_vendor = ssh._ssh_vendor_manager._cached_ssh_vendor
 
448
        ssh._ssh_vendor_manager._cached_ssh_vendor = self._vendor
 
449
        if sys.platform == 'win32':
 
450
            # Win32 needs to use the UNICODE api
 
451
            self._homedir = os.getcwdu()
 
452
            # Normalize the path or it will be wrongly escaped
 
453
            self._homedir = osutils.normpath(self._homedir)
 
454
        else:
 
455
            # But unix SFTP servers should just deal in bytestreams
 
456
            self._homedir = os.getcwd()
 
457
        if self._server_homedir is None:
 
458
            self._server_homedir = self._homedir
 
459
        self._root = '/'
 
460
        if sys.platform == 'win32':
 
461
            self._root = ''
 
462
        self._listener = SocketListener(self._run_server_entry)
 
463
        self._listener.setDaemon(True)
 
464
        self._listener.start()
 
465
 
 
466
    def stop_server(self):
 
467
        self._listener.stop()
 
468
        ssh._ssh_vendor_manager._cached_ssh_vendor = self._original_vendor
 
469
 
 
470
    def get_bogus_url(self):
 
471
        """See bzrlib.transport.Server.get_bogus_url."""
 
472
        # this is chosen to try to prevent trouble with proxies, wierd dns, etc
 
473
        # we bind a random socket, so that we get a guaranteed unused port
 
474
        # we just never listen on that port
 
475
        s = socket.socket()
 
476
        s.bind(('localhost', 0))
 
477
        return 'sftp://%s:%s/' % s.getsockname()
 
478
 
 
479
 
 
480
class SFTPFullAbsoluteServer(SFTPServer):
 
481
    """A test server for sftp transports, using absolute urls and ssh."""
 
482
 
 
483
    def get_url(self):
 
484
        """See bzrlib.transport.Server.get_url."""
 
485
        homedir = self._homedir
 
486
        if sys.platform != 'win32':
 
487
            # Remove the initial '/' on all platforms but win32
 
488
            homedir = homedir[1:]
 
489
        return self._get_sftp_url(urlutils.escape(homedir))
 
490
 
 
491
 
 
492
class SFTPServerWithoutSSH(SFTPServer):
 
493
    """An SFTP server that uses a simple TCP socket pair rather than SSH."""
 
494
 
 
495
    def __init__(self):
 
496
        super(SFTPServerWithoutSSH, self).__init__()
 
497
        self._vendor = ssh.LoopbackVendor()
 
498
 
 
499
    def _run_server(self, sock):
 
500
        # Re-import these as locals, so that they're still accessible during
 
501
        # interpreter shutdown (when all module globals get set to None, leading
 
502
        # to confusing errors like "'NoneType' object has no attribute 'error'".
 
503
        class FakeChannel(object):
 
504
            def get_transport(self):
 
505
                return self
 
506
            def get_log_channel(self):
 
507
                return 'paramiko'
 
508
            def get_name(self):
 
509
                return '1'
 
510
            def get_hexdump(self):
 
511
                return False
 
512
            def close(self):
 
513
                pass
 
514
 
 
515
        server = paramiko.SFTPServer(
 
516
            FakeChannel(), 'sftp', StubServer(self), StubSFTPServer,
 
517
            root=self._root, home=self._server_homedir)
 
518
        try:
 
519
            server.start_subsystem(
 
520
                'sftp', None, ssh.SocketAsChannelAdapter(sock))
 
521
        except socket.error, e:
 
522
            if (len(e.args) > 0) and (e.args[0] == errno.EPIPE):
 
523
                # it's okay for the client to disconnect abruptly
 
524
                # (bug in paramiko 1.6: it should absorb this exception)
 
525
                pass
 
526
            else:
 
527
                raise
 
528
        except Exception, e:
 
529
            # This typically seems to happen during interpreter shutdown, so
 
530
            # most of the useful ways to report this error are won't work.
 
531
            # Writing the exception type, and then the text of the exception,
 
532
            # seems to be the best we can do.
 
533
            import sys
 
534
            sys.stderr.write('\nEXCEPTION %r: ' % (e.__class__,))
 
535
            sys.stderr.write('%s\n\n' % (e,))
 
536
        server.finish_subsystem()
 
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("~/")
 
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 start_server(self, backing_server=None):
 
566
        self._server_homedir = '/dev/noone/runs/tests/here'
 
567
        super(SFTPSiblingAbsoluteServer, self).start_server(backing_server)
 
568