/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 bzrlib/selftest/stub_sftp.py

[merge] robertc's integration, updated tests to check for retcode=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
 
#
 
1
# Copyright (C) 2005 Robey Pointer <robey@lag.net>, Canonical Ltd
 
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
5
5
# the Free Software Foundation; either version 2 of the License, or
6
6
# (at your option) any later version.
7
 
#
 
7
 
8
8
# This program is distributed in the hope that it will be useful,
9
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
11
# GNU General Public License for more details.
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
 
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
 
 
 
23
from paramiko import ServerInterface, SFTPServerInterface, SFTPServer, SFTPAttributes, \
 
24
    SFTPHandle, SFTP_OK, AUTH_SUCCESSFUL, OPEN_SUCCEEDED
 
25
 
 
26
 
 
27
class StubServer (ServerInterface):
49
28
    def check_auth_password(self, username, password):
50
29
        # all are allowed
51
 
        self.log('sftpserver - authorizing: %s' % (username,))
52
 
        return paramiko.AUTH_SUCCESSFUL
 
30
        return AUTH_SUCCESSFUL
53
31
 
54
32
    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
 
 
 
33
        return OPEN_SUCCEEDED
 
34
 
 
35
 
 
36
class StubSFTPHandle (SFTPHandle):
61
37
    def stat(self):
62
38
        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)
 
39
            return SFTPAttributes.from_stat(os.fstat(self.readfile.fileno()))
 
40
        except OSError, e:
 
41
            return SFTPServer.convert_errno(e.errno)
67
42
 
68
43
    def chattr(self, attr):
69
44
        # python doesn't have equivalents to fchown or fchmod, so we have to
70
45
        # use the stored filename
71
 
        trace.mutter('Changing permissions on %s to %s', self.filename, attr)
72
46
        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().
 
47
            SFTPServer.set_file_attr(self.filename, attr)
 
48
        except OSError, e:
 
49
            return SFTPServer.convert_errno(e.errno)
 
50
 
 
51
 
 
52
class StubSFTPServer (SFTPServerInterface):
 
53
    def __init__(self, server, root):
 
54
        SFTPServerInterface.__init__(self, server)
84
55
        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
 
 
 
56
        
97
57
    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
58
        return self.root + self.canonicalize(path)
104
59
 
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
60
    def list_folder(self, path):
136
61
        path = self._realpath(path)
137
62
        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)
 
63
            out = [ ]
 
64
            flist = os.listdir(path)
146
65
            for fname in flist:
147
 
                attr = paramiko.SFTPAttributes.from_stat(
148
 
                    os.stat(osutils.pathjoin(path, fname)))
 
66
                attr = SFTPAttributes.from_stat(os.stat(os.path.join(path, fname)))
149
67
                attr.filename = fname
150
68
                out.append(attr)
151
69
            return out
152
 
        except OSError as e:
153
 
            return paramiko.SFTPServer.convert_errno(e.errno)
 
70
        except OSError, e:
 
71
            return SFTPServer.convert_errno(e.errno)
154
72
 
155
73
    def stat(self, path):
156
74
        path = self._realpath(path)
157
75
        try:
158
 
            return paramiko.SFTPAttributes.from_stat(os.stat(path))
159
 
        except OSError as e:
160
 
            return paramiko.SFTPServer.convert_errno(e.errno)
 
76
            return SFTPAttributes.from_stat(os.stat(path))
 
77
        except OSError, e:
 
78
            return SFTPServer.convert_errno(e.errno)
161
79
 
162
80
    def lstat(self, path):
163
81
        path = self._realpath(path)
164
82
        try:
165
 
            return paramiko.SFTPAttributes.from_stat(os.lstat(path))
166
 
        except OSError as e:
167
 
            return paramiko.SFTPServer.convert_errno(e.errno)
 
83
            return SFTPAttributes.from_stat(os.lstat(path))
 
84
        except OSError, e:
 
85
            return SFTPServer.convert_errno(e.errno)
168
86
 
169
87
    def open(self, path, flags, attr):
170
88
        path = self._realpath(path)
171
89
        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
 
 
 
90
            fd = os.open(path, flags)
 
91
        except OSError, e:
 
92
            return SFTPServer.convert_errno(e.errno)
182
93
        if (flags & os.O_CREAT) and (attr is not None):
183
 
            attr._flags &= ~attr.FLAG_PERMISSIONS
184
 
            paramiko.SFTPServer.set_file_attr(path, attr)
 
94
            SFTPServer.set_file_attr(path, attr)
185
95
        if flags & os.O_WRONLY:
186
 
            fstr = 'wb'
 
96
            fstr = 'w'
187
97
        elif flags & os.O_RDWR:
188
 
            fstr = 'rb+'
 
98
            fstr = 'r+'
189
99
        else:
190
100
            # O_RDONLY (== 0)
191
 
            fstr = 'rb'
 
101
            fstr = 'r'
192
102
        try:
193
103
            f = os.fdopen(fd, fstr)
194
 
        except (IOError, OSError) as e:
195
 
            return paramiko.SFTPServer.convert_errno(e.errno)
 
104
        except OSError, e:
 
105
            return SFTPServer.convert_errno(e.errno)
196
106
        fobj = StubSFTPHandle()
197
107
        fobj.filename = path
198
108
        fobj.readfile = f
203
113
        path = self._realpath(path)
204
114
        try:
205
115
            os.remove(path)
206
 
        except OSError as e:
207
 
            return paramiko.SFTPServer.convert_errno(e.errno)
208
 
        return paramiko.SFTP_OK
 
116
        except OSError, e:
 
117
            return SFTPServer.convert_errno(e.errno)
 
118
        return SFTP_OK
209
119
 
210
120
    def rename(self, oldpath, newpath):
211
121
        oldpath = self._realpath(oldpath)
212
122
        newpath = self._realpath(newpath)
213
123
        try:
214
124
            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
 
125
        except OSError, e:
 
126
            return SFTPServer.convert_errno(e.errno)
 
127
        return SFTP_OK
234
128
 
235
129
    def mkdir(self, path, attr):
236
130
        path = self._realpath(path)
237
131
        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)
 
132
            os.mkdir(path)
244
133
            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
 
134
                SFTPServer.set_file_attr(path, attr)
 
135
        except OSError, e:
 
136
            return SFTPServer.convert_errno(e.errno)
 
137
        return SFTP_OK
250
138
 
251
139
    def rmdir(self, path):
252
140
        path = self._realpath(path)
253
141
        try:
254
142
            os.rmdir(path)
255
 
        except OSError as e:
256
 
            return paramiko.SFTPServer.convert_errno(e.errno)
257
 
        return paramiko.SFTP_OK
 
143
        except OSError, e:
 
144
            return SFTPServer.convert_errno(e.errno)
 
145
        return SFTP_OK
258
146
 
259
 
    # removed: chattr
 
147
    # removed: chattr, symlink, readlink
260
148
    # (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