1##############################################################################
2#
3# Copyright (c) 2001, 2002 Zope Foundation and Contributors.
4# All Rights Reserved.
5#
6# This software is subject to the provisions of the Zope Public License,
7# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
8# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
9# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
10# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
11# FOR A PARTICULAR PURPOSE.
12#
13##############################################################################
14"""Base implementations of the Publisher objects
15
16Specifically, 'BaseRequest', 'BaseResponse', and 'DefaultPublication' are
17specified here.
18"""
19from cStringIO import StringIO
20
21from zope.interface import implements, providedBy
22from zope.interface.common.mapping import IReadMapping, IEnumerableMapping
23from zope.exceptions.exceptionformatter import print_exception
24from zope.security.proxy import removeSecurityProxy
25
26from zope.publisher.interfaces import IPublication, IHeld
27from zope.publisher.interfaces import NotFound, DebugError, Unauthorized
28from zope.publisher.interfaces import IRequest, IResponse, IDebugFlags
29from zope.publisher.publish import mapply
30
31_marker = object()
32
33class BaseResponse(object):
34    """Base Response Class
35    """
36
37    __slots__ = (
38        '_result',    # The result of the application call
39        '_request',   # The associated request (if any)
40        )
41
42    implements(IResponse)
43
44    def __init__(self):
45        self._request = None
46
47    def setResult(self, result):
48        'See IPublisherResponse'
49        self._result = result
50
51    def handleException(self, exc_info):
52        'See IPublisherResponse'
53        f = StringIO()
54        print_exception(
55            exc_info[0], exc_info[1], exc_info[2], 100, f)
56        self.setResult(f.getvalue())
57
58    def internalError(self):
59        'See IPublisherResponse'
60        pass
61
62    def reset(self):
63        'See IPublisherResponse'
64        pass
65
66    def retry(self):
67        'See IPublisherResponse'
68        return self.__class__()
69
70class RequestDataGetter(object):
71
72    implements(IReadMapping)
73
74    def __init__(self, request):
75        self.__get = getattr(request, self._gettrname)
76
77    def __getitem__(self, name):
78        return self.__get(name)
79
80    def get(self, name, default=None):
81        return self.__get(name, default)
82
83    def __contains__(self, key):
84        lookup = self.get(key, self)
85        return lookup is not self
86
87    has_key = __contains__
88
89class RequestDataMapper(object):
90
91    implements(IEnumerableMapping)
92
93    def __init__(self, request):
94        self.__map = getattr(request, self._mapname)
95
96    def __getitem__(self, name):
97        return self.__map[name]
98
99    def get(self, name, default=None):
100        return self.__map.get(name, default)
101
102    def __contains__(self, key):
103        lookup = self.get(key, self)
104        return lookup is not self
105
106    has_key = __contains__
107
108    def keys(self):
109        return self.__map.keys()
110
111    def __iter__(self):
112        return iter(self.keys())
113
114    def items(self):
115        return self.__map.items()
116
117    def values(self):
118        return self.__map.values()
119
120    def __len__(self):
121        return len(self.__map)
122
123class RequestDataProperty(object):
124
125    def __init__(self, gettr_class):
126        self.__gettr_class = gettr_class
127
128    def __get__(self, request, rclass=None):
129        if request is not None:
130            return self.__gettr_class(request)
131
132    def __set__(*args):
133        raise AttributeError('Unassignable attribute')
134
135
136class RequestEnvironment(RequestDataMapper):
137    _mapname = '_environ'
138
139
140class DebugFlags(object):
141    """Debugging flags."""
142
143    implements(IDebugFlags)
144
145    sourceAnnotations = False
146    showTAL = False
147
148
149class BaseRequest(object):
150    """Represents a publishing request.
151
152    This object provides access to request data. Request data may
153    vary depending on the protocol used.
154
155    Request objects are created by the object publisher and will be
156    passed to published objects through the argument name, REQUEST.
157
158    The request object is a mapping object that represents a
159    collection of variable to value mappings.
160    """
161
162    implements(IRequest)
163
164    __slots__ = (
165        '__provides__',      # Allow request to directly provide interfaces
166        '_held',             # Objects held until the request is closed
167        '_traversed_names',  # The names that have been traversed
168        '_last_obj_traversed', # Object that was traversed last
169        '_traversal_stack',  # Names to be traversed, in reverse order
170        '_environ',          # The request environment variables
171        '_response',         # The response
172        '_args',             # positional arguments
173        '_body_instream',    # input stream
174        '_body',             # The request body as a string
175        '_publication',      # publication object
176        '_principal',        # request principal, set by publication
177        'interaction',       # interaction, set by interaction
178        'debug',             # debug flags
179        'annotations',       # per-package annotations
180        )
181
182    environment = RequestDataProperty(RequestEnvironment)
183
184    def __init__(self, body_instream, environ, response=None,
185                 positional=None):
186        self._traversal_stack = []
187        self._last_obj_traversed = None
188        self._traversed_names = []
189        self._environ = environ
190
191        self._args = positional or ()
192
193        if response is None:
194            self._response = self._createResponse()
195        else:
196            self._response = response
197
198        self._response._request = self
199
200        self._body_instream = body_instream
201        self._held = ()
202        self._principal = None
203        self.debug = DebugFlags()
204        self.interaction = None
205        self.annotations = {}
206
207    def setPrincipal(self, principal):
208        self._principal = principal
209
210    principal = property(lambda self: self._principal)
211
212    def _getPublication(self):
213        'See IPublisherRequest'
214        return getattr(self, '_publication', None)
215
216    publication = property(_getPublication)
217
218    def processInputs(self):
219        'See IPublisherRequest'
220        # Nothing to do here
221
222    def retry(self):
223        'See IPublisherRequest'
224        raise TypeError('Retry is not supported')
225
226    def setPublication(self, pub):
227        'See IPublisherRequest'
228        self._publication = pub
229
230    def supportsRetry(self):
231        'See IPublisherRequest'
232        return 0
233
234    def traverse(self, obj):
235        'See IPublisherRequest'
236
237        publication = self.publication
238
239        traversal_stack = self._traversal_stack
240        traversed_names = self._traversed_names
241
242        prev_object = None
243        while True:
244
245            self._last_obj_traversed = obj
246
247            if removeSecurityProxy(obj) is not removeSecurityProxy(prev_object):
248                # Invoke hooks (but not more than once).
249                publication.callTraversalHooks(self, obj)
250
251            if not traversal_stack:
252                # Finished traversal.
253                break
254
255            prev_object = obj
256
257            # Traverse to the next step.
258            entry_name = traversal_stack.pop()
259            traversed_names.append(entry_name)
260            obj = publication.traverseName(self, obj, entry_name)
261
262        return obj
263
264    def close(self):
265        'See IPublicationRequest'
266
267        for held in self._held:
268            if IHeld.providedBy(held):
269                held.release()
270
271        self._held = None
272        self._body_instream = None
273        self._publication = None
274
275    def getPositionalArguments(self):
276        'See IPublicationRequest'
277        return self._args
278
279    def _getResponse(self):
280        return self._response
281
282    response = property(_getResponse)
283
284    def getTraversalStack(self):
285        'See IPublicationRequest'
286        return list(self._traversal_stack) # Return a copy
287
288    def hold(self, object):
289        'See IPublicationRequest'
290        self._held = self._held + (object,)
291
292    def setTraversalStack(self, stack):
293        'See IPublicationRequest'
294        self._traversal_stack[:] = list(stack)
295
296    def _getBodyStream(self):
297        'See zope.publisher.interfaces.IApplicationRequest'
298        return self._body_instream
299
300    bodyStream = property(_getBodyStream)
301
302    def __len__(self):
303        'See Interface.Common.Mapping.IEnumerableMapping'
304        return len(self.keys())
305
306    def items(self):
307        'See Interface.Common.Mapping.IEnumerableMapping'
308        result = []
309        get = self.get
310        for k in self.keys():
311            result.append((k, get(k)))
312        return result
313
314    def keys(self):
315        'See Interface.Common.Mapping.IEnumerableMapping'
316        return self._environ.keys()
317
318    def __iter__(self):
319        return iter(self.keys())
320
321    def values(self):
322        'See Interface.Common.Mapping.IEnumerableMapping'
323        result = []
324        get = self.get
325        for k in self.keys():
326            result.append(get(k))
327        return result
328
329    def __getitem__(self, key):
330        'See Interface.Common.Mapping.IReadMapping'
331        result = self.get(key, _marker)
332        if result is _marker:
333            raise KeyError(key)
334        else:
335            return result
336
337    def get(self, key, default=None):
338        'See Interface.Common.Mapping.IReadMapping'
339        result = self._environ.get(key, _marker)
340        if result is not _marker:
341            return result
342
343        return default
344
345    def __contains__(self, key):
346        'See Interface.Common.Mapping.IReadMapping'
347        lookup = self.get(key, self)
348        return lookup is not self
349
350    has_key = __contains__
351
352    def _createResponse(self):
353        # Should be overridden by subclasses
354        return BaseResponse()
355
356    def __nonzero__(self):
357        # This is here to avoid calling __len__ for boolean tests
358        return 1
359
360    def __str__(self):
361        L1 = self.items()
362        L1.sort()
363        return "\n".join(map(lambda item: "%s:\t%s" % item, L1))
364
365    def _setupPath_helper(self, attr):
366        path = self.get(attr, "/")
367        if path.endswith('/'):
368            # Remove trailing backslash, so that we will not get an empty
369            # last entry when splitting the path.
370            path = path[:-1]
371            self._endswithslash = True
372        else:
373            self._endswithslash = False
374
375        clean = []
376        for item in path.split('/'):
377            if not item or item == '.':
378                continue
379            elif item == '..':
380                # try to remove the last name
381                try:
382                    del clean[-1]
383                except IndexError:
384                    # the list of names was empty, so do nothing and let the
385                    # string '..' be placed on the list
386                    pass
387            clean.append(item)
388
389        clean.reverse()
390        self.setTraversalStack(clean)
391
392        self._path_suffix = None
393
394class TestRequest(BaseRequest):
395
396    __slots__ = ('_presentation_type', )
397
398    def __init__(self, path, body_instream=None, environ=None):
399
400        if environ is None:
401            environ = {}
402
403        environ['PATH_INFO'] = path
404        if body_instream is None:
405            body_instream = StringIO('')
406
407        super(TestRequest, self).__init__(body_instream, environ)
408
409class DefaultPublication(object):
410    """A stub publication.
411
412    This works just like Zope2's ZPublisher. It rejects any name
413    starting with an underscore and any objects (specifically: method)
414    that doesn't have a docstring.
415    """
416    implements(IPublication)
417
418    require_docstrings = True
419
420    def __init__(self, app):
421        self.app = app
422
423    def beforeTraversal(self, request):
424        # Lop off leading and trailing empty names
425        stack = request.getTraversalStack()
426        while stack and not stack[-1]:
427            stack.pop() # toss a trailing empty name
428        while stack and not stack[0]:
429            stack.pop(0) # toss a leading empty name
430        request.setTraversalStack(stack)
431
432    def getApplication(self, request):
433        return self.app
434
435    def callTraversalHooks(self, request, ob):
436        pass
437
438    def traverseName(self, request, ob, name, check_auth=1):
439        if name.startswith('_'):
440            raise Unauthorized(name)
441        if hasattr(ob, name):
442            subob = getattr(ob, name)
443        else:
444            try:
445                subob = ob[name]
446            except (KeyError, IndexError,
447                    TypeError, AttributeError):
448                raise NotFound(ob, name, request)
449        if self.require_docstrings and not getattr(subob, '__doc__', None):
450            raise DebugError(subob, 'Missing or empty doc string')
451        return subob
452
453    def getDefaultTraversal(self, request, ob):
454        return ob, ()
455
456    def afterTraversal(self, request, ob):
457        pass
458
459    def callObject(self, request, ob):
460        return mapply(ob, request.getPositionalArguments(), request)
461
462    def afterCall(self, request, ob):
463        pass
464
465    def endRequest(self, request, ob):
466        pass
467
468    def handleException(self, object, request, exc_info, retry_allowed=1):
469        # Let the response handle it as best it can.
470        request.response.reset()
471        request.response.handleException(exc_info)
472
473
474class TestPublication(DefaultPublication):
475
476    def traverseName(self, request, ob, name, check_auth=1):
477        if hasattr(ob, name):
478            subob = getattr(ob, name)
479        else:
480            try:
481                subob = ob[name]
482            except (KeyError, IndexError,
483                    TypeError, AttributeError):
484                raise NotFound(ob, name, request)
485        return subob
486