Changeset 4087
- Timestamp:
- 10/30/06 16:07:14 (3 years ago)
- Location:
- trunk/trac/web
- Files:
-
- 4 modified
Legend:
- Unmodified
- Added
- Removed
-
trunk/trac/web/api.py
r4010 r4087 124 124 This class provides a convenience API over WSGI. 125 125 """ 126 args = None127 hdf = None128 authname = None129 perm = None130 session = None131 126 132 127 def __init__(self, environ, start_response): … … 135 130 @param environ: The WSGI environment dict 136 131 @param start_response: The WSGI callback for starting the response 132 @param callbacks: A dictionary of functions that are used to lazily 133 evaluate attribute lookups 137 134 """ 138 135 self.environ = environ … … 142 139 self._response = None 143 140 144 self._inheaders = [(name[5:].replace('_', '-').lower(), value)145 for name, value in environ.items()146 if name.startswith('HTTP_')]147 if 'CONTENT_LENGTH' in environ:148 self._inheaders.append(('content-length',149 environ['CONTENT_LENGTH']))150 if 'CONTENT_TYPE' in environ:151 self._inheaders.append(('content-type', environ['CONTENT_TYPE']))152 141 self._outheaders = [] 153 142 self._outcharset = None 154 155 self.incookie = Cookie()156 cookie = self.get_header('Cookie')157 if cookie:158 self.incookie.load(cookie, ignore_parse_errors=True)159 143 self.outcookie = Cookie() 144 145 self.callbacks = { 146 'args': Request._parse_args, 147 'incookie': Request._parse_cookies, 148 '_inheaders': Request._parse_headers 149 } 160 150 161 151 self.base_url = self.environ.get('trac.base_url') … … 165 155 self.abs_href = Href(self.base_url) 166 156 167 self.args = self._parse_args() 168 169 def _parse_args(self): 170 """Parse the supplied request parameters into a dictionary.""" 171 args = _RequestArgs() 172 173 fp = self.environ['wsgi.input'] 174 ctype = self.get_header('Content-Type') 175 if ctype: 176 # Avoid letting cgi.FieldStorage consume the input stream when the 177 # request does not contain form data 178 ctype, options = cgi.parse_header(ctype) 179 if ctype not in ('application/x-www-form-urlencoded', 180 'multipart/form-data'): 181 fp = StringIO('') 182 183 fs = cgi.FieldStorage(fp, environ=self.environ, keep_blank_values=True) 184 if fs.list: 185 for name in fs.keys(): 186 values = fs[name] 187 if not isinstance(values, list): 188 values = [values] 189 for value in values: 190 if not value.filename: 191 value = unicode(value.value, 'utf-8') 192 if name in args: 193 if isinstance(args[name], list): 194 args[name].append(value) 195 else: 196 args[name] = [args[name], value] 197 else: 198 args[name] = value 199 200 return args 201 202 def _reconstruct_url(self): 203 """Reconstruct the absolute base URL of the application.""" 204 host = self.get_header('Host') 205 if not host: 206 # Missing host header, so reconstruct the host from the 207 # server name and port 208 default_port = {'http': 80, 'https': 443} 209 if self.server_port and self.server_port != default_port[self.scheme]: 210 host = '%s:%d' % (self.server_name, self.server_port) 211 else: 212 host = self.server_name 213 return urlparse.urlunparse((self.scheme, host, self.base_path, None, 214 None, None)) 157 def __getattr__(self, name): 158 """Performs lazy attribute lookup by delegating to the functions in the 159 callbacks dictionary.""" 160 if name in self.callbacks: 161 value = self.callbacks[name](self) 162 setattr(self, name, value) 163 return value 164 return getattr(super(Request, self), name) 165 166 def __repr__(self): 167 return '<%s "%s %s">' % (self.__class__.__name__, self.method, 168 self.path_info) 169 170 # Public API 215 171 216 172 method = property(fget=lambda self: self.environ['REQUEST_METHOD'], … … 258 214 self._outheaders.append((name, unicode(value).encode('utf-8'))) 259 215 260 def _send_cookie_headers(self):261 for name in self.outcookie.keys():262 path = self.outcookie[name].get('path')263 if path:264 path = path.replace(' ', '%20') \265 .replace(';', '%3B') \266 .replace(',', '%3C')267 self.outcookie[name]['path'] = path268 269 cookies = self.outcookie.output(header='')270 for cookie in cookies.splitlines():271 self._outheaders.append(('Set-Cookie', cookie.strip()))272 273 216 def end_headers(self): 274 217 """Must be called after all headers have been sent and before the actual … … 463 406 self._write(data) 464 407 408 # Internal methods 409 410 def _parse_args(self): 411 """Parse the supplied request parameters into a dictionary.""" 412 args = _RequestArgs() 413 414 fp = self.environ['wsgi.input'] 415 ctype = self.get_header('Content-Type') 416 if ctype: 417 # Avoid letting cgi.FieldStorage consume the input stream when the 418 # request does not contain form data 419 ctype, options = cgi.parse_header(ctype) 420 if ctype not in ('application/x-www-form-urlencoded', 421 'multipart/form-data'): 422 fp = StringIO('') 423 424 fs = cgi.FieldStorage(fp, environ=self.environ, keep_blank_values=True) 425 if fs.list: 426 for name in fs.keys(): 427 values = fs[name] 428 if not isinstance(values, list): 429 values = [values] 430 for value in values: 431 if not value.filename: 432 value = unicode(value.value, 'utf-8') 433 if name in args: 434 if isinstance(args[name], list): 435 args[name].append(value) 436 else: 437 args[name] = [args[name], value] 438 else: 439 args[name] = value 440 441 return args 442 443 def _parse_cookies(self): 444 cookies = Cookie() 445 header = self.get_header('Cookie') 446 if header: 447 cookies.load(header, ignore_parse_errors=True) 448 return cookies 449 450 def _parse_headers(self): 451 headers = [(name[5:].replace('_', '-').lower(), value) 452 for name, value in self.environ.items() 453 if name.startswith('HTTP_')] 454 if 'CONTENT_LENGTH' in self.environ: 455 headers.append(('content-length', self.environ['CONTENT_LENGTH'])) 456 if 'CONTENT_TYPE' in self.environ: 457 headers.append(('content-type', self.environ['CONTENT_TYPE'])) 458 return headers 459 460 def _reconstruct_url(self): 461 """Reconstruct the absolute base URL of the application.""" 462 host = self.get_header('Host') 463 if not host: 464 # Missing host header, so reconstruct the host from the 465 # server name and port 466 default_port = {'http': 80, 'https': 443} 467 if self.server_port and self.server_port != default_port[self.scheme]: 468 host = '%s:%d' % (self.server_name, self.server_port) 469 else: 470 host = self.server_name 471 return urlparse.urlunparse((self.scheme, host, self.base_path, None, 472 None, None)) 473 474 def _send_cookie_headers(self): 475 for name in self.outcookie.keys(): 476 path = self.outcookie[name].get('path') 477 if path: 478 path = path.replace(' ', '%20') \ 479 .replace(';', '%3B') \ 480 .replace(',', '%3C') 481 self.outcookie[name]['path'] = path 482 483 cookies = self.outcookie.output(header='') 484 for cookie in cookies.splitlines(): 485 self._outheaders.append(('Set-Cookie', cookie.strip())) 486 465 487 466 488 class IAuthenticator(Interface): … … 476 498 """Extension point interface for request handlers.""" 477 499 478 # implementing classes should set this property to `True` if they479 # don't need session and authentication related information480 anonymous_request = False481 482 # implementing classes should set this property to `False` if they483 # don't need the HDF data and don't produce content using a template484 use_template = True485 486 500 def match_request(req): 487 501 """Return whether the handler wants to process the given request.""" -
trunk/trac/web/chrome.py
r4063 r4087 27 27 from genshi.template import TemplateLoader, MarkupTemplate, TextTemplate 28 28 29 from trac import __version__ as VERSION 29 30 from trac import mimeview 30 31 from trac.config import * … … 45 46 """ 46 47 linkid = '%s:%s' % (rel, href) 47 linkset = req. environ.setdefault('trac.chrome.linkset', set())48 linkset = req.chrome.setdefault('linkset', set()) 48 49 if linkid in linkset: 49 50 return # Already added that link … … 57 58 link['class'] = classname 58 59 59 links = req. environ.setdefault('trac.chrome.links', {})60 links = req.chrome.setdefault('links', {}) 60 61 links.setdefault(rel, []).append(link) 61 62 linkset.add(linkid) … … 65 66 in the generated HTML page. 66 67 """ 67 if filename.startswith('common/') and ' trac.htdocs_location' in req.environ:68 href = Href(req. environ['trac.htdocs_location'])68 if filename.startswith('common/') and 'htdocs_location' in req.chrome: 69 href = Href(req.chrome['htdocs_location']) 69 70 filename = filename[7:] 70 71 else: … … 74 75 def add_script(req, filename, mimetype='text/javascript'): 75 76 """Add a reference to an external javascript file to the template.""" 76 scriptset = req. environ.setdefault('trac.chrome.scriptset', set())77 scriptset = req.chrome.setdefault('trac.chrome.scriptset', set()) 77 78 if filename in scriptset: 78 79 return False # Already added that script 79 80 80 if filename.startswith('common/') and ' trac.htdocs_location' in req.environ:81 href = Href(req. environ['trac.htdocs_location'])81 if filename.startswith('common/') and 'htdocs_location' in req.chrome: 82 href = Href(req.chrome['htdocs_location']) 82 83 filename = filename[7:] 83 84 else: … … 85 86 script = {'href': href(filename), 'type': mimetype} 86 87 87 req. environ.setdefault('trac.chrome.scripts', []).append(script)88 req.chrome.setdefault('scripts', []).append(script) 88 89 scriptset.add(filename) 89 90 … … 227 228 # IRequestHandler methods 228 229 229 anonymous_request = True230 use_template = False231 232 230 def match_request(self, req): 233 231 match = re.match(r'/chrome/(?P<prefix>[^/]+)/+(?P<filename>[/\w\-\.]+)', … … 286 284 287 285 def prepare_request(self, req, handler=None): 288 req.environ['trac.chrome.links'] = {} 289 req.environ['trac.chrome.scripts'] = [] 286 """Prepare the basic chrome data for the request. 287 288 @param req: the request object 289 @param handler: the `IRequestHandler` instance that is processing the 290 request 291 """ 292 self.log.debug('Prepare chrome data for request') 293 294 chrome = {'links': {}, 'scripts': []} 295 296 # This is ugly... we can't pass the real Request object to the 297 # add_xxx methods, because it doesn't yet have the chrome attribute 298 class FakeRequest(object): 299 def __init__(self, req): 300 self.base_path = req.base_path 301 self.chrome = chrome 302 fakereq = FakeRequest(req) 303 290 304 htdocs_location = self.htdocs_location or req.href.chrome('common') 291 req.environ['trac.htdocs_location'] = htdocs_location.rstrip('/') + '/'305 chrome['htdocs_location'] = htdocs_location.rstrip('/') + '/' 292 306 293 307 # HTML <head> links 294 add_link( req, 'start', req.href.wiki())295 add_link( req, 'search', req.href.search())296 add_link( req, 'help', req.href.wiki('TracGuide'))297 add_stylesheet( req, 'common/css/trac.css')298 add_script( req, 'common/js/jquery.js')299 add_script( req, 'common/js/trac.js')300 add_script( req, 'common/js/search.js')308 add_link(fakereq, 'start', req.href.wiki()) 309 add_link(fakereq, 'search', req.href.search()) 310 add_link(fakereq, 'help', req.href.wiki('TracGuide')) 311 add_stylesheet(fakereq, 'common/css/trac.css') 312 add_script(fakereq, 'common/js/jquery.js') 313 add_script(fakereq, 'common/js/trac.js') 314 add_script(fakereq, 'common/js/search.js') 301 315 302 316 icon = self.env.project_icon … … 308 322 icon = req.href.chrome('common', icon) 309 323 mimetype = mimeview.get_mimetype(icon) 310 add_link( req, 'icon', icon, mimetype=mimetype)311 add_link( req, 'shortcut icon', icon, mimetype=mimetype)324 add_link(fakereq, 'icon', icon, mimetype=mimetype) 325 add_link(fakereq, 'shortcut icon', icon, mimetype=mimetype) 312 326 313 327 # Logo image 314 req.environ['trac.chrome.logo'] = self.get_logo_data(req.href)328 chrome['logo'] = self.get_logo_data(req.href) 315 329 316 330 # Navigation links … … 342 356 nav[category][-1]['active'] = True 343 357 344 req.environ['trac.chrome.nav'] = nav 358 chrome['nav'] = nav 359 360 return chrome 345 361 346 362 def get_logo_data(self, href): … … 369 385 """Add chrome-related data to the HDF (deprecated).""" 370 386 req.hdf['HTTP.PathInfo'] = req.path_info 371 req.hdf['htdocs_location'] = req. environ.get('trac.htdocs_location')387 req.hdf['htdocs_location'] = req.chrome['htdocs_location'] 372 388 373 389 req.hdf['chrome.href'] = req.href.chrome() 374 req.hdf['chrome.links'] = req. environ.get('trac.chrome.links', [])375 req.hdf['chrome. logo'] = req.environ.get('trac.chrome.logo', {})376 req.hdf['chrome. scripts'] = req.environ.get('trac.chrome.scripts', [])377 378 for category, items in req.environ.get('trac.chrome.nav', {}).items():390 req.hdf['chrome.links'] = req.chrome['links'] 391 req.hdf['chrome.scripts'] = req.chrome['scripts'] 392 req.hdf['chrome.logo'] = req.chrome['logo'] 393 394 for category, items in chrome['nav'].items(): 379 395 for item in items: 380 396 prefix = 'chrome.nav.%s.%s' % (category, item['name']) … … 382 398 383 399 def populate_data(self, req, data): 384 from trac import __version__ as VERSION385 386 400 data.update(self._default_context_data) 387 401 data.setdefault('trac', {}).update({ … … 398 412 399 413 chrome = data.setdefault('chrome', {}) 400 chrome.update({401 'footer': Markup(self.env.project_footer),402 })403 414 if req: 404 chrome.update({ 405 'htdocs_location': req.environ.get('trac.htdocs_location'), 406 'logo': req.environ.get('trac.chrome.logo', {}), 407 'links': req.environ.get('trac.chrome.links', []), 408 'nav': req.environ.get('trac.chrome.nav', {}), 409 'scripts': req.environ.get('trac.chrome.scripts', []), 410 }) 415 chrome.update(req.chrome) 411 416 else: 412 417 chrome.update({ … … 414 419 'logo': self.get_logo_data(self.env.abs_href), 415 420 }) 416 417 data['req'] = req 418 data['abs_href'] = req and req.abs_href or self.env.abs_href 419 data['href'] = req and req.href 420 data['perm'] = req and req.perm 421 data['authname'] = req and req.authname or '<trac>' 422 423 # Timezone-dependant functions 421 chrome.update({ 422 'footer': Markup(self.env.project_footer) 423 }) 424 424 425 tzinfo = None 425 426 if req: 426 427 tzinfo = req.tz 427 data['format_datetime'] = partial(format_datetime, tzinfo=tzinfo) 428 data['format_date'] = partial(format_date, tzinfo=tzinfo) 429 data['format_time'] = partial(format_time, tzinfo=tzinfo) 430 data['fromtimestamp'] = partial(datetime.fromtimestamp, tz=tzinfo) 428 429 data.update({ 430 'req': req, 431 'abs_href': req and req.abs_href or self.env.abs_href, 432 'href': req and req.href, 433 'perm': req and req.perm, 434 'authname': req and req.authname or '<trac>', 435 'format_datetime': partial(format_datetime, tzinfo=tzinfo), 436 'format_date': partial(format_date, tzinfo=tzinfo), 437 'format_time': partial(format_time, tzinfo=tzinfo), 438 'fromtimestamp': partial(datetime.fromtimestamp, tz=tzinfo) 439 }) 431 440 432 441 def load_template(self, filename, method=None): -
trunk/trac/web/main.py
r4086 r4087 31 31 from trac.env import open_environment 32 32 from trac.perm import PermissionCache, NoPermissionCache, PermissionError 33 from trac.util import reversed, get_lines_from_file, get_last_traceback 33 from trac.util import get_lines_from_file, get_last_traceback 34 from trac.util.compat import partial, reversed 34 35 from trac.util.datefmt import format_datetime, http_date, localtz, timezone 35 36 from trac.util.html import Markup … … 116 117 if req.perm: 117 118 for action in req.perm.permissions(): 118 req.hdf['trac.acl.' + action] = True119 hdf['trac.acl.' + action] = True 119 120 120 121 for arg in [k for k in req.args.keys() if k]: … … 144 145 145 146 default_timezone = Option('trac', 'default_timezone', '', 146 doc="""The default timezone to use""") 147 148 147 """The default timezone to use""") 149 148 150 149 # Public API … … 165 164 site chrome. 166 165 """ 166 self.log.debug('Dispatching %r', req) 167 chrome = Chrome(self.env) 168 169 # Setup request callbacks for lazily-evaluated properties 170 req.callbacks.update({ 171 'authname': self.authenticate, 172 'chrome': chrome.prepare_request, 173 'hdf': self._get_hdf, 174 'perm': self._get_perm, 175 'session': self._get_session, 176 'tz': self._get_timezone 177 }) 178 167 179 # Select the component that should handle the request 168 180 chosen_handler = None 169 early_error = None 170 try: 171 if not req.path_info or req.path_info == '/': 172 chosen_handler = self.default_handler 173 else: 174 for handler in self.handlers: 175 if handler.match_request(req): 176 chosen_handler = handler 177 break 178 179 chosen_handler = self._pre_process_request(req, chosen_handler) 180 except: 181 early_error = sys.exc_info() 182 183 if not chosen_handler and not early_error: 184 early_error = (HTTPNotFound('No handler matched request to %s', 185 req.path_info), 186 None, None) 187 188 # Attach user information to the request 189 anonymous_request = getattr(chosen_handler, 'anonymous_request', 190 False) 191 if not anonymous_request: 192 try: 193 req.authname = self.authenticate(req) 194 req.perm = PermissionCache(self.env, req.authname) 195 req.session = Session(self.env, req) 196 except: 197 anonymous_request = True 198 early_error = sys.exc_info() 199 if anonymous_request: 200 req.authname = 'anonymous' 201 req.perm = NoPermissionCache() 202 203 try: 204 req.tz = timezone(req.session.get('tz', self.default_timezone 205 or 'missing')) 206 except: 207 req.tz = localtz 208 209 # Prepare HDF for the clearsilver template 210 try: 211 use_template = getattr(chosen_handler, 'use_template', True) 212 req.hdf = None 213 if use_template: 214 chrome = Chrome(self.env) 215 req.hdf = HDFWrapper(loadpaths=chrome.get_all_templates_dirs()) 216 populate_hdf(req.hdf, self.env, req) 217 chrome.prepare_request(req, chosen_handler) 218 except: 219 req.hdf = None # revert to sending plaintext error 220 if not early_error: 221 raise 222 223 if early_error: 224 try: 225 self._post_process_request(req) 226 except Exception, e: 227 self.log.exception(e) 228 raise early_error[0], early_error[1], early_error[2] 181 if not req.path_info or req.path_info == '/': 182 chosen_handler = self.default_handler 183 else: 184 for handler in self.handlers: 185 if handler.match_request(req): 186 chosen_handler = handler 187 break 188 chosen_handler = self._pre_process_request(req, chosen_handler) 189 if not chosen_handler: 190 raise HTTPNotFound('No handler matched request to %s', 191 req.path_info) 192 193 req.callbacks['chrome'] = partial(chrome.prepare_request, 194 handler=chosen_handler) 229 195 230 196 # Process the request and render the template … … 233 199 resp = chosen_handler.process_request(req) 234 200 if resp: 235 chrome = Chrome(self.env)236 201 if len(resp) == 2: # Clearsilver 237 202 chrome.populate_hdf(req) … … 267 232 raise HTTPInternalError(e.message) 268 233 234 # Internal methods 235 236 def _get_hdf(self, req): 237 hdf = HDFWrapper(loadpaths=Chrome(self.env).get_all_templates_dirs()) 238 populate_hdf(hdf, self.env, req) 239 return hdf 240 241 def _get_perm(self, req): 242 return PermissionCache(self.env, req.authname) 243 244 def _get_session(self, req): 245 return Session(self.env, req) 246 247 def _get_timezone(self, req): 248 try: 249 return timezone(req.session.get('tz', self.default_timezone 250 or 'missing')) 251 except: 252 return localtz 253 269 254 def _pre_process_request(self, req, chosen_handler): 270 for f in self.filters:271 chosen_handler = f .pre_process_request(req, chosen_handler)255 for filter_ in self.filters: 256 chosen_handler = filter_.pre_process_request(req, chosen_handler) 272 257 return chosen_handler 273 258 274 259 def _post_process_request(self, req, template=None, content_type=None): 275 260 for f in reversed(self.filters): -
trunk/trac/web/session.py
r3616 r4087 60 60 61 61 def get_session(self, sid, authenticated=False): 62 self.env.log.debug('Retrieving session for ID %r', sid) 63 62 64 db = self.env.get_db_cnx() 63 65 cursor = db.cursor()
