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.stream.read()
232
return self.request.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
# werkzeug >= 0.6 does iri-to-uri transform if it gets unicode, but our
273
# url is already url-quoted, so we better give it str to have same behaviour
274
# with werkzeug 0.5.x and 0.6.x:
275
url = str(url) # if url is unicode, it should contain ascii chars only
276
abort(redirect(url, code=code))
278
def http_user_agent(self):
279
return self.environ.get('HTTP_USER_AGENT', '')
280
http_user_agent = EnvironProxy(http_user_agent)
282
def http_referer(self):
283
return self.environ.get('HTTP_REFERER', '')
284
http_referer = EnvironProxy(http_referer)
286
# the output related methods
287
def write(self, *data):
288
""" Write to output stream. """
289
self.request.out_stream.writelines(data)
291
def redirectedOutput(self, function, *args, **kw):
292
""" Redirect output during function, return redirected output """
293
buf = StringIO.StringIO()
296
function(*args, **kw)
299
text = buf.getvalue()
303
def redirect(self, file=None):
304
""" Redirect output to file, or restore saved output """
306
self.writestack.append(self.write)
307
self.write = file.write
309
self.write = self.writestack.pop()
311
def send_file(self, fileobj, bufsize=8192, do_flush=None):
312
""" Send a file to the output stream.
314
@param fileobj: a file-like object (supporting read, close)
315
@param bufsize: size of chunks to read/write
316
@param do_flush: call flush after writing?
318
def simple_wrapper(fileobj, bufsize):
319
return iter(lambda: fileobj.read(bufsize), '')
320
file_wrapper = self.environ.get('wsgi.file_wrapper', simple_wrapper)
321
self.request.direct_passthrough = True
322
self.request.response = file_wrapper(fileobj, bufsize)
323
raise MoinMoinFinish('sent file')
325
# fully deprecated functions, with warnings
326
def getScriptname(self):
328
"request.getScriptname() is deprecated, please use the request's script_root property.",
330
return self.request.script_root
332
def getBaseURL(self):
334
"request.getBaseURL() is deprecated, please use the request's "
335
"url_root property or the abs_href object if urls should be generated.",
337
return self.request.url_root
339
def getQualifiedURL(self, uri=''):
340
""" Return an absolute URL starting with schema and host.
342
Already qualified urls are returned unchanged.
344
@param uri: server rooted uri e.g /scriptname/pagename.
345
It must start with a slash. Must be ascii and url encoded.
348
scheme = urlparse.urlparse(uri)[0]
352
host_url = self.request.host_url.rstrip('/')
353
result = "%s%s" % (host_url, uri)
355
# This might break qualified urls in redirects!
356
# e.g. mapping 'http://netloc' -> '/'
357
result = wikiutil.mapURL(self, result)
360
class AuxilaryMixin(object):
362
Mixin for diverse attributes and methods that aren't clearly assignable
363
to a particular phase of the request.
366
# several attributes used by other code to hold state across calls
367
_fmt_hd_counters = EnvironProxy('_fmt_hd_counters')
368
parsePageLinks_running = EnvironProxy('parsePageLinks_running', lambda o: {})
369
mode_getpagelinks = EnvironProxy('mode_getpagelinks', 0)
371
pragma = EnvironProxy('pragma', lambda o: {})
372
_login_messages = EnvironProxy('_login_messages', lambda o: [])
373
_login_multistage = EnvironProxy('_login_multistage', None)
374
_login_multistage_name = EnvironProxy('_login_multistage_name', None)
375
_setuid_real_user = EnvironProxy('_setuid_real_user', None)
376
pages = EnvironProxy('pages', lambda o: {})
378
def uid_generator(self):
380
if hasattr(self, 'page') and hasattr(self.page, 'page_name'):
381
pagename = self.page.page_name
382
return UniqueIDGenerator(pagename=pagename)
383
uid_generator = EnvironProxy(uid_generator)
386
""" Lazy initialize the dicts on the first access """
387
dicts = self.cfg.dicts(self)
389
dicts = EnvironProxy(dicts)
392
""" Lazy initialize the groups on the first access """
393
groups = self.cfg.groups(self)
395
groups = EnvironProxy(groups)
398
self.current_lang = self.cfg.language_default
399
if hasattr(self, '_fmt_hd_counters'):
400
del self._fmt_hd_counters
401
if hasattr(self, 'uid_generator'):
402
del self.uid_generator
404
def getPragma(self, key, defval=None):
405
""" Query a pragma value (#pragma processing instruction)
407
Keys are not case-sensitive.
409
return self.pragma.get(key.lower(), defval)
411
def setPragma(self, key, value):
412
""" Set a pragma value (#pragma processing instruction)
414
Keys are not case-sensitive.
416
self.pragma[key.lower()] = value
418
class XMLRPCContext(HTTPContext, AuxilaryMixin):
419
""" Context to act during a XMLRPC request. """
421
class AllContext(HTTPContext, AuxilaryMixin):
422
""" Catchall context to be able to quickly test old Moin code. """
424
class ScriptContext(AllContext):
425
""" Context to act in scripting environments (e.g. former request_cli).
427
For input, sys.stdin is used as 'wsgi.input', output is written directly
428
to sys.stdout though.
430
def __init__(self, url=None, pagename=''):
432
url = 'http://localhost:0/' # just some somehow valid dummy URL
433
environ = create_environ(base_url=url) # XXX not sure about base_url, but makes "make underlay" work
434
environ['HTTP_USER_AGENT'] = 'CLI/Script'
435
environ['wsgi.input'] = sys.stdin
436
request = Request(environ)
437
super(ScriptContext, self).__init__(request)
438
from MoinMoin import wsgiapp
441
def write(self, *data):
443
if isinstance(d, unicode):
444
d = d.encode(config.charset)