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

« back to all changes in this revision

Viewing changes to MoinMoin/web/contexts.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 - Context objects which are passed thru instead of the classic
4
 
               request objects. Currently contains legacy wrapper code for
5
 
               a single request object.
6
 
 
7
 
    @copyright: 2008-2008 MoinMoin:FlorianKrupicka
8
 
    @license: GNU GPL, see COPYING for details.
9
 
"""
10
 
 
11
 
import time, inspect, StringIO, sys, warnings
12
 
 
13
 
from werkzeug import Headers, http_date, create_environ, redirect, abort
14
 
from werkzeug.exceptions import Unauthorized, NotFound
15
 
 
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
24
 
 
25
 
from MoinMoin import log
26
 
logging = log.getLogger(__name__)
27
 
NoDefault = object()
28
 
 
29
 
class EnvironProxy(property):
30
 
    """ Proxy attribute lookups to keys in the environ. """
31
 
    def __init__(self, name, default=NoDefault):
32
 
        """
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.
37
 
 
38
 
        @param name: key (or factory for convenience)
39
 
        @param default: literal object or callable
40
 
        """
41
 
        if not isinstance(name, basestring):
42
 
            default = name
43
 
            name = default.__name__
44
 
        self.name = 'moin.' + name
45
 
        self.default = default
46
 
        property.__init__(self, self.get, self.set, self.delete)
47
 
 
48
 
    def get(self, obj):
49
 
        if self.name in obj.environ:
50
 
            res = obj.environ[self.name]
51
 
        else:
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))
57
 
            else:
58
 
                res = obj.environ.setdefault(self.name, factory)
59
 
        return res
60
 
 
61
 
    def set(self, obj, value):
62
 
        obj.environ[self.name] = value
63
 
 
64
 
    def delete(self, obj):
65
 
        del obj.environ[self.name]
66
 
 
67
 
    def __repr__(self):
68
 
        return "<%s for '%s'>" % (self.__class__.__name__,
69
 
                                  self.name)
70
 
 
71
 
class Context(object):
72
 
    """ Standard implementation for the context interface.
73
 
 
74
 
    This one wraps up a Moin-Request object and the associated
75
 
    environ and also keeps track of it's changes.
76
 
    """
77
 
    def __init__(self, request):
78
 
        assert isinstance(request, Request)
79
 
 
80
 
        self.request = request
81
 
        self.environ = environ = request.environ
82
 
        self.personalities = self.environ.setdefault(
83
 
            'context.personalities', []
84
 
        )
85
 
        self.personalities.append(self.__class__.__name__)
86
 
 
87
 
    def become(self, cls):
88
 
        """ Become another context, based on given class.
89
 
 
90
 
        @param cls: class to change to, must be a sister class
91
 
        @rtype: boolean
92
 
        @return: wether a class change took place
93
 
        """
94
 
        if self.__class__ is cls:
95
 
            return False
96
 
        else:
97
 
            self.personalities.append(cls)
98
 
            self.__class__ = cls
99
 
            return True
100
 
 
101
 
    def __repr__(self):
102
 
        return "<%s %r>" % (self.__class__.__name__, self.personalities)
103
 
 
104
 
class BaseContext(Context):
105
 
    """ Implements a basic context, that provides some common attributes.
106
 
    Most attributes are lazily initialized via descriptors. """
107
 
 
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'))
112
 
 
113
 
    lang = EnvironProxy('lang')
114
 
    content_lang = EnvironProxy('content_lang', lambda o: o.cfg.language_default)
115
 
    current_lang = EnvironProxy('current_lang')
116
 
 
117
 
    html_formatter = EnvironProxy('html_formatter', lambda o: text_html.Formatter(o))
118
 
    formatter = EnvironProxy('formatter', lambda o: o.html_formatter)
119
 
 
120
 
    page = EnvironProxy('page', None)
121
 
 
122
 
    # now the more complex factories
123
 
    def cfg(self):
124
 
        if self.request.given_config is not None:
125
 
            return self.request.given_config('MoinMoin._tests.wikiconfig')
126
 
        try:
127
 
            self.clock.start('load_multi_cfg')
128
 
            cfg = multiconfig.getConfig(self.request.url)
129
 
            self.clock.stop('load_multi_cfg')
130
 
            return cfg
131
 
        except error.NoConfigMatchedError:
132
 
            raise NotFound('<p>No wiki configuration matching the URL found!</p>')
133
 
    cfg = EnvironProxy(cfg)
134
 
 
135
 
    def getText(self):
136
 
        lang = self.lang
137
 
        def _(text, i18n=i18n, request=self, lang=lang, **kw):
138
 
            return i18n.getText(text, request, lang, **kw)
139
 
        return _
140
 
 
141
 
    getText = property(getText)
142
 
    _ = getText
143
 
 
144
 
    def isSpiderAgent(self):
145
 
        """ Simple check if useragent is a spider bot. """
146
 
        cfg = self.cfg
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
150
 
        return False
151
 
    isSpiderAgent = EnvironProxy(isSpiderAgent)
152
 
 
153
 
    def rootpage(self):
154
 
        from MoinMoin.Page import RootPage
155
 
        return RootPage(self)
156
 
    rootpage = EnvironProxy(rootpage)
157
 
 
158
 
    def rev(self):
159
 
        try:
160
 
            return int(self.values['rev'])
161
 
        except:
162
 
            return None
163
 
    rev = EnvironProxy(rev)
164
 
 
165
 
    def _theme(self):
166
 
        self.initTheme()
167
 
        return self.theme
168
 
    theme = EnvironProxy('theme', _theme)
169
 
 
170
 
    # finally some methods to act on those attributes
171
 
    def setContentLanguage(self, lang):
172
 
        """ Set the content language, used for the content div
173
 
 
174
 
        Actions that generate content in the user language, like search,
175
 
        should set the content direction to the user language before they
176
 
        call send_title!
177
 
        """
178
 
        self.content_lang = lang
179
 
        self.current_lang = lang
180
 
 
181
 
    def initTheme(self):
182
 
        """ Set theme - forced theme, user theme or wiki default """
183
 
        if self.cfg.theme_force:
184
 
            theme_name = self.cfg.theme_default
185
 
        else:
186
 
            theme_name = self.user.theme_name
187
 
        load_theme_fallback(self, theme_name)
188
 
 
189
 
 
190
 
class HTTPContext(BaseContext):
191
 
    """ Context that holds attributes and methods for manipulation of
192
 
    incoming and outgoing HTTP data. """
193
 
 
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())
198
 
 
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):
203
 
            self.name = name
204
 
            property.__init__(self, self.get, self.set, self.delete)
205
 
        def get(self, obj):
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)
211
 
 
212
 
    mimetype = _proxy('mimetype')
213
 
    content_type = _proxy('content_type')
214
 
    status = _proxy('status')
215
 
    status_code = _proxy('status_code')
216
 
 
217
 
    del _proxy
218
 
 
219
 
    # proxy further attribute lookups to the underlying request first
220
 
    def __getattr__(self, name):
221
 
        try:
222
 
            return getattr(self.request, name)
223
 
        except AttributeError, e:
224
 
            return super(HTTPContext, self).__getattribute__(name)
225
 
 
226
 
    # methods regarding manipulation of HTTP related data
227
 
    def read(self, n=None):
228
 
        """ Read n bytes (or everything) from input stream. """
229
 
        if n is None:
230
 
            return self.request.stream.read()
231
 
        else:
232
 
            return self.request.stream.read(n)
233
 
 
234
 
    def makeForbidden(self, resultcode, msg):
235
 
        status = {401: Unauthorized,
236
 
                  403: Forbidden,
237
 
                  404: NotFound,
238
 
                  503: SurgeProtection}
239
 
        raise status[resultcode](msg)
240
 
 
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)
245
 
 
246
 
    def disableHttpCaching(self, level=1):
247
 
        """ Prevent caching of pages that should not be cached.
248
 
 
249
 
        level == 1 means disabling caching when we have a cookie set
250
 
        level == 2 means completely disabling caching (used by Page*Editor)
251
 
 
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.
255
 
 
256
 
        AVOID using no-cache and no-store for attachments as it is completely broken on IE!
257
 
 
258
 
        Details: http://support.microsoft.com/support/kb/articles/Q234/0/67.ASP
259
 
        """
260
 
        if level == 1 and self.headers.get('Pragma') == 'no-cache':
261
 
            return
262
 
 
263
 
        if level == 1:
264
 
            self.headers['Cache-Control'] = 'private, must-revalidate, max-age=10'
265
 
        elif level == 2:
266
 
            self.headers['Cache-Control'] = 'no-cache'
267
 
            self.headers['Pragma'] = 'no-cache'
268
 
        self.request.expires = time.time() - 3600 * 24 * 365
269
 
 
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))
277
 
 
278
 
    def http_user_agent(self):
279
 
        return self.environ.get('HTTP_USER_AGENT', '')
280
 
    http_user_agent = EnvironProxy(http_user_agent)
281
 
 
282
 
    def http_referer(self):
283
 
        return self.environ.get('HTTP_REFERER', '')
284
 
    http_referer = EnvironProxy(http_referer)
285
 
 
286
 
    # the output related methods
287
 
    def write(self, *data):
288
 
        """ Write to output stream. """
289
 
        self.request.out_stream.writelines(data)
290
 
 
291
 
    def redirectedOutput(self, function, *args, **kw):
292
 
        """ Redirect output during function, return redirected output """
293
 
        buf = StringIO.StringIO()
294
 
        self.redirect(buf)
295
 
        try:
296
 
            function(*args, **kw)
297
 
        finally:
298
 
            self.redirect()
299
 
        text = buf.getvalue()
300
 
        buf.close()
301
 
        return text
302
 
 
303
 
    def redirect(self, file=None):
304
 
        """ Redirect output to file, or restore saved output """
305
 
        if file:
306
 
            self.writestack.append(self.write)
307
 
            self.write = file.write
308
 
        else:
309
 
            self.write = self.writestack.pop()
310
 
 
311
 
    def send_file(self, fileobj, bufsize=8192, do_flush=None):
312
 
        """ Send a file to the output stream.
313
 
 
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?
317
 
        """
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')
324
 
 
325
 
    # fully deprecated functions, with warnings
326
 
    def getScriptname(self):
327
 
        warnings.warn(
328
 
            "request.getScriptname() is deprecated, please use the request's script_root property.",
329
 
            DeprecationWarning)
330
 
        return self.request.script_root
331
 
 
332
 
    def getBaseURL(self):
333
 
        warnings.warn(
334
 
            "request.getBaseURL() is deprecated, please use the request's "
335
 
            "url_root property or the abs_href object if urls should be generated.",
336
 
            DeprecationWarning)
337
 
        return self.request.url_root
338
 
 
339
 
    def getQualifiedURL(self, uri=''):
340
 
        """ Return an absolute URL starting with schema and host.
341
 
 
342
 
        Already qualified urls are returned unchanged.
343
 
 
344
 
        @param uri: server rooted uri e.g /scriptname/pagename.
345
 
                    It must start with a slash. Must be ascii and url encoded.
346
 
        """
347
 
        import urlparse
348
 
        scheme = urlparse.urlparse(uri)[0]
349
 
        if scheme:
350
 
            return uri
351
 
 
352
 
        host_url = self.request.host_url.rstrip('/')
353
 
        result = "%s%s" % (host_url, uri)
354
 
 
355
 
        # This might break qualified urls in redirects!
356
 
        # e.g. mapping 'http://netloc' -> '/'
357
 
        result = wikiutil.mapURL(self, result)
358
 
        return result
359
 
 
360
 
class AuxilaryMixin(object):
361
 
    """
362
 
    Mixin for diverse attributes and methods that aren't clearly assignable
363
 
    to a particular phase of the request.
364
 
    """
365
 
 
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)
370
 
 
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: {})
377
 
 
378
 
    def uid_generator(self):
379
 
        pagename = None
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)
384
 
 
385
 
    def dicts(self):
386
 
        """ Lazy initialize the dicts on the first access """
387
 
        dicts = self.cfg.dicts(self)
388
 
        return dicts
389
 
    dicts = EnvironProxy(dicts)
390
 
 
391
 
    def groups(self):
392
 
        """ Lazy initialize the groups on the first access """
393
 
        groups = self.cfg.groups(self)
394
 
        return groups
395
 
    groups = EnvironProxy(groups)
396
 
 
397
 
    def reset(self):
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
403
 
 
404
 
    def getPragma(self, key, defval=None):
405
 
        """ Query a pragma value (#pragma processing instruction)
406
 
 
407
 
            Keys are not case-sensitive.
408
 
        """
409
 
        return self.pragma.get(key.lower(), defval)
410
 
 
411
 
    def setPragma(self, key, value):
412
 
        """ Set a pragma value (#pragma processing instruction)
413
 
 
414
 
            Keys are not case-sensitive.
415
 
        """
416
 
        self.pragma[key.lower()] = value
417
 
 
418
 
class XMLRPCContext(HTTPContext, AuxilaryMixin):
419
 
    """ Context to act during a XMLRPC request. """
420
 
 
421
 
class AllContext(HTTPContext, AuxilaryMixin):
422
 
    """ Catchall context to be able to quickly test old Moin code. """
423
 
 
424
 
class ScriptContext(AllContext):
425
 
    """ Context to act in scripting environments (e.g. former request_cli).
426
 
 
427
 
    For input, sys.stdin is used as 'wsgi.input', output is written directly
428
 
    to sys.stdout though.
429
 
    """
430
 
    def __init__(self, url=None, pagename=''):
431
 
        if url is None:
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
439
 
        wsgiapp.init(self)
440
 
 
441
 
    def write(self, *data):
442
 
        for d in data:
443
 
            if isinstance(d, unicode):
444
 
                d = d.encode(config.charset)
445
 
            else:
446
 
                d = str(d)
447
 
            sys.stdout.write(d)