changeset 4329:d9d151ce2f6f

merged moin/1.8-wsgi-fkrupicka
author Thomas Waldmann <tw AT waldmann-edv DOT de>
date Tue, 23 Sep 2008 23:02:50 +0200
parents 99ed52b53e4e (current diff) 7eb12d1f0856 (diff)
children b6d5bf04b9e6
files MoinMoin/failure.py MoinMoin/request/__init__.py MoinMoin/request/_tests/test_request.py MoinMoin/request/request_cgi.py MoinMoin/request/request_cli.py MoinMoin/request/request_fcgi.py MoinMoin/request/request_modpython.py MoinMoin/request/request_standalone.py MoinMoin/request/request_twisted.py MoinMoin/request/request_wsgi.py MoinMoin/server/__init__.py MoinMoin/server/daemon.py MoinMoin/server/server_cgi.py MoinMoin/server/server_fastcgi.py MoinMoin/server/server_modpython.py MoinMoin/server/server_standalone.py MoinMoin/server/server_twisted.py MoinMoin/server/server_wsgi.py MoinMoin/support/cgitb.py MoinMoin/support/thfcgi.py wiki/server/moin.fcg wiki/server/moin_flup_wsgi.py wiki/server/moinmodpy.htaccess wiki/server/moinmodpy.py wiki/server/mointwisted wiki/server/mointwisted.cmd wiki/server/mointwisted.py wiki/server/wikiserver.py
diffstat 231 files changed, 22268 insertions(+), 6716 deletions(-) [+]
line wrap: on
line diff
--- a/MANIFEST.in	Tue Sep 23 00:44:45 2008 +0200
+++ b/MANIFEST.in	Tue Sep 23 23:02:50 2008 +0200
@@ -13,6 +13,9 @@
 # include stuff for translators
 recursive-include   MoinMoin/i18n *
 
+# include non-py stuff from werkzeug
+recursive-include   MoinMoin/support/werkzeug/debug *
+
 # contrib stuff
 recursive-include   contrib *
 
--- a/MoinMoin/Page.py	Tue Sep 23 00:44:45 2008 +0200
+++ b/MoinMoin/Page.py	Tue Sep 23 23:02:50 2008 +0200
@@ -109,7 +109,8 @@
             (for 'meta') or the complete cache ('pagelists').
             @param request: the request object
         """
-        elog = request.editlog
+        from MoinMoin.logfile import editlog
+        elog = editlog.EditLog(request)
         old_pos = self.log_pos
         new_pos, items = elog.news(old_pos)
         if items:
@@ -753,12 +754,13 @@
                 url = "%s/%s/%s" % (request.cfg.url_prefix_action, action, url)
             url = '%s?%s' % (url, querystr)
 
+        if not relative:
+            url = '%s/%s' % (request.script_root, url)
+
         # Add anchor
         if anchor:
             url = "%s#%s" % (url, wikiutil.url_quote_plus(anchor))
 
-        if not relative:
-            url = '%s/%s' % (request.getScriptname(), url)
         return url
 
     def link_to_raw(self, request, text, querystr=None, anchor=None, **kw):
@@ -963,26 +965,25 @@
             offer a dialogue to save it to disk (used by Save action).
         """
         request = self.request
-        request.setHttpHeader("Content-type: text/plain; charset=%s" % config.charset)
+        request.mimetype = 'text/plain'
         if self.exists():
             # use the correct last-modified value from the on-disk file
             # to ensure cacheability where supported. Because we are sending
             # RAW (file) content, the file mtime is correct as Last-Modified header.
-            request.setHttpHeader("Status: 200 OK")
-            request.setHttpHeader("Last-Modified: %s" % util.timefuncs.formathttpdate(os.path.getmtime(self._text_filename())))
+            request.status_code = 200
+            request.last_modified = os.path.getmtime(self._text_filename())
             text = self.encodeTextMimeType(self.body)
             #request.setHttpHeader("Content-Length: %d" % len(text))  # XXX WRONG! text is unicode obj, but we send utf-8!
             if content_disposition:
                 # TODO: fix the encoding here, plain 8 bit is not allowed according to the RFCs
                 # There is no solution that is compatible to IE except stripping non-ascii chars
                 filename_enc = "%s.txt" % self.page_name.encode(config.charset)
-                request.setHttpHeader('Content-Disposition: %s; filename="%s"' % (
-                                      content_disposition, filename_enc))
+                dispo_string = '%s; filename="%s"' % (content_disposition, filename_enc)
+                request.headers.add('Content-Disposition', dispo_string)
         else:
-            request.setHttpHeader('Status: 404 NOTFOUND')
+            request.status_code = 404
             text = u"Page %s not found." % self.page_name
 
-        request.emit_http_headers()
         request.write(text)
 
     def send_page(self, **keywords):
@@ -1007,7 +1008,7 @@
         send_special = keywords.get('send_special', False)
         print_mode = keywords.get('print_mode', 0)
         if print_mode:
-            media = 'media' in request.form and request.form['media'][0] or 'print'
+            media = request.form.get('media', 'print')
         else:
             media = 'screen'
         self.hilite_re = (keywords.get('hilite_re') or
@@ -1026,10 +1027,7 @@
             # redirect to another page
             # note that by including "action=show", we prevent endless looping
             # (see code in "request") or any cascaded redirection
-            request.http_redirect('%s/%s?action=show&redirect=%s' % (
-                request.getScriptname(),
-                wikiutil.quoteWikinameURL(pi['redirect']),
-                wikiutil.url_quote_plus(self.page_name, ''), ))
+            request.http_redirect(request.href(pi['redirect'], action='show', redirect=self.page_name))
             return
 
         # if necessary, load the formatter
@@ -1076,12 +1074,12 @@
         page_exists = self.exists()
         if not content_only:
             if emit_headers:
-                request.setHttpHeader("Content-Type: %s; charset=%s" % (self.output_mimetype, self.output_charset))
+                request.content_type = "%s; charset=%s" % (self.output_mimetype, self.output_charset)
                 if page_exists:
                     if not request.user.may.read(self.page_name):
-                        request.setHttpHeader('Status: 403 Permission Denied')
+                        request.status_code = 403
                     else:
-                        request.setHttpHeader('Status: 200 OK')
+                        request.status_code = 200
                     if not request.cacheable:
                         # use "nocache" headers if we're using a method that is not simply "display"
                         request.disableHttpCaching(level=2)
@@ -1096,8 +1094,7 @@
                         #request.setHttpHeader("Last-Modified: %s" % util.timefuncs.formathttpdate(lastmod))
                         pass
                 else:
-                    request.setHttpHeader('Status: 404 NOTFOUND')
-                request.emit_http_headers()
+                    request.status_code = 404
 
             if not page_exists and self.request.isSpiderAgent:
                 # don't send any 404 content to bots
@@ -1117,7 +1114,7 @@
                 # This redirect message is very annoying.
                 # Less annoying now without the warning sign.
                 if 'redirect' in request.form:
-                    redir = request.form['redirect'][0]
+                    redir = request.form['redirect']
                     request.theme.add_msg('<strong>%s</strong><br>' % (
                         _('Redirected from page "%(page)s"') % {'page':
                             wikiutil.link_tag(request, wikiutil.quoteWikinameURL(redir) + "?action=show", self.formatter.text(redir))}), "info")
--- a/MoinMoin/PageEditor.py	Tue Sep 23 00:44:45 2008 +0200
+++ b/MoinMoin/PageEditor.py	Tue Sep 23 23:02:50 2008 +0200
@@ -197,12 +197,11 @@
 
         # Emmit http_headers after checks (send_page)
         request.disableHttpCaching(level=2)
-        request.emit_http_headers()
 
         # check if we want to load a draft
         use_draft = None
         if 'button_load_draft' in form:
-            wanted_draft_timestamp = int(form.get('draft_ts', ['0'])[0])
+            wanted_draft_timestamp = int(form.get('draft_ts', '0'))
             if wanted_draft_timestamp:
                 draft = self._load_draft()
                 if draft is not None:
@@ -214,7 +213,7 @@
         if use_draft is not None:
             title = _('Draft of "%(pagename)s"')
             # Propagate original revision
-            rev = int(form['draft_rev'][0])
+            rev = int(form['draft_rev'])
             self.set_raw_body(use_draft, modified=1)
             preview = use_draft
         elif preview is None:
@@ -234,7 +233,7 @@
 
         # get request parameters
         try:
-            text_rows = int(form['rows'][0])
+            text_rows = int(form['rows'])
         except StandardError:
             text_rows = self.cfg.edit_rows
             if request.user.valid:
@@ -278,7 +277,7 @@
             raw_body = self.get_raw_body()
         elif 'template' in form:
             # If the page does not exist, we try to get the content from the template parameter.
-            template_page = wikiutil.unquoteWikiname(form['template'][0])
+            template_page = wikiutil.unquoteWikiname(form['template'])
             if request.user.may.read(template_page):
                 raw_body = Page(request, template_page).get_raw_body()
                 if raw_body:
@@ -332,10 +331,9 @@
             raw_body = _('Describe %s here.') % (self.page_name, )
 
         # send form
-        request.write('<form id="editor" method="post" action="%s/%s#preview" onSubmit="flgChange = false;">' % (
-            request.getScriptname(),
-            wikiutil.quoteWikinameURL(self.page_name),
-            ))
+        request.write('<form id="editor" method="post" action="%s#preview" onSubmit="flgChange = false;">' % (
+                request.href(self.page_name)
+        ))
 
         # yet another weird workaround for broken IE6 (it expands the text
         # editor area to the right after you begin to type...). IE sucks...
@@ -351,7 +349,7 @@
         request.write('<input type="hidden" name="ticket" value="%s">' % wikiutil.createTicket(request))
 
         # Save backto in a hidden input
-        backto = form.get('backto', [None])[0]
+        backto = form.get('backto')
         if backto:
             request.write(unicode(html.INPUT(type="hidden", name="backto", value=backto)))
 
@@ -410,7 +408,7 @@
     document.write('<label for="chktrivialtop">%(label)s</label>');
     //-->
 </script> ''' % {
-                'checked': ('', 'checked')[form.get('trivial', ['0'])[0] == '1'],
+                'checked': ('', 'checked')[form.get('trivial', '0') == '1'],
                 'label': _("Trivial change"),
             })
 
@@ -460,7 +458,7 @@
 <label for="chktrivial">%(label)s</label>
 
 ''' % {
-                'checked': ('', 'checked')[form.get('trivial', ['0'])[0] == '1'],
+                'checked': ('', 'checked')[form.get('trivial', '0') == '1'],
                 'label': _("Trivial change"),
                 })
 
@@ -469,7 +467,7 @@
 <input type="checkbox" name="rstrip" id="chkrstrip" value="1" %(checked)s>
 <label for="chkrstrip">%(label)s</label>
 ''' % {
-            'checked': ('', 'checked')[form.get('rstrip', ['0'])[0] == '1'],
+            'checked': ('', 'checked')[form.get('rstrip', '0') == '1'],
             'label': _('Remove trailing whitespace from each line')
             })
         request.write("</p>")
@@ -514,7 +512,7 @@
         self._save_draft(newtext, rev) # shall we really save a draft on CANCEL?
         self.lock.release()
 
-        backto = request.form.get('backto', [None])[0]
+        backto = request.form.get('backto')
         if backto:
             pg = Page(request, backto)
             request.http_redirect(pg.url(request, relative=False))
@@ -672,7 +670,7 @@
 
             # Remove cache entry (if exists)
             pg = Page(self.request, self.page_name)
-            key = self.request.form.get('key', ['text_html'])[0]
+            key = self.request.form.get('key', 'text_html')
             caching.CacheEntry(self.request, pg, key, scope='item').remove()
             caching.CacheEntry(self.request, pg, "pagelinks", scope='item').remove()
 
--- a/MoinMoin/PageGraphicalEditor.py	Tue Sep 23 00:44:45 2008 +0200
+++ b/MoinMoin/PageGraphicalEditor.py	Tue Sep 23 23:02:50 2008 +0200
@@ -52,7 +52,6 @@
         form = request.form
         _ = self._
         request.disableHttpCaching(level=2)
-        request.emit_http_headers()
 
         raw_body = ''
         msg = None
@@ -90,7 +89,7 @@
         # check if we want to load a draft
         use_draft = None
         if 'button_load_draft' in form:
-            wanted_draft_timestamp = int(form.get('draft_ts', ['0'])[0])
+            wanted_draft_timestamp = int(form.get('draft_ts', '0'))
             if wanted_draft_timestamp:
                 draft = self._load_draft()
                 if draft is not None:
@@ -102,7 +101,7 @@
         if use_draft is not None:
             title = _('Draft of "%(pagename)s"')
             # Propagate original revision
-            rev = int(form['draft_rev'][0])
+            rev = int(form['draft_rev'])
             self.set_raw_body(use_draft, modified=1)
             preview = use_draft
         elif preview is None:
@@ -122,7 +121,7 @@
 
         # get request parameters
         try:
-            text_rows = int(form['rows'][0])
+            text_rows = int(form['rows'])
         except StandardError:
             text_rows = self.cfg.edit_rows
             if request.user.valid:
@@ -168,7 +167,7 @@
             raw_body = self.get_raw_body()
         elif 'template' in form:
             # If the page does not exist, we try to get the content from the template parameter.
-            template_page = wikiutil.unquoteWikiname(form['template'][0])
+            template_page = wikiutil.unquoteWikiname(form['template'])
             if request.user.may.read(template_page):
                 raw_body = Page(request, template_page).get_raw_body()
                 if raw_body:
@@ -222,9 +221,8 @@
             raw_body = _('Describe %s here.') % (self.page_name, )
 
         # send form
-        request.write('<form id="editor" method="post" action="%s/%s#preview">' % (
-            request.getScriptname(),
-            wikiutil.quoteWikinameURL(self.page_name),
+        request.write('<form id="editor" method="post" action="%s#preview">' % (
+                request.href(self.page_name)
             ))
 
         # yet another weird workaround for broken IE6 (it expands the text
@@ -245,7 +243,7 @@
         request.write('<input type="hidden" name="ticket" value="%s">' % wikiutil.createTicket(request))
 
         # Save backto in a hidden input
-        backto = form.get('backto', [None])[0]
+        backto = form.get('backto')
         if backto:
             request.write(unicode(html.INPUT(type="hidden", name="backto", value=backto)))
 
@@ -296,7 +294,7 @@
 <input type="checkbox" name="trivial" id="chktrivialtop" value="1" %(checked)s onclick="toggle_trivial(this)">
 <label for="chktrivialtop">%(label)s</label>
 ''' % {
-          'checked': ('', 'checked')[form.get('trivial', ['0'])[0] == '1'],
+          'checked': ('', 'checked')[form.get('trivial', '0') == '1'],
           'label': _("Trivial change"),
        })
 
@@ -313,7 +311,7 @@
         url_prefix_local = request.cfg.url_prefix_local
         wikipage = wikiutil.quoteWikinameURL(self.page_name)
         fckbasepath = url_prefix_local + '/applets/FCKeditor'
-        wikiurl = request.getScriptname()
+        wikiurl = request.script_root
         if not wikiurl or wikiurl[-1] != '/':
             wikiurl += '/'
         themepath = '%s/%s' % (url_prefix_static, request.theme.name)
@@ -375,7 +373,7 @@
 &nbsp;
 <input type="checkbox" name="trivial" id="chktrivial" value="1" %(checked)s onclick="toggle_trivial(this)">
 <label for="chktrivial">%(label)s</label> ''' % {
-                'checked': ('', 'checked')[form.get('trivial', ['0'])[0] == '1'],
+                'checked': ('', 'checked')[form.get('trivial', '0') == '1'],
                 'label': _("Trivial change"),
                 })
 
@@ -384,7 +382,7 @@
 <input type="checkbox" name="rstrip" id="chkrstrip" value="1" %(checked)s>
 <label for="chkrstrip">%(label)s</label>
 </p> ''' % {
-            'checked': ('', 'checked')[form.get('rstrip', ['0'])[0] == '1'],
+            'checked': ('', 'checked')[form.get('rstrip', '0') == '1'],
             'label': _('Remove trailing whitespace from each line')
             })
 
--- a/MoinMoin/_tests/ldap_testbase.py	Tue Sep 23 00:44:45 2008 +0200
+++ b/MoinMoin/_tests/ldap_testbase.py	Tue Sep 23 23:02:50 2008 +0200
@@ -41,7 +41,7 @@
 SLAPD_EXECUTABLE = 'slapd'  # filename of LDAP server executable - if it is not
                             # in your PATH, you have to give full path/filename.
 
-import os, shutil, tempfile, time
+import os, shutil, tempfile, time, base64, md5
 from StringIO import StringIO
 import signal
 
@@ -187,6 +187,8 @@
         f.write(db_config)
         f.close()
 
+        rootpw = '{MD5}' + base64.b64encode(md5.new(self.rootpw).digest())
+
         # create slapd.conf from content template in slapd_config
         slapd_config = slapd_config % {
             'ldap_dir': self.ldap_dir,
@@ -194,7 +196,7 @@
             'schema_dir': self.schema_dir,
             'basedn': self.basedn,
             'rootdn': self.rootdn,
-            'rootpw': self.rootpw,
+            'rootpw': rootpw,
         }
         if isinstance(slapd_config, unicode):
             slapd_config = slapd_config.encode(self.coding)
--- a/MoinMoin/_tests/ldap_testdata.py	Tue Sep 23 00:44:45 2008 +0200
+++ b/MoinMoin/_tests/ldap_testdata.py	Tue Sep 23 23:02:50 2008 +0200
@@ -78,14 +78,16 @@
 objectClass: account
 objectClass: simpleSecurityObject
 uid: usera
-userPassword: usera
+# this is md5 encoded 'usera' for password
+userPassword: {MD5}aXqgOSc5gSW7YoLi9BSmvg==
 
 dn: uid=userb,ou=Unit B,ou=Users,ou=testing,dc=example,dc=org
 cn: Vorname Nachname
 objectClass: inetOrgPerson
 sn: Nachname
 uid: userb
-userPassword: userb
+# this is md5 encoded 'userb' for password
+userPassword: {MD5}ThvfQsM7OQFjqSUQOX2XsA==
 
 dn: cn=Group A,ou=Groups,ou=testing,dc=example,dc=org
 cn: Group A
--- a/MoinMoin/_tests/test_sourcecode.py	Tue Sep 23 00:44:45 2008 +0200
+++ b/MoinMoin/_tests/test_sourcecode.py	Tue Sep 23 23:02:50 2008 +0200
@@ -19,6 +19,7 @@
 EXCLUDE = [
     '/contrib/DesktopEdition/setup_py2exe.py', # has crlf
     '/contrib/TWikiDrawPlugin', # 3rd party java stuff
+    '/contrib/flup-server', # 3rd party WSGI adapters
     '/MoinMoin/support', # 3rd party libs or non-broken stdlib stuff
     '/wiki/htdocs/applets/FCKeditor', # 3rd party GUI editor
     '/tests/wiki', # this is our test wiki
--- a/MoinMoin/_tests/test_user.py	Tue Sep 23 00:44:45 2008 +0200
+++ b/MoinMoin/_tests/test_user.py	Tue Sep 23 23:02:50 2008 +0200
@@ -38,11 +38,11 @@
 
     def setup_method(self, method):
         # Save original user and cookie
-        self.saved_cookie = self.request.saved_cookie
+        self.saved_cookie = self.request.cookies
         self.saved_user = self.request.user
 
         # Create anon user for the tests
-        self.request.saved_cookie = ''
+        self.request.cookies = {}
         self.request.user = user.User(self.request)
 
         # Prevent user list caching - we create and delete users too fast for that.
@@ -64,7 +64,7 @@
             del self.user
 
         # Restore original user
-        self.request.saved_cookie = self.saved_cookie
+        self.request.cookies = self.saved_cookie
         self.request.user = self.saved_user
 
         # Remove user name to id cache, or next test will fail
--- a/MoinMoin/_tests/test_wikiutil.py	Tue Sep 23 00:44:45 2008 +0200
+++ b/MoinMoin/_tests/test_wikiutil.py	Tue Sep 23 23:02:50 2008 +0200
@@ -9,7 +9,7 @@
 
 import py
 
-from MoinMoin import wikiutil
+from MoinMoin import config, wikiutil
 
 
 class TestQueryStringSupport:
@@ -969,4 +969,80 @@
         assert relative_page == wikiutil.RelPageName(current_page, absolute_page)
 
 
+class TestNormalizePagename(object):
+
+    def testPageInvalidChars(self):
+        """ request: normalize pagename: remove invalid unicode chars
+
+        Assume the default setting
+        """
+        test = u'\u0000\u202a\u202b\u202c\u202d\u202e'
+        expected = u''
+        result = wikiutil.normalize_pagename(test, self.request.cfg)
+        assert result == expected
+
+    def testNormalizeSlashes(self):
+        """ request: normalize pagename: normalize slashes """
+        cases = (
+            (u'/////', u''),
+            (u'/a', u'a'),
+            (u'a/', u'a'),
+            (u'a/////b/////c', u'a/b/c'),
+            (u'a b/////c d/////e f', u'a b/c d/e f'),
+            )
+        for test, expected in cases:
+            result = wikiutil.normalize_pagename(test, self.request.cfg)
+            assert result == expected
+
+    def testNormalizeWhitespace(self):
+        """ request: normalize pagename: normalize whitespace """
+        cases = (
+            (u'         ', u''),
+            (u'    a', u'a'),
+            (u'a    ', u'a'),
+            (u'a     b     c', u'a b c'),
+            (u'a   b  /  c    d  /  e   f', u'a b/c d/e f'),
+            # All 30 unicode spaces
+            (config.chars_spaces, u''),
+            )
+        for test, expected in cases:
+            result = wikiutil.normalize_pagename(test, self.request.cfg)
+            assert result == expected
+
+    def testUnderscoreTestCase(self):
+        """ request: normalize pagename: underscore convert to spaces and normalized
+
+        Underscores should convert to spaces, then spaces should be
+        normalized, order is important!
+        """
+        cases = (
+            (u'         ', u''),
+            (u'  a', u'a'),
+            (u'a  ', u'a'),
+            (u'a  b  c', u'a b c'),
+            (u'a  b  /  c  d  /  e  f', u'a b/c d/e f'),
+            )
+        for test, expected in cases:
+            result = wikiutil.normalize_pagename(test, self.request.cfg)
+            assert result == expected
+
+class TestGroupPages(object):
+
+    def testNormalizeGroupName(self):
+        """ request: normalize pagename: restrict groups to alpha numeric Unicode
+
+        Spaces should normalize after invalid chars removed!
+        """
+        cases = (
+            # current acl chars
+            (u'Name,:Group', u'NameGroup'),
+            # remove than normalize spaces
+            (u'Name ! @ # $ % ^ & * ( ) + Group', u'Name Group'),
+            )
+        for test, expected in cases:
+            # validate we are testing valid group names
+            if wikiutil.isGroupPage(test, self.request.cfg):
+                result = wikiutil.normalize_pagename(test, self.request.cfg)
+                assert result == expected
+
 coverage_modules = ['MoinMoin.wikiutil']
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MoinMoin/_tests/test_wsgiapp.py	Tue Sep 23 23:02:50 2008 +0200
@@ -0,0 +1,41 @@
+# -*- coding: utf-8 -*-
+"""
+    MoinMoin - MoinMoin.wsgiapp Tests
+
+    @copyright: 2008 MoinMoin:FlorianKrupicka
+    @license: GNU GPL, see COPYING for details.
+"""
+from StringIO import StringIO
+
+from MoinMoin import wsgiapp
+
+DOC_TYPE = '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">'
+
+class TestApplication:
+
+    # These should exist
+    PAGES = ('FrontPage', 'RecentChanges', 'HelpContents', 'FindPage')
+    # ... and these should not
+    NO_PAGES = ('FooBar', 'TheNone/ExistantPage/', '%33Strange%74Codes')
+
+    def testWSGIAppExisting(self):
+        for page in self.PAGES:
+            def _test_():
+                appiter, status, headers = self.client.get('/%s' % page)
+                print repr(list(appiter))
+                assert status[:3] == '200'
+                assert ('Content-Type', 'text/html; charset=utf-8') in headers
+                output = ''.join(appiter)
+                for needle in (DOC_TYPE, page):
+                    assert needle in output
+            yield _test_
+
+    def testWSGIAppAbsent(self):
+        for page in self.NO_PAGES:
+            def _test_():
+                appiter, status, headers = self.client.get('/%s' % page)
+                assert status[:3] == '404'
+                output = ''.join(appiter)
+                for needle in ('new empty page', 'page template'):
+                    assert needle in output
+            yield _test_
--- a/MoinMoin/action/AttachFile.py	Tue Sep 23 00:44:45 2008 +0200
+++ b/MoinMoin/action/AttachFile.py	Tue Sep 23 23:02:50 2008 +0200
@@ -27,7 +27,7 @@
     @license: GNU GPL, see COPYING for details.
 """
 
-import os, time, zipfile, mimetypes, errno
+import os, time, zipfile, mimetypes, errno, datetime
 
 from MoinMoin import log
 logging = log.getLogger(__name__)
@@ -76,34 +76,20 @@
     else:
         return u"/".join(pieces[:-1]), pieces[-1]
 
-
-def attachUrl(request, pagename, filename=None, **kw):
-    # filename is not used yet, but should be used later to make a sub-item url
-    if kw:
-        qs = '?%s' % wikiutil.makeQueryString(kw, want_unicode=False)
-    else:
-        qs = ''
-    return "%s/%s%s" % (request.getScriptname(), wikiutil.quoteWikinameURL(pagename), qs)
-
-
 def getAttachUrl(pagename, filename, request, addts=0, escaped=0, do='get', drawing='', upload=False):
     """ Get URL that points to attachment `filename` of page `pagename`. """
     if upload:
         if not drawing:
-            url = attachUrl(request, pagename, filename,
-                            rename=wikiutil.taintfilename(filename), action=action_name)
+            url = request.href(pagename, rename=wikiutil.taintfilename(filename),
+                               action=action_name)
         else:
-            url = attachUrl(request, pagename, filename,
-                            rename=wikiutil.taintfilename(filename), drawing=drawing, action=action_name)
+            url = request.href(pagename, rename=wikiutil.taintfilename(filename),
+                               drawing=drawing, action=action_name)
     else:
         if not drawing:
-            url = attachUrl(request, pagename, filename,
-                            target=filename, action=action_name, do=do)
+            url = request.href(pagename, target=filename, action=action_name, do=do)
         else:
-            url = attachUrl(request, pagename, filename,
-                            drawing=drawing, action=action_name)
-    if escaped:
-        url = wikiutil.escape(url)
+            url = request.href(pagename, drawing=drawing, action=action_name)
     return url
 
 
@@ -123,7 +109,7 @@
     fmt = request.formatter
     attach_count = _('[%d attachments]') % len(files)
     attach_icon = request.theme.make_icon('attach', vars={'attach_count': attach_count})
-    attach_link = (fmt.url(1, attachUrl(request, pagename, action=action_name), rel='nofollow') +
+    attach_link = (fmt.url(1, request.href(pagename, action=action_name), rel='nofollow') +
                    attach_icon +
                    fmt.url(0))
     return attach_link
@@ -251,10 +237,10 @@
     _ = request.getText
 
     error = None
-    if not request.form.get('target', [''])[0]:
+    if not request.values.get('target'):
         error = _("Filename of attachment not specified!")
     else:
-        filename = wikiutil.taintfilename(request.form['target'][0])
+        filename = wikiutil.taintfilename(request.values['target'])
         fpath = getFilename(request, pagename, filename)
 
         if os.path.isfile(fpath):
@@ -408,14 +394,14 @@
 
     now = time.time()
     pubpath = request.cfg.url_prefix_static + "/applets/TWikiDrawPlugin"
-    basename = request.form['drawing'][0]
+    basename = request.form['drawing']
     drawpath = getAttachUrl(pagename, basename + '.draw', request, escaped=1)
     pngpath = getAttachUrl(pagename, basename + '.png', request, escaped=1)
-    pagelink = attachUrl(request, pagename, '', action=action_name, ts=now)
+    pagelink = request.href(pagename, action=action_name, ts=now)
     helplink = Page(request, "HelpOnActions/AttachFile").url(request)
-    savelink = attachUrl(request, pagename, '', action=action_name, do='savedrawing')
+    savelink = request.href(pagename, action=action_name, do='savedrawing')
     #savelink = Page(request, pagename).url(request) # XXX include target filename param here for twisted
-                                           # request, {'savename': request.form['drawing'][0]+'.draw'}
+                                           # request, {'savename': request.form['drawing']+'.draw'}
     #savelink = '/cgi-bin/dumpform.bat'
 
     timestamp = '&amp;ts=%s' % now
@@ -460,7 +446,7 @@
     if writeable:
         request.write('<h2>' + _("New Attachment") + '</h2>')
         request.write("""
-<form action="%(baseurl)s/%(pagename)s" method="POST" enctype="multipart/form-data">
+<form action="%(url)s" method="POST" enctype="multipart/form-data">
 <dl>
 <dt>%(upload_label_file)s</dt>
 <dd><input type="file" name="file" size="50"></dd>
@@ -477,14 +463,13 @@
 </p>
 </form>
 """ % {
-    'baseurl': request.getScriptname(),
-    'pagename': wikiutil.quoteWikinameURL(pagename),
+    'url': request.href(pagename),
     'action_name': action_name,
     'upload_label_file': _('File to upload'),
     'upload_label_rename': _('Rename to'),
-    'rename': request.form.get('rename', [''])[0],
+    'rename': request.form.get('rename', ''),
     'upload_label_overwrite': _('Overwrite existing attachment of same name'),
-    'overwrite_checked': ('', 'checked')[request.form.get('overwrite', ['0'])[0] == '1'],
+    'overwrite_checked': ('', 'checked')[request.form.get('overwrite', '0') == '1'],
     'upload_button': _('Upload'),
     'textcha': TextCha(request).render(),
 })
@@ -495,7 +480,7 @@
     if not writeable:
         request.write('<p>%s</p>' % _('You are not allowed to attach a file to this page.'))
 
-    if writeable and request.form.get('drawing', [None])[0]:
+    if writeable and request.form.get('drawing'):
         send_hotdraw(pagename, request)
 
 
@@ -507,12 +492,12 @@
     """ Main dispatcher for the 'AttachFile' action. """
     _ = request.getText
 
-    do = request.form.get('do', ['upload_form'])
-    handler = globals().get('_do_%s' % do[0])
+    do = request.values.get('do', 'upload_form')
+    handler = globals().get('_do_%s' % do)
     if handler:
         msg = handler(pagename, request)
     else:
-        msg = _('Unsupported AttachFile sub-action: %s') % (wikiutil.escape(do[0]), )
+        msg = _('Unsupported AttachFile sub-action: %s') % wikiutil.escape(do)
     if msg:
         error_msg(pagename, request, msg)
 
@@ -524,7 +509,6 @@
 def upload_form(pagename, request, msg=''):
     _ = request.getText
 
-    request.emit_http_headers()
     # Use user interface language for this generated page
     request.setContentLanguage(request.lang)
     request.theme.add_msg(msg, "dialog")
@@ -555,9 +539,15 @@
         return _('TextCha: Wrong answer! Go back and try again...')
 
     form = request.form
-    overwrite = form.get('overwrite', [u'0'])[0]
+
+    file_upload = request.files.get('file')
+    if not file_upload:
+        # This might happen when trying to upload file names
+        # with non-ascii characters on Safari.
+        return _("No file content. Delete non ASCII characters from the file name and try again.")
+
     try:
-        overwrite = int(overwrite)
+        overwrite = int(form.get('overwrite', '0'))
     except:
         overwrite = 0
 
@@ -567,35 +557,26 @@
     if overwrite and not request.user.may.delete(pagename):
         return _('You are not allowed to overwrite a file attachment of this page.')
 
-    filename = form.get('file__filename__')
-    rename = form.get('rename', [u''])[0].strip()
+    rename = form.get('rename', u'').strip()
     if rename:
         target = rename
     else:
-        target = filename
+        target = file_upload.filename
 
-    target = preprocess_filename(target)
     target = wikiutil.clean_input(target)
 
     if not target:
         return _("Filename of attachment not specified!")
 
-    # get file content
-    filecontent = request.form.get('file', [None])[0]
-    if filecontent is None:
-        # This might happen when trying to upload file names
-        # with non-ascii characters on Safari.
-        return _("No file content. Delete non ASCII characters from the file name and try again.")
-
     # add the attachment
     try:
-        target, bytes = add_attachment(request, pagename, target, filecontent, overwrite=overwrite)
+        target, bytes = add_attachment(request, pagename, target, file_upload.stream, overwrite=overwrite)
         msg = _("Attachment '%(target)s' (remote name '%(filename)s')"
                 " with %(bytes)d bytes saved.") % {
-                'target': target, 'filename': filename, 'bytes': bytes}
+                'target': target, 'filename': file_upload.filename, 'bytes': bytes}
     except AttachmentAlreadyExists:
         msg = _("Attachment '%(target)s' (remote name '%(filename)s') already exists.") % {
-            'target': target, 'filename': filename}
+            'target': target, 'filename': file_upload.filename}
 
     # return attachment list
     upload_form(pagename, request, msg)
@@ -607,8 +588,8 @@
     if not request.user.may.write(pagename):
         return _('You are not allowed to save a drawing on this page.')
 
-    filename = request.form['filename'][0]
-    filecontent = request.form['filepath'][0]
+    filename = request.form['filename']
+    filecontent = request.form['filepath']
 
     basepath, basename = os.path.split(filename)
     basename, ext = os.path.splitext(basename)
@@ -644,7 +625,6 @@
     if ext == '.map':
         os.utime(attach_dir, None)
 
-    request.emit_http_headers()
     request.write("OK")
 
 
@@ -710,17 +690,17 @@
 
     if 'cancel' in request.form:
         return _('Move aborted!')
-    if not wikiutil.checkTicket(request, request.form['ticket'][0]):
+    if not wikiutil.checkTicket(request, request.form['ticket']):
         return _('Please use the interactive user interface to move attachments!')
     if not request.user.may.delete(pagename):
         return _('You are not allowed to move attachments from this page.')
 
     if 'newpagename' in request.form:
-        new_pagename = request.form.get('newpagename')[0]
+        new_pagename = request.form.get('newpagename')
     else:
         upload_form(pagename, request, msg=_("Move aborted because new page name is empty."))
     if 'newattachmentname' in request.form:
-        new_attachment = request.form.get('newattachmentname')[0]
+        new_attachment = request.form.get('newattachmentname')
         if new_attachment != wikiutil.taintfilename(new_attachment):
             upload_form(pagename, request, msg=_("Please use a valid filename for attachment '%(filename)s'.") % {
                                   'filename': new_attachment})
@@ -728,7 +708,7 @@
     else:
         upload_form(pagename, request, msg=_("Move aborted because new attachment name is empty."))
 
-    attachment = request.form.get('oldattachmentname')[0]
+    attachment = request.form.get('oldattachmentname')
     move_file(request, pagename, new_pagename, attachment, new_attachment)
 
 
@@ -743,11 +723,10 @@
 
     # move file
     d = {'action': action_name,
-         'baseurl': request.getScriptname(),
+         'url': request.href(pagename),
          'do': 'attachment_move',
          'ticket': wikiutil.createTicket(request),
          'pagename': pagename,
-         'pagename_quoted': wikiutil.quoteWikinameURL(pagename),
          'attachment_name': filename,
          'move': _('Move'),
          'cancel': _('Cancel'),
@@ -755,7 +734,7 @@
          'attachment_label': _("New attachment name"),
         }
     formhtml = '''
-<form action="%(baseurl)s/%(pagename_quoted)s" method="POST">
+<form action="%(url)s" method="POST">
 <input type="hidden" name="action" value="%(action)s">
 <input type="hidden" name="do" value="%(do)s">
 <input type="hidden" name="ticket" value="%(ticket)s">
@@ -796,9 +775,10 @@
     if not filename:
         return # error msg already sent in _access_file
 
-    timestamp = timefuncs.formathttpdate(int(os.path.getmtime(fpath)))
-    if request.if_modified_since == timestamp:
-        request.emit_http_headers(["Status: 304 Not modified"])
+    timestamp = datetime.datetime.fromtimestamp(os.path.getmtime(fpath))
+    if_modified = request.if_modified_since
+    if if_modified and if_modified >= timestamp:
+        request.status_code = 304
     else:
         mt = wikiutil.MimeType(filename=filename)
         content_type = mt.content_type()
@@ -814,12 +794,11 @@
         dangerous = mime_type in request.cfg.mimetypes_xss_protect
         content_dispo = dangerous and 'attachment' or 'inline'
 
-        request.emit_http_headers([
-            'Content-Type: %s' % content_type,
-            'Last-Modified: %s' % timestamp,
-            'Content-Length: %d' % os.path.getsize(fpath),
-            'Content-Disposition: %s; filename="%s"' % (content_dispo, filename_enc),
-        ])
+        request.content_type = content_type
+        request.last_modified = timestamp
+        request.content_length = os.path.getsize(fpath)
+        content_dispo_string = '%s; filename="%s"' % (content_dispo, filename_enc)
+        request.headers.add('Content-Disposition', content_dispo_string)
 
         # send data
         request.send_file(open(fpath, 'rb'))
@@ -1053,7 +1032,6 @@
         return
 
     # send header & title
-    request.emit_http_headers()
     # Use user interface language for this generated page
     request.setContentLanguage(request.lang)
     title = _('attachment:%(filename)s of %(pagename)s') % {
--- a/MoinMoin/action/CopyPage.py	Tue Sep 23 00:44:45 2008 +0200
+++ b/MoinMoin/action/CopyPage.py	Tue Sep 23 23:02:50 2008 +0200
@@ -46,20 +46,19 @@
         """ copy this page to "pagename" """
         _ = self._
         form = self.form
-        newpagename = form.get('newpagename', [u''])[0]
-        newpagename = self.request.normalizePagename(newpagename)
-        comment = form.get('comment', [u''])[0]
+        newpagename = form.get('newpagename', u'')
+        newpagename = wikiutil.normalize_pagename(newpagename, self.cfg)
+        comment = form.get('comment', u'')
         comment = wikiutil.clean_input(comment)
 
         self.page = PageEditor(self.request, self.pagename)
         success, msgs = self.page.copyPage(newpagename, comment)
 
         copy_subpages = 0
-        if form.has_key('copy_subpages'):
-            try:
-                copy_subpages = int(form['copy_subpages'][0])
-            except:
-                pass
+        try:
+            copy_subpages = int(form['copy_subpages'])
+        except:
+            pass
 
         if copy_subpages and self.subpages or (not self.users_subpages and self.subpages):
             for name in self.subpages:
@@ -85,7 +84,7 @@
 
             d = {
                 'subpage': subpages,
-                'subpages_checked': ('', 'checked')[self.request.form.get('subpages_checked', ['0'])[0] == '1'],
+                'subpages_checked': ('', 'checked')[self.request.form.get('subpages_checked', '0') == '1'],
                 'subpage_label': _('Copy all /subpages too?'),
                 'pagename': wikiutil.escape(self.pagename, True),
                 'newname_label': _("New name"),
--- a/MoinMoin/action/DeletePage.py	Tue Sep 23 00:44:45 2008 +0200
+++ b/MoinMoin/action/DeletePage.py	Tue Sep 23 23:02:50 2008 +0200
@@ -44,7 +44,7 @@
     def do_action(self):
         """ Delete pagename """
         form = self.form
-        comment = form.get('comment', [u''])[0]
+        comment = form.get('comment', u'')
         comment = wikiutil.clean_input(comment)
 
         # Create a page editor that does not do editor backups, because
@@ -53,11 +53,10 @@
         success, msgs = self.page.deletePage(comment)
 
         delete_subpages = 0
-        if 'delete_subpages' in form:
-            try:
-                delete_subpages = int(form['delete_subpages'][0])
-            except:
-                pass
+        try:
+            delete_subpages = int(form['delete_subpages'])
+        except:
+            pass
 
         if delete_subpages and self.subpages:
             for name in self.subpages:
@@ -75,7 +74,7 @@
 
             d = {
                 'subpage': subpages,
-                'subpages_checked': ('', 'checked')[self.request.form.get('subpages_checked', ['0'])[0] == '1'],
+                'subpages_checked': ('', 'checked')[self.request.form.get('subpages_checked', '0') == '1'],
                 'subpage_label': _('Delete all /subpages too?'),
                 'comment_label': _("Optional reason for the deletion"),
                 'buttons_html': buttons_html,
--- a/MoinMoin/action/Despam.py	Tue Sep 23 00:44:45 2008 +0200
+++ b/MoinMoin/action/Despam.py	Tue Sep 23 23:02:50 2008 +0200
@@ -104,14 +104,13 @@
     request.write('''
 </table>
 <p>
-<form method="post" action="%s/%s">
+<form method="post" action="%s">
 <input type="hidden" name="action" value="Despam">
 <input type="hidden" name="editor" value="%s">
 <input type="submit" name="ok" value="%s">
 </form>
 </p>
-''' % (request.getScriptname(), wikiutil.quoteWikinameURL(pagename),
-       wikiutil.url_quote(editor), _("Revert all!")))
+''' % (request.href(pagename), wikiutil.url_quote(editor), _("Revert all!")))
 
 def revert_page(request, pagename, editor):
     if not request.user.may.revert(pagename):
@@ -183,11 +182,10 @@
         request.theme.add_msg(_('You are not allowed to use this action.'), "error")
         return Page.Page(request, pagename).send_page()
 
-    editor = request.form.get('editor', [None])[0]
+    editor = request.form.get('editor')
     timestamp = time.time() - DAYS * 24 * 3600
-    ok = request.form.get('ok', [0])[0]
+    ok = request.form.get('ok', 0)
 
-    request.emit_http_headers()
     request.theme.send_title("Despam", pagename=pagename)
     # Start content (important for RTL support)
     request.write(request.formatter.startContent("content"))
--- a/MoinMoin/action/LikePages.py	Tue Sep 23 00:44:45 2008 +0200
+++ b/MoinMoin/action/LikePages.py	Tue Sep 23 23:02:50 2008 +0200
@@ -41,8 +41,6 @@
         return
 
     # more than one match, list 'em
-    request.emit_http_headers()
-
     # This action generate data using the user language
     request.setContentLanguage(request.lang)
 
--- a/MoinMoin/action/Load.py	Tue Sep 23 00:44:45 2008 +0200
+++ b/MoinMoin/action/Load.py	Tue Sep 23 23:02:50 2008 +0200
@@ -36,11 +36,11 @@
         form = self.form
         request = self.request
 
-        comment = form.get('comment', [u''])[0]
+        comment = form.get('comment', u'')
         comment = wikiutil.clean_input(comment)
 
         filename = form.get('file__filename__')
-        rename = form.get('rename', [''])[0].strip()
+        rename = form.get('rename', '').strip()
         if rename:
             target = rename
         else:
@@ -50,7 +50,7 @@
         target = wikiutil.clean_input(target)
 
         if target:
-            filecontent = form['file'][0]
+            filecontent = form['file']
             if hasattr(filecontent, 'read'): # a file-like object
                 filecontent = filecontent.read() # XXX reads complete file into memory!
             filecontent = wikiutil.decodeUnknownInput(filecontent)
--- a/MoinMoin/action/LocalSiteMap.py	Tue Sep 23 00:44:45 2008 +0200
+++ b/MoinMoin/action/LocalSiteMap.py	Tue Sep 23 23:02:50 2008 +0200
@@ -31,7 +31,6 @@
 
 def execute(pagename, request):
     _ = request.getText
-    request.emit_http_headers()
 
     # This action generate data using the user language
     request.setContentLanguage(request.lang)
--- a/MoinMoin/action/MyPages.py	Tue Sep 23 00:44:45 2008 +0200
+++ b/MoinMoin/action/MyPages.py	Tue Sep 23 23:02:50 2008 +0200
@@ -58,7 +58,6 @@
     pagecontent = pagecontent.replace('\n', '\r\n')
 
     from MoinMoin.parser.text_moin_wiki import Parser as WikiParser
-    request.emit_http_headers()
 
     # This action generate data using the user language
     request.setContentLanguage(request.lang)
--- a/MoinMoin/action/PackagePages.py	Tue Sep 23 00:44:45 2008 +0200
+++ b/MoinMoin/action/PackagePages.py	Tue Sep 23 23:02:50 2008 +0200
@@ -68,11 +68,11 @@
         form = self.request.form
 
         # Get new name from form and normalize.
-        pagelist = form.get('pagelist', [u''])[0]
-        packagename = form.get('packagename', [u''])[0]
-        include_attachments = form.get('include_attachments', [False])[0]
+        pagelist = form.get('pagelist', u'')
+        packagename = form.get('packagename', u'')
+        include_attachments = form.get('include_attachments', False)
 
-        if not form.get('submit', [None])[0]:
+        if not form.get('submit'):
             self.request.theme.add_msg(self.makeform(), "dialog")
             raise ActionError
 
@@ -115,7 +115,7 @@
             error = u'<p class="error">%s</p>\n' % error
 
         d = {
-            'baseurl': self.request.getScriptname(),
+            'url': self.request.href(self.pagename),
             'error': error,
             'action': self.__class__.__name__,
             'pagename': wikiutil.escape(self.pagename, True),
@@ -128,7 +128,7 @@
         }
         form = '''
 %(error)s
-<form method="post" action="%(baseurl)s/%(pagename_quoted)s">
+<form method="post" action="%(url)s">
 <input type="hidden" name="action" value="%(action)s">
 <table>
     <tr>
@@ -196,7 +196,7 @@
 
         pages = []
         for pagename in pagelist:
-            pagename = self.request.normalizePagename(pagename)
+            pagename = wikiutil.normalize_pagename(pagename, self.request.cfg)
             if pagename:
                 page = Page(self.request, pagename)
                 if page.exists() and self.request.user.may.read(pagename):
--- a/MoinMoin/action/RenamePage.py	Tue Sep 23 00:44:45 2008 +0200
+++ b/MoinMoin/action/RenamePage.py	Tue Sep 23 23:02:50 2008 +0200
@@ -45,20 +45,19 @@
         """ Rename this page to "pagename" """
         _ = self._
         form = self.form
-        newpagename = form.get('newpagename', [u''])[0]
-        newpagename = self.request.normalizePagename(newpagename)
-        comment = form.get('comment', [u''])[0]
+        newpagename = form.get('newpagename', u'')
+        newpagename = wikiutil.normalize_pagename(newpagename, self.cfg)
+        comment = form.get('comment', u'')
         comment = wikiutil.clean_input(comment)
 
         self.page = PageEditor(self.request, self.pagename)
         success, msgs = self.page.renamePage(newpagename, comment)
 
         rename_subpages = 0
-        if 'rename_subpages' in form:
-            try:
-                rename_subpages = int(form['rename_subpages'][0])
-            except:
-                pass
+        try:
+            rename_subpages = int(form['rename_subpages'])
+        except:
+            pass
 
         if rename_subpages and self.subpages:
             for name in self.subpages:
@@ -84,7 +83,7 @@
 
             d = {
                 'subpage': subpages,
-                'subpages_checked': ('', 'checked')[self.request.form.get('subpages_checked', ['0'])[0] == '1'],
+                'subpages_checked': ('', 'checked')[self.request.form.get('subpages_checked', '0') == '1'],
                 'subpage_label': _('Rename all /subpages too?'),
                 'pagename': wikiutil.escape(self.pagename, True),
                 'newname_label': _("New name"),
--- a/MoinMoin/action/RenderAsDocbook.py	Tue Sep 23 00:44:45 2008 +0200
+++ b/MoinMoin/action/RenderAsDocbook.py	Tue Sep 23 23:02:50 2008 +0200
@@ -9,5 +9,5 @@
 
 def execute(pagename, request):
     url = Page(request, pagename).url(request, {'action': 'show', 'mimetype': 'text/docbook'})
-    request.http_redirect(url)
+    return request.http_redirect(url)
 
--- a/MoinMoin/action/SpellCheck.py	Tue Sep 23 00:44:45 2008 +0200
+++ b/MoinMoin/action/SpellCheck.py	Tue Sep 23 23:02:50 2008 +0200
@@ -102,7 +102,7 @@
     from MoinMoin.PageEditor import PageEditor
     # get the new words as a string (if any are marked at all)
     try:
-        newwords = request.form['newwords']
+        newwords = request.form.getlist('newwords')
     except KeyError:
         # no new words checked
         return
@@ -187,8 +187,8 @@
 
         # add a form containing the bad words
         if own_form:
-            msg = msg + ('<form method="post" action="%s/%s">\n'
-                         '<input type="hidden" name="action" value="%s">\n') % (request.getScriptname(), wikiutil.quoteWikinameURL(page.page_name), action_name)
+            msg = msg + ('<form method="post" action="%s">\n'
+                         '<input type="hidden" name="action" value="%s">\n') % (request.href(page.page_name), action_name)
 
         checkbox = '<input type="checkbox" name="newwords" value="%(word)s">%(word)s&nbsp;&nbsp;'
         msg = msg + (
--- a/MoinMoin/action/SubscribeUser.py	Tue Sep 23 00:44:45 2008 +0200
+++ b/MoinMoin/action/SubscribeUser.py	Tue Sep 23 23:02:50 2008 +0200
@@ -16,30 +16,28 @@
 
 def show_form(pagename, request):
     _ = request.getText
-    request.emit_http_headers()
     request.theme.send_title(_("Subscribe users to the page %s") % pagename, pagename=pagename)
 
     request.write("""
-<form action="%s/%s" method="POST" enctype="multipart/form-data">
+<form action="%s" method="POST" enctype="multipart/form-data">
 <input type="hidden" name="action" value="SubscribeUser">
 %s <input type="text" name="users" size="50">
 <input type="submit" value="Subscribe">
 </form>
-""" % (request.getScriptname(), wikiutil.quoteWikinameURL(pagename),
+""" % (request.href(pagename),
       _("Enter user names (comma separated):")))
     request.theme.send_footer(pagename)
     request.theme.send_closing_html()
 
 def show_result(pagename, request):
     _ = request.getText
-    request.emit_http_headers()
 
     request.theme.send_title(_("Subscribed for %s:") % pagename, pagename=pagename)
 
     from MoinMoin.formatter.text_html import Formatter
     formatter = Formatter(request)
 
-    result = subscribe_users(request, request.form['users'][0].split(","), pagename, formatter)
+    result = subscribe_users(request, request.form['users'].split(","), pagename, formatter)
     request.write(result)
 
     request.theme.send_footer(pagename)
@@ -119,9 +117,8 @@
         request_url = "localhost/"
 
     # Setup MoinMoin environment
-    from MoinMoin.request import request_cli
-    request = request_cli.Request(url=request_url)
-    request.form = request.args = request.setup_args()
+    from MoinMoin.web.contexts import ScriptContext
+    request = ScriptContext(url=request_url)
 
     from MoinMoin.formatter.text_plain import Formatter
     formatter = Formatter(request)
--- a/MoinMoin/action/SyncPages.py	Tue Sep 23 00:44:45 2008 +0200
+++ b/MoinMoin/action/SyncPages.py	Tue Sep 23 23:02:50 2008 +0200
@@ -118,7 +118,7 @@
         """ Does some fixup on the parameters. """
         # Load the password
         if "password" in self.request.form:
-            params["password"] = self.request.form["password"][0]
+            params["password"] = self.request.form["password"]
 
         # merge the pageList case into the pageMatch case
         if params["pageList"] is not None:
--- a/MoinMoin/action/__init__.py	Tue Sep 23 00:44:45 2008 +0200
+++ b/MoinMoin/action/__init__.py	Tue Sep 23 23:02:50 2008 +0200
@@ -18,10 +18,12 @@
     Additionally to the usual stuff, we provide an ActionBase class here with
     some of the usual base functionality for an action, like checking
     actions_excluded, making and checking tickets, rendering some form,
-    displaying errors and doing stuff after an action.
+    displaying errors and doing stuff after an action. Also utility functions
+    regarding actions are located here.
 
     @copyright: 2000-2004 Juergen Hermann <jh@web.de>,
                 2006 MoinMoin:ThomasWaldmann
+                2008 MoinMoin:FlorianKrupicka
     @license: GNU GPL, see COPYING for details.
 """
 
@@ -88,7 +90,7 @@
             return True
         # Require a valid ticket. Make outside attacks harder by
         # requiring two full HTTP transactions
-        ticket = self.form.get('ticket', [''])[0]
+        ticket = self.form.get('ticket', '')
         return wikiutil.checkTicket(self.request, ticket)
 
     # UI ---------------------------------------------------------------------
@@ -134,19 +136,17 @@
 
         d = {
             'method': self.method,
-            'baseurl': self.request.getScriptname(),
+            'url': self.request.href(self.pagename),
             'enctype': self.enctype,
             'error_html': error_html,
             'actionname': self.actionname,
-            'pagename': self.pagename,
-            'pagename_quoted': wikiutil.quoteWikinameURL(self.pagename),
             'ticket_html': ticket_html,
             'user_html': self.get_form_html(buttons_html),
         }
 
         form_html = '''
 %(error_html)s
-<form action="%(baseurl)s/%(pagename_quoted)s" method="%(method)s" enctype="%(enctype)s">
+<form action="%(url)s/%(pagename_quoted)s" method="%(method)s" enctype="%(enctype)s">
 <div>
 <input type="hidden" name="action" value="%(actionname)s">
 %(ticket_html)s
@@ -243,7 +243,7 @@
     if not request.user.may.read(pagename):
         Page(request, pagename).send_page()
     else:
-        mimetype = request.form.get('mimetype', [u"text/html"])[0]
+        mimetype = request.values.get('mimetype', u"text/html")
         rev = request.rev or 0
         if rev == 0:
             request.cacheable = cacheable
@@ -261,15 +261,14 @@
         DEPRECATED: remove this action when we don't need it any more for compatibility.
     """
     if 'mimetype' not in request.form:
-        request.form['mimetype'] = [u"text/plain"]
+        request.form['mimetype'] = u"text/plain"
     do_show(pagename, request, count_hit=0, cacheable=0)
 
 def do_content(pagename, request):
     """ same as do_show, but we only show the content """
     # XXX temporary fix to make it work until Page.send_page gets refactored
-    request.setHttpHeader("Content-Type: text/html; charset=%s" % config.charset)
-    request.setHttpHeader('Status: 200 OK')
-    request.emit_http_headers()
+    request.mimetype = 'text/html'
+    request.status_code = 200
     do_show(pagename, request, count_hit=0, content_only=1)
 
 def do_print(pagename, request):
@@ -283,10 +282,10 @@
 def do_refresh(pagename, request):
     """ Handle refresh action """
     # Without arguments, refresh action will refresh the page text_html cache.
-    arena = request.form.get('arena', ['Page.py'])[0]
+    arena = request.form.get('arena', 'Page.py')
     if arena == 'Page.py':
         arena = Page(request, pagename)
-    key = request.form.get('key', ['text_html'])[0]
+    key = request.form.get('key', 'text_html')
 
     # Remove cache entry (if exists), and send the page
     from MoinMoin import caching
@@ -296,27 +295,71 @@
 
 def do_goto(pagename, request):
     """ redirect to another page """
-    target = request.form.get('target', [''])[0]
+    target = request.form.get('target', '')
     request.http_redirect(Page(request, target).url(request))
 
 # Dispatching ----------------------------------------------------------------
-def getNames(cfg):
-    if not hasattr(cfg.cache, 'action_names'):
-        lnames = names[:]
-        lnames.extend(wikiutil.getPlugins('action', cfg))
-        cfg.cache.action_names = lnames # remember it
-    return cfg.cache.action_names
+def get_names(config):
+    """ Get a list of known actions.
 
-def getHandler(request, action, identifier="execute"):
-    """ return a handler function for a given action or None """
+    @param config: a config object
+    @rtype: set
+    @return: set of known actions
+    """
+    if not hasattr(config.cache, 'action_names'):
+        actions = names[:]
+        actions.extend(wikiutil.getPlugins('action', config))
+        actions = set(action for action in actions
+                      if not action in config.actions_excluded)
+        config.cache.action_names = actions # remember it
+    return config.cache.action_names
+
+def getHandler(cfg, action, identifier="execute"):
+    """ return a handler function for a given action or None.
+
+    TODO: remove request dependency
+    """
     # check for excluded actions
-    if action in request.cfg.actions_excluded:
+    if action in cfg.actions_excluded:
         return None
 
     try:
-        handler = wikiutil.importPlugin(request.cfg, "action", action, identifier)
+        handler = wikiutil.importPlugin(cfg, "action", action, identifier)
     except wikiutil.PluginMissingError:
         handler = globals().get('do_' + action)
 
     return handler
 
+def get_available_actions(config, page, user):
+        """ Get a list of actions available on a particular page
+        for a particular user.
+
+        The set does not contain actions that starts with lower case.
+        Themes use this set to display the actions to the user.
+
+        @param config: a config object (for the per-wiki actions)
+        @param page: the page to which the actions should apply
+        @param user: the user which wants to apply an action
+        @rtype: set
+        @return: set of avaiable actions
+        """
+        if not user.may.read(page.page_name):
+            return []
+
+
+        actions = get_names(config)
+
+        # Filter non ui actions (starts with lower case letter)
+        actions = [action for action in actions if not action[0].islower()]
+
+        # Filter actions by page type, acl and user state
+        excluded = []
+        if (page.isUnderlayPage() and not page.isStandardPage()) or \
+                not user.may.write(page.page_name) or \
+                not user.may.delete(page.page_name):
+                # Prevent modification of underlay only pages, or pages
+                # the user can't write and can't delete
+                excluded = [u'RenamePage', u'DeletePage', ] # AttachFile must NOT be here!
+        return set(action for action in actions if not action in excluded)
+
+
--- a/MoinMoin/action/_tests/test_attachfile.py	Tue Sep 23 00:44:45 2008 +0200
+++ b/MoinMoin/action/_tests/test_attachfile.py	Tue Sep 23 23:02:50 2008 +0200
@@ -68,7 +68,7 @@
         Tests if AttachFile.getAttachUrl taints a filename
         """
         filename = "<test2.txt>"
-        expect = "rename=_test2.txt_&"
+        expect = "rename=_test2.txt_"
         result = AttachFile.getAttachUrl(self.pagename, filename, self.request, upload=True)
 
         assert expect in result
--- a/MoinMoin/action/bookmark.py	Tue Sep 23 00:44:45 2008 +0200
+++ b/MoinMoin/action/bookmark.py	Tue Sep 23 23:02:50 2008 +0200
@@ -19,7 +19,7 @@
         request.theme.add_msg(_("You must login to use this action: %(action)s.") % {"action": actname}, "error")
         return Page(request, pagename).send_page()
 
-    timestamp = request.form.get('time', [None])[0]
+    timestamp = request.form.get('time')
     if timestamp is not None:
         if timestamp == 'del':
             tm = None
--- a/MoinMoin/action/cache.py	Tue Sep 23 00:44:45 2008 +0200
+++ b/MoinMoin/action/cache.py	Tue Sep 23 23:02:50 2008 +0200
@@ -197,10 +197,7 @@
 
 def url(request, key, do='get'):
     """ return URL for the object cached for key """
-    return "%s/?%s" % (
-        request.getScriptname(),
-        wikiutil.makeQueryString(dict(action=action_name, do=do, key=key), want_unicode=False))
-
+    return request.href(action=action_name, do=do, key=key)
 
 def _get_headers(request, key):
     """ get last_modified and headers cached for key """
@@ -243,7 +240,7 @@
         _do_remove(request, key)
 
 def execute(pagename, request):
-    do = request.form.get('do', [None])[0]
-    key = request.form.get('key', [None])[0]
+    do = request.form.get('do')
+    key = request.form.get('key')
     _do(request, do, key)
 
--- a/MoinMoin/action/chart.py	Tue Sep 23 00:44:45 2008 +0200
+++ b/MoinMoin/action/chart.py	Tue Sep 23 23:02:50 2008 +0200
@@ -19,7 +19,7 @@
         request.theme.add_msg(_("Charts are not available!"), "error")
         return request.page.send_page()
 
-    chart_type = request.form.get('type', [''])[0].strip()
+    chart_type = request.form.get('type', '').strip()
     if not chart_type:
         request.theme.add_msg(_('You need to provide a chart type!'), "error")
         return request.page.send_page()
--- a/MoinMoin/action/diff.py	Tue Sep 23 00:44:45 2008 +0200
+++ b/MoinMoin/action/diff.py	Tue Sep 23 23:02:50 2008 +0200
@@ -21,7 +21,7 @@
         return
 
     try:
-        date = request.form['date'][0]
+        date = request.form['date']
         try:
             date = long(date) # must be long for py 2.2.x
         except StandardError:
@@ -30,11 +30,11 @@
         date = 0
 
     try:
-        rev1 = int(request.form.get('rev1', [-1])[0])
+        rev1 = int(request.form.get('rev1', -1))
     except StandardError:
         rev1 = 0
     try:
-        rev2 = int(request.form.get('rev2', [0])[0])
+        rev2 = int(request.form.get('rev2', 0))
     except StandardError:
         rev2 = 0
 
@@ -44,7 +44,7 @@
             rev1 = -1
 
     # spacing flag?
-    ignorews = int(request.form.get('ignorews', [0])[0])
+    ignorews = int(request.form.get('ignorews', 0))
 
     _ = request.getText
 
@@ -71,7 +71,6 @@
     # This action generates content in the user language
     request.setContentLanguage(request.lang)
 
-    request.emit_http_headers()
     request.theme.send_title(_('Diff for "%s"') % (pagename, ), pagename=pagename, allow_doubleclick=1)
 
     if rev1 > 0 and rev2 > 0 and rev1 > rev2 or rev1 == 0 and rev2 > 0:
--- a/MoinMoin/action/dumpform.py	Tue Sep 23 00:44:45 2008 +0200
+++ b/MoinMoin/action/dumpform.py	Tue Sep 23 23:02:50 2008 +0200
@@ -12,6 +12,5 @@
     """ dump the form data we received in this request for debugging """
     data = util.dumpFormData(request.form)
 
-    request.emit_http_headers()
     request.write("<html><body>%s</body></html>" % data)
 
--- a/MoinMoin/action/edit.py	Tue Sep 23 00:44:45 2008 +0200
+++ b/MoinMoin/action/edit.py	Tue Sep 23 23:02:50 2008 +0200
@@ -33,7 +33,7 @@
     if editor not in valideditors:
         editor = request.cfg.editor_default
 
-    editorparam = request.form.get('editor', [editor])[0]
+    editorparam = request.form.get('editor', editor)
     if editorparam == "guipossible":
         lasteditor = editor
     elif editorparam == "textonly":
@@ -49,11 +49,11 @@
         editor = 'text'
 
     rev = request.rev or 0
-    savetext = request.form.get('savetext', [None])[0]
-    comment = request.form.get('comment', [u''])[0]
-    category = request.form.get('category', [None])[0]
-    rstrip = int(request.form.get('rstrip', ['0'])[0])
-    trivial = int(request.form.get('trivial', ['0'])[0])
+    savetext = request.form.get('savetext')
+    comment = request.form.get('comment', u'')
+    category = request.form.get('category')
+    rstrip = int(request.form.get('rstrip', '0'))
+    trivial = int(request.form.get('trivial', '0'))
 
     if 'button_switch' in request.form:
         if editor == 'text':
@@ -78,7 +78,7 @@
     cancelled = 'button_cancel' in request.form
 
     if request.cfg.edit_ticketing:
-        ticket = request.form.get('ticket', [''])[0]
+        ticket = request.form.get('ticket', '')
         if not wikiutil.checkTicket(request, ticket):
             request.theme.add_msg(_('Please use the interactive user interface to use action %(actionname)s!') % {'actionname': 'edit' }, "error")
             pg.send_page()
@@ -88,7 +88,7 @@
     try:
         if lasteditor == 'gui':
             # convert input from Graphical editor
-            format = request.form.get('format', ['wiki'])[0]
+            format = request.form.get('format', 'wiki')
             if format == 'wiki':
                 converter_name = 'text_html_text_moin_wiki'
             else:
--- a/MoinMoin/action/fckdialog.py	Tue Sep 23 00:44:45 2008 +0200
+++ b/MoinMoin/action/fckdialog.py	Tue Sep 23 23:02:50 2008 +0200
@@ -15,7 +15,6 @@
 
 def macro_dialog(request):
     help = get_macro_help(request)
-    request.emit_http_headers()
     request.write(
         '''<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
 <html>
@@ -164,13 +163,12 @@
 
 def page_list(request):
     from MoinMoin import search
-    name = request.form.get("pagename", [""])[0]
+    name = request.form.get("pagename", "")
     if name:
         searchresult = search.searchPages(request, 't:"%s"' % name)
         pages = [p.page_name for p in searchresult.hits]
     else:
         pages = [name]
-    request.emit_http_headers()
     request.write(
         '''<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
 <html>
@@ -201,9 +199,8 @@
 ''' % "".join(["<option>%s</option>\n" % p for p in pages]))
 
 def link_dialog(request):
-    request.emit_http_headers()
     # list of wiki pages
-    name = request.form.get("pagename", [""])[0]
+    name = request.form.get("pagename", "")
     if name:
         from MoinMoin import search
         # XXX error handling!
@@ -242,7 +239,7 @@
 
     # wiki url
     url_prefix_static = request.cfg.url_prefix_static
-    scriptname = request.getScriptname()
+    scriptname = request.script_root
     if not scriptname or scriptname[-1] != "/":
         scriptname += "/"
     action = scriptname
@@ -367,9 +364,8 @@
 ##############################################################################
 
 def attachment_dialog(request):
-    request.emit_http_headers()
     # list of wiki pages
-    name = request.form.get("pagename", [""])[0]
+    name = request.form.get("pagename", "")
     if name:
         from MoinMoin import search
         # XXX error handling!
@@ -393,7 +389,7 @@
 
     # wiki url
     url_prefix_static = request.cfg.url_prefix_static
-    scriptname = request.getScriptname()
+    scriptname = request.script_root
     if not scriptname or scriptname[-1] != "/":
         scriptname += "/"
     action = scriptname
@@ -461,7 +457,6 @@
 ##############################################################################
 
 def image_dialog(request):
-    request.emit_http_headers()
     url_prefix_static = request.cfg.url_prefix_static
     request.write('''
 <!--
--- a/MoinMoin/action/fullsearch.py	Tue Sep 23 00:44:45 2008 +0200
+++ b/MoinMoin/action/fullsearch.py	Tue Sep 23 23:02:50 2008 +0200
@@ -22,12 +22,12 @@
     'fullsearch' with localized string. If both missing, default to
     True (might happen with Safari) if this isn't an advanced search.
 """
-    form = request.form
+    form = request.values
     if 'titlesearch' in form and 'fullsearch' in form:
         ret = -1 # spammer / bot
     else:
         try:
-            ret = int(form['titlesearch'][0])
+            ret = int(form['titlesearch'])
         except ValueError:
             ret = 1
         except KeyError:
@@ -37,7 +37,7 @@
 def isAdvancedSearch(request):
     """ Return True if advanced search is requested """
     try:
-        return int(request.form['advancedsearch'][0])
+        return int(request.values['advancedsearch'])
     except KeyError:
         return False
 
@@ -66,38 +66,40 @@
 
     advancedsearch = isAdvancedSearch(request)
 
+    form = request.values
+
     # context is relevant only for full search
     if titlesearch:
         context = 0
     elif advancedsearch:
         context = 180 # XXX: hardcoded context count for advancedsearch
     else:
-        context = int(request.form.get('context', [0])[0])
+        context = int(form.get('context', 0))
 
     # Get other form parameters
-    needle = request.form.get(fieldname, [''])[0]
-    case = int(request.form.get('case', [0])[0])
-    regex = int(request.form.get('regex', [0])[0]) # no interface currently
-    hitsFrom = int(request.form.get('from', [0])[0])
+    needle = form.get(fieldname, '')
+    case = int(form.get('case', 0))
+    regex = int(form.get('regex', 0)) # no interface currently
+    hitsFrom = int(form.get('from', 0))
     mtime = None
     msg = ''
     historysearch = 0
 
     # if advanced search is enabled we construct our own search query
     if advancedsearch:
-        and_terms = request.form.get('and_terms', [''])[0].strip()
-        or_terms = request.form.get('or_terms', [''])[0].strip()
-        not_terms = request.form.get('not_terms', [''])[0].strip()
-        #xor_terms = request.form.get('xor_terms', [''])[0].strip()
-        categories = request.form.get('categories', [''])
-        timeframe = request.form.get('time', [''])[0].strip()
-        language = request.form.get('language', [''])
-        mimetype = request.form.get('mimetype', [0])
-        excludeunderlay = request.form.get('excludeunderlay', [0])[0]
-        nosystemitems = request.form.get('nosystemitems', [0])[0]
-        historysearch = request.form.get('historysearch', [0])[0]
+        and_terms = form.get('and_terms', '').strip()
+        or_terms = form.get('or_terms', '').strip()
+        not_terms = form.get('not_terms', '').strip()
+        #xor_terms = form.get('xor_terms', '').strip()
+        categories = form.getlist('categories') or ['']
+        timeframe = form.get('time', '').strip()
+        language = form.getlist('language') or ['']
+        mimetype = form.getlist('mimetype') or [0]
+        excludeunderlay = form.get('excludeunderlay', 0)
+        nosystemitems = form.get('nosystemitems', 0)
+        historysearch = form.get('historysearch', 0)
 
-        mtime = request.form.get('mtime', [''])[0]
+        mtime = form.get('mtime', '')
         if mtime:
             mtime_parsed = None
 
@@ -223,12 +225,10 @@
         Page(request, pagename).send_page()
         return
 
-    request.emit_http_headers()
-
     # This action generates data using the user language
     request.setContentLanguage(request.lang)
 
-    request.theme.send_title(title % needle, form=request.form, pagename=pagename)
+    request.theme.send_title(title % needle, form=form, pagename=pagename)
 
     # Start content (important for RTL support)
     request.write(request.formatter.startContent("content"))
--- a/MoinMoin/action/info.py	Tue Sep 23 00:44:45 2008 +0200
+++ b/MoinMoin/action/info.py	Tue Sep 23 23:02:50 2008 +0200
@@ -187,8 +187,6 @@
     page = Page(request, pagename)
     title = page.split_title()
 
-    request.emit_http_headers()
-
     request.setContentLanguage(request.lang)
     f = request.formatter
 
@@ -207,14 +205,8 @@
         request.write("[%s] " % page.link_to(request, text=text, querystr=querystr, rel='nofollow'))
     request.write(f.paragraph(0))
 
-    try:
-        show_hitcounts = int(request.form.get('hitcounts', [0])[0]) != 0
-    except ValueError:
-        show_hitcounts = False
-    try:
-        show_general = int(request.form.get('general', [0])[0]) != 0
-    except ValueError:
-        show_general = False
+    show_hitcounts = int(request.form.get('hitcounts', 0)) != 0
+    show_general = int(request.form.get('general', 0)) != 0
 
     if show_hitcounts:
         from MoinMoin.stats import hitcounts
--- a/MoinMoin/action/links.py	Tue Sep 23 00:44:45 2008 +0200
+++ b/MoinMoin/action/links.py	Tue Sep 23 23:02:50 2008 +0200
@@ -15,11 +15,11 @@
 
     # get the MIME type
     if 'mimetype' in form:
-        mimetype = form['mimetype'][0]
+        mimetype = form['mimetype']
     else:
         mimetype = "text/html"
 
-    request.emit_http_headers(["Content-Type: %s; charset=%s" % (mimetype, config.charset)])
+    request.mimetype = mimetype
 
     if mimetype == "text/html":
         request.theme.send_title(_('Full Link List for "%s"') % request.cfg.sitename)
--- a/MoinMoin/action/login.py	Tue Sep 23 00:44:45 2008 +0200
+++ b/MoinMoin/action/login.py	Tue Sep 23 23:02:50 2008 +0200
@@ -38,7 +38,6 @@
         form.append(html.INPUT(type='hidden', name='stage',
                                value=request._login_multistage_name))
 
-        request.emit_http_headers()
         request.theme.send_title(_("Login"), pagename=self.pagename)
         # Start content (important for RTL support)
         request.write(request.formatter.startContent("content"))
@@ -59,7 +58,7 @@
 
         error = None
 
-        islogin = form.get('login', [''])[0]
+        islogin = form.get('login', '')
 
         if islogin: # user pressed login button
             if request._login_multistage:
@@ -74,7 +73,6 @@
             return self.page.send_page()
 
         else: # show login form
-            request.emit_http_headers()
             request.theme.send_title(_("Login"), pagename=self.pagename)
             # Start content (important for RTL support)
             request.write(request.formatter.startContent("content"))
--- a/MoinMoin/action/newaccount.py	Tue Sep 23 00:44:45 2008 +0200
+++ b/MoinMoin/action/newaccount.py	Tue Sep 23 23:02:50 2008 +0200
@@ -19,7 +19,7 @@
     _ = request.getText
     form = request.form
 
-    if request.request_method != 'POST':
+    if request.method != 'POST':
         return
 
     if not TextCha(request).check_answer_from_form():
@@ -30,7 +30,7 @@
 
     # Require non-empty name
     try:
-        theuser.name = form['name'][0]
+        theuser.name = form['name']
     except KeyError:
         return _("Empty user name. Please enter a user name.")
 
@@ -45,8 +45,8 @@
         return _("This user name already belongs to somebody else.")
 
     # try to get the password and pw repeat
-    password = form.get('password1', [''])[0]
-    password2 = form.get('password2', [''])[0]
+    password = form.get('password1', '')
+    password2 = form.get('password2', '')
 
     # Check if password is given and matches with password repeat
     if password != password2:
@@ -69,7 +69,7 @@
             return "Can't encode password: %s" % str(err)
 
     # try to get the email, for new users it is required
-    email = wikiutil.clean_input(form.get('email', [''])[0])
+    email = wikiutil.clean_input(form.get('email', ''))
     theuser.email = email.strip()
     if not theuser.email and 'email' not in request.cfg.user_form_remove:
         return _("Please provide your email address. If you lose your"
@@ -172,7 +172,6 @@
         request.theme.add_msg(_create_user(request), "dialog")
         return page.send_page()
     else: # show create form
-        request.emit_http_headers()
         request.theme.send_title(_("Create Account"), pagename=pagename)
 
         request.write(request.formatter.startContent("content"))
--- a/MoinMoin/action/newpage.py	Tue Sep 23 00:44:45 2008 +0200
+++ b/MoinMoin/action/newpage.py	Tue Sep 23 23:02:50 2008 +0200
@@ -19,8 +19,8 @@
     def __init__(self, request, referrer):
         self.request = request
         self.referrer = referrer # The page the user came from
-        self.pagename = self.request.form.get('pagename', [None])[0]
-        self.nametemplate = self.request.form.get('nametemplate', ['%s'])[0]
+        self.pagename = self.request.form.get('pagename')
+        self.nametemplate = self.request.form.get('nametemplate', '%s')
         self.nametemplate = self.nametemplate.replace('\x00', '')
 
     def checkAndCombineArguments(self):
@@ -82,11 +82,11 @@
             pagename = self.pagename
             query = {'action': 'edit', 'backto': self.referrer}
 
-            template = self.request.form.get('template', [''])[0]
+            template = self.request.form.get('template', '')
             if template:
                 query['template'] = template
 
-            parent = self.request.form.get('parent', [''])[0]
+            parent = self.request.form.get('parent', '')
             if parent:
                 pagename = "%s/%s" % (parent, pagename)
 
@@ -97,7 +97,7 @@
 
 def execute(pagename, request):
     """ Temporary glue code for current moin action system """
-    if request.request_method != 'POST':
+    if request.method != 'POST':
         return False, u''
 
     return NewPage(request, pagename).render()
--- a/MoinMoin/action/pollsistersites.py	Tue Sep 23 00:44:45 2008 +0200
+++ b/MoinMoin/action/pollsistersites.py	Tue Sep 23 23:02:50 2008 +0200
@@ -54,5 +54,5 @@
         except TypeError: # catch bug in python 2.5: "EnvironmentError expected at most 3 arguments, got 4"
             status.append(u"Site: %s Status: Not updated." % sistername)
 
-    request.emit_http_headers(["Content-Type: text/plain; charset=UTF-8"])
+    request.mimetype = 'text/plain'
     request.write("\r\n".join(status).encode("utf-8"))
--- a/MoinMoin/action/recoverpass.py	Tue Sep 23 00:44:45 2008 +0200
+++ b/MoinMoin/action/recoverpass.py	Tue Sep 23 23:02:50 2008 +0200
@@ -30,7 +30,7 @@
 Contact the owner of the wiki, who can enable email.""")
 
     try:
-        email = wikiutil.clean_input(form['email'][0].lower())
+        email = wikiutil.clean_input(form['email'].lower())
         if not email:
             # continue if email not given
             raise KeyError
@@ -42,7 +42,7 @@
         pass
 
     try:
-        username = wikiutil.clean_input(form['name'][0])
+        username = wikiutil.clean_input(form['name'])
         if not username:
             # continue if name not given
             raise KeyError
@@ -160,13 +160,13 @@
         page.send_page()
         return
 
-    submitted = form.get('account_sendmail', [''])[0]
-    token = form.get('token', [''])[0]
-    newpass = form.get('password', [''])[0]
-    name = form.get('name', [''])[0]
+    submitted = form.get('account_sendmail', '')
+    token = form.get('token', '')
+    newpass = form.get('password', '')
+    name = form.get('name', '')
 
     if token and name and newpass:
-        newpass2 = form.get('password_repeat', [''])[0]
+        newpass2 = form.get('password_repeat', '')
         msg = _("Passwords don't match!")
         msg_type = 'error'
         if newpass == newpass2:
@@ -190,7 +190,6 @@
             return
 
     if token and name:
-        request.emit_http_headers()
         request.theme.send_title(_("Password reset"), pagename=pagename)
 
         request.write(request.formatter.startContent("content"))
@@ -205,13 +204,12 @@
         request.theme.send_footer(pagename)
         request.theme.send_closing_html()
     elif submitted: # user pressed create button
-        if request.request_method != 'POST':
+        if request.method != 'POST':
             return
         msg = _do_recover(request)
         request.theme.add_msg(msg, "dialog")
         page.send_page()
     else: # show create form
-        request.emit_http_headers()
         request.theme.send_title(_("Lost password"), pagename=pagename)
 
         request.write(request.formatter.startContent("content"))
--- a/MoinMoin/action/refresh.py	Tue Sep 23 00:44:45 2008 +0200
+++ b/MoinMoin/action/refresh.py	Tue Sep 23 23:02:50 2008 +0200
@@ -11,10 +11,10 @@
 def execute(pagename, request):
     """ Handle refresh action """
     # Without arguments, refresh action will refresh the page text_html cache.
-    arena = request.form.get('arena', ['Page.py'])[0]
+    arena = request.form.get('arena', 'Page.py')
     if arena == 'Page.py':
         arena = Page(request, pagename)
-    key = request.form.get('key', ['text_html'])[0]
+    key = request.form.get('key', 'text_html')
 
     # Remove cache entry (if exists), and send the page
     from MoinMoin import caching
--- a/MoinMoin/action/revert.py	Tue Sep 23 00:44:45 2008 +0200
+++ b/MoinMoin/action/revert.py	Tue Sep 23 23:02:50 2008 +0200
@@ -48,10 +48,10 @@
     def do_action(self):
         """ revert pagename """
         form = self.form
-        comment = form.get('comment', [u''])[0]
+        comment = form.get('comment', u'')
         comment = wikiutil.clean_input(comment)
 
-        if self.request.request_method != 'POST':
+        if self.request.method != 'POST':
             return False, u''
 
         rev = self.request.rev
--- a/MoinMoin/action/rss_rc.py	Tue Sep 23 00:44:45 2008 +0200
+++ b/MoinMoin/action/rss_rc.py	Tue Sep 23 23:02:50 2008 +0200
@@ -24,8 +24,7 @@
     """ Send recent changes as an RSS document
     """
     if not wikixml.ok:
-        httpheaders = ["Content-Type: text/plain; charset=%s" % config.charset]
-        request.emit_http_headers(httpheaders)
+        request.mimetype = 'text/plain'
         request.write("rss_rc action is not supported because of missing pyxml module.")
         return
 
@@ -34,22 +33,22 @@
     # get params
     items_limit = 100
     try:
-        max_items = int(request.form['items'][0])
+        max_items = int(request.form['items'])
         max_items = min(max_items, items_limit) # not more than `items_limit`
     except (KeyError, ValueError):
         # not more than 15 items in a RSS file by default
         max_items = 15
     try:
-        unique = int(request.form.get('unique', [0])[0])
+        unique = int(request.form.get('unique', 0))
     except ValueError:
         unique = 0
     try:
-        diffs = int(request.form.get('diffs', [0])[0])
+        diffs = int(request.form.get('diffs', 0))
     except ValueError:
         diffs = 0
     ## ddiffs inserted by Ralf Zosel <ralf@zosel.com>, 04.12.2003
     try:
-        ddiffs = int(request.form.get('ddiffs', [0])[0])
+        ddiffs = int(request.form.get('ddiffs', 0))
     except ValueError:
         ddiffs = 0
 
@@ -86,28 +85,26 @@
     if request.if_modified_since == timestamp:
         if request.if_none_match:
             if request.if_none_match == etag:
-                request.emit_http_headers(["Status: 304 Not modified"])
+                request.status_code = 304
         else:
-            request.emit_http_headers(["Status: 304 Not modified"])
+            request.status_code = 304
     elif request.if_none_match == etag:
         if request.if_modified_since:
             if request.if_modified_since == timestamp:
-                request.emit_http_headers(["Status: 304 Not modified"])
+                request.status_code = 304
         else:
-            request.emit_http_headers(["Status: 304 Not modified"])
+            request.status_code = 304
     else:
         # generate an Expires header, using whatever setting the admin
         # defined for suggested cache lifetime of the RecentChanges RSS doc
-        expires = timefuncs.formathttpdate(time.time() + cfg.rss_cache)
+        expires = time.time() + cfg.rss_cache
 
-        httpheaders = ["Content-Type: text/xml; charset=%s" % config.charset,
-                       "Expires: %s" % expires,
-                       "Last-Modified: %s" % timestamp,
-                       "Etag: %s" % etag, ]
+        request.mime_type = 'text/xml'
+        request.expires = expires
+        request.last_modified = lastmod
+        request.headers.add('Etag', etag)
 
         # send the generated XML document
-        request.emit_http_headers(httpheaders)
-
         baseurl = request.getBaseURL()
         if not baseurl.endswith('/'):
             baseurl += '/'
--- a/MoinMoin/action/serveopenid.py	Tue Sep 23 00:44:45 2008 +0200
+++ b/MoinMoin/action/serveopenid.py	Tue Sep 23 23:02:50 2008 +0200
@@ -18,7 +18,7 @@
 from openid.server import server
 from openid.message import IDENTIFIER_SELECT
 from MoinMoin.widget import html
-from MoinMoin.request import MoinMoinFinish
+from MoinMoin.web.request import MoinMoinFinish
 
 def execute(pagename, request):
     return MoinOpenIDServer(pagename, request).handle()
@@ -31,9 +31,8 @@
 
     def serveYadisEP(self, endpoint_url):
         request = self.request
-        hdrs = ['Content-type: application/xrds+xml']
+        request.content_type = 'application/xrds+xml'
 
-        request.emit_http_headers(hdrs)
         user_url = request.getQualifiedURL(request.page.url(request))
         self.request.write("""\
 <?xml version="1.0" encoding="UTF-8"?>
@@ -79,9 +78,8 @@
 
     def serveYadisIDP(self, endpoint_url):
         request = self.request
-        hdrs = ['Content-type: application/xrds+xml']
+        request.content_type = 'application/xrds+xml'
 
-        request.emit_http_headers(hdrs)
         user_url = request.getQualifiedURL(request.page.url(request))
         self.request.write("""\
 <?xml version="1.0" encoding="UTF-8"?>
@@ -190,7 +188,7 @@
         server_url = request.getQualifiedURL(
                          request.page.url(request, querystr={'action': 'serveopenid'}))
 
-        yadis_type = form.get('yadis', [None])[0]
+        yadis_type = form.get('yadis')
         if yadis_type == 'ep':
             return self.serveYadisEP(server_url)
         elif yadis_type == 'idp':
@@ -198,7 +196,7 @@
 
         # if the identity is set it must match the server URL
         # sort of arbitrary, but we have to have some restriction
-        identity = form.get('openid.identity', [None])[0]
+        identity = form.get('openid.identity')
         if identity == IDENTIFIER_SELECT:
             identity, server_url = self._make_identity()
             if not identity:
@@ -217,7 +215,7 @@
         openidsrv = server.Server(store, op_endpoint=server_url)
 
         answer = None
-        if form.has_key('dontapprove'):
+        if 'dontapprove' in form:
             answer = self.handle_response(False, username, identity)
             if answer is None:
                 return
@@ -227,8 +225,8 @@
                 return
         else:
             query = {}
-            for key in form.keys():
-                query[key] = form[key][0]
+            for key in form:
+                query[key] = form[key]
             try:
                 openidreq = openidsrv.decodeRequest(query)
             except Exception, e:
@@ -249,10 +247,9 @@
             else:
                 answer = openidsrv.handleRequest(openidreq)
         webanswer = openidsrv.encodeResponse(answer)
-        headers = ['Status: %d OpenID status' % webanswer.code]
+        request.status = '%d OpenID status' % webanswer.code
         for hdr in webanswer.headers:
-            headers += [hdr+': '+webanswer.headers[hdr]]
-        request.emit_http_headers(headers)
+            request.headers.add(hdr, webanswer.headers[hdr])
         request.write(webanswer.body)
         raise MoinMoinFinish
 
@@ -266,7 +263,7 @@
         if session_nonce is not None:
             del self.request.session['openidserver.nonce']
         # use empty string if nothing was sent
-        form_nonce = form.get('nonce', [''])[0]
+        form_nonce = form.get('nonce', '')
         if session_nonce != form_nonce:
             self.request.makeForbidden403()
             self.request.write('invalid nonce')
@@ -285,7 +282,7 @@
             return openidreq.answer(False)
 
 
-        if form.get('remember', ['no'])[0] == 'yes':
+        if form.get('remember', 'no') == 'yes':
             if not hasattr(request.user, 'openid_trusted_roots'):
                 request.user.openid_trusted_roots = []
             request.user.openid_trusted_roots.append(strbase64(openidreq.trust_root))
@@ -324,7 +321,6 @@
 Once you have logged in, simply reload this page.'''))
             return
 
-        request.emit_http_headers()
         request.theme.send_title(_("OpenID Trust verification"), pagename=request.page.page_name)
         # Start content (important for RTL support)
         request.write(request.formatter.startContent("content"))
@@ -397,7 +393,6 @@
         request = self.request
         _ = self._
 
-        request.emit_http_headers()
         request.theme.send_title(_("OpenID not served"), pagename=request.page.page_name)
         # Start content (important for RTL support)
         request.write(request.formatter.startContent("content"))
--- a/MoinMoin/action/showtags.py	Tue Sep 23 00:44:45 2008 +0200
+++ b/MoinMoin/action/showtags.py	Tue Sep 23 23:02:50 2008 +0200
@@ -13,9 +13,7 @@
 from MoinMoin.wikisync import TagStore
 
 def execute(pagename, request):
-    mimetype = "text/plain"
-
-    request.emit_http_headers(["Content-Type: %s; charset=%s" % (mimetype, config.charset)])
+    request.mimetype = "text/plain"
 
     page = Page(request, pagename)
     tags = TagStore(page)
--- a/MoinMoin/action/sisterpages.py	Tue Sep 23 00:44:45 2008 +0200
+++ b/MoinMoin/action/sisterpages.py	Tue Sep 23 23:02:50 2008 +0200
@@ -32,27 +32,25 @@
     if request.if_modified_since == timestamp:
         if request.if_none_match:
             if request.if_none_match == etag:
-                request.emit_http_headers(["Status: 304 Not modified"])
+                request.status_code = 304
         else:
-            request.emit_http_headers(["Status: 304 Not modified"])
+            request.status_code = 304
     elif request.if_none_match == etag:
         if request.if_modified_since:
             if request.if_modified_since == timestamp:
-                request.emit_http_headers(["Status: 304 Not modified"])
+                request.status_code = 304
         else:
-            request.emit_http_headers(["Status: 304 Not modified"])
+            request.status_code = 304
     else:
         # generate an Expires header, using 1d cache lifetime of sisterpages list
-        expires = timefuncs.formathttpdate(time.time() + 24*3600)
+        expires = time.time() + 24*3600
 
-        httpheaders = ["Content-Type: text/plain; charset=UTF-8",
-                       "Expires: %s" % expires,
-                       "Last-Modified: %s" % timestamp,
-                       "Etag: %s" % etag, ]
+        request.mime_type = 'text/plain'
+        request.expires = expires
+        request.last_modified = timestamp
+        request.headers.add("Etag", etag)
 
         # send the generated XML document
-        request.emit_http_headers(httpheaders)
-
         baseurl = request.getBaseURL()
         if not baseurl.endswith('/'):
             baseurl += '/'
--- a/MoinMoin/action/sitemap.py	Tue Sep 23 00:44:45 2008 +0200
+++ b/MoinMoin/action/sitemap.py	Tue Sep 23 23:02:50 2008 +0200
@@ -65,7 +65,7 @@
     form = request.form
     request.user.datetime_fmt = datetime_fmt
 
-    request.emit_http_headers(["Content-Type: text/xml; charset=UTF-8"])
+    request.mimetype ='text/xml'
 
     # we emit a piece of data so other side doesn't get bored:
     request.write("""<?xml version="1.0" encoding="UTF-8"?>\r\n""")
@@ -86,7 +86,7 @@
 
     # Get page dict readable by current user
     try:
-        underlay = int(form.get('underlay', [1])[0])
+        underlay = int(form.get('underlay', 1))
     except ValueError:
         underlay = 1
     pages = request.rootpage.getPageDict(include_underlay=underlay)
--- a/MoinMoin/action/thread_monitor.py	Tue Sep 23 00:44:45 2008 +0200
+++ b/MoinMoin/action/thread_monitor.py	Tue Sep 23 23:02:50 2008 +0200
@@ -33,7 +33,6 @@
     else:
         dump_fname = "nowhere"
 
-    request.emit_http_headers()
     request.write('<html><body>A dump has been saved to %s.</body></html>' % dump_fname)
 
 def execute_wiki(pagename, request):
@@ -44,8 +43,6 @@
         request.theme.add_msg(_('You are not allowed to use this action.'), "error")
         return Page.Page(request, pagename).send_page()
 
-    request.emit_http_headers()
-
     request.theme.send_title("Thread monitor")
     request.write('<pre>')
 
--- a/MoinMoin/action/titleindex.py	Tue Sep 23 00:44:45 2008 +0200
+++ b/MoinMoin/action/titleindex.py	Tue Sep 23 23:02:50 2008 +0200
@@ -18,11 +18,10 @@
 
     # get the MIME type
     if 'mimetype' in form:
-        mimetype = form['mimetype'][0]
+        mimetype = form['mimetype']
     else:
         mimetype = "text/plain"
-
-    request.emit_http_headers(["Content-Type: %s; charset=%s" % (mimetype, config.charset)])
+    request.mimetype = mimetype
 
     # Get list of user readable pages
     pages = request.rootpage.getPageList()
--- a/MoinMoin/action/userprefs.py	Tue Sep 23 00:44:45 2008 +0200
+++ b/MoinMoin/action/userprefs.py	Tue Sep 23 23:02:50 2008 +0200
@@ -16,7 +16,7 @@
     Return error msg_class, msg tuple or None, None.
     """
     _ = request.getText
-    sub = request.form.get('handler', [None])[0]
+    sub = request.form.get('handler')
 
     if sub in request.cfg.userprefs_disabled:
         return None, None
@@ -68,7 +68,7 @@
     else:
         msg_class, msg = None, None
 
-    sub = request.form.get('sub', [''])[0]
+    sub = request.args.get('sub', '')
     cls = None
     if sub and sub not in request.cfg.userprefs_disabled:
         try:
@@ -101,7 +101,6 @@
         title = _("Settings") + ":" + title
     else:
         title = _("Settings")
-    request.emit_http_headers()
     request.theme.add_msg(msg, msg_class)
     request.theme.send_title(title, page=request.page, pagename=pagename)
     # Start content (important for RTL support)
--- a/MoinMoin/action/userprofile.py	Tue Sep 23 00:44:45 2008 +0200
+++ b/MoinMoin/action/userprofile.py	Tue Sep 23 23:02:50 2008 +0200
@@ -17,9 +17,9 @@
     if not request.user.isSuperUser():
         request.theme.add_msg(_("Only superuser is allowed to use this action."), "error")
     else:
-        user_name = form.get('name', [''])[0]
-        key = form.get('key', [''])[0]
-        val = form.get('val', [''])[0]
+        user_name = form.get('name', '')
+        key = form.get('key', '')
+        val = form.get('val', '')
         if key in cfg.user_checkbox_fields:
             val = int(val)
         uid = user.getUserId(request, user_name)
--- a/MoinMoin/auth/__init__.py	Tue Sep 23 00:44:45 2008 +0200
+++ b/MoinMoin/auth/__init__.py	Tue Sep 23 23:02:50 2008 +0200
@@ -257,3 +257,94 @@
                'userprefslink': userprefslink,
                'sendmypasswordlink': sendmypasswordlink}
 
+def handle_login(request, userobj=None, username=None, password=None,
+                 attended=True, openid_identifier=None, stage=None):
+    """
+    Process a 'login' request by going through the configured authentication
+    methods in turn. The passable keyword arguments are explained in more
+    detail at the top of this file.
+    """
+    params = {
+        'username': username,
+        'password': password,
+        'attended': attended,
+        'openid_identifier': openid_identifier,
+        'multistage': (stage and True) or None
+    }
+    for authmethod in request.cfg.auth:
+        if stage and authmethod.name != stage:
+            continue
+        ret = authmethod.login(request, userobj, **params)
+
+        userobj = ret.user_obj
+        cont = ret.continue_flag
+        if stage:
+            stage = None
+            del params['multistage']
+
+        if ret.multistage:
+            request._login_multistage = ret.multistage
+            request._login_multistage_name = authmethod.name
+            return userobj
+
+        if ret.redirect_to:
+            nextstage = auth.get_multistage_continuation_url(request, authmethod.name)
+            url = ret.redirect_to
+            url = url.replace('%return_form', quote_plus(nextstage))
+            url = url.replace('%return', quote(nextstage))
+            abort(redirect(url))
+        msg = ret.message
+        if msg and not msg in request._login_messages:
+            request._login_messages.append(msg)
+
+        if not cont:
+            break
+
+    return userobj
+
+def handle_logout(request, userobj):
+    """ Logout the passed user from every configured authentication method. """
+    for authmethod in request.cfg.auth:
+        userobj, cont = authmethod.logout(request, userobj, cookie=request.cookies)
+        if not cont:
+            break
+    return userobj
+
+def handle_request(request, userobj):
+    """ Handle the per-request callbacks of the configured authentication methods. """
+    for authmethod in request.cfg.auth:
+        userobj, cont = authmethod.request(request, userobj, cookie=request.cookies)
+        if not cont:
+            break
+    return userobj
+
+def setup_setuid(request, userobj):
+    """ Check for setuid conditions in the session and setup an user
+    object accordingly. Returns a tuple of the new user objects.
+
+    @param request: a moin request object
+    @param userobj: a moin user object
+    @rtype: boolean
+    @return: (new_user, user) or (user, None)
+    """
+    old_user = None
+    if 'setuid' in request.session and userobj.isSuperUser():
+        old_user = userobj
+        uid = request.session['setuid']
+        userobj = user.User(request, uid, auth_method='setuid')
+        userobj.valid = True
+    return (userobj, old_user)
+
+def setup_from_session(request, session):
+    userobj = None
+    if 'user.id' in session:
+        auth_userid = session['user.id']
+        auth_method = session['user.auth_method']
+        auth_attrs = session['user.auth_attribs']
+        if auth_method and auth_method in \
+                [auth.name for auth in request.cfg.auth]:
+            userobj = user.User(request, id=auth_userid,
+                                auth_method=auth_method,
+                                auth_attribs=auth_attrs)
+    logging.debug("session started for user %r", userobj)
+    return userobj
--- a/MoinMoin/auth/_tests/test_auth.py	Tue Sep 23 00:44:45 2008 +0200
+++ b/MoinMoin/auth/_tests/test_auth.py	Tue Sep 23 23:02:50 2008 +0200
@@ -5,14 +5,11 @@
     @copyright: 2008 MoinMoin:ThomasWaldmann
     @license: GNU GPL, see COPYING for details.
 """
-
 import py.test
 py.test.skip("broken due to test Config refactoring, fix later")
 
-import StringIO, urllib
-
-from MoinMoin.server.server_wsgi import WsgiConfig
-from MoinMoin.request import request_wsgi
+from MoinMoin.web.request import TestRequest, evaluate_request
+from MoinMoin import wsgiapp
 from MoinMoin._tests import wikiconfig
 
 
@@ -25,55 +22,32 @@
 
         Some test needs specific config values, or they will fail.
         """
-        config = WsgiConfig() # you MUST create an instance
 
     def teardown_class(cls):
         """ Stuff that should run to clean up the state of this test class
 
         """
-        pass
 
-    def setup_env(self, **kw):
-        default_environ = {
-            'SERVER_NAME': 'localhost',
-            'SERVER_PORT': '80',
-            'SCRIPT_NAME': '',
-            'PATH_INFO': '/',
-            'QUERY_STRING': '',
-            'REQUEST_METHOD': 'GET',
-            'REMOTE_ADDR': '10.10.10.10',
-            'HTTP_HOST': 'localhost',
-            #'HTTP_COOKIE': '',
-            #'HTTP_ACCEPT_LANGUAGE': '',
-        }
-        env = {}
-        env.update(default_environ)
-        env.update(kw)
-        if 'wsgi.input' not in env:
-            env['wsgi.input'] = StringIO.StringIO()
-        return env
-
-    def process_request(self, environ):
-        request = request_wsgi.Request(environ)
-        save_user = request.user # keep a reference, request.finish does "del request.user"
-        request.run()
-        request.user = save_user
-        return request # request.status, request.headers, request.output()
+    def run_request(self, **params):
+        request = TestRequest(**params)
+        context = wsgiapp.init(request)
+        wsgiapp.run(context)
+        return context
 
 
 class TestNoAuth(AuthTest):
     def testNoAuth(self):
         """ run a simple request, no auth, just check if it succeeds """
-        environ = self.setup_env()
-        request = self.process_request(environ)
+        request = self.run_request()
 
         # anon user?
         assert not request.user.valid
 
+        appiter, status, headers = evaluate_request(request.request)
         # check if the request resulted in normal status, result headers and content
-        assert request.status == '200 OK'
+        assert status[:3] == '200'
         has_ct = has_v = has_cc = False
-        for k, v in request.headers:
+        for k, v in headers:
             if k == 'Content-Type':
                 assert v.startswith('text/html')
                 has_ct = True
@@ -90,8 +64,7 @@
         assert has_v
         # XXX BROKEN?:
         #assert has_cc # cache anon user's content
-        output = request.output()
-        assert '</html>' in output
+        assert '</html>' in ''.join(appiter)
 
 class TestAnonSession(AuthTest):
     class Config(wikiconfig.Config):
@@ -103,20 +76,21 @@
         trail_expected = []
         first = True
         for pagename in self.PAGES:
-            environ = self.setup_env(PATH_INFO='/%s' % pagename,
-                                     HTTP_COOKIE=cookie)
-            request = self.process_request(environ)
+            environ_overrides = {'HTTP_COOKIE': cookie}
+            request = self.run_request(path='/%s' % pagename,
+                                       environ_overrides=environ_overrides)
 
             # anon user?
             assert not request.user.valid
 
             # Do we have a session?
-            assert request.session
+            assert request.session is not None
 
+            appiter, status, headers = evaluate_request(request.request)
             # check if the request resulted in normal status, result headers and content
-            assert request.status == '200 OK'
+            assert status[:3] == '200'
             has_ct = has_v = has_cc = False
-            for k, v in request.headers:
+            for k, v in headers:
                 if k == 'Content-Type':
                     assert v.startswith('text/html')
                     has_ct = True
@@ -134,8 +108,7 @@
             assert has_v
             # XX BROKEN
             #assert not has_cc # do not cache anon user's (with session!) content
-            output = request.output()
-            assert '</html>' in output
+            assert '</html>' in ''.join(appiter)
 
             # The trail is only ever saved on the second page display
             # because otherwise anonymous sessions would be created
@@ -163,24 +136,27 @@
     def testHttpAuthSession(self):
         """ run some requests with http auth, check whether session works """
         username = u'HttpAuthTestUser'
+        auth_info = u'%s:%s' % (username, u'testpass')
+        auth_header = 'Basic %s' % auth_info.encode('base64')
         cookie = ''
         trail_expected = []
         first = True
         for pagename in self.PAGES:
-            environ = self.setup_env(AUTH_TYPE='Basic', REMOTE_USER=str(username),
-                                     PATH_INFO='/%s' % pagename,
-                                     HTTP_COOKIE=cookie)
-            request = self.process_request(environ)
+            environ_overrides = {'HTTP_COOKIE': cookie,
+                                 'HTTP_AUTHORIZATION': auth_header}
+            request = self.run_request(path='/%s' % pagename,
+                                       environ_overrides=environ_overrides)
 
             # Login worked?
             assert request.user.valid
             assert request.user.name == username
 
             # Do we have a session?
-            assert request.session
+            assert request.session is not None
 
+            appiter, status, headers = evaluate_request(request.request)
             # check if the request resulted in normal status, result headers and content
-            assert request.status == '200 OK'
+            assert status[:3] == '200'
             has_ct = has_v = has_cc = False
             for k, v in request.headers:
                 if k == 'Content-Type':
@@ -199,8 +175,7 @@
             assert has_ct
             assert has_v
             assert has_cc # do not cache logged-in user's content
-            output = request.output()
-            assert '</html>' in output
+            assert '</html>' in ''.join(appiter)
 
             # The trail is only ever saved on the second page display
             # because otherwise anonymous sessions would be created
@@ -233,30 +208,29 @@
         first = True
         for pagename in self.PAGES:
             if first:
-                formdata = urllib.urlencode({
-                    'name': username.encode('utf-8'),
-                    'password': password.encode('utf-8'),
+                formdata = {
+                    'name': username,
+                    'password': password,
                     'login': 'login',
-                })
-                environ = self.setup_env(PATH_INFO='/%s' % pagename,
-                                         HTTP_CONTENT_TYPE='application/x-www-form-urlencoded',
-                                         HTTP_CONTENT_LENGTH='%d' % len(formdata),
-                                         QUERY_STRING='action=login', REQUEST_METHOD='POST',
-                                         **{'wsgi.input': StringIO.StringIO(formdata)})
+                }
+                request = self.run_request(path='/%s' % pagename,
+                                           query_string='login=login',
+                                           method='POST', form_data=formdata)
             else: # not first page, use session cookie
-                environ = self.setup_env(PATH_INFO='/%s' % pagename,
-                                         HTTP_COOKIE=cookie)
-            request = self.process_request(environ)
+                environ_overrides = {'HTTP_COOKIE': cookie}
+                request = self.run_request(path='/%s' % pagename,
+                                           environ_overrides=environ_overrides)
 
             # Login worked?
             assert request.user.valid
             assert request.user.name == username
 
             # Do we have a session?
-            assert request.session
+            assert request.session is not None
 
+            appiter, status, headers = evaluate_request(request.request)
             # check if the request resulted in normal status, result headers and content
-            assert request.status == '200 OK'
+            assert status[:3] == '200'
             has_ct = has_v = has_cc = False
             for k, v in request.headers:
                 if k == 'Content-Type':
@@ -275,8 +249,7 @@
             assert has_ct
             assert has_v
             assert has_cc # do not cache logged-in user's content
-            output = request.output()
-            assert '</html>' in output
+            assert '</html>' in ''.join(appiter)
 
             # The trail is only ever saved on the second page display
             # because otherwise anonymous sessions would be created
--- a/MoinMoin/auth/_tests/test_ldap_login.py	Tue Sep 23 00:44:45 2008 +0200
+++ b/MoinMoin/auth/_tests/test_ldap_login.py	Tue Sep 23 23:02:50 2008 +0200
@@ -12,6 +12,7 @@
 from MoinMoin._tests.ldap_testbase import LDAPTstBase, LdapEnvironment, check_environ, SLAPD_EXECUTABLE
 from MoinMoin._tests.ldap_testdata import *
 from MoinMoin._tests import nuke_user, wikiconfig
+from MoinMoin.auth import handle_login
 
 # first check if we have python 2.4, python-ldap and slapd:
 msg = check_environ()
@@ -60,21 +61,21 @@
         handle_auth = self.request.handle_auth
 
         # tests that must not authenticate:
-        u = handle_auth(None, username='', password='', login=True)
+        u = handle_login(self.request, None, username='', password='')
         assert u is None
-        u = handle_auth(None, username='usera', password='', login=True)
+        u = handle_login(self.request, None, username='usera', password='')
         assert u is None
-        u = handle_auth(None, username='usera', password='userawrong', login=True)
+        u = handle_login(self.request, None, username='usera', password='userawrong')
         assert u is None
-        u = handle_auth(None, username='userawrong', password='usera', login=True)
+        u = handle_login(self.request, None, username='userawrong', password='usera')
         assert u is None
 
         # tests that must authenticate:
-        u1 = handle_auth(None, username='usera', password='usera', login=True)
+        u1 = handle_login(self.request, None, username='usera', password='usera')
         assert u1 is not None
         assert u1.valid
 
-        u2 = handle_auth(None, username='userb', password='userb', login=True)
+        u2 = handle_login(self.request, None, username='userb', password='userb')
         assert u2 is not None
         assert u2.valid
 
@@ -111,27 +112,25 @@
 
         nuke_user(self.request, u'usera')
 
-        handle_auth = self.request.handle_auth
-
         # do a LDAPAuth login (as a side effect, this autocreates the user profile):
-        u1 = handle_auth(None, username='usera', password='usera', login=True)
+        u1 = handle_login(self.request, None, username='usera', password='usera')
         assert u1 is not None
         assert u1.valid
 
         # now we kill the LDAP server:
-        self.ldap_env.slapd.stop()
+        #self.ldap_env.slapd.stop()
 
         # now try a MoinAuth login:
         # try the default password that worked in 1.7 up to rc1:
-        u2 = handle_auth(None, username='usera', password='{SHA}NotStored', login=True)
+        u2 = handle_login(self.request, None, username='usera', password='{SHA}NotStored')
         assert u2 is None
 
         # try using no password:
-        u2 = handle_auth(None, username='usera', password='', login=True)
+        u2 = handle_login(self.request, None, username='usera', password='')
         assert u2 is None
 
         # try using wrong password:
-        u2 = handle_auth(None, username='usera', password='wrong', login=True)
+        u2 = handle_login(self.request, None, username='usera', password='wrong')
         assert u2 is None
 
 
@@ -221,7 +220,7 @@
         handle_auth = self.request.handle_auth
 
         # authenticate user (with primary slapd):
-        u1 = handle_auth(None, username='usera', password='usera', login=True)
+        u1 = handle_login(self.request, None, username='usera', password='usera')
         assert u1 is not None
         assert u1.valid
 
@@ -229,7 +228,7 @@
         self.ldap_envs[0].slapd.stop()
 
         # try if we can still authenticate (with the second slapd):
-        u2 = handle_auth(None, username='usera', password='usera', login=True)
+        u2 = handle_login(self.request, None, username='usera', password='usera')
         assert u2 is not None
         assert u2.valid
 
--- a/MoinMoin/auth/http.py	Tue Sep 23 00:44:45 2008 +0200
+++ b/MoinMoin/auth/http.py	Tue Sep 23 23:02:50 2008 +0200
@@ -14,7 +14,6 @@
 """
 
 from MoinMoin import config, user
-from MoinMoin.request import request_twisted, request_cli, request_standalone
 from MoinMoin.auth import BaseAuth
 from base64 import decodestring
 
@@ -36,45 +35,10 @@
         if user_obj:
             return user_obj, True
 
-        # for standalone, request authorization and verify it,
-        # deny access if it isn't verified
-        if isinstance(request, request_standalone.Request):
-            request.setHttpHeader('WWW-Authenticate: Basic realm="MoinMoin"')
-            auth = request.headers.get('Authorization')
-            if auth:
-                auth = auth.split()[-1]
-                info = decodestring(auth).split(':', 1)
-                if len(info) == 2:
-                    u = user.User(request, auth_username=info[0], password=info[1],
-                                  auth_method=self.name, auth_attribs=[])
-            if not u:
-                request.makeForbidden(401, _('You need to log in.'))
-        # for Twisted, just check
-        elif isinstance(request, request_twisted.Request):
-            username = request.twistd.getUser().decode(config.charset)
-            password = request.twistd.getPassword().decode(config.charset)
-            # when using Twisted http auth, we use username and password from
-            # the moin user profile, so both can be changed by user.
-            u = user.User(request, auth_username=username, password=password,
-                          auth_method=self.name, auth_attribs=())
-        elif not isinstance(request, request_cli.Request):
-            env = request.env
-            auth_type = env.get('AUTH_TYPE', '')
-            if auth_type in ['Basic', 'Digest', 'NTLM', 'Negotiate', ]:
-                username = env.get('REMOTE_USER', '').decode(config.charset)
-                if auth_type in ('NTLM', 'Negotiate', ):
-                    # converting to standard case so the user can even enter wrong case
-                    # (added since windows does not distinguish between e.g.
-                    #  "Mike" and "mike")
-                    username = username.split('\\')[-1] # split off domain e.g.
-                                                        # from DOMAIN\user
-                    # this "normalizes" the login name from {meier, Meier, MEIER} to Meier
-                    # put a comment sign in front of next line if you don't want that:
-                    username = username.title()
-                # when using http auth, we have external user name and password,
-                # we don't use the moin user profile for those attributes.
-                u = user.User(request, auth_username=username,
-                              auth_method=self.name, auth_attribs=('name', 'password'))
+        authobj = request.authorization
+        if authobj:
+            u = user.User(request, auth_username=authobj.username,
+                          auth_method=self.name, auth_attribs=('name', 'password'))
 
         if u and self.autocreate:
             u.create_or_update()
--- a/MoinMoin/auth/openidrp.py	Tue Sep 23 00:44:45 2008 +0200
+++ b/MoinMoin/auth/openidrp.py	Tue Sep 23 23:02:50 2008 +0200
@@ -136,7 +136,7 @@
                                         MoinOpenIDStore(request))
         query = {}
         for key in request.form:
-            query[key] = request.form[key][0]
+            query[key] = request.form[key]
         current_url = get_multistage_continuation_url(request, self.name,
                                                       {'oidstage': '1'})
         info = oidconsumer.complete(query, current_url)
--- a/MoinMoin/auth/sslclientcert.py	Tue Sep 23 00:44:45 2008 +0200
+++ b/MoinMoin/auth/sslclientcert.py	Tue Sep 23 23:02:50 2008 +0200
@@ -11,7 +11,6 @@
 """
 
 from MoinMoin import config, user
-from MoinMoin.request import request_twisted
 from MoinMoin.auth import BaseAuth
 
 class SSLClientCertAuth(BaseAuth):
@@ -35,60 +34,53 @@
     def request(self, request, user_obj, **kw):
         u = None
         changed = False
-        # check if we are running Twisted
-        if isinstance(request, request_twisted.Request):
-            return user_obj, True # not supported if we run twisted
-            # Addendum: this seems to need quite some twisted insight and coding.
-            # A pointer i got on #twisted: divmod's vertex.sslverify
-            # If you really need this, feel free to implement and test it and
-            # submit a patch if it works.
-        else:
-            env = request.env
-            if env.get('SSL_CLIENT_VERIFY', 'FAILURE') == 'SUCCESS':
-
-                # check authority list if given
-                if self.authorities and env.get('SSL_CLIENT_I_DN_OU') in self.authorities:
-                    return user_obj, True
 
-                email_lower = None
-                if self.email_key:
-                    email = env.get('SSL_CLIENT_S_DN_Email', '').decode(config.charset)
-                    email_lower = email.lower()
-                commonname_lower = None
-                if self.name_key:
-                    commonname = env.get('SSL_CLIENT_S_DN_CN', '').decode(config.charset)
-                    commonname_lower = commonname.lower()
-                if email_lower or commonname_lower:
-                    for uid in user.getUserList(request):
-                        u = user.User(request, uid,
-                                      auth_method=self.name, auth_attribs=())
-                        if self.email_key and email_lower and u.email.lower() == email_lower:
-                            u.auth_attribs = ('email', 'password')
-                            if self.use_name and commonname_lower != u.name.lower():
-                                u.name = commonname
-                                changed = True
-                                u.auth_attribs = ('email', 'name', 'password')
-                            break
-                        if self.name_key and commonname_lower and u.name.lower() == commonname_lower:
-                            u.auth_attribs = ('name', 'password')
-                            if self.use_email and email_lower != u.email.lower():
-                                u.email = email
-                                changed = True
-                                u.auth_attribs = ('name', 'email', 'password')
-                            break
-                    else:
-                        u = None
-                    if u is None:
-                        # user wasn't found, so let's create a new user object
-                        u = user.User(request, name=commonname_lower, auth_username=commonname_lower,
-                                      auth_method=self.name)
+        env = request.environ
+        if env.get('SSL_CLIENT_VERIFY', 'FAILURE') == 'SUCCESS':
+
+            # check authority list if given
+            if self.authorities and env.get('SSL_CLIENT_I_DN_OU') in self.authorities:
+                return user_obj, True
+
+            email_lower = None
+            if self.email_key:
+                email = env.get('SSL_CLIENT_S_DN_Email', '').decode(config.charset)
+                email_lower = email.lower()
+            commonname_lower = None
+            if self.name_key:
+                commonname = env.get('SSL_CLIENT_S_DN_CN', '').decode(config.charset)
+                commonname_lower = commonname.lower()
+            if email_lower or commonname_lower:
+                for uid in user.getUserList(request):
+                    u = user.User(request, uid,
+                                  auth_method=self.name, auth_attribs=())
+                    if self.email_key and email_lower and u.email.lower() == email_lower:
+                        u.auth_attribs = ('email', 'password')
+                        if self.use_name and commonname_lower != u.name.lower():
+                            u.name = commonname
+                            changed = True
+                            u.auth_attribs = ('email', 'name', 'password')
+                        break
+                    if self.name_key and commonname_lower and u.name.lower() == commonname_lower:
                         u.auth_attribs = ('name', 'password')
-                        if self.use_email:
+                        if self.use_email and email_lower != u.email.lower():
                             u.email = email
+                            changed = True
                             u.auth_attribs = ('name', 'email', 'password')
-            elif user_obj and user_obj.auth_method == self.name:
-                user_obj.valid = False
-                return user_obj, False
+                        break
+                else:
+                    u = None
+                if u is None:
+                    # user wasn't found, so let's create a new user object
+                    u = user.User(request, name=commonname_lower, auth_username=commonname_lower,
+                                  auth_method=self.name)
+                    u.auth_attribs = ('name', 'password')
+                    if self.use_email:
+                        u.email = email
+                        u.auth_attribs = ('name', 'email', 'password')
+        elif user_obj and user_obj.auth_method == self.name:
+            user_obj.valid = False
+            return user_obj, False
         if u and self.autocreate:
             u.create_or_update(changed)
         if u and u.valid:
--- a/MoinMoin/config/multiconfig.py	Tue Sep 23 00:44:45 2008 +0200
+++ b/MoinMoin/config/multiconfig.py	Tue Sep 23 23:02:50 2008 +0200
@@ -16,13 +16,15 @@
 from MoinMoin import log
 logging = log.getLogger(__name__)
 
-from MoinMoin import config, error, util, wikiutil
+from MoinMoin import config, error, util, wikiutil, web
 from MoinMoin.auth import MoinAuth
+import MoinMoin.auth as authmodule
 import MoinMoin.events as events
 from MoinMoin.events import PageChangedEvent, PageRenamedEvent
 from MoinMoin.events import PageDeletedEvent, PageCopiedEvent
 from MoinMoin.events import PageRevertedEvent, FileAttachedEvent
 from MoinMoin import session
+import MoinMoin.web.session
 from MoinMoin.packages import packLine
 from MoinMoin.security import AccessControlList
 from MoinMoin.support.python_compatibility import set
@@ -694,6 +696,8 @@
      "See HelpOnSessions."),
     ('session_id_handler', DefaultExpression('session.MoinCookieSessionIDHandler()'),
      "Only used by the DefaultSessionHandler, see HelpOnSessions."),
+    ('session_service', DefaultExpression('web.session.FileSessionService()'),
+     "New session service (used by the new WSGI layer)"),
     ('cookie_secure', None,
      'Use secure cookie. (None = auto-enable secure cookie for https, True = ever use secure cookie, False = never use secure cookie).'),
     ('cookie_domain', None,
--- a/MoinMoin/conftest.py	Tue Sep 23 00:44:45 2008 +0200
+++ b/MoinMoin/conftest.py	Tue Sep 23 23:02:50 2008 +0200
@@ -30,6 +30,8 @@
 sys.path.insert(0, str(moindir))
 
 from MoinMoin.support.python_compatibility import set
+from MoinMoin.web.request import TestRequest, Client
+from MoinMoin.wsgiapp import application, init
 from MoinMoin._tests import maketestwiki, wikiconfig
 
 coverage_modules = set()
@@ -55,7 +57,6 @@
         coverage.erase()
         coverage.start()
 
-
     py.test.config.addoptions('MoinMoin options', py.test.config.Option('-C',
         '--coverage', action='callback', callback=callback,
         help='Output information about code coverage (slow!)'))
@@ -65,17 +66,12 @@
 
 
 def init_test_request(given_config=None, static_state=[False]):
-    from MoinMoin.request import request_cli
-    from MoinMoin.user import User
-    from MoinMoin.formatter.text_html import Formatter as HtmlFormatter
     if not static_state[0]:
         maketestwiki.run(True)
         static_state[0] = True
-    request = request_cli.Request(given_config=given_config)
-    request.form = request.args = request.setup_args()
-    request.user = User(request)
-    request.html_formatter = HtmlFormatter(request)
-    request.formatter = request.html_formatter
+    request = TestRequest()
+    request.given_config = given_config
+    request = init(request)
     return request
 
 
@@ -100,6 +96,7 @@
             cls.request = init_test_request(given_config=cls.Config)
         else:
             cls.request = self.parent.request
+        cls.client = Client(application)
         super(MoinClassCollector, self).setup()
 
 
--- a/MoinMoin/converter/_tests/test_text_html_text_moin_wiki.py	Tue Sep 23 00:44:45 2008 +0200
+++ b/MoinMoin/converter/_tests/test_text_html_text_moin_wiki.py	Tue Sep 23 23:02:50 2008 +0200
@@ -14,7 +14,7 @@
 from MoinMoin.converter import text_html_text_moin_wiki as converter
 from MoinMoin.parser.text_moin_wiki import Parser
 from MoinMoin.formatter.text_gedit import Formatter
-from MoinMoin.request import Clock
+from MoinMoin.util.clock import Clock
 from MoinMoin.error import ConvertError
 
 convert = converter.convert
--- a/MoinMoin/converter/text_html_text_moin_wiki.py	Tue Sep 23 00:44:45 2008 +0200
+++ b/MoinMoin/converter/text_html_text_moin_wiki.py	Tue Sep 23 23:02:50 2008 +0200
@@ -1205,7 +1205,7 @@
         href = attrs.pop('href', None)
         css_class = attrs.get('class')
 
-        scriptname = self.request.getScriptname()
+        scriptname = self.request.script_root
         if scriptname == "":
             scriptname = "/"
 
--- a/MoinMoin/failure.py	Tue Sep 23 00:44:45 2008 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,208 +0,0 @@
-# -*- coding: iso-8859-1 -*-
-""" MoinMoin failure
-
-    Handle fatal errors by showing a message and debugging information.
-
-    @copyright: 2004-2005 Nir Soffer <nirs@freeshell.org>
-    @license: GNU GPL, see COPYING for details.
-"""
-import sys, os
-import traceback
-
-from MoinMoin import log
-logging = log.getLogger(__name__)
-
-from MoinMoin.support import cgitb
-from MoinMoin.error import ConfigurationError
-
-
-class View(cgitb.View):
-    """ Display an error message and debugging information
-
-    Additions to cgitb.View:
-     - Multiple tracebacks support
-     - Debugging information is shown only in debug mode
-     - Moin application information
-     - General help text and links
-     - Handle multiple paragraphs in exception message
-
-    cgitb is heavily modified cgitb, but fully backward compatible with
-    the standard cgitb. It should not contain any moin specific code.
-
-    cgitb was refactored to be easy to customize by applications
-    developers. This moin specific subclass is an example.
-    """
-    debugInfoID = 'debug-info'
-
-    def formatContent(self):
-        content = (
-            self.script(),
-            self.formatStylesheet(),
-            self.formatTitle(),
-            self.formatMessage(),
-            self.formatButtons(),
-            self.formatDebugInfo(),
-            self.formatTextTraceback()
-            )
-        return ''.join(content)
-
-    def script(self):
-        return '''
-<script type="text/javascript">
-function toggleDebugInfo() {
-    var tb = document.getElementById('%s');
-    if (tb == null) return;
-    tb.style.display = tb.style.display ? '' : 'none';
-}
-</script>
-''' % self.debugInfoID
-
-    def stylesheet(self):
-        return cgitb.View.stylesheet(self) + """
-.cgitb .buttons {margin: 0.5em 0; padding: 5px 10px;}
-.cgitb .buttons li {display: inline; margin: 0; padding: 0 0.25em;}
-"""
-
-    def formatMessage(self):
-        """ handle multiple paragraphs messages and add general help """
-        f = self.formatter
-        text = [self.formatExceptionMessage(self.info)]
-
-        if self.info[0] == ConfigurationError:
-            tbt = traceback.extract_tb(self.info[1].exceptions()[-1][2])[-1]
-            text.append(
-                f.paragraph('Error in your configuration file "%s"'
-                            ' around line %d.' % tbt[:2]))
-        else:
-            text.append(
-                f.paragraph("If you want to report a bug, please save "
-                            "this page and  attach it to your bug report."))
-        return ''.join(text)
-
-    def formatButtons(self):
-        """ Add 'buttons' to the error dialog """
-        f = self.formatter
-        buttons = [f.link('javascript:toggleDebugInfo()',
-                          'Show debugging information')]
-        if self.info[0] != ConfigurationError:
-            buttons.append(
-                   f.link('http://moinmo.in/MoinMoinBugs',
-                          'Report bug'))
-            buttons.append(
-                   f.link('http://moinmo.in/FrontPage',
-                          'Visit MoinMoin wiki'))
-        return f.list(buttons, {'class': 'buttons'})
-
-    def formatDebugInfo(self):
-        """ Put debugging information in a hidden div """
-        attributes = {'id': self.debugInfoID}
-        info = [self.debugInfoHideScript(),
-                self.formatTraceback(),
-                self.formatSystemDetails(), ]
-        return self.formatter.section(''.join(info), attributes)
-
-    def debugInfoHideScript(self):
-        """ Hide debug info for javascript enabled browsers """
-        if self.debug:
-            return ''
-        return '''
-<script type="text/javascript">toggleDebugInfo()</script>
-'''
-
-    def formatTraceback(self):
-        return self.formatAllTracebacks(self.formatOneTraceback)
-
-    def formatTextTraceback(self):
-        template = self.textTracebackTemplate()
-        return template % self.formatAllTracebacks(self.formatOneTextTraceback)
-
-    def formatAllTracebacks(self, formatFuction):
-        """ Format multiple tracebacks using formatFunction """
-        tracebacks = []
-        for ttype, tvalue, tb in self.exceptions():
-            if ttype is None:
-                break
-            tracebacks.append(formatFuction((ttype, tvalue, tb)))
-            del tb
-        return ''.join(tracebacks)
-
-    def exceptions(self):
-        """ Return a list of exceptions info, starting at self.info """
-        try:
-            return [self.info] + self.info[1].exceptions()
-        except AttributeError:
-            return [self.info]
-
-    def applicationDetails(self):
-        """ Add MoinMoin details to system details """
-        from MoinMoin import version
-        return ['MoinMoin: Release %s (%s)' % (version.release,
-                                              version.revision)]
-
-    def formatExceptionMessage(self, info):
-        """ Handle multiple paragraphs in exception message """
-        text = cgitb.View.exceptionMessage(self, info)
-        text = text.split('\n\n')
-        text = ''.join([self.formatter.paragraph(item) for item in text])
-        return text
-
-
-def handle(request, err):
-    """ Handle failures
-
-    Display fancy error view, or fallback to simple text traceback
-    """
-    if 'MOIN_DEBUG' in os.environ:
-        raise err
-
-    savedError = sys.exc_info()
-    logging.exception('An exception occurred, URI was "%s".' % request.request_uri)
-
-    try:
-        display = request.cfg.traceback_show # might fail if we have no cfg yet
-    except:
-        # default to True here to allow an admin setting up the wiki
-        # to see the errors made in the configuration file
-        display = True
-
-    try:
-        debug = 'debug' in request.form
-    except:
-        debug = False
-
-    try:
-        # try to output a nice html error page
-        handler = cgitb.Hook(file=request, display=display, viewClass=View, debug=debug)
-        handler.handle(savedError)
-    except:
-        # if that fails, log the cgitb problem ...
-        logging.exception('cgitb raised this exception')
-        # ... and try again with a simpler output method:
-        request.write('<pre>\n')
-        printTextException(request, savedError, display)
-        request.write('\nAdditionally cgitb raised this exception:\n')
-        printTextException(request, display=display)
-        request.write('</pre>\n')
-
-
-
-def printTextException(request, info=None, display=True):
-    """ Simple text exception that should never fail
-
-    Print all exceptions in a composite error.
-    """
-    if not display:
-        request.write("(Traceback display forbidden by configuration)\n")
-        return
-    from MoinMoin import wikiutil
-    if info is None:
-        info = sys.exc_info()
-    try:
-        exceptions = [info] + info[1].exceptions()
-    except AttributeError:
-        exceptions = [info]
-    for info in exceptions:
-        text = ''.join(traceback.format_exception(*info))
-        text = wikiutil.escape(text)
-        request.write(text)
-
--- a/MoinMoin/formatter/__init__.py	Tue Sep 23 00:44:45 2008 +0200
+++ b/MoinMoin/formatter/__init__.py	Tue Sep 23 23:02:50 2008 +0200
@@ -75,12 +75,12 @@
 
     def startContent(self, content_id="content", **kw):
         if self.page:
-            self.request.begin_include(self.page.page_name)
+            self.request.uid_generator.begin(self.page.page_name)
         return ""
 
     def endContent(self):
         if self.page:
-            self.request.end_include()
+            self.request.uid_generator.end()
         return ""
 
     # Links ##############################################################
@@ -94,7 +94,7 @@
             return ''
         if not pagename and page:
             pagename = page.page_name
-        pagename = self.request.normalizePagename(pagename)
+        pagename = wikiutil.normalize_pagename(pagename, self.request.cfg)
         if pagename and pagename not in self.pagelinks:
             self.pagelinks.append(pagename)
 
@@ -411,11 +411,11 @@
         '''
         Take an ID and make it unique in the current namespace.
         '''
-        ns = self.request.include_id
+        ns = self.request.uid_generator.include_id
         if not ns is None:
             ns = self.sanitize_to_id(ns)
         id = self.sanitize_to_id(id)
-        id = self.request.make_unique_id(id, ns)
+        id = self.request.uid_generator(id, ns)
         return id
 
     def qualify_id(self, id):
@@ -425,7 +425,7 @@
         is suitable if the dot ('.') is valid in IDs for your
         formatter.
         '''
-        ns = self.request.include_id
+        ns = self.request.uid_generator.include_id
         if not ns is None:
             ns = self.sanitize_to_id(ns)
             return '%s.%s' % (ns, id)
--- a/MoinMoin/formatter/text_docbook.py	Tue Sep 23 00:44:45 2008 +0200
+++ b/MoinMoin/formatter/text_docbook.py	Tue Sep 23 23:02:50 2008 +0200
@@ -364,7 +364,7 @@
     def url(self, on, url=None, css=None, **kw):
         if url and url.startswith("/"):
             # convert to absolute path:
-            url = "%s%s"%(self.request.getBaseURL(), url)
+            url = "%s%s"%(self.request.base_url, url)
 
         if not on:
             self._cleanupUlinkNode()
--- a/MoinMoin/formatter/text_html.py	Tue Sep 23 00:44:45 2008 +0200
+++ b/MoinMoin/formatter/text_html.py	Tue Sep 23 23:02:50 2008 +0200
@@ -410,7 +410,7 @@
         """
 
         if hasattr(self, 'page'):
-            self.request.begin_include(self.page.page_name)
+            self.request.uid_generator.begin(self.page.page_name)
 
         result = []
         # Use the content language
@@ -434,7 +434,7 @@
         result.append(self.anchordef('bottom'))
         result.append(self._close('div', newline=newline))
         if hasattr(self, 'page'):
-            self.request.end_include()
+            self.request.uid_generator.end()
         return ''.join(result)
 
     def lang(self, on, lang_name):
--- a/MoinMoin/i18n/__init__.py	Tue Sep 23 00:44:45 2008 +0200
+++ b/MoinMoin/i18n/__init__.py	Tue Sep 23 23:02:50 2008 +0200
@@ -358,15 +358,12 @@
     the request, normalizing to lower case.
     """
     fallback = []
-    accepted = request.http_accept_language
+    accepted = request.accept_languages
     if accepted:
-        # Extract the languages names from the string
-        accepted = accepted.split(',')
-        accepted = [lang.split(';')[0] for lang in accepted]
         # Add base language for each sub language. If the user specified
         # a sub language like "en-us", we will try to to provide it or
         # a least the base language "en" in this case.
-        for lang in accepted:
+        for lang, quality in accepted:
             lang = lang.lower()
             fallback.append(lang)
             if '-' in lang:
--- a/MoinMoin/logfile/editlog.py	Tue Sep 23 00:44:45 2008 +0200
+++ b/MoinMoin/logfile/editlog.py	Tue Sep 23 23:02:50 2008 +0200
@@ -163,7 +163,7 @@
         If `host` is None, it's read from request vars.
         """
         if host is None:
-            host = request.remote_addr
+            host = request.remote_addr or ''
 
         if request.cfg.log_reverse_dns_lookups:
             import socket
--- a/MoinMoin/macro/AdvancedSearch.py	Tue Sep 23 00:44:45 2008 +0200
+++ b/MoinMoin/macro/AdvancedSearch.py	Tue Sep 23 23:02:50 2008 +0200
@@ -46,7 +46,7 @@
     @param default: value if not present (default: '')
     @param escaped: if True, escape value so it can be used for html generation (default: False)
     """
-    value = request.form.get(name, [default])[0]
+    value = request.form.get(name, default)
     if escaped:
         value = wikiutil.escape(value, quote=True)
     return value
@@ -166,7 +166,7 @@
 
     # the dialogue
     return f.rawHTML('\n'.join([
-        u'<form method="get" action="%s/%s">' % (macro.request.getScriptname(), wikiutil.quoteWikinameURL(macro.request.formatter.page.page_name)),
+        u'<form method="get" action="%s">' % macro.request.href(macro.request.formatter.page.page_name),
         u'<div>',
         u'<input type="hidden" name="action" value="fullsearch">',
         u'<input type="hidden" name="advancedsearch" value="1">',
--- a/MoinMoin/macro/FullSearch.py	Tue Sep 23 00:44:45 2008 +0200
+++ b/MoinMoin/macro/FullSearch.py	Tue Sep 23 23:02:50 2008 +0200
@@ -43,7 +43,7 @@
     """
     _ = macro._
     if 'value' in macro.form:
-        default = wikiutil.escape(macro.form["value"][0], quote=1)
+        default = wikiutil.escape(macro.form["value"], quote=1)
     else:
         default = ''
 
@@ -67,7 +67,7 @@
     # Format
     type = (type == "titlesearch")
     html = [
-        u'<form method="get" action="%s/%s">' % (macro.request.getScriptname(), wikiutil.quoteWikinameURL(macro.request.formatter.page.page_name)),
+        u'<form method="get" action="%s">' % macro.request.href(macro.request.formatter.page.page_name),
         u'<div>',
         u'<input type="hidden" name="action" value="fullsearch">',
         u'<input type="hidden" name="titlesearch" value="%i">' % type,
--- a/MoinMoin/macro/MonthCalendar.py	Tue Sep 23 00:44:45 2008 +0200
+++ b/MoinMoin/macro/MonthCalendar.py	Tue Sep 23 23:02:50 2008 +0200
@@ -217,7 +217,7 @@
     # does the url have calendar params (= somebody has clicked on prev/next links in calendar) ?
     if 'calparms' in macro.form:
         has_calparms = 1 # yes!
-        text2 = macro.form['calparms'][0]
+        text2 = macro.form['calparms']
         try:
             cparmpagename, cparmyear, cparmmonth, cparmoffset, cparmoffset2, cparmheight6, cparmanniversary, cparmtemplate = \
                 parseargs(request, text2, thispage, currentyear, currentmonth, 0, 0, False, False, u'')
--- a/MoinMoin/macro/NewPage.py	Tue Sep 23 00:44:45 2008 +0200
+++ b/MoinMoin/macro/NewPage.py	Tue Sep 23 23:02:50 2008 +0200
@@ -76,7 +76,7 @@
 
         # TODO: better abstract this using the formatter
         html = [
-            u'<form class="macro" method="POST" action="%s/%s"><div>' % (self.request.getScriptname(), wikiutil.quoteWikinameURL(self.formatter.page.page_name)),
+            u'<form class="macro" method="POST" action="%s"><div>' % self.request.href(self.formatter.page.page_name),
             u'<input type="hidden" name="action" value="newpage">',
             u'<input type="hidden" name="parent" value="%s">' % wikiutil.escape(self.parent, 1),
             u'<input type="hidden" name="template" value="%s">' % wikiutil.escape(self.template, 1),
--- a/MoinMoin/macro/TableOfContents.py	Tue Sep 23 00:44:45 2008 +0200
+++ b/MoinMoin/macro/TableOfContents.py	Tue Sep 23 23:02:50 2008 +0200
@@ -46,12 +46,12 @@
 
     def startContent(self, *args, **kw):
         res = FormatterBase.startContent(self, *args, **kw)
-        self.collected_headings.append([1, self.request.include_id, None])
+        self.collected_headings.append([1, self.request.uid_generator.include_id, None])
         return res
 
     def endContent(self):
         res = FormatterBase.endContent(self)
-        self.collected_headings.append([0, self.request.include_id, None])
+        self.collected_headings.append([0, self.request.uid_generator.include_id, None])
         return res
 
     def heading(self, on, depth, **kw):
@@ -142,7 +142,7 @@
 
     pname = macro.formatter.page.page_name
 
-    macro.request.push_unique_ids()
+    macro.request.uid_generator.push()
 
     macro.request._tocfm_collected_headings = []
     macro.request._tocfm_orig_formatter = macro.formatter
@@ -224,7 +224,7 @@
         result.append(macro.formatter.number_list(0))
         lastlvl -= 1
 
-    macro.request.pop_unique_ids()
+    macro.request.uid_generator.pop()
 
     result.append(macro.formatter.div(0))
     return ''.join(result)
--- a/MoinMoin/macro/TeudView.py	Tue Sep 23 00:44:45 2008 +0200
+++ b/MoinMoin/macro/TeudView.py	Tue Sep 23 23:02:50 2008 +0200
@@ -34,7 +34,7 @@
     pagename = macro.formatter.page.page_name
 
     if 'module' in macro.form:
-        modname = macro.form["module"][0]
+        modname = macro.form["module"]
         try:
             obj = pydoc.locate(modname)
         except pydoc.ErrorDuringImport, value:
--- a/MoinMoin/macro/__init__.py	Tue Sep 23 00:44:45 2008 +0200
+++ b/MoinMoin/macro/__init__.py	Tue Sep 23 23:02:50 2008 +0200
@@ -280,7 +280,7 @@
         """
         _ = self._
         html = [
-            u'<form method="get" action="%s/%s"><div>' % (self.request.getScriptname(), wikiutil.quoteWikinameURL(self.formatter.page.page_name)),
+            u'<form method="get" action="%s"><div>' % self.request.href(self.formatter.page.page_name),
             u'<div>',
             u'<input type="hidden" name="action" value="goto">',
             u'<input type="text" name="target" size="30">',
--- a/MoinMoin/macro/_tests/test_Action.py	Tue Sep 23 00:44:45 2008 +0200
+++ b/MoinMoin/macro/_tests/test_Action.py	Tue Sep 23 23:02:50 2008 +0200
@@ -26,7 +26,7 @@
         m = make_macro(self.request, self.page)
         result = Action.macro_Action(m, 'raw')
         nuke_page(request, self.pagename)
-        expected = '<a href="./AutoCreatedMoinMoinTemporaryTestPageForAction?action=raw">raw</a>'
+        expected = '<a href="/AutoCreatedMoinMoinTemporaryTestPageForAction?action=raw">raw</a>'
         assert result == expected
 
 coverage_modules = ['MoinMoin.macro.Action']
--- a/MoinMoin/macro/_tests/test_EmbedObject.py	Tue Sep 23 00:44:45 2008 +0200
+++ b/MoinMoin/macro/_tests/test_EmbedObject.py	Tue Sep 23 23:02:50 2008 +0200
@@ -54,7 +54,7 @@
         m = make_macro(self.request, self.page)
         filename = 'test.mpg'
         result = m.execute('EmbedObject', u'%s' % filename)
-        assert '<object data="./AutoCreatedMoinMoinTemporaryTestPageForEmbedObject?action=AttachFile&amp;do=get&amp;target=test.mpg"' in result
+        assert '<object data="/AutoCreatedMoinMoinTemporaryTestPageForEmbedObject?action=AttachFile&amp;do=get&amp;target=test.mpg"' in result
         assert 'align="middle"' in result
         assert 'value="transparent"' in result
 
@@ -64,7 +64,7 @@
         filename = 'test.mpg'
         height = '50 %' # also tests that space is allowed in there
         result = m.execute('EmbedObject', u'target=%s, height=%s' % (filename, height))
-        assert '<object data="./AutoCreatedMoinMoinTemporaryTestPageForEmbedObject?action=AttachFile&amp;do=get&amp;target=test.mpg"' in result
+        assert '<object data="/AutoCreatedMoinMoinTemporaryTestPageForEmbedObject?action=AttachFile&amp;do=get&amp;target=test.mpg"' in result
         assert 'height="50%"' in result
         assert 'align="middle"' in result
 
--- a/MoinMoin/macro/_tests/test_StatsChart.py	Tue Sep 23 00:44:45 2008 +0200
+++ b/MoinMoin/macro/_tests/test_StatsChart.py	Tue Sep 23 23:02:50 2008 +0200
@@ -34,19 +34,19 @@
     def testStatsChart_useragents(self):
         """ macro StatsChart useragents test: 'tests useragents' and clean page scope cache """
         result = self._test_macro(u'StatsChart', u'useragents')
-        expected = u'<form action="./AutoCreatedMoinMoinTemporaryTestPageStatsChart" method="GET"'
+        expected = u'<form action="/AutoCreatedMoinMoinTemporaryTestPageStatsChart" method="GET"'
         assert expected in result
 
     def testStatsChart_hitcounts(self):
         """ macro StatsChart hitcounts test: 'tests hitcounts' and clean page scope cache  """
         result = self._test_macro(u'StatsChart', u'hitcounts')
-        expected = u'<form action="./AutoCreatedMoinMoinTemporaryTestPageStatsChart" method="GET"'
+        expected = u'<form action="/AutoCreatedMoinMoinTemporaryTestPageStatsChart" method="GET"'
         assert expected in result
 
     def testStatsChart_languages(self):
         """ macro StatsChart languages test: 'tests languages' and clean page scope cache  """
         result = self._test_macro(u'StatsChart', u'hitcounts')
-        expected = u'<form action="./AutoCreatedMoinMoinTemporaryTestPageStatsChart" method="GET"'
+        expected = u'<form action="/AutoCreatedMoinMoinTemporaryTestPageStatsChart" method="GET"'
         assert expected in result
 
 coverage_modules = ['MoinMoin.stats']
--- a/MoinMoin/mail/mailimport.py	Tue Sep 23 00:44:45 2008 +0200
+++ b/MoinMoin/mail/mailimport.py	Tue Sep 23 23:02:50 2008 +0200
@@ -17,7 +17,6 @@
 from MoinMoin.action.AttachFile import add_attachment, AttachmentAlreadyExists
 from MoinMoin.Page import Page
 from MoinMoin.PageEditor import PageEditor
-from MoinMoin.request.request_cli import Request as RequestCLI
 # python, at least up to 2.4, ships a broken parser for headers
 from MoinMoin.support.HeaderFixed import decode_header
 
@@ -184,7 +183,7 @@
         generate_summary = True
         pagename = pagename[1:].lstrip()
 
-    pagename = request.normalizePagename(pagename)
+    pagename = wikiutil.normalize_pagename(pagename, request.cfg)
 
     if choose_html and msg['html']:
         content = "{{{#!html\n%s\n}}}" % msg['html'].replace("}}}", "} } }")
@@ -313,11 +312,12 @@
 
 if __name__ == "__main__":
     if len(sys.argv) > 1:
-        url = sys.argv[1]
+        request_url = sys.argv[1]
     else:
-        url = 'localhost/'
+        request_url = 'localhost/'
 
-    request = RequestCLI(url=url)
+    from MoinMoin.web.contexts import ScriptContext
+    request = ScriptContext(url=request_url)
 
     try:
         import_mail_from_file(request, infile)
--- a/MoinMoin/packages.py	Tue Sep 23 00:44:45 2008 +0200
+++ b/MoinMoin/packages.py	Tue Sep 23 23:02:50 2008 +0200
@@ -529,9 +529,8 @@
         request_url = "localhost/"
 
     # Setup MoinMoin environment
-    from MoinMoin.request import request_cli
-    request = request_cli.Request(url=request_url)
-    request.form = request.args = request.setup_args()
+    from MoinMoin.web.contexts import ScriptContext
+    request = ScriptContext(url=request_url)
 
     package = ZipPackage(request, packagefile)
     if not package.isPackage():
--- a/MoinMoin/parser/_tests/test_text_creole.py	Tue Sep 23 00:44:45 2008 +0200
+++ b/MoinMoin/parser/_tests/test_text_creole.py	Tue Sep 23 23:02:50 2008 +0200
@@ -369,14 +369,14 @@
     needle = re.compile(text % r'(.+)')
     _tests = [
         # test,           expected
-        ('[[SomeNonExistentPage]]', '<a class="nonexistent" href="./SomeNonExistentPage">SomeNonExistentPage</a>'),
-        ('[[SomeNonExistentPage#anchor]]', '<a class="nonexistent" href="./SomeNonExistentPage#anchor">SomeNonExistentPage#anchor</a>'),
-        ('[[something]]', '<a class="nonexistent" href="./something">something</a>'),
-        ('[[some thing]]', '<a class="nonexistent" href="./some%20thing">some thing</a>'),
-        ('[[something|some text]]', '<a class="nonexistent" href="./something">some text</a>'),
-        ('[[../something]]', '<a class="nonexistent" href="./something">../something</a>'),
-        ('[[/something]]', '<a class="nonexistent" href="./%s/something">/something</a>' % PAGENAME),
-        ('[[something#anchor]]', '<a class="nonexistent" href="./something#anchor">something#anchor</a>'),
+        ('[[SomeNonExistentPage]]', '<a class="nonexistent" href="/SomeNonExistentPage">SomeNonExistentPage</a>'),
+        ('[[SomeNonExistentPage#anchor]]', '<a class="nonexistent" href="/SomeNonExistentPage#anchor">SomeNonExistentPage#anchor</a>'),
+        ('[[something]]', '<a class="nonexistent" href="/something">something</a>'),
+        ('[[some thing]]', '<a class="nonexistent" href="/some%20thing">some thing</a>'),
+        ('[[something|some text]]', '<a class="nonexistent" href="/something">some text</a>'),
+        ('[[../something]]', '<a class="nonexistent" href="/something">../something</a>'),
+        ('[[/something]]', '<a class="nonexistent" href="/%s/something">/something</a>' % PAGENAME),
+        ('[[something#anchor]]', '<a class="nonexistent" href="/something#anchor">something#anchor</a>'),
         ('[[MoinMoin:something]]', '<a class="interwiki" href="http://moinmo.in/something" title="MoinMoin">something</a>'),
         ('[[MoinMoin:something|some text]]', '<a class="interwiki" href="http://moinmo.in/something" title="MoinMoin">some text</a>'),
         ('[[MoinMoin:with space]]', '<a class="interwiki" href="http://moinmo.in/with%20space" title="MoinMoin">with space</a>'),
--- a/MoinMoin/parser/_tests/test_text_moin_wiki.py	Tue Sep 23 00:44:45 2008 +0200
+++ b/MoinMoin/parser/_tests/test_text_moin_wiki.py	Tue Sep 23 23:02:50 2008 +0200
@@ -493,20 +493,20 @@
     needle = re.compile(text % r'(.+)')
     _tests = [
         # test,           expected
-        ('SomeNonExistentPage', '<a class="nonexistent" href="./SomeNonExistentPage">SomeNonExistentPage</a>'),
-        ('SomeNonExistentPage#anchor', '<a class="nonexistent" href="./SomeNonExistentPage#anchor">SomeNonExistentPage#anchor</a>'),
-        ('[[something]]', '<a class="nonexistent" href="./something">something</a>'),
-        ('[[some thing]]', '<a class="nonexistent" href="./some%20thing">some thing</a>'),
-        ('[[something|some text]]', '<a class="nonexistent" href="./something">some text</a>'),
-        ('[[../something]]', '<a class="nonexistent" href="./something">../something</a>'),
-        ('[[/something]]', '<a class="nonexistent" href="./%s/something">/something</a>' % PAGENAME),
-        ('[[something#anchor]]', '<a class="nonexistent" href="./something#anchor">something#anchor</a>'),
+        ('SomeNonExistentPage', '<a class="nonexistent" href="/SomeNonExistentPage">SomeNonExistentPage</a>'),
+        ('SomeNonExistentPage#anchor', '<a class="nonexistent" href="/SomeNonExistentPage#anchor">SomeNonExistentPage#anchor</a>'),
+        ('[[something]]', '<a class="nonexistent" href="/something">something</a>'),
+        ('[[some thing]]', '<a class="nonexistent" href="/some%20thing">some thing</a>'),
+        ('[[something|some text]]', '<a class="nonexistent" href="/something">some text</a>'),
+        ('[[../something]]', '<a class="nonexistent" href="/something">../something</a>'),
+        ('[[/something]]', '<a class="nonexistent" href="/%s/something">/something</a>' % PAGENAME),
+        ('[[something#anchor]]', '<a class="nonexistent" href="/something#anchor">something#anchor</a>'),
         ('MoinMoin:something', '<a class="interwiki" href="http://moinmo.in/something" title="MoinMoin">something</a>'),
         ('[[MoinMoin:something|some text]]', '<a class="interwiki" href="http://moinmo.in/something" title="MoinMoin">some text</a>'),
         ('[[MoinMoin:with space]]', '<a class="interwiki" href="http://moinmo.in/with%20space" title="MoinMoin">with space</a>'),
         ('[[MoinMoin:with space|some text]]', '<a class="interwiki" href="http://moinmo.in/with%20space" title="MoinMoin">some text</a>'),
         # no interwiki:
-        ('[[ABC:n]]', '<a class="nonexistent" href="./ABC%3An">ABC:n</a>'), # finnish/swedish abbreviations / possessive
+        ('[[ABC:n]]', '<a class="nonexistent" href="/ABC%3An">ABC:n</a>'), # finnish/swedish abbreviations / possessive
         ('ABC:n', 'ABC:n'), # finnish/swedish abbreviations / possessive
         ('lowercase:nointerwiki', 'lowercase:nointerwiki'),
         ('[[http://google.com/|google]]', '<a class="http" href="http://google.com/">google</a>'),
--- a/MoinMoin/request/__init__.py	Tue Sep 23 00:44:45 2008 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1683 +0,0 @@
-# -*- coding: iso-8859-1 -*-
-"""
-    MoinMoin - RequestBase Implementation
-
-    @copyright: 2001-2003 Juergen Hermann <jh@web.de>,
-                2003-2008 MoinMoin:ThomasWaldmann
-    @license: GNU GPL, see COPYING for details.
-"""
-
-# Support for remote IP address detection when using (reverse) proxy (or even proxies).
-# If you exactly KNOW which (reverse) proxies you can trust, put them into the list
-# below, so we can determine the "outside" IP as your trusted proxies see it.
-
-proxies_trusted = [] # trust noone!
-#proxies_trusted = ['127.0.0.1', ] # can be a list of multiple IPs
-
-from MoinMoin import log
-logging = log.getLogger(__name__)
-
-def find_remote_addr(addrs):
-    """ Find the last remote IP address before it hits our reverse proxies.
-        The LAST address in the <addrs> list is the remote IP as detected by the server
-        (not taken from some x-forwarded-for header).
-        The FIRST address in the <addrs> list might be the client's IP - if noone cheats
-        and everyone supports x-f-f header.
-
-        See http://bob.pythonmac.org/archives/2005/09/23/apache-x-forwarded-for-caveat/
-
-        For debug loglevel, we log all <addrs>.
-
-        TODO: refactor request code to first do some basic IP init, then load configuration,
-        TODO: then do proxy processing.
-        TODO: add wikiconfig configurability for proxies_trusted
-        TODO: later, make it possible to put multipe remote IP addrs into edit-log
-    """
-    logging.debug("request.find_remote_addr: addrs == %r" % addrs)
-    if proxies_trusted:
-        result = [addr for addr in addrs if addr not in proxies_trusted]
-        if result:
-            return result[-1] # last IP before it hit our trusted (reverse) proxies
-    return addrs[-1] # this is a safe remote_addr, not taken from x-f-f header
-
-
-import os, re, time, sys, cgi, StringIO
-import Cookie
-import traceback
-
-from MoinMoin.Page import Page
-from MoinMoin import config, wikiutil, user, caching, error
-from MoinMoin.config import multiconfig
-from MoinMoin.support.python_compatibility import set
-from MoinMoin.util import IsWin9x
-from MoinMoin.util.clock import Clock
-from MoinMoin import auth
-from urllib import quote, quote_plus
-
-# umask setting --------------------------------------------------------
-def set_umask(new_mask=0777^config.umask):
-    """ Set the OS umask value (and ignore potential failures on OSes where
-        this is not supported).
-        Default: the bitwise inverted value of config.umask
-    """
-    try:
-        old_mask = os.umask(new_mask)
-    except:
-        # maybe we are on win32?
-        pass
-
-# We do this at least once per Python process, when request is imported.
-# If other software parts (like twistd's daemonize() function) set an
-# unwanted umask, we have to call this again to set the correct one:
-set_umask()
-
-# Exceptions -----------------------------------------------------------
-
-class MoinMoinFinish(Exception):
-    """ Raised to jump directly to end of run() function, where finish is called """
-
-
-class HeadersAlreadySentException(Exception):
-    """ Is raised if the headers were already sent when emit_http_headers is called."""
-
-
-class RemoteClosedConnection(Exception):
-    """ Remote end closed connection during request """
-
-# Utilities
-
-def cgiMetaVariable(header, scheme='http'):
-    """ Return CGI meta variable for header name
-
-    e.g 'User-Agent' -> 'HTTP_USER_AGENT'
-    See http://www.faqs.org/rfcs/rfc3875.html section 4.1.18
-    """
-    var = '%s_%s' % (scheme, header)
-    return var.upper().replace('-', '_')
-
-
-# Request Base ----------------------------------------------------------
-
-class RequestBase(object):
-    """ A collection for all data associated with ONE request. """
-
-    # Defaults (used by sub classes)
-    http_accept_language = 'en'
-    server_name = 'localhost'
-    server_port = '80'
-
-    # Extra headers we support. Both standalone and twisted store
-    # headers as lowercase.
-    moin_location = 'x-moin-location'
-    proxy_host = 'x-forwarded-host' # original host: header as seen by the proxy (e.g. wiki.example.org)
-    proxy_xff = 'x-forwarded-for' # list of original remote_addrs as seen by the proxies (e.g. <clientip>,<proxy1>,<proxy2>,...)
-
-    def __init__(self, properties={}, given_config=None):
-
-        # twistd's daemonize() overrides our umask, so we reset it here every
-        # request. we do it for all request types to avoid similar problems.
-        set_umask()
-
-        self._finishers = []
-
-        self._auth_redirected = False
-
-        # Decode values collected by sub classes
-        self.path_info = self.decodePagename(self.path_info)
-
-        self.failed = 0
-        self._available_actions = None
-        self._known_actions = None
-
-        # Pages meta data that we collect in one request
-        self.pages = {}
-
-        self.sent_headers = None
-        self.user_headers = []
-        self.cacheable = 0 # may this output get cached by http proxies/caches?
-        self.http_caching_disabled = 0 # see disableHttpCaching()
-        self.page = None
-        self._dicts = None
-
-        # session handling. users cannot rely on a session being
-        # created, but we should always set request.session
-        self.session = {}
-
-        # setuid handling requires an attribute in the request
-        # that stores the real user
-        self._setuid_real_user = None
-
-        # Check for dumb proxy requests
-        # TODO relying on request_uri will not work on all servers, especially
-        # not on external non-Apache servers
-        self.forbidden = False
-        if self.request_uri.startswith('http://'):
-            self.makeForbidden403()
-
-        # Init
-        else:
-            self.writestack = []
-            self.clock = Clock()
-            self.clock.start('total')
-            self.clock.start('base__init__')
-            # order is important here!
-            self.__dict__.update(properties)
-            try:
-                self._load_multi_cfg(given_config)
-            except error.NoConfigMatchedError:
-                self.makeForbidden(404, 'No wiki configuration matching the URL found!\r\n')
-                return
-
-            self.isSpiderAgent = self.check_spider()
-
-            # Set decode charsets.  Input from the user is always in
-            # config.charset, which is the page charsets. Except
-            # path_info, which may use utf-8, and handled by decodePagename.
-            self.decode_charsets = [config.charset]
-
-            if self.query_string.startswith('action=xmlrpc'):
-                self.args = {}
-                self.form = {}
-                self.action = 'xmlrpc'
-                self.rev = None
-            else:
-                try:
-                    self.args = self.form = self.setup_args()
-                except UnicodeError:
-                    self.makeForbidden(403, "The input you sent could not be understood.")
-                    return
-                self.action = self.form.get('action', ['show'])[0]
-                try:
-                    self.rev = int(self.form['rev'][0])
-                except:
-                    self.rev = None
-
-            from MoinMoin.Page import RootPage
-            self.rootpage = RootPage(self)
-
-            from MoinMoin.logfile import editlog
-            self.editlog = editlog.EditLog(self)
-
-            from MoinMoin import i18n
-            self.i18n = i18n
-            i18n.i18n_init(self)
-
-            # authentication might require translated forms, so
-            # have a try at guessing the language from the browser
-            lang = i18n.requestLanguage(self, try_user=False)
-            self.getText = lambda text, i18n=self.i18n, request=self, lang=lang, **kw: i18n.getText(text, request, lang, **kw)
-
-            # session handler start, auth
-            self.parse_cookie()
-            user_obj = self.cfg.session_handler.start(self, self.cfg.session_id_handler)
-            shfinisher = lambda request: self.cfg.session_handler.finish(request, request.user,
-                                                                         self.cfg.session_id_handler)
-            self.add_finisher(shfinisher)
-            # set self.user even if _handle_auth_form raises an Exception
-            self.user = None
-            self.user = self._handle_auth_form(user_obj)
-            del user_obj
-            self.cfg.session_handler.after_auth(self, self.cfg.session_id_handler, self.user)
-            if not self.user:
-                self.user = user.User(self, auth_method='request:invalid')
-
-            # setuid handling, check isSuperUser() because the user
-            # might have lost the permission between requests
-            if 'setuid' in self.session and self.user.isSuperUser():
-                self._setuid_real_user = self.user
-                uid = self.session['setuid']
-                self.user = user.User(self, uid, auth_method='setuid')
-                # set valid to True so superusers can even switch
-                # to disable accounts
-                self.user.valid = True
-
-            if self.action != 'xmlrpc':
-                if not self.forbidden and self.isForbidden():
-                    self.makeForbidden403()
-                if not self.forbidden and self.surge_protect():
-                    self.makeUnavailable503()
-
-            self.pragma = {}
-            self.mode_getpagelinks = 0 # is > 0 as long as we are in a getPageLinks call
-            self.parsePageLinks_running = {} # avoid infinite recursion by remembering what we are already running
-
-            self.lang = i18n.requestLanguage(self)
-            # Language for content. Page content should use the wiki default lang,
-            # but generated content like search results should use the user language.
-            self.content_lang = self.cfg.language_default
-            self.getText = lambda text, i18n=self.i18n, request=self, lang=self.lang, **kv: i18n.getText(text, request, lang, **kv)
-
-            self.reset()
-
-            from MoinMoin.formatter.text_html import Formatter
-            self.html_formatter = Formatter(self)
-            self.formatter = self.html_formatter
-
-            self.clock.stop('base__init__')
-
-    def surge_protect(self, kick_him=False):
-        """ check if someone requesting too much from us,
-            if kick_him is True, we unconditionally blacklist the current user/ip
-        """
-        limits = self.cfg.surge_action_limits
-        if not limits:
-            return False
-
-        if self.remote_addr.startswith('127.'): # localnet
-            return False
-
-        validuser = self.user.valid
-        current_id = validuser and self.user.name or self.remote_addr
-        current_action = self.action
-
-        default_limit = limits.get('default', (30, 60))
-
-        now = int(time.time())
-        surgedict = {}
-        surge_detected = False
-
-        try:
-            # if we have common farm users, we could also use scope='farm':
-            cache = caching.CacheEntry(self, 'surgeprotect', 'surge-log', scope='wiki', use_encode=True)
-            if cache.exists():
-                data = cache.content()
-                data = data.split("\n")
-                for line in data:
-                    try:
-                        id, t, action, surge_indicator = line.split("\t")
-                        t = int(t)
-                        maxnum, dt = limits.get(action, default_limit)
-                        if t >= now - dt:
-                            events = surgedict.setdefault(id, {})
-                            timestamps = events.setdefault(action, [])
-                            timestamps.append((t, surge_indicator))
-                    except StandardError:
-                        pass
-
-            maxnum, dt = limits.get(current_action, default_limit)
-            events = surgedict.setdefault(current_id, {})
-            timestamps = events.setdefault(current_action, [])
-            surge_detected = len(timestamps) > maxnum
-
-            surge_indicator = surge_detected and "!" or ""
-            timestamps.append((now, surge_indicator))
-            if surge_detected:
-                if len(timestamps) < maxnum * 2:
-                    timestamps.append((now + self.cfg.surge_lockout_time, surge_indicator)) # continue like that and get locked out
-
-            if current_action not in ('cache', 'AttachFile', ): # don't add cache/AttachFile accesses to all or picture galleries will trigger SP
-                current_action = 'all' # put a total limit on user's requests
-                maxnum, dt = limits.get(current_action, default_limit)
-                events = surgedict.setdefault(current_id, {})
-                timestamps = events.setdefault(current_action, [])
-
-                if kick_him: # ban this guy, NOW
-                    timestamps.extend([(now + self.cfg.surge_lockout_time, "!")] * (2 * maxnum))
-
-                surge_detected = surge_detected or len(timestamps) > maxnum
-
-                surge_indicator = surge_detected and "!" or ""
-                timestamps.append((now, surge_indicator))
-                if surge_detected:
-                    if len(timestamps) < maxnum * 2:
-                        timestamps.append((now + self.cfg.surge_lockout_time, surge_indicator)) # continue like that and get locked out
-
-            data = []
-            for id, events in surgedict.items():
-                for action, timestamps in events.items():
-                    for t, surge_indicator in timestamps:
-                        data.append("%s\t%d\t%s\t%s" % (id, t, action, surge_indicator))
-            data = "\n".join(data)
-            cache.update(data)
-        except StandardError:
-            pass
-
-        if surge_detected and validuser and self.user.auth_method in self.cfg.auth_methods_trusted:
-            logging.info("Trusted user %s would have triggered surge protection if not trusted." % self.user.name)
-            return False  # do not subject trusted users to surge protection
-
-        return surge_detected
-
-    def getDicts(self):
-        """ Lazy initialize the dicts on the first access """
-        if self._dicts is None:
-            from MoinMoin import wikidicts
-            dicts = wikidicts.GroupDict(self)
-            dicts.load_dicts()
-            self._dicts = dicts
-        return self._dicts
-
-    def delDicts(self):
-        """ Delete the dicts, used by some tests """
-        del self._dicts
-        self._dicts = None
-
-    dicts = property(getDicts, None, delDicts)
-
-    def _load_multi_cfg(self, given_config=None):
-        # protect against calling multiple times
-        if not hasattr(self, 'cfg'):
-            if given_config is None:
-                self.clock.start('load_multi_cfg')
-                self.cfg = multiconfig.getConfig(self.url)
-                self.clock.stop('load_multi_cfg')
-            else:
-                self.cfg = given_config('MoinMoin._tests.wikiconfig') # used for tests' TestConfig
-
-    def setAcceptedCharsets(self, accept_charset):
-        """ Set accepted_charsets by parsing accept-charset header
-
-        Set self.accepted_charsets to an ordered list based on http_accept_charset.
-
-        Reference: http://www.w3.org/Protocols/rfc2616/rfc2616.txt
-
-        TODO: currently no code use this value.
-
-        @param accept_charset: accept-charset header
-        """
-        charsets = []
-        if accept_charset:
-            accept_charset = accept_charset.lower()
-            # Add iso-8859-1 if needed
-            if (not '*' in accept_charset and
-                'iso-8859-1' not in accept_charset):
-                accept_charset += ',iso-8859-1'
-
-            # Make a list, sorted by quality value, using Schwartzian Transform
-            # Create list of tuples (value, name) , sort, extract names
-            for item in accept_charset.split(','):
-                if ';' in item:
-                    name, qval = item.split(';')
-                    qval = 1.0 - float(qval.split('=')[1])
-                else:
-                    name, qval = item, 0
-                charsets.append((qval, name))
-            charsets.sort()
-            # Remove *, its not clear what we should do with it later
-            charsets = [name for qval, name in charsets if name != '*']
-
-        self.accepted_charsets = charsets
-
-    def _setup_vars_from_std_env(self, env):
-        """ Set common request variables from CGI environment
-
-        Parse a standard CGI environment as created by common web servers.
-        Reference: http://www.faqs.org/rfcs/rfc3875.html
-
-        @param env: dict like object containing cgi meta variables
-        """
-        # Values we can just copy
-        self.env = env
-        self.http_accept_language = env.get('HTTP_ACCEPT_LANGUAGE', self.http_accept_language)
-        self.server_name = env.get('SERVER_NAME', self.server_name)
-        self.server_port = env.get('SERVER_PORT', self.server_port)
-        self.saved_cookie = env.get('HTTP_COOKIE', '')
-        self.script_name = env.get('SCRIPT_NAME', '')
-        self.path_info = env.get('PATH_INFO', '')
-        self.query_string = env.get('QUERY_STRING', '')
-        self.request_method = env.get('REQUEST_METHOD', None)
-        self.remote_addr = env.get('REMOTE_ADDR', '')
-        self.http_user_agent = env.get('HTTP_USER_AGENT', '')
-        try:
-            self.content_length = int(env.get('CONTENT_LENGTH'))
-        except (TypeError, ValueError):
-            self.content_length = None
-        self.if_modified_since = env.get('If-modified-since') or env.get(cgiMetaVariable('If-modified-since'))
-        self.if_none_match = env.get('If-none-match') or env.get(cgiMetaVariable('If-none-match'))
-
-        # REQUEST_URI is not part of CGI spec, but an addition of Apache.
-        self.request_uri = env.get('REQUEST_URI', '')
-
-        # Values that need more work
-        self.setHttpReferer(env.get('HTTP_REFERER'))
-        self.setIsSSL(env)
-        self.setHost(env.get('HTTP_HOST'))
-        self.fixURI(env)
-
-        self.setURL(env)
-        #self.debugEnvironment(env)
-
-    def setHttpReferer(self, referer):
-        """ Set http_referer, making sure its ascii
-
-        IE might send non-ascii value.
-        """
-        value = ''
-        if referer:
-            value = unicode(referer, 'ascii', 'replace')
-            value = value.encode('ascii', 'replace')
-        self.http_referer = value
-
-    def setIsSSL(self, env):
-        """ Set is_ssl
-
-        @param env: dict like object containing cgi meta variables
-        """
-        self.is_ssl = bool(env.get('SSL_PROTOCOL') or
-                           env.get('SSL_PROTOCOL_VERSION') or
-                           env.get('HTTPS') == 'on')
-
-    def setHost(self, host=None):
-        """ Set http_host
-
-        Create from server name and port if missing. Previous code
-        default to localhost.
-        """
-        if not host:
-            port = ''
-            standardPort = ('80', '443')[self.is_ssl]
-            if self.server_port != standardPort:
-                port = ':' + self.server_port
-            host = self.server_name + port
-        self.http_host = host
-
-    def fixURI(self, env):
-        """ Fix problems with script_name and path_info
-
-        Handle the strange charset semantics on Windows and other non
-        posix systems. path_info is transformed into the system code
-        page by the web server. Additionally, paths containing dots let
-        most webservers choke.
-
-        Broken environment variables in different environments:
-                path_info script_name
-        Apache1     X          X      PI does not contain dots
-        Apache2     X          X      PI is not encoded correctly
-        IIS         X          X      path_info include script_name
-        Other       ?          -      ? := Possible and even RFC-compatible.
-                                      - := Hopefully not.
-
-        @param env: dict like object containing cgi meta variables
-        """
-        # Fix the script_name when using Apache on Windows.
-        server_software = env.get('SERVER_SOFTWARE', '')
-        if os.name == 'nt' and 'Apache/' in server_software:
-            # Removes elements ending in '.' from the path.
-            self.script_name = '/'.join([x for x in self.script_name.split('/')
-                                         if not x.endswith('.')])
-
-        # Fix path_info
-        if os.name != 'posix' and self.request_uri != '':
-            # Try to recreate path_info from request_uri.
-            import urlparse
-            scriptAndPath = urlparse.urlparse(self.request_uri)[2]
-            path = scriptAndPath.replace(self.script_name, '', 1)
-            self.path_info = wikiutil.url_unquote(path, want_unicode=False)
-        elif os.name == 'nt':
-            # Recode path_info to utf-8
-            path = wikiutil.decodeWindowsPath(self.path_info)
-            self.path_info = path.encode("utf-8")
-
-            # Fix bug in IIS/4.0 when path_info contain script_name
-            if self.path_info.startswith(self.script_name):
-                self.path_info = self.path_info[len(self.script_name):]
-
-    def setURL(self, env):
-        """ Set url, used to locate wiki config
-
-        This is the place to manipulate url parts as needed.
-
-        @param env: dict like object containing cgi meta variables or http headers.
-        """
-        # proxy support
-        self.rewriteRemoteAddr(env)
-        self.rewriteHost(env)
-
-        self.rewriteURI(env)
-
-        if not self.request_uri:
-            self.request_uri = self.makeURI()
-        self.url = self.http_host + self.request_uri
-
-    def rewriteHost(self, env):
-        """ Rewrite http_host transparently
-
-        Get the proxy host using 'X-Forwarded-Host' header, added by
-        Apache 2 and other proxy software.
-
-        TODO: Will not work for Apache 1 or others that don't add this header.
-
-        TODO: If we want to add an option to disable this feature it
-        should be in the server script, because the config is not
-        loaded at this point, and must be loaded after url is set.
-
-        @param env: dict like object containing cgi meta variables or http headers.
-        """
-        proxy_host = (env.get(self.proxy_host) or
-                      env.get(cgiMetaVariable(self.proxy_host)))
-        if proxy_host:
-            self.http_host = proxy_host
-
-    def rewriteRemoteAddr(self, env):
-        """ Rewrite remote_addr transparently
-
-        Get the proxy remote addr using 'X-Forwarded-For' header, added by
-        Apache 2 and other proxy software.
-
-        TODO: Will not work for Apache 1 or others that don't add this header.
-
-        TODO: If we want to add an option to disable this feature it
-        should be in the server script, because the config is not
-        loaded at this point, and must be loaded after url is set.
-
-        @param env: dict like object containing cgi meta variables or http headers.
-        """
-        xff = (env.get(self.proxy_xff) or
-               env.get(cgiMetaVariable(self.proxy_xff)))
-        if xff:
-            xff = [addr.strip() for addr in xff.split(',')]
-            xff.append(self.remote_addr)
-            self.remote_addr = find_remote_addr(xff)
-
-    def rewriteURI(self, env):
-        """ Rewrite request_uri, script_name and path_info transparently
-
-        Useful when running mod python or when running behind a proxy,
-        e.g run on localhost:8000/ and serve as example.com/wiki/.
-
-        Uses private 'X-Moin-Location' header to set the script name.
-        This allow setting the script name when using Apache 2
-        <location> directive::
-
-            <Location /my/wiki/>
-                RequestHeader set X-Moin-Location /my/wiki/
-            </location>
-
-        TODO: does not work for Apache 1 and others that do not allow
-        setting custom headers per request.
-
-        @param env: dict like object containing cgi meta variables or http headers.
-        """
-        location = (env.get(self.moin_location) or
-                    env.get(cgiMetaVariable(self.moin_location)))
-        if location is None:
-            return
-
-        scriptAndPath = self.script_name + self.path_info
-        location = location.rstrip('/')
-        self.script_name = location
-
-        # This may happen when using mod_python
-        if scriptAndPath.startswith(location):
-            self.path_info = scriptAndPath[len(location):]
-
-        # Recreate the URI from the modified parts
-        if self.request_uri:
-            self.request_uri = self.makeURI()
-
-    def makeURI(self):
-        """ Return uri created from uri parts """
-        uri = self.script_name + wikiutil.url_quote(self.path_info)
-        if self.query_string:
-            uri += '?' + self.query_string
-        return uri
-
-    def splitURI(self, uri):
-        """ Return path and query splited from uri
-
-        Just like CGI environment, the path is unquoted, the query is not.
-        """
-        if '?' in uri:
-            path, query = uri.split('?', 1)
-        else:
-            path, query = uri, ''
-        return wikiutil.url_unquote(path, want_unicode=False), query
-
-    def _handle_auth_form(self, user_obj):
-        username = self.form.get('name', [None])[0]
-        password = self.form.get('password', [None])[0]
-        oid = self.form.get('openid_identifier', [None])[0]
-        login = 'login' in self.form
-        logout = 'logout' in self.form
-        stage = self.form.get('stage', [None])[0]
-        return self.handle_auth(user_obj, attended=True, username=username,
-                                password=password, login=login, logout=logout,
-                                stage=stage, openid_identifier=oid)
-
-    def handle_auth(self, user_obj, attended=False, **kw):
-        username = kw.get('username')
-        password = kw.get('password')
-        oid = kw.get('openid_identifier')
-        login = kw.get('login')
-        logout = kw.get('logout')
-        stage = kw.get('stage')
-        extra = {
-            'cookie': self.cookie,
-        }
-        if login:
-            extra['attended'] = attended
-            extra['username'] = username
-            extra['password'] = password
-            extra['openid_identifier'] = oid
-            if stage:
-                extra['multistage'] = True
-        login_msgs = []
-        self._login_multistage = None
-
-        if logout and 'setuid' in self.session:
-            del self.session['setuid']
-            return user_obj
-
-        for authmethod in self.cfg.auth:
-            if logout:
-                user_obj, cont = authmethod.logout(self, user_obj, **extra)
-            elif login:
-                if stage and authmethod.name != stage:
-                    continue
-                ret = authmethod.login(self, user_obj, **extra)
-                user_obj = ret.user_obj
-                cont = ret.continue_flag
-                if stage:
-                    stage = None
-                    del extra['multistage']
-                if ret.multistage:
-                    self._login_multistage = ret.multistage
-                    self._login_multistage_name = authmethod.name
-                    return user_obj
-                if ret.redirect_to:
-                    nextstage = auth.get_multistage_continuation_url(self, authmethod.name)
-                    url = ret.redirect_to
-                    url = url.replace('%return_form', quote_plus(nextstage))
-                    url = url.replace('%return', quote(nextstage))
-                    self._auth_redirected = True
-                    self.http_redirect(url)
-                    return user_obj
-                msg = ret.message
-                if msg and not msg in login_msgs:
-                    login_msgs.append(msg)
-            else:
-                user_obj, cont = authmethod.request(self, user_obj, **extra)
-            if not cont:
-                break
-
-        self._login_messages = login_msgs
-        return user_obj
-
-    def handle_jid_auth(self, jid):
-        return user.get_by_jabber_id(self, jid)
-
-    def parse_cookie(self):
-        try:
-            self.cookie = Cookie.SimpleCookie(self.saved_cookie)
-        except Cookie.CookieError:
-            self.cookie = None
-
-    def reset(self):
-        """ Reset request state.
-
-        Called after saving a page, before serving the updated
-        page. Solves some practical problems with request state
-        modified during saving.
-
-        """
-        # This is the content language and has nothing to do with
-        # The user interface language. The content language can change
-        # during the rendering of a page by lang macros
-        self.current_lang = self.cfg.language_default
-
-        # caches unique ids
-        self.init_unique_ids()
-
-        if hasattr(self, "_fmt_hd_counters"):
-            del self._fmt_hd_counters
-
-    def loadTheme(self, theme_name):
-        """ Load the Theme to use for this request.
-
-        @param theme_name: the name of the theme
-        @type theme_name: str
-        @rtype: int
-        @return: success code
-                 0 on success
-                 1 if user theme could not be loaded,
-                 2 if a hard fallback to modern theme was required.
-        """
-        fallback = 0
-        if theme_name == "<default>":
-            theme_name = self.cfg.theme_default
-
-        try:
-            Theme = wikiutil.importPlugin(self.cfg, 'theme', theme_name, 'Theme')
-        except wikiutil.PluginMissingError:
-            fallback = 1
-            try:
-                Theme = wikiutil.importPlugin(self.cfg, 'theme', self.cfg.theme_default, 'Theme')
-            except wikiutil.PluginMissingError:
-                fallback = 2
-                from MoinMoin.theme.modern import Theme
-
-        self.theme = Theme(self)
-        return fallback
-
-    def setContentLanguage(self, lang):
-        """ Set the content language, used for the content div
-
-        Actions that generate content in the user language, like search,
-        should set the content direction to the user language before they
-        call send_title!
-        """
-        self.content_lang = lang
-        self.current_lang = lang
-
-    def getPragma(self, key, defval=None):
-        """ Query a pragma value (#pragma processing instruction)
-
-            Keys are not case-sensitive.
-        """
-        return self.pragma.get(key.lower(), defval)
-
-    def setPragma(self, key, value):
-        """ Set a pragma value (#pragma processing instruction)
-
-            Keys are not case-sensitive.
-        """
-        self.pragma[key.lower()] = value
-
-    def getPathinfo(self):
-        """ Return the remaining part of the URL. """
-        return self.path_info
-
-    def getScriptname(self):
-        """ Return the scriptname part of the URL ('/path/to/my.cgi'). """
-        if self.script_name == '/':
-            return ''
-        return self.script_name
-
-    def getKnownActions(self):
-        """ Create a dict of avaiable actions
-
-        Return cached version if avaiable.
-
-        @rtype: dict
-        @return: dict of all known actions
-        """
-        try:
-            self.cfg.cache.known_actions # check
-        except AttributeError:
-            from MoinMoin import action
-            self.cfg.cache.known_actions = set(action.getNames(self.cfg))
-
-        # Return a copy, so clients will not change the set.
-        return self.cfg.cache.known_actions.copy()
-
-    def getAvailableActions(self, page):
-        """ Get list of avaiable actions for this request
-
-        The dict does not contain actions that starts with lower case.
-        Themes use this dict to display the actions to the user.
-
-        @param page: current page, Page object
-        @rtype: dict
-        @return: dict of avaiable actions
-        """
-        if self._available_actions is None:
-            # some actions might make sense for non-existing pages, so we just
-            # require read access here. Can be later refined to some action
-            # specific check:
-            if not self.user.may.read(page.page_name):
-                return []
-
-            # Filter non ui actions (starts with lower case letter)
-            actions = self.getKnownActions()
-            actions = [action for action in actions if not action[0].islower()]
-
-            # Filter wiki excluded actions
-            actions = [action for action in actions if not action in self.cfg.actions_excluded]
-
-            # Filter actions by page type, acl and user state
-            excluded = []
-            if ((page.isUnderlayPage() and not page.isStandardPage()) or
-                not self.user.may.write(page.page_name) or
-                not self.user.may.delete(page.page_name)):
-                # Prevent modification of underlay only pages, or pages
-                # the user can't write and can't delete
-                excluded = [u'RenamePage', u'DeletePage', ] # AttachFile must NOT be here!
-            actions = [action for action in actions if not action in excluded]
-
-            self._available_actions = set(actions)
-
-        # Return a copy, so clients will not change the dict.
-        return self._available_actions.copy()
-
-    def redirectedOutput(self, function, *args, **kw):
-        """ Redirect output during function, return redirected output """
-        buf = StringIO.StringIO()
-        self.redirect(buf)
-        try:
-            function(*args, **kw)
-        finally:
-            self.redirect()
-        text = buf.getvalue()
-        buf.close()
-        return text
-
-    def redirect(self, file=None):
-        """ Redirect output to file, or restore saved output """
-        if file:
-            self.writestack.append(self.write)
-            self.write = file.write
-        else:
-            self.write = self.writestack.pop()
-
-    def log(self, msg):
-        """ DEPRECATED - Log msg to logging framework
-            Please call logging.info(...) directly!
-        """
-        msg = msg.strip()
-        # Encode unicode msg
-        if isinstance(msg, unicode):
-            msg = msg.encode(config.charset)
-        logging.info(msg)
-
-    def timing_log(self, start, action):
-        """ Log to timing log (for performance analysis) """
-        indicator = ''
-        if start:
-            total = "vvv"
-        else:
-            self.clock.stop('total') # make sure it is stopped
-            total_secs = self.clock.timings['total']
-            # we add some stuff that is easy to grep when searching for peformance problems:
-            if total_secs > 50:
-                indicator += '!4!'
-            elif total_secs > 20:
-                indicator += '!3!'
-            elif total_secs > 10:
-                indicator += '!2!'
-            elif total_secs > 2:
-                indicator += '!1!'
-            total = self.clock.value('total')
-            # use + for existing pages, - for non-existing pages
-            if self.page is not None:
-                indicator += self.page.exists() and '+' or '-'
-            if self.isSpiderAgent:
-                indicator += "B"
-
-        pid = os.getpid()
-        msg = 'Timing %5d %-6s %4s %-10s %s\n' % (pid, total, indicator, action, self.url)
-        logging.info(msg)
-
-    def send_file(self, fileobj, bufsize=8192, do_flush=False):
-        """ Send a file to the output stream.
-
-        @param fileobj: a file-like object (supporting read, close)
-        @param bufsize: size of chunks to read/write
-        @param do_flush: call flush after writing?
-        """
-        while True:
-            buf = fileobj.read(bufsize)
-            if not buf:
-                break
-            self.write(buf)
-            if do_flush:
-                self.flush()
-
-    def write(self, *data):
-        """ Write to output stream. """
-        raise NotImplementedError
-
-    def encode(self, data):
-        """ encode data (can be both unicode strings and strings),
-            preparing for a single write()
-        """
-        wd = []
-        for d in data:
-            try:
-                if isinstance(d, unicode):
-                    # if we are REALLY sure, we can use "strict"
-                    d = d.encode(config.charset, 'replace')
-                elif d is None:
-                    continue
-                wd.append(d)
-            except UnicodeError:
-                logging.error("Unicode error on: %s" % repr(d))
-        return ''.join(wd)
-
-    def decodePagename(self, name):
-        """ Decode path, possibly using non ascii characters
-
-        Does not change the name, only decode to Unicode.
-
-        First split the path to pages, then decode each one. This enables
-        us to decode one page using config.charset and another using
-        utf-8. This situation happens when you try to add to a name of
-        an existing page.
-
-        See http://www.w3.org/TR/REC-html40/appendix/notes.html#h-B.2.1
-
-        @param name: page name, string
-        @rtype: unicode
-        @return decoded page name
-        """
-        # Split to pages and decode each one
-        pages = name.split('/')
-        decoded = []
-        for page in pages:
-            # Recode from utf-8 into config charset. If the path
-            # contains user typed parts, they are encoded using 'utf-8'.
-            if config.charset != 'utf-8':
-                try:
-                    page = unicode(page, 'utf-8', 'strict')
-                    # Fit data into config.charset, replacing what won't
-                    # fit. Better have few "?" in the name than crash.
-                    page = page.encode(config.charset, 'replace')
-                except UnicodeError:
-                    pass
-
-            # Decode from config.charset, replacing what can't be decoded.
-            page = unicode(page, config.charset, 'replace')
-            decoded.append(page)
-
-        # Assemble decoded parts
-        name = u'/'.join(decoded)
-        return name
-
-    def normalizePagename(self, name):
-        """ Normalize page name
-
-        Prevent creating page names with invisible characters or funny
-        whitespace that might confuse the users or abuse the wiki, or
-        just does not make sense.
-
-        Restrict even more group pages, so they can be used inside acl lines.
-
-        @param name: page name, unicode
-        @rtype: unicode
-        @return: decoded and sanitized page name
-        """
-        # Strip invalid characters
-        name = config.page_invalid_chars_regex.sub(u'', name)
-
-        # Split to pages and normalize each one
-        pages = name.split(u'/')
-        normalized = []
-        for page in pages:
-            # Ignore empty or whitespace only pages
-            if not page or page.isspace():
-                continue
-
-            # Cleanup group pages.
-            # Strip non alpha numeric characters, keep white space
-            if wikiutil.isGroupPage(self, page):
-                page = u''.join([c for c in page
-                                 if c.isalnum() or c.isspace()])
-
-            # Normalize white space. Each name can contain multiple
-            # words separated with only one space. Split handle all
-            # 30 unicode spaces (isspace() == True)
-            page = u' '.join(page.split())
-
-            normalized.append(page)
-
-        # Assemble components into full pagename
-        name = u'/'.join(normalized)
-        return name
-
-    def read(self, n):
-        """ Read n bytes from input stream. """
-        raise NotImplementedError
-
-    def flush(self):
-        """ Flush output stream. """
-        pass
-
-    def check_spider(self):
-        """ check if the user agent for current request is a spider/bot """
-        isSpider = False
-        ua = self.getUserAgent()
-        if ua and self.cfg.cache.ua_spiders:
-            isSpider = self.cfg.cache.ua_spiders.search(ua) is not None
-        return isSpider
-
-    def isForbidden(self):
-        """ check for web spiders and refuse anything except viewing """
-        forbidden = 0
-        # we do not have a parsed query string here, so we can just do simple matching
-        qs = self.query_string
-        action = self.action
-        if ((qs != '' or self.request_method != 'GET') and
-            action != 'rss_rc' and
-            # allow spiders to get attachments and do 'show'
-            not (action == 'AttachFile' and 'do=get' in qs) and
-            action != 'show' and
-            action != 'sitemap'
-            ):
-            forbidden = self.isSpiderAgent
-
-        if not forbidden and self.cfg.hosts_deny:
-            ip = self.remote_addr
-            for host in self.cfg.hosts_deny:
-                if host[-1] == '.' and ip.startswith(host):
-                    forbidden = 1
-                    logging.debug("hosts_deny (net): %s" % str(forbidden))
-                    break
-                if ip == host:
-                    forbidden = 1
-                    logging.debug("hosts_deny (ip): %s" % str(forbidden))
-                    break
-        return forbidden
-
-    def setup_args(self):
-        """ Return args dict
-        First, we parse the query string (usually this is used in GET methods,
-        but TwikiDraw uses ?action=AttachFile&do=savedrawing plus posted stuff).
-        Second, we update what we got in first step by the stuff we get from
-        the form (or by a POST). We invoke _setup_args_from_cgi_form to handle
-        possible file uploads.
-        """
-        args = cgi.parse_qs(self.query_string, keep_blank_values=1)
-        args = self.decodeArgs(args)
-        # if we have form data (in a POST), those override the stuff we already have:
-        if self.request_method == 'POST':
-            postargs = self._setup_args_from_cgi_form()
-            args.update(postargs)
-        return args
-
-    def _setup_args_from_cgi_form(self, form=None):
-        """ Return args dict from a FieldStorage
-
-        Create the args from a given form. Each key contain a list of values.
-        This method usually gets overridden in classes derived from this - it
-        is their task to call this method with an appropriate form parameter.
-
-        @param form: a cgi.FieldStorage
-        @rtype: dict
-        @return: dict with form keys, each contains a list of values
-        """
-        args = {}
-        for key in form:
-            values = form[key]
-            if not isinstance(values, list):
-                values = [values]
-            fixedResult = []
-            for item in values:
-                if isinstance(item, cgi.FieldStorage) and item.filename:
-                    fixedResult.append(item.file) # open data tempfile
-                    # Save upload file name in a separate key
-                    args[key + '__filename__'] = item.filename
-                else:
-                    fixedResult.append(item.value)
-            args[key] = fixedResult
-
-        return self.decodeArgs(args)
-
-    def decodeArgs(self, args):
-        """ Decode args dict
-
-        Decoding is done in a separate path because it is reused by
-        other methods and sub classes.
-        """
-        decode = wikiutil.decodeUserInput
-        result = {}
-        for key in args:
-            if key + '__filename__' in args:
-                # Copy file data as is
-                result[key] = args[key]
-            elif key.endswith('__filename__'):
-                result[key] = decode(args[key], self.decode_charsets)
-            else:
-                result[key] = [decode(value, self.decode_charsets) for value in args[key]]
-        return result
-
-    def getBaseURL(self):
-        """ Return a fully qualified URL to this script. """
-        return self.getQualifiedURL(self.getScriptname())
-
-    def getQualifiedURL(self, uri=''):
-        """ Return an absolute URL starting with schema and host.
-
-        Already qualified urls are returned unchanged.
-
-        @param uri: server rooted uri e.g /scriptname/pagename.
-                    It must start with a slash. Must be ascii and url encoded.
-        """
-        import urlparse
-        scheme = urlparse.urlparse(uri)[0]
-        if scheme:
-            return uri
-
-        scheme = ('http', 'https')[self.is_ssl]
-        result = "%s://%s%s" % (scheme, self.http_host, uri)
-
-        # This might break qualified urls in redirects!
-        # e.g. mapping 'http://netloc' -> '/'
-        return wikiutil.mapURL(self, result)
-
-    def getUserAgent(self):
-        """ Get the user agent. """
-        return self.http_user_agent
-
-    def makeForbidden(self, resultcode, msg):
-        statusmsg = {
-            401: 'Authorization required',
-            403: 'FORBIDDEN',
-            404: 'Not found',
-            503: 'Service unavailable',
-        }
-        headers = [
-            'Status: %d %s' % (resultcode, statusmsg[resultcode]),
-            'Content-Type: text/plain; charset=utf-8'
-        ]
-        # when surge protection triggered, tell bots to come back later...
-        if resultcode == 503:
-            headers.append('Retry-After: %d' % self.cfg.surge_lockout_time)
-        self.emit_http_headers(headers)
-        self.write(msg)
-        self.forbidden = True
-
-    def makeForbidden403(self):
-        self.makeForbidden(403, 'You are not allowed to access this!\r\n')
-
-    def makeUnavailable503(self):
-        self.makeForbidden(503, "Warning:\r\n"
-                   "You triggered the wiki's surge protection by doing too many requests in a short time.\r\n"
-                   "Please make a short break reading the stuff you already got.\r\n"
-                   "When you restart doing requests AFTER that, slow down or you might get locked out for a longer time!\r\n")
-
-    def initTheme(self):
-        """ Set theme - forced theme, user theme or wiki default """
-        if self.cfg.theme_force:
-            theme_name = self.cfg.theme_default
-        else:
-            theme_name = self.user.theme_name
-        self.loadTheme(theme_name)
-
-    def _try_redirect_spaces_page(self, pagename):
-        if '_' in pagename and not self.page.exists():
-            pname = pagename.replace('_', ' ')
-            pg = Page(self, pname)
-            if pg.exists():
-                url = pg.url(self)
-                self.http_redirect(url)
-                return True
-        return False
-
-    def run(self):
-        # Exit now if __init__ failed or request is forbidden
-        if self.failed or self.forbidden or self._auth_redirected:
-            # Don't sleep() here, it binds too much of our resources!
-            return self.finish()
-
-        _ = self.getText
-        self.clock.start('run')
-
-        self.initTheme()
-
-        action_name = self.action
-        if self.cfg.log_timing:
-            self.timing_log(True, action_name)
-
-        if action_name == 'xmlrpc':
-            from MoinMoin import xmlrpc
-            if self.query_string == 'action=xmlrpc':
-                xmlrpc.xmlrpc(self)
-            elif self.query_string == 'action=xmlrpc2':
-                xmlrpc.xmlrpc2(self)
-            if self.cfg.log_timing:
-                self.timing_log(False, action_name)
-            return self.finish()
-
-        # parse request data
-        try:
-            # The last component in path_info is the page name, if any
-            path = self.getPathinfo()
-
-            # we can have all action URLs like this: /action/ActionName/PageName?action=ActionName&...
-            # this is just for robots.txt being able to forbid them for crawlers
-            prefix = self.cfg.url_prefix_action
-            if prefix is not None:
-                prefix = '/%s/' % prefix # e.g. '/action/'
-                if path.startswith(prefix):
-                    # remove prefix and action name
-                    path = path[len(prefix):]
-                    action, path = (path.split('/', 1) + ['', ''])[:2]
-                    path = '/' + path
-
-            if path.startswith('/'):
-                pagename = self.normalizePagename(path)
-            else:
-                pagename = None
-
-            # need to inform caches that content changes based on:
-            # * cookie (even if we aren't sending one now)
-            # * User-Agent (because a bot might be denied and get no content)
-            # * Accept-Language (except if moin is told to ignore browser language)
-            if self.cfg.language_ignore_browser:
-                self.setHttpHeader("Vary: Cookie,User-Agent")
-            else:
-                self.setHttpHeader("Vary: Cookie,User-Agent,Accept-Language")
-
-            # Handle request. We have these options:
-            # 1. jump to page where user left off
-            if not pagename and self.user.remember_last_visit and action_name == 'show':
-                pagetrail = self.user.getTrail()
-                if pagetrail:
-                    # Redirect to last page visited
-                    last_visited = pagetrail[-1]
-                    wikiname, pagename = wikiutil.split_interwiki(last_visited)
-                    if wikiname != 'Self':
-                        wikitag, wikiurl, wikitail, error = wikiutil.resolve_interwiki(self, wikiname, pagename)
-                        url = wikiurl + wikiutil.quoteWikinameURL(wikitail)
-                    else:
-                        url = Page(self, pagename).url(self)
-                else:
-                    # Or to localized FrontPage
-                    url = wikiutil.getFrontPage(self).url(self)
-                self.http_redirect(url)
-                return self.finish()
-
-            # 2. handle action
-            else:
-                # pagename could be empty after normalization e.g. '///' -> ''
-                # Use localized FrontPage if pagename is empty
-                if not pagename:
-                    self.page = wikiutil.getFrontPage(self)
-                else:
-                    self.page = Page(self, pagename)
-                    if self._try_redirect_spaces_page(pagename):
-                        return self.finish()
-
-                msg = None
-                # Complain about unknown actions
-                if not action_name in self.getKnownActions():
-                    msg = _("Unknown action %(action_name)s.") % {
-                            'action_name': wikiutil.escape(action_name), }
-
-                # Disallow non available actions
-                elif action_name[0].isupper() and not action_name in self.getAvailableActions(self.page):
-                    msg = _("You are not allowed to do %(action_name)s on this page.") % {
-                            'action_name': wikiutil.escape(action_name), }
-                    if not self.user.valid:
-                        # Suggest non valid user to login
-                        msg += " " + _("Login and try again.")
-
-                if msg:
-                    self.theme.add_msg(msg, "error")
-                    self.page.send_page()
-                # Try action
-                else:
-                    from MoinMoin import action
-                    handler = action.getHandler(self, action_name)
-                    if handler is None:
-                        msg = _("You are not allowed to do %(action_name)s on this page.") % {
-                                'action_name': wikiutil.escape(action_name), }
-                        if not self.user.valid:
-                            # Suggest non valid user to login
-                            msg += " " + _("Login and try again.")
-                        self.theme.add_msg(msg, "error")
-                        self.page.send_page()
-                    else:
-                        handler(self.page.page_name, self)
-
-            # every action that didn't use to raise MoinMoinFinish must call this now:
-            # self.theme.send_closing_html()
-
-        except MoinMoinFinish:
-            pass
-        except RemoteClosedConnection:
-            # at least clean up
-            pass
-        except SystemExit:
-            raise # fcgi uses this to terminate a thread
-        except Exception, err:
-            try:
-                # nothing we can do about further failures!
-                self.fail(err)
-            except:
-                pass
-
-        if self.cfg.log_timing:
-            self.timing_log(False, action_name)
-
-        return self.finish()
-
-    def http_redirect(self, url):
-        """ Redirect to a fully qualified, or server-rooted URL
-
-        @param url: relative or absolute url, ascii using url encoding.
-        """
-        url = self.getQualifiedURL(url)
-        self.emit_http_headers(["Status: 302 Found", "Location: %s" % url])
-
-    def emit_http_headers(self, more_headers=[], testing=False):
-        """ emit http headers after some preprocessing / checking
-
-            Makes sure we only emit headers once.
-            Encodes to ASCII if it gets unicode headers.
-            Make sure we have exactly one Content-Type and one Status header.
-            Make sure Status header string begins with a integer number.
-
-            For emitting (testing == False), it calls the server specific
-            _emit_http_headers method. For testing, it returns the result.
-
-            @param more_headers: list of additional header strings
-            @param testing: set to True by test code
-        """
-        user_headers = self.user_headers
-        self.user_headers = []
-        tracehere = ''.join(traceback.format_stack()[:-1])
-        all_headers = [(hdr, tracehere) for hdr in more_headers] + user_headers
-
-        if self.sent_headers:
-            # Send headers only once
-            logging.error("Attempt to send headers twice!")
-            logging.error("First attempt:\n%s" % self.sent_headers)
-            logging.error("Second attempt:\n%s" % tracehere)
-            raise HeadersAlreadySentException("emit_http_headers has already been called before!")
-        else:
-            self.sent_headers = tracehere
-
-        # assemble dict of http headers
-        headers = {}
-        traces = {}
-        for header, trace in all_headers:
-            if isinstance(header, unicode):
-                header = header.encode('ascii')
-            key, value = header.split(':', 1)
-            lkey = key.lower()
-            value = value.lstrip()
-            if lkey in headers:
-                if lkey in ['vary', 'cache-control', 'content-language', ]:
-                    # these headers (list might be incomplete) allow multiple values
-                    # that can be merged into a comma separated list
-                    headers[lkey] = headers[lkey][0], '%s, %s' % (headers[lkey][1], value)
-                    traces[lkey] = trace
-                else:
-                    logging.warning("Duplicate http header: %r (ignored)" % header)
-                    logging.warning("Header added first at:\n%s" % traces[lkey])
-                    logging.warning("Header added again at:\n%s" % trace)
-            else:
-                headers[lkey] = (key, value)
-                traces[lkey] = trace
-
-        if 'content-type' not in headers:
-            headers['content-type'] = ('Content-type', 'text/html; charset=%s' % config.charset)
-
-        if 'status' not in headers:
-            headers['status'] = ('Status', '200 OK')
-        else:
-            # check if we got a valid status
-            try:
-                status = headers['status'][1]
-                int(status.split(' ', 1)[0])
-            except:
-                logging.error("emit_http_headers called with invalid header Status: %r" % status)
-                headers['status'] = ('Status', '500 Server Error - invalid status header')
-
-        header_format = '%s: %s'
-        st_header = header_format % headers['status']
-        del headers['status']
-        ct_header = header_format % headers['content-type']
-        del headers['content-type']
-
-        headers = [header_format % kv_tuple for kv_tuple in headers.values()] # make a list of strings
-        headers = [st_header, ct_header] + headers # do NOT change order!
-        if not testing:
-            self._emit_http_headers(headers)
-        else:
-            return headers
-
-    def _emit_http_headers(self, headers):
-        """ server specific method to emit http headers.
-
-            @param headers: a list of http header strings in this FIXED order:
-                1. status header (always present and valid, e.g. "200 OK")
-                2. content type header (always present)
-                3. other headers (optional)
-        """
-        raise NotImplementedError
-
-    def setHttpHeader(self, header):
-        """ Save header for later send.
-
-            Attention: although we use a list here, some implementations use a dict,
-            thus multiple calls with the same header type do NOT work in the end!
-        """
-        # save a traceback with the header for duplicate bug reporting
-        self.user_headers.append((header, ''.join(traceback.format_stack()[:-1])))
-
-    def fail(self, err):
-        """ Fail when we can't continue
-
-        Send 500 status code with the error name. Reference:
-        http://www.w3.org/Protocols/rfc2616/rfc2616-sec6.html#sec6.1.1
-
-        Log the error, then let failure module handle it.
-
-        @param err: Exception instance or subclass.
-        """
-        self.failed = 1 # save state for self.run()
-        # we should not generate the headers two times
-        if not self.sent_headers:
-            self.emit_http_headers(['Status: 500 MoinMoin Internal Error'])
-        from MoinMoin import failure
-        failure.handle(self, err)
-
-    def make_unique_id(self, base, namespace=None):
-        """
-        Generates a unique ID using a given base name. Appends a running count to the base.
-
-        Needs to stay deterministic!
-
-        @param base: the base of the id
-        @type base: unicode
-        @param namespace: the namespace for the ID, used when including pages
-
-        @returns: a unique (relatively to the namespace) ID
-        @rtype: unicode
-        """
-        if not isinstance(base, unicode):
-            base = unicode(str(base), 'ascii', 'ignore')
-        if not namespace in self._page_ids:
-            self._page_ids[namespace] = {}
-        count = self._page_ids[namespace].get(base, -1) + 1
-        self._page_ids[namespace][base] = count
-        if not count:
-            return base
-        return u'%s-%d' % (base, count)
-
-    def init_unique_ids(self):
-        '''Initialise everything needed for unique IDs'''
-        self._unique_id_stack = []
-        self._page_ids = {None: {}}
-        self.include_id = None
-        self._include_stack = []
-
-    def push_unique_ids(self):
-        '''
-        Used by the TOC macro, this ensures that the ID namespaces
-        are reset to the status when the current include started.
-        This guarantees that doing the ID enumeration twice results
-        in the same results, on any level.
-        '''
-        self._unique_id_stack.append((self._page_ids, self.include_id))
-        self.include_id, pids = self._include_stack[-1]
-        # make a copy of the containing ID namespaces, that is to say
-        # go back to the level we had at the previous include
-        self._page_ids = {}
-        for namespace in pids:
-            self._page_ids[namespace] = pids[namespace].copy()
-
-    def pop_unique_ids(self):
-        '''
-        Used by the TOC macro to reset the ID namespaces after
-        having parsed the page for TOC generation and after
-        printing the TOC.
-        '''
-        self._page_ids, self.include_id = self._unique_id_stack.pop()
-
-    def begin_include(self, base):
-        '''
-        Called by the formatter when a document begins, which means
-        that include causing nested documents gives us an include
-        stack in self._include_id_stack.
-        '''
-        pids = {}
-        for namespace in self._page_ids:
-            pids[namespace] = self._page_ids[namespace].copy()
-        self._include_stack.append((self.include_id, pids))
-        self.include_id = self.make_unique_id(base)
-        # if it's the page name then set it to None so we don't
-        # prepend anything to IDs, but otherwise keep it.
-        if self.page and self.page.page_name == self.include_id:
-            self.include_id = None
-
-    def end_include(self):
-        '''
-        Called by the formatter when a document ends, restores
-        the current include ID to the previous one and discards
-        the page IDs state we kept around for push_unique_ids().
-        '''
-        self.include_id, pids = self._include_stack.pop()
-
-    def httpDate(self, when=None, rfc='1123'):
-        """ Returns http date string, according to rfc2068
-
-        See http://www.cse.ohio-state.edu/cgi-bin/rfc/rfc2068.html#sec-3.3
-
-        A http 1.1 server should use only rfc1123 date, but cookie's
-        "expires" field should use the older obsolete rfc850 date.
-
-        Note: we can not use strftime() because that honors the locale
-        and rfc2822 requires english day and month names.
-
-        We can not use email.Utils.formatdate because it formats the
-        zone as '-0000' instead of 'GMT', and creates only rfc1123
-        dates. This is a modified version of email.Utils.formatdate
-        from Python 2.4.
-
-        @param when: seconds from epoch, as returned by time.time()
-        @param rfc: conform to rfc ('1123' or '850')
-        @rtype: string
-        @return: http date conforming to rfc1123 or rfc850
-        """
-        if when is None:
-            when = time.time()
-        now = time.gmtime(when)
-        month = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul',
-                 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'][now.tm_mon - 1]
-        if rfc == '1123':
-            day = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'][now.tm_wday]
-            date = '%02d %s %04d' % (now.tm_mday, month, now.tm_year)
-        elif rfc == '850':
-            day = ["Monday", "Tuesday", "Wednesday", "Thursday",
-                    "Friday", "Saturday", "Sunday"][now.tm_wday]
-            date = '%02d-%s-%s' % (now.tm_mday, month, str(now.tm_year)[-2:])
-        else:
-            raise ValueError("Invalid rfc value: %s" % rfc)
-
-        return '%s, %s %02d:%02d:%02d GMT' % (day, date, now.tm_hour,
-                                              now.tm_min, now.tm_sec)
-
-    def disableHttpCaching(self, level=1):
-        """ Prevent caching of pages that should not be cached.
-
-        level == 1 means disabling caching when we have a cookie set
-        level == 2 means completely disabling caching (used by Page*Editor)
-
-        This is important to prevent caches break acl by providing one
-        user pages meant to be seen only by another user, when both users
-        share the same caching proxy.
-
-        AVOID using no-cache and no-store for attachments as it is completely broken on IE!
-
-        Details: http://support.microsoft.com/support/kb/articles/Q234/0/67.ASP
-        """
-        if level <= self.http_caching_disabled:
-            return # only make caching stricter
-
-        if level == 1:
-            # Set Cache control header for http 1.1 caches
-            # See http://www.cse.ohio-state.edu/cgi-bin/rfc/rfc2109.html#sec-4.2.3
-            # and http://www.cse.ohio-state.edu/cgi-bin/rfc/rfc2068.html#sec-14.9
-            #self.setHttpHeader('Cache-Control: no-cache="set-cookie", private, max-age=0')
-            self.setHttpHeader('Cache-Control: private, must-revalidate, max-age=10')
-        elif level == 2:
-            self.setHttpHeader('Cache-Control: no-cache')
-
-        # only do this once to avoid 'duplicate header' warnings
-        # (in case the caching disabling is being made stricter)
-        if not self.http_caching_disabled:
-            # Set Expires for http 1.0 caches (does not support Cache-Control)
-            when = time.time() - (3600 * 24 * 365)
-            self.setHttpHeader('Expires: %s' % self.httpDate(when=when))
-
-        # Set Pragma for http 1.0 caches
-        # See http://www.cse.ohio-state.edu/cgi-bin/rfc/rfc2068.html#sec-14.32
-        # DISABLED for level == 1 to fix IE https file attachment downloading trouble.
-        if level == 2:
-            self.setHttpHeader('Pragma: no-cache')
-
-        self.http_caching_disabled = level
-
-    def finish(self):
-        """ General cleanup on end of request
-
-        Delete circular references - all object that we create using self.name = class(self).
-        This helps Python to collect these objects and keep our memory footprint lower.
-        """
-        for method in self._finishers:
-            method(self)
-        # only execute finishers once
-        self._finishers = []
-
-        for attr_name in [
-            'editlog', # avoid leaking file handles for open edit-log
-            'theme',
-            'dicts',
-            'user',
-            'rootpage',
-            'page',
-            'html_formatter',
-            'formatter',
-            #'cfg', -- do NOT delattr cfg - it causes problems in the xapian indexing thread
-            ]:
-            try:
-                delattr(self, attr_name)
-            except:
-                pass
-
-    def add_finisher(self, method):
-        self._finishers.append(method)
-
-    # Debug ------------------------------------------------------------
-
-    def debugEnvironment(self, env):
-        """ Environment debugging aid """
-        # Keep this one name per line so its easy to comment stuff
-        names = [
-#             'http_accept_language',
-#             'http_host',
-#             'http_referer',
-#             'http_user_agent',
-#             'is_ssl',
-            'path_info',
-            'query_string',
-#             'remote_addr',
-            'request_method',
-#             'request_uri',
-#             'saved_cookie',
-            'script_name',
-#             'server_name',
-#             'server_port',
-            ]
-        names.sort()
-        attributes = []
-        for name in names:
-            attributes.append('  %s = %r\n' % (name, getattr(self, name, None)))
-        attributes = ''.join(attributes)
-
-        environment = []
-        names = env.keys()
-        names.sort()
-        for key in names:
-            environment.append('  %s = %r\n' % (key, env[key]))
-        environment = ''.join(environment)
-
-        data = '\nRequest Attributes\n%s\nEnvironment\n%s' % (attributes, environment)
-        f = open('/tmp/env.log', 'a')
-        try:
-            f.write(data)
-        finally:
-            f.close()
-
--- a/MoinMoin/request/_tests/test_request.py	Tue Sep 23 00:44:45 2008 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,137 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
-    MoinMoin - MoinMoin.module_tested Tests
-
-    @copyright: 2003-2004 by Juergen Hermann <jh@web.de>,
-                2007 by MoinMoin:ThomasWaldmann
-    @license: GNU GPL, see COPYING for details.
-"""
-
-import py
-
-from MoinMoin import config, wikiutil
-from MoinMoin._tests import wikiconfig
-
-from MoinMoin.request import HeadersAlreadySentException
-
-class TestNormalizePagename(object):
-
-    def testPageInvalidChars(self):
-        """ request: normalize pagename: remove invalid unicode chars
-
-        Assume the default setting
-        """
-        test = u'\u0000\u202a\u202b\u202c\u202d\u202e'
-        expected = u''
-        result = self.request.normalizePagename(test)
-        assert result == expected
-
-    def testNormalizeSlashes(self):
-        """ request: normalize pagename: normalize slashes """
-        cases = (
-            (u'/////', u''),
-            (u'/a', u'a'),
-            (u'a/', u'a'),
-            (u'a/////b/////c', u'a/b/c'),
-            (u'a b/////c d/////e f', u'a b/c d/e f'),
-            )
-        for test, expected in cases:
-            result = self.request.normalizePagename(test)
-            assert result == expected
-
-    def testNormalizeWhitespace(self):
-        """ request: normalize pagename: normalize whitespace """
-        cases = (
-            (u'         ', u''),
-            (u'    a', u'a'),
-            (u'a    ', u'a'),
-            (u'a     b     c', u'a b c'),
-            (u'a   b  /  c    d  /  e   f', u'a b/c d/e f'),
-            # All 30 unicode spaces
-            (config.chars_spaces, u''),
-            )
-        for test, expected in cases:
-            result = self.request.normalizePagename(test)
-            assert result == expected
-
-    def testUnderscoreTestCase(self):
-        """ request: normalize pagename: underscore convert to spaces and normalized
-
-        Underscores should convert to spaces, then spaces should be
-        normalized, order is important!
-        """
-        cases = (
-            (u'         ', u''),
-            (u'  a', u'a'),
-            (u'a  ', u'a'),
-            (u'a  b  c', u'a b c'),
-            (u'a  b  /  c  d  /  e  f', u'a b/c d/e f'),
-            )
-        for test, expected in cases:
-            result = self.request.normalizePagename(test)
-            assert result == expected
-
-
-class TestGroupPages(object):
-
-    def testNormalizeGroupName(self):
-        """ request: normalize pagename: restrict groups to alpha numeric Unicode
-
-        Spaces should normalize after invalid chars removed!
-        """
-        cases = (
-            # current acl chars
-            (u'Name,:Group', u'NameGroup'),
-            # remove than normalize spaces
-            (u'Name ! @ # $ % ^ & * ( ) + Group', u'Name Group'),
-            )
-        for test, expected in cases:
-            # validate we are testing valid group names
-            if wikiutil.isGroupPage(self.request, test):
-                result = self.request.normalizePagename(test)
-                assert result == expected
-
-
-class TestHTTPDate(object):
-
-    def testRFC1123Date(self):
-        """ request: httpDate default rfc1123 """
-        assert self.request.httpDate(0) == 'Thu, 01 Jan 1970 00:00:00 GMT'
-
-    def testRFC850Date(self):
-        """ request: httpDate rfc850 """
-        assert self.request.httpDate(0, rfc='850') == 'Thursday, 01-Jan-70 00:00:00 GMT'
-
-
-class TestHTTPHeaders(object):
-    std_headers = ['Status: 200 OK', 'Content-type: text/html; charset=%s' % config.charset]
-
-    def setup_method(self, method):
-        self.request.sent_headers = None
-
-    def testAutoAddStdHeaders(self):
-        """ test if the usual headers get auto-added if not specified """
-        headers_out = self.request.emit_http_headers(testing=True)
-        assert headers_out == self.std_headers
-
-    def testHeadersOnlyOnce(self):
-        """ test if trying to call emit_http_headers multiple times raises an exception """
-        self.request.emit_http_headers(testing=True)
-        py.test.raises(HeadersAlreadySentException, self.request.emit_http_headers, [], {'testing': True})
-
-    def testDuplicateHeadersIgnored(self):
-        """ test if duplicate headers get ignored """
-        headers_in = self.std_headers + ['Status: 500 Server Error']
-        headers_expected = self.std_headers
-        headers_out = self.request.emit_http_headers(headers_in, testing=True)
-        assert headers_out == headers_expected
-
-    def testListHeaders(self):
-        """ test if header values get merged into a list for headers supporting it """
-        headers_in = self.std_headers + ['Vary: aaa', 'vary: bbb']
-        headers_expected = self.std_headers + ['Vary: aaa, bbb']
-        headers_out = self.request.emit_http_headers(headers_in, testing=True)
-        assert headers_out == headers_expected
-
-coverage_modules = ['MoinMoin.request']
-
--- a/MoinMoin/request/request_cgi.py	Tue Sep 23 00:44:45 2008 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,73 +0,0 @@
-# -*- coding: iso-8859-1 -*-
-"""
-    MoinMoin - CGI Request Implementation for std. CGI web servers
-    like Apache or IIS or others.
-
-    @copyright: 2001-2003 by Juergen Hermann <jh@web.de>,
-                2003-2006 MoinMoin:ThomasWaldmann
-    @license: GNU GPL, see COPYING for details.
-"""
-import sys, os, cgi
-
-from MoinMoin import log
-logging = log.getLogger(__name__)
-
-from MoinMoin.request import RequestBase, RemoteClosedConnection
-
-class Request(RequestBase):
-    """ specialized on CGI requests """
-
-    def __init__(self, properties={}):
-        try:
-            # force input/output to binary
-            if sys.platform == "win32":
-                import msvcrt
-                msvcrt.setmode(sys.stdin.fileno(), os.O_BINARY)
-                msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
-
-            self._setup_vars_from_std_env(os.environ)
-            RequestBase.__init__(self, properties)
-
-        except Exception, err:
-            self.fail(err)
-
-    def _setup_args_from_cgi_form(self):
-        """ Override to create cgi form """
-        form = cgi.FieldStorage(keep_blank_values=1)
-        return RequestBase._setup_args_from_cgi_form(self, form)
-
-    def read(self, n):
-        """ Read from input stream. """
-        if n is None:
-            logging.warning("calling request.read(None) might block")
-            return sys.stdin.read()
-        else:
-            return sys.stdin.read(n)
-
-    def write(self, *data):
-        """ Write to output stream. """
-        data = self.encode(data)
-        try:
-            sys.stdout.write(data)
-        except Exception:
-            raise RemoteClosedConnection()
-
-    def flush(self):
-        sys.stdout.flush()
-
-    def finish(self):
-        RequestBase.finish(self)
-        # flush the output, ignore errors caused by the user closing the socket
-        try:
-            sys.stdout.flush()
-        except IOError, ex:
-            import errno
-            if ex.errno != errno.EPIPE:
-                raise
-
-    def _emit_http_headers(self, headers):
-        """ private method to send out preprocessed list of HTTP headers """
-        for header in headers:
-            self.write("%s\r\n" % header)
-        self.write("\r\n")
-
--- a/MoinMoin/request/request_cli.py	Tue Sep 23 00:44:45 2008 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,104 +0,0 @@
-# -*- coding: iso-8859-1 -*-
-"""
-    MoinMoin - CLI Request Implementation for commandline usage.
-
-    @copyright: 2001-2003 Juergen Hermann <jh@web.de>,
-                2003-2006 MoinMoin:ThomasWaldmann
-    @license: GNU GPL, see COPYING for details.
-"""
-import sys
-
-from MoinMoin import log
-logging = log.getLogger(__name__)
-
-from MoinMoin.request import RequestBase, RemoteClosedConnection
-
-class Request(RequestBase):
-    """ specialized on command line interface and script requests """
-
-    def __init__(self, url='CLI', pagename='', properties={}, given_config=None):
-        self.saved_cookie = ''
-        self.path_info = '/' + pagename
-        self.query_string = ''
-        self.remote_addr = '127.0.0.1'
-        self.is_ssl = 0
-        self.http_user_agent = 'CLI/Script'
-        self.url = url
-        self.request_method = 'GET'
-        self.request_uri = '/' + pagename # TODO check if /pagename works as URI for CLI usage
-        self.http_host = 'localhost'
-        self.http_referer = ''
-        self.script_name = '.'
-        self.content_length = None
-        self.if_modified_since = None
-        self.if_none_match = None
-        RequestBase.__init__(self, properties, given_config)
-        self.cfg.caching_formats = [] # don't spoil the cache
-        self.initTheme() # usually request.run() does this, but we don't use it
-
-    def _setup_args_from_cgi_form(self):
-        """ Override to create cli form """
-        #form = cgi.FieldStorage()
-        #return RequestBase._setup_args_from_cgi_form(self, form)
-        return {}
-
-    def read(self, n):
-        """ Read from input stream. """
-        if n is None:
-            logging.warning("calling request.read(None) might block")
-            return sys.stdin.read()
-        else:
-            return sys.stdin.read(n)
-
-    def write(self, *data):
-        """ Write to output stream. """
-        data = self.encode(data)
-        try:
-            sys.stdout.write(data)
-        except IOError:
-            raise RemoteClosedConnection()
-
-    def flush(self):
-        sys.stdout.flush()
-
-    def finish(self):
-        RequestBase.finish(self)
-        # flush the output, ignore errors caused by the user closing the socket
-        try:
-            sys.stdout.flush()
-        except IOError, ex:
-            import errno
-            if ex.errno != errno.EPIPE:
-                raise
-
-    def isForbidden(self):
-        """ Nothing is forbidden """
-        return 0
-
-    # Accessors --------------------------------------------------------
-
-    def getQualifiedURL(self, uri=None):
-        """ Return a full URL starting with schema and host
-
-        TODO: does this create correct pages when you render wiki pages
-              within a cli request?!
-        """
-        return uri
-
-    # Headers ----------------------------------------------------------
-
-    def setHttpHeader(self, header):
-        pass
-
-    def _emit_http_headers(self, headers):
-        """ private method to send out preprocessed list of HTTP headers """
-        pass
-
-    def http_redirect(self, url):
-        """ Redirect to a fully qualified, or server-rooted URL
-
-        TODO: Does this work for rendering redirect pages?
-        """
-        raise Exception("Redirect not supported for command line tools!")
-
-
--- a/MoinMoin/request/request_fcgi.py	Tue Sep 23 00:44:45 2008 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,77 +0,0 @@
-# -*- coding: iso-8859-1 -*-
-"""
-    MoinMoin - FastCGI Request Implementation for fastcgi and Apache
-    (and maybe others).
-
-    @copyright: 2001-2003 Juergen Hermann <jh@web.de>,
-                2003-2006 MoinMoin:ThomasWaldmann
-    @license: GNU GPL, see COPYING for details.
-"""
-from MoinMoin import log
-logging = log.getLogger(__name__)
-
-from MoinMoin.request import RequestBase, RemoteClosedConnection
-
-class Request(RequestBase):
-    """ specialized on FastCGI requests """
-
-    def __init__(self, fcgRequest, env, form, properties={}):
-        """ Initializes variables from FastCGI environment and saves
-            FastCGI request and form for further use.
-
-            @param fcgRequest: the FastCGI request instance.
-            @param env: environment passed by FastCGI.
-            @param form: FieldStorage passed by FastCGI.
-        """
-        try:
-            self.fcgreq = fcgRequest
-            self.fcgenv = env
-            self.fcgform = form
-            self._setup_vars_from_std_env(env)
-            RequestBase.__init__(self, properties)
-
-        except Exception, err:
-            self.fail(err)
-
-    def _setup_args_from_cgi_form(self):
-        """ Override to use FastCGI form """
-        # thfcgi used keep_blank_values=1 internally for fcgform
-        return RequestBase._setup_args_from_cgi_form(self, self.fcgform)
-
-    def read(self, n):
-        """ Read from input stream. """
-        if n is None:
-            logging.warning("calling request.read(None) might block")
-            return self.fcgreq.stdin.read()
-        else:
-            return self.fcgreq.stdin.read(n)
-
-    def write(self, *data):
-        """ Write to output stream. """
-        data = self.encode(data)
-        try:
-            self.fcgreq.out.write(data)
-        except Exception:
-            raise RemoteClosedConnection()
-
-    def send_file(self, fileobj, bufsize=8192, do_flush=True):
-        # as thfcgi buffers everything we write until we do a flush, we use
-        # do_flush=True as default here (otherwise the sending of big file
-        # attachments would consume lots of memory)
-        return RequestBase.send_file(self, fileobj, bufsize, do_flush)
-
-    def flush(self):
-        """ Flush output stream. """
-        self.fcgreq.flush_out()
-
-    def finish(self):
-        """ Call finish method of FastCGI request to finish handling of this request. """
-        RequestBase.finish(self)
-        self.fcgreq.finish()
-
-    def _emit_http_headers(self, headers):
-        """ private method to send out preprocessed list of HTTP headers """
-        for header in headers:
-            self.write("%s\r\n" % header)
-        self.write("\r\n")
-
--- a/MoinMoin/request/request_modpython.py	Tue Sep 23 00:44:45 2008 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,168 +0,0 @@
-# -*- coding: iso-8859-1 -*-
-"""
-    MoinMoin - mod_python Request Implementation for Apache and mod_python.
-
-    @copyright: 2001-2003 Juergen Hermann <jh@web.de>,
-                2003-2006 MoinMoin:ThomasWaldmann
-    @license: GNU GPL, see COPYING for details.
-"""
-from MoinMoin import log
-logging = log.getLogger(__name__)
-
-from MoinMoin import wikiutil
-from MoinMoin.request import RequestBase, RemoteClosedConnection
-
-class Request(RequestBase):
-    """ specialized on mod_python requests """
-
-    def __init__(self, req, properties={}):
-        """ Saves mod_pythons request and sets basic variables using
-            the req.subprocess_env, cause this provides a standard
-            way to access the values we need here.
-
-            @param req: the mod_python request instance
-        """
-        try:
-            # flags if headers sent out contained content-type or status
-            self._have_ct = 0
-            self._have_status = 0
-
-            req.add_common_vars()
-            self.mpyreq = req
-            # some mod_python 2.7.X has no get method for table objects,
-            # so we make a real dict out of it first.
-            if not hasattr(req.subprocess_env, 'get'):
-                env = dict(req.subprocess_env)
-            else:
-                env = req.subprocess_env
-            self._setup_vars_from_std_env(env)
-            RequestBase.__init__(self, properties)
-
-        except Exception, err:
-            self.fail(err)
-
-    def fixURI(self, env):
-        """ Fix problems with script_name and path_info using
-        PythonOption directive to rewrite URI.
-
-        This is needed when using Apache 1 or other server which does
-        not support adding custom headers per request. With mod_python we
-        can use the PythonOption directive:
-
-            <Location /url/to/mywiki/>
-                PythonOption X-Moin-Location /url/to/mywiki/
-            </location>
-
-        Note that *neither* script_name *nor* path_info can be trusted
-        when Moin is invoked as a mod_python handler with apache1, so we
-        must build both using request_uri and the provided PythonOption.
-        """
-        # Be compatible with release 1.3.5 "Location" option
-        # TODO: Remove in later release, we should have one option only.
-        old_location = 'Location'
-        options_table = self.mpyreq.get_options()
-        if not hasattr(options_table, 'get'):
-            options = dict(options_table)
-        else:
-            options = options_table
-        location = options.get(self.moin_location) or options.get(old_location)
-        if location:
-            env[self.moin_location] = location
-            # Try to recreate script_name and path_info from request_uri.
-            import urlparse
-            scriptAndPath = urlparse.urlparse(self.request_uri)[2]
-            self.script_name = location.rstrip('/')
-            path = scriptAndPath.replace(self.script_name, '', 1)
-            self.path_info = wikiutil.url_unquote(path, want_unicode=False)
-
-        RequestBase.fixURI(self, env)
-
-    def _setup_args_from_cgi_form(self):
-        """ Override to use mod_python.util.FieldStorage
-
-        It's little different from cgi.FieldStorage, so we need to
-        duplicate the conversion code.
-        """
-        from mod_python import util
-        form = util.FieldStorage(self.mpyreq, keep_blank_values=1) # by default this evaluates query string AND body POST data!
-
-        args = {}
-
-        # You cannot get rid of .keys() here
-        for key in form.keys():
-            if key is None:
-                continue
-            values = form[key]
-            if not isinstance(values, list):
-                values = [values]
-            fixedResult = []
-            for item in values:
-                if isinstance(item, util.StringField):
-                    fixedResult.append(item.value)
-                elif isinstance(item, util.Field) and item.filename:
-                    fixedResult.append(item.file)
-                    # Remember filenames with a name hack
-                    args[key + '__filename__'] = item.filename
-                    # XXX Now it gets extremely dirty to work around a problem in mod_python 3.3.1: XXX
-                    # Without the next line, item.file will be closed when item/form leaves this scope.
-                    # I guess some reference counting is not implemented correctly for item.file,
-                    # so we just keep a reference to item to keep it alive...
-                    fixedResult.append(item)  # we are lucky, nobody uses the 2nd list item anyway
-                    # If you are reading this, please switch to mod_wsgi. :)
-                elif isinstance(item, str):
-                    # mod_python 2.7 might return strings instead of Field objects.
-                    fixedResult.append(item)
-            args[key] = fixedResult
-
-        result = self.decodeArgs(args)
-        return result  # XXX without the hack above, item.file gets closed when returning! XXX
-
-    def run(self, req):
-        """ mod_python calls this with its request object. We don't
-            need it cause its already passed to __init__. So ignore
-            it and just return RequestBase.run.
-
-            @param req: the mod_python request instance
-        """
-        return RequestBase.run(self)
-
-    def read(self, n):
-        """ Read from input stream. """
-        if n is None:
-            logging.warning("calling request.read(None) might block")
-            return self.mpyreq.read()
-        else:
-            return self.mpyreq.read(n)
-
-    def write(self, *data):
-        """ Write to output stream. """
-        data = self.encode(data)
-        try:
-            self.mpyreq.write(data)
-        except Exception:
-            raise RemoteClosedConnection()
-
-    def flush(self):
-        """ We can't flush it, so do nothing. """
-        pass
-
-    def finish(self):
-        """ Just return apache.OK. Status is set in req.status. """
-        RequestBase.finish(self)
-        # is it possible that we need to return something else here?
-        from mod_python import apache
-        return apache.OK
-
-    def _emit_http_headers(self, headers):
-        """ private method to send out preprocessed list of HTTP headers """
-        st_header, ct_header, other_headers = headers[0], headers[1], headers[2:]
-        status = st_header.split(':', 1)[1].lstrip()
-        self.mpyreq.status = int(status.split(' ', 1)[0])
-        self.mpyreq.content_type = ct_header.split(':', 1)[1].lstrip()
-        for header in other_headers:
-            key, value = header.split(':', 1)
-            value = value.lstrip()
-            self.mpyreq.headers_out[key] = value
-        # this is for mod_python 2.7.X, for 3.X it's a NOP
-        self.mpyreq.send_http_header()
-
--- a/MoinMoin/request/request_standalone.py	Tue Sep 23 00:44:45 2008 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,108 +0,0 @@
-# -*- coding: iso-8859-1 -*-
-"""
-    MoinMoin - Standalone Moin Server Request Implementation
-
-    @copyright: 2001-2003 Juergen Hermann <jh@web.de>,
-                2003-2006 MoinMoin:ThomasWaldmann
-    @license: GNU GPL, see COPYING for details.
-"""
-import cgi
-
-from MoinMoin import log
-logging = log.getLogger(__name__)
-
-from MoinMoin.request import RequestBase, RemoteClosedConnection
-
-class Request(RequestBase):
-    """ specialized on StandAlone Server (MoinMoin.server.server_standalone) requests """
-    script_name = ''
-
-    def __init__(self, sa, properties={}):
-        """
-        @param sa: stand alone server object
-        @param properties: ...
-        """
-        try:
-            self.sareq = sa
-            self.wfile = sa.wfile
-            self.rfile = sa.rfile
-            self.headers = sa.headers
-            self.is_ssl = 0
-
-            # Copy headers
-            self.http_accept_language = (sa.headers.getheader('accept-language')
-                                         or self.http_accept_language)
-            self.http_user_agent = sa.headers.getheader('user-agent', '')
-            try:
-                self.content_length = int(sa.headers.getheader('content-length'))
-            except (TypeError, ValueError):
-                self.content_length = None
-            co = [c for c in sa.headers.getheaders('cookie') if c]
-            self.saved_cookie = ', '.join(co) or ''
-            self.if_modified_since = sa.headers.getheader('if-modified-since')
-            self.if_none_match = sa.headers.getheader('if-none-match')
-
-            # Copy rest from standalone request
-            self.server_name = sa.server.server_name
-            self.server_port = str(sa.server.server_port)
-            self.request_method = sa.command
-            self.request_uri = sa.path
-            self.remote_addr = sa.client_address[0]
-
-            # Values that need more work
-            self.path_info, self.query_string = self.splitURI(sa.path)
-            self.setHttpReferer(sa.headers.getheader('referer'))
-            self.setHost(sa.headers.getheader('host'))
-            self.setURL(sa.headers)
-
-            ##self.debugEnvironment(sa.headers)
-
-            RequestBase.__init__(self, properties)
-
-        except Exception, err:
-            self.fail(err)
-
-    def _setup_args_from_cgi_form(self):
-        """ Override to create standalone form """
-        form = cgi.FieldStorage(self.rfile, headers=self.headers, environ={'REQUEST_METHOD': 'POST'},
-                                keep_blank_values=1)
-        return RequestBase._setup_args_from_cgi_form(self, form)
-
-    def read(self, n):
-        """ Read from input stream """
-        if n is None:
-            logging.warning("calling request.read(None) might block")
-            return self.rfile.read()
-        else:
-            return self.rfile.read(n)
-
-    def write(self, *data):
-        """ Write to output stream. """
-        data = self.encode(data)
-        try:
-            self.wfile.write(data)
-        except Exception:
-            raise RemoteClosedConnection()
-
-    def flush(self):
-        self.wfile.flush()
-
-    def finish(self):
-        RequestBase.finish(self)
-        self.wfile.flush()
-
-    # Headers ----------------------------------------------------------
-
-    def _emit_http_headers(self, headers):
-        """ private method to send out preprocessed list of HTTP headers """
-        st_header, other_headers = headers[0], headers[1:]
-        status = st_header.split(':', 1)[1].lstrip()
-        status_code, status_msg = status.split(' ', 1)
-        status_code = int(status_code)
-        self.sareq.send_response(status_code, status_msg)
-        for header in other_headers:
-            key, value = header.split(':', 1)
-            value = value.lstrip()
-            self.sareq.send_header(key, value)
-        self.sareq.end_headers()
-
--- a/MoinMoin/request/request_twisted.py	Tue Sep 23 00:44:45 2008 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,143 +0,0 @@
-# -*- coding: iso-8859-1 -*-
-"""
-    MoinMoin - Twisted Request Implementation
-
-    @copyright: 2001-2003 Juergen Hermann <jh@web.de>,
-                2003-2006 MoinMoin:ThomasWaldmann
-    @license: GNU GPL, see COPYING for details.
-"""
-
-from MoinMoin import log
-logging = log.getLogger(__name__)
-
-from MoinMoin.request import RequestBase, MoinMoinFinish, RemoteClosedConnection
-
-class Request(RequestBase):
-    """ specialized on Twisted requests """
-
-    def __init__(self, twistedRequest, pagename, reactor, properties={}):
-        try:
-            self.twistd = twistedRequest
-            self.reactor = reactor
-
-            # Copy headers
-            self.http_accept_language = self.twistd.getHeader('Accept-Language')
-            self.saved_cookie = self.twistd.getHeader('Cookie')
-            self.http_user_agent = self.twistd.getHeader('User-Agent')
-            try:
-                self.content_length = int(self.twistd.getHeader('Content-Length'))
-            except (TypeError, ValueError):
-                self.content_length = None
-            self.if_modified_since = self.twistd.getHeader('If-Modified-Since')
-            self.if_none_match = self.twistd.getHeader('If-None-Match')
-
-            # Copy values from twisted request
-            self.server_protocol = self.twistd.clientproto
-            self.server_name = self.twistd.getRequestHostname().split(':')[0]
-            self.server_port = str(self.twistd.getHost()[2])
-            self.is_ssl = self.twistd.isSecure()
-            self.path_info = '/' + '/'.join([pagename] + self.twistd.postpath)
-            self.request_method = self.twistd.method
-            self.remote_addr = self.twistd.getClientIP()
-            self.request_uri = self.twistd.uri
-            self.script_name = "/" + '/'.join(self.twistd.prepath[:-1])
-
-            # Values that need more work
-            self.query_string = self.splitURI(self.twistd.uri)[1]
-            self.setHttpReferer(self.twistd.getHeader('Referer'))
-            self.setHost()
-            self.setURL(self.twistd.getAllHeaders())
-
-            ##self.debugEnvironment(twistedRequest.getAllHeaders())
-
-            RequestBase.__init__(self, properties)
-
-        except MoinMoinFinish: # might be triggered by http_redirect
-            self.emit_http_headers() # send headers (important for sending MOIN_ID cookie)
-            self.finish()
-
-        except Exception, err:
-            # Wrap err inside an internal error if needed
-            from MoinMoin import error
-            if isinstance(err, error.FatalError):
-                self.delayedError = err
-            else:
-                self.delayedError = error.InternalError(str(err))
-
-    def run(self):
-        """ Handle delayed errors then invoke base class run """
-        if hasattr(self, 'delayedError'):
-            self.fail(self.delayedError)
-            return self.finish()
-        RequestBase.run(self)
-
-    def setup_args(self):
-        """ Return args dict
-
-        Twisted already parsed args, including __filename__ hacking,
-        but did not decode the values.
-        """
-        # All of the arguments, including URL and POST arguments (using keep_blank_values=1 internally).
-        return self.decodeArgs(self.twistd.args)
-
-    def read(self, n):
-        """ Read from input stream. """
-        # XXX why is that wrong?:
-        #rd = self.reactor.callFromThread(self.twistd.read)
-
-        # XXX do we need self.reactor.callFromThread with that?
-        # XXX if yes, why doesn't it work?
-        self.twistd.content.seek(0, 0)
-        if n is None:
-            logging.warning("calling request.read(None) might block")
-            rd = self.twistd.content.read()
-        else:
-            rd = self.twistd.content.read(n)
-        #print "request.RequestTwisted.read: data=\n" + str(rd)
-        return rd
-
-    def write(self, *data):
-        """ Write to output stream. """
-        #print "request.RequestTwisted.write: data=\n" + wd
-        data = self.encode(data)
-        try:
-            self.reactor.callFromThread(self.twistd.write, data)
-        except Exception:
-            raise RemoteClosedConnection()
-
-    def flush(self):
-        pass # XXX is there a flush in twisted?
-
-    def finish(self):
-        RequestBase.finish(self)
-        self.reactor.callFromThread(self.twistd.finish)
-
-    # Headers ----------------------------------------------------------
-
-    def _emit_http_headers(self, headers):
-        """ private method to send out preprocessed list of HTTP headers """
-        st_header, other_headers = headers[0], headers[1:]
-        status = st_header.split(':', 1)[1].lstrip()
-        status_code, status_msg = status.split(' ', 1)
-        self.twistd.setResponseCode(int(status_code), status_msg)
-        for header in other_headers:
-            key, value = header.split(':', 1)
-            value = value.lstrip()
-            if key.lower() == 'set-cookie':
-                key, value = value.split('=', 1)
-                self.twistd.addCookie(key, value)
-            else:
-                self.twistd.setHeader(key, value)
-
-    def http_redirect(self, url):
-        """ Redirect to a fully qualified, or server-rooted URL
-
-        @param url: relative or absolute url, ascii using url encoding.
-        """
-        url = self.getQualifiedURL(url)
-        self.twistd.redirect(url)
-        # calling finish here will send the rest of the data to the next
-        # request. leave the finish call to run()
-        #self.twistd.finish()
-        raise MoinMoinFinish
-
--- a/MoinMoin/request/request_wsgi.py	Tue Sep 23 00:44:45 2008 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,80 +0,0 @@
-# -*- coding: iso-8859-1 -*-
-"""
-    MoinMoin - WSGI Request Implementation for std. WSGI web servers.
-
-    @copyright: 2001-2003 Juergen Hermann <jh@web.de>,
-                2003-2006 MoinMoin:ThomasWaldmann
-    @license: GNU GPL, see COPYING for details.
-"""
-import cgi, StringIO
-
-from MoinMoin import log
-logging = log.getLogger(__name__)
-
-from MoinMoin.request import RequestBase, RemoteClosedConnection
-
-class Request(RequestBase):
-    """ specialized on WSGI requests """
-    def __init__(self, env):
-        try:
-            self.env = env
-            self.hasContentType = False
-
-            self.stdin = env['wsgi.input']
-            self.stdout = StringIO.StringIO()
-
-            # used by MoinMoin.server.server_wsgi:
-            self.status = '200 OK'
-            self.headers = []
-
-            # used by send_file()
-            self._send_file = None
-            self._send_bufsize = None
-
-            self._setup_vars_from_std_env(env)
-            RequestBase.__init__(self, {})
-
-        except Exception, err:
-            self.fail(err)
-
-    def _setup_args_from_cgi_form(self):
-        """ Override to create cgi form """
-        form = cgi.FieldStorage(fp=self.stdin, environ=self.env, keep_blank_values=1)
-        return RequestBase._setup_args_from_cgi_form(self, form)
-
-    def read(self, n):
-        if n is None:
-            logging.warning("calling request.read(None) might block")
-            return self.stdin.read()
-        else:
-            return self.stdin.read(n)
-
-    def send_file(self, fileobj, bufsize=8192, do_flush=None):
-        # For now, we just remember fileobj and bufsize for sending it later:
-        self._send_file = fileobj
-        self._send_bufsize = bufsize
-
-    def write(self, *data):
-        data = self.encode(data)
-        try:
-            self.stdout.write(data)
-        except Exception:
-            raise RemoteClosedConnection()
-
-    def _emit_http_headers(self, headers):
-        """ private method to send out preprocessed list of HTTP headers """
-        st_header, other_headers = headers[0], headers[1:]
-        self.status = st_header.split(':', 1)[1].lstrip()
-        for header in other_headers:
-            key, value = header.split(':', 1)
-            value = value.lstrip()
-            self.headers.append((key, value))
-
-    def flush(self):
-        pass
-
-    def output(self):
-        # called by MoinMoin.server.server_wsgi
-        return self.stdout.getvalue()
-
-
--- a/MoinMoin/script/__init__.py	Tue Sep 23 00:44:45 2008 +0200
+++ b/MoinMoin/script/__init__.py	Tue Sep 23 23:02:50 2008 +0200
@@ -173,11 +173,11 @@
 
     def init_request(self):
         """ create request """
-        from MoinMoin.request import request_cli
+        from MoinMoin.web.contexts import ScriptContext
         if self.options.wiki_url:
-            self.request = request_cli.Request(self.options.wiki_url, self.options.page)
+            self.request = ScriptContext(self.options.wiki_url, self.options.page)
         else:
-            self.request = request_cli.Request(pagename=self.options.page)
+            self.request = ScriptContext(pagename=self.options.page)
 
     def mainloop(self):
         # Insert config dir or the current directory to the start of the path.
--- a/MoinMoin/script/cli/show.py	Tue Sep 23 00:44:45 2008 +0200
+++ b/MoinMoin/script/cli/show.py	Tue Sep 23 23:02:50 2008 +0200
@@ -7,6 +7,7 @@
 """
 
 from MoinMoin.script import MoinScript
+from MoinMoin.wsgiapp import run
 
 class PluginScript(MoinScript):
     """\
@@ -27,4 +28,4 @@
 
     def mainloop(self):
         self.init_request()
-        self.request.run()
+        run(self.request)
--- a/MoinMoin/script/server/standalone.py	Tue Sep 23 00:44:45 2008 +0200
+++ b/MoinMoin/script/server/standalone.py	Tue Sep 23 23:02:50 2008 +0200
@@ -11,8 +11,8 @@
 import signal
 
 from MoinMoin.script import MoinScript
-from MoinMoin.server.server_standalone import StandaloneConfig, run
-from MoinMoin.server.daemon import Daemon
+from MoinMoin.util.daemon import Daemon
+from MoinMoin.web.serving import run_server
 
 class PluginScript(MoinScript):
     def __init__(self, argv, def_values):
@@ -38,18 +38,6 @@
             help="Set the ip to listen on. Use \"\" for all interfaces. Default: localhost"
         )
         self.parser.add_option(
-            "--serverClass", dest="serverClass",
-            help="Set the server model to use. Choices: ThreadPool, serverClass, Forking, Simple. Default: ThreadPool"
-        )
-        self.parser.add_option(
-            "--threadLimit", dest="threadLimit", type="int",
-            help="Set the maximum number of threads to use. Default: 10"
-        )
-        self.parser.add_option(
-            "--requestQueueSize", dest="requestQueueSize", type="int",
-            help="Set the size of the request queue. Default: 50"
-        )
-        self.parser.add_option(
             "--start", dest="start", action="store_true",
             help="Start server in background."
         )
@@ -61,23 +49,16 @@
             "--pidfile", dest="pidfile",
             help="Set file to store pid of moin daemon in. Default: moin.pid"
         )
+        self.parser.add_option(
+            "--debug", dest="debug", action="store_true",
+            help="Enable debug mode of server (show tracebacks)"
+        )
 
     def mainloop(self):
         # we don't expect non-option arguments
         if self.args:
             self.parser.error("incorrect number of arguments")
 
-        thread_choices = ["ThreadPool", "Threading", "Forking", "Simple"]
-        serverClass = "ThreadPool"
-        if self.options.serverClass:
-            thread_choices2 = [x.upper() for x in thread_choices]
-            thread_choice = self.options.serverClass.upper()
-            try:
-                serverClass_index = thread_choices2.index(thread_choice)
-            except ValueError:
-                self.parser.error("invalid serverClass type")
-            serverClass = thread_choices[serverClass_index]
-
         pidfile = "moin.pid"
         if self.options.pidfile:
             pidfile = self.options.pidfile
@@ -106,6 +87,13 @@
                     # some other import went wrong
                     raise
 
+            # intialize some defaults if missing
+            for option in ('docs', 'user', 'group', 'port', 'interface', 'debug'):
+                if not hasattr(Config, option):
+                    value = getattr(DefaultConfig, option)
+                    setattr(Config, option, value)
+
+            # override with cmdline options
             if self.options.docs:
                 Config.docs = self.options.docs
             if self.options.user:
@@ -116,26 +104,29 @@
                 Config.port = self.options.port
             if self.options.interface:
                 Config.interface = self.options.interface
-            Config.serverClass = serverClass + 'Server'
-            if self.options.threadLimit:
-                Config.threadLimit = self.options.threadLimit
-            if self.options.requestQueueSize:
-                Config.requestQueueSize = self.options.requestQueueSize
+            if self.options.debug:
+                Config.debug = True
+
+            if not hasattr(Config, 'docs'):
+                docs = os.path.join('wiki', 'htdocs')
+                if not os.path.exists(docs):
+                    docs = "/usr/share/moin/htdocs"
+                Config.docs = docs
 
             if self.options.start:
-                daemon = Daemon('moin', pidfile, run, Config)
+                daemon = Daemon('moin', pidfile, run_server, Config)
                 daemon.do_start()
             else:
-                run(Config)
+                run_server(Config.interface, Config.port, Config.docs,
+                           use_debugger=Config.debug, user=Config.user,
+                           group=Config.group)
 
-class DefaultConfig(StandaloneConfig):
+class DefaultConfig:
     docs = os.path.join('wiki', 'htdocs')
     if not os.path.exists(docs):
         docs = "/usr/share/moin/htdocs"
-    user = ''
-    group = ''
+    user = None
+    group = None
     port = 8080
     interface = 'localhost'
-    serverClass = 'ThreadPoolServer'
-    threadLimit = 10
-    requestQueueSize = 50
+    debug = False
--- a/MoinMoin/search/builtin.py	Tue Sep 23 00:44:45 2008 +0200
+++ b/MoinMoin/search/builtin.py	Tue Sep 23 23:02:50 2008 +0200
@@ -401,9 +401,9 @@
 
         @param request: current request
         """
-        from MoinMoin.request.request_cli import Request
+        from MoinMoin.web.contexts import ScriptContext
         from MoinMoin.security import Permissions
-        request = Request(request.url)
+        request = ScriptContext(request.url)
         class SecurityPolicy(Permissions):
             def read(self, *args, **kw):
                 return True
--- a/MoinMoin/server/__init__.py	Tue Sep 23 00:44:45 2008 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,98 +0,0 @@
-# -*- coding: iso-8859-1 -*-
-"""
-    MoinMoin server package
-
-    Supply common server utilities.
-
-    @copyright: 2004 Nir Soffer
-    @license: GNU GPL, see COPYING for details.
-"""
-
-import os
-from StringIO import StringIO
-
-from MoinMoin import log
-logging = log.getLogger(__name__)
-
-from MoinMoin import config
-
-
-def switchUID(uid, gid):
-    """ Switch identity to safe user and group
-
-    Does not support Windows, because the necessary calls are not available.
-    TODO: can we use win32api calls to achieve the same effect on Windows?
-
-    Raise RuntimeError if can't switch or trying to switch to root.
-    """
-    if uid == 0 or gid == 0:
-        # We will not run as root. If you like to run a web
-        # server as root, then hack this code.
-        raise RuntimeError('will not run as root!')
-
-    try:
-        os.setgid(gid)
-        os.setuid(uid)
-    except (OSError, AttributeError):
-        # Either we can't switch, or we are on windows, which does not have
-        # those calls.
-        raise RuntimeError("can't change uid/gid to %s/%s" %
-                           (uid, gid))
-    logging.info("Running as uid/gid %d/%d" % (uid, gid))
-
-
-class Config:
-    """ Base class for server configuration
-
-    When you create a server, you should run it with a Config
-    instance. Sub class to define the default values.
-
-    This class does all error checking needed for config values, and will
-    raise a RuntimeError on any fatal error.
-    """
-    # some defaults that should be common for all servers:
-    url_prefix_static = config.url_prefix_static
-    docs = None # document root (if supported)
-    user = None # user we shall use for running (if supported)
-    group = None # group ...
-    port = None # tcp port number (if supported)
-
-    def __init__(self):
-        """ Validate and post process configuration values
-
-        Will raise RuntimeError for any wrong config value.
-        """
-        # Check that docs path is accessible
-        if self.docs:
-            self.docs = os.path.normpath(os.path.abspath(self.docs))
-            if not os.access(self.docs, os.F_OK | os.R_OK | os.X_OK):
-                raise RuntimeError("Can't access docs directory '%s'. Check docs "
-                                   "setting and permissions." % self.docs)
-
-        # Don't check uid and gid on windows, those calls are not available.
-        if os.name == 'nt':
-            self.uid = self.gid = 0
-            return
-
-        self.uid = os.getuid()
-        self.gid = os.getgid()
-
-        # If serving privileged port, we must run as root to bind the port.
-        # we will give up root privileges later
-        if self.port and self.port < 1024 and self.uid != 0:
-            raise RuntimeError('Must run as root to serve port number under 1024. '
-                               'Run as root or change port setting.')
-
-        if self.user and self.group and self.uid == 0:
-            # If we run as root to serve privileged port, we change user and group
-            # to a safe setting. Get the uid and gid now, switch later.
-            import pwd, grp
-            try:
-                self.uid = pwd.getpwnam(self.user)[2]
-            except KeyError:
-                raise RuntimeError("Unknown user: '%s', check user setting" % self.user)
-            try:
-                self.gid = grp.getgrnam(self.group)[2]
-            except KeyError:
-                raise RuntimeError("Unknown group: '%s', check group setting" % self.group)
-
--- a/MoinMoin/server/daemon.py	Tue Sep 23 00:44:45 2008 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,266 +0,0 @@
-"""
-Daemon - daemon script and controller
-
-This module is based on twisted.scripts.twistd, modified by Nir Soffer
-to work with non Twisted code.
-
-The Daemon class, represents a background process using a pid
-file. When you create an instance, the process may be running or not.
-After creating an instance, you can call one of its do_xxx() methods.
-
-The DaemonScript class is a script that control a daemon, with a
-functionality similar to apachectl. To create a daemon script, create an
-instacne and call its run() method.
-
-Typical usage::
-
-    # Daemon script
-    import daemon
-    import myserver
-    script = daemon.DaemonScript('myserver', 'myserver.pid',
-                                 myserver.run, myserver.Config)
-    script.run()
-
-
-Copyright (c) 2005 Nir Soffer <nirs@freeshell.org>
-
-Twisted, the Framework of Your Internet
-Copyright (c) 2001-2004 Twisted Matrix Laboratories.
-
-Permission is hereby granted, free of charge, to any person obtaining
-a copy of this software and associated documentation files (the
-"Software"), to deal in the Software without restriction, including
-without limitation the rights to use, copy, modify, merge, publish,
-distribute, sublicense, and/or sell copies of the Software, and to
-permit persons to whom the Software is furnished to do so, subject to
-the following conditions:
-
-The above copyright notice and this permission notice shall be
-included in all copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
-LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
-OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
-WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-"""
-
-import sys, os, errno, signal, time
-
-
-class Error(Exception):
-    """ Daemon error """
-
-
-class Daemon:
-    """ A background process
-
-    Represent a background process, which may be running or not. The
-    process can be started, stopped, restarted or killed.
-    """
-    commandPrefix = 'do_'
-
-    def __init__(self, name, pidfile, function, *args, **kw):
-        """ Create a daemon
-
-        @param name: name of the process
-        @param pidfile: pid filename
-        @param function: the server main function, will block until the
-            server is done.
-        @param args: arguments to pass to function
-        @param kw: keyword arguments to pass to function
-        """
-        self.name = name
-        self.function = function
-        self.args = args
-        self.kw = kw
-        self.pidFile = os.path.abspath(pidfile)
-
-    # --------------------------------------------------------------------
-    # Commands
-
-    def do_start(self):
-        """ Start the daemon process
-
-        Start will daemonize then block until the server is killed and
-        then cleanup the pid file on the way out.
-        """
-        running, pid = self.status()
-        if running:
-            raise Error("another application is running with pid %s "
-                        "(try restart)" % pid)
-        self.daemonize()
-        self.writePID()
-        try:
-            self.function(*self.args, **self.kw)
-        finally:
-            self.removePID()
-
-    def do_stop(self):
-        """ Stop the daemon process
-
-        Terminate or raise an error we can't handle here. On success,
-        the pid file will be cleaned by the terminated process.
-        """
-        running, pid = self.status()
-        if not running:
-            return self.log("%s is not running" % self.name)
-        os.kill(pid, signal.SIGINT)
-
-    def do_kill(self):
-        """ Kill the daemon process
-
-        Kill or raise an error which we can't handle here. Clean the
-        pid file for the killed process.
-        """
-        running, pid = self.status()
-        if not running:
-            return self.log("%s is not running" % self.name)
-        os.kill(pid, signal.SIGKILL)
-        self.removePID()
-
-    def do_restart(self):
-        """ stop, wait until pid file gone and start again """
-        running, pid = self.status()
-        if not running:
-            self.log("%s is not running, trying to start" % self.name)
-        else:
-            self.do_stop()
-        timeoutSeconds = 2.0
-        start = time.time()
-        while time.time() - start < timeoutSeconds:
-            running, pid = self.status()
-            if not running:
-                break
-            time.sleep(0.1)
-        else:
-            raise Error("could not start after %s seconds" % timeoutSeconds)
-        self.do_start()
-
-    # -------------------------------------------------------------------
-    # Private
-
-    def status(self):
-        """ Return status tuple (running, pid)
-
-        See http://www.erlenstar.demon.co.uk/unix/faq_2.html#SEC18
-        """
-        running = False
-        pid = self.readPID()
-        if pid is not None:
-            try:
-                os.kill(pid, 0)
-                running = True
-            except OSError, err:
-                if err.errno == errno.ESRCH:
-                    # No such process or security enhancements are causing
-                    # the system to deny its existence.
-                    self.log("removing stale pid file: %s" % self.pidFile)
-                    self.removePID()
-                else:
-                    raise
-        return running, pid
-
-    def readPID(self):
-        """ Return the pid from the pid file
-
-        If there is no pid file, return None. If pid file is corrupted,
-        remove it. If its not readable, raise.
-        """
-        pid = None
-        try:
-            pid = int(file(self.pidFile).read())
-        except IOError, err:
-            if err.errno != errno.ENOENT:
-                raise
-        except ValueError:
-            self.warn("removing corrupted pid file: %s" % self.pidFile)
-            self.removePID()
-        return pid
-
-    def daemonize(self):
-        """ Make the current process a daemon
-
-        See http://www.erlenstar.demon.co.uk/unix/faq_toc.html#TOC16
-        """
-        if os.fork():   # launch child and...
-            os._exit(0) # kill off parent
-        os.setsid()
-        if os.fork():   # launch child and...
-            os._exit(0) # kill off parent again.
-        os.umask(0077)
-        null = os.open('/dev/null', os.O_RDWR)
-        for i in range(3):
-            try:
-                os.dup2(null, i)
-            except OSError, e:
-                if e.errno != errno.EBADF:
-                    raise
-        os.close(null)
-
-    def writePID(self):
-        pid = str(os.getpid())
-        open(self.pidFile, 'wb').write(pid)
-
-    def removePID(self):
-        try:
-            os.remove(self.pidFile)
-        except OSError, err:
-            if err.errno != errno.ENOENT:
-                raise
-
-    def warn(self, message):
-        self.log('warning: %s' % message)
-
-    def log(self, message):
-        """ TODO: does it work after daemonize? """
-        sys.stderr.write(message + '\n')
-
-
-class DaemonScript(Daemon):
-    """ A script controlling a daemon
-
-    TODO: add --pid-dir option?
-    """
-
-    def run(self):
-        """ Check commandline arguments and run a command """
-        args = len(sys.argv)
-        if args == 1:
-            self.usage('nothing to do')
-        elif args > 2:
-            self.usage("too many arguments")
-        try:
-            command = sys.argv[1]
-            func = getattr(self, self.commandPrefix + command)
-            func()
-        except AttributeError:
-            self.usage('unknown command %r' % command)
-        except Exception, why:
-            sys.exit("error: %s" % str(why))
-
-    def usage(self, message):
-        sys.stderr.write('error: %s\n' % message)
-        sys.stderr.write(self.help())
-        sys.exit(1)
-
-    def help(self):
-        return """
-%(name)s - MoinMoin daemon
-
-usage: %(name)s command
-
-commands:
-  start     start the server
-  stop      stop the server
-  restart   stop then start the server
-  kill      kill the server
-
-@copyright: 2004-2005 Thomas Waldmann, Nir Soffer
-@license: GNU GPL, see COPYING for details.
-""" % {
-    'name': self.name,
-}
-
--- a/MoinMoin/server/server_cgi.py	Tue Sep 23 00:44:45 2008 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,63 +0,0 @@
-# -*- coding: iso-8859-1 -*-
-"""
-    MoinMoin - CGI pseudo Server
-
-    This is not really a server, it is just so that CGI stuff (the real
-    server is likely Apache or IIS or some other std. CGI server) looks
-    similar to what we have for Twisted and standalone server.
-
-    Minimal usage:
-
-        from MoinMoin.server.server_cgi import CgiConfig, run
-
-        class Config(CgiConfig):
-            pass
-
-        run(Config)
-
-    See more options in CgiConfig class.
-
-    @copyright: 2006 MoinMoin:ThomasWaldmann
-    @license: GNU GPL, see COPYING for details.
-"""
-
-from MoinMoin.server import Config
-from MoinMoin.request import request_cgi
-
-# Server globals
-config = None
-
-# ------------------------------------------------------------------------
-# Public interface
-
-class CgiConfig(Config):
-    """ CGI default config """
-    name = 'moin'
-    properties = {}
-
-    # Development options
-    hotshotProfile = None # e.g. "moin.prof"
-
-
-def run(configClass):
-    """ Create and run a Cgi Request
-
-    See CgiConfig for available options
-
-    @param configClass: config class
-    """
-
-    config = configClass()
-
-    if config.hotshotProfile:
-        import hotshot
-        config.hotshotProfile = hotshot.Profile(config.hotshotProfile)
-        config.hotshotProfile.start()
-
-    request = request_cgi.Request(properties=config.properties)
-    request.run()
-
-    if config.hotshotProfile:
-        config.hotshotProfile.close()
-
-
--- a/MoinMoin/server/server_fastcgi.py	Tue Sep 23 00:44:45 2008 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,59 +0,0 @@
-# -*- coding: iso-8859-1 -*-
-"""
-    MoinMoin.server.server_fastcgi
-
-    This is not really a server, it is just so that fastcgi stuff
-    (the real server is likely Apache2) fits the model we have for
-    Twisted and standalone server.
-
-    Minimal usage:
-
-        from MoinMoin.server.server_fastcgi import FastCgiConfig, run
-
-        class Config(FastCgiConfig):
-            pass
-
-        run(Config)
-
-    See more options in FastCgiConfig class.
-
-    @copyright: 2007 MoinMoin:ThomasWaldmann
-
-    @license: GNU GPL, see COPYING for details.
-"""
-
-from MoinMoin.server import Config
-from MoinMoin.request import request_fcgi
-from MoinMoin.support import thfcgi
-
-# Set threads flag, so other code can use proper locking.
-from MoinMoin import config
-config.use_threads = 1
-del config
-
-class FastCgiConfig(Config):
-    """ Set up default server """
-    properties = {}
-    # properties = {'script_name': '/'}
-
-    # how many requests shall be handled by a moin fcgi process before it dies,
-    # -1 mean "unlimited lifetime":
-    max_requests = -1
-
-    # how many threads to use (1 means use only main program, non-threaded)
-    max_threads = 5
-
-    # backlog, use in socket.listen(backlog) call
-    backlog = 5
-
-    # default port
-    port = None
-
-def run(ConfigClass=FastCgiConfig):
-    config = ConfigClass()
-
-    handle_request = lambda req, env, form, properties=config.properties: \
-                         request_fcgi.Request(req, env, form, properties=properties).run()
-    fcg = thfcgi.FCGI(handle_request, port=config.port, max_requests=config.max_requests, backlog=config.backlog, max_threads=config.max_threads)
-    fcg.run()
-
--- a/MoinMoin/server/server_modpython.py	Tue Sep 23 00:44:45 2008 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,46 +0,0 @@
-# -*- coding: iso-8859-1 -*-
-"""
-    MoinMoin.server.server_modpython
-
-    This is not really a server, it is just so that modpython stuff
-    (the real server is likely Apache2) fits the model we have for
-    Twisted and standalone server.
-
-    Minimal usage:
-
-        from MoinMoin.server.server_modpython import CgiConfig, run
-
-        class Config(CgiConfig):
-            pass
-
-        run(Config)
-
-    See more options in CgiConfig class.
-
-    @copyright: 2006 MoinMoin:ThomasWaldmann
-    @license: GNU GPL, see COPYING for details.
-"""
-
-from MoinMoin.server import Config
-from MoinMoin.request import request_modpython
-
-# Set threads flag, so other code can use proper locking.
-# TODO: It seems that modpy does not use threads, so we don't need to
-# set it here. Do we have another method to check this?
-from MoinMoin import config
-config.use_threads = 1
-del config
-
-# Server globals
-config = None
-
-class ModpythonConfig(Config):
-    """ Set up default server """
-    properties = {}
-
-
-def modpythonHandler(request, ConfigClass=ModpythonConfig):
-    config = ConfigClass()
-    moinreq = request_modpython.Request(request, config.properties)
-    return moinreq.run(request)
-
--- a/MoinMoin/server/server_standalone.py	Tue Sep 23 00:44:45 2008 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,637 +0,0 @@
-# -*- coding: iso-8859-1 -*-
-"""
-    MoinMoin - Stand-alone HTTP Server
-
-    This is a simple, fast and very easy to install server. Its
-    recommended for personal wikis or public wikis with little load.
-
-    It is not well tested in public wikis with heavy load. In these case
-    you might want to use twisted, fast cgi or mod python, or if you
-    can't use those, cgi.
-
-    Minimal usage:
-
-        from MoinMoin.server.server_standalone import StandaloneConfig, run
-
-        class Config(StandaloneConfig):
-            docs = '/usr/share/moin/wiki/htdocs'
-            user = 'www-data'
-            group = 'www-data'
-
-        run(Config)
-
-    See more options in StandaloneConfig class.
-
-    For security, the server will not run as root. If you try to run it
-    as root, it will run as the user and group in the config. If you run
-    it as a normal user, it will run with your regular user and group.
-
-    Significant contributions to this module by R. Church <rc@ghostbitch.org>
-
-    @copyright: 2001-2004 MoinMoin:JuergenHermann,
-                2005 MoinMoin:AlexanderSchremmer,
-                2005 MoinMoin:NirSoffer
-    @license: GNU GPL, see COPYING for details.
-"""
-
-import os, sys, time, socket, errno, shutil
-import BaseHTTPServer, SimpleHTTPServer, SocketServer
-
-from MoinMoin import log
-logging = log.getLogger(__name__)
-
-from MoinMoin import version, wikiutil
-from MoinMoin.server import Config, switchUID
-from MoinMoin.request import request_standalone
-from MoinMoin.util import timefuncs
-
-# Server globals
-httpd = None
-config = None
-
-
-class SimpleServer(BaseHTTPServer.HTTPServer):
-    """ Simplest server, serving one request after another
-
-    This server is good for personal wiki, or when lowest memory
-    footprint is needed.
-    """
-    use_threads = False
-
-    def __init__(self, config):
-        self.htdocs = config.docs
-        self.request_queue_size = config.requestQueueSize
-        self._abort = 0
-        address = (config.interface, config.port)
-        BaseHTTPServer.HTTPServer.__init__(self, address, MoinRequestHandler)
-
-    def server_activate(self):
-        BaseHTTPServer.HTTPServer.server_activate(self)
-        logging.info("Serving on %s:%d" % self.server_address)
-
-    def serve_forever(self):
-        """Handle one request at a time until we die """
-        while not self._abort:
-            self.handle_request()
-
-    def die(self):
-        """Abort this server instance's serving loop """
-        # Close hotshot profiler
-        if config.hotshotProfile:
-            config.hotshotProfile.close()
-
-        if config.cProfileProfile and config.cProfile:
-            config.cProfile.dump_stats(config.cProfileProfile)
-
-        # Set abort flag, then make request to wake the server
-        self._abort = 1
-        try:
-            import httplib
-            addr = self.server_address
-            if not addr[0]:
-                addr = ("localhost", addr[1])
-            req = httplib.HTTP('%s:%d' % addr)
-            req.connect()
-            req.putrequest('DIE', '/')
-            req.endheaders()
-            del req
-        except socket.error, err:
-            # Ignore certain errors
-            if err.args[0] not in [errno.EADDRNOTAVAIL, ]:
-                raise
-
-
-class ThreadingServer(SimpleServer):
-    """ Serve each request in a new thread
-
-    This server is used since release 1.3 and seems to work nice with
-    little load.
-
-    From release 1.3.5 there is a thread limit, that should help to
-    limit the load on the server.
-    """
-    use_threads = True
-
-    def __init__(self, config):
-        self.thread_limit = config.threadLimit
-        from threading import Condition
-        self.lock = Condition()
-        SimpleServer.__init__(self, config)
-
-    def process_request(self, request, client_address):
-        """ Start a new thread to process the request
-
-        If the thread limit has been reached, wait on the lock. The
-        next thread will notify when finished.
-        """
-        from threading import Thread, activeCount
-        self.lock.acquire()
-        try:
-            if activeCount() > self.thread_limit:
-                self.lock.wait()
-            if self._abort:
-                return
-            t = Thread(target=self.process_request_thread,
-                       args=(request, client_address))
-            t.setDaemon(True)
-            t.start()
-        finally:
-            self.lock.release()
-
-    def process_request_thread(self, request, client_address):
-        """ Called for each request on a new thread
-
-        Notify the main thread on the end of each request.
-        """
-        try:
-            self.finish_request(request, client_address)
-        except:
-            self.handle_error(request, client_address)
-        self.close_request(request)
-        self.lock.acquire()
-        try:
-            # Main thread might be waiting
-            self.lock.notify()
-        finally:
-            self.lock.release()
-
-
-class ThreadPoolServer(SimpleServer):
-    """ Threading server using a pool of threads
-
-    This is a new experimental server, using a pool of threads instead
-    of creating new thread for each request. This is similar to Apache
-    worker mpm, with a simpler constant thread pool.
-
-    This server is 5 times faster than ThreadingServer for static
-    files, and about the same for wiki pages.
-    """
-    use_threads = True
-
-    def __init__(self, config):
-        self.queue = []
-        # The size of the queue need more testing
-        self.queueSize = config.threadLimit * 2
-        self.poolSize = config.threadLimit
-        from threading import Condition
-        self.lock = Condition()
-        SimpleServer.__init__(self, config)
-
-    def serve_forever(self):
-        """ Create a thread pool then invoke base class method """
-        from threading import Thread
-        for dummy in range(self.poolSize):
-            t = Thread(target=self.serve_forever_thread)
-            t.setDaemon(True)
-            t.start()
-        SimpleServer.serve_forever(self)
-
-    def process_request(self, request, client_address):
-        """ Called for each request
-
-        Insert the request into the queue. If the queue is full, wait
-        until one of the request threads pop a request. During the wait,
-        new connections might be dropped.
-        """
-        self.lock.acquire()
-        try:
-            if len(self.queue) >= self.queueSize:
-                self.lock.wait()
-            if self._abort:
-                return
-            self.queue.insert(0, (request, client_address))
-            self.lock.notify()
-        finally:
-            self.lock.release()
-
-    def serve_forever_thread(self):
-        """ The main loop of request threads
-
-        Pop a request from the queue and process it.
-        """
-        while not self._abort:
-            request, client_address = self.pop_request()
-            try:
-                self.finish_request(request, client_address)
-            except:
-                self.handle_error(request, client_address)
-            self.close_request(request)
-
-    def pop_request(self):
-        """ Pop a request from the queue
-
-        If the queue is empty, wait for notification. If the queue was
-        full, notify the main thread which may be waiting.
-        """
-        self.lock.acquire()
-        try:
-            while not self._abort:
-                try:
-                    item = self.queue.pop()
-                    if len(self.queue) == self.queueSize - 1:
-                        # Queue was full - main thread might be waiting
-                        self.lock.notify()
-                    return item
-                except IndexError:
-                    self.lock.wait()
-        finally:
-            self.lock.release()
-        sys.exit()
-
-    def die(self):
-        """ Wake all threads then invoke base class die
-
-        Threads should exist when _abort is True.
-        """
-        self._abort = True
-        self.wake_all_threads()
-        time.sleep(0.1)
-        SimpleServer.die(self)
-
-    def wake_all_threads(self):
-        self.lock.acquire()
-        try:
-            self.lock.notifyAll()
-        finally:
-            self.lock.release()
-
-
-class ForkingServer(SocketServer.ForkingMixIn, SimpleServer):
-    """ Serve each request in a new process
-
-    This is new untested server, first tests show rather pathetic cgi
-    like performance. No data is cached between requests.
-
-    The mixin has its own process limit.
-    """
-    max_children = 10
-
-
-class MoinRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
-
-    bufferSize = 8 * 1024 # used to serve static files
-    staticExpire = 365 * 24 * 3600 # 1 year expiry for static files
-
-    def __init__(self, request, client_address, server):
-        self.server_version = "MoinMoin %s %s %s" % (version.release,
-                                                     version.revision,
-                                                     server.__class__.__name__)
-        self.expires = 0
-        SimpleHTTPServer.SimpleHTTPRequestHandler.__init__(self, request,
-            client_address, server)
-
-    def log_message(self, format, *args):
-        logging.info("%s %s" % (self.address_string(), format % args))
-
-    # -------------------------------------------------------------------
-    # do_METHOD dispatchers - called for each request
-
-    def do_DIE(self):
-        if self.server._abort:
-            self.log_error("Shutting down")
-
-    def do_ALL(self):
-        """ Handle requests (request type GET/HEAD/POST is in self.command)
-
-        Separate between wiki pages and css and image url by similar
-        system as cgi and twisted, the url_prefix_static prefix.
-        """
-        prefix = config.url_prefix_static
-        if self.path.startswith(prefix + '/'):
-            self.path = self.path[len(prefix):]
-            self.serve_static_file()
-        elif self.path in ['/favicon.ico', '/robots.txt']:
-            self.serve_static_file()
-        else:
-            self.serve_moin()
-
-    do_POST = do_ALL
-    do_GET = do_ALL
-    do_HEAD = do_ALL
-
-    # -------------------------------------------------------------------
-    # Serve methods
-
-    def serve_static_file(self):
-        """ Serve files from the htdocs directory """
-        self.expires = self.staticExpire
-        path = self.path.split("?", 1)
-        if len(path) > 1:
-            self.path = path[0] # XXX ?params
-
-        try:
-            fn = getattr(SimpleHTTPServer.SimpleHTTPRequestHandler, 'do_' + self.command)
-            fn(self)
-        except socket.error, err:
-            # Ignore certain errors
-            if err.args[0] not in [errno.EPIPE, errno.ECONNABORTED]:
-                raise
-
-    def serve_moin(self):
-        """ Serve a request using moin """
-        # don't make an Expires header for wiki pages
-        self.expires = 0
-
-        try:
-            req = request_standalone.Request(self, properties=config.properties)
-            req.run()
-        except socket.error, err:
-            # Ignore certain errors
-            if err.args[0] not in [errno.EPIPE, errno.ECONNABORTED]:
-                raise
-
-    def translate_path(self, uri):
-        """ Translate a /-separated PATH to the local filename syntax.
-
-        Components that mean special things to the local file system
-        (e.g. drive or directory names) are ignored.
-        """
-        path = wikiutil.url_unquote(uri, want_unicode=False)
-        path = path.replace('\\', '/')
-        words = path.split('/')
-        words = filter(None, words)
-
-        path = self.server.htdocs
-        bad_uri = 0
-        for word in words:
-            drive, word = os.path.splitdrive(word)
-            if drive:
-                bad_uri = 1
-            head, word = os.path.split(word)
-            if word in (os.curdir, os.pardir):
-                bad_uri = 1
-                continue
-            path = os.path.join(path, word)
-
-        if bad_uri:
-            self.log_error("Detected bad request URI '%s', translated to '%s'"
-                           % (uri, path, ))
-        return path
-
-    def end_headers(self):
-        """overload the default end_headers, inserting expires header"""
-        if self.expires:
-            now = time.time()
-            expires = now + self.expires
-            self.send_header('Expires', timefuncs.formathttpdate(expires))
-        SimpleHTTPServer.SimpleHTTPRequestHandler.end_headers(self)
-
-    def copyfile(self, source, outputfile):
-        """Copy all data between two file objects.
-
-        Modify the base class method to change the buffer size. Test
-        shows that for the typical static files we serve, 8K buffer is
-        faster than the default 16K buffer.
-        """
-        shutil.copyfileobj(source, outputfile, length=self.bufferSize)
-
-    def address_string(self):
-        """We don't want to do reverse DNS lookups, just return IP address."""
-        return self.client_address[0]
-
-
-try:
-    from tlslite.api import TLSSocketServerMixIn, X509, X509CertChain, SessionCache, parsePEMKey
-    from tlslite.TLSConnection import TLSConnection
-except ImportError:
-    pass
-else:
-    class SecureRequestRedirect(BaseHTTPServer.BaseHTTPRequestHandler):
-        def handle(self):
-            self.close_connection = 1
-            try:
-                self.raw_requestline = self.rfile.readline()
-            except socket.error:
-                return
-            if self.parse_request():
-                host = self.headers.get('Host', socket.gethostname())
-                path = self.path
-            else:
-                host = '%s:%s' % (socket.gethostname(),
-                        self.request.getsockname()[1])
-                path = '/'
-
-            self.requestline = 'ERROR: Redirecting to https://%s%s' % (host, path)
-            self.request_version = 'HTTP/1.1'
-            self.command = 'GET'
-            self.send_response(301, 'Document Moved')
-            self.send_header('Date', self.date_time_string())
-            self.send_header('Location', 'https://%s%s' % (host, path))
-            self.send_header('Connection', 'close')
-            self.send_header('Content-Length', '0')
-            self.wfile.write('\r\n')
-
-    class SecureThreadPoolServer(TLSSocketServerMixIn, ThreadPoolServer):
-        def __init__(self, config):
-            ThreadPoolServer.__init__(self, config)
-
-            cert = open(config.ssl_certificate).read()
-            x509 = X509()
-            x509.parse(cert)
-            self.certChain = X509CertChain([x509])
-
-            priv = open(config.ssl_privkey).read()
-            self.privateKey = parsePEMKey(priv, private=True)
-
-            self.sessionCache = SessionCache()
-
-        def finish_request(self, sock, client_address):
-            # Peek into the packet, if it starts with GET or POS(T) then
-            # redirect, otherwise let TLSLite handle the connection.
-            peek = sock.recv(3, socket.MSG_PEEK).lower()
-            if peek == 'get' or peek == 'pos':
-                SecureRequestRedirect(sock, client_address, self)
-                return
-            tls_connection = TLSConnection(sock)
-            if self.handshake(tls_connection):
-                self.RequestHandlerClass(tls_connection, client_address, self)
-            else:
-                # This will probably fail because the TLSConnection has
-                # already written SSL stuff to the socket. But not sure what
-                # else we should do.
-                SecureRequestRedirect(sock, client_address, self)
-
-        def handshake(self, tls_connection):
-            try:
-                tls_connection.handshakeServer(certChain=self.certChain,
-                                               privateKey=self.privateKey,
-                                               sessionCache=self.sessionCache)
-                tls_connection.ignoreAbruptClose = True
-                return True
-            except:
-                return False
-
-
-def memoryProfileDecorator(func, profile):
-    """ Return a profiled function """
-    def profiledFunction(*args, **kw):
-        profile.addRequest()
-        return func(*args, **kw)
-    return profiledFunction
-
-
-def hotshotProfileDecorator(func, profile):
-    """ Return a profiled function """
-    profile.moin_requests_done = 0
-    def profiledFunction(*args, **kw):
-        profile.moin_requests_done += 1
-        if profile.moin_requests_done == 1:
-            # Don't profile first request, its not interesting
-            return func(*args, **kw)
-        return profile.runcall(func, *args, **kw)
-
-    return profiledFunction
-
-
-def quit(signo, stackframe):
-    """ Signal handler for aborting signals """
-    global httpd, config
-    logging.info("Thanks for using MoinMoin!")
-
-    fname = config.pycallgraph_output
-    if fname:
-        import pycallgraph
-        if fname.endswith('.png'):
-            pycallgraph.make_dot_graph(fname)
-        elif fname.endswith('.dot'):
-            pycallgraph.save_dot(fname)
-
-    if httpd:
-        httpd.die()
-
-
-def registerSignalHandlers(func):
-    """ Register signal handlers on platforms that support it """
-    try:
-        import signal
-        signal.signal(signal.SIGABRT, func)
-        signal.signal(signal.SIGINT, func)
-        signal.signal(signal.SIGTERM, func)
-    except ImportError:
-        pass
-
-
-def makeServer(config):
-    """ Create a new server, based on the the platform capabilities
-
-    Try to create the server class specified in the config. If threads
-    are not available, fallback to ForkingServer. If fork is not
-    available, fallback to a SimpleServer.
-    """
-    serverClass = globals()[config.serverClass]
-    if serverClass.use_threads:
-        try:
-            import threading
-        except ImportError:
-            serverClass = ForkingServer
-    if serverClass is ForkingServer and not hasattr(os, "fork"):
-        serverClass = SimpleServer
-    if serverClass.__name__ != config.serverClass:
-        logging.error('%s is not available on this platform, falling back '
-                      'to %s\n' % (config.serverClass, serverClass.__name__))
-
-    from MoinMoin import config as _config
-    _config.use_threads = serverClass.use_threads
-    return serverClass(config)
-
-# ------------------------------------------------------------------------
-# Public interface
-
-class StandaloneConfig(Config):
-    """ Standalone server default config """
-    name = 'moin'
-    properties = {}
-    docs = '/usr/share/moin/htdocs'
-    user = 'www-data'
-    group = 'www-data'
-    port = 8000
-    interface = 'localhost'
-
-    # Advanced options
-    serverClass = 'ThreadPoolServer'
-    threadLimit = 10
-    # The size of the listen backlog. Twisted uses a default of 50.
-    # Tests on Mac OS X show many failed request with backlog of 5 or 10.
-    requestQueueSize = 50
-
-    # Development options
-    memoryProfile = None
-    hotshotProfile = None
-    cProfile = None # internal use only
-    cProfileProfile = None
-    pycallgraph_output = None
-
-def cProfileDecorator(func, profile):
-    """ Return a profiled function """
-    profile.moin_requests_done = 0
-    def profiledFunction(*args, **kw):
-        profile.moin_requests_done += 1
-        if profile.moin_requests_done == 1:
-            # Don't profile first request, it's not interesting
-            return func(*args, **kw)
-        return profile.runcall(func, *args, **kw)
-
-    return profiledFunction
-
-def run(configClass):
-    """ Create and run a moin server
-
-    See StandaloneConfig for available options
-
-    @param configClass: config class
-    """
-    # Run only once!
-    global httpd, config
-    if httpd is not None:
-        raise RuntimeError("You can run only one server per process!")
-
-    config = configClass()
-
-    if config.hotshotProfile and config.cProfileProfile:
-        raise RuntimeError("You cannot run two profilers simultaneously.")
-
-    # Install hotshot profiled serve_moin method. To compare with other
-    # servers, we profile the part that create and run the request.
-    if config.hotshotProfile:
-        import hotshot
-        config.hotshotProfile = hotshot.Profile(config.hotshotProfile)
-        MoinRequestHandler.serve_moin = hotshotProfileDecorator(
-            MoinRequestHandler.serve_moin, config.hotshotProfile)
-
-    if config.cProfileProfile:
-        import cProfile
-        # Create a new cProfile.Profile object using config.cProfileProfile
-        # as the path for the output file.
-        config.cProfile = cProfile.Profile()
-        MoinRequestHandler.serve_moin = cProfileDecorator(
-            MoinRequestHandler.serve_moin, config.cProfile)
-
-    # Install a memory profiled serve_moin method
-    if config.memoryProfile:
-        config.memoryProfile.sample()
-        MoinRequestHandler.serve_moin = memoryProfileDecorator(
-            MoinRequestHandler.serve_moin, config.memoryProfile)
-
-    # initialize pycallgraph, if wanted
-    if config.pycallgraph_output:
-        try:
-            import pycallgraph
-            pycallgraph.settings['include_stdlib'] = False
-            pcg_filter = pycallgraph.GlobbingFilter(exclude=['pycallgraph.*',
-                                                             'unknown.*',
-                                                    ],
-                                                    max_depth=9999)
-            pycallgraph.start_trace(reset=True, filter_func=pcg_filter)
-        except ImportError:
-            config.pycallgraph_output = None
-
-
-    registerSignalHandlers(quit)
-    httpd = makeServer(config)
-
-    # Run as a safe user (posix only)
-    if os.name == 'posix' and os.getuid() == 0:
-        switchUID(config.uid, config.gid)
-
-    httpd.serve_forever()
-
--- a/MoinMoin/server/server_twisted.py	Tue Sep 23 00:44:45 2008 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,283 +0,0 @@
-# -*- coding: iso-8859-1 -*-
-"""
-    MoinMoin.server.server_twisted
-
-    Create standalone twisted based server.
-
-    Minimal usage:
-
-        from MoinMoin.server.server_twisted import TwistedConfig, makeApp
-
-        class Config(TwistedConfig):
-            docs = '/usr/share/moin/wiki/htdocs'
-            user = 'www-data'
-            group = 'www-data'
-
-        application = makeApp(Config)
-
-    Then run this code with twistd -y yourcode.py. See moin_twisted script.
-
-    @copyright: 2004 Thomas Waldmann, Oliver Graf, Nir Soffer
-    @license: GNU GPL, see COPYING for details.
-"""
-
-from twisted.application import internet, service
-from twisted.web import static, server, vhost, resource
-from twisted.internet import threads, reactor
-
-try:
-    from twisted.internet import ssl
-except ImportError:
-    ssl = None
-
-# Enable threads
-from twisted.python import threadable
-threadable.init(1)
-
-# MoinMoin imports
-from MoinMoin.request import request_twisted
-from MoinMoin.server import Config
-
-# Set threads flag, so other code can use proper locking
-from MoinMoin import config
-config.use_threads = True
-del config
-
-# Server globals
-config = None
-
-
-class WikiResource(resource.Resource):
-    """ Wiki resource """
-    isLeaf = 1
-
-    def render(self, request):
-        return server.NOT_DONE_YET
-
-
-class WikiRoot(resource.Resource):
-    """ Wiki root resource """
-
-    def getChild(self, name, request):
-        # Serve images and css from url_prefix_static
-        if request.prepath == [] and name == config.url_prefix_static[1:]:
-            return resource.Resource.getChild(self, name, request)
-
-        # Serve special 'root' files from url_prefix_static
-        elif name in ['favicon.ico', 'robots.txt'] and request.postpath == []:
-            return self.children[config.url_prefix_static[1:]].getChild(name, request)
-
-        # All other through moin
-
-        # TODO: fix profile code to include the request init and ignore
-        # first request. I'm not doing this now since its better done
-        # with the new twisted code waiting in fix branch. --Nir
-        else:
-            if config.memoryProfile:
-                config.memoryProfile.addRequest()
-            req = request_twisted.Request(request, name, reactor, properties=config.properties)
-            if config.hotshotProfile:
-                threads.deferToThread(config.hotshotProfile.runcall, req.run)
-            else:
-                threads.deferToThread(req.run)
-            return WikiResource()
-
-
-class MoinRequest(server.Request):
-    """ MoinMoin request
-
-    Enable passing of file-upload filenames
-    """
-
-    def requestReceived(self, command, path, version):
-        """ Called by channel when all data has been received.
-
-        Override server.Request method for POST requests, to fix file
-        uploads issue.
-        """
-        if command == 'POST':
-            self.requestReceivedPOST(path, version)
-        else:
-            server.Request.requestReceived(self, command, path, version)
-
-    def requestReceivedPOST(self, path, version):
-        """ Handle POST requests
-
-        This is a modified copy of server.Request.requestRecived,
-        modified to use cgi.FieldStorage to handle file uploads
-        correctly.
-
-        Creates an extra member extended_args which also has
-        filenames of file uploads ( FIELDNAME__filename__ ).
-        """
-        import cgi
-
-        self.content.seek(0, 0)
-        self.args = {}
-        self.extended_args = {}
-        self.stack = []
-
-        self.method = 'POST'
-        self.uri = path
-        self.clientproto = version
-        x = self.uri.split('?')
-
-        argstring = ""
-        if len(x) == 1:
-            self.path = self.uri
-        else:
-            if len(x) != 2:
-                from twisted.python import log
-                log.msg("May ignore parts of this invalid URI: %s"
-                        % repr(self.uri))
-            self.path, argstring = x[0], x[1]
-
-        # cache the client and server information, we'll need this later to be
-        # serialized and sent with the request so CGIs will work remotely
-        self.client = self.channel.transport.getPeer()
-        self.host = self.channel.transport.getHost()
-
-        # create dummy env for cgi.FieldStorage
-        env = {
-            'REQUEST_METHOD': self.method,
-            'QUERY_STRING': argstring,
-            }
-        form = cgi.FieldStorage(fp=self.content,
-                                environ=env,
-                                headers=self.received_headers)
-
-        # Argument processing
-
-        args = self.args
-        try:
-            keys = form.keys()
-        except TypeError:
-            pass
-        else:
-            for key in keys:
-                values = form[key]
-                if not isinstance(values, list):
-                    values = [values]
-                fixedResult = []
-                for i in values:
-                    if isinstance(i, cgi.FieldStorage) and i.filename:
-                        fixedResult.append(i.file)
-                        # multiple uploads to same form field are stupid!
-                        args[key + '__filename__'] = i.filename
-                    else:
-                        fixedResult.append(i.value)
-                args[key] = fixedResult
-
-        self.process()
-
-
-class MoinSite(server.Site):
-    """ Moin site """
-    requestFactory = MoinRequest
-
-    def startFactory(self):
-        """ Setup before starting """
-        # Memory profile
-        if config.memoryProfile:
-            config.memoryProfile.sample()
-
-        # hotshot profile
-        if config.hotshotProfile:
-            import hotshot
-            config.hotshotProfile = hotshot.Profile(config.hotshotProfile)
-        server.Site.startFactory(self)
-
-    def stopFactory(self):
-        """ Cleaup before stoping """
-        server.Site.stopFactory(self)
-        if config.hotshotProfile:
-            config.hotshotProfile.close()
-
-
-class TwistedConfig(Config):
-    """ Twisted server default config """
-
-    name = 'mointwisted'
-    properties = {}
-    docs = '/usr/share/moin/htdocs'
-    user = 'www-data'
-    group = 'www-data'
-    port = 8080
-    interfaces = ['']
-    threads = 10
-    timeout = 15 * 60 # 15 minutes
-    logPath_twisted = None # Twisted log file
-    virtualHosts = None
-    memoryProfile = None
-    hotshotProfile = None
-
-    # sslcert = ('/whereever/cert/sitekey.pem', '/whereever/cert/sitecert.pem')
-    sslcert = None
-
-    def __init__(self):
-        Config.__init__(self)
-
-        # Check for '' in interfaces, then ignore other
-        if '' in self.interfaces:
-            self.interfaces = ['']
-
-
-def makeApp(ConfigClass):
-    """ Generate and return an application
-
-    See MoinMoin.server.Config for config options
-
-    @param ConfigClass: config class
-    @rtype: application object
-    @return twisted application, needed by twistd
-    """
-    # Create config instance (raise RuntimeError if config invalid)
-    global config
-    config = ConfigClass()
-
-    # Set number of threads
-    reactor.suggestThreadPoolSize(config.threads)
-
-    # The root of the HTTP hierarchy
-    default = WikiRoot()
-
-    # Here is where img and css and some special files come from
-    default.putChild(config.url_prefix_static[1:], static.File(config.docs))
-
-    # Generate the Site factory
-    # TODO: Maybe we can use WikiRoot instead of this
-    # ----------------------------------------------
-    root = vhost.NameVirtualHost()
-    root.default = default
-    # ----------------------------------------------
-    site = MoinSite(root, logPath=config.logPath_twisted, timeout=config.timeout)
-
-    # Make application
-    application = service.Application("web", uid=config.uid, gid=config.gid)
-    sc = service.IServiceCollection(application)
-
-    # Listen to all interfaces in config.interfaces
-    for entry in config.interfaces:
-        # Add a TCPServer for each interface.
-
-        # This is an hidden experimantal feature: each entry in
-        # interface may contain a port, using 'ip:port'.
-        # Note: the format is subject to change!
-        try:
-            interface, port = entry.split(':', 1)
-        except ValueError:
-            interface, port = entry, config.port
-
-        # Might raise ValueError if not integer.
-        # TODO: check if we can use string port, like 'http'
-        port = int(port)
-
-        if port == 443 and ssl and ssl.supported and config.sslcert:
-            sslContext = ssl.DefaultOpenSSLContextFactory(*config.sslcert)
-            s = internet.SSLServer(port, site, sslContext, interface=interface)
-        else:
-            s = internet.TCPServer(port, site, interface=interface)
-        s.setServiceParent(sc)
-
-    return application
-
--- a/MoinMoin/server/server_wsgi.py	Tue Sep 23 00:44:45 2008 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,39 +0,0 @@
-"""
-    MoinMoin - WSGI application
-
-    Minimal code for using this:
-
-    from MoinMoin.server.server_wsgi import WsgiConfig, moinmoinApp
-
-    class Config(WsgiConfig):
-        pass
-
-    config = Config() # you MUST create an instance
-    # use moinmoinApp here with your WSGI server / gateway
-
-    @copyright: 2005 Anakim Border <akborder@gmail.com>,
-                2007 MoinMoin:ThomasWaldmann
-    @license: GNU GPL, see COPYING for details.
-"""
-
-from MoinMoin.server import Config
-from MoinMoin.request import request_wsgi
-
-class WsgiConfig(Config):
-    """ WSGI default config """
-    pass
-
-
-def moinmoinApp(environ, start_response):
-    request = request_wsgi.Request(environ)
-    request.run()
-    start_response(request.status, request.headers)
-    if request._send_file is not None:
-        # moin wants to send a file (e.g. AttachFile.do_get)
-        def simple_wrapper(fileobj, bufsize):
-            return iter(lambda: fileobj.read(bufsize), '')
-        file_wrapper = environ.get('wsgi.file_wrapper', simple_wrapper)
-        return file_wrapper(request._send_file, request._send_bufsize)
-    else:
-        return [request.output()] # don't we have a filelike there also!?
-
--- a/MoinMoin/session.py	Tue Sep 23 00:44:45 2008 +0200
+++ b/MoinMoin/session.py	Tue Sep 23 23:02:50 2008 +0200
@@ -19,6 +19,7 @@
 from MoinMoin import caching
 from MoinMoin.user import User
 from MoinMoin.util import random_string
+from MoinMoin.web.utils import cookie_date
 import time, random
 
 class SessionData(object):
@@ -312,12 +313,12 @@
         if cfg.cookie_path:
             cookie[cookie_name]['path'] = cfg.cookie_path
         else:
-            path = request.getScriptname()
+            path = request.script_root
             if not path:
                 path = '/'
             cookie[cookie_name]['path'] = path
         # Set expires for older clients
-        cookie[cookie_name]['expires'] = request.httpDate(when=expires, rfc='850')
+        cookie[cookie_name]['expires'] = cookie_date(expires)
         # a secure cookie is not transmitted over unsecure connections:
         if (cfg.cookie_secure or  # True means: force secure cookies
             cfg.cookie_secure is None and request.is_ssl):  # None means: https -> secure cookie
@@ -326,11 +327,13 @@
 
     def _set_cookie(self, request, cookie_string, expires):
         """ Set cookie, raw helper. """
-        lifetime = int(expires - time.time())
-        cookie = self._make_cookie(request, self.cookie_name, cookie_string,
-                                   lifetime, expires)
+        lifetime = expires - time.time()
+        domain = request.cfg.cookie_domain or None
+        path = request.cfg.cookie_path or None
         # Set cookie
-        request.setHttpHeader(cookie)
+        request.set_cookie(self.cookie_name, cookie_string,
+                                    max_age=lifetime, expires=expires,
+                                    path=path, domain=domain)
         # IMPORTANT: Prevent caching of current page and cookie
         request.disableHttpCaching()
 
@@ -341,8 +344,10 @@
 
     def get(self, request):
         session_name = None
-        if request.cookie and self.cookie_name in request.cookie:
-            session_name = request.cookie[self.cookie_name].value
+        if request.cookies and self.cookie_name in request.cookies:
+            session_name = request.cookies[self.cookie_name]
+            if hasattr(session_name, 'value'):
+                session_name = session_name.value
             session_name = ''.join([c for c in session_name
                                     if c in self._SESSION_NAME_CHARS])
             session_name = session_name[:self._SESSION_NAME_LEN]
@@ -351,7 +356,7 @@
 
 
 def _get_anon_session_lifetime(request):
-    if request.cfg.anonymous_session_lifetime:
+    if hasattr(request.cfg, 'anonymous_session_lifetime'):
         return request.cfg.anonymous_session_lifetime * 3600
     return 0
 
@@ -411,12 +416,12 @@
                             if user_obj:
                                 sessiondata.is_stored = True
             else:
-                store = not (not request.cfg.anonymous_session_lifetime)
+                store = hasattr(request.cfg, 'anonymous_session_lifetime')
                 sessiondata.is_stored = store
         else:
             session_name = session_id_handler.generate_new_id(request)
             logging.debug("starting session (new session_name %r)" % session_name)
-            store = not (not request.cfg.anonymous_session_lifetime)
+            store = hasattr(request.cfg, 'anonymous_session_lifetime')
             sessiondata = self.dataclass(request, session_name)
             sessiondata.is_new = True
             sessiondata.is_stored = store
--- a/MoinMoin/stats/hitcounts.py	Tue Sep 23 00:44:45 2008 +0200
+++ b/MoinMoin/stats/hitcounts.py	Tue Sep 23 23:02:50 2008 +0200
@@ -151,7 +151,7 @@
         filterpage = wikiutil.decodeUserInput(params)
 
     if request and request.form and 'page' in request.form:
-        filterpage = request.form['page'][0]
+        filterpage = request.form['page']
 
     days, views, edits = get_data(pagename, request, filterpage)
 
@@ -201,7 +201,7 @@
     # check params
     filterpage = None
     if request and request.form and 'page' in request.form:
-        filterpage = request.form['page'][0]
+        filterpage = request.form['page']
 
     days, views, edits = get_data(pagename, request, filterpage)
 
@@ -252,11 +252,8 @@
         (request.cfg.chart_options['width'], request.cfg.chart_options['height']),
         image, days)
 
-    headers = [
-        "Content-Type: image/gif",
-        "Content-Length: %d" % len(image.getvalue()),
-    ]
-    request.emit_http_headers(headers)
+    request.content_type = 'image/gif'
+    request.content_length = len(image.getvalue())
 
     # copy the image
     image.reset()
--- a/MoinMoin/stats/pagesize.py	Tue Sep 23 00:44:45 2008 +0200
+++ b/MoinMoin/stats/pagesize.py	Tue Sep 23 23:02:50 2008 +0200
@@ -113,11 +113,8 @@
         (request.cfg.chart_options['width'], request.cfg.chart_options['height']),
         image, labels)
 
-    headers = [
-        "Content-Type: image/gif",
-        "Content-Length: %d" % len(image.getvalue()),
-    ]
-    request.emit_http_headers(headers)
+    request.content_type = 'image/gif'
+    request.content_length = len(image.getvalue())
 
     # copy the image
     image.reset()
--- a/MoinMoin/stats/useragents.py	Tue Sep 23 00:44:45 2008 +0200
+++ b/MoinMoin/stats/useragents.py	Tue Sep 23 23:02:50 2008 +0200
@@ -173,11 +173,8 @@
         (request.cfg.chart_options['width'], request.cfg.chart_options['height']),
         image, labels)
 
-    headers = [
-        "Content-Type: image/gif",
-        "Content-Length: %d" % len(image.getvalue()),
-    ]
-    request.emit_http_headers(headers)
+    request.content_type = 'image/gif'
+    request.content_length = len(image.getvalue())
 
     # copy the image
     image.reset()
--- a/MoinMoin/support/cgitb.py	Tue Sep 23 00:44:45 2008 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,619 +0,0 @@
-"""More comprehensive traceback formatting for Python scripts.
-
-To enable this module, do:
-
-    import cgitb; cgitb.enable()
-
-at the top of your script.  The optional arguments to enable() are:
-
-    display     - if true, tracebacks are displayed in the web browser
-    logdir      - if set, tracebacks are written to files in this directory
-    context     - number of lines of source code to show for each stack frame
-    format      - 'text' or 'html' controls the output format
-    viewClass   - sub class of View. Create this if you want to customize the
-                  layout of the traceback.
-    debug       - may be used by viewClass to decide on level of detail
-
-By default, tracebacks are displayed but not saved, the context is 5 lines
-and the output format is 'html' (for backwards compatibility with the
-original use of this module).
-
-Alternatively, if you have caught an exception and want cgitb to display it
-for you, call cgitb.handler().  The optional argument to handler() is a
-3-item tuple (etype, evalue, etb) just like the value of sys.exc_info().
-The default handler displays output as HTML.
-
-
-2005-04-22 Nir Soffer <nirs@freeshell.org>
-
-Rewrite:
- - Refactor html and text functions to View class, HTMLFormatter and
-   TextFormatter. No more duplicate formating code.
- - Layout is done with minimal html and css, in a way it can't be
-   affected by surrounding code.
- - Built to be easy to subclass and modify without duplicating code.
- - Change layout, important details come first.
- - Factor frame analyzing and formatting into separate class.
- - Add debug argument, can be used to change error display e.g. user
-   error view, developer error view.
- - Add viewClass argument, make it easy to customize the traceback view.
- - Easy to customize system details and application details.
-
-The main goal of this rewrite was to have a traceback that can render
-few tracebacks combined. It's needed when you wrap an expection and want
-to print both the traceback up to the wrapper exception, and the
-original traceback. There is no code to support this here, but it's easy
-to add by using your own View sub class.
-"""
-
-__author__ = 'Ka-Ping Yee'
-__version__ = '$Revision: 1.10 $'
-
-import sys, os, pydoc, inspect, linecache, tokenize, keyword
-
-
-def reset():
-    """ Reset the CGI and the browser
-    
-    Return a string that resets the CGI and browser to a known state.
-    TODO: probably some of this is not needed any more.
-    """
-    return """<!--: spam
-Content-Type: text/html
-
-<body><font style="color: white; font-size: 1px"> -->
-<body><font style="color: white; font-size: 1px"> --> -->
-</font> </font> </font> </script> </object> </blockquote> </pre>
-</table> </table> </table> </table> </table> </font> </font> </font>
-"""
-
-__UNDEF__ = [] # a special sentinel object
-
-
-class HiddenObject:
-    def __repr__(self):
-        return "<HIDDEN>"
-HiddenObject = HiddenObject()
-
-class HTMLFormatter:
-    """ Minimal html formatter """
-
-    def attributes(self, attributes=None):
-        if attributes:
-            result = [' %s="%s"' % (k, v) for k, v in attributes.items()]
-            return ''.join(result)
-        return ''
-
-    def tag(self, name, text, attributes=None):
-        return '<%s%s>%s</%s>\n' % (name, self.attributes(attributes), text, name)
-
-    def section(self, text, attributes=None):
-        return self.tag('div', text, attributes)
-
-    def title(self, text, attributes=None):
-        return self.tag('h1', text, attributes)
-
-    def subTitle(self, text, attributes=None):
-        return self.tag('h2', text, attributes)
-
-    def subSubTitle(self, text, attributes=None):
-        return self.tag('h3', text, attributes)
-
-    def paragraph(self, text, attributes=None):
-        return self.tag('p', text, attributes)
-
-    def list(self, items, attributes=None):
-        return self.formatList('ul', items, attributes)
-
-    def orderedList(self, items, attributes=None):
-        return self.formatList('ol', items, attributes)
-
-    def formatList(self, name, items, attributes=None):
-        """ Send list of raw texts or formatted items. """
-        if isinstance(items, (list, tuple)):
-            items = '\n' + ''.join([self.listItem(i) for i in items])
-        return self.tag(name, items, attributes)
-
-    def listItem(self, text, attributes=None):
-        return self.tag('li', text, attributes)
-
-    def link(self, href, text, attributes=None):
-        if attributes is None:
-            attributes = {}
-        attributes['href'] = href
-        return self.tag('a', text, attributes)
-
-    def strong(self, text, attributes=None):
-        return self.tag('strong', text, attributes)
-
-    def em(self, text, attributes=None):
-        return self.tag('em', text, attributes)
-
-    def repr(self, object):
-        return pydoc.html.repr(object)
-
-
-class TextFormatter:
-    """ Plain text formatter """
-
-    def section(self, text, attributes=None):
-        return text
-
-    def title(self, text, attributes=None):
-        lineBellow = '=' * len(text)
-        return '%s\n%s\n\n' % (text, lineBellow)
-
-    def subTitle(self, text, attributes=None):
-        lineBellow = '-' * len(text)
-        return '%s\n%s\n\n' % (text, lineBellow)
-
-    def subSubTitle(self, text, attributes=None):
-        lineBellow = '~' * len(text)
-        return '%s\n%s\n\n' % (text, lineBellow)
-
-    def paragraph(self, text, attributes=None):
-        return text + '\n\n'
-
-    def list(self, items, attributes=None):
-        if isinstance(items, (list, tuple)):
-            items = [' * %s\n' % i for i in items]
-            return ''.join(items) + '\n'
-        return items
-
-    def orderedList(self, items, attributes=None):
-        if isinstance(items, (list, tuple)):
-            result = []
-            for i in range(items):
-                result.append(' %d. %s\n' % (i, items[i]))
-            return ''.join(result) + '\n'
-        return items
-
-    def listItem(self, text, attributes=None):
-        return ' * %s\n' % text
-
-    def link(self, href, text, attributes=None):
-        return '[[%s]]' % text
-
-    def strong(self, text, attributes=None):
-        return text
-
-    def em(self, text, attributes=None):
-        return text
-
-    def repr(self, object):
-        return repr(object)
-
-
-class Frame:
-    """ Analyze and format single frame in a traceback """
-
-    def __init__(self, frame, file, lnum, func, lines, index):
-        self.frame = frame
-        self.file = file
-        self.lnum = lnum
-        self.func = func
-        self.lines = lines
-        self.index = index
-
-    def format(self, formatter):
-        """ Return formatted content """
-        self.formatter = formatter
-        vars, highlight = self.scan()
-        items = [self.formatCall(),
-                 self.formatContext(highlight),
-                 self.formatVariables(vars)]
-        return ''.join(items)
-
-    # -----------------------------------------------------------------
-    # Private - formatting
-
-    def formatCall(self):
-        call = '%s in %s%s' % (self.formatFile(),
-                               self.formatter.strong(self.func),
-                               self.formatArguments(),)
-        return self.formatter.paragraph(call, {'class': 'call'})
-
-    def formatFile(self):
-        """ Return formatted file link """
-        if not self.file:
-            return '?'
-        file = pydoc.html.escape(os.path.abspath(self.file))
-        return self.formatter.link('file://' + file, file)
-
-    def formatArguments(self):
-        """ Return formated arguments list """
-        if self.func == '?':
-            return ''
-
-        def formatValue(value):
-            return '=' + self.formatter.repr(value)
-
-        args, varargs, varkw, locals = inspect.getargvalues(self.frame)
-        return inspect.formatargvalues(args, varargs, varkw, locals,
-                                       formatvalue=formatValue)
-
-    def formatContext(self, highlight):
-        """ Return formatted context, next call highlighted """
-        if self.index is None:
-            return ''
-        context = []
-        i = self.lnum - self.index
-        for line in self.lines:
-            line = '%5d  %s' % (i, pydoc.html.escape(line))
-            attributes = {}
-            if i in highlight:
-                attributes = {'class': 'highlight'}
-            context.append(self.formatter.listItem(line, attributes))
-            i += 1
-        context = '\n' + ''.join(context) + '\n'
-        return self.formatter.orderedList(context, {'class': 'context'})
-
-    def formatVariables(self, vars):
-        """ Return formatted variables """
-        done = {}
-        dump = []
-        for name, where, value in vars:
-            if name in done:
-                continue
-            done[name] = 1
-            if value is __UNDEF__:
-                dump.append('%s %s' % (name, self.formatter.em('undefined')))
-            else:
-                dump.append(self.formatNameValue(name, where, value))
-        return self.formatter.list(dump, {'class': 'variables'})
-
-    def formatNameValue(self, name, where, value):
-        """ Format variable name and value according to scope """
-        if where in ['global', 'builtin']:
-            name = '%s %s' % (self.formatter.em(where),
-                              self.formatter.strong(name))
-        elif where == 'local':
-            name = self.formatter.strong(name)
-        else:
-            name = where + self.formatter.strong(name.split('.')[-1])
-        return '%s = %s' % (name, self.formatter.repr(value))
-
-    # ---------------------------------------------------------------
-    # Private - analyzing code
-
-    def scan(self):
-        """ Scan frame for vars while setting highlight line """
-        highlight = {}
-
-        def reader(lnum=[self.lnum]):
-            highlight[lnum[0]] = 1
-            try:
-                return linecache.getline(self.file, lnum[0])
-            finally:
-                lnum[0] += 1
-
-        vars = self.scanVariables(reader)
-        return vars, highlight
-
-    def scanVariables(self, reader):
-        """ Lookup variables in one logical Python line """
-        vars, lasttoken, parent, prefix, value = [], None, None, '', __UNDEF__
-        for ttype, token, start, end, line in tokenize.generate_tokens(reader):
-            if ttype == tokenize.NEWLINE:
-                break
-            if ttype == tokenize.NAME and token not in keyword.kwlist:
-                if lasttoken == '.':
-                    if parent is not __UNDEF__:
-                        if self.unsafe_name(token):
-                            value = HiddenObject
-                        else:
-                            value = getattr(parent, token, __UNDEF__)
-                        vars.append((prefix + token, prefix, value))
-                else:
-                    where, value = self.lookup(token)
-                    vars.append((token, where, value))
-            elif token == '.':
-                prefix += lasttoken + '.'
-                parent = value
-            else:
-                parent, prefix = None, ''
-            lasttoken = token
-        return vars
-
-    def lookup(self, name):
-        """ Return the scope and the value of name """
-        scope = None
-        value = __UNDEF__
-        locals = inspect.getargvalues(self.frame)[3]
-        if name in locals:
-            scope, value = 'local', locals[name]
-        elif name in self.frame.f_globals:
-            scope, value = 'global', self.frame.f_globals[name]
-        elif '__builtins__' in self.frame.f_globals:
-            scope = 'builtin'
-            builtins = self.frame.f_globals['__builtins__']
-            if isinstance(builtins, dict):
-                value = builtins.get(name, __UNDEF__)
-            else:
-                value = getattr(builtins, name, __UNDEF__)
-        if self.unsafe_name(name):
-            value = HiddenObject
-        return scope, value
-
-    def unsafe_name(self, name):
-        return name in self.frame.f_globals.get("unsafe_names", ())
-
-class View:
-    """ Traceback view """
-
-    frameClass = Frame # analyze and format a frame
-
-    def __init__(self, info=None, debug=0):
-        """ Save starting info or current exception info """
-        self.info = info or sys.exc_info()
-        self.debug = debug
-
-    def format(self, formatter, context=5):
-        self.formatter = formatter
-        self.context = context
-        return formatter.section(self.formatContent(), {'class': 'cgitb'})
-
-    def formatContent(self):
-        """ General layout - override to change layout """
-        content = (
-            self.formatStylesheet(),
-            self.formatTitle(),
-            self.formatMessage(),
-            self.formatTraceback(),
-            self.formatSystemDetails(),
-            self.formatTextTraceback(),
-            )
-        return ''.join(content)
-
-    # -----------------------------------------------------------------
-    # Stylesheet
-
-    def formatStylesheet(self):
-        """ Format inline html stylesheet """
-        return '<style type="text/css">%s</style>' % self.stylesheet()
-
-    def stylesheet(self):
-        """ Return stylesheet rules. Override to change rules.
-
-        The rules are sparated to make it easy to extend.
-
-        The stylesheet must work even if sorounding code define the same
-        css names, and it must not change the sorounding code look and
-        behavior. This is done by having all content in a .traceback
-        section.
-        """
-        return """
-.cgitb {background: #E6EAF0; border: 1px solid #4D6180; direction: ltr;}
-.cgitb p {margin: 0.5em 0; padding: 5px 10px; text-align: left;}
-.cgitb ol {margin: 0}
-.cgitb li {margin: 0.25em 0;}
-.cgitb h1, .cgitb h2, .cgitb h3 {padding: 5px 10px; margin: 0; background: #4D6180; color: white;}
-.cgitb h1 {font-size: 1.3em;}
-.cgitb h2 {font-size: 1em; margin-top: 1em;}
-.cgitb h3 {font-size: 1em;}
-.cgitb .frames {margin: 0; padding: 0; color: #606060}
-.cgitb .frames li {display: block;}
-.cgitb .call {padding: 5px 10px; background: #A3B4CC; color: black}
-.cgitb .context {padding: 0; font-family: monospace; }
-.cgitb .context li {display: block; white-space: pre;}
-.cgitb .context li.highlight {background: #C0D3F0; color: black}
-.cgitb .variables {padding: 5px 10px; font-family: monospace;}
-.cgitb .variables li {display: inline;}
-.cgitb .variables li:after {content: ", ";}
-.cgitb .variables li:last-child:after {content: "";}
-.cgitb .exception {border: 1px solid #4D6180; margin: 10px}
-.cgitb .exception h3 {background: #4D6180; color: white;}
-.cgitb .exception p {color: black;}
-.cgitb .exception ul {padding: 0 10px; font-family: monospace;}
-.cgitb .exception li {display: block;}
-"""
-
-    # -----------------------------------------------------------------
-    # Head
-
-    def formatTitle(self):
-        return self.formatter.title(self.exceptionTitle(self.info))
-
-    def formatMessage(self):
-        return self.formatter.paragraph(self.exceptionMessage(self.info))
-
-    # -----------------------------------------------------------------
-    # Traceback
-
-    def formatTraceback(self):
-        """ Return formatted traceback """
-        return self.formatOneTraceback(self.info)
-
-    def formatOneTraceback(self, info):
-        """ Format one traceback
-        
-        Separate to enable formatting multiple tracebacks.
-        """
-        output = [self.formatter.subTitle('Traceback'),
-                  self.formatter.paragraph(self.tracebackText(info)),
-                  self.formatter.orderedList(self.tracebackFrames(info),
-                                            {'class': 'frames'}),
-                  self.formatter.section(self.formatException(info),
-                                         {'class': 'exception'}), ]
-        return self.formatter.section(''.join(output), {'class': 'traceback'})
-
-    def tracebackFrames(self, info):
-        frames = []
-        traceback = info[2]
-        for record in inspect.getinnerframes(traceback, self.context):
-            frame = self.frameClass(*record)
-            frames.append(frame.format(self.formatter))
-        del traceback
-        return frames
-
-    def tracebackText(self, info):
-        return '''A problem occurred in a Python script.  Here is the
-        sequence of function calls leading up to the error, in the
-        order they occurred.'''
-
-    # --------------------------------------------------------------------
-    # Exception
-
-    def formatException(self, info):
-        items = [self.formatExceptionTitle(info),
-                 self.formatExceptionMessage(info),
-                 self.formatExceptionAttributes(info), ]
-        return ''.join(items)
-
-    def formatExceptionTitle(self, info):
-        return self.formatter.subSubTitle(self.exceptionTitle(info))
-
-    def formatExceptionMessage(self, info):
-        return self.formatter.paragraph(self.exceptionMessage(info))
-
-    def formatExceptionAttributes(self, info):
-        attribtues = []
-        for name, value in self.exceptionAttributes(info):
-            value = self.formatter.repr(value)
-            attribtues.append('%s = %s' % (name, value))
-        return self.formatter.list(attribtues)
-
-    def exceptionAttributes(self, info):
-        """ Return list of tuples [(name, value), ...] """
-        instance = info[1]
-        attribtues = []
-        for name in dir(instance):
-            if name.startswith('_'):
-                continue
-            value = getattr(instance, name)
-            attribtues.append((name, value))
-        return attribtues
-
-    def exceptionTitle(self, info):
-        type = info[0]
-        return getattr(type, '__name__', str(type))
-
-    def exceptionMessage(self, info):
-        instance = info[1]
-        return pydoc.html.escape(str(instance))
-
-
-    # -----------------------------------------------------------------
-    # System details
-
-    def formatSystemDetails(self):
-        details = ['Date: %s' % self.date(),
-                   'Platform: %s' % self.platform(),
-                   'Python: %s' % self.python(), ]
-        details += self.applicationDetails()
-        return (self.formatter.subTitle('System Details') +
-                self.formatter.list(details, {'class': 'system'}))
-
-    def date(self):
-        import time
-        rfc2822Date = time.strftime("%a, %d %b %Y %H:%M:%S +0000", time.gmtime())
-        return rfc2822Date
-
-    def platform(self):
-        try:
-            return pydoc.html.escape(' '.join(os.uname()))
-        except:
-            return pydoc.html.escape('%s (%s)' % (sys.platform, os.name))
-
-    def python(self):
-        return 'Python %s (%s)' % (sys.version.split()[0], sys.executable)
-
-    def applicationDetails(self):
-        """ Override for your application """
-        return []
-
-    # -----------------------------------------------------------------
-    # Text traceback
-
-    def formatTextTraceback(self):
-        template = self.textTracebackTemplate()
-        return template % self.formatOneTextTraceback(self.info)
-
-    def formatOneTextTraceback(self, info):
-        """ Separate to enable formatting multiple tracebacks. """
-        import traceback
-        return pydoc.html.escape(''.join(traceback.format_exception(*info)))
-
-    def textTracebackTemplate(self):
-        return '''
-    
-<!-- The above is a description of an error in a Python program,
-     formatted for a Web browser. In case you are not reading this 
-     in a Web browser, here is the original traceback:
-
-%s
--->
-'''
-
-
-class Hook:
-    """A hook to replace sys.excepthook that shows tracebacks in HTML."""
-
-    def __init__(self, display=1, logdir=None, context=5, file=None,
-                 format="html", viewClass=View, debug=0):
-        self.display = display          # send tracebacks to browser if true
-        self.logdir = logdir            # log tracebacks to files if not None
-        self.context = context          # number of source code lines per frame
-        self.file = file or sys.stdout  # place to send the output
-        self.format = format
-        self.viewClass = viewClass
-        self.debug = debug
-
-    def __call__(self, etype, evalue, etb):
-        self.handle((etype, evalue, etb))
-
-    def handle(self, info=None):
-        info = info or sys.exc_info()
-        if self.format.lower() == "html":
-            formatter = HTMLFormatter()
-            self.file.write(reset())
-            plain = False
-        else:
-            formatter = TextFormatter()
-            plain = True
-        try:
-            view = self.viewClass(info, self.debug)
-            doc = view.format(formatter, self.context)
-        except:
-            raise
-            import traceback
-            doc = ''.join(traceback.format_exception(*info))
-            plain = True
-
-        if self.display:
-            if plain:
-                doc = doc.replace('&', '&amp;').replace('<', '&lt;')
-                self.file.write('<pre>' + doc + '</pre>\n')
-            else:
-                self.file.write(doc + '\n')
-        else:
-            self.file.write('<p>A problem occurred in a Python script.\n')
-
-        if self.logdir is not None:
-            import tempfile
-            suffix = ['.txt', '.html'][self.format == "html"]
-            (fd, path) = tempfile.mkstemp(suffix=suffix, dir=self.logdir)
-            try:
-                file = os.fdopen(fd, 'w')
-                file.write(doc)
-                file.close()
-                msg = '<p> %s contains the description of this error.' % path
-            except:
-                msg = '<p> Tried to save traceback to %s, but failed.' % path
-            self.file.write(msg + '\n')
-        try:
-            self.file.flush()
-        except: pass
-
-
-handler = Hook().handle
-
-def enable(display=1, logdir=None, context=5, format="html", viewClass=View, debug=0):
-    """Install an exception handler that formats tracebacks as HTML.
-
-    The optional argument 'display' can be set to 0 to suppress sending the
-    traceback to the browser, and 'logdir' can be set to a directory to cause
-    tracebacks to be written to files there."""
-    sys.excepthook = Hook(display=display, logdir=logdir, context=context,
-                          format=format, viewClass=viewClass, debug=debug)
-
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MoinMoin/support/flup/ChangeLog	Tue Sep 23 23:02:50 2008 +0200
@@ -0,0 +1,243 @@
+2008-07-23  Allan Saddi  <allan@saddi.com>
+
+	* Add support for configuring UNIX domain sockets (for servers that
+	  support them) in the paste.server_factory implementations. Thanks
+	  to Dan Roberts for the code.
+
+2008-07-22  Allan Saddi  <allan@saddi.com>
+
+	* Release 1.0.1
+
+	* Attempt to deduce missing PATH_INFO and/or QUERY_STRING from
+	  REQUEST_URI, if present. Patch provided by Richard Davies.
+
+2007-09-10  Allan Saddi  <allan@saddi.com>
+
+	* Fix readline implementations so size argument is checked
+	  earlier.
+
+2007-07-14  Allan Saddi  <allan@saddi.com>
+
+	* Prevent ThreadPool inconsistences if an exception is
+	  actually raised. Thanks to Tim Chen for the patch.
+
+2007-06-05  Allan Saddi  <allan@saddi.com>
+
+	* Remove publisher and middleware packages.
+	* Add cgi server for completeness.
+
+2007-05-17  Allan Saddi  <allan@saddi.com>
+
+	* Fix fcgi_fork so it can run on Solaris. Thanks to
+	  Basil Crow for the patch.
+
+2007-01-22  Allan Saddi  <allan@saddi.com>
+
+	* Fix eunuchs import issue.
+
+2007-01-10  Allan Saddi  <allan@saddi.com>
+
+	* Support gzip compression of XHTML pages using the
+	  correct MIME type.
+
+2006-12-29  Allan Saddi  <allan@saddi.com>
+
+	* Deprecate WSGI_SCRIPT_NAME and scriptName in scgi_base.
+	  Modern versions of mod_scgi correctly set SCRIPT_NAME &
+	  PATH_INFO.
+
+2006-12-13  Allan Saddi  <allan@saddi.com>
+
+	* Fix problem in session.py seen when optimization is on.
+
+2006-12-05  Allan Saddi  <allan@saddi.com>
+
+	* Update servers to default to an empty QUERY_STRING if
+	  not present in the environ.
+	* Update gzip.py: compresslevel -> compress_level
+	* Update gzip.py by updating docstrings and renaming
+	  classes/methods/functions to better follow Python naming
+	  conventions. NB: mimeTypes keyword parameter is now
+	  mime_types.
+
+2006-12-02  Allan Saddi  <allan@saddi.com>
+
+	* Change intra-package imports into absolute imports.
+
+2006-12-02  Allan Saddi  <allan@saddi.com>
+
+	* Add forceCookieOutput attribute to SessionService to
+	  force Set-Cookie output for the current request.
+
+2006-12-01  Allan Saddi  <allan@saddi.com>
+
+	* Update setup script.
+
+2006-11-26  Allan Saddi  <allan@saddi.com>
+
+	* Don't attempt to install signal handlers under Windows
+	  to improve compatibility.
+
+2006-11-24  Allan Saddi  <allan@saddi.com>
+
+	* Add *_thread egg entry-point aliases.
+	* Add UNIX domain socket support to scgi, scgi_fork,
+	  scgi_app.
+	* Add flup.client package which contains various
+	  WSGI -> connector client implentations. (So far: FastCGI,
+	  and SCGI.)
+
+2006-11-19  Allan Saddi  <allan@saddi.com>
+
+	* Change mime-type matching algorithm in GzipMiddleware.
+	  Strip parameters (e.g. "encoding") and accept a list of
+	  regexps. By default, compress 'text/.*' mime-types.
+
+2006-11-10  Allan Saddi  <allan@saddi.com>
+
+	* Add cookieAttributes to SessionService to make it easier
+	  to customize the generated cookie's attributes.
+
+2006-08-28  Allan Saddi  <allan@saddi.com>
+
+	* Add support for FastCGI roles other than FCGI_RESPONDER.
+	  Patch provided by Seairth Jacobs.
+
+2006-08-02  Allan Saddi  <allan@saddi.com>
+
+	* Add cookieExpiration keyword to SessionService /
+	  SessionMiddleware to adjust the session cookie's expiration.
+	  Thanks to Blaise Laflamme for the suggestion.
+
+2006-06-27  Allan Saddi  <allan@saddi.com>
+
+	* Set close-on-exec flag on all server sockets. Thanks to
+	  Ralf Schmitt for reporting the problem.
+
+2006-06-18  Allan Saddi  <allan@saddi.com>
+
+	* Stop ignoring EPIPE exceptions, as this is probably the
+	  wrong thing to do. (Application is unaware of disconnected
+	  clients and the CPU spins when sending large files to a
+	  disconnected client.) Thanks to Ivan Sagalaev for bringing
+	  this to my attention.
+
+	  NB: Existing applications that use the flup servers may begin
+	  seeing socket.error exceptions...
+
+2006-05-18  Allan Saddi  <allan@saddi.com>
+
+	* Added umask keyword parameter to fcgi and fcgi_fork,
+	  for use when binding to a UNIX socket.
+
+2006-05-03  Allan Saddi  <allan@saddi.com>
+
+	* Fix illusive problem with AJP implementation. Thanks to
+	  Moshe Van der Sterre for explaining the problem and
+	  providing a fix.
+
+2006-04-06  Allan Saddi  <allan@saddi.com>
+
+	* Catch a strange FieldStorage case. Seen in production.
+	  Not quite sure what causes it.
+
+2006-03-21  Allan Saddi  <allan@saddi.com>
+
+	* Add maxRequests option to PreforkServer. Patch provided by
+	  Wojtek Sobczuk.
+
+2006-02-23  Allan Saddi  <allan@saddi.com>
+
+	* Add paste.server_factory-compliant factories and respective
+	  egg entry points. Thanks to Luis Bruno for the code.
+
+	  Add debug option to servers, which is True by default.
+	  Currently, only server-level error handling is affected.
+	
+2006-01-15  Allan Saddi  <allan@saddi.com>
+
+	* Change the behavior of ImportingModuleResolver when dealing
+	  with ImportErrors. Previously, it would act as if the module
+	  did not exist. Now, it propagates the exception to another
+	  level (outer middleware or WSGI). Reported by Scot Doyle.
+
+2006-01-05  Allan Saddi  <allan@saddi.com>
+
+	* Improve Windows compatibility by conditionally installing
+	  SIGHUP handler. Thanks to Brad Miller for pointing out the
+	  problem and providing a fix.
+
+2005-12-19  Allan Saddi  <allan@saddi.com>
+
+	* Fix socket leak in eunuchs socketpair() wrapper. Thanks to
+	  Georg Bauer for pointing this out.
+
+2005-12-16  Allan Saddi  <allan@saddi.com>
+
+	* Switch to setuptools for egg support.
+	* Add higher-level 404 error page support. Thanks to Scot Doyle
+	  for suggesting the idea and providing code. If you previously
+	  subclassed Publisher to provide a custom 404 error page, this
+	  is now broken. It will have to be massaged to fit the new
+	  calling convention.
+
+2005-11-28  Allan Saddi  <allan@saddi.com>
+
+	* Fix issue with FCGI_GET_VALUES handling. Thanks to
+	  Timothy Wright for pointing this out.
+
+2005-11-18  Allan Saddi  <allan@saddi.com>
+
+	* When running under Python < 2.4, attempt to use socketpair()
+	  from eunuchs module.
+
+2005-09-07  Allan Saddi  <allan@saddi.com>
+
+	* Python 2.3 doesn't define socket.SHUT_WR, which affected
+	  the closing of the FastCGI socket with the server. This would
+	  cause output to hang. Thanks to Eugene Lazutkin for bringing
+	  the problem to my attention and going out of his way to help
+	  me debug it!
+
+2005-07-03  Allan Saddi  <allan@saddi.com>
+
+	* Ensure session identifiers only contain ASCII characters when
+	  using a non-ASCII locale. Thanks to Ksenia Marasanova for the
+	  the fix.
+
+2005-06-12  Allan Saddi  <allan@saddi.com>
+
+	* Cleanly close connection socket to avoid sending a TCP RST to
+	  the web server. (fcgi_base) Fix suggested by Dima Barsky.
+
+2005-05-31  Allan Saddi  <allan@saddi.com>
+
+	* Take scriptName from the WSGI_SCRIPT_NAME environment variable
+	  passed from the web server, if present.
+	* Check if scriptName is None, and if so, don't modify SCRIPT_NAME
+	  & PATH_INFO. For better compatibility with cgi2scgi. (scgi_base)
+
+2005-05-18  Allan Saddi  <allan@saddi.com>
+
+	* Change default allowedServers for ajp and scgi to ['127.0.0.1'].
+	* Accept PATH_INFO from environment for scgi servers, in case
+	  cgi2scgi is being used. Submitted by Ian Bicking.
+	* Change threaded servers so wsgi.multiprocess is False by default.
+	  Allow it to be changed by keyword argument.
+	* Fix wsgi.multiprocess for scgi_fork. (Set to True.)
+
+2005-05-15  Allan Saddi  <allan@saddi.com>
+
+	* Prevent possible deadlock related to DiskSessionStore locking.
+	* Add logic to SessionStore so that it will block if attempting to
+	  check out a Session that's already been checked out.
+
+2005-05-14  Allan Saddi  <allan@saddi.com>
+
+	* Convert the use of decorators in session.py to something
+	  compatible with Python <2.4.
+
+2005-04-23  Allan Saddi  <allan@saddi.com>
+
+	* Ensure that SessionStore.checkOutSession() never returns an
+	  invalidated Session. Reported by Rene Dudfield.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MoinMoin/support/flup/NOTES.moin	Tue Sep 23 23:02:50 2008 +0200
@@ -0,0 +1,17 @@
+NOTES for bundled flup:
+=======================
+This directory contains the flup WSGI adapter package. It has been
+patched to support completely singlethreaded non-forking servers for
+deployment in webservers, that have their own process spawning strategy.
+
+The shipped version is available via mercurial checkout:
+
+hg clone -r aa983e43105e http://hg.saddi.com/flup-server
+
+The singlethreaded server patch was provided via a trac bugreport
+(http://trac.saddi.com/flup/ticket/22) and is provided in this directory
+as singleserver.diff for convenience. It has already been applied.
+
+Thanks to Allan Saddi <allan@saddi.com> for writing flup and to
+the unnamed contributor <tv@inoi.fi> for the single-thread patch.
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MoinMoin/support/flup/__init__.py	Tue Sep 23 23:02:50 2008 +0200
@@ -0,0 +1,1 @@
+#
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MoinMoin/support/flup/client/__init__.py	Tue Sep 23 23:02:50 2008 +0200
@@ -0,0 +1,1 @@
+#
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MoinMoin/support/flup/client/fcgi_app.py	Tue Sep 23 23:02:50 2008 +0200
@@ -0,0 +1,461 @@
+# Copyright (c) 2006 Allan Saddi <allan@saddi.com>
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+#    notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+#    notice, this list of conditions and the following disclaimer in the
+#    documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+#
+# $Id$
+
+__author__ = 'Allan Saddi <allan@saddi.com>'
+__version__ = '$Revision$'
+
+import select
+import struct
+import socket
+import errno
+
+__all__ = ['FCGIApp']
+
+# Constants from the spec.
+FCGI_LISTENSOCK_FILENO = 0
+
+FCGI_HEADER_LEN = 8
+
+FCGI_VERSION_1 = 1
+
+FCGI_BEGIN_REQUEST = 1
+FCGI_ABORT_REQUEST = 2
+FCGI_END_REQUEST = 3
+FCGI_PARAMS = 4
+FCGI_STDIN = 5
+FCGI_STDOUT = 6
+FCGI_STDERR = 7
+FCGI_DATA = 8
+FCGI_GET_VALUES = 9
+FCGI_GET_VALUES_RESULT = 10
+FCGI_UNKNOWN_TYPE = 11
+FCGI_MAXTYPE = FCGI_UNKNOWN_TYPE
+
+FCGI_NULL_REQUEST_ID = 0
+
+FCGI_KEEP_CONN = 1
+
+FCGI_RESPONDER = 1
+FCGI_AUTHORIZER = 2
+FCGI_FILTER = 3
+
+FCGI_REQUEST_COMPLETE = 0
+FCGI_CANT_MPX_CONN = 1
+FCGI_OVERLOADED = 2
+FCGI_UNKNOWN_ROLE = 3
+
+FCGI_MAX_CONNS = 'FCGI_MAX_CONNS'
+FCGI_MAX_REQS = 'FCGI_MAX_REQS'
+FCGI_MPXS_CONNS = 'FCGI_MPXS_CONNS'
+
+FCGI_Header = '!BBHHBx'
+FCGI_BeginRequestBody = '!HB5x'
+FCGI_EndRequestBody = '!LB3x'
+FCGI_UnknownTypeBody = '!B7x'
+
+FCGI_BeginRequestBody_LEN = struct.calcsize(FCGI_BeginRequestBody)
+FCGI_EndRequestBody_LEN = struct.calcsize(FCGI_EndRequestBody)
+FCGI_UnknownTypeBody_LEN = struct.calcsize(FCGI_UnknownTypeBody)
+
+if __debug__:
+    import time
+
+    # Set non-zero to write debug output to a file.
+    DEBUG = 0
+    DEBUGLOG = '/tmp/fcgi_app.log'
+
+    def _debug(level, msg):
+        if DEBUG < level:
+            return
+
+        try:
+            f = open(DEBUGLOG, 'a')
+            f.write('%sfcgi: %s\n' % (time.ctime()[4:-4], msg))
+            f.close()
+        except:
+            pass
+
+def decode_pair(s, pos=0):
+    """
+    Decodes a name/value pair.
+
+    The number of bytes decoded as well as the name/value pair
+    are returned.
+    """
+    nameLength = ord(s[pos])
+    if nameLength & 128:
+        nameLength = struct.unpack('!L', s[pos:pos+4])[0] & 0x7fffffff
+        pos += 4
+    else:
+        pos += 1
+
+    valueLength = ord(s[pos])
+    if valueLength & 128:
+        valueLength = struct.unpack('!L', s[pos:pos+4])[0] & 0x7fffffff
+        pos += 4
+    else:
+        pos += 1
+
+    name = s[pos:pos+nameLength]
+    pos += nameLength
+    value = s[pos:pos+valueLength]
+    pos += valueLength
+
+    return (pos, (name, value))
+
+def encode_pair(name, value):
+    """
+    Encodes a name/value pair.
+
+    The encoded string is returned.
+    """
+    nameLength = len(name)
+    if nameLength < 128:
+        s = chr(nameLength)
+    else:
+        s = struct.pack('!L', nameLength | 0x80000000L)
+
+    valueLength = len(value)
+    if valueLength < 128:
+        s += chr(valueLength)
+    else:
+        s += struct.pack('!L', valueLength | 0x80000000L)
+
+    return s + name + value
+
+class Record(object):
+    """
+    A FastCGI Record.
+
+    Used for encoding/decoding records.
+    """
+    def __init__(self, type=FCGI_UNKNOWN_TYPE, requestId=FCGI_NULL_REQUEST_ID):
+        self.version = FCGI_VERSION_1
+        self.type = type
+        self.requestId = requestId
+        self.contentLength = 0
+        self.paddingLength = 0
+        self.contentData = ''
+
+    def _recvall(sock, length):
+        """
+        Attempts to receive length bytes from a socket, blocking if necessary.
+        (Socket may be blocking or non-blocking.)
+        """
+        dataList = []
+        recvLen = 0
+        while length:
+            try:
+                data = sock.recv(length)
+            except socket.error, e:
+                if e[0] == errno.EAGAIN:
+                    select.select([sock], [], [])
+                    continue
+                else:
+                    raise
+            if not data: # EOF
+                break
+            dataList.append(data)
+            dataLen = len(data)
+            recvLen += dataLen
+            length -= dataLen
+        return ''.join(dataList), recvLen
+    _recvall = staticmethod(_recvall)
+
+    def read(self, sock):
+        """Read and decode a Record from a socket."""
+        try:
+            header, length = self._recvall(sock, FCGI_HEADER_LEN)
+        except:
+            raise EOFError
+
+        if length < FCGI_HEADER_LEN:
+            raise EOFError
+        
+        self.version, self.type, self.requestId, self.contentLength, \
+                      self.paddingLength = struct.unpack(FCGI_Header, header)
+
+        if __debug__: _debug(9, 'read: fd = %d, type = %d, requestId = %d, '
+                             'contentLength = %d' %
+                             (sock.fileno(), self.type, self.requestId,
+                              self.contentLength))
+        
+        if self.contentLength:
+            try:
+                self.contentData, length = self._recvall(sock,
+                                                         self.contentLength)
+            except:
+                raise EOFError
+
+            if length < self.contentLength:
+                raise EOFError
+
+        if self.paddingLength:
+            try:
+                self._recvall(sock, self.paddingLength)
+            except:
+                raise EOFError
+
+    def _sendall(sock, data):
+        """
+        Writes data to a socket and does not return until all the data is sent.
+        """
+        length = len(data)
+        while length:
+            try:
+                sent = sock.send(data)
+            except socket.error, e:
+                if e[0] == errno.EAGAIN:
+                    select.select([], [sock], [])
+                    continue
+                else:
+                    raise
+            data = data[sent:]
+            length -= sent
+    _sendall = staticmethod(_sendall)
+
+    def write(self, sock):
+        """Encode and write a Record to a socket."""
+        self.paddingLength = -self.contentLength & 7
+
+        if __debug__: _debug(9, 'write: fd = %d, type = %d, requestId = %d, '
+                             'contentLength = %d' %
+                             (sock.fileno(), self.type, self.requestId,
+                              self.contentLength))
+
+        header = struct.pack(FCGI_Header, self.version, self.type,
+                             self.requestId, self.contentLength,
+                             self.paddingLength)
+        self._sendall(sock, header)
+        if self.contentLength:
+            self._sendall(sock, self.contentData)
+        if self.paddingLength:
+            self._sendall(sock, '\x00'*self.paddingLength)
+
+class FCGIApp(object):
+    def __init__(self, command=None, connect=None, host=None, port=None,
+                 filterEnviron=True):
+        if host is not None:
+            assert port is not None
+            connect=(host, port)
+
+        assert (command is not None and connect is None) or \
+               (command is None and connect is not None)
+
+        self._command = command
+        self._connect = connect
+
+        self._filterEnviron = filterEnviron
+        
+        #sock = self._getConnection()
+        #print self._fcgiGetValues(sock, ['FCGI_MAX_CONNS', 'FCGI_MAX_REQS', 'FCGI_MPXS_CONNS'])
+        #sock.close()
+        
+    def __call__(self, environ, start_response):
+        # For sanity's sake, we don't care about FCGI_MPXS_CONN
+        # (connection multiplexing). For every request, we obtain a new
+        # transport socket, perform the request, then discard the socket.
+        # This is, I believe, how mod_fastcgi does things...
+
+        sock = self._getConnection()
+
+        # Since this is going to be the only request on this connection,
+        # set the request ID to 1.
+        requestId = 1
+
+        # Begin the request
+        rec = Record(FCGI_BEGIN_REQUEST, requestId)
+        rec.contentData = struct.pack(FCGI_BeginRequestBody, FCGI_RESPONDER, 0)
+        rec.contentLength = FCGI_BeginRequestBody_LEN
+        rec.write(sock)
+
+        # Filter WSGI environ and send it as FCGI_PARAMS
+        if self._filterEnviron:
+            params = self._defaultFilterEnviron(environ)
+        else:
+            params = self._lightFilterEnviron(environ)
+        # TODO: Anything not from environ that needs to be sent also?
+        self._fcgiParams(sock, requestId, params)
+        self._fcgiParams(sock, requestId, {})
+
+        # Transfer wsgi.input to FCGI_STDIN
+        content_length = int(environ.get('CONTENT_LENGTH') or 0)
+        while True:
+            chunk_size = min(content_length, 4096)
+            s = environ['wsgi.input'].read(chunk_size)
+            content_length -= len(s)
+            rec = Record(FCGI_STDIN, requestId)
+            rec.contentData = s
+            rec.contentLength = len(s)
+            rec.write(sock)
+
+            if not s: break
+
+        # Empty FCGI_DATA stream
+        rec = Record(FCGI_DATA, requestId)
+        rec.write(sock)
+
+        # Main loop. Process FCGI_STDOUT, FCGI_STDERR, FCGI_END_REQUEST
+        # records from the application.
+        result = []
+        while True:
+            inrec = Record()
+            inrec.read(sock)
+            if inrec.type == FCGI_STDOUT:
+                if inrec.contentData:
+                    result.append(inrec.contentData)
+                else:
+                    # TODO: Should probably be pedantic and no longer
+                    # accept FCGI_STDOUT records?