1
# -*- coding: iso-8859-1 -*-
3
MoinMoin - Utility functions for the web-layer
5
@copyright: 2003-2008 MoinMoin:ThomasWaldmann,
6
2008-2008 MoinMoin:FlorianKrupicka
7
@license: GNU GPL, see COPYING for details.
11
from werkzeug import abort, redirect, cookie_date, Response
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
19
logging = log.getLogger(__name__)
21
def check_forbidden(request):
22
""" Simple action and host access checks
24
Spider agents are checked against the called actions,
25
hosts against the blacklist. Raises Forbidden if triggered.
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:
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)
40
if remote_addr == host:
41
logging.debug("hosts_deny (ip): %s" % remote_addr)
45
def check_surge_protect(request, kick=False):
46
""" Check for excessive requests
48
Raises a SurgeProtection exception on wiki overuse.
50
@param request: a moin request object
52
limits = request.cfg.surge_action_limits
56
remote_addr = request.remote_addr or ''
57
if remote_addr.startswith('127.'):
60
validuser = request.user.valid
61
current_id = validuser and request.user.name or remote_addr
62
current_action = request.action
64
default_limit = limits.get('default', (30, 60))
66
now = int(time.time())
68
surge_detected = False
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)
74
data = cache.content()
75
data = data.split("\n")
78
id, t, action, surge_indicator = line.split("\t")
80
maxnum, dt = limits.get(action, default_limit)
82
events = surgedict.setdefault(id, {})
83
timestamps = events.setdefault(action, [])
84
timestamps.append((t, surge_indicator))
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
93
surge_indicator = surge_detected and "!" or ""
94
timestamps.append((now, surge_indicator))
96
if len(timestamps) < maxnum * 2:
97
timestamps.append((now + request.cfg.surge_lockout_time, surge_indicator)) # continue like that and get locked out
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, [])
105
if kick: # ban this guy, NOW
106
timestamps.extend([(now + request.cfg.surge_lockout_time, "!")] * (2 * maxnum))
108
surge_detected = surge_detected or len(timestamps) > maxnum
110
surge_indicator = surge_detected and "!" or ""
111
timestamps.append((now, surge_indicator))
113
if len(timestamps) < maxnum * 2:
114
timestamps.append((now + request.cfg.surge_lockout_time, surge_indicator)) # continue like that and get locked out
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)
123
except StandardError:
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)
130
raise SurgeProtection(retry_after=request.cfg.surge_lockout_time)
134
def redirect_last_visited(request):
135
pagetrail = request.user.getTrail()
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)
144
url = Page(request, pagename).url(request)
146
# Or to localized FrontPage
147
url = wikiutil.getFrontPage(request).url(request)
148
url = request.getQualifiedURL(url)
149
return abort(redirect(url))
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
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.
166
self.unique_stack.append((self.page_ids, self.include_id))
167
self.include_id, pids = self.include_stack[-1]
169
for namespace in pids:
170
self.page_ids[namespace] = pids[namespace].copy()
174
Used by the TOC macro to reset the ID namespaces after
175
having parsed the page for TOC generation and after
178
self.page_ids, self.include_id = self.unique_stack.pop()
179
return self.page_ids, self.include_id
181
def begin(self, base):
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.
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
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().
203
self.include_id, pids = self.include_stack.pop()
205
def __call__(self, base, namespace=None):
207
Generates a unique ID using a given base name. Appends a running count to the base.
209
Needs to stay deterministic!
211
@param base: the base of the id
213
@param namespace: the namespace for the ID, used when including pages
215
@returns: a unique (relatively to the namespace) ID
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
226
return u'%s-%d' % (base, count)
229
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
231
<head><title>%(title)s</title></head>
232
<body><h1>%(title)s</h1>
237
def fatal_response(error):
238
""" Create a response from MoinMoin.error.FatalError instances. """
239
html = FATALTMPL % dict(title=error.__class__.__name__,
241
return Response(html, status=500, mimetype='text/html')