changeset 4210:d9de4fa12f23

Merged: 1.8
author Florian Krupicka <florian.krupicka@googlemail.com>
date Wed, 09 Jul 2008 20:43:29 +0200
parents 7abbc2bb328d (diff) 3433428abe41 (current diff)
children dde44d6e24ae
files MoinMoin/PageEditor.py MoinMoin/_tests/test_wikiutil.py MoinMoin/action/AttachFile.py MoinMoin/action/MyPages.py MoinMoin/action/PackagePages.py MoinMoin/action/SyncPages.py MoinMoin/action/backup.py MoinMoin/action/info.py MoinMoin/action/newaccount.py MoinMoin/action/recoverpass.py MoinMoin/auth/__init__.py MoinMoin/auth/_tests/test_auth.py MoinMoin/config/multiconfig.py MoinMoin/macro/TableOfContents.py MoinMoin/macro/_tests/test_EmbedObject.py MoinMoin/request/__init__.py MoinMoin/session.py MoinMoin/theme/__init__.py MoinMoin/user.py MoinMoin/util/filesys.py MoinMoin/wikiutil.py MoinMoin/xmlrpc/__init__.py
diffstat 92 files changed, 1808 insertions(+), 801 deletions(-) [+]
line wrap: on
line diff
--- a/MoinMoin/Page.py	Tue Jul 08 16:58:19 2008 +0200
+++ b/MoinMoin/Page.py	Wed Jul 09 20:43:29 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:
@@ -758,7 +759,7 @@
             url = "%s#%s" % (url, wikiutil.url_quote_plus(anchor))
 
         if not relative:
-            url = '%s/%s' % (request.getScriptname(), url)
+            url = '%s/%s' % (request.script_root, url)
         return url
 
     def link_to_raw(self, request, text, querystr=None, anchor=None, **kw):
@@ -963,26 +964,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 +1007,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
@@ -1027,7 +1027,7 @@
             # 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(),
+                request.script_root,
                 wikiutil.quoteWikinameURL(pi['redirect']),
                 wikiutil.url_quote_plus(self.page_name, ''), ))
             return
@@ -1076,12 +1076,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 +1096,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
--- a/MoinMoin/PageEditor.py	Tue Jul 08 16:58:19 2008 +0200
+++ b/MoinMoin/PageEditor.py	Wed Jul 09 20:43:29 2008 +0200
@@ -196,7 +196,6 @@
 
         # 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
@@ -332,7 +331,7 @@
 
         # send form
         request.write('<form id="editor" method="post" action="%s/%s#preview" onSubmit="flgChange = false;">' % (
-            request.getScriptname(),
+            request.script_root,
             wikiutil.quoteWikinameURL(self.page_name),
             ))
 
--- a/MoinMoin/PageGraphicalEditor.py	Tue Jul 08 16:58:19 2008 +0200
+++ b/MoinMoin/PageGraphicalEditor.py	Wed Jul 09 20:43:29 2008 +0200
@@ -52,7 +52,6 @@
         form = request.form
         _ = self._
         request.disableHttpCaching(level=2)
-        request.emit_http_headers()
 
         raw_body = ''
         msg = None
@@ -223,7 +222,7 @@
 
         # send form
         request.write('<form id="editor" method="post" action="%s/%s#preview">' % (
-            request.getScriptname(),
+            request.script_root,
             wikiutil.quoteWikinameURL(self.page_name),
             ))
 
@@ -313,7 +312,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)
--- a/MoinMoin/_tests/test_wikiutil.py	Tue Jul 08 16:58:19 2008 +0200
+++ b/MoinMoin/_tests/test_wikiutil.py	Wed Jul 09 20:43:29 2008 +0200
@@ -9,7 +9,7 @@
 
 import py
 
-from MoinMoin import wikiutil
+from MoinMoin import config, wikiutil
 
 
 class TestQueryStringSupport:
@@ -969,4 +969,86 @@
         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 setup_method(self, method):
+        self.config = self.TestConfig(page_group_regex=r'.+Group')
+
+    def teardown_method(self, method):
+        del self.config
+
+    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']
--- a/MoinMoin/action/AttachFile.py	Tue Jul 08 16:58:19 2008 +0200
+++ b/MoinMoin/action/AttachFile.py	Wed Jul 09 20:43:29 2008 +0200
@@ -83,7 +83,7 @@
         qs = '?%s' % wikiutil.makeQueryString(kw, want_unicode=False)
     else:
         qs = ''
-    return "%s/%s%s" % (request.getScriptname(), wikiutil.quoteWikinameURL(pagename), qs)
+    return "%s/%s%s" % (request.script_root, wikiutil.quoteWikinameURL(pagename), qs)
 
 
 def getAttachUrl(pagename, filename, request, addts=0, escaped=0, do='get', drawing='', upload=False):
@@ -251,10 +251,10 @@
     _ = request.getText
 
     error = None
-    if not request.form.get('target', [''])[0]:
+    if not request.form.get('target'):
         error = _("Filename of attachment not specified!")
     else:
-        filename = wikiutil.taintfilename(request.form['target'][0])
+        filename = wikiutil.taintfilename(request.form['target'])
         fpath = getFilename(request, pagename, filename)
 
         if os.path.isfile(fpath):
@@ -408,14 +408,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)
     helplink = Page(request, "HelpOnActions/AttachFile").url(request)
     savelink = attachUrl(request, 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
@@ -477,14 +477,14 @@
 </p>
 </form>
 """ % {
-    'baseurl': request.getScriptname(),
+    'baseurl': request.script_root,
     'pagename': wikiutil.quoteWikinameURL(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 +495,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,8 +507,8 @@
     """ Main dispatcher for the 'AttachFile' action. """
     _ = request.getText
 
-    do = request.form.get('do', ['upload_form'])
-    handler = globals().get('_do_%s' % do[0])
+    do = request.form.get('do', 'upload_form')
+    handler = globals().get('_do_%s' % do)
     if handler:
         msg = handler(pagename, request)
     else:
@@ -524,7 +524,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,7 +554,7 @@
         return _('TextCha: Wrong answer! Go back and try again...')
 
     form = request.form
-    overwrite = form.get('overwrite', [u'0'])[0]
+    overwrite = form.get('overwrite', u'0')
     try:
         overwrite = int(overwrite)
     except:
@@ -568,7 +567,7 @@
         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:
@@ -581,7 +580,7 @@
         return _("Filename of attachment not specified!")
 
     # get file content
-    filecontent = request.form.get('file', [None])[0]
+    filecontent = request.form.get('file')
     if filecontent is None:
         # This might happen when trying to upload file names
         # with non-ascii characters on Safari.
@@ -607,8 +606,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 +643,6 @@
     if ext == '.map':
         os.utime(attach_dir, None)
 
-    request.emit_http_headers()
     request.write("OK")
 
 
@@ -710,17 +708,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 +726,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,7 +741,7 @@
 
     # move file
     d = {'action': action_name,
-         'baseurl': request.getScriptname(),
+         'baseurl': request.script_root,
          'do': 'attachment_move',
          'ticket': wikiutil.createTicket(request),
          'pagename': pagename,
@@ -796,9 +794,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 = os.path.getmtime(fpath)
+    if_modified = time.mktime(request.if_modified_since.timetuple())
+    if if_modified >= timestamp:
+        request.status_code = 304
     else:
         mt = wikiutil.MimeType(filename=filename)
         content_type = mt.content_type()
@@ -814,12 +813,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 +1051,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 Jul 08 16:58:19 2008 +0200
+++ b/MoinMoin/action/CopyPage.py	Wed Jul 09 20:43:29 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:
@@ -86,7 +85,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 Jul 08 16:58:19 2008 +0200
+++ b/MoinMoin/action/DeletePage.py	Wed Jul 09 20:43:29 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'][0])
+        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 Jul 08 16:58:19 2008 +0200
+++ b/MoinMoin/action/Despam.py	Wed Jul 09 20:43:29 2008 +0200
@@ -110,7 +110,7 @@
 <input type="submit" name="ok" value="%s">
 </form>
 </p>
-''' % (request.getScriptname(), wikiutil.quoteWikinameURL(pagename),
+''' % (request.script_root, wikiutil.quoteWikinameURL(pagename),
        wikiutil.url_quote(editor), _("Revert all!")))
 
 def revert_page(request, pagename, editor):
@@ -183,11 +183,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 Jul 08 16:58:19 2008 +0200
+++ b/MoinMoin/action/LikePages.py	Wed Jul 09 20:43:29 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 Jul 08 16:58:19 2008 +0200
+++ b/MoinMoin/action/Load.py	Wed Jul 09 20:43:29 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 Jul 08 16:58:19 2008 +0200
+++ b/MoinMoin/action/LocalSiteMap.py	Wed Jul 09 20:43:29 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 Jul 08 16:58:19 2008 +0200
+++ b/MoinMoin/action/MyPages.py	Wed Jul 09 20:43:29 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 Jul 08 16:58:19 2008 +0200
+++ b/MoinMoin/action/PackagePages.py	Wed Jul 09 20:43:29 2008 +0200
@@ -68,10 +68,10 @@
         form = self.request.form
 
         # Get new name from form and normalize.
-        pagelist = form.get('pagelist', [u''])[0]
-        packagename = form.get('packagename', [u''])[0]
+        pagelist = form.get('pagelist', u'')
+        packagename = form.get('packagename', u'')
 
-        if not form.get('submit', [None])[0]:
+        if not form.get('submit'):
             self.request.theme.add_msg(self.makeform(), "dialog")
             raise ActionError
 
@@ -114,7 +114,7 @@
             error = u'<p class="error">%s</p>\n' % error
 
         d = {
-            'baseurl': self.request.getScriptname(),
+            'baseurl': self.request.script_root,
             'error': error,
             'action': self.__class__.__name__,
             'pagename': wikiutil.escape(self.pagename, True),
@@ -187,7 +187,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 Jul 08 16:58:19 2008 +0200
+++ b/MoinMoin/action/RenamePage.py	Wed Jul 09 20:43:29 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:
@@ -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': _('Rename all /subpages too?'),
                 'pagename': wikiutil.escape(self.pagename, True),
                 'newname_label': _("New name"),
--- a/MoinMoin/action/SpellCheck.py	Tue Jul 08 16:58:19 2008 +0200
+++ b/MoinMoin/action/SpellCheck.py	Wed Jul 09 20:43:29 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
@@ -188,7 +188,7 @@
         # 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)
+                         '<input type="hidden" name="action" value="%s">\n') % (request.script_root, wikiutil.quoteWikinameURL(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 Jul 08 16:58:19 2008 +0200
+++ b/MoinMoin/action/SubscribeUser.py	Wed Jul 09 20:43:29 2008 +0200
@@ -16,7 +16,6 @@
 
 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("""
@@ -25,21 +24,20 @@
 %s <input type="text" name="users" size="50">
 <input type="submit" value="Subscribe">
 </form>
-""" % (request.getScriptname(), wikiutil.quoteWikinameURL(pagename),
+""" % (request.script_root, wikiutil.quoteWikinameURL(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)
--- a/MoinMoin/action/SyncPages.py	Tue Jul 08 16:58:19 2008 +0200
+++ b/MoinMoin/action/SyncPages.py	Wed Jul 09 20:43:29 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 Jul 08 16:58:19 2008 +0200
+++ b/MoinMoin/action/__init__.py	Wed Jul 09 20:43:29 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,7 +136,7 @@
 
         d = {
             'method': self.method,
-            'baseurl': self.request.getScriptname(),
+            'baseurl': self.request.script_root,
             'enctype': self.enctype,
             'error_html': error_html,
             'actionname': self.actionname,
@@ -243,7 +245,8 @@
     if not request.user.may.read(pagename):
         Page(request, pagename).send_page()
     else:
-        mimetype = request.form.get('mimetype', [u"text/html"])[0]
+        from MoinMoin.web.contexts import HTTPContext, RenderContext
+        mimetype = request.form.get('mimetype', u"text/html")
         rev = request.rev or 0
         if rev == 0:
             request.cacheable = cacheable
@@ -261,15 +264,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 +285,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,19 +298,30 @@
 
 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.
+
+    @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(request, action, identifier="execute"):
-    """ return a handler function for a given action or None """
+    """ 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:
         return None
@@ -320,3 +333,37 @@
 
     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/bookmark.py	Tue Jul 08 16:58:19 2008 +0200
+++ b/MoinMoin/action/bookmark.py	Wed Jul 09 20:43:29 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/chart.py	Tue Jul 08 16:58:19 2008 +0200
+++ b/MoinMoin/action/chart.py	Wed Jul 09 20:43:29 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 Jul 08 16:58:19 2008 +0200
+++ b/MoinMoin/action/diff.py	Wed Jul 09 20:43:29 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 Jul 08 16:58:19 2008 +0200
+++ b/MoinMoin/action/dumpform.py	Wed Jul 09 20:43:29 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 Jul 08 16:58:19 2008 +0200
+++ b/MoinMoin/action/edit.py	Wed Jul 09 20:43:29 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 Jul 08 16:58:19 2008 +0200
+++ b/MoinMoin/action/fckdialog.py	Wed Jul 09 20:43:29 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 Jul 08 16:58:19 2008 +0200
+++ b/MoinMoin/action/fullsearch.py	Wed Jul 09 20:43:29 2008 +0200
@@ -27,7 +27,7 @@
         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.form['advancedsearch'])
     except KeyError:
         return False
 
@@ -72,32 +72,32 @@
     elif advancedsearch:
         context = 180 # XXX: hardcoded context count for advancedsearch
     else:
-        context = int(request.form.get('context', [0])[0])
+        context = int(request.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 = request.form.get(fieldname, '')
+    case = int(request.form.get('case', 0))
+    regex = int(request.form.get('regex', 0)) # no interface currently
+    hitsFrom = int(request.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 = request.form.get('and_terms', '').strip()
+        or_terms = request.form.get('or_terms', '').strip()
+        not_terms = request.form.get('not_terms', '').strip()
+        #xor_terms = request.form.get('xor_terms', '').strip()
+        categories = request.form.getlist('categories') or ['']
+        timeframe = request.form.get('time', '').strip()
+        language = request.form.getlist('language') or ['']
+        mimetype = request.form.getlist('mimetype') or [0]
+        excludeunderlay = request.form.get('excludeunderlay', 0)
+        nosystemitems = request.form.get('nosystemitems', 0)
+        historysearch = request.form.get('historysearch', 0)
 
-        mtime = request.form.get('mtime', [''])[0]
+        mtime = request.form.get('mtime', '')
         if mtime:
             mtime_parsed = None
 
@@ -223,8 +223,6 @@
         Page(request, pagename).send_page()
         return
 
-    request.emit_http_headers()
-
     # This action generates data using the user language
     request.setContentLanguage(request.lang)
 
--- a/MoinMoin/action/info.py	Tue Jul 08 16:58:19 2008 +0200
+++ b/MoinMoin/action/info.py	Wed Jul 09 20:43:29 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 Jul 08 16:58:19 2008 +0200
+++ b/MoinMoin/action/links.py	Wed Jul 09 20:43:29 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 Jul 08 16:58:19 2008 +0200
+++ b/MoinMoin/action/login.py	Wed Jul 09 20:43:29 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 Jul 08 16:58:19 2008 +0200
+++ b/MoinMoin/action/newaccount.py	Wed Jul 09 20:43:29 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"
@@ -83,7 +83,7 @@
     # save data
     theuser.save()
 
-    if form.has_key('create_and_mail'):
+    if 'create_and_mail' in form:
         theuser.mailAccountData()
 
     result = _("User account created! You can use this account to login now...")
@@ -174,13 +174,12 @@
     _ = request.getText
     form = request.form
 
-    submitted = form.has_key('create_only') or form.has_key('create_and_mail')
+    submitted = 'create_only' in form or 'create_and_mail' in form:
 
     if submitted: # user pressed create button
         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 Jul 08 16:58:19 2008 +0200
+++ b/MoinMoin/action/newpage.py	Wed Jul 09 20:43:29 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 Jul 08 16:58:19 2008 +0200
+++ b/MoinMoin/action/pollsistersites.py	Wed Jul 09 20:43:29 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 Jul 08 16:58:19 2008 +0200
+++ b/MoinMoin/action/recoverpass.py	Wed Jul 09 20:43:29 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 Jul 08 16:58:19 2008 +0200
+++ b/MoinMoin/action/refresh.py	Wed Jul 09 20:43:29 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 Jul 08 16:58:19 2008 +0200
+++ b/MoinMoin/action/revert.py	Wed Jul 09 20:43:29 2008 +0200
@@ -51,10 +51,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 Jul 08 16:58:19 2008 +0200
+++ b/MoinMoin/action/rss_rc.py	Wed Jul 09 20:43:29 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 Jul 08 16:58:19 2008 +0200
+++ b/MoinMoin/action/serveopenid.py	Wed Jul 09 20:43:29 2008 +0200
@@ -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 Jul 08 16:58:19 2008 +0200
+++ b/MoinMoin/action/showtags.py	Wed Jul 09 20:43:29 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 Jul 08 16:58:19 2008 +0200
+++ b/MoinMoin/action/sisterpages.py	Wed Jul 09 20:43:29 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 Jul 08 16:58:19 2008 +0200
+++ b/MoinMoin/action/sitemap.py	Wed Jul 09 20:43:29 2008 +0200
@@ -64,7 +64,7 @@
     request.user.datetime_fmt = datetime_fmt
     base = request.getBaseURL()
 
-    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""")
@@ -83,7 +83,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 Jul 08 16:58:19 2008 +0200
+++ b/MoinMoin/action/thread_monitor.py	Wed Jul 09 20:43:29 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 Jul 08 16:58:19 2008 +0200
+++ b/MoinMoin/action/titleindex.py	Wed Jul 09 20:43:29 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 Jul 08 16:58:19 2008 +0200
+++ b/MoinMoin/action/userprefs.py	Wed Jul 09 20:43:29 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.form.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 Jul 08 16:58:19 2008 +0200
+++ b/MoinMoin/action/userprofile.py	Wed Jul 09 20:43:29 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 Jul 08 16:58:19 2008 +0200
+++ b/MoinMoin/auth/__init__.py	Wed Jul 09 20:43:29 2008 +0200
@@ -257,3 +257,67 @@
                'userprefslink': userprefslink,
                'sendmypasswordlink': sendmypasswordlink}
 
+def handle_login(request, userobj=None, username=None, password=None,
+                 attended=True, openid_identifier=None, stage=None):
+    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)
+
+    return userobj
+
+def handle_logout(request, userobj):
+    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):
+    for authmethod in request.cfg.auth:
+        userobj, cont = authmethod.request(request, userobj, cookie=request.cookies)
+        if not cont:
+            break
+    return userobj
+
+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 Jul 08 16:58:19 2008 +0200
+++ b/MoinMoin/auth/_tests/test_auth.py	Wed Jul 09 20:43:29 2008 +0200
@@ -6,11 +6,8 @@
     @license: GNU GPL, see COPYING for details.
 """
 
-import StringIO, urllib
-
-from MoinMoin.server.server_wsgi import WsgiConfig
-from MoinMoin.request import request_wsgi
-
+from MoinMoin.web.request import TestRequest
+from MoinMoin import wsgiapp
 
 class TestAuth:
     """ test misc. auth methods """
@@ -21,7 +18,8 @@
 
         Some test needs specific config values, or they will fail.
         """
-        config = WsgiConfig() # you MUST create an instance
+        # Why this?
+        # config = WsgiConfig() # you MUST create an instance
 
     def teardown_class(cls):
         """ Stuff that should run to clean up the state of this test class
@@ -29,35 +27,16 @@
         """
         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)
-        request.run()
-        return request # request.status, request.headers, request.output()
+    def run_request(self, **params):
+        if not 'REMOTE_ADDR' in params:
+            params['REMOTE_ADDR'] = '10.10.10.10'
+        request = TestRequest(**params)
+        request = wsgiapp.init(request)
+        return wsgiapp.run(request)
 
     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
@@ -82,8 +61,7 @@
         assert has_v
         # XXX BROKEN?:
         #assert has_cc # cache anon user's content
-        output = request.output()
-        assert '</html>' in output
+        assert '</html>' in request.output()
 
     def testAnonSession(self):
         """ run some requests, no auth, check if anon sessions work """
@@ -92,15 +70,14 @@
         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)
+            request = self.run_request(path='/%s' % pagename,
+                                       HTTP_COOKIE=cookie)
 
             # anon user?
             assert not request.user.valid
 
             # Do we have a session?
-            assert request.session
+            assert request.session is not None
 
             # check if the request resulted in normal status, result headers and content
             assert request.status == '200 OK'
@@ -123,8 +100,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 request.output()
 
             # The trail is only ever saved on the second page display
             # because otherwise anonymous sessions would be created
@@ -148,22 +124,23 @@
         """ run some requests with http auth, check whether session works """
         from MoinMoin.auth.http import HTTPAuth
         username = u'HttpAuthTestUser'
+        auth_info = u'%s:%s' % (username, u'testpass')
+        auth_header = 'Basic %s' % auth_info.encode('base64')
         self.config = self.TestConfig(auth=[HTTPAuth()], user_autocreate=True)
         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)
+            request = self.run_request(path='/%s' % pagename,
+                                       HTTP_COOKIE=cookie,
+                                       HTTP_AUTHORIZATION=auth_header)
 
             # 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
 
             # check if the request resulted in normal status, result headers and content
             assert request.status == '200 OK'
@@ -185,8 +162,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 request.output()
 
             # The trail is only ever saved on the second page display
             # because otherwise anonymous sessions would be created
@@ -216,27 +192,24 @@
         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='action=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)
+                request = self.run_request(path='/%s' % pagename,
+                                           HTTP_COOKIE=cookie)
 
             # 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
 
             # check if the request resulted in normal status, result headers and content
             assert request.status == '200 OK'
@@ -258,8 +231,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 request.output()
 
             # The trail is only ever saved on the second page display
             # because otherwise anonymous sessions would be created
--- a/MoinMoin/auth/http.py	Tue Jul 08 16:58:19 2008 +0200
+++ b/MoinMoin/auth/http.py	Wed Jul 09 20:43:29 2008 +0200
@@ -32,45 +32,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:
             u.create_or_update()
--- a/MoinMoin/config/multiconfig.py	Tue Jul 08 16:58:19 2008 +0200
+++ b/MoinMoin/config/multiconfig.py	Wed Jul 09 20:43:29 2008 +0200
@@ -18,6 +18,7 @@
 
 from MoinMoin import config, error, util, wikiutil
 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
@@ -26,6 +27,7 @@
 from MoinMoin.packages import packLine
 from MoinMoin.security import AccessControlList
 from MoinMoin.support.python_compatibility import set
+from MoinMoin.web.session import FileSessionService
 
 _url_re_cache = None
 _farmconfig_mtime = None
--- a/MoinMoin/conftest.py	Tue Jul 08 16:58:19 2008 +0200
+++ b/MoinMoin/conftest.py	Wed Jul 09 20:43:29 2008 +0200
@@ -71,20 +71,15 @@
 
 
 def init_test_request(static_state=[False]):
-    from MoinMoin.request import request_cli
-    from MoinMoin.user import User
-    from MoinMoin.formatter.text_html import Formatter as HtmlFormatter
+    from MoinMoin.web.request import create_request
+    from MoinMoin.wsgiapp import init as request_init
     if not static_state[0]:
         maketestwiki.run(True)
         static_state[0] = True
-    request = request_cli.Request()
-    request.form = request.args = request.setup_args()
-    request.user = User(request)
-    request.html_formatter = HtmlFormatter(request)
-    request.formatter = request.html_formatter
+    request = create_request()
+    request = request_init(request)
     return request
 
-
 class TestConfig:
     """ Custom configuration for unit tests
 
--- a/MoinMoin/converter/text_html_text_moin_wiki.py	Tue Jul 08 16:58:19 2008 +0200
+++ b/MoinMoin/converter/text_html_text_moin_wiki.py	Wed Jul 09 20:43:29 2008 +0200
@@ -1120,7 +1120,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/formatter/__init__.py	Tue Jul 08 16:58:19 2008 +0200
+++ b/MoinMoin/formatter/__init__.py	Wed Jul 09 20:43:29 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_html.py	Tue Jul 08 16:58:19 2008 +0200
+++ b/MoinMoin/formatter/text_html.py	Wed Jul 09 20:43:29 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/logfile/editlog.py	Tue Jul 08 16:58:19 2008 +0200
+++ b/MoinMoin/logfile/editlog.py	Wed Jul 09 20:43:29 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 Jul 08 16:58:19 2008 +0200
+++ b/MoinMoin/macro/AdvancedSearch.py	Wed Jul 09 20:43:29 2008 +0200
@@ -161,7 +161,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/%s">' % (macro.request.script_root, wikiutil.quoteWikinameURL(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 Jul 08 16:58:19 2008 +0200
+++ b/MoinMoin/macro/FullSearch.py	Wed Jul 09 20:43:29 2008 +0200
@@ -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/%s">' % (macro.request.script_root, wikiutil.quoteWikinameURL(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/NewPage.py	Tue Jul 08 16:58:19 2008 +0200
+++ b/MoinMoin/macro/NewPage.py	Wed Jul 09 20:43:29 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/%s"><div>' % (self.request.script_root, wikiutil.quoteWikinameURL(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 Jul 08 16:58:19 2008 +0200
+++ b/MoinMoin/macro/TableOfContents.py	Wed Jul 09 20:43:29 2008 +0200
@@ -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
@@ -219,7 +219,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/__init__.py	Tue Jul 08 16:58:19 2008 +0200
+++ b/MoinMoin/macro/__init__.py	Wed Jul 09 20:43:29 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/%s"><div>' % (self.request.script_root, wikiutil.quoteWikinameURL(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 Jul 08 16:58:19 2008 +0200
+++ b/MoinMoin/macro/_tests/test_Action.py	Wed Jul 09 20:43:29 2008 +0200
@@ -40,7 +40,7 @@
         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
 
 
--- a/MoinMoin/macro/_tests/test_EmbedObject.py	Tue Jul 08 16:58:19 2008 +0200
+++ b/MoinMoin/macro/_tests/test_EmbedObject.py	Wed Jul 09 20:43:29 2008 +0200
@@ -71,6 +71,7 @@
         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.ogg"' in result
         assert 'align="middle"' in result
         assert 'value="transparent"' in result
 
@@ -81,6 +82,7 @@
         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.ogg"' in result
         assert 'height="50%"' in result
         assert 'align="middle"' in result
 
--- a/MoinMoin/macro/_tests/test_StatsChart.py	Tue Jul 08 16:58:19 2008 +0200
+++ b/MoinMoin/macro/_tests/test_StatsChart.py	Wed Jul 09 20:43:29 2008 +0200
@@ -48,19 +48,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 Jul 08 16:58:19 2008 +0200
+++ b/MoinMoin/mail/mailimport.py	Wed Jul 09 20:43:29 2008 +0200
@@ -184,7 +184,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("}}}", "} } }")
--- a/MoinMoin/parser/_tests/test_text_creole.py	Tue Jul 08 16:58:19 2008 +0200
+++ b/MoinMoin/parser/_tests/test_text_creole.py	Wed Jul 09 20:43:29 2008 +0200
@@ -387,14 +387,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 Jul 08 16:58:19 2008 +0200
+++ b/MoinMoin/parser/_tests/test_text_moin_wiki.py	Wed Jul 09 20:43:29 2008 +0200
@@ -518,20 +518,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 Jul 08 16:58:19 2008 +0200
+++ b/MoinMoin/request/__init__.py	Wed Jul 09 20:43:29 2008 +0200
@@ -47,6 +47,7 @@
 
 from MoinMoin.Page import Page
 from MoinMoin import config, wikiutil, user, caching, error
+from MoinMoin.action import get_names, get_available_actions
 from MoinMoin.config import multiconfig
 from MoinMoin.support.python_compatibility import set
 from MoinMoin.util import IsWin9x
@@ -125,10 +126,6 @@
         # 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 = {}
 
@@ -195,9 +192,6 @@
             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)
@@ -779,58 +773,11 @@
             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
+        """ DEPRECATED! use MoinMoin.action.get_available_actions instead
         """
-        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)
+        if getattr(self, '_available_actions', None) is None:
+            self._available_actions = get_available_actions(self.cfg, page, self.user)
 
         # Return a copy, so clients will not change the dict.
         return self._available_actions.copy()
@@ -968,48 +915,7 @@
         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):
+    def read(self, n=None):
         """ Read n bytes from input stream. """
         raise NotImplementedError
 
@@ -1117,7 +1023,7 @@
 
     def getBaseURL(self):
         """ Return a fully qualified URL to this script. """
-        return self.getQualifiedURL(self.getScriptname())
+        return self.getQualifiedURL(self.script_root)
 
     def getQualifiedURL(self, uri=''):
         """ Return an absolute URL starting with schema and host.
@@ -1179,6 +1085,7 @@
         self.loadTheme(theme_name)
 
     def _try_redirect_spaces_page(self, pagename):
+        logging.info('redirect %s', pagename)
         if '_' in pagename and not self.page.exists():
             pname = pagename.replace('_', ' ')
             pg = Page(self, pname)
@@ -1190,7 +1097,7 @@
 
     def run(self):
         # Exit now if __init__ failed or request is forbidden
-        if self.failed or self.forbidden or self._auth_redirected:
+        if self.forbidden or self._auth_redirected:
             # Don't sleep() here, it binds too much of our resources!
             return self.finish()
 
@@ -1230,7 +1137,7 @@
                     path = '/' + path
 
             if path.startswith('/'):
-                pagename = self.normalizePagename(path)
+                pagename = wikiutil.normalize_pagename(path, self.cfg)
             else:
                 pagename = None
 
@@ -1275,7 +1182,7 @@
 
                 msg = None
                 # Complain about unknown actions
-                if not action_name in self.getKnownActions():
+                if not action_name in get_names(self.cfg):
                     msg = _("Unknown action %(action_name)s.") % {
                             'action_name': wikiutil.escape(action_name), }
 
@@ -1315,12 +1222,6 @@
             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)
@@ -1432,23 +1333,6 @@
         # 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.
@@ -1526,45 +1410,6 @@
         '''
         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.
 
--- a/MoinMoin/request/_tests/test_request.py	Tue Jul 08 16:58:19 2008 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,142 +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.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 setup_method(self, method):
-        self.config = self.TestConfig(page_group_regex=r'.+Group')
-
-    def teardown_method(self, method):
-        del self.config
-
-    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/session.py	Tue Jul 08 16:58:19 2008 +0200
+++ b/MoinMoin/session.py	Wed Jul 09 20:43:29 2008 +0200
@@ -13,6 +13,8 @@
 
 import Cookie
 
+from werkzeug.utils import cookie_date
+
 from MoinMoin import log
 logging = log.getLogger(__name__)
 
@@ -312,21 +314,23 @@
         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)
         return cookie.output()
 
     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()
 
@@ -337,8 +341,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]
@@ -347,7 +353,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
 
@@ -407,12 +413,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 Jul 08 16:58:19 2008 +0200
+++ b/MoinMoin/stats/hitcounts.py	Wed Jul 09 20:43:29 2008 +0200
@@ -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 Jul 08 16:58:19 2008 +0200
+++ b/MoinMoin/stats/pagesize.py	Wed Jul 09 20:43:29 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 Jul 08 16:58:19 2008 +0200
+++ b/MoinMoin/stats/useragents.py	Wed Jul 09 20:43:29 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/parsedatetime/parsedatetime_consts.py	Tue Jul 08 16:58:19 2008 +0200
+++ b/MoinMoin/support/parsedatetime/parsedatetime_consts.py	Wed Jul 09 20:43:29 2008 +0200
@@ -574,4 +574,4 @@
 
         return sources
 
-        
\ No newline at end of file
+        
--- a/MoinMoin/theme/__init__.py	Tue Jul 08 16:58:19 2008 +0200
+++ b/MoinMoin/theme/__init__.py	Wed Jul 09 20:43:29 2008 +0200
@@ -7,6 +7,7 @@
 """
 
 from MoinMoin import i18n, wikiutil, config, version, caching
+from MoinMoin.action import get_available_actions
 from MoinMoin.Page import Page
 from MoinMoin.util import pysupport
 
@@ -361,7 +362,7 @@
             pass
 
         # Handle regular pagename like "FrontPage"
-        pagename = request.normalizePagename(pagename)
+        pagename = wikiutil.normalize_pagename(pagename, request.cfg)
 
         # Use localized pages for the current user
         if localize:
@@ -743,7 +744,7 @@
             'search_value': wikiutil.escape(form.get('value', [''])[0], 1),
             'search_full_label': _('Text'),
             'search_title_label': _('Titles'),
-            'baseurl': self.request.getScriptname(),
+            'baseurl': self.request.script_root,
             'pagename_quoted': wikiutil.quoteWikinameURL(d['page'].page_name),
             }
         d.update(updates)
@@ -980,7 +981,7 @@
         disabled = ' disabled class="disabled"'
 
         # Format standard actions
-        available = request.getAvailableActions(page)
+        available = get_available_actions(request.cfg, page, request.user)
         for action in menu:
             data = {'action': action, 'disabled': '', 'title': titles[action]}
             # removes excluded actions from the more actions menu
@@ -1051,7 +1052,7 @@
             'options': '\n'.join(options),
             'rev_field': rev and '<input type="hidden" name="rev" value="%d">' % rev or '',
             'do_button': _("Do"),
-            'baseurl': self.request.getScriptname(),
+            'baseurl': self.request.script_root,
             'pagename_quoted': wikiutil.quoteWikinameURL(page.page_name),
             }
         html = '''
@@ -1554,7 +1555,7 @@
             page = Page(request, pagename)
         if keywords.get('msg', ''):
             raise DeprecationWarning("Using send_page(msg=) is deprecated! Use theme.add_msg() instead!")
-        scriptname = request.getScriptname()
+        scriptname = request.script_root
         pagename_quoted = wikiutil.quoteWikinameURL(pagename)
 
         # get name of system pages
@@ -1586,7 +1587,7 @@
         # if it is an action or edit/search, send query headers (noindex,nofollow):
         if request.query_string:
             user_head.append(request.cfg.html_head_queries)
-        elif request.request_method == 'POST':
+        elif request.method == 'POST':
             user_head.append(request.cfg.html_head_posts)
         # we don't want to have BadContent stuff indexed:
         elif pagename in ['BadContent', 'LocalBadContent', ]:
@@ -1648,12 +1649,12 @@
             #~         # this shopuld never happend in theory, but let's be sure
             #~         pass
             #~     else:
-            #~         request.write('<link rel="First" href="%s/%s">\n' % (request.getScriptname(), quoteWikinameURL(all_pages[0]))
+            #~         request.write('<link rel="First" href="%s/%s">\n' % (request.script_root, quoteWikinameURL(all_pages[0]))
             #~         if pos > 0:
-            #~             request.write('<link rel="Previous" href="%s/%s">\n' % (request.getScriptname(), quoteWikinameURL(all_pages[pos-1])))
+            #~             request.write('<link rel="Previous" href="%s/%s">\n' % (request.script_root, quoteWikinameURL(all_pages[pos-1])))
             #~         if pos+1 < len(all_pages):
-            #~             request.write('<link rel="Next" href="%s/%s">\n' % (request.getScriptname(), quoteWikinameURL(all_pages[pos+1])))
-            #~         request.write('<link rel="Last" href="%s/%s">\n' % (request.getScriptname(), quoteWikinameURL(all_pages[-1])))
+            #~             request.write('<link rel="Next" href="%s/%s">\n' % (request.script_root, quoteWikinameURL(all_pages[pos+1])))
+            #~         request.write('<link rel="Last" href="%s/%s">\n' % (request.script_root, quoteWikinameURL(all_pages[-1])))
 
             if page_parent_page:
                 output.append('<link rel="Up" href="%s/%s">\n' % (scriptname, wikiutil.quoteWikinameURL(page_parent_page)))
@@ -1678,7 +1679,6 @@
         output.append("</head>\n")
         request.write(''.join(output))
         output = []
-        request.flush()
 
         # start the <body>
         bodyattr = []
@@ -1772,7 +1772,6 @@
         # emit it
         request.write(''.join(output))
         output = []
-        request.flush()
         self._send_title_called = True
 
     def send_footer(self, pagename, **keywords):
@@ -1813,4 +1812,48 @@
         #request.write('<!-- auth_method == %s -->' % repr(request.user.auth_method))
         request.write('</body>\n</html>\n\n')
 
+class ThemeNotFound(Exception):
+    """ Thrown if the supplied theme could not be found anywhere """
 
+def load_theme(request, theme_name=None):
+    """ Load a theme for this request.
+
+    @param request: moin request
+    @param theme_name: the name of the theme
+    @type theme_name: str
+    @rtype: Theme
+    @return: a theme initialized for the request
+    """
+    if theme_name is None or theme_name == '<default>':
+        theme_name = request.cfg.theme_default
+        
+    try:
+        Theme = wikiutil.importPlugin(request.cfg, 'theme', theme_name, 'Theme')
+    except wikiutil.PluginMissingError:
+        raise ThemeNotFound(theme_name)
+
+    return Theme(request)
+
+def load_theme_fallback(request, theme_name=None):
+    """ Try loading a theme, falling back to defaults on error.
+
+    @param request: moin request
+    @param theme_name: the name of the theme
+    @type theme_name: str
+    @rtype: int
+    @return: A statuscode for how successful the loading was
+             0 - theme was loaded
+             1 - fallback to default theme 
+             2 - serious fallback to builtin theme
+    """
+    fallback = 0
+    try:
+        request.theme = load_theme(request, theme_name)
+    except ThemeNotFound:
+        fallback = 1
+        try:
+            request.theme = load_theme(request.cfg.theme_default)
+        except ThemeNotFound:
+            fallback = 2
+            from MoinMoin.theme.modern import Theme
+            request.theme = Theme(request)
--- a/MoinMoin/theme/classic.py	Tue Jul 08 16:58:19 2008 +0200
+++ b/MoinMoin/theme/classic.py	Wed Jul 09 20:43:29 2008 +0200
@@ -13,6 +13,7 @@
 """
 
 from MoinMoin import caching
+from MoinMoin.action import get_available_actions
 from MoinMoin.theme import ThemeBase
 
 
@@ -183,7 +184,7 @@
         rev = d['rev']
         html = []
         page = d['page']
-        available = request.getAvailableActions(page)
+        available = get_available_actions(request.cfg, page, request.user)
         if available:
             available = list(available)
             available.sort()
--- a/MoinMoin/user.py	Tue Jul 08 16:58:19 2008 +0200
+++ b/MoinMoin/user.py	Wed Jul 09 20:43:29 2008 +0200
@@ -195,7 +195,7 @@
     @param name: user name, unicode
     """
     normalized = normalizeName(name)
-    return (name == normalized) and not wikiutil.isGroupPage(request, name)
+    return (name == normalized) and not wikiutil.isGroupPage(name, request.cfg)
 
 
 def encodeList(items):
--- a/MoinMoin/userform/login.py	Tue Jul 08 16:58:19 2008 +0200
+++ b/MoinMoin/userform/login.py	Wed Jul 09 20:43:29 2008 +0200
@@ -33,8 +33,8 @@
         """ Create the complete HTML form code. """
         _ = self._
         request = self.request
-        sn = request.getScriptname()
-        pi = request.getPathinfo()
+        sn = request.script_root
+        pi = request.path
         action = u"%s%s" % (sn, pi)
         hints = []
         for authm in request.cfg.auth:
--- a/MoinMoin/userprefs/changepass.py	Tue Jul 08 16:58:19 2008 +0200
+++ b/MoinMoin/userprefs/changepass.py	Wed Jul 09 20:43:29 2008 +0200
@@ -40,7 +40,7 @@
         if form.has_key('cancel'):
             return
 
-        if request.request_method != 'POST':
+        if request.method != 'POST':
             return
 
         password = form.get('password1', [''])[0]
--- a/MoinMoin/userprefs/notification.py	Tue Jul 08 16:58:19 2008 +0200
+++ b/MoinMoin/userprefs/notification.py	Wed Jul 09 20:43:29 2008 +0200
@@ -46,7 +46,7 @@
         _ = self._
         form = self.request.form
 
-        if self.request.request_method != 'POST':
+        if self.request.method != 'POST':
             return
         theuser = self.request.user
         if not theuser:
--- a/MoinMoin/userprefs/oid.py	Tue Jul 08 16:58:19 2008 +0200
+++ b/MoinMoin/userprefs/oid.py	Wed Jul 09 20:43:29 2008 +0200
@@ -138,7 +138,7 @@
         if form.has_key('cancel'):
             return
 
-        if self.request.request_method != 'POST':
+        if self.request.method != 'POST':
             return
 
         if form.has_key('remove'):
@@ -150,8 +150,8 @@
         return
 
     def _make_form(self):
-        sn = self.request.getScriptname()
-        pi = self.request.getPathinfo()
+        sn = self.request.script_root
+        pi = self.request.path
         action = u"%s%s" % (sn, pi)
         _form = html.FORM(action=action)
         _form.append(html.INPUT(type="hidden", name="action", value="userprefs"))
--- a/MoinMoin/userprefs/prefs.py	Tue Jul 08 16:58:19 2008 +0200
+++ b/MoinMoin/userprefs/prefs.py	Wed Jul 09 20:43:29 2008 +0200
@@ -9,6 +9,7 @@
 
 import time
 from MoinMoin import user, util, wikiutil, events
+from MoinMoin.theme import load_theme_fallback
 from MoinMoin.widget import html
 from MoinMoin.userprefs import UserPrefBase
 
@@ -61,7 +62,7 @@
         form = self.request.form
         request = self.request
 
-        if request.request_method != 'POST':
+        if request.method != 'POST':
             return
 
         if not 'name' in request.user.auth_attribs:
@@ -163,7 +164,7 @@
             # already loaded theme is just replaced (works cause
             # nothing has been emitted yet)
             request.user.theme_name = theme_name
-            if request.loadTheme(theme_name) > 0:
+            if load_theme_fallback(request, theme_name) > 0:
                 theme_name = wikiutil.escape(theme_name)
                 return 'error', _("The theme '%(theme_name)s' could not be loaded!") % locals()
 
--- a/MoinMoin/userprefs/suid.py	Tue Jul 08 16:58:19 2008 +0200
+++ b/MoinMoin/userprefs/suid.py	Wed Jul 09 20:43:29 2008 +0200
@@ -39,7 +39,7 @@
             return
 
         if (wikiutil.checkTicket(self.request, self.request.form['ticket'][0])
-            and self.request.request_method == 'POST'):
+            and self.request.method == 'POST'):
             uid = form.get('selected_user', [''])[0]
             if not uid:
                 return 'error', _("No user selected")
--- a/MoinMoin/util/filesys.py	Tue Jul 08 16:58:19 2008 +0200
+++ b/MoinMoin/util/filesys.py	Wed Jul 09 20:43:29 2008 +0200
@@ -212,12 +212,12 @@
         """
         try:
             from Carbon import File
+            try:
+                return File.FSRef(path).as_pathname()
+            except File.Error:
+                return None
         except ImportError:
             return None
-        try:
-            return File.FSRef(path).as_pathname()
-        except File.Error:
-            return None
 
 else:
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MoinMoin/web/__init__.py	Wed Jul 09 20:43:29 2008 +0200
@@ -0,0 +1,10 @@
+# -*- coding: iso-8859-1 -*-
+"""
+    MoinMoin - This package contains the interface between webserver
+               and application. This is meant to become a replacement
+               and/or port of code currently scattered in MoinMoin.request
+               and MoinMoin.server.
+
+    @copyright: 2008-2008 MoinMoin:FlorianKrupicka
+    @license: GNU GPL, see COPYING for details.
+"""
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MoinMoin/web/api.py	Wed Jul 09 20:43:29 2008 +0200
@@ -0,0 +1,40 @@
+# -*- coding: iso-8859-1 -*-
+"""
+    MoinMoin - Interface definitions
+
+    Interface definitions for the Request object and associated classes
+    and services.
+
+    @copyright: 2008 MoinMoin:FlorianKrupicka
+    @license: GNU GPL, see COPYING for details.
+"""
+
+class Interface(object):
+    """ An interface (marker class) """
+
+class IContext(Interface):
+    """
+    A context object represents the request in different phases of the
+    request/response cycle, e.g. session setup, page renderin (formatter
+    or parser), XML-RPC, etc.
+    """
+    def become(cls):
+        """ Become another context, based on the given class. """
+
+
+class ISessionService(Interface):
+    """
+    A session service returns a session object given a request object and
+    provides services like persisting sessions and cleaning up occasionally.
+    """
+    def get_session(request):
+        """ Return a session object pertaining to the particular request."""
+
+    def destroy_session(request, session):
+        """ Destroy an existing session (make it unusable). """
+
+    def finalize(request, session):
+        """
+        Do final modifications to the request and/or session before sending
+        headers and body to the cliebt.
+        """
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MoinMoin/web/apps.py	Wed Jul 09 20:43:29 2008 +0200
@@ -0,0 +1,41 @@
+# -*- coding: iso-8859-1 -*-
+"""
+    MoinMoin - WSGI apps and middlewares
+
+    @copyright: 2003-2008 MoinMoin:ThomasWaldmann,
+                2008-2008 MoinMoin:FlorianKrupicka
+    @license: GNU GPL, see COPYING for details.
+"""
+from werkzeug.exceptions import HTTPException
+
+from MoinMoin.web.request import Request
+from MoinMoin.web.contexts import XMLRPCContext
+
+class HTTPExceptionsMiddleware(object):
+    def __init__(self, app):
+        self.app = app
+
+    def __call__(self, environ, start_response):
+        try:
+            return self.app(environ, start_response)
+        except HTTPException, e:
+            return e(environ, start_response)
+
+class XMLRPCApp(object):
+    """ Handles XML-RPC method calls or dispatches to next layer """
+    
+    def __init__(self, app):
+        self.app = app
+        
+    def __call__(self, environ, start_response):
+        request = Request(environ)
+        action = request.args.get('action')
+        
+        from MoinMoin import xmlrpc
+
+        if action == 'xmlrpc':
+            return xmlrpc.xmlrpc(XMLRPCContext(environ))
+        elif action == 'xmlrpc2':
+            return xmlrpc.xmlrpc2(XMLRPCContext(environ))
+        else:
+            return self.app(environ, start_response)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MoinMoin/web/contexts.py	Wed Jul 09 20:43:29 2008 +0200
@@ -0,0 +1,383 @@
+# -*- coding: iso-8859-1 -*-
+"""
+    MoinMoin - Context objects which are passed thru instead of the classic
+               request objects. Currently contains legacy wrapper code for
+               a single request object.
+
+    @copyright: 2008-2008 MoinMoin:FlorianKrupicka
+    @license: GNU GPL, see COPYING for details.
+"""
+
+import time, inspect, StringIO
+
+from werkzeug.utils import Headers, http_date
+from werkzeug.exceptions import Unauthorized, NotFound
+
+from MoinMoin import i18n, error, user
+from MoinMoin.config import multiconfig
+from MoinMoin.formatter import text_html
+from MoinMoin.theme import load_theme_fallback
+from MoinMoin.util.clock import Clock
+from MoinMoin.web.request import Request
+from MoinMoin.web.utils import check_spider, UniqueIDGenerator
+from MoinMoin.web.exceptions import Forbidden, SurgeProtection
+from MoinMoin.web.api import IContext
+
+from MoinMoin import log
+logging = log.getLogger(__name__)
+default = object()
+
+class EnvironProxy(property):
+    """ Proxy attribute lookups to keys in the environ. """
+    def __init__(self, name, factory=default):
+        """
+        An entry will be proxied to the supplied name in the .environ
+        object of the property holder. A factory can be supplied, for
+        values that need to be preinstantiated. If given as first
+        parameter name is taken from the callable too.
+
+        @param name: key (or factory for convenience)
+        @param factory: literal object or callable
+        """
+        if not isinstance(name, basestring):
+            factory = name
+            name = factory.__name__
+        self.name = name
+        self.full_name = 'moin.%s' % name
+        self.factory = factory
+        property.__init__(self, self.get, self.set, self.delete)
+
+    def get(self, obj):
+        logging.debug("GET: '%s' on '%r'", self.name, obj)
+        if self.full_name in obj.environ:
+            res = obj.environ[self.full_name]
+        else:
+            factory = self.factory
+            if factory is default:
+                raise AttributeError(self.name)
+            elif hasattr(factory, '__call__'):
+                res = obj.environ.setdefault(self.full_name, factory(obj))
+            else:
+                res = obj.environ.setdefault(self.full_name, factory)
+        return res
+
+    def set(self, obj, value):
+        logging.debug("SET: '%s' on '%r' to '%r'", self.name, obj, value)
+        obj.environ[self.full_name] = value
+
+    def delete(self, obj):
+        logging.debug("DEL: '%s' on '%r'", self.name, obj)
+        del obj.environ[self.full_name]
+
+    def __repr__(self):
+        return "<%s for '%s'>" % (self.__class__.__name__,
+                                  self.full_name)
+
+class Context(object):
+    """ Standard implementation for the context interface.
+
+    This one wraps up a Moin-Request object and the associated
+    environ and also keeps track of it's changes.
+    """
+    __slots__ = ['request', 'environ']
+    __implements__ = (IContext, )
+
+    def __init__(self, request):
+        assert isinstance(request, Request)
+        self.request = request
+        self.environ = request.environ
+        self.personalities.append(self.__class__)
+
+    personalities = EnvironProxy('context.personalities', lambda o: list())
+
+    def become(self, cls):
+        """ Become another context, based on given class.
+
+        @param cls: class to change to, must be a sister class
+        @rtype: boolean
+        @return: wether a class change took place
+        """
+        if self.__class__ is cls:
+            return False
+        else:
+            self.personalities.append(cls)
+            self.__class__ = cls
+            return True
+
+class UserMixin(object):
+    """ Mixin for user attributes and methods. """
+    def user(self):
+        return user.User(self, auth_method='request:invalid')
+    user = EnvironProxy(user)
+
+class LanguageMixin(object):
+    """ Mixin for language attributes and methods. """
+    def lang(self):
+        for key in ('moin.user.lang', 'moin.request.lang'):
+            if key in self.environ:
+                return self.environ[key]
+
+        if i18n.languages is None:
+            i18n.i18n_init(self)
+        lang = None
+
+        user = getattr(self, 'user')
+        if user and user.valid and user.language:
+            lang = user.language
+            self.environ['moin.user.lang'] = lang
+        else:
+            if i18n.languages and not self.cfg.language_ignore_browser:
+                for l in self.request.accept_languages:
+                    if l in i18n.languages:
+                        lang = l
+                        break
+
+            if lang is None and self.cfg.language_default in i18n.languages:
+                lang = self.cfg.language_default
+            else:
+                lang = 'en'
+            self.environ['moin.request.lang'] = lang
+        return lang
+    lang = property(lang)
+
+    def getText(self):
+        lang = self.lang
+        def _(text, i18n=i18n, request=self, lang=lang, **kw):
+            return i18n.getText(text, request, lang, **kw)
+        return _
+    getText = EnvironProxy(getText)
+
+    def content_lang(self):
+        return self.cfg.language_default
+    content_lang = EnvironProxy(content_lang)
+    current_lang = EnvironProxy('current_lang')
+
+    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
+
+
+class HTTPMixin(object):
+    """ Mixin for HTTP attributes and methods. """
+    forbidden = EnvironProxy('old.forbidden', 0)
+    session = EnvironProxy('session')
+
+    _auth_redirected = EnvironProxy('old._auth_redirected', 0)
+    _cache_disabled = EnvironProxy('old._cache_disabled', 0)
+    cacheable = EnvironProxy('old.cacheable', 0)
+
+    def write(self, *data):
+        if len(data) > 1:
+            logging.warning("Some code still uses write with multiple arguments, "
+                            "consider changing this soon")
+        self.request.stream.writelines(data)
+
+    # implementation of methods expected by RequestBase
+    def send_file(self, fileobj, bufsize=8192, do_flush=None):
+        pass
+
+    def read(self, n=None):
+        if n is None:
+            return self.request.in_data
+        else:
+            return self.request.input_stream.read(n)
+
+    def makeForbidden(self, resultcode, msg):
+        status = {401: Unauthorized,
+                  403: Forbidden,
+                  404: NotFound,
+                  503: SurgeProtection}
+        raise status[resultcode](msg)
+
+    def setHttpHeader(self, header):
+        header, value = header.split(':', 1)
+        self.headers.add(header, value)
+
+    def disableHttpCaching(self, level=1):
+        if level <= self._cache_disabled:
+            return
+
+        if level == 1:
+            self.headers.add('Cache-Control', 'private, must-revalidate, mag-age=10')
+        elif level == 2:
+            self.headers.add('Cache-Control', 'no-cache')
+            self.headers.set('Pragma', 'no-cache')
+
+        if not self._cache_disabled:
+            when = time.time() - (3600 * 24 * 365)
+            self.headers.set('Expires', http_date(when))
+
+        self._cache_disabled = level
+
+    def isSpiderAgent(self):
+        return check_spider(self.request.user_agent, self.cfg)
+    isSpiderAgent = EnvironProxy(isSpiderAgent)
+
+class ActionMixin(object):
+    """ Mixin for the action related attributes. """
+    def action(self):
+        return self.request.values.get('action', 'show')
+    action = EnvironProxy(action)
+
+    def rev(self):
+        try:
+            return int(self.values['rev'])
+        except:
+            return None
+    rev = EnvironProxy(rev)
+
+class ConfigMixin(object):
+    """ Mixin for the everneeded config object. """
+    def cfg(self):
+        try:
+            self.clock.start('load_multi_cfg')
+            cfg = multiconfig.getConfig(self.request.url)
+            self.clock.stop('load_multi_cfg')
+            return cfg
+        except error.NoConfigMatchedError:
+            raise NotFound('<p>No wiki configuration matching the URL found!</p>')
+    cfg = EnvironProxy(cfg)
+
+class FormatterMixin(object):
+    """ Mixin for the standard formatter attributes. """
+    def html_formatter(self):
+        return text_html.Formatter(self)
+    html_formatter = EnvironProxy(html_formatter)
+
+    def formatter(self):
+        return self.html_formatter
+    formatter = EnvironProxy(formatter)
+
+class PageMixin(object):
+    """ Mixin for ondemand rootpage. """
+    page = EnvironProxy('page', None)
+    def rootpage(self):
+        from MoinMoin.Page import RootPage
+        return RootPage(self)
+    rootpage = EnvironProxy(rootpage)
+
+class AuxilaryMixin(object):
+    """
+    Mixin for diverse attributes and methods that aren't clearly assignable
+    to a particular phase of the request.
+    """
+    _fmt_hd_counters = EnvironProxy('_fmt_hd_counters')
+    parsePageLinks_running = EnvironProxy('parsePageLinks_running', lambda o: {})
+    mode_getpagelinks = EnvironProxy('mode_getpagelinks', 0)
+    clock = EnvironProxy('clock', lambda o: Clock())
+    pragma = EnvironProxy('pragma', lambda o: {})
+    _login_messages = EnvironProxy('_login_messages', lambda o: [])
+    _login_multistage = EnvironProxy('_login_multistage', None)
+    _setuid_real_user = EnvironProxy('_setuid_real_user', None)
+    pages = EnvironProxy('pages', lambda o: {})
+
+    def uid_generator(self):
+        pagename = None
+        if hasattr(self, 'page') and hasattr(self.page, 'page_name'):
+            pagename = self.page.page_name
+        return UniqueIDGenerator(pagename=pagename)
+    uid_generator = EnvironProxy(uid_generator)
+
+    def dicts(self):
+        """ Lazy initialize the dicts on the first access """
+        from MoinMoin import wikidicts
+        dicts = wikidicts.GroupDict(self)
+        dicts.load_dicts()
+        return dicts
+    dicts = EnvironProxy(dicts)
+
+    def reset(self):
+        self.current_lang = self.cfg.language_default
+        if hasattr(self, '_fmt_hd_counters'):
+            del self._fmt_hd_counters
+        if hasattr(self, 'uid_generator'):
+            del self.uid_generator
+
+    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
+
+class ThemeMixin(object):
+    """ Mixin for the theme attributes and methods. """
+    def _theme(self):
+        self.initTheme()
+        return self.theme
+    theme = EnvironProxy('theme', _theme)
+
+    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
+        load_theme_fallback(self, theme_name)
+
+class RedirectMixin(object):
+    """ Mixin to redirect output into buffers instead to the client. """
+    writestack = EnvironProxy('old.writestack', lambda o: list())
+
+    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()
+
+class HTTPContext(Context, HTTPMixin, ConfigMixin, UserMixin,
+                  LanguageMixin, AuxilaryMixin):
+    """ Context to act mainly in HTTP handling related phases. """
+    def __getattr__(self, name):
+        try:
+            return getattr(self.request, name)
+        except AttributeError, e:
+            return super(HTTPContext, self).__getattribute__(name)
+
+class RenderContext(Context, RedirectMixin, ConfigMixin, UserMixin,
+                    LanguageMixin, ThemeMixin, AuxilaryMixin,
+                    ActionMixin, PageMixin, FormatterMixin):
+    """ Context to act during the rendering phase. """
+    def write(self, *data):
+        if len(data) > 1:
+            logging.warning("Some code still uses write with multiple arguments, "
+                            "consider changing this soon")
+        self.request.stream.writelines(data)
+
+    def output(self):
+        return self.request()
+
+# TODO: extend xmlrpc context
+class XMLRPCContext(HTTPContext):
+    """ Context to act during a XMLRPC request. """
+
+class AllContext(HTTPContext, RenderContext):
+    """ Catchall context to be able to quickly test old Moin code. """
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MoinMoin/web/exceptions.py	Wed Jul 09 20:43:29 2008 +0200
@@ -0,0 +1,33 @@
+# -*- coding: iso-8859-1 -*-
+"""
+    MoinMoin - HTTP exceptions
+
+    @copyright: 2008-2008 MoinMoin:FlorianKrupicka
+    @license: GNU GPL, see COPYING for details.
+"""
+
+from werkzeug import exceptions
+
+class SurgeProtection(exceptions.ServiceUnavailable):
+    name = 'Surge protection'
+    description = (
+        "<strong>Warning:</strong>"
+        "<p>You triggered the wiki's surge protection by doing too many requests in a short time.</p>"
+        "<p>Please make a short break reading the stuff you already got.</p>"
+        "<p>When you restart doing requests AFTER that, slow down or you might get locked out for a longer time!</p>"
+    )
+    
+    def __init__(self, description=None, retry_after=3600):
+        exceptions.ServiceUnavailable.__init__(self, description)
+        self.retry_after = retry_after
+
+    def get_headers(self, environ):
+        headers = exceptions.ServiceUnavailable.get_headers(self, environ)
+        headers.append(('Retry-After', '%d' % self.retry_after))
+        return headers
+
+class Forbidden(exceptions.Forbidden):
+    description = "<p>You are not allowed to access this!</p>"
+
+class PageNotFound(exceptions.NotFound):
+    pass
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MoinMoin/web/request.py	Wed Jul 09 20:43:29 2008 +0200
@@ -0,0 +1,104 @@
+# -*- coding: iso-8859-1 -*-
+"""
+    MoinMoin - New slimmed down WSGI Request.
+
+    @copyright: 2008-2008 MoinMoin:FlorianKrupicka
+    @license: GNU GPL, see COPYING for details.
+"""
+
+import re
+from StringIO import StringIO
+
+from werkzeug.wrappers import Request as WerkzeugRequest
+from werkzeug.wrappers import Response as WerkzeugResponse
+from werkzeug.utils import EnvironHeaders, cached_property
+from werkzeug.utils import create_environ, url_encode
+
+from MoinMoin import config
+
+from MoinMoin import log
+logging = log.getLogger(__name__)
+
+class Request(WerkzeugRequest, WerkzeugResponse):
+    """ A full featured Request/Response object.
+
+    To better distinguish incoming and outgoing data/headers,
+    incoming versions are prefixed with 'in_' in contrast to
+    original Werkzeug implementation.
+    """
+    charset = config.charset
+    encoding_errors = 'replace'
+    default_mimetype = 'text/html'
+
+    def __init__(self, environ, populate_request=True, shallow=False,
+                 response=None, status=None, headers=None, mimetype=None,
+                 content_type=None):
+        WerkzeugRequest.__init__(self, environ, populate_request, shallow)
+        WerkzeugResponse.__init__(self, response, status, headers,
+                                  mimetype, content_type)
+
+    data = WerkzeugResponse.data
+    stream = WerkzeugResponse.stream
+    cache_control = WerkzeugResponse.cache_control
+
+    def in_cache_control(self):
+        cache_control = self.environ.get('HTTP_CACHE_CONTROL')
+        return parse_cache_control_header(cache_control)
+    in_cache_control = cached_property(in_cache_control)
+
+    def in_headers(self):
+        return EnvironHeaders(self.environ)
+    in_headers = cached_property(in_headers, doc=WerkzeugRequest.headers.__doc__)
+
+    def in_stream(self):
+        if self._data_stream is None:
+            self._load_form_data()
+        return self._data_stream
+    in_stream = property(in_stream, doc=WerkzeugRequest.stream.__doc__)
+
+    def in_data(self):
+        return self.in_stream.read()
+    in_data = cached_property(in_data, doc=WerkzeugRequest.data.__doc__)
+
+class TestRequest(Request):
+    def __init__(self, path="/", query_string=None, method='GET',
+                 input_stream=None, content_type=None, content_length=0,
+                 form_data=None, **env):
+        self.errors_stream = StringIO()
+        self.output_stream = StringIO()
+
+        if form_data is not None:
+            form_data = url_encode(form_data)
+            content_type = 'application/x-www-form-urlencoded'
+            content_length = len(form_data)
+            input_stream = StringIO(form_data)
+        environ = create_environ(path=path, query_string=query_string,
+                                 method=method, input_stream=input_stream,
+                                 content_type=content_type,
+                                 content_length=content_length,
+                                 errors_stream=self.errors_stream)
+
+        for k,v in env.items():
+            environ[k] = v
+
+        environ['HTTP_USER_AGENT'] = 'MoinMoin/TestRequest'
+        super(TestRequest, self).__init__(environ)
+
+    def __call__(self):
+        def start_response(status, headers, exc_info=None):
+            return self.output_stream.write
+
+        appiter = Request.__call__(self, self.environ, start_response)
+        for s in appiter:
+            self.output_stream.write(s)
+        return self.output_stream.getvalue()
+
+    def output(self):
+        """ Content of the WSGI output stream. """
+        return self.output_stream.getvalue()
+    output = property(output, doc=output.__doc__)
+
+    def errors(self):
+        """ Content of the WSGI error stream. """
+        return self.errors_stream.getvalue()
+    errors = property(errors, doc=errors.__doc__)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MoinMoin/web/session.py	Wed Jul 09 20:43:29 2008 +0200
@@ -0,0 +1,73 @@
+# -*- coding: iso-8859-1 -*-
+"""
+    MoinMoin - WSGI session handling
+
+    Session handling
+
+    @copyright: 2008 MoinMoin:FlorianKrupicka
+    @license: GNU GPL, see COPYING for details.
+"""
+import time
+
+from werkzeug.utils import dump_cookie
+from werkzeug.contrib.sessions import FilesystemSessionStore, Session
+
+from MoinMoin.web.api import ISessionService
+from MoinMoin import caching
+
+from MoinMoin import log
+logging = log.getLogger(__name__)
+
+class MoinSession(Session):
+    """ Compatibility interface to Werkzeug-sessions for old Moin-code. """
+    is_new = property(lambda s: s.new)
+    is_stored = property(lambda s: True)
+
+class FileSessionService(object):
+    """
+    This sample session service stores session information in a temporary
+    directory and identifis the session via a cookie in the request/response
+    cycle.
+    """
+
+    __implements__ = (ISessionService, )
+
+    def __init__(self, cookie_name='MOIN_SESSION'):
+        self.store = FilesystemSessionStore(session_class=MoinSession)
+        self.cookie_name = cookie_name
+
+    def get_session(self, request):
+        sid = request.cookies.get(self.cookie_name, None)
+        if sid is None:
+            session = self.store.new()
+        else:
+            session = self.store.get(sid)
+        return session
+
+    def destroy_session(self, request, session):
+        session.clear()
+        self.store.delete(session)
+        session.modified = session.new = False
+
+    def finalize(self, request, session):
+        userobj = request.user
+        if userobj and userobj.valid:
+            if 'user.id' in session and session['user.id'] != userobj.id:
+                request.cfg.session_service.delete(session)
+            session['user.id'] = userobj.id
+            session['user.auth_method'] = userobj.auth_method
+            session['user.auth_attribs'] = userobj.auth_attribs
+            logging.debug("after auth: storing valid user into session: %r" % userobj.name)
+        else:
+            if 'user.id' in session:
+                self.destroy_session(request, session)
+
+        if session.should_save:
+            self.store.save(session)
+            cookie_lifetime = request.cfg.cookie_lifetime * 3600
+            cookie_expires = time.time() + cookie_lifetime
+            cookie = dump_cookie(self.cookie_name, session.sid,
+                                 cookie_lifetime, cookie_expires,
+                                 request.cfg.cookie_domain,
+                                 request.cfg.cookie_path)
+            request.headers.add('Set-Cookie', cookie)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MoinMoin/web/utils.py	Wed Jul 09 20:43:29 2008 +0200
@@ -0,0 +1,251 @@
+# -*- coding: iso-8859-1 -*-
+"""
+    MoinMoin - Utility functions for the web-layer
+
+    @copyright: 2003-2008 MoinMoin:ThomasWaldmann,
+                2008-2008 MoinMoin:FlorianKrupicka
+    @license: GNU GPL, see COPYING for details.
+"""
+import time
+
+from werkzeug.exceptions import abort
+from werkzeug.utils import redirect
+
+from MoinMoin import log
+from MoinMoin import wikiutil
+from MoinMoin.Page import Page
+from MoinMoin.web.exceptions import Forbidden, SurgeProtection
+
+logging = log.getLogger(__name__)
+
+def check_spider(useragent, cfg):
+    """ Simple check if useragent is a spider bot
+
+    @param useragent: werkzeug.useragents.UserAgent
+    @param cfg: wikiconfig instance
+    """
+    is_spider = False
+    if useragent and cfg.cache.ua_spiders:
+        is_spider = cfg.cache.ua_spiders.search(useragent.browser) is not None
+    return is_spider
+
+def check_setuid(request, userobj):
+    """ Check for setuid conditions.
+    Returns a tuple of either new user and old user
+    or just user and None.
+
+    @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 check_forbidden(request):
+    """ Simple action and host access checks
+
+    Spider agents are checked against the called actions,
+    hosts against the blacklist. Raises Forbidden if triggered.
+    """
+    args = request.args
+    action = args.get('action')
+    if ((args or request.method != 'GET') and
+        action not in ['rss_rc', 'show', 'sitemap'] and
+        not (action == 'AttachFile' and args.get('do') == 'get')):
+        if check_spider(request.user_agent, request.cfg):
+            raise Forbidden()
+    if request.cfg.hosts_deny:
+        remote_addr = request.remote_addr
+        for host in request.cfg.hosts_deny:
+            if host[-1] == '.' and remote_addr.startswith(host):
+                logging.debug("hosts_deny (net): %s" % str(forbidden))
+                raise Forbidden()
+            if remote_addr == host:
+                logging.debug("hosts_deny (ip): %s" % str(forbidden))
+                raise Forbidden()
+    return False
+
+def check_surge_protect(request, kick=False):
+    """ Check for excessive requests
+
+    Raises a SurgeProtection exception on wiki overuse.
+
+    @param request: a moin request object
+    """
+    limits = request.cfg.surge_action_limits
+    if not limits:
+        return False
+
+    validuser = request.user.valid
+    current_id = validuser and request.user.name or request.remote_addr or ''
+
+    if not validuser and current_id.startswith('127.'): # localnet
+        return False
+    current_action = request.action
+
+    default_limit = request.cfg.surge_action_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(request, '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 + request.cfg.surge_lockout_time, surge_indicator)) # continue like that and get locked out
+
+        if current_action != 'AttachFile': # don't add 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 + request.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 + request.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:
+        raise SurgeProtection(retry_after=request.cfg.surge_lockout_time)
+    else:
+        return False
+
+def redirect_last_visited(request):
+    pagetrail = request.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(request, wikiname, pagename)
+            url = wikiurl + wikiutil.quoteWikinameURL(wikitail)
+        else:
+            url = Page(request, pagename).url(request)
+    else:
+        # Or to localized FrontPage
+        url = wikiutil.getFrontPage(request).url(request)
+    url = request.getQualifiedURL(url)
+    return abort(redirect(url))
+
+class UniqueIDGenerator(object):
+    def __init__(self, pagename=None):
+        self.unique_stack = []
+        self.include_stack = []
+        self.include_id = None
+        self.page_ids = {None: {}}
+        self.pagename = pagename
+
+    def push(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_stack.append((self.page_ids, self.include_id))
+        self.include_id, pids = self.include_stack[-1]
+        self.page_ids = {}
+        for namespace in pids:
+            self.page_ids[namespace] = pids[namespace].copy()
+
+    def pop(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_stack.pop()
+        return self.page_ids, self.include_id
+
+    def begin(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(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.pagename and self.pagename == self.include_id:
+            self.include_id = None
+
+    def end(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().
+        """
+        self.include_id, pids = self.include_stack.pop()
+
+    def __call__(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)
--- a/MoinMoin/widget/browser.py	Tue Jul 08 16:58:19 2008 +0200
+++ b/MoinMoin/widget/browser.py	Wed Jul 09 20:43:29 2008 +0200
@@ -97,7 +97,7 @@
         fmt = self.request.formatter
 
         result = []
-        result.append(fmt.rawHTML('<form action="%s/%s" method="GET" name="%sform">' % (self.request.getScriptname(), wikiutil.quoteWikinameURL(self.request.page.page_name), self.data_id)))
+        result.append(fmt.rawHTML('<form action="%s/%s" method="GET" name="%sform">' % (self.request.script_root, wikiutil.quoteWikinameURL(self.request.page.page_name), self.data_id)))
         result.append(fmt.div(1))
 
         havefilters = False
--- a/MoinMoin/wikiutil.py	Tue Jul 08 16:58:19 2008 +0200
+++ b/MoinMoin/wikiutil.py	Wed Jul 09 20:43:29 2008 +0200
@@ -563,7 +563,7 @@
             if not line or line[0] == '#':
                 continue
             try:
-                line = "%s %s/InterWiki" % (line, request.getScriptname())
+                line = "%s %s/InterWiki" % (line, request.script_root)
                 wikitag, urlprefix, dummy = line.split(None, 2)
             except ValueError:
                 pass
@@ -573,9 +573,9 @@
         del lines
 
         # add own wiki as "Self" and by its configured name
-        _interwiki_list['Self'] = request.getScriptname() + '/'
+        _interwiki_list['Self'] = request.script_root + '/'
         if request.cfg.interwikiname:
-            _interwiki_list[request.cfg.interwikiname] = request.getScriptname() + '/'
+            _interwiki_list[request.cfg.interwikiname] = request.script_root + '/'
 
         # save for later
         request.cfg.cache.interwiki_list = _interwiki_list
@@ -647,7 +647,7 @@
     if wikiname in _interwiki_list:
         return (wikiname, _interwiki_list[wikiname], pagename, False)
     else:
-        return (wikiname, request.getScriptname(), "/InterWiki", True)
+        return (wikiname, request.script_root, "/InterWiki", True)
 
 def resolve_interwiki(request, wikiname, pagename):
     """ Resolve an interwiki reference (wikiname:pagename).
@@ -662,7 +662,7 @@
     if wikiname in _interwiki_list:
         return (wikiname, _interwiki_list[wikiname], pagename, False)
     else:
-        return (wikiname, request.getScriptname(), "/InterWiki", True)
+        return (wikiname, request.script_root, "/InterWiki", True)
 
 def join_wiki(wikiurl, wikitail):
     """
@@ -709,14 +709,14 @@
     return request.cfg.cache.page_template_regexact.search(pagename) is not None
 
 
-def isGroupPage(request, pagename):
+def isGroupPage(pagename, cfg):
     """ Is this a name of group page?
 
     @param pagename: the page name
     @rtype: bool
     @return: true if page is a form page
     """
-    return request.cfg.cache.page_group_regexact.search(pagename) is not None
+    return cfg.cache.page_group_regexact.search(pagename) is not None
 
 
 def filterCategoryPages(request, pagelist):
@@ -2239,6 +2239,47 @@
 #############################################################################
 ### Misc
 #############################################################################
+def normalize_pagename(name, cfg):
+    """ 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 isGroupPage(page, cfg):
+            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 taintfilename(basename):
     """
     Make a filename that is supposed to be a plain name secure, i.e.
@@ -2356,7 +2397,7 @@
     if text is None:
         text = params # default
     if formatter:
-        url = "%s/%s" % (request.getScriptname(), params)
+        url = "%s/%s" % (request.script_root, params)
         # formatter.url will escape the url part
         if on is not None:
             tag = formatter.url(on, url, css_class, **kw)
@@ -2375,7 +2416,7 @@
                 attrs += ' id="%s"' % id
             if name:
                 attrs += ' name="%s"' % name
-            tag = '<a%s href="%s/%s">' % (attrs, request.getScriptname(), params)
+            tag = '<a%s href="%s/%s">' % (attrs, request.script_root, params)
             if not on:
                 tag = "%s%s</a>" % (tag, text)
         logging.warning("wikiutil.link_tag called without formatter and without request.html_formatter. tag=%r" % (tag, ))
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MoinMoin/wsgiapp.py	Wed Jul 09 20:43:29 2008 +0200
@@ -0,0 +1,214 @@
+# -*- coding: iso-8859-1 -*-
+"""
+    MoinMoin - WSGI application
+
+    @copyright: 2003-2008 MoinMoin:ThomasWaldmann,
+                2008-2008 MoinMoin:FlorianKrupicka
+    @license: GNU GPL, see COPYING for details.
+"""
+from werkzeug.utils import responder
+from werkzeug.wrappers import Response
+from werkzeug.exceptions import NotFound
+
+from MoinMoin.web.contexts import HTTPContext, RenderContext, AllContext
+from MoinMoin.web.request import Request
+from MoinMoin.web.utils import check_spider, check_forbidden, check_setuid
+from MoinMoin.web.utils import check_surge_protect
+from MoinMoin.web.apps import HTTPExceptionsMiddleware
+
+from MoinMoin.Page import Page
+from MoinMoin import config, wikiutil, user, caching, error
+from MoinMoin.action import get_names, get_available_actions
+from MoinMoin.config import multiconfig
+from MoinMoin.support.python_compatibility import set
+from MoinMoin.util import IsWin9x
+from MoinMoin.request import MoinMoinFinish, RemoteClosedConnection
+from MoinMoin import auth
+
+from MoinMoin import log
+logging = log.getLogger(__name__)
+
+def init(request):
+    request = AllContext(request)
+    request.clock.start('total')
+    request.clock.start('base__init__')
+
+    request.session = request.cfg.session_service.get_session(request)
+
+    # auth & user handling
+    # first try setting up from session
+    userobj = auth.setup_from_session(request, request.session)
+
+    # then handle login/logout forms
+    form = request.values
+
+    if 'login' in form:
+        params = {
+            'username': form.get('name'),
+            'password': form.get('password'),
+            'attended': True,
+            'openid_identifier': form.get('openid_identifier'),
+            'stage': form.get('stage')
+        }
+        userobj = auth.handle_login(request, userobj, **params)
+    elif 'logout' in form:
+        userobj = auth.handle_logout(request, userobj)
+    else:
+        userobj = auth.handle_request(request, userobj)
+
+    # check for setuid-handling of users
+    userobj, olduser = check_setuid(request, userobj)
+
+    if not userobj:
+        userobj = user.User(request, auth_method='request:invalid')
+
+    request.user = userobj
+    request._setuid_real_user = olduser
+
+    # preliminary access control
+    # check against spiders, blacklists and request-spam
+    check_forbidden(request)
+    check_surge_protect(request)
+
+    request.reset()
+
+    request.clock.stop('base__init__')
+    return request
+
+def run(request):
+
+    _ = request.getText
+    request.clock.start('run')
+
+    request.initTheme()
+
+    action_name = request.action
+    if request.cfg.log_timing:
+        request.timing_log(True, action_name)
+
+    # parse request data
+    try:
+        # The last component in path_info is the page name, if any
+        path = request.path
+
+        # 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 = request.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 = wikiutil.normalize_pagename(path, request.cfg)
+        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 request.cfg.language_ignore_browser:
+            request.setHttpHeader("Vary: Cookie,User-Agent")
+        else:
+            request.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 request.user.remember_last_visit and action_name == 'show':
+            pagetrail = request.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(request, wikiname, pagename)
+                    url = wikiurl + wikiutil.quoteWikinameURL(wikitail)
+                else:
+                    url = Page(request, pagename).url(request)
+            else:
+                # Or to localized FrontPage
+                url = wikiutil.getFrontPage(request).url(request)
+            return abort(redirect(url))
+
+        # 2. handle action
+        else:
+            # pagename could be empty after normalization e.g. '///' -> ''
+            # Use localized FrontPage if pagename is empty
+            if not pagename:
+                request.page = wikiutil.getFrontPage(request)
+            else:
+                request.page = Page(request, pagename)
+                if '_' in pagename and not request.page.exists():
+                    pagename = pagename.replace('_', ' ')
+                    page = Page(request, pagename)
+                    if page.exists():
+                        url = page.url(request)
+                        return abort(redirect(url))
+
+            msg = None
+            # Complain about unknown actions
+            if not action_name in get_names(request.cfg):
+                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 request.getAvailableActions(request.page):
+                msg = _("You are not allowed to do %(action_name)s on this page.") % {
+                        'action_name': wikiutil.escape(action_name), }
+                if not request.user.valid:
+                    # Suggest non valid user to login
+                    msg += " " + _("Login and try again.")
+
+            if msg:
+                request.theme.add_msg(msg, "error")
+                request.page.send_page()
+            # Try action
+            else:
+                from MoinMoin import action
+                handler = action.getHandler(request, 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 request.user.valid:
+                        # Suggest non valid user to login
+                        msg += " " + _("Login and try again.")
+                    request.theme.add_msg(msg, "error")
+                    request.page.send_page()
+                else:
+                    handler(request.page.page_name, request)
+
+        # every action that didn't use to raise MoinMoinFinish must call this now:
+        # request.theme.send_closing_html()
+
+    except MoinMoinFinish:
+        pass
+    except RemoteClosedConnection:
+        # at least clean up
+        pass
+    except SystemExit:
+        raise # fcgi uses this to terminate a thread
+
+    if request.cfg.log_timing:
+        request.timing_log(False, action_name)
+
+        #return request.finish()
+    request.cfg.session_service.finalize(request, request.session)
+    return request
+
+def application(request):
+    run(init(request))
+
+    if getattr(request, '_send_file', None) 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 = request.environ.get('wsgi.file_wrapper', simple_wrapper)
+        request.response = file_wrapper(request._send_file, request._send_bufsize)
+    return request
+
+application = Request.application(application)
+application = HTTPExceptionsMiddleware(application)
--- a/MoinMoin/xmlrpc/__init__.py	Tue Jul 08 16:58:19 2008 +0200
+++ b/MoinMoin/xmlrpc/__init__.py	Wed Jul 09 20:43:29 2008 +0200
@@ -30,7 +30,7 @@
 from MoinMoin import log
 logging = log.getLogger(__name__)
 
-from MoinMoin import config, user, wikiutil
+from MoinMoin import auth, config, user, wikiutil
 from MoinMoin.Page import Page
 from MoinMoin.PageEditor import PageEditor
 from MoinMoin.logfile import editlog
@@ -166,10 +166,8 @@
                 # serialize it
                 response = xmlrpclib.dumps(response, methodresponse=1, allow_none=True)
 
-        self.request.emit_http_headers([
-            "Content-Type: text/xml; charset=utf-8",
-            "Content-Length: %d" % len(response),
-        ])
+        self.request.content_type = 'text/xml'
+        self.request.content_length = len(response)
         self.request.write(response)
 
     def dispatch(self, method, params):
@@ -681,12 +679,12 @@
             or the password were wrong.
         """
         id_handler = XmlRpcAuthTokenIDHandler()
+        request = self.request
 
-        u = self.request.cfg.session_handler.start(self.request, id_handler)
-        u = self.request.handle_auth(u, username=username,
-                                     password=password, login=True)
+        request.session = request.cfg.session_service.get_session(request)
 
-        self.request.cfg.session_handler.after_auth(self.request, id_handler, u)
+        u = auth.setup_from_session(request, request.session)
+        u = auth.handle_login(request, u, username=username, password=password)
 
         if u and u.valid:
             return id_handler.token