~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/stub_sftp.py

  • Committer: Vincent Ladeuil
  • Date: 2013-08-09 15:09:23 UTC
  • mto: This revision was merged to the branch mainline in revision 6587.
  • Revision ID: v.ladeuil+lp@free.fr-20130809150923-y71dusyorep0hbkt
Fix various typos in docstrings. Rename 'buffer' to 'buf' since it's now a python builtin function.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005 Robey Pointer <robey@lag.net>, Canonical Ltd
2
 
 
 
1
# Copyright (C) 2005, 2006, 2008-2011 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., 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
25
 
from bzrlib.osutils import pathjoin
26
 
from bzrlib.trace import mutter
27
 
 
28
 
 
29
 
class StubServer (ServerInterface):
30
 
    def __init__(self, test_case):
31
 
        ServerInterface.__init__(self)
32
 
        self._test_case = test_case
 
23
import paramiko
 
24
import socket
 
25
import SocketServer
 
26
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
33
45
 
34
46
    def check_auth_password(self, username, password):
35
47
        # all are allowed
36
 
        self._test_case.log('sftpserver - authorizing: %s' % (username,))
37
 
        return AUTH_SUCCESSFUL
 
48
        self.log('sftpserver - authorizing: %s' % (username,))
 
49
        return paramiko.AUTH_SUCCESSFUL
38
50
 
39
51
    def check_channel_request(self, kind, chanid):
40
 
        self._test_case.log('sftpserver - channel request: %s, %s' % (kind, chanid))
41
 
        return OPEN_SUCCEEDED
42
 
 
43
 
 
44
 
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
 
45
58
    def stat(self):
46
59
        try:
47
 
            return SFTPAttributes.from_stat(os.fstat(self.readfile.fileno()))
 
60
            return paramiko.SFTPAttributes.from_stat(
 
61
                os.fstat(self.readfile.fileno()))
48
62
        except OSError, e:
49
 
            return SFTPServer.convert_errno(e.errno)
 
63
            return paramiko.SFTPServer.convert_errno(e.errno)
50
64
 
51
65
    def chattr(self, attr):
52
66
        # python doesn't have equivalents to fchown or fchmod, so we have to
53
67
        # use the stored filename
54
 
        mutter('Changing permissions on %s to %s', self.filename, attr)
 
68
        trace.mutter('Changing permissions on %s to %s', self.filename, attr)
55
69
        try:
56
 
            SFTPServer.set_file_attr(self.filename, attr)
 
70
            paramiko.SFTPServer.set_file_attr(self.filename, attr)
57
71
        except OSError, e:
58
 
            return SFTPServer.convert_errno(e.errno)
59
 
 
60
 
 
61
 
class StubSFTPServer (SFTPServerInterface):
62
 
    def __init__(self, server, root):
63
 
        SFTPServerInterface.__init__(self, server)
 
72
            return paramiko.SFTPServer.convert_errno(e.errno)
 
73
 
 
74
 
 
75
class StubSFTPServer(paramiko.SFTPServerInterface):
 
76
 
 
77
    def __init__(self, server, root, home=None):
 
78
        paramiko.SFTPServerInterface.__init__(self, server)
 
79
        # All paths are actually relative to 'root'.
 
80
        # this is like implementing chroot().
64
81
        self.root = root
65
 
        
 
82
        if home is None:
 
83
            self.home = ''
 
84
        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))
 
89
            self.home = home[len(self.root):]
 
90
        if self.home.startswith('/'):
 
91
            self.home = self.home[1:]
 
92
        server.log('sftpserver - new connection')
 
93
 
66
94
    def _realpath(self, path):
 
95
        # paths returned from self.canonicalize() always start with
 
96
        # a path separator. So if 'root' is just '/', this would cause
 
97
        # a double slash at the beginning '//home/dir'.
 
98
        if self.root == '/':
 
99
            return self.canonicalize(path)
67
100
        return self.root + self.canonicalize(path)
68
101
 
 
102
    if sys.platform == 'win32':
 
103
        def canonicalize(self, path):
 
104
            # Win32 sftp paths end up looking like
 
105
            #     sftp://host@foo/h:/foo/bar
 
106
            # which means absolute paths look like:
 
107
            #     /h:/foo/bar
 
108
            # and relative paths stay the same:
 
109
            #     foo/bar
 
110
            # win32 needs to use the Unicode APIs. so we require the
 
111
            # paths to be utf8 (Linux just uses bytestreams)
 
112
            thispath = path.decode('utf8')
 
113
            if path.startswith('/'):
 
114
                # Abspath H:/foo/bar
 
115
                return os.path.normpath(thispath[1:])
 
116
            else:
 
117
                return os.path.normpath(os.path.join(self.home, thispath))
 
118
    else:
 
119
        def canonicalize(self, path):
 
120
            if os.path.isabs(path):
 
121
                return osutils.normpath(path)
 
122
            else:
 
123
                return osutils.normpath('/' + os.path.join(self.home, path))
 
124
 
69
125
    def chattr(self, path, attr):
70
126
        try:
71
 
            SFTPServer.set_file_attr(path, attr)
 
127
            paramiko.SFTPServer.set_file_attr(path, attr)
72
128
        except OSError, e:
73
 
            return SFTPServer.convert_errno(e.errno)
74
 
        return SFTP_OK
 
129
            return paramiko.SFTPServer.convert_errno(e.errno)
 
130
        return paramiko.SFTP_OK
75
131
 
76
132
    def list_folder(self, path):
77
133
        path = self._realpath(path)
78
134
        try:
79
135
            out = [ ]
80
 
            flist = os.listdir(path)
 
136
            # 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
 
139
            if sys.platform == 'win32':
 
140
                flist = [f.encode('utf8') for f in os.listdir(path)]
 
141
            else:
 
142
                flist = os.listdir(path)
81
143
            for fname in flist:
82
 
                attr = SFTPAttributes.from_stat(os.stat(pathjoin(path, fname)))
 
144
                attr = paramiko.SFTPAttributes.from_stat(
 
145
                    os.stat(osutils.pathjoin(path, fname)))
83
146
                attr.filename = fname
84
147
                out.append(attr)
85
148
            return out
86
149
        except OSError, e:
87
 
            return SFTPServer.convert_errno(e.errno)
 
150
            return paramiko.SFTPServer.convert_errno(e.errno)
88
151
 
89
152
    def stat(self, path):
90
153
        path = self._realpath(path)
91
154
        try:
92
 
            return SFTPAttributes.from_stat(os.stat(path))
 
155
            return paramiko.SFTPAttributes.from_stat(os.stat(path))
93
156
        except OSError, e:
94
 
            return SFTPServer.convert_errno(e.errno)
 
157
            return paramiko.SFTPServer.convert_errno(e.errno)
95
158
 
96
159
    def lstat(self, path):
97
160
        path = self._realpath(path)
98
161
        try:
99
 
            return SFTPAttributes.from_stat(os.lstat(path))
 
162
            return paramiko.SFTPAttributes.from_stat(os.lstat(path))
100
163
        except OSError, e:
101
 
            return SFTPServer.convert_errno(e.errno)
 
164
            return paramiko.SFTPServer.convert_errno(e.errno)
102
165
 
103
166
    def open(self, path, flags, attr):
104
167
        path = self._realpath(path)
105
168
        try:
106
 
            if hasattr(os, 'O_BINARY'):
107
 
                flags |= os.O_BINARY
108
 
            if (attr is not None) and hasattr(attr, 'st_mode'):
 
169
            flags |= getattr(os, 'O_BINARY', 0)
 
170
            if getattr(attr, 'st_mode', None):
109
171
                fd = os.open(path, flags, attr.st_mode)
110
172
            else:
111
 
                fd = os.open(path, flags)
 
173
                # os.open() defaults to 0777 which is
 
174
                # an odd default mode for files
 
175
                fd = os.open(path, flags, 0666)
112
176
        except OSError, e:
113
 
            return SFTPServer.convert_errno(e.errno)
 
177
            return paramiko.SFTPServer.convert_errno(e.errno)
 
178
 
114
179
        if (flags & os.O_CREAT) and (attr is not None):
115
180
            attr._flags &= ~attr.FLAG_PERMISSIONS
116
 
            SFTPServer.set_file_attr(path, attr)
 
181
            paramiko.SFTPServer.set_file_attr(path, attr)
117
182
        if flags & os.O_WRONLY:
118
183
            fstr = 'wb'
119
184
        elif flags & os.O_RDWR:
123
188
            fstr = 'rb'
124
189
        try:
125
190
            f = os.fdopen(fd, fstr)
126
 
        except OSError, e:
127
 
            return SFTPServer.convert_errno(e.errno)
 
191
        except (IOError, OSError), e:
 
192
            return paramiko.SFTPServer.convert_errno(e.errno)
128
193
        fobj = StubSFTPHandle()
129
194
        fobj.filename = path
130
195
        fobj.readfile = f
136
201
        try:
137
202
            os.remove(path)
138
203
        except OSError, e:
139
 
            return SFTPServer.convert_errno(e.errno)
140
 
        return SFTP_OK
 
204
            return paramiko.SFTPServer.convert_errno(e.errno)
 
205
        return paramiko.SFTP_OK
141
206
 
142
207
    def rename(self, oldpath, newpath):
143
208
        oldpath = self._realpath(oldpath)
145
210
        try:
146
211
            os.rename(oldpath, newpath)
147
212
        except OSError, e:
148
 
            return SFTPServer.convert_errno(e.errno)
149
 
        return SFTP_OK
 
213
            return paramiko.SFTPServer.convert_errno(e.errno)
 
214
        return paramiko.SFTP_OK
150
215
 
151
216
    def mkdir(self, path, attr):
152
217
        path = self._realpath(path)
153
218
        try:
154
 
            if attr is not None and hasattr(attr, 'st_mode'):
 
219
            # Using getattr() in case st_mode is None or 0
 
220
            # both evaluate to False
 
221
            if getattr(attr, 'st_mode', None):
155
222
                os.mkdir(path, attr.st_mode)
156
223
            else:
157
224
                os.mkdir(path)
158
225
            if attr is not None:
159
226
                attr._flags &= ~attr.FLAG_PERMISSIONS
160
 
                SFTPServer.set_file_attr(path, attr)
 
227
                paramiko.SFTPServer.set_file_attr(path, attr)
161
228
        except OSError, e:
162
 
            return SFTPServer.convert_errno(e.errno)
163
 
        return SFTP_OK
 
229
            return paramiko.SFTPServer.convert_errno(e.errno)
 
230
        return paramiko.SFTP_OK
164
231
 
165
232
    def rmdir(self, path):
166
233
        path = self._realpath(path)
167
234
        try:
168
235
            os.rmdir(path)
169
236
        except OSError, e:
170
 
            return SFTPServer.convert_errno(e.errno)
171
 
        return SFTP_OK
 
237
            return paramiko.SFTPServer.convert_errno(e.errno)
 
238
        return paramiko.SFTP_OK
172
239
 
173
240
    # removed: chattr, symlink, readlink
174
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