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

« back to all changes in this revision

Viewing changes to MoinMoin/web/utils.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 - Utility functions for the web-layer
4
 
 
5
 
    @copyright: 2003-2008 MoinMoin:ThomasWaldmann,
6
 
                2008-2008 MoinMoin:FlorianKrupicka
7
 
    @license: GNU GPL, see COPYING for details.
8
 
"""
9
 
import time
10
 
 
11
 
from werkzeug import abort, redirect, cookie_date, Response
12
 
 
13
 
from MoinMoin import caching
14
 
from MoinMoin import log
15
 
from MoinMoin import wikiutil
16
 
from MoinMoin.Page import Page
17
 
from MoinMoin.web.exceptions import Forbidden, SurgeProtection
18
 
 
19
 
logging = log.getLogger(__name__)
20
 
 
21
 
def check_forbidden(request):
22
 
    """ Simple action and host access checks
23
 
 
24
 
    Spider agents are checked against the called actions,
25
 
    hosts against the blacklist. Raises Forbidden if triggered.
26
 
    """
27
 
    args = request.args
28
 
    action = args.get('action')
29
 
    if ((args or request.method != 'GET') and
30
 
        action not in ['rss_rc', 'show', 'sitemap'] and
31
 
        not (action == 'AttachFile' and args.get('do') == 'get')):
32
 
        if request.isSpiderAgent:
33
 
            raise Forbidden()
34
 
    if request.cfg.hosts_deny:
35
 
        remote_addr = request.remote_addr
36
 
        for host in request.cfg.hosts_deny:
37
 
            if host[-1] == '.' and remote_addr.startswith(host):
38
 
                logging.debug("hosts_deny (net): %s" % remote_addr)
39
 
                raise Forbidden()
40
 
            if remote_addr == host:
41
 
                logging.debug("hosts_deny (ip): %s" % remote_addr)
42
 
                raise Forbidden()
43
 
    return False
44
 
 
45
 
def check_surge_protect(request, kick=False):
46
 
    """ Check for excessive requests
47
 
 
48
 
    Raises a SurgeProtection exception on wiki overuse.
49
 
 
50
 
    @param request: a moin request object
51
 
    """
52
 
    limits = request.cfg.surge_action_limits
53
 
    if not limits:
54
 
        return False
55
 
 
56
 
    remote_addr = request.remote_addr or ''
57
 
    if remote_addr.startswith('127.'):
58
 
        return False
59
 
 
60
 
    validuser = request.user.valid
61
 
    current_id = validuser and request.user.name or remote_addr
62
 
    current_action = request.action
63
 
 
64
 
    default_limit = limits.get('default', (30, 60))
65
 
 
66
 
    now = int(time.time())
67
 
    surgedict = {}
68
 
    surge_detected = False
69
 
 
70
 
    try:
71
 
        # if we have common farm users, we could also use scope='farm':
72
 
        cache = caching.CacheEntry(request, 'surgeprotect', 'surge-log', scope='wiki', use_encode=True)
73
 
        if cache.exists():
74
 
            data = cache.content()
75
 
            data = data.split("\n")
76
 
            for line in data:
77
 
                try:
78
 
                    id, t, action, surge_indicator = line.split("\t")
79
 
                    t = int(t)
80
 
                    maxnum, dt = limits.get(action, default_limit)
81
 
                    if t >= now - dt:
82
 
                        events = surgedict.setdefault(id, {})
83
 
                        timestamps = events.setdefault(action, [])
84
 
                        timestamps.append((t, surge_indicator))
85
 
                except StandardError:
86
 
                    pass
87
 
 
88
 
        maxnum, dt = limits.get(current_action, default_limit)
89
 
        events = surgedict.setdefault(current_id, {})
90
 
        timestamps = events.setdefault(current_action, [])
91
 
        surge_detected = len(timestamps) > maxnum
92
 
 
93
 
        surge_indicator = surge_detected and "!" or ""
94
 
        timestamps.append((now, surge_indicator))
95
 
        if surge_detected:
96
 
            if len(timestamps) < maxnum * 2:
97
 
                timestamps.append((now + request.cfg.surge_lockout_time, surge_indicator)) # continue like that and get locked out
98
 
 
99
 
        if current_action not in ('cache', 'AttachFile', ): # don't add cache/AttachFile accesses to all or picture galleries will trigger SP
100
 
            current_action = 'all' # put a total limit on user's requests
101
 
            maxnum, dt = limits.get(current_action, default_limit)
102
 
            events = surgedict.setdefault(current_id, {})
103
 
            timestamps = events.setdefault(current_action, [])
104
 
 
105
 
            if kick: # ban this guy, NOW
106
 
                timestamps.extend([(now + request.cfg.surge_lockout_time, "!")] * (2 * maxnum))
107
 
 
108
 
            surge_detected = surge_detected or len(timestamps) > maxnum
109
 
 
110
 
            surge_indicator = surge_detected and "!" or ""
111
 
            timestamps.append((now, surge_indicator))
112
 
            if surge_detected:
113
 
                if len(timestamps) < maxnum * 2:
114
 
                    timestamps.append((now + request.cfg.surge_lockout_time, surge_indicator)) # continue like that and get locked out
115
 
 
116
 
        data = []
117
 
        for id, events in surgedict.items():
118
 
            for action, timestamps in events.items():
119
 
                for t, surge_indicator in timestamps:
120
 
                    data.append("%s\t%d\t%s\t%s" % (id, t, action, surge_indicator))
121
 
        data = "\n".join(data)
122
 
        cache.update(data)
123
 
    except StandardError:
124
 
        pass
125
 
 
126
 
    if surge_detected and validuser and request.user.auth_method in request.cfg.auth_methods_trusted:
127
 
        logging.info("Trusted user %s would have triggered surge protection if not trusted.", request.user.name)
128
 
        return False
129
 
    elif surge_detected:
130
 
        raise SurgeProtection(retry_after=request.cfg.surge_lockout_time)
131
 
    else:
132
 
        return False
133
 
 
134
 
def redirect_last_visited(request):
135
 
    pagetrail = request.user.getTrail()
136
 
    if pagetrail:
137
 
        # Redirect to last page visited
138
 
        last_visited = pagetrail[-1]
139
 
        wikiname, pagename = wikiutil.split_interwiki(last_visited)
140
 
        if wikiname != 'Self':
141
 
            wikitag, wikiurl, wikitail, error = wikiutil.resolve_interwiki(request, wikiname, pagename)
142
 
            url = wikiurl + wikiutil.quoteWikinameURL(wikitail)
143
 
        else:
144
 
            url = Page(request, pagename).url(request)
145
 
    else:
146
 
        # Or to localized FrontPage
147
 
        url = wikiutil.getFrontPage(request).url(request)
148
 
    url = request.getQualifiedURL(url)
149
 
    return abort(redirect(url))
150
 
 
151
 
class UniqueIDGenerator(object):
152
 
    def __init__(self, pagename=None):
153
 
        self.unique_stack = []
154
 
        self.include_stack = []
155
 
        self.include_id = None
156
 
        self.page_ids = {None: {}}
157
 
        self.pagename = pagename
158
 
 
159
 
    def push(self):
160
 
        """
161
 
        Used by the TOC macro, this ensures that the ID namespaces
162
 
        are reset to the status when the current include started.
163
 
        This guarantees that doing the ID enumeration twice results
164
 
        in the same results, on any level.
165
 
        """
166
 
        self.unique_stack.append((self.page_ids, self.include_id))
167
 
        self.include_id, pids = self.include_stack[-1]
168
 
        self.page_ids = {}
169
 
        for namespace in pids:
170
 
            self.page_ids[namespace] = pids[namespace].copy()
171
 
 
172
 
    def pop(self):
173
 
        """
174
 
        Used by the TOC macro to reset the ID namespaces after
175
 
        having parsed the page for TOC generation and after
176
 
        printing the TOC.
177
 
        """
178
 
        self.page_ids, self.include_id = self.unique_stack.pop()
179
 
        return self.page_ids, self.include_id
180
 
 
181
 
    def begin(self, base):
182
 
        """
183
 
        Called by the formatter when a document begins, which means
184
 
        that include causing nested documents gives us an include
185
 
        stack in self.include_id_stack.
186
 
        """
187
 
        pids = {}
188
 
        for namespace in self.page_ids:
189
 
            pids[namespace] = self.page_ids[namespace].copy()
190
 
        self.include_stack.append((self.include_id, pids))
191
 
        self.include_id = self(base)
192
 
        # if it's the page name then set it to None so we don't
193
 
        # prepend anything to IDs, but otherwise keep it.
194
 
        if self.pagename and self.pagename == self.include_id:
195
 
            self.include_id = None
196
 
 
197
 
    def end(self):
198
 
        """
199
 
        Called by the formatter when a document ends, restores
200
 
        the current include ID to the previous one and discards
201
 
        the page IDs state we kept around for push().
202
 
        """
203
 
        self.include_id, pids = self.include_stack.pop()
204
 
 
205
 
    def __call__(self, base, namespace=None):
206
 
        """
207
 
        Generates a unique ID using a given base name. Appends a running count to the base.
208
 
 
209
 
        Needs to stay deterministic!
210
 
 
211
 
        @param base: the base of the id
212
 
        @type base: unicode
213
 
        @param namespace: the namespace for the ID, used when including pages
214
 
 
215
 
        @returns: a unique (relatively to the namespace) ID
216
 
        @rtype: unicode
217
 
        """
218
 
        if not isinstance(base, unicode):
219
 
            base = unicode(str(base), 'ascii', 'ignore')
220
 
        if not namespace in self.page_ids:
221
 
            self.page_ids[namespace] = {}
222
 
        count = self.page_ids[namespace].get(base, -1) + 1
223
 
        self.page_ids[namespace][base] = count
224
 
        if not count:
225
 
            return base
226
 
        return u'%s-%d' % (base, count)
227
 
 
228
 
FATALTMPL = """
229
 
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
230
 
<html>
231
 
<head><title>%(title)s</title></head>
232
 
<body><h1>%(title)s</h1>
233
 
<pre>
234
 
%(body)s
235
 
</pre></body></html>
236
 
"""
237
 
def fatal_response(error):
238
 
    """ Create a response from MoinMoin.error.FatalError instances. """
239
 
    html = FATALTMPL % dict(title=error.__class__.__name__,
240
 
                            body=str(error))
241
 
    return Response(html, status=500, mimetype='text/html')