~ubuntu-branches/ubuntu/natty/moin/natty-updates

« back to all changes in this revision

Viewing changes to MoinMoin/wsgiapp.py

  • Committer: Bazaar Package Importer
  • Author(s): Jonas Smedegaard
  • Date: 2008-06-22 21:17:13 UTC
  • mto: This revision was merged to the branch mainline in revision 18.
  • Revision ID: james.westby@ubuntu.com-20080622211713-inlv5k4eifxckelr
ImportĀ upstreamĀ versionĀ 1.7.0

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# -*- coding: iso-8859-1 -*-
2
 
"""
3
 
    MoinMoin - WSGI application
4
 
 
5
 
    @copyright: 2003-2008 MoinMoin:ThomasWaldmann,
6
 
                2008-2008 MoinMoin:FlorianKrupicka
7
 
    @license: GNU GPL, see COPYING for details.
8
 
"""
9
 
import os
10
 
 
11
 
from MoinMoin import log
12
 
logging = log.getLogger(__name__)
13
 
 
14
 
from MoinMoin.web.contexts import AllContext, Context, XMLRPCContext
15
 
from MoinMoin.web.exceptions import HTTPException
16
 
from MoinMoin.web.request import Request, MoinMoinFinish, HeaderSet
17
 
from MoinMoin.web.utils import check_forbidden, check_surge_protect, fatal_response, \
18
 
    redirect_last_visited
19
 
from MoinMoin.Page import Page
20
 
from MoinMoin import auth, config, i18n, user, wikiutil, xmlrpc, error
21
 
from MoinMoin.action import get_names, get_available_actions
22
 
 
23
 
 
24
 
def set_umask(new_mask=0777^config.umask):
25
 
    """ Set the OS umask value (and ignore potential failures on OSes where
26
 
        this is not supported).
27
 
        Default: the bitwise inverted value of config.umask
28
 
    """
29
 
    try:
30
 
        old_mask = os.umask(new_mask)
31
 
    except:
32
 
        # maybe we are on win32?
33
 
        pass
34
 
 
35
 
 
36
 
def init(request):
37
 
    """
38
 
    Wraps an incoming WSGI request in a Context object and initializes
39
 
    several important attributes.
40
 
    """
41
 
    set_umask() # do it once per request because maybe some server
42
 
                # software sets own umask
43
 
 
44
 
    if isinstance(request, Context):
45
 
        context, request = request, request.request
46
 
    else:
47
 
        context = AllContext(request)
48
 
    context.clock.start('total')
49
 
    context.clock.start('init')
50
 
 
51
 
    context.lang = setup_i18n_preauth(context)
52
 
 
53
 
    context.session = context.cfg.session_service.get_session(context)
54
 
 
55
 
    context.user = setup_user(context, context.session)
56
 
 
57
 
    context.lang = setup_i18n_postauth(context)
58
 
 
59
 
    def finish():
60
 
        pass
61
 
 
62
 
    context.finish = finish
63
 
 
64
 
    context.reset()
65
 
 
66
 
    context.clock.stop('init')
67
 
    return context
68
 
 
69
 
def run(context):
70
 
    """ Run a context trough the application. """
71
 
    context.clock.start('run')
72
 
    request = context.request
73
 
 
74
 
    # preliminary access checks (forbidden, bots, surge protection)
75
 
    try:
76
 
        try:
77
 
            check_forbidden(context)
78
 
            check_surge_protect(context)
79
 
 
80
 
            action_name = context.action
81
 
 
82
 
            # handle XMLRPC calls
83
 
            if action_name == 'xmlrpc':
84
 
                response = xmlrpc.xmlrpc(XMLRPCContext(request))
85
 
            elif action_name == 'xmlrpc2':
86
 
                response = xmlrpc.xmlrpc2(XMLRPCContext(request))
87
 
            else:
88
 
                response = dispatch(request, context, action_name)
89
 
            context.cfg.session_service.finalize(context, context.session)
90
 
            return response
91
 
        except MoinMoinFinish:
92
 
            return request
93
 
    finally:
94
 
        context.finish()
95
 
        context.clock.stop('run')
96
 
 
97
 
def remove_prefix(path, prefix=None):
98
 
    """ Remove an url prefix from the path info and return shortened path. """
99
 
    # we can have all action URLs like this: /action/ActionName/PageName?action=ActionName&...
100
 
    # this is just for robots.txt being able to forbid them for crawlers
101
 
    if prefix is not None:
102
 
        prefix = '/%s/' % prefix # e.g. '/action/'
103
 
        if path.startswith(prefix):
104
 
            # remove prefix and action name
105
 
            path = path[len(prefix):]
106
 
            action, path = (path.split('/', 1) + ['', ''])[:2]
107
 
            path = '/' + path
108
 
    return path
109
 
 
110
 
def dispatch(request, context, action_name='show'):
111
 
    cfg = context.cfg
112
 
 
113
 
    # The last component in path_info is the page name, if any
114
 
    path = remove_prefix(request.path, cfg.url_prefix_action)
115
 
 
116
 
    if path.startswith('/'):
117
 
        pagename = wikiutil.normalize_pagename(path, cfg)
118
 
    else:
119
 
        pagename = None
120
 
 
121
 
    # need to inform caches that content changes based on:
122
 
    # * cookie (even if we aren't sending one now)
123
 
    # * User-Agent (because a bot might be denied and get no content)
124
 
    # * Accept-Language (except if moin is told to ignore browser language)
125
 
    hs = HeaderSet(('Cookie', 'User-Agent'))
126
 
    if not cfg.language_ignore_browser:
127
 
        hs.add('Accept-Language')
128
 
    request.headers['Vary'] = str(hs)
129
 
 
130
 
    # Handle request. We have these options:
131
 
    # 1. jump to page where user left off
132
 
    if not pagename and context.user.remember_last_visit and action_name == 'show':
133
 
        response = redirect_last_visited(context)
134
 
    # 2. handle action
135
 
    else:
136
 
        response = handle_action(context, pagename, action_name)
137
 
    if isinstance(response, Context):
138
 
        response = response.request
139
 
    return response
140
 
 
141
 
def handle_action(context, pagename, action_name='show'):
142
 
    """ Actual dispatcher function for non-XMLRPC actions.
143
 
 
144
 
    Also sets up the Page object for this request, normalizes and
145
 
    redirects to canonical pagenames and checks for non-allowed
146
 
    actions.
147
 
    """
148
 
    _ = context.getText
149
 
    cfg = context.cfg
150
 
 
151
 
    # pagename could be empty after normalization e.g. '///' -> ''
152
 
    # Use localized FrontPage if pagename is empty
153
 
    if not pagename:
154
 
        context.page = wikiutil.getFrontPage(context)
155
 
    else:
156
 
        context.page = Page(context, pagename)
157
 
        if '_' in pagename and not context.page.exists():
158
 
            pagename = pagename.replace('_', ' ')
159
 
            page = Page(context, pagename)
160
 
            if page.exists():
161
 
                url = page.url(context)
162
 
                return context.http_redirect(url)
163
 
 
164
 
    msg = None
165
 
    # Complain about unknown actions
166
 
    if not action_name in get_names(cfg):
167
 
        msg = _("Unknown action %(action_name)s.") % {
168
 
                'action_name': wikiutil.escape(action_name), }
169
 
 
170
 
    # Disallow non available actions
171
 
    elif action_name[0].isupper() and not action_name in \
172
 
            get_available_actions(cfg, context.page, context.user):
173
 
        msg = _("You are not allowed to do %(action_name)s on this page.") % {
174
 
                'action_name': wikiutil.escape(action_name), }
175
 
        if not context.user.valid:
176
 
            # Suggest non valid user to login
177
 
            msg += " " + _("Login and try again.")
178
 
 
179
 
    if msg:
180
 
        context.theme.add_msg(msg, "error")
181
 
        context.page.send_page()
182
 
    # Try action
183
 
    else:
184
 
        from MoinMoin import action
185
 
        handler = action.getHandler(context, action_name)
186
 
        if handler is None:
187
 
            msg = _("You are not allowed to do %(action_name)s on this page.") % {
188
 
                    'action_name': wikiutil.escape(action_name), }
189
 
            if not context.user.valid:
190
 
                # Suggest non valid user to login
191
 
                msg += " " + _("Login and try again.")
192
 
            context.theme.add_msg(msg, "error")
193
 
            context.page.send_page()
194
 
        else:
195
 
            handler(context.page.page_name, context)
196
 
 
197
 
    return context
198
 
 
199
 
def setup_user(context, session):
200
 
    """ Try to retrieve a valid user object from the request, be it
201
 
    either through the session or through a login. """
202
 
    # first try setting up from session
203
 
    userobj = auth.setup_from_session(context, session)
204
 
    userobj, olduser = auth.setup_setuid(context, userobj)
205
 
    context._setuid_real_user = olduser
206
 
 
207
 
    # then handle login/logout forms
208
 
    form = context.request.values
209
 
 
210
 
    if 'login' in form:
211
 
        params = {
212
 
            'username': form.get('name'),
213
 
            'password': form.get('password'),
214
 
            'attended': True,
215
 
            'openid_identifier': form.get('openid_identifier'),
216
 
            'stage': form.get('stage')
217
 
        }
218
 
        userobj = auth.handle_login(context, userobj, **params)
219
 
    elif 'logout' in form:
220
 
        userobj = auth.handle_logout(context, userobj)
221
 
    else:
222
 
        userobj = auth.handle_request(context, userobj)
223
 
 
224
 
    # if we still have no user obj, create a dummy:
225
 
    if not userobj:
226
 
        userobj = user.User(context, auth_method='invalid')
227
 
 
228
 
    return userobj
229
 
 
230
 
def setup_i18n_preauth(context):
231
 
    """ Determine language for the request in absence of any user info. """
232
 
    if i18n.languages is None:
233
 
        i18n.i18n_init(context)
234
 
 
235
 
    lang = None
236
 
    if i18n.languages:
237
 
        cfg = context.cfg
238
 
        if not cfg.language_ignore_browser:
239
 
            for l, w in context.request.accept_languages:
240
 
                logging.debug("client accepts language %r, weight %r" % (l, w))
241
 
                if l in i18n.languages:
242
 
                    logging.debug("moin supports language %r" % l)
243
 
                    lang = l
244
 
                    break
245
 
            else:
246
 
                logging.debug("moin does not support any language client accepts")
247
 
        if not lang:
248
 
            if cfg.language_default in i18n.languages:
249
 
                lang = cfg.language_default
250
 
                logging.debug("fall back to cfg.language_default (%r)" % lang)
251
 
    if not lang:
252
 
        lang = 'en'
253
 
        logging.debug("emergency fallback to 'en'")
254
 
    logging.debug("setup_i18n_preauth returns %r" % lang)
255
 
    return lang
256
 
 
257
 
def setup_i18n_postauth(context):
258
 
    """ Determine language for the request after user-id is established. """
259
 
    user = context.user
260
 
    if user and user.valid and user.language:
261
 
        logging.debug("valid user that has configured some specific language to use in his user profile")
262
 
        lang = user.language
263
 
    else:
264
 
        logging.debug("either no valid user or no specific language configured in user profile, using lang setup by setup_i18n_preauth")
265
 
        lang = context.lang
266
 
    logging.debug("setup_i18n_postauth returns %r" % lang)
267
 
    return lang
268
 
 
269
 
class Application(object):
270
 
    def __init__(self, app_config=None):
271
 
 
272
 
        class AppRequest(Request):
273
 
            given_config = app_config
274
 
 
275
 
        self.Request = AppRequest
276
 
 
277
 
    def __call__(self, environ, start_response):
278
 
        try:
279
 
            request = None
280
 
            request = self.Request(environ)
281
 
            context = init(request)
282
 
            response = run(context)
283
 
            context.clock.stop('total')
284
 
        except HTTPException, e:
285
 
            response = e
286
 
        except error.ConfigurationError, e:
287
 
            # this is stuff the user should see on the web interface:
288
 
            response = fatal_response(e)
289
 
        except Exception, e:
290
 
            # we avoid raising more exceptions here to preserve the original exception
291
 
            url_info = request and ' [%s]' % request.url or ''
292
 
            # have exceptions logged within the moin logging framework:
293
 
            logging.exception("An exception has occurred%s." % url_info)
294
 
            # re-raise exception, so e.g. the debugger middleware gets it
295
 
            raise
296
 
 
297
 
        return response(environ, start_response)
298
 
 
299
 
#XXX: default application using the default config from disk
300
 
application = Application()