Back to index

moin  1.9.0~rc2
wsgiapp.py
Go to the documentation of this file.
00001 # -*- coding: iso-8859-1 -*-
00002 """
00003     MoinMoin - WSGI application
00004 
00005     @copyright: 2003-2008 MoinMoin:ThomasWaldmann,
00006                 2008-2008 MoinMoin:FlorianKrupicka
00007     @license: GNU GPL, see COPYING for details.
00008 """
00009 from MoinMoin.web.contexts import AllContext, Context, XMLRPCContext
00010 from MoinMoin.web.exceptions import HTTPException
00011 from MoinMoin.web.request import Request, MoinMoinFinish, HeaderSet
00012 from MoinMoin.web.utils import check_forbidden, check_surge_protect, fatal_response, \
00013     redirect_last_visited
00014 from MoinMoin.Page import Page
00015 from MoinMoin import auth, i18n, user, wikiutil, xmlrpc, error
00016 from MoinMoin.action import get_names, get_available_actions
00017 
00018 from MoinMoin import log
00019 logging = log.getLogger(__name__)
00020 
00021 def init(request):
00022     """
00023     Wraps an incoming WSGI request in a Context object and initializes
00024     several important attributes.
00025     """
00026     if isinstance(request, Context):
00027         context, request = request, request.request
00028     else:
00029         context = AllContext(request)
00030     context.clock.start('total')
00031     context.clock.start('init')
00032 
00033     context.lang = setup_i18n_preauth(context)
00034 
00035     context.session = context.cfg.session_service.get_session(context)
00036 
00037     context.user = setup_user(context, context.session)
00038 
00039     context.lang = setup_i18n_postauth(context)
00040 
00041     def finish():
00042         pass
00043 
00044     context.finish = finish
00045 
00046     context.reset()
00047 
00048     context.clock.stop('init')
00049     return context
00050 
00051 def run(context):
00052     """ Run a context trough the application. """
00053     context.clock.start('run')
00054     request = context.request
00055 
00056     # preliminary access checks (forbidden, bots, surge protection)
00057     try:
00058         try:
00059             check_forbidden(context)
00060             check_surge_protect(context)
00061 
00062             action_name = context.action
00063 
00064             # handle XMLRPC calls
00065             if action_name == 'xmlrpc':
00066                 response = xmlrpc.xmlrpc(XMLRPCContext(request))
00067             elif action_name == 'xmlrpc2':
00068                 response = xmlrpc.xmlrpc2(XMLRPCContext(request))
00069             else:
00070                 response = dispatch(request, context, action_name)
00071             context.cfg.session_service.finalize(context, context.session)
00072             return response
00073         except MoinMoinFinish:
00074             return request
00075     finally:
00076         context.finish()
00077         context.clock.stop('run')
00078 
00079 def remove_prefix(path, prefix=None):
00080     """ Remove an url prefix from the path info and return shortened path. """
00081     # we can have all action URLs like this: /action/ActionName/PageName?action=ActionName&...
00082     # this is just for robots.txt being able to forbid them for crawlers
00083     if prefix is not None:
00084         prefix = '/%s/' % prefix # e.g. '/action/'
00085         if path.startswith(prefix):
00086             # remove prefix and action name
00087             path = path[len(prefix):]
00088             action, path = (path.split('/', 1) + ['', ''])[:2]
00089             path = '/' + path
00090     return path
00091 
00092 def dispatch(request, context, action_name='show'):
00093     cfg = context.cfg
00094 
00095     # The last component in path_info is the page name, if any
00096     path = remove_prefix(request.path, cfg.url_prefix_action)
00097 
00098     if path.startswith('/'):
00099         pagename = wikiutil.normalize_pagename(path, cfg)
00100     else:
00101         pagename = None
00102 
00103     # need to inform caches that content changes based on:
00104     # * cookie (even if we aren't sending one now)
00105     # * User-Agent (because a bot might be denied and get no content)
00106     # * Accept-Language (except if moin is told to ignore browser language)
00107     hs = HeaderSet(('Cookie', 'User-Agent'))
00108     if not cfg.language_ignore_browser:
00109         hs.add('Accept-Language')
00110     request.headers.add('Vary', str(hs))
00111 
00112     # Handle request. We have these options:
00113     # 1. jump to page where user left off
00114     if not pagename and context.user.remember_last_visit and action_name == 'show':
00115         response = redirect_last_visited(context)
00116     # 2. handle action
00117     else:
00118         response = handle_action(context, pagename, action_name)
00119     if isinstance(response, Context):
00120         response = response.request
00121     return response
00122 
00123 def handle_action(context, pagename, action_name='show'):
00124     """ Actual dispatcher function for non-XMLRPC actions.
00125 
00126     Also sets up the Page object for this request, normalizes and
00127     redirects to canonical pagenames and checks for non-allowed
00128     actions.
00129     """
00130     _ = context.getText
00131     cfg = context.cfg
00132 
00133     # pagename could be empty after normalization e.g. '///' -> ''
00134     # Use localized FrontPage if pagename is empty
00135     if not pagename:
00136         context.page = wikiutil.getFrontPage(context)
00137     else:
00138         context.page = Page(context, pagename)
00139         if '_' in pagename and not context.page.exists():
00140             pagename = pagename.replace('_', ' ')
00141             page = Page(context, pagename)
00142             if page.exists():
00143                 url = page.url(context)
00144                 return context.http_redirect(url)
00145 
00146     msg = None
00147     # Complain about unknown actions
00148     if not action_name in get_names(cfg):
00149         msg = _("Unknown action %(action_name)s.") % {
00150                 'action_name': wikiutil.escape(action_name), }
00151 
00152     # Disallow non available actions
00153     elif action_name[0].isupper() and not action_name in \
00154             get_available_actions(cfg, context.page, context.user):
00155         msg = _("You are not allowed to do %(action_name)s on this page.") % {
00156                 'action_name': wikiutil.escape(action_name), }
00157         if not context.user.valid:
00158             # Suggest non valid user to login
00159             msg += " " + _("Login and try again.")
00160 
00161     if msg:
00162         context.theme.add_msg(msg, "error")
00163         context.page.send_page()
00164     # Try action
00165     else:
00166         from MoinMoin import action
00167         handler = action.getHandler(context, action_name)
00168         if handler is None:
00169             msg = _("You are not allowed to do %(action_name)s on this page.") % {
00170                     'action_name': wikiutil.escape(action_name), }
00171             if not context.user.valid:
00172                 # Suggest non valid user to login
00173                 msg += " " + _("Login and try again.")
00174             context.theme.add_msg(msg, "error")
00175             context.page.send_page()
00176         else:
00177             handler(context.page.page_name, context)
00178 
00179     return context
00180 
00181 def setup_user(context, session):
00182     """ Try to retrieve a valid user object from the request, be it
00183     either through the session or through a login. """
00184     # first try setting up from session
00185     userobj = auth.setup_from_session(context, session)
00186     userobj, olduser = auth.setup_setuid(context, userobj)
00187     context._setuid_real_user = olduser
00188 
00189     # then handle login/logout forms
00190     form = context.request.values
00191 
00192     if 'login' in form:
00193         params = {
00194             'username': form.get('name'),
00195             'password': form.get('password'),
00196             'attended': True,
00197             'openid_identifier': form.get('openid_identifier'),
00198             'stage': form.get('stage')
00199         }
00200         userobj = auth.handle_login(context, userobj, **params)
00201     elif 'logout' in form:
00202         userobj = auth.handle_logout(context, userobj)
00203     else:
00204         userobj = auth.handle_request(context, userobj)
00205 
00206     # if we still have no user obj, create a dummy:
00207     if not userobj:
00208         userobj = user.User(context, auth_method='invalid')
00209 
00210     return userobj
00211 
00212 def setup_i18n_preauth(context):
00213     """ Determine language for the request in absence of any user info. """
00214     if i18n.languages is None:
00215         i18n.i18n_init(context)
00216 
00217     cfg = context.cfg
00218     lang = None
00219     if i18n.languages and not cfg.language_ignore_browser:
00220         for l in context.request.accept_languages:
00221             if l in i18n.languages:
00222                 lang = l
00223                 break
00224     if lang is None and cfg.language_default in i18n.languages:
00225         lang = cfg.language_default
00226     else:
00227         lang = 'en'
00228     return lang
00229 
00230 def setup_i18n_postauth(context):
00231     """ Determine language for the request after user-id is established. """
00232     user = context.user
00233     if user and user.valid and user.language:
00234         return user.language
00235     else:
00236         return context.lang
00237 
00238 class Application(object):
00239     def __init__(self, app_config=None):
00240 
00241         class AppRequest(Request):
00242             given_config = app_config
00243 
00244         self.Request = AppRequest
00245 
00246     def __call__(self, environ, start_response):
00247         try:
00248             request = None
00249             request = self.Request(environ)
00250             context = init(request)
00251             response = run(context)
00252             context.clock.stop('total')
00253         except HTTPException, e:
00254             response = e
00255         except error.ConfigurationError, e:
00256             # this is stuff the user should see on the web interface:
00257             response = fatal_response(e)
00258         except Exception, e:
00259             # we avoid raising more exceptions here to preserve the original exception
00260             url_info = request and ' [%s]' % request.url or ''
00261             # have exceptions logged within the moin logging framework:
00262             logging.exception("An exception has occurred%s." % url_info)
00263             # re-raise exception, so e.g. the debugger middleware gets it
00264             raise
00265 
00266         return response(environ, start_response)
00267 
00268 #XXX: default application using the default config from disk
00269 application = Application()