1
# -*- coding: iso-8859-1 -*-
3
MoinMoin - WSGI application
5
@copyright: 2003-2008 MoinMoin:ThomasWaldmann,
6
2008-2008 MoinMoin:FlorianKrupicka
7
@license: GNU GPL, see COPYING for details.
11
from MoinMoin import log
12
logging = log.getLogger(__name__)
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, \
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
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
30
old_mask = os.umask(new_mask)
32
# maybe we are on win32?
38
Wraps an incoming WSGI request in a Context object and initializes
39
several important attributes.
41
set_umask() # do it once per request because maybe some server
42
# software sets own umask
44
if isinstance(request, Context):
45
context, request = request, request.request
47
context = AllContext(request)
48
context.clock.start('total')
49
context.clock.start('init')
51
context.lang = setup_i18n_preauth(context)
53
context.session = context.cfg.session_service.get_session(context)
55
context.user = setup_user(context, context.session)
57
context.lang = setup_i18n_postauth(context)
62
context.finish = finish
66
context.clock.stop('init')
70
""" Run a context trough the application. """
71
context.clock.start('run')
72
request = context.request
74
# preliminary access checks (forbidden, bots, surge protection)
77
check_forbidden(context)
78
check_surge_protect(context)
80
action_name = context.action
83
if action_name == 'xmlrpc':
84
response = xmlrpc.xmlrpc(XMLRPCContext(request))
85
elif action_name == 'xmlrpc2':
86
response = xmlrpc.xmlrpc2(XMLRPCContext(request))
88
response = dispatch(request, context, action_name)
89
context.cfg.session_service.finalize(context, context.session)
91
except MoinMoinFinish:
95
context.clock.stop('run')
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]
110
def dispatch(request, context, action_name='show'):
113
# The last component in path_info is the page name, if any
114
path = remove_prefix(request.path, cfg.url_prefix_action)
116
if path.startswith('/'):
117
pagename = wikiutil.normalize_pagename(path, cfg)
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)
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)
136
response = handle_action(context, pagename, action_name)
137
if isinstance(response, Context):
138
response = response.request
141
def handle_action(context, pagename, action_name='show'):
142
""" Actual dispatcher function for non-XMLRPC actions.
144
Also sets up the Page object for this request, normalizes and
145
redirects to canonical pagenames and checks for non-allowed
151
# pagename could be empty after normalization e.g. '///' -> ''
152
# Use localized FrontPage if pagename is empty
154
context.page = wikiutil.getFrontPage(context)
156
context.page = Page(context, pagename)
157
if '_' in pagename and not context.page.exists():
158
pagename = pagename.replace('_', ' ')
159
page = Page(context, pagename)
161
url = page.url(context)
162
return context.http_redirect(url)
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), }
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.")
180
context.theme.add_msg(msg, "error")
181
context.page.send_page()
184
from MoinMoin import action
185
handler = action.getHandler(context, action_name)
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()
195
handler(context.page.page_name, context)
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
207
# then handle login/logout forms
208
form = context.request.values
212
'username': form.get('name'),
213
'password': form.get('password'),
215
'openid_identifier': form.get('openid_identifier'),
216
'stage': form.get('stage')
218
userobj = auth.handle_login(context, userobj, **params)
219
elif 'logout' in form:
220
userobj = auth.handle_logout(context, userobj)
222
userobj = auth.handle_request(context, userobj)
224
# if we still have no user obj, create a dummy:
226
userobj = user.User(context, auth_method='invalid')
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)
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)
246
logging.debug("moin does not support any language client accepts")
248
if cfg.language_default in i18n.languages:
249
lang = cfg.language_default
250
logging.debug("fall back to cfg.language_default (%r)" % lang)
253
logging.debug("emergency fallback to 'en'")
254
logging.debug("setup_i18n_preauth returns %r" % lang)
257
def setup_i18n_postauth(context):
258
""" Determine language for the request after user-id is established. """
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")
264
logging.debug("either no valid user or no specific language configured in user profile, using lang setup by setup_i18n_preauth")
266
logging.debug("setup_i18n_postauth returns %r" % lang)
269
class Application(object):
270
def __init__(self, app_config=None):
272
class AppRequest(Request):
273
given_config = app_config
275
self.Request = AppRequest
277
def __call__(self, environ, start_response):
280
request = self.Request(environ)
281
context = init(request)
282
response = run(context)
283
context.clock.stop('total')
284
except HTTPException, e:
286
except error.ConfigurationError, e:
287
# this is stuff the user should see on the web interface:
288
response = fatal_response(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
297
return response(environ, start_response)
299
#XXX: default application using the default config from disk
300
application = Application()