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