/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/smart/request.py

  • Committer: Vincent Ladeuil
  • Date: 2007-04-13 16:16:54 UTC
  • mto: (2420.1.1 bzr.http.auth)
  • mto: This revision was merged to the branch mainline in revision 2463.
  • Revision ID: v.ladeuil+lp@free.fr-20070413161654-kp4kajrrr23jge2f
Complete tests.

* bzrlib/transport/http/_urllib.py:
(HttpTransport_urllib.__init__): Be more strict on valid users.
(HttpTransport_urllib._ask_password): Delete John's TODO. See ? I
didn't forget ! :-)

* bzrlib/tests/test_ui.py: 
Fix some inconsistencies.

* bzrlib/tests/test_http.py:
Add more tests.
(TestHTTPBasicAuth.setUp): Setup a private ui_factory.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2006-2010 Canonical Ltd
 
1
# Copyright (C) 2006, 2007 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
12
12
#
13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
 
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
 
 
17
 
"""Infrastructure for server-side request handlers.
18
 
 
19
 
Interesting module attributes:
20
 
    * The request_handlers registry maps verb names to SmartServerRequest
21
 
      classes.
22
 
    * The jail_info threading.local() object is used to prevent accidental
23
 
      opening of BzrDirs outside of the backing transport, or any other
24
 
      transports placed in jail_info.transports.  The jail_info is reset on
25
 
      every call into a request handler (which can happen an arbitrary number
26
 
      of times during a request).
27
 
"""
28
 
 
29
 
# XXX: The class names are a little confusing: the protocol will instantiate a
30
 
# SmartServerRequestHandler, whose dispatch_command method creates an instance
31
 
# of a SmartServerRequest subclass.
 
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
16
 
 
17
"""Basic server-side logic for dealing with requests."""
32
18
 
33
19
 
34
20
import tempfile
35
 
import thread
36
 
import threading
37
21
 
38
22
from bzrlib import (
39
23
    bzrdir,
40
 
    debug,
41
24
    errors,
42
 
    osutils,
43
 
    registry,
44
 
    revision,
45
 
    trace,
46
 
    urlutils,
 
25
    revision
47
26
    )
48
 
from bzrlib.lazy_import import lazy_import
49
 
lazy_import(globals(), """
50
 
from bzrlib.bundle import serializer
51
 
""")
52
 
 
53
 
 
54
 
jail_info = threading.local()
55
 
jail_info.transports = None
56
 
 
57
 
 
58
 
def _install_hook():
59
 
    bzrdir.BzrDir.hooks.install_named_hook(
60
 
        'pre_open', _pre_open_hook, 'checking server jail')
61
 
 
62
 
 
63
 
def _pre_open_hook(transport):
64
 
    allowed_transports = getattr(jail_info, 'transports', None)
65
 
    if allowed_transports is None:
66
 
        return
67
 
    abspath = transport.base
68
 
    for allowed_transport in allowed_transports:
69
 
        try:
70
 
            allowed_transport.relpath(abspath)
71
 
        except errors.PathNotChild:
72
 
            continue
73
 
        else:
74
 
            return
75
 
    raise errors.JailBreak(abspath)
76
 
 
77
 
 
78
 
_install_hook()
79
 
 
80
 
 
81
 
class SmartServerRequest(object):
82
 
    """Base class for request handlers.
83
 
 
84
 
    To define a new request, subclass this class and override the `do` method
85
 
    (and if appropriate, `do_body` as well).  Request implementors should take
86
 
    care to call `translate_client_path` and `transport_from_client_path` as
87
 
    appropriate when dealing with paths received from the client.
88
 
    """
89
 
    # XXX: rename this class to BaseSmartServerRequestHandler ?  A request
90
 
    # *handler* is a different concept to the request.
91
 
 
92
 
    def __init__(self, backing_transport, root_client_path='/', jail_root=None):
93
 
        """Constructor.
94
 
 
95
 
        :param backing_transport: the base transport to be used when performing
96
 
            this request.
97
 
        :param root_client_path: the client path that maps to the root of
98
 
            backing_transport.  This is used to interpret relpaths received
99
 
            from the client.  Clients will not be able to refer to paths above
100
 
            this root.  If root_client_path is None, then no translation will
101
 
            be performed on client paths.  Default is '/'.
102
 
        :param jail_root: if specified, the root of the BzrDir.open jail to use
103
 
            instead of backing_transport.
104
 
        """
105
 
        self._backing_transport = backing_transport
106
 
        if jail_root is None:
107
 
            jail_root = backing_transport
108
 
        self._jail_root = jail_root
109
 
        if root_client_path is not None:
110
 
            if not root_client_path.startswith('/'):
111
 
                root_client_path = '/' + root_client_path
112
 
            if not root_client_path.endswith('/'):
113
 
                root_client_path += '/'
114
 
        self._root_client_path = root_client_path
115
 
        self._body_chunks = []
116
 
 
117
 
    def _check_enabled(self):
118
 
        """Raises DisabledMethod if this method is disabled."""
119
 
        pass
120
 
 
121
 
    def do(self, *args):
122
 
        """Mandatory extension point for SmartServerRequest subclasses.
123
 
 
124
 
        Subclasses must implement this.
125
 
 
126
 
        This should return a SmartServerResponse if this command expects to
127
 
        receive no body.
128
 
        """
129
 
        raise NotImplementedError(self.do)
130
 
 
131
 
    def execute(self, *args):
132
 
        """Public entry point to execute this request.
133
 
 
134
 
        It will return a SmartServerResponse if the command does not expect a
135
 
        body.
136
 
 
137
 
        :param *args: the arguments of the request.
138
 
        """
139
 
        self._check_enabled()
140
 
        return self.do(*args)
141
 
 
142
 
    def do_body(self, body_bytes):
143
 
        """Called if the client sends a body with the request.
144
 
 
145
 
        The do() method is still called, and must have returned None.
146
 
 
147
 
        Must return a SmartServerResponse.
148
 
        """
149
 
        if body_bytes != '':
150
 
            raise errors.SmartProtocolError('Request does not expect a body')
151
 
 
152
 
    def do_chunk(self, chunk_bytes):
153
 
        """Called with each body chunk if the request has a streamed body.
154
 
 
155
 
        The do() method is still called, and must have returned None.
156
 
        """
157
 
        self._body_chunks.append(chunk_bytes)
158
 
 
159
 
    def do_end(self):
160
 
        """Called when the end of the request has been received."""
161
 
        body_bytes = ''.join(self._body_chunks)
162
 
        self._body_chunks = None
163
 
        return self.do_body(body_bytes)
164
 
 
165
 
    def setup_jail(self):
166
 
        jail_info.transports = [self._jail_root]
167
 
 
168
 
    def teardown_jail(self):
169
 
        jail_info.transports = None
170
 
 
171
 
    def translate_client_path(self, client_path):
172
 
        """Translate a path received from a network client into a local
173
 
        relpath.
174
 
 
175
 
        All paths received from the client *must* be translated.
176
 
 
177
 
        :param client_path: the path from the client.
178
 
        :returns: a relpath that may be used with self._backing_transport
179
 
            (unlike the untranslated client_path, which must not be used with
180
 
            the backing transport).
181
 
        """
182
 
        if self._root_client_path is None:
183
 
            # no translation necessary!
184
 
            return client_path
185
 
        if not client_path.startswith('/'):
186
 
            client_path = '/' + client_path
187
 
        if client_path + '/' == self._root_client_path:
188
 
            return '.'
189
 
        if client_path.startswith(self._root_client_path):
190
 
            path = client_path[len(self._root_client_path):]
191
 
            relpath = urlutils.joinpath('/', path)
192
 
            if not relpath.startswith('/'):
193
 
                raise ValueError(relpath)
194
 
            return urlutils.escape('.' + relpath)
195
 
        else:
196
 
            raise errors.PathNotChild(client_path, self._root_client_path)
197
 
 
198
 
    def transport_from_client_path(self, client_path):
199
 
        """Get a backing transport corresponding to the location referred to by
200
 
        a network client.
201
 
 
202
 
        :seealso: translate_client_path
203
 
        :returns: a transport cloned from self._backing_transport
204
 
        """
205
 
        relpath = self.translate_client_path(client_path)
206
 
        return self._backing_transport.clone(relpath)
 
27
from bzrlib.bundle.serializer import write_bundle
207
28
 
208
29
 
209
30
class SmartServerResponse(object):
210
 
    """A response to a client request.
211
 
 
212
 
    This base class should not be used. Instead use
213
 
    SuccessfulSmartServerResponse and FailedSmartServerResponse as appropriate.
214
 
    """
215
 
 
216
 
    def __init__(self, args, body=None, body_stream=None):
217
 
        """Constructor.
218
 
 
219
 
        :param args: tuple of response arguments.
220
 
        :param body: string of a response body.
221
 
        :param body_stream: iterable of bytestrings to be streamed to the
222
 
            client.
223
 
        """
 
31
    """Response generated by SmartServerRequestHandler."""
 
32
 
 
33
    def __init__(self, args, body=None):
224
34
        self.args = args
225
 
        if body is not None and body_stream is not None:
226
 
            raise errors.BzrError(
227
 
                "'body' and 'body_stream' are mutually exclusive.")
228
35
        self.body = body
229
 
        self.body_stream = body_stream
230
 
 
231
 
    def __eq__(self, other):
232
 
        if other is None:
233
 
            return False
234
 
        return (other.args == self.args and
235
 
                other.body == self.body and
236
 
                other.body_stream is self.body_stream)
237
 
 
238
 
    def __repr__(self):
239
 
        return "<%s args=%r body=%r>" % (self.__class__.__name__,
240
 
            self.args, self.body)
241
 
 
242
 
 
243
 
class FailedSmartServerResponse(SmartServerResponse):
244
 
    """A SmartServerResponse for a request which failed."""
245
 
 
246
 
    def is_successful(self):
247
 
        """FailedSmartServerResponse are not successful."""
248
 
        return False
249
 
 
250
 
 
251
 
class SuccessfulSmartServerResponse(SmartServerResponse):
252
 
    """A SmartServerResponse for a successfully completed request."""
253
 
 
254
 
    def is_successful(self):
255
 
        """SuccessfulSmartServerResponse are successful."""
256
 
        return True
257
 
 
 
36
 
 
37
# XXX: TODO: Create a SmartServerRequestHandler which will take the responsibility
 
38
# for delivering the data for a request. This could be done with as the
 
39
# StreamServer, though that would create conflation between request and response
 
40
# which may be undesirable.
258
41
 
259
42
class SmartServerRequestHandler(object):
260
43
    """Protocol logic for smart server.
261
 
 
 
44
    
262
45
    This doesn't handle serialization at all, it just processes requests and
263
46
    creates responses.
264
47
    """
272
55
 
273
56
    # TODO: Better way of representing the body for commands that take it,
274
57
    # and allow it to be streamed into the server.
275
 
 
276
 
    def __init__(self, backing_transport, commands, root_client_path,
277
 
        jail_root=None):
278
 
        """Constructor.
279
 
 
280
 
        :param backing_transport: a Transport to handle requests for.
281
 
        :param commands: a registry mapping command names to SmartServerRequest
282
 
            subclasses. e.g. bzrlib.transport.smart.vfs.vfs_commands.
283
 
        """
 
58
    
 
59
    def __init__(self, backing_transport):
284
60
        self._backing_transport = backing_transport
285
 
        self._root_client_path = root_client_path
286
 
        self._commands = commands
287
 
        if jail_root is None:
288
 
            jail_root = backing_transport
289
 
        self._jail_root = jail_root
 
61
        self._converted_command = False
 
62
        self.finished_reading = False
 
63
        self._body_bytes = ''
290
64
        self.response = None
291
 
        self.finished_reading = False
292
 
        self._command = None
293
 
        if 'hpss' in debug.debug_flags:
294
 
            self._request_start_time = osutils.timer_func()
295
 
            self._thread_id = thread.get_ident()
296
 
 
297
 
    def _trace(self, action, message, extra_bytes=None, include_time=False):
298
 
        # It is a bit of a shame that this functionality overlaps with that of 
299
 
        # ProtocolThreeRequester._trace. However, there is enough difference
300
 
        # that just putting it in a helper doesn't help a lot. And some state
301
 
        # is taken from the instance.
302
 
        if include_time:
303
 
            t = '%5.3fs ' % (osutils.timer_func() - self._request_start_time)
304
 
        else:
305
 
            t = ''
306
 
        if extra_bytes is None:
307
 
            extra = ''
308
 
        else:
309
 
            extra = ' ' + repr(extra_bytes[:40])
310
 
            if len(extra) > 33:
311
 
                extra = extra[:29] + extra[-1] + '...'
312
 
        trace.mutter('%12s: [%s] %s%s%s'
313
 
                     % (action, self._thread_id, t, message, extra))
314
65
 
315
66
    def accept_body(self, bytes):
316
 
        """Accept body data."""
317
 
        if self._command is None:
318
 
            # no active command object, so ignore the event.
319
 
            return
320
 
        self._run_handler_code(self._command.do_chunk, (bytes,), {})
321
 
        if 'hpss' in debug.debug_flags:
322
 
            self._trace('accept body',
323
 
                        '%d bytes' % (len(bytes),), bytes)
 
67
        """Accept body data.
 
68
 
 
69
        This should be overriden for each command that desired body data to
 
70
        handle the right format of that data. I.e. plain bytes, a bundle etc.
 
71
 
 
72
        The deserialisation into that format should be done in the Protocol
 
73
        object. Set self.desired_body_format to the format your method will
 
74
        handle.
 
75
        """
 
76
        # default fallback is to accumulate bytes.
 
77
        self._body_bytes += bytes
 
78
        
 
79
    def _end_of_body_handler(self):
 
80
        """An unimplemented end of body handler."""
 
81
        raise NotImplementedError(self._end_of_body_handler)
 
82
        
 
83
    def do_hello(self):
 
84
        """Answer a version request with my version."""
 
85
        return SmartServerResponse(('ok', '1'))
 
86
 
 
87
    def do_has(self, relpath):
 
88
        r = self._backing_transport.has(relpath) and 'yes' or 'no'
 
89
        return SmartServerResponse((r,))
 
90
 
 
91
    def do_get(self, relpath):
 
92
        backing_bytes = self._backing_transport.get_bytes(relpath)
 
93
        return SmartServerResponse(('ok',), backing_bytes)
 
94
 
 
95
    def _deserialise_optional_mode(self, mode):
 
96
        # XXX: FIXME this should be on the protocol object.
 
97
        if mode == '':
 
98
            return None
 
99
        else:
 
100
            return int(mode)
 
101
 
 
102
    def do_append(self, relpath, mode):
 
103
        self._converted_command = True
 
104
        self._relpath = relpath
 
105
        self._mode = self._deserialise_optional_mode(mode)
 
106
        self._end_of_body_handler = self._handle_do_append_end
 
107
    
 
108
    def _handle_do_append_end(self):
 
109
        old_length = self._backing_transport.append_bytes(
 
110
            self._relpath, self._body_bytes, self._mode)
 
111
        self.response = SmartServerResponse(('appended', '%d' % old_length))
 
112
 
 
113
    def do_delete(self, relpath):
 
114
        self._backing_transport.delete(relpath)
 
115
 
 
116
    def do_iter_files_recursive(self, relpath):
 
117
        transport = self._backing_transport.clone(relpath)
 
118
        filenames = transport.iter_files_recursive()
 
119
        return SmartServerResponse(('names',) + tuple(filenames))
 
120
 
 
121
    def do_list_dir(self, relpath):
 
122
        filenames = self._backing_transport.list_dir(relpath)
 
123
        return SmartServerResponse(('names',) + tuple(filenames))
 
124
 
 
125
    def do_mkdir(self, relpath, mode):
 
126
        self._backing_transport.mkdir(relpath,
 
127
                                      self._deserialise_optional_mode(mode))
 
128
 
 
129
    def do_move(self, rel_from, rel_to):
 
130
        self._backing_transport.move(rel_from, rel_to)
 
131
 
 
132
    def do_put(self, relpath, mode):
 
133
        self._converted_command = True
 
134
        self._relpath = relpath
 
135
        self._mode = self._deserialise_optional_mode(mode)
 
136
        self._end_of_body_handler = self._handle_do_put
 
137
 
 
138
    def _handle_do_put(self):
 
139
        self._backing_transport.put_bytes(self._relpath,
 
140
                self._body_bytes, self._mode)
 
141
        self.response = SmartServerResponse(('ok',))
 
142
 
 
143
    def _deserialise_offsets(self, text):
 
144
        # XXX: FIXME this should be on the protocol object.
 
145
        offsets = []
 
146
        for line in text.split('\n'):
 
147
            if not line:
 
148
                continue
 
149
            start, length = line.split(',')
 
150
            offsets.append((int(start), int(length)))
 
151
        return offsets
 
152
 
 
153
    def do_put_non_atomic(self, relpath, mode, create_parent, dir_mode):
 
154
        self._converted_command = True
 
155
        self._end_of_body_handler = self._handle_put_non_atomic
 
156
        self._relpath = relpath
 
157
        self._dir_mode = self._deserialise_optional_mode(dir_mode)
 
158
        self._mode = self._deserialise_optional_mode(mode)
 
159
        # a boolean would be nicer XXX
 
160
        self._create_parent = (create_parent == 'T')
 
161
 
 
162
    def _handle_put_non_atomic(self):
 
163
        self._backing_transport.put_bytes_non_atomic(self._relpath,
 
164
                self._body_bytes,
 
165
                mode=self._mode,
 
166
                create_parent_dir=self._create_parent,
 
167
                dir_mode=self._dir_mode)
 
168
        self.response = SmartServerResponse(('ok',))
 
169
 
 
170
    def do_readv(self, relpath):
 
171
        self._converted_command = True
 
172
        self._end_of_body_handler = self._handle_readv_offsets
 
173
        self._relpath = relpath
324
174
 
325
175
    def end_of_body(self):
326
176
        """No more body data will be received."""
327
 
        self._run_handler_code(self._command.do_end, (), {})
 
177
        self._run_handler_code(self._end_of_body_handler, (), {})
328
178
        # cannot read after this.
329
179
        self.finished_reading = True
330
 
        if 'hpss' in debug.debug_flags:
331
 
            self._trace('end of body', '', include_time=True)
 
180
 
 
181
    def _handle_readv_offsets(self):
 
182
        """accept offsets for a readv request."""
 
183
        offsets = self._deserialise_offsets(self._body_bytes)
 
184
        backing_bytes = ''.join(bytes for offset, bytes in
 
185
            self._backing_transport.readv(self._relpath, offsets))
 
186
        self.response = SmartServerResponse(('readv',), backing_bytes)
 
187
        
 
188
    def do_rename(self, rel_from, rel_to):
 
189
        self._backing_transport.rename(rel_from, rel_to)
 
190
 
 
191
    def do_rmdir(self, relpath):
 
192
        self._backing_transport.rmdir(relpath)
 
193
 
 
194
    def do_stat(self, relpath):
 
195
        stat = self._backing_transport.stat(relpath)
 
196
        return SmartServerResponse(('stat', str(stat.st_size), oct(stat.st_mode)))
 
197
        
 
198
    def do_get_bundle(self, path, revision_id):
 
199
        # open transport relative to our base
 
200
        t = self._backing_transport.clone(path)
 
201
        control, extra_path = bzrdir.BzrDir.open_containing_from_transport(t)
 
202
        repo = control.open_repository()
 
203
        tmpf = tempfile.TemporaryFile()
 
204
        base_revision = revision.NULL_REVISION
 
205
        write_bundle(repo, revision_id, base_revision, tmpf)
 
206
        tmpf.seek(0)
 
207
        return SmartServerResponse((), tmpf.read())
 
208
 
 
209
    def dispatch_command(self, cmd, args):
 
210
        """Deprecated compatibility method.""" # XXX XXX
 
211
        func = getattr(self, 'do_' + cmd, None)
 
212
        if func is None:
 
213
            raise errors.SmartProtocolError("bad request %r" % (cmd,))
 
214
        self._run_handler_code(func, args, {})
332
215
 
333
216
    def _run_handler_code(self, callable, args, kwargs):
334
217
        """Run some handler specific code 'callable'.
340
223
        from them.
341
224
        """
342
225
        result = self._call_converting_errors(callable, args, kwargs)
343
 
 
344
226
        if result is not None:
345
227
            self.response = result
346
228
            self.finished_reading = True
 
229
        # handle unconverted commands
 
230
        if not self._converted_command:
 
231
            self.finished_reading = True
 
232
            if result is None:
 
233
                self.response = SmartServerResponse(('ok',))
347
234
 
348
235
    def _call_converting_errors(self, callable, args, kwargs):
349
236
        """Call callable converting errors to Response objects."""
350
 
        # XXX: most of this error conversion is VFS-related, and thus ought to
351
 
        # be in SmartServerVFSRequestHandler somewhere.
352
 
        try:
353
 
            self._command.setup_jail()
354
 
            try:
355
 
                return callable(*args, **kwargs)
356
 
            finally:
357
 
                self._command.teardown_jail()
358
 
        except (KeyboardInterrupt, SystemExit):
359
 
            raise
360
 
        except Exception, err:
361
 
            err_struct = _translate_error(err)
362
 
            return FailedSmartServerResponse(err_struct)
363
 
 
364
 
    def headers_received(self, headers):
365
 
        # Just a no-op at the moment.
366
 
        if 'hpss' in debug.debug_flags:
367
 
            self._trace('headers', repr(headers))
368
 
 
369
 
    def args_received(self, args):
370
 
        cmd = args[0]
371
 
        args = args[1:]
372
 
        try:
373
 
            command = self._commands.get(cmd)
374
 
        except LookupError:
375
 
            if 'hpss' in debug.debug_flags:
376
 
                self._trace('hpss unknown request', 
377
 
                            cmd, repr(args)[1:-1])
378
 
            raise errors.UnknownSmartMethod(cmd)
379
 
        if 'hpss' in debug.debug_flags:
380
 
            from bzrlib.smart import vfs
381
 
            if issubclass(command, vfs.VfsRequest):
382
 
                action = 'hpss vfs req'
383
 
            else:
384
 
                action = 'hpss request'
385
 
            self._trace(action, 
386
 
                        '%s %s' % (cmd, repr(args)[1:-1]))
387
 
        self._command = command(
388
 
            self._backing_transport, self._root_client_path, self._jail_root)
389
 
        self._run_handler_code(self._command.execute, args, {})
390
 
 
391
 
    def end_received(self):
392
 
        if self._command is None:
393
 
            # no active command object, so ignore the event.
394
 
            return
395
 
        self._run_handler_code(self._command.do_end, (), {})
396
 
        if 'hpss' in debug.debug_flags:
397
 
            self._trace('end', '', include_time=True)
398
 
 
399
 
    def post_body_error_received(self, error_args):
400
 
        # Just a no-op at the moment.
401
 
        pass
402
 
 
403
 
 
404
 
def _translate_error(err):
405
 
    if isinstance(err, errors.NoSuchFile):
406
 
        return ('NoSuchFile', err.path)
407
 
    elif isinstance(err, errors.FileExists):
408
 
        return ('FileExists', err.path)
409
 
    elif isinstance(err, errors.DirectoryNotEmpty):
410
 
        return ('DirectoryNotEmpty', err.path)
411
 
    elif isinstance(err, errors.IncompatibleRepositories):
412
 
        return ('IncompatibleRepositories', str(err.source), str(err.target),
413
 
            str(err.details))
414
 
    elif isinstance(err, errors.ShortReadvError):
415
 
        return ('ShortReadvError', err.path, str(err.offset), str(err.length),
416
 
                str(err.actual))
417
 
    elif isinstance(err, errors.UnstackableRepositoryFormat):
418
 
        return (('UnstackableRepositoryFormat', str(err.format), err.url))
419
 
    elif isinstance(err, errors.UnstackableBranchFormat):
420
 
        return ('UnstackableBranchFormat', str(err.format), err.url)
421
 
    elif isinstance(err, errors.NotStacked):
422
 
        return ('NotStacked',)
423
 
    elif isinstance(err, UnicodeError):
424
 
        # If it is a DecodeError, than most likely we are starting
425
 
        # with a plain string
426
 
        str_or_unicode = err.object
427
 
        if isinstance(str_or_unicode, unicode):
428
 
            # XXX: UTF-8 might have \x01 (our protocol v1 and v2 seperator
429
 
            # byte) in it, so this encoding could cause broken responses.
430
 
            # Newer clients use protocol v3, so will be fine.
431
 
            val = 'u:' + str_or_unicode.encode('utf-8')
432
 
        else:
433
 
            val = 's:' + str_or_unicode.encode('base64')
434
 
        # This handles UnicodeEncodeError or UnicodeDecodeError
435
 
        return (err.__class__.__name__, err.encoding, val, str(err.start),
436
 
                str(err.end), err.reason)
437
 
    elif isinstance(err, errors.TransportNotPossible):
438
 
        if err.msg == "readonly transport":
439
 
            return ('ReadOnlyError', )
440
 
    elif isinstance(err, errors.ReadError):
441
 
        # cannot read the file
442
 
        return ('ReadError', err.path)
443
 
    elif isinstance(err, errors.PermissionDenied):
444
 
        return ('PermissionDenied', err.path, err.extra)
445
 
    elif isinstance(err, errors.TokenMismatch):
446
 
        return ('TokenMismatch', err.given_token, err.lock_token)
447
 
    elif isinstance(err, errors.LockContention):
448
 
        return ('LockContention',)
449
 
    # Unserialisable error.  Log it, and return a generic error
450
 
    trace.log_exception_quietly()
451
 
    return ('error', str(err))
452
 
 
453
 
 
454
 
class HelloRequest(SmartServerRequest):
455
 
    """Answer a version request with the highest protocol version this server
456
 
    supports.
457
 
    """
458
 
 
459
 
    def do(self):
460
 
        return SuccessfulSmartServerResponse(('ok', '2'))
461
 
 
462
 
 
463
 
class GetBundleRequest(SmartServerRequest):
464
 
    """Get a bundle of from the null revision to the specified revision."""
465
 
 
466
 
    def do(self, path, revision_id):
467
 
        # open transport relative to our base
468
 
        t = self.transport_from_client_path(path)
469
 
        control, extra_path = bzrdir.BzrDir.open_containing_from_transport(t)
470
 
        repo = control.open_repository()
471
 
        tmpf = tempfile.TemporaryFile()
472
 
        base_revision = revision.NULL_REVISION
473
 
        serializer.write_bundle(repo, revision_id, base_revision, tmpf)
474
 
        tmpf.seek(0)
475
 
        return SuccessfulSmartServerResponse((), tmpf.read())
476
 
 
477
 
 
478
 
class SmartServerIsReadonly(SmartServerRequest):
479
 
    # XXX: this request method belongs somewhere else.
480
 
 
481
 
    def do(self):
482
 
        if self._backing_transport.is_readonly():
483
 
            answer = 'yes'
484
 
        else:
485
 
            answer = 'no'
486
 
        return SuccessfulSmartServerResponse((answer,))
487
 
 
488
 
 
489
 
request_handlers = registry.Registry()
490
 
request_handlers.register_lazy(
491
 
    'append', 'bzrlib.smart.vfs', 'AppendRequest')
492
 
request_handlers.register_lazy(
493
 
    'Branch.get_config_file', 'bzrlib.smart.branch',
494
 
    'SmartServerBranchGetConfigFile')
495
 
request_handlers.register_lazy(
496
 
    'Branch.get_parent', 'bzrlib.smart.branch', 'SmartServerBranchGetParent')
497
 
request_handlers.register_lazy(
498
 
    'Branch.get_tags_bytes', 'bzrlib.smart.branch',
499
 
    'SmartServerBranchGetTagsBytes')
500
 
request_handlers.register_lazy(
501
 
    'Branch.set_tags_bytes', 'bzrlib.smart.branch',
502
 
    'SmartServerBranchSetTagsBytes')
503
 
request_handlers.register_lazy(
504
 
    'Branch.get_stacked_on_url', 'bzrlib.smart.branch', 'SmartServerBranchRequestGetStackedOnURL')
505
 
request_handlers.register_lazy(
506
 
    'Branch.last_revision_info', 'bzrlib.smart.branch', 'SmartServerBranchRequestLastRevisionInfo')
507
 
request_handlers.register_lazy(
508
 
    'Branch.lock_write', 'bzrlib.smart.branch', 'SmartServerBranchRequestLockWrite')
509
 
request_handlers.register_lazy( 'Branch.revision_history',
510
 
    'bzrlib.smart.branch', 'SmartServerRequestRevisionHistory')
511
 
request_handlers.register_lazy( 'Branch.set_config_option',
512
 
    'bzrlib.smart.branch', 'SmartServerBranchRequestSetConfigOption')
513
 
request_handlers.register_lazy( 'Branch.set_last_revision',
514
 
    'bzrlib.smart.branch', 'SmartServerBranchRequestSetLastRevision')
515
 
request_handlers.register_lazy(
516
 
    'Branch.set_last_revision_info', 'bzrlib.smart.branch',
517
 
    'SmartServerBranchRequestSetLastRevisionInfo')
518
 
request_handlers.register_lazy(
519
 
    'Branch.set_last_revision_ex', 'bzrlib.smart.branch',
520
 
    'SmartServerBranchRequestSetLastRevisionEx')
521
 
request_handlers.register_lazy(
522
 
    'Branch.set_parent_location', 'bzrlib.smart.branch',
523
 
    'SmartServerBranchRequestSetParentLocation')
524
 
request_handlers.register_lazy(
525
 
    'Branch.unlock', 'bzrlib.smart.branch', 'SmartServerBranchRequestUnlock')
526
 
request_handlers.register_lazy(
527
 
    'BzrDir.cloning_metadir', 'bzrlib.smart.bzrdir',
528
 
    'SmartServerBzrDirRequestCloningMetaDir')
529
 
request_handlers.register_lazy(
530
 
    'BzrDir.create_branch', 'bzrlib.smart.bzrdir',
531
 
    'SmartServerRequestCreateBranch')
532
 
request_handlers.register_lazy(
533
 
    'BzrDir.create_repository', 'bzrlib.smart.bzrdir',
534
 
    'SmartServerRequestCreateRepository')
535
 
request_handlers.register_lazy(
536
 
    'BzrDir.find_repository', 'bzrlib.smart.bzrdir',
537
 
    'SmartServerRequestFindRepositoryV1')
538
 
request_handlers.register_lazy(
539
 
    'BzrDir.find_repositoryV2', 'bzrlib.smart.bzrdir',
540
 
    'SmartServerRequestFindRepositoryV2')
541
 
request_handlers.register_lazy(
542
 
    'BzrDir.find_repositoryV3', 'bzrlib.smart.bzrdir',
543
 
    'SmartServerRequestFindRepositoryV3')
544
 
request_handlers.register_lazy(
545
 
    'BzrDir.get_config_file', 'bzrlib.smart.bzrdir',
546
 
    'SmartServerBzrDirRequestConfigFile')
547
 
request_handlers.register_lazy(
548
 
    'BzrDirFormat.initialize', 'bzrlib.smart.bzrdir',
549
 
    'SmartServerRequestInitializeBzrDir')
550
 
request_handlers.register_lazy(
551
 
    'BzrDirFormat.initialize_ex_1.16', 'bzrlib.smart.bzrdir',
552
 
    'SmartServerRequestBzrDirInitializeEx')
553
 
request_handlers.register_lazy(
554
 
    'BzrDir.open', 'bzrlib.smart.bzrdir', 'SmartServerRequestOpenBzrDir')
555
 
request_handlers.register_lazy(
556
 
    'BzrDir.open_2.1', 'bzrlib.smart.bzrdir', 'SmartServerRequestOpenBzrDir_2_1')
557
 
request_handlers.register_lazy(
558
 
    'BzrDir.open_branch', 'bzrlib.smart.bzrdir',
559
 
    'SmartServerRequestOpenBranch')
560
 
request_handlers.register_lazy(
561
 
    'BzrDir.open_branchV2', 'bzrlib.smart.bzrdir',
562
 
    'SmartServerRequestOpenBranchV2')
563
 
request_handlers.register_lazy(
564
 
    'BzrDir.open_branchV3', 'bzrlib.smart.bzrdir',
565
 
    'SmartServerRequestOpenBranchV3')
566
 
request_handlers.register_lazy(
567
 
    'delete', 'bzrlib.smart.vfs', 'DeleteRequest')
568
 
request_handlers.register_lazy(
569
 
    'get', 'bzrlib.smart.vfs', 'GetRequest')
570
 
request_handlers.register_lazy(
571
 
    'get_bundle', 'bzrlib.smart.request', 'GetBundleRequest')
572
 
request_handlers.register_lazy(
573
 
    'has', 'bzrlib.smart.vfs', 'HasRequest')
574
 
request_handlers.register_lazy(
575
 
    'hello', 'bzrlib.smart.request', 'HelloRequest')
576
 
request_handlers.register_lazy(
577
 
    'iter_files_recursive', 'bzrlib.smart.vfs', 'IterFilesRecursiveRequest')
578
 
request_handlers.register_lazy(
579
 
    'list_dir', 'bzrlib.smart.vfs', 'ListDirRequest')
580
 
request_handlers.register_lazy(
581
 
    'mkdir', 'bzrlib.smart.vfs', 'MkdirRequest')
582
 
request_handlers.register_lazy(
583
 
    'move', 'bzrlib.smart.vfs', 'MoveRequest')
584
 
request_handlers.register_lazy(
585
 
    'put', 'bzrlib.smart.vfs', 'PutRequest')
586
 
request_handlers.register_lazy(
587
 
    'put_non_atomic', 'bzrlib.smart.vfs', 'PutNonAtomicRequest')
588
 
request_handlers.register_lazy(
589
 
    'readv', 'bzrlib.smart.vfs', 'ReadvRequest')
590
 
request_handlers.register_lazy(
591
 
    'rename', 'bzrlib.smart.vfs', 'RenameRequest')
592
 
request_handlers.register_lazy(
593
 
    'PackRepository.autopack', 'bzrlib.smart.packrepository',
594
 
    'SmartServerPackRepositoryAutopack')
595
 
request_handlers.register_lazy('Repository.gather_stats',
596
 
                               'bzrlib.smart.repository',
597
 
                               'SmartServerRepositoryGatherStats')
598
 
request_handlers.register_lazy('Repository.get_parent_map',
599
 
                               'bzrlib.smart.repository',
600
 
                               'SmartServerRepositoryGetParentMap')
601
 
request_handlers.register_lazy(
602
 
    'Repository.get_revision_graph', 'bzrlib.smart.repository', 'SmartServerRepositoryGetRevisionGraph')
603
 
request_handlers.register_lazy(
604
 
    'Repository.has_revision', 'bzrlib.smart.repository', 'SmartServerRequestHasRevision')
605
 
request_handlers.register_lazy(
606
 
    'Repository.insert_stream', 'bzrlib.smart.repository', 'SmartServerRepositoryInsertStream')
607
 
request_handlers.register_lazy(
608
 
    'Repository.insert_stream_1.19', 'bzrlib.smart.repository', 'SmartServerRepositoryInsertStream_1_19')
609
 
request_handlers.register_lazy(
610
 
    'Repository.insert_stream_locked', 'bzrlib.smart.repository', 'SmartServerRepositoryInsertStreamLocked')
611
 
request_handlers.register_lazy(
612
 
    'Repository.is_shared', 'bzrlib.smart.repository', 'SmartServerRepositoryIsShared')
613
 
request_handlers.register_lazy(
614
 
    'Repository.lock_write', 'bzrlib.smart.repository', 'SmartServerRepositoryLockWrite')
615
 
request_handlers.register_lazy(
616
 
    'Repository.set_make_working_trees', 'bzrlib.smart.repository',
617
 
    'SmartServerRepositorySetMakeWorkingTrees')
618
 
request_handlers.register_lazy(
619
 
    'Repository.unlock', 'bzrlib.smart.repository', 'SmartServerRepositoryUnlock')
620
 
request_handlers.register_lazy(
621
 
    'Repository.get_rev_id_for_revno', 'bzrlib.smart.repository',
622
 
    'SmartServerRepositoryGetRevIdForRevno')
623
 
request_handlers.register_lazy(
624
 
    'Repository.get_stream', 'bzrlib.smart.repository',
625
 
    'SmartServerRepositoryGetStream')
626
 
request_handlers.register_lazy(
627
 
    'Repository.get_stream_1.19', 'bzrlib.smart.repository',
628
 
    'SmartServerRepositoryGetStream_1_19')
629
 
request_handlers.register_lazy(
630
 
    'Repository.tarball', 'bzrlib.smart.repository',
631
 
    'SmartServerRepositoryTarball')
632
 
request_handlers.register_lazy(
633
 
    'rmdir', 'bzrlib.smart.vfs', 'RmdirRequest')
634
 
request_handlers.register_lazy(
635
 
    'stat', 'bzrlib.smart.vfs', 'StatRequest')
636
 
request_handlers.register_lazy(
637
 
    'Transport.is_readonly', 'bzrlib.smart.request', 'SmartServerIsReadonly')
 
237
        try:
 
238
            return callable(*args, **kwargs)
 
239
        except errors.NoSuchFile, e:
 
240
            return SmartServerResponse(('NoSuchFile', e.path))
 
241
        except errors.FileExists, e:
 
242
            return SmartServerResponse(('FileExists', e.path))
 
243
        except errors.DirectoryNotEmpty, e:
 
244
            return SmartServerResponse(('DirectoryNotEmpty', e.path))
 
245
        except errors.ShortReadvError, e:
 
246
            return SmartServerResponse(('ShortReadvError',
 
247
                e.path, str(e.offset), str(e.length), str(e.actual)))
 
248
        except UnicodeError, e:
 
249
            # If it is a DecodeError, than most likely we are starting
 
250
            # with a plain string
 
251
            str_or_unicode = e.object
 
252
            if isinstance(str_or_unicode, unicode):
 
253
                # XXX: UTF-8 might have \x01 (our seperator byte) in it.  We
 
254
                # should escape it somehow.
 
255
                val = 'u:' + str_or_unicode.encode('utf-8')
 
256
            else:
 
257
                val = 's:' + str_or_unicode.encode('base64')
 
258
            # This handles UnicodeEncodeError or UnicodeDecodeError
 
259
            return SmartServerResponse((e.__class__.__name__,
 
260
                    e.encoding, val, str(e.start), str(e.end), e.reason))
 
261
        except errors.TransportNotPossible, e:
 
262
            if e.msg == "readonly transport":
 
263
                return SmartServerResponse(('ReadOnlyError', ))
 
264
            else:
 
265
                raise
 
266
 
 
267