1
# -*- coding: iso-8859-1 -*-
3
MoinMoin - Context objects which are passed thru instead of the classic
4
request objects. Currently contains legacy wrapper code for
5
a single request object.
7
@copyright: 2008-2008 MoinMoin:FlorianKrupicka
8
@license: GNU GPL, see COPYING for details.
11
import time, inspect, StringIO, sys, warnings
13
from werkzeug import Headers, http_date, create_environ, redirect, abort
14
from werkzeug.exceptions import Unauthorized, NotFound
16
from MoinMoin import i18n, error, user, config, wikiutil
17
from MoinMoin.config import multiconfig
18
from MoinMoin.formatter import text_html
19
from MoinMoin.theme import load_theme_fallback
20
from MoinMoin.util.clock import Clock
21
from MoinMoin.web.request import Request, MoinMoinFinish
22
from MoinMoin.web.utils import UniqueIDGenerator
23
from MoinMoin.web.exceptions import Forbidden, SurgeProtection
25
from MoinMoin import log
26
logging = log.getLogger(__name__)
29
class EnvironProxy(property):
30
""" Proxy attribute lookups to keys in the environ. """
31
def __init__(self, name, default=NoDefault):
33
An entry will be proxied to the supplied name in the .environ
34
object of the property holder. A factory can be supplied, for
35
values that need to be preinstantiated. If given as first
36
parameter name is taken from the callable too.
38
@param name: key (or factory for convenience)
39
@param default: literal object or callable
41
if not isinstance(name, basestring):
43
name = default.__name__
44
self.name = 'moin.' + name
45
self.default = default
46
property.__init__(self, self.get, self.set, self.delete)
49
if self.name in obj.environ:
50
res = obj.environ[self.name]
52
factory = self.default
53
if factory is NoDefault:
54
raise AttributeError(self.name)
55
elif hasattr(factory, '__call__'):
56
res = obj.environ.setdefault(self.name, factory(obj))
58
res = obj.environ.setdefault(self.name, factory)
61
def set(self, obj, value):
62
obj.environ[self.name] = value
64
def delete(self, obj):
65
del obj.environ[self.name]
68
return "<%s for '%s'>" % (self.__class__.__name__,
71
class Context(object):
72
""" Standard implementation for the context interface.
74
This one wraps up a Moin-Request object and the associated
75
environ and also keeps track of it's changes.
77
def __init__(self, request):
78
assert isinstance(request, Request)
80
self.request = request
81
self.environ = environ = request.environ
82
self.personalities = self.environ.setdefault(
83
'context.personalities', []
85
self.personalities.append(self.__class__.__name__)
87
def become(self, cls):
88
""" Become another context, based on given class.
90
@param cls: class to change to, must be a sister class
92
@return: wether a class change took place
94
if self.__class__ is cls:
97
self.personalities.append(cls)
102
return "<%s %r>" % (self.__class__.__name__, self.personalities)
104
class BaseContext(Context):
105
""" Implements a basic context, that provides some common attributes.
106
Most attributes are lazily initialized via descriptors. """
108
# first the trivial attributes
109
action = EnvironProxy('action', lambda o: o.request.values.get('action', 'show'))
110
clock = EnvironProxy('clock', lambda o: Clock())
111
user = EnvironProxy('user', lambda o: user.User(o, auth_method='request:invalid'))
113
lang = EnvironProxy('lang')
114
content_lang = EnvironProxy('content_lang', lambda o: o.cfg.language_default)
115
current_lang = EnvironProxy('current_lang')
117
html_formatter = EnvironProxy('html_formatter', lambda o: text_html.Formatter(o))
118
formatter = EnvironProxy('formatter', lambda o: o.html_formatter)
120
page = EnvironProxy('page', None)
122
# now the more complex factories
124
if self.request.given_config is not None:
125
return self.request.given_config('MoinMoin._tests.wikiconfig')
127
self.clock.start('load_multi_cfg')
128
cfg = multiconfig.getConfig(self.request.url)
129
self.clock.stop('load_multi_cfg')
131
except error.NoConfigMatchedError:
132
raise NotFound('<p>No wiki configuration matching the URL found!</p>')
133
cfg = EnvironProxy(cfg)
137
def _(text, i18n=i18n, request=self, lang=lang, **kw):
138
return i18n.getText(text, request, lang, **kw)
141
getText = property(getText)
144
def isSpiderAgent(self):
145
""" Simple check if useragent is a spider bot. """
147
user_agent = self.http_user_agent
148
if user_agent and cfg.cache.ua_spiders:
149
return cfg.cache.ua_spiders.search(user_agent) is not None
151
isSpiderAgent = EnvironProxy(isSpiderAgent)
154
from MoinMoin.Page import RootPage
155
return RootPage(self)
156
rootpage = EnvironProxy(rootpage)
160
return int(self.values['rev'])
163
rev = EnvironProxy(rev)
168
theme = EnvironProxy('theme', _theme)
170
# finally some methods to act on those attributes
171
def setContentLanguage(self, lang):
172
""" Set the content language, used for the content div
174
Actions that generate content in the user language, like search,
175
should set the content direction to the user language before they
178
self.content_lang = lang
179
self.current_lang = lang
182
""" Set theme - forced theme, user theme or wiki default """
183
if self.cfg.theme_force:
184
theme_name = self.cfg.theme_default
186
theme_name = self.user.theme_name
187
load_theme_fallback(self, theme_name)
190
class HTTPContext(BaseContext):
191
""" Context that holds attributes and methods for manipulation of
192
incoming and outgoing HTTP data. """
194
session = EnvironProxy('session')
195
_auth_redirected = EnvironProxy('old._auth_redirected', 0)
196
cacheable = EnvironProxy('old.cacheable', 0)
197
writestack = EnvironProxy('old.writestack', lambda o: list())
199
# proxy some descriptors of the underlying WSGI request, since
200
# setting on those does not work over __(g|s)etattr__-proxies
201
class _proxy(property):
202
def __init__(self, name):
204
property.__init__(self, self.get, self.set, self.delete)
206
return getattr(obj.request, self.name)
207
def set(self, obj, value):
208
setattr(obj.request, self.name, value)
209
def delete(self, obj):
210
delattr(obj.request, self.name)
212
mimetype = _proxy('mimetype')
213
content_type = _proxy('content_type')
214
status = _proxy('status')
215
status_code = _proxy('status_code')
219
# proxy further attribute lookups to the underlying request first
220
def __getattr__(self, name):
222
return getattr(self.request, name)
223
except AttributeError, e:
224
return super(HTTPContext, self).__getattribute__(name)
226
# methods regarding manipulation of HTTP related data
227
def read(self, n=None):
228
""" Read n bytes (or everything) from input stream. """
230
return self.request.in_data
232
return self.request.in_stream.read(n)
234
def makeForbidden(self, resultcode, msg):
235
status = {401: Unauthorized,
238
503: SurgeProtection}
239
raise status[resultcode](msg)
241
def setHttpHeader(self, header):
242
logging.warning("Deprecated call to request.setHttpHeader('k:v'), use request.headers.add/set('k', 'v')")
243
header, value = header.split(':', 1)
244
self.headers.add(header, value)
246
def disableHttpCaching(self, level=1):
247
""" Prevent caching of pages that should not be cached.
249
level == 1 means disabling caching when we have a cookie set
250
level == 2 means completely disabling caching (used by Page*Editor)
252
This is important to prevent caches break acl by providing one
253
user pages meant to be seen only by another user, when both users
254
share the same caching proxy.
256
AVOID using no-cache and no-store for attachments as it is completely broken on IE!
258
Details: http://support.microsoft.com/support/kb/articles/Q234/0/67.ASP
260
if level == 1 and self.headers.get('Pragma') == 'no-cache':
264
self.headers['Cache-Control'] = 'private, must-revalidate, max-age=10'
266
self.headers['Cache-Control'] = 'no-cache'
267
self.headers['Pragma'] = 'no-cache'
268
self.request.expires = time.time() - 3600 * 24 * 365
270
def http_redirect(self, url, code=302):
271
""" Raise a simple redirect exception. """
272
abort(redirect(url, code=code))
274
def http_user_agent(self):
275
return self.environ.get('HTTP_USER_AGENT', '')
276
http_user_agent = EnvironProxy(http_user_agent)
278
def http_referer(self):
279
return self.environ.get('HTTP_REFERER', '')
280
http_referer = EnvironProxy(http_referer)
282
# the output related methods
283
def write(self, *data):
284
""" Write to output stream. """
285
self.request.out_stream.writelines(data)
287
def redirectedOutput(self, function, *args, **kw):
288
""" Redirect output during function, return redirected output """
289
buf = StringIO.StringIO()
292
function(*args, **kw)
295
text = buf.getvalue()
299
def redirect(self, file=None):
300
""" Redirect output to file, or restore saved output """
302
self.writestack.append(self.write)
303
self.write = file.write
305
self.write = self.writestack.pop()
307
def send_file(self, fileobj, bufsize=8192, do_flush=None):
308
""" Send a file to the output stream.
310
@param fileobj: a file-like object (supporting read, close)
311
@param bufsize: size of chunks to read/write
312
@param do_flush: call flush after writing?
314
def simple_wrapper(fileobj, bufsize):
315
return iter(lambda: fileobj.read(bufsize), '')
316
file_wrapper = self.environ.get('wsgi.file_wrapper', simple_wrapper)
317
self.request.direct_passthrough = True
318
self.request.response = file_wrapper(fileobj, bufsize)
319
raise MoinMoinFinish('sent file')
321
# fully deprecated functions, with warnings
322
def getScriptname(self):
324
"request.getScriptname() is deprecated, please use the request's script_root property.",
326
return self.request.script_root
328
def getBaseURL(self):
330
"request.getBaseURL() is deprecated, please use the request's "
331
"url_root property or the abs_href object if urls should be generated.",
333
return self.request.url_root
335
def getQualifiedURL(self, uri=''):
336
""" Return an absolute URL starting with schema and host.
338
Already qualified urls are returned unchanged.
340
@param uri: server rooted uri e.g /scriptname/pagename.
341
It must start with a slash. Must be ascii and url encoded.
344
scheme = urlparse.urlparse(uri)[0]
348
host_url = self.request.host_url.rstrip('/')
349
result = "%s%s" % (host_url, uri)
351
# This might break qualified urls in redirects!
352
# e.g. mapping 'http://netloc' -> '/'
353
result = wikiutil.mapURL(self, result)
356
class AuxilaryMixin(object):
358
Mixin for diverse attributes and methods that aren't clearly assignable
359
to a particular phase of the request.
362
# several attributes used by other code to hold state across calls
363
_fmt_hd_counters = EnvironProxy('_fmt_hd_counters')
364
parsePageLinks_running = EnvironProxy('parsePageLinks_running', lambda o: {})
365
mode_getpagelinks = EnvironProxy('mode_getpagelinks', 0)
367
pragma = EnvironProxy('pragma', lambda o: {})
368
_login_messages = EnvironProxy('_login_messages', lambda o: [])
369
_login_multistage = EnvironProxy('_login_multistage', None)
370
_login_multistage_name = EnvironProxy('_login_multistage_name', None)
371
_setuid_real_user = EnvironProxy('_setuid_real_user', None)
372
pages = EnvironProxy('pages', lambda o: {})
374
def uid_generator(self):
376
if hasattr(self, 'page') and hasattr(self.page, 'page_name'):
377
pagename = self.page.page_name
378
return UniqueIDGenerator(pagename=pagename)
379
uid_generator = EnvironProxy(uid_generator)
382
""" Lazy initialize the dicts on the first access """
383
dicts = self.cfg.dicts(self)
385
dicts = EnvironProxy(dicts)
388
""" Lazy initialize the groups on the first access """
389
groups = self.cfg.groups(self)
391
groups = EnvironProxy(groups)
394
self.current_lang = self.cfg.language_default
395
if hasattr(self, '_fmt_hd_counters'):
396
del self._fmt_hd_counters
397
if hasattr(self, 'uid_generator'):
398
del self.uid_generator
400
def getPragma(self, key, defval=None):
401
""" Query a pragma value (#pragma processing instruction)
403
Keys are not case-sensitive.
405
return self.pragma.get(key.lower(), defval)
407
def setPragma(self, key, value):
408
""" Set a pragma value (#pragma processing instruction)
410
Keys are not case-sensitive.
412
self.pragma[key.lower()] = value
414
class XMLRPCContext(HTTPContext, AuxilaryMixin):
415
""" Context to act during a XMLRPC request. """
417
class AllContext(HTTPContext, AuxilaryMixin):
418
""" Catchall context to be able to quickly test old Moin code. """
420
class ScriptContext(AllContext):
421
""" Context to act in scripting environments (e.g. former request_cli).
423
For input, sys.stdin is used as 'wsgi.input', output is written directly
424
to sys.stdout though.
426
def __init__(self, url=None, pagename=''):
428
url = 'http://localhost:0/' # just some somehow valid dummy URL
429
environ = create_environ(base_url=url) # XXX not sure about base_url, but makes "make underlay" work
430
environ['HTTP_USER_AGENT'] = 'CLI/Script'
431
environ['wsgi.input'] = sys.stdin
432
request = Request(environ)
433
super(ScriptContext, self).__init__(request)
434
from MoinMoin import wsgiapp
437
def write(self, *data):
439
if isinstance(d, unicode):
440
d = d.encode(config.charset)