changeset 4658:f37524774ba4

merged moin/1.8
author Reimar Bauer <rb.proj AT googlemail DOT com>
date Mon, 16 Mar 2009 22:41:59 +0100
parents a04008fe1233 (diff) 1da5c7d21660 (current diff)
children 1b3b177719ee
files MoinMoin/_tests/test_user.py MoinMoin/action/Load.py
diffstat 1981 files changed, 220397 insertions(+), 182864 deletions(-) [+]
line wrap: on
line diff
--- a/.hgignore	Mon Mar 16 22:39:33 2009 +0100
+++ b/.hgignore	Mon Mar 16 22:41:59 2009 +0100
@@ -4,8 +4,14 @@
 ^wiki/underlay/
 ^wiki/data/edit-log
 ^wiki/data/event-log
+^wiki/data/user/
 ^wiki/data/cache/
 ^wikiconfig_local.*
+^wikiserverconfig_local.*
 ^MoinMoin/i18n/POTFILES(\.in)?$
 .coverage
+^.project
+^.pydevproject
+^.settings
+^MANIFEST
 
--- a/.hgtags	Mon Mar 16 22:39:33 2009 +0100
+++ b/.hgtags	Mon Mar 16 22:41:59 2009 +0100
@@ -33,3 +33,4 @@
 7fee549f435d119801075f0d18fc57822f78f0eb 1.8.0
 fad2936d33a7eac9370f3cdc28f0ec375518d5cf 1.8.1
 1f0db10c207f697de8d496f298167d7de76f30ac 1.8.2
+d706f5d4f4ecc935a69b0c6c5b90d47a643e82c4 1.9.0beta1
--- a/MANIFEST.in	Mon Mar 16 22:39:33 2009 +0100
+++ b/MANIFEST.in	Mon Mar 16 22:41:59 2009 +0100
@@ -13,6 +13,12 @@
 # include stuff for translators
 recursive-include   MoinMoin/i18n *
 
+# include static htdocs
+recursive-include   MoinMoin/web/static/htdocs *
+
+# include non-py stuff from werkzeug
+recursive-include   MoinMoin/support/werkzeug/debug *
+
 # contrib stuff
 recursive-include   contrib *
 
--- a/Makefile	Mon Mar 16 22:39:33 2009 +0100
+++ b/Makefile	Mon Mar 16 22:41:59 2009 +0100
@@ -13,33 +13,27 @@
 
 install-docs:
 	-mkdir build
-	wget -U MoinMoin/Makefile -O build/INSTALL.html "http://master18.moinmo.in/MoinMoin/InstallDocs?action=print"
+	wget -U MoinMoin/Makefile -O build/INSTALL.html "http://master19.moinmo.in/InstallDocs?action=print"
 	sed \
-		-e 's#href="/#href="http://master18.moinmo.in/#g' \
-		-e 's#http://[a-z\.]*/wiki/classic/#/wiki/classic/#g' \
-		-e 's#http://[a-z\.]*/wiki/modern/#/wiki/modern/#g' \
-		-e 's#http://[a-z\.]*/wiki/rightsidebar/#/wiki/rightsidebar/#g' \
-		-e 's#/wiki/classic/#wiki/htdocs/classic/#g' \
-		-e 's#/wiki/modern/#wiki/htdocs/modern/#g' \
-		-e 's#/wiki/rightsidebar/#wiki/htdocs/rightsidebar/#g' \
+		-e 's#href="/#href="http://master19.moinmo.in/#g' \
+		-e 's#http://[a-z0-9\.]*/wiki/modernized/#../wiki/htdocs/modernized/#g' \
+		-e 's#/wiki/common/#../wiki/htdocs/common/#g' \
+		-e 's#/wiki/modernized/#../wiki/htdocs/modernized/#g' \
         build/INSTALL.html >docs/INSTALL.html
 	-rm build/INSTALL.html
 
-	wget -U MoinMoin/Makefile -O build/UPDATE.html "http://master18.moinmo.in/HelpOnUpdating?action=print"
+	wget -U MoinMoin/Makefile -O build/UPDATE.html "http://master19.moinmo.in/HelpOnUpdating?action=print"
 	sed \
-		-e 's#href="/#href="http://master18.moinmo.in/#g' \
-		-e 's#http://[a-z\.]*/wiki/classic/#/wiki/classic/#g' \
-		-e 's#http://[a-z\.]*/wiki/modern/#/wiki/modern/#g' \
-		-e 's#http://[a-z\.]*/wiki/rightsidebar/#/wiki/rightsidebar/#g' \
-		-e 's#/wiki/classic/#wiki/htdocs/classic/#g' \
-		-e 's#/wiki/modern/#wiki/htdocs/modern/#g' \
-		-e 's#/wiki/rightsidebar/#wiki/htdocs/rightsidebar/#g' \
+		-e 's#href="/#href="http://master19.moinmo.in/#g' \
+		-e 's#http://[a-z0-9\.]*/wiki/modernized/#../wiki/htdocs/modernized/#g' \
+		-e 's#/wiki/common/#../wiki/htdocs/common/#g' \
+		-e 's#/wiki/modernized/#../wiki/htdocs/modernized/#g' \
         build/UPDATE.html >docs/UPDATE.html
 	-rm build/UPDATE.html
 	-rmdir build
 
 interwiki:
-	wget -U MoinMoin/Makefile -O $(share)/data/intermap.txt "http://master18.moinmo.in/InterWikiMap?action=raw"
+	wget -U MoinMoin/Makefile -O $(share)/data/intermap.txt "http://master19.moinmo.in/InterWikiMap?action=raw"
 	chmod 664 $(share)/data/intermap.txt
 
 check-tabs:
@@ -47,26 +41,30 @@
 
 # Create documentation
 epydoc: patchlevel
-	@epydoc -o ../html-1.8 --name=MoinMoin --url=http://moinmo.in/ --graph=all --graph-font=Arial MoinMoin
+	@epydoc -o ../html-1.9 --name=MoinMoin --url=http://moinmo.in/ --graph=all --graph-font=Arial MoinMoin
 
 # Create new underlay directory from MoinMaster
 # Should be used only on TW machine
 underlay:
 	rm -rf $(share)/underlay
-	MoinMoin/script/moin.py --config-dir=/srv/moin/cfg/1.8 --wiki-url=master18.moinmo.in/ maint globaledit
-	MoinMoin/script/moin.py --config-dir=/srv/moin/cfg/1.8 --wiki-url=master18.moinmo.in/ maint reducewiki --target-dir=$(share)/underlay
+	MoinMoin/script/moin.py --config-dir=/srv/moin/cfg/1.9 --wiki-url=http://master19.moinmo.in/ maint globaledit
+	MoinMoin/script/moin.py --config-dir=/srv/moin/cfg/1.9 --wiki-url=http://master19.moinmo.in/ maint reducewiki --target-dir=$(share)/underlay
 	rm -rf $(share)/underlay/pages/InterWikiMap
 	rm -rf $(share)/underlay/pages/MoinPagesEditorGroup
 	cd $(share); rm -f underlay.tar; tar cf underlay.tar underlay
 
 pagepacks:
 	@python MoinMoin/_tests/maketestwiki.py
-	@MoinMoin/script/moin.py --config-dir=MoinMoin/_tests maint mkpagepacks
+	@MoinMoin/script/moin.py --config-dir=MoinMoin/_tests --wiki-url=http://localhost/ maint mkpagepacks
 	cd $(share) ; rm -rf underlay
 	cp -a $(testwiki)/underlay $(share)/
 	
 dist:
 	-rm MANIFEST
+	-rm -rf tests/wiki
+	-rm -rf wiki/data/cache/{__metalock__,__session__,wikiconfig}
+	->wiki/data/event-log
+	->wiki/data/edit-log
 	python setup.py sdist
 
 # Create patchlevel module
--- a/MoinMoin/Page.py	Mon Mar 16 22:39:33 2009 +0100
+++ b/MoinMoin/Page.py	Mon Mar 16 22:41:59 2009 +0100
@@ -109,7 +109,8 @@
             (for 'meta') or the complete cache ('pagelists').
             @param request: the request object
         """
-        elog = request.editlog
+        from MoinMoin.logfile import editlog
+        elog = editlog.EditLog(request)
         old_pos = self.log_pos
         new_pos, items = elog.news(old_pos)
         if items:
@@ -753,6 +754,9 @@
                 url = "%s/%s/%s" % (request.cfg.url_prefix_action, action, url)
             url = '%s?%s' % (url, querystr)
 
+        if not relative:
+            url = '%s/%s' % (request.script_root, url)
+
         # Add anchor
         if anchor:
             fmt = getattr(self, 'formatter', request.html_formatter)
@@ -760,8 +764,6 @@
                 anchor = fmt.sanitize_to_id(anchor)
             url = "%s#%s" % (url, anchor)
 
-        if not relative:
-            url = '%s/%s' % (request.getScriptname(), url)
         return url
 
     def link_to_raw(self, request, text, querystr=None, anchor=None, **kw):
@@ -966,26 +968,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):
@@ -1010,11 +1011,11 @@
         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.values.get('media', 'print')
         else:
             media = 'screen'
         self.hilite_re = (keywords.get('hilite_re') or
-                          request.form.get('highlight', [None])[0])
+                          request.values.get('highlight'))
 
         # count hit?
         if keywords.get('count_hit', 0):
@@ -1025,7 +1026,7 @@
         pi = self.pi
 
         if 'redirect' in pi and not (
-            'action' in request.form or 'redirect' in request.form or content_only):
+            'action' in request.values or 'redirect' in request.values or content_only):
             # redirect to another page
             # note that by including "action=show", we prevent endless looping
             # (see code in "request") or any cascaded redirection
@@ -1054,8 +1055,6 @@
             try:
                 self.formatter.set_highlight_re(self.hilite_re)
             except re.error, err:
-                if 'highlight' in request.form:
-                    del request.form['highlight']
                 request.theme.add_msg(_('Invalid highlighting regular expression "%(regex)s": %(error)s') % {
                                           'regex': self.hilite_re,
                                           'error': str(err),
@@ -1080,12 +1079,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)
@@ -1100,8 +1099,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
@@ -1120,8 +1118,8 @@
 
                 # This redirect message is very annoying.
                 # Less annoying now without the warning sign.
-                if 'redirect' in request.form:
-                    redir = request.form['redirect'][0]
+                if 'redirect' in request.values:
+                    redir = request.values['redirect']
                     request.theme.add_msg('<strong>%s</strong><br>' % (
                         _('Redirected from page "%(page)s"') % {'page':
                             wikiutil.link_tag(request, wikiutil.quoteWikinameURL(redir) + "?action=show", self.formatter.text(redir))}), "info")
--- a/MoinMoin/PageEditor.py	Mon Mar 16 22:39:33 2009 +0100
+++ b/MoinMoin/PageEditor.py	Mon Mar 16 22:41:59 2009 +0100
@@ -197,12 +197,11 @@
 
         # Emit http_headers after checks (send_page)
         request.disableHttpCaching(level=2)
-        request.emit_http_headers()
 
         # check if we want to load a draft
         use_draft = None
         if 'button_load_draft' in form:
-            wanted_draft_timestamp = int(form.get('draft_ts', ['0'])[0])
+            wanted_draft_timestamp = int(form.get('draft_ts', '0'))
             if wanted_draft_timestamp:
                 draft = self._load_draft()
                 if draft is not None:
@@ -214,7 +213,7 @@
         if use_draft is not None:
             title = _('Draft of "%(pagename)s"')
             # Propagate original revision
-            rev = int(form['draft_rev'][0])
+            rev = int(form['draft_rev'])
             self.set_raw_body(use_draft, modified=1)
             preview = use_draft
         elif preview is None:
@@ -234,7 +233,7 @@
 
         # get request parameters
         try:
-            text_rows = int(form['rows'][0])
+            text_rows = int(form['rows'])
         except StandardError:
             text_rows = self.cfg.edit_rows
             if request.user.valid:
@@ -276,9 +275,9 @@
             # If the page exists, we get the text from the page.
             # TODO: maybe warn if template argument was ignored because the page exists?
             raw_body = self.get_raw_body()
-        elif 'template' in form:
+        elif 'template' in request.values:
             # If the page does not exist, we try to get the content from the template parameter.
-            template_page = wikiutil.unquoteWikiname(form['template'][0])
+            template_page = wikiutil.unquoteWikiname(request.values['template'])
             if request.user.may.read(template_page):
                 raw_body = Page(request, template_page).get_raw_body()
                 if raw_body:
@@ -332,10 +331,9 @@
             raw_body = _('Describe %s here.') % (self.page_name, )
 
         # send form
-        request.write('<form id="editor" method="post" action="%s/%s#preview" onSubmit="flgChange = false;">' % (
-            request.getScriptname(),
-            wikiutil.quoteWikinameURL(self.page_name),
-            ))
+        request.write('<form id="editor" method="post" action="%s#preview" onSubmit="flgChange = false;">' % (
+                request.href(self.page_name)
+        ))
 
         # yet another weird workaround for broken IE6 (it expands the text
         # editor area to the right after you begin to type...). IE sucks...
@@ -351,7 +349,7 @@
         request.write('<input type="hidden" name="ticket" value="%s">' % wikiutil.createTicket(request))
 
         # Save backto in a hidden input
-        backto = form.get('backto', [None])[0]
+        backto = request.values.get('backto')
         if backto:
             request.write(unicode(html.INPUT(type="hidden", name="backto", value=backto)))
 
@@ -410,7 +408,7 @@
     document.write('<label for="chktrivialtop">%(label)s</label>');
     //-->
 </script> ''' % {
-                'checked': ('', 'checked')[form.get('trivial', ['0'])[0] == '1'],
+                'checked': ('', 'checked')[form.get('trivial', '0') == '1'],
                 'label': _("Trivial change"),
             })
 
@@ -460,7 +458,7 @@
 <label for="chktrivial">%(label)s</label>
 
 ''' % {
-                'checked': ('', 'checked')[form.get('trivial', ['0'])[0] == '1'],
+                'checked': ('', 'checked')[form.get('trivial', '0') == '1'],
                 'label': _("Trivial change"),
                 })
 
@@ -469,7 +467,7 @@
 <input type="checkbox" name="rstrip" id="chkrstrip" value="1" %(checked)s>
 <label for="chkrstrip">%(label)s</label>
 ''' % {
-            'checked': ('', 'checked')[form.get('rstrip', ['0'])[0] == '1'],
+            'checked': ('', 'checked')[form.get('rstrip', '0') == '1'],
             'label': _('Remove trailing whitespace from each line')
             })
         request.write("</p>")
@@ -514,7 +512,7 @@
         self._save_draft(newtext, rev) # shall we really save a draft on CANCEL?
         self.lock.release()
 
-        backto = request.form.get('backto', [None])[0]
+        backto = request.values.get('backto')
         if backto:
             pg = Page(request, backto)
             request.http_redirect(pg.url(request, relative=False))
@@ -672,7 +670,7 @@
 
             # Remove cache entry (if exists)
             pg = Page(self.request, self.page_name)
-            key = self.request.form.get('key', ['text_html'])[0]
+            key = self.request.form.get('key', 'text_html') # XXX see cleanup code in deletePage
             caching.CacheEntry(self.request, pg, key, scope='item').remove()
             caching.CacheEntry(self.request, pg, "pagelinks", scope='item').remove()
 
--- a/MoinMoin/PageGraphicalEditor.py	Mon Mar 16 22:39:33 2009 +0100
+++ b/MoinMoin/PageGraphicalEditor.py	Mon Mar 16 22:41:59 2009 +0100
@@ -87,12 +87,11 @@
 
         # Emit http_headers after checks (send_page)
         request.disableHttpCaching(level=2)
-        request.emit_http_headers()
 
         # check if we want to load a draft
         use_draft = None
         if 'button_load_draft' in form:
-            wanted_draft_timestamp = int(form.get('draft_ts', ['0'])[0])
+            wanted_draft_timestamp = int(form.get('draft_ts', '0'))
             if wanted_draft_timestamp:
                 draft = self._load_draft()
                 if draft is not None:
@@ -104,7 +103,7 @@
         if use_draft is not None:
             title = _('Draft of "%(pagename)s"')
             # Propagate original revision
-            rev = int(form['draft_rev'][0])
+            rev = int(form['draft_rev'])
             self.set_raw_body(use_draft, modified=1)
             preview = use_draft
         elif preview is None:
@@ -124,7 +123,7 @@
 
         # get request parameters
         try:
-            text_rows = int(form['rows'][0])
+            text_rows = int(form['rows'])
         except StandardError:
             text_rows = self.cfg.edit_rows
             if request.user.valid:
@@ -168,9 +167,9 @@
             # If the page exists, we get the text from the page.
             # TODO: maybe warn if template argument was ignored because the page exists?
             raw_body = self.get_raw_body()
-        elif 'template' in form:
+        elif 'template' in request.values:
             # If the page does not exist, we try to get the content from the template parameter.
-            template_page = wikiutil.unquoteWikiname(form['template'][0])
+            template_page = wikiutil.unquoteWikiname(request.values['template'])
             if request.user.may.read(template_page):
                 raw_body = Page(request, template_page).get_raw_body()
                 if raw_body:
@@ -224,9 +223,8 @@
             raw_body = _('Describe %s here.') % (self.page_name, )
 
         # send form
-        request.write('<form id="editor" method="post" action="%s/%s#preview">' % (
-            request.getScriptname(),
-            wikiutil.quoteWikinameURL(self.page_name),
+        request.write('<form id="editor" method="post" action="%s#preview">' % (
+                request.href(self.page_name)
             ))
 
         # yet another weird workaround for broken IE6 (it expands the text
@@ -247,7 +245,7 @@
         request.write('<input type="hidden" name="ticket" value="%s">' % wikiutil.createTicket(request))
 
         # Save backto in a hidden input
-        backto = form.get('backto', [None])[0]
+        backto = request.values.get('backto')
         if backto:
             request.write(unicode(html.INPUT(type="hidden", name="backto", value=backto)))
 
@@ -298,7 +296,7 @@
 <input type="checkbox" name="trivial" id="chktrivialtop" value="1" %(checked)s onclick="toggle_trivial(this)">
 <label for="chktrivialtop">%(label)s</label>
 ''' % {
-          'checked': ('', 'checked')[form.get('trivial', ['0'])[0] == '1'],
+          'checked': ('', 'checked')[form.get('trivial', '0') == '1'],
           'label': _("Trivial change"),
        })
 
@@ -315,9 +313,7 @@
         url_prefix_local = request.cfg.url_prefix_local
         wikipage = wikiutil.quoteWikinameURL(self.page_name)
         fckbasepath = request.cfg.url_prefix_fckeditor
-        wikiurl = request.getScriptname()
-        if not wikiurl or wikiurl[-1] != '/':
-            wikiurl += '/'
+        wikiurl = request.script_root + '/'
         themepath = '%s/%s' % (url_prefix_static, request.theme.name)
         smileypath = themepath + '/img'
         # auto-generating a list for SmileyImages does NOT work from here!
@@ -377,7 +373,7 @@
 &nbsp;
 <input type="checkbox" name="trivial" id="chktrivial" value="1" %(checked)s onclick="toggle_trivial(this)">
 <label for="chktrivial">%(label)s</label> ''' % {
-                'checked': ('', 'checked')[form.get('trivial', ['0'])[0] == '1'],
+                'checked': ('', 'checked')[form.get('trivial', '0') == '1'],
                 'label': _("Trivial change"),
                 })
 
@@ -386,7 +382,7 @@
 <input type="checkbox" name="rstrip" id="chkrstrip" value="1" %(checked)s>
 <label for="chkrstrip">%(label)s</label>
 </p> ''' % {
-            'checked': ('', 'checked')[form.get('rstrip', ['0'])[0] == '1'],
+            'checked': ('', 'checked')[form.get('rstrip', '0') == '1'],
             'label': _('Remove trailing whitespace from each line')
             })
 
--- a/MoinMoin/_tests/ldap_testbase.py	Mon Mar 16 22:39:33 2009 +0100
+++ b/MoinMoin/_tests/ldap_testbase.py	Mon Mar 16 22:41:59 2009 +0100
@@ -32,8 +32,6 @@
 
     # /etc/init.d/apparmor stop
 
-    Requires Python 2.4 (for subprocess module).
-
     @copyright: 2008 by Thomas Waldmann
     @license: GNU GPL, see COPYING for details.
 """
@@ -41,14 +39,10 @@
 SLAPD_EXECUTABLE = 'slapd'  # filename of LDAP server executable - if it is not
                             # in your PATH, you have to give full path/filename.
 
-import os, shutil, tempfile, time
+import os, shutil, tempfile, time, base64, md5
 from StringIO import StringIO
 import signal
-
-try:
-    import subprocess  # needs Python 2.4
-except ImportError:
-    subprocess = None
+import subprocess
 
 try:
     import ldap, ldif, ldap.modlist  # needs python-ldap
@@ -61,8 +55,6 @@
         Either return some failure reason if we can't or None if everything
         looks OK.
     """
-    if subprocess is None:
-        return "You need at least python 2.4 to use ldap_testbase."
     if ldap is None:
         return "You need python-ldap installed to use ldap_testbase."
     slapd = False
@@ -187,6 +179,8 @@
         f.write(db_config)
         f.close()
 
+        rootpw = '{MD5}' + base64.b64encode(md5.new(self.rootpw).digest())
+
         # create slapd.conf from content template in slapd_config
         slapd_config = slapd_config % {
             'ldap_dir': self.ldap_dir,
@@ -194,7 +188,7 @@
             'schema_dir': self.schema_dir,
             'basedn': self.basedn,
             'rootdn': self.rootdn,
-            'rootpw': self.rootpw,
+            'rootpw': rootpw,
         }
         if isinstance(slapd_config, unicode):
             slapd_config = slapd_config.encode(self.coding)
--- a/MoinMoin/_tests/ldap_testdata.py	Mon Mar 16 22:39:33 2009 +0100
+++ b/MoinMoin/_tests/ldap_testdata.py	Mon Mar 16 22:41:59 2009 +0100
@@ -78,14 +78,16 @@
 objectClass: account
 objectClass: simpleSecurityObject
 uid: usera
-userPassword: usera
+# this is md5 encoded 'usera' for password
+userPassword: {MD5}aXqgOSc5gSW7YoLi9BSmvg==
 
 dn: uid=userb,ou=Unit B,ou=Users,ou=testing,dc=example,dc=org
 cn: Vorname Nachname
 objectClass: inetOrgPerson
 sn: Nachname
 uid: userb
-userPassword: userb
+# this is md5 encoded 'userb' for password
+userPassword: {MD5}ThvfQsM7OQFjqSUQOX2XsA==
 
 dn: cn=Group A,ou=Groups,ou=testing,dc=example,dc=org
 cn: Group A
--- a/MoinMoin/_tests/test_sourcecode.py	Mon Mar 16 22:39:33 2009 +0100
+++ b/MoinMoin/_tests/test_sourcecode.py	Mon Mar 16 22:41:59 2009 +0100
@@ -19,10 +19,10 @@
 EXCLUDE = [
     '/contrib/DesktopEdition/setup_py2exe.py', # has crlf
     '/contrib/TWikiDrawPlugin', # 3rd party java stuff
+    '/contrib/flup-server', # 3rd party WSGI adapters
     '/MoinMoin/support', # 3rd party libs or non-broken stdlib stuff
-    '/wiki/htdocs/applets/FCKeditor', # 3rd party GUI editor
+    '/MoinMoin/web/static/htdocs', # this is our dist static stuff
     '/tests/wiki', # this is our test wiki
-    '/wiki/htdocs', # this is our dist static stuff
     '/wiki/data/pages', # wiki pages, there may be .py attachments
 ]
 
--- a/MoinMoin/_tests/test_user.py	Mon Mar 16 22:39:33 2009 +0100
+++ b/MoinMoin/_tests/test_user.py	Mon Mar 16 22:41:59 2009 +0100
@@ -39,11 +39,11 @@
 
     def setup_method(self, method):
         # Save original user and cookie
-        self.saved_cookie = self.request.saved_cookie
+        self.saved_cookie = self.request.cookies
         self.saved_user = self.request.user
 
         # Create anon user for the tests
-        self.request.saved_cookie = ''
+        self.request.cookies = {}
         self.request.user = user.User(self.request)
 
         # Prevent user list caching - we create and delete users too fast for that.
@@ -65,7 +65,7 @@
             del self.user
 
         # Restore original user
-        self.request.saved_cookie = self.saved_cookie
+        self.request.cookies = self.saved_cookie
         self.request.user = self.saved_user
 
         # Remove user name to id cache, or next test will fail
--- a/MoinMoin/_tests/test_wikiutil.py	Mon Mar 16 22:39:33 2009 +0100
+++ b/MoinMoin/_tests/test_wikiutil.py	Mon Mar 16 22:41:59 2009 +0100
@@ -9,7 +9,9 @@
 
 import py
 
-from MoinMoin import wikiutil
+from MoinMoin import config, wikiutil
+
+from werkzeug import MultiDict
 
 
 class TestQueryStringSupport:
@@ -21,17 +23,13 @@
     ]
     def testParseQueryString(self):
         for qstr, expected_str, expected_unicode in self.tests:
-            assert wikiutil.parseQueryString(qstr, want_unicode=False) == expected_str
-            assert wikiutil.parseQueryString(qstr, want_unicode=True) == expected_unicode
-            assert wikiutil.parseQueryString(unicode(qstr), want_unicode=False) == expected_str
-            assert wikiutil.parseQueryString(unicode(qstr), want_unicode=True) == expected_unicode
+            assert wikiutil.parseQueryString(qstr) == MultiDict(expected_unicode)
+            assert wikiutil.parseQueryString(unicode(qstr)) == MultiDict(expected_unicode)
 
     def testMakeQueryString(self):
         for qstr, in_str, in_unicode in self.tests:
-            assert wikiutil.parseQueryString(wikiutil.makeQueryString(in_unicode, want_unicode=False), want_unicode=False) == in_str
-            assert wikiutil.parseQueryString(wikiutil.makeQueryString(in_str, want_unicode=False), want_unicode=False) == in_str
-            assert wikiutil.parseQueryString(wikiutil.makeQueryString(in_unicode, want_unicode=True), want_unicode=True) == in_unicode
-            assert wikiutil.parseQueryString(wikiutil.makeQueryString(in_str, want_unicode=True), want_unicode=True) == in_unicode
+            assert wikiutil.parseQueryString(wikiutil.makeQueryString(in_unicode)) == MultiDict(in_unicode)
+            assert wikiutil.parseQueryString(wikiutil.makeQueryString(in_str)) == MultiDict(in_unicode)
 
 
 class TestTickets:
@@ -973,4 +971,80 @@
         assert relative_page == wikiutil.RelPageName(current_page, absolute_page)
 
 
+class TestNormalizePagename(object):
+
+    def testPageInvalidChars(self):
+        """ request: normalize pagename: remove invalid unicode chars
+
+        Assume the default setting
+        """
+        test = u'\u0000\u202a\u202b\u202c\u202d\u202e'
+        expected = u''
+        result = wikiutil.normalize_pagename(test, self.request.cfg)
+        assert result == expected
+
+    def testNormalizeSlashes(self):
+        """ request: normalize pagename: normalize slashes """
+        cases = (
+            (u'/////', u''),
+            (u'/a', u'a'),
+            (u'a/', u'a'),
+            (u'a/////b/////c', u'a/b/c'),
+            (u'a b/////c d/////e f', u'a b/c d/e f'),
+            )
+        for test, expected in cases:
+            result = wikiutil.normalize_pagename(test, self.request.cfg)
+            assert result == expected
+
+    def testNormalizeWhitespace(self):
+        """ request: normalize pagename: normalize whitespace """
+        cases = (
+            (u'         ', u''),
+            (u'    a', u'a'),
+            (u'a    ', u'a'),
+            (u'a     b     c', u'a b c'),
+            (u'a   b  /  c    d  /  e   f', u'a b/c d/e f'),
+            # All 30 unicode spaces
+            (config.chars_spaces, u''),
+            )
+        for test, expected in cases:
+            result = wikiutil.normalize_pagename(test, self.request.cfg)
+            assert result == expected
+
+    def testUnderscoreTestCase(self):
+        """ request: normalize pagename: underscore convert to spaces and normalized
+
+        Underscores should convert to spaces, then spaces should be
+        normalized, order is important!
+        """
+        cases = (
+            (u'         ', u''),
+            (u'  a', u'a'),
+            (u'a  ', u'a'),
+            (u'a  b  c', u'a b c'),
+            (u'a  b  /  c  d  /  e  f', u'a b/c d/e f'),
+            )
+        for test, expected in cases:
+            result = wikiutil.normalize_pagename(test, self.request.cfg)
+            assert result == expected
+
+class TestGroupPages(object):
+
+    def testNormalizeGroupName(self):
+        """ request: normalize pagename: restrict groups to alpha numeric Unicode
+
+        Spaces should normalize after invalid chars removed!
+        """
+        cases = (
+            # current acl chars
+            (u'Name,:Group', u'NameGroup'),
+            # remove than normalize spaces
+            (u'Name ! @ # $ % ^ & * ( ) + Group', u'Name Group'),
+            )
+        for test, expected in cases:
+            # validate we are testing valid group names
+            if wikiutil.isGroupPage(test, self.request.cfg):
+                result = wikiutil.normalize_pagename(test, self.request.cfg)
+                assert result == expected
+
 coverage_modules = ['MoinMoin.wikiutil']
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MoinMoin/_tests/test_wsgiapp.py	Mon Mar 16 22:41:59 2009 +0100
@@ -0,0 +1,42 @@
+# -*- coding: utf-8 -*-
+"""
+    MoinMoin - MoinMoin.wsgiapp Tests
+
+    @copyright: 2008 MoinMoin:FlorianKrupicka
+    @license: GNU GPL, see COPYING for details.
+"""
+from StringIO import StringIO
+
+from MoinMoin import wsgiapp
+
+DOC_TYPE = '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">'
+
+class TestApplication:
+    # self.client is made by conftest
+
+    # These should exist
+    PAGES = ('FrontPage', 'RecentChanges', 'HelpContents', 'FindPage')
+    # ... and these should not
+    NO_PAGES = ('FooBar', 'TheNone/ExistantPage/', '%33Strange%74Codes')
+
+    def testWSGIAppExisting(self):
+        for page in self.PAGES:
+            def _test_(page=page):
+                appiter, status, headers = self.client.get('/%s' % page)
+                output = ''.join(appiter)
+                print output
+                assert status[:3] == '200'
+                assert ('Content-Type', 'text/html; charset=utf-8') in headers
+                for needle in (DOC_TYPE, page):
+                    assert needle in output
+            yield _test_
+
+    def testWSGIAppAbsent(self):
+        for page in self.NO_PAGES:
+            def _test_(page=page):
+                appiter, status, headers = self.client.get('/%s' % page)
+                assert status[:3] == '404'
+                output = ''.join(appiter)
+                for needle in ('new empty page', 'page template'):
+                    assert needle in output
+            yield _test_
--- a/MoinMoin/action/AttachFile.py	Mon Mar 16 22:39:33 2009 +0100
+++ b/MoinMoin/action/AttachFile.py	Mon Mar 16 22:41:59 2009 +0100
@@ -27,16 +27,22 @@
     @license: GNU GPL, see COPYING for details.
 """
 
-import os, time, zipfile, mimetypes, errno
+import os, time, zipfile, errno, datetime
+from StringIO import StringIO
 
 from MoinMoin import log
 logging = log.getLogger(__name__)
 
-from MoinMoin import config, wikiutil, packages
+# keep both imports below as they are, order is important:
+from MoinMoin import wikiutil
+import mimetypes
+
+from MoinMoin import config, packages
 from MoinMoin.Page import Page
 from MoinMoin.util import filesys, timefuncs
 from MoinMoin.security.textcha import TextCha
 from MoinMoin.events import FileAttachedEvent, send_event
+from MoinMoin.support import tarfile
 
 action_name = __name__.split('.')[-1]
 
@@ -76,34 +82,20 @@
     else:
         return u"/".join(pieces[:-1]), pieces[-1]
 
-
-def attachUrl(request, pagename, filename=None, **kw):
-    # filename is not used yet, but should be used later to make a sub-item url
-    if kw:
-        qs = '?%s' % wikiutil.makeQueryString(kw, want_unicode=False)
-    else:
-        qs = ''
-    return "%s/%s%s" % (request.getScriptname(), wikiutil.quoteWikinameURL(pagename), qs)
-
-
 def getAttachUrl(pagename, filename, request, addts=0, escaped=0, do='get', drawing='', upload=False):
     """ Get URL that points to attachment `filename` of page `pagename`. """
     if upload:
         if not drawing:
-            url = attachUrl(request, pagename, filename,
-                            rename=wikiutil.taintfilename(filename), action=action_name)
+            url = request.href(pagename, rename=wikiutil.taintfilename(filename),
+                               action=action_name)
         else:
-            url = attachUrl(request, pagename, filename,
-                            rename=wikiutil.taintfilename(filename), drawing=drawing, action=action_name)
+            url = request.href(pagename, rename=wikiutil.taintfilename(filename),
+                               drawing=drawing, action=action_name)
     else:
         if not drawing:
-            url = attachUrl(request, pagename, filename,
-                            target=filename, action=action_name, do=do)
+            url = request.href(pagename, target=filename, action=action_name, do=do)
         else:
-            url = attachUrl(request, pagename, filename,
-                            drawing=drawing, action=action_name)
-    if escaped:
-        url = wikiutil.escape(url)
+            url = request.href(pagename, drawing=drawing, action=action_name)
     return url
 
 
@@ -123,7 +115,7 @@
     fmt = request.formatter
     attach_count = _('[%d attachments]') % len(files)
     attach_icon = request.theme.make_icon('attach', vars={'attach_count': attach_count})
-    attach_link = (fmt.url(1, attachUrl(request, pagename, action=action_name), rel='nofollow') +
+    attach_link = (fmt.url(1, request.href(pagename, action=action_name), rel='nofollow') +
                    attach_icon +
                    fmt.url(0))
     return attach_link
@@ -231,7 +223,7 @@
     """
     from MoinMoin.logfile import editlog
     t = wikiutil.timestamp2version(time.time())
-    fname = wikiutil.url_quote(filename, want_unicode=True)
+    fname = wikiutil.url_quote(filename)
 
     # Write to global log
     log = editlog.EditLog(request)
@@ -251,10 +243,10 @@
     _ = request.getText
 
     error = None
-    if not request.form.get('target', [''])[0]:
+    if not request.values.get('target'):
         error = _("Filename of attachment not specified!")
     else:
-        filename = wikiutil.taintfilename(request.form['target'][0])
+        filename = wikiutil.taintfilename(request.values['target'])
         fpath = getFilename(request, pagename, filename)
 
         if os.path.isfile(fpath):
@@ -322,7 +314,7 @@
                          fmt.text(label_get) +
                          fmt.url(0))
 
-            if ext == '.draw':
+            if ext == '.tdraw':
                 links.append(fmt.url(1, getAttachUrl(pagename, file, request, drawing=base)) +
                              fmt.text(label_edit) +
                              fmt.url(0))
@@ -408,14 +400,15 @@
 
     now = time.time()
     pubpath = request.cfg.url_prefix_static + "/applets/TWikiDrawPlugin"
-    basename = request.form['drawing'][0]
-    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)
+    basename = request.values['drawing']
+    ci = ContainerItem(request, pagename, basename + '.tdraw')
+    drawpath = ci.member_url(basename + '.draw')
+    pngpath = ci.member_url(basename + '.png')
+    pagelink = request.href(pagename, action=action_name, ts=now)
     helplink = Page(request, "HelpOnActions/AttachFile").url(request)
-    savelink = attachUrl(request, pagename, '', action=action_name, do='savedrawing')
+    savelink = request.href(pagename, action=action_name, do='savedrawing')
     #savelink = Page(request, pagename).url(request) # XXX include target filename param here for twisted
-                                           # request, {'savename': request.form['drawing'][0]+'.draw'}
+                                           # request, {'savename': request.values['drawing']+'.draw'}
     #savelink = '/cgi-bin/dumpform.bat'
 
     timestamp = '&amp;ts=%s' % now
@@ -460,7 +453,7 @@
     if writeable:
         request.write('<h2>' + _("New Attachment") + '</h2>')
         request.write("""
-<form action="%(baseurl)s/%(pagename)s" method="POST" enctype="multipart/form-data">
+<form action="%(url)s" method="POST" enctype="multipart/form-data">
 <dl>
 <dt>%(upload_label_file)s</dt>
 <dd><input type="file" name="file" size="50"></dd>
@@ -477,14 +470,13 @@
 </p>
 </form>
 """ % {
-    'baseurl': request.getScriptname(),
-    'pagename': wikiutil.quoteWikinameURL(pagename),
+    'url': request.href(pagename),
     'action_name': action_name,
     'upload_label_file': _('File to upload'),
     'upload_label_rename': _('Rename to'),
-    'rename': wikiutil.escape(request.form.get('rename', [''])[0], 1),
+    'rename': wikiutil.escape(request.values.get('rename', ''), 1),
     '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 +487,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.values.get('drawing'):
         send_hotdraw(pagename, request)
 
 
@@ -507,12 +499,12 @@
     """ Main dispatcher for the 'AttachFile' action. """
     _ = request.getText
 
-    do = request.form.get('do', ['upload_form'])
-    handler = globals().get('_do_%s' % do[0])
+    do = request.values.get('do', 'upload_form')
+    handler = globals().get('_do_%s' % do)
     if handler:
         msg = handler(pagename, request)
     else:
-        msg = _('Unsupported AttachFile sub-action: %s') % (wikiutil.escape(do[0]), )
+        msg = _('Unsupported AttachFile sub-action: %s') % wikiutil.escape(do)
     if msg:
         error_msg(pagename, request, msg)
 
@@ -524,7 +516,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")
@@ -536,17 +527,6 @@
     request.theme.send_closing_html()
 
 
-def preprocess_filename(filename):
-    """ preprocess the filename we got from upload form,
-        strip leading drive and path (IE misbehaviour)
-    """
-    if filename and len(filename) > 1 and (filename[1] == ':' or filename[0] == '\\'): # C:.... or \path... or \\server\...
-        bsindex = filename.rfind('\\')
-        if bsindex >= 0:
-            filename = filename[bsindex+1:]
-    return filename
-
-
 def _do_upload(pagename, request):
     _ = request.getText
     # Currently we only check TextCha for upload (this is what spammers ususally do),
@@ -555,9 +535,15 @@
         return _('TextCha: Wrong answer! Go back and try again...')
 
     form = request.form
-    overwrite = form.get('overwrite', [u'0'])[0]
+
+    file_upload = request.files.get('file')
+    if not file_upload:
+        # This might happen when trying to upload file names
+        # with non-ascii characters on Safari.
+        return _("No file content. Delete non ASCII characters from the file name and try again.")
+
     try:
-        overwrite = int(overwrite)
+        overwrite = int(form.get('overwrite', '0'))
     except:
         overwrite = 0
 
@@ -567,84 +553,122 @@
     if overwrite and not request.user.may.delete(pagename):
         return _('You are not allowed to overwrite a file attachment of this page.')
 
-    filename = form.get('file__filename__', u'')
-    rename = form.get('rename', [u''])[0].strip()
+    rename = form.get('rename', u'').strip()
     if rename:
         target = rename
     else:
-        target = filename
+        target = file_upload.filename or u''
 
-    target = preprocess_filename(target)
     target = wikiutil.clean_input(target)
 
     if not target:
         return _("Filename of attachment not specified!")
 
-    # get file content
-    filecontent = request.form.get('file', [None])[0]
-    if filecontent is None:
-        # This might happen when trying to upload file names
-        # with non-ascii characters on Safari.
-        return _("No file content. Delete non ASCII characters from the file name and try again.")
-
     # add the attachment
     try:
-        target, bytes = add_attachment(request, pagename, target, filecontent, overwrite=overwrite)
+        target, bytes = add_attachment(request, pagename, target, file_upload.stream, overwrite=overwrite)
         msg = _("Attachment '%(target)s' (remote name '%(filename)s')"
                 " with %(bytes)d bytes saved.") % {
-                'target': target, 'filename': filename, 'bytes': bytes}
+                'target': target, 'filename': file_upload.filename, 'bytes': bytes}
     except AttachmentAlreadyExists:
         msg = _("Attachment '%(target)s' (remote name '%(filename)s') already exists.") % {
-            'target': target, 'filename': filename}
+            'target': target, 'filename': file_upload.filename}
 
     # return attachment list
     upload_form(pagename, request, msg)
 
 
+class ContainerItem:
+    """ A storage container (multiple objects in 1 tarfile) """
+
+    def __init__(self, request, pagename, containername):
+        self.request = request
+        self.pagename = pagename
+        self.containername = containername
+        self.container_filename = getFilename(request, pagename, containername)
+
+    def member_url(self, member):
+        """ return URL for accessing container member
+            (we use same URL for get (GET) and put (POST))
+        """
+        url = Page(self.request, self.pagename).url(self.request, {
+            'action': 'AttachFile',
+            'do': 'box',  # shorter to type than 'container'
+            'target': self.containername,
+            #'member': member,
+        })
+        return url + '&member=%s' % member
+        # member needs to be last in qs because twikidraw looks for "file extension" at the end
+
+    def get(self, member):
+        """ return a file-like object with the member file data
+        """
+        tf = tarfile.TarFile(self.container_filename)
+        return tf.extractfile(member)
+
+    def put(self, member, content, content_length=None):
+        """ save data into a container's member """
+        tf = tarfile.TarFile(self.container_filename, mode='a')
+        if isinstance(member, unicode):
+            member = member.encode('utf-8')
+        ti = tarfile.TarInfo(member)
+        if isinstance(content, str):
+            if content_length is None:
+                content_length = len(content)
+            content = StringIO(content) # we need a file obj
+        elif not hasattr(content, 'read'):
+            logging.error("unsupported content object: %r" % content)
+            raise
+        assert content_length >= 0  # we don't want -1 interpreted as 4G-1
+        ti.size = content_length
+        tf.addfile(ti, content)
+        tf.close()
+
+    def truncate(self):
+        f = open(self.container_filename, 'w')
+        f.close()
+
+    def exists(self):
+        return os.path.exists(self.container_filename)
+
 def _do_savedrawing(pagename, request):
     _ = request.getText
 
     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]
+    file_upload = request.files.get('filepath')
+    if not file_upload:
+        # This might happen when trying to upload file names
+        # with non-ascii characters on Safari.
+        return _("No file content. Delete non ASCII characters from the file name and try again.")
 
+    filename = request.form['filename']
     basepath, basename = os.path.split(filename)
     basename, ext = os.path.splitext(basename)
 
-    # get directory, and possibly create it
-    attach_dir = getAttachDir(request, pagename, create=1)
-    savepath = os.path.join(attach_dir, basename + ext)
-
-    if ext == '.draw':
-        _addLogEntry(request, 'ATTDRW', pagename, basename + ext)
+    ci = ContainerItem(request, pagename, basename + '.tdraw')
+    filecontent = file_upload.stream
+    content_length = None
+    if ext == '.draw': # TWikiDraw POSTs this first
+        _addLogEntry(request, 'ATTDRW', pagename, basename + '.tdraw')
+        ci.truncate()
         filecontent = filecontent.read() # read file completely into memory
         filecontent = filecontent.replace("\r", "")
     elif ext == '.map':
+        # touch attachment directory to invalidate cache if new map is saved
+        attach_dir = getAttachDir(request, pagename)
+        os.utime(attach_dir, None)
         filecontent = filecontent.read() # read file completely into memory
         filecontent = filecontent.strip()
-
-    if filecontent:
-        # filecontent is either a file or a non-empty string
-        stream = open(savepath, 'wb')
-        try:
-            _write_stream(filecontent, stream)
-        finally:
-            stream.close()
     else:
-        # filecontent is empty string (e.g. empty map file), delete the target file
-        try:
-            os.unlink(savepath)
-        except OSError, err:
-            if err.errno != errno.ENOENT: # no such file
-                raise
+        #content_length = file_upload.content_length
+        # XXX gives -1 for wsgiref :( If this is fixed, we could use the file obj,
+        # without reading it into memory completely:
+        filecontent = filecontent.read()
 
-    # touch attachment directory to invalidate cache if new map is saved
-    if ext == '.map':
-        os.utime(attach_dir, None)
+    ci.put(basename + ext, filecontent, content_length)
 
-    request.emit_http_headers()
     request.write("OK")
 
 
@@ -710,17 +734,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 +752,7 @@
     else:
         upload_form(pagename, request, msg=_("Move aborted because new attachment name is empty."))
 
-    attachment = request.form.get('oldattachmentname')[0]
+    attachment = request.form.get('oldattachmentname')
     move_file(request, pagename, new_pagename, attachment, new_attachment)
 
 
@@ -743,11 +767,10 @@
 
     # move file
     d = {'action': action_name,
-         'baseurl': request.getScriptname(),
+         'url': request.href(pagename),
          'do': 'attachment_move',
          'ticket': wikiutil.createTicket(request),
          'pagename': pagename,
-         'pagename_quoted': wikiutil.quoteWikinameURL(pagename),
          'attachment_name': filename,
          'move': _('Move'),
          'cancel': _('Cancel'),
@@ -755,7 +778,7 @@
          'attachment_label': _("New attachment name"),
         }
     formhtml = '''
-<form action="%(baseurl)s/%(pagename_quoted)s" method="POST">
+<form action="%(url)s" method="POST">
 <input type="hidden" name="action" value="%(action)s">
 <input type="hidden" name="do" value="%(do)s">
 <input type="hidden" name="ticket" value="%(ticket)s">
@@ -787,6 +810,46 @@
     return thispage.send_page()
 
 
+def _do_box(pagename, request):
+    _ = request.getText
+
+    pagename, filename, fpath = _access_file(pagename, request)
+    if not request.user.may.read(pagename):
+        return _('You are not allowed to get attachments from this page.')
+    if not filename:
+        return # error msg already sent in _access_file
+
+    timestamp = datetime.datetime.fromtimestamp(os.path.getmtime(fpath))
+    if_modified = request.if_modified_since
+    if if_modified and if_modified >= timestamp:
+        request.status_code = 304
+    else:
+        ci = ContainerItem(request, pagename, filename)
+        filename = wikiutil.taintfilename(request.values['member'])
+        mt = wikiutil.MimeType(filename=filename)
+        content_type = mt.content_type()
+        mime_type = mt.mime_type()
+
+        # 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 = filename.encode(config.charset)
+
+        # for dangerous files (like .html), when we are in danger of cross-site-scripting attacks,
+        # we just let the user store them to disk ('attachment').
+        # For safe files, we directly show them inline (this also works better for IE).
+        dangerous = mime_type in request.cfg.mimetypes_xss_protect
+        content_dispo = dangerous and 'attachment' or 'inline'
+
+        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(ci.get(filename))
+
+
 def _do_get(pagename, request):
     _ = request.getText
 
@@ -796,9 +859,10 @@
     if not filename:
         return # error msg already sent in _access_file
 
-    timestamp = timefuncs.formathttpdate(int(os.path.getmtime(fpath)))
-    if request.if_modified_since == timestamp:
-        request.emit_http_headers(["Status: 304 Not modified"])
+    timestamp = datetime.datetime.fromtimestamp(os.path.getmtime(fpath))
+    if_modified = request.if_modified_since
+    if if_modified and if_modified >= timestamp:
+        request.status_code = 304
     else:
         mt = wikiutil.MimeType(filename=filename)
         content_type = mt.content_type()
@@ -814,12 +878,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 +1116,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	Mon Mar 16 22:39:33 2009 +0100
+++ b/MoinMoin/action/CopyPage.py	Mon Mar 16 22:41:59 2009 +0100
@@ -46,20 +46,19 @@
         """ copy this page to "pagename" """
         _ = self._
         form = self.form
-        newpagename = form.get('newpagename', [u''])[0]
-        newpagename = self.request.normalizePagename(newpagename)
-        comment = form.get('comment', [u''])[0]
+        newpagename = form.get('newpagename', u'')
+        newpagename = wikiutil.normalize_pagename(newpagename, self.cfg)
+        comment = form.get('comment', u'')
         comment = wikiutil.clean_input(comment)
 
         self.page = PageEditor(self.request, self.pagename)
         success, msgs = self.page.copyPage(newpagename, comment)
 
         copy_subpages = 0
-        if form.has_key('copy_subpages'):
-            try:
-                copy_subpages = int(form['copy_subpages'][0])
-            except:
-                pass
+        try:
+            copy_subpages = int(form['copy_subpages'])
+        except:
+            pass
 
         if copy_subpages and self.subpages or (not self.users_subpages and self.subpages):
             for name in self.subpages:
@@ -85,7 +84,7 @@
 
             d = {
                 'subpage': subpages,
-                'subpages_checked': ('', 'checked')[self.request.form.get('subpages_checked', ['0'])[0] == '1'],
+                'subpages_checked': ('', 'checked')[self.request.form.get('subpages_checked', '0') == '1'],
                 'subpage_label': _('Copy all /subpages too?'),
                 'pagename': wikiutil.escape(self.pagename, True),
                 'newname_label': _("New name"),
--- a/MoinMoin/action/DeletePage.py	Mon Mar 16 22:39:33 2009 +0100
+++ b/MoinMoin/action/DeletePage.py	Mon Mar 16 22:41:59 2009 +0100
@@ -44,7 +44,7 @@
     def do_action(self):
         """ Delete pagename """
         form = self.form
-        comment = form.get('comment', [u''])[0]
+        comment = form.get('comment', u'')
         comment = wikiutil.clean_input(comment)
 
         # Create a page editor that does not do editor backups, because
@@ -53,11 +53,10 @@
         success, msgs = self.page.deletePage(comment)
 
         delete_subpages = 0
-        if 'delete_subpages' in form:
-            try:
-                delete_subpages = int(form['delete_subpages'][0])
-            except:
-                pass
+        try:
+            delete_subpages = int(form['delete_subpages'])
+        except:
+            pass
 
         if delete_subpages and self.subpages:
             for name in self.subpages:
@@ -75,7 +74,7 @@
 
             d = {
                 'subpage': subpages,
-                'subpages_checked': ('', 'checked')[self.request.form.get('subpages_checked', ['0'])[0] == '1'],
+                'subpages_checked': ('', 'checked')[self.request.form.get('subpages_checked', '0') == '1'],
                 'subpage_label': _('Delete all /subpages too?'),
                 'comment_label': _("Optional reason for the deletion"),
                 'buttons_html': buttons_html,
--- a/MoinMoin/action/Despam.py	Mon Mar 16 22:39:33 2009 +0100
+++ b/MoinMoin/action/Despam.py	Mon Mar 16 22:41:59 2009 +0100
@@ -12,6 +12,9 @@
 
 import time
 
+from MoinMoin import log
+logging = log.getLogger(__name__)
+
 from MoinMoin.logfile import editlog
 from MoinMoin.util.dataset import TupleDataset, Column
 from MoinMoin.widget.browser import DataBrowserWidget
@@ -66,7 +69,7 @@
             pg.link_to(request, text=_("Select Author"),
                 querystr={
                     'action': 'Despam',
-                    'editor': editor, # was: url_quote_plus()
+                    'editor': repr(editor),
                 })))
 
     table = DataBrowserWidget(request)
@@ -104,14 +107,13 @@
     request.write('''
 </table>
 <p>
-<form method="post" action="%s/%s">
+<form method="post" action="%s">
 <input type="hidden" name="action" value="Despam">
 <input type="hidden" name="editor" value="%s">
 <input type="submit" name="ok" value="%s">
 </form>
 </p>
-''' % (request.getScriptname(), wikiutil.quoteWikinameURL(pagename),
-       wikiutil.url_quote(editor), _("Revert all!")))
+''' % (request.href(pagename), wikiutil.url_quote(editor), _("Revert all!")))
 
 def revert_page(request, pagename, editor):
     if not request.user.may.revert(pagename):
@@ -150,7 +152,7 @@
 def revert_pages(request, editor, timestamp):
     _ = request.getText
 
-    editor = wikiutil.url_unquote(editor, want_unicode=False)
+    editor = wikiutil.url_unquote(editor)
     timestamp = int(timestamp * 1000000)
     log = editlog.EditLog(request)
     pages = {}
@@ -183,11 +185,11 @@
         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.values.get('editor')
     timestamp = time.time() - DAYS * 24 * 3600
-    ok = request.form.get('ok', [0])[0]
+    ok = request.form.get('ok', 0)
+    logging.debug("editor: %r ok: %r" % (editor, ok))
 
-    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	Mon Mar 16 22:39:33 2009 +0100
+++ b/MoinMoin/action/LikePages.py	Mon Mar 16 22:41:59 2009 +0100
@@ -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	Mon Mar 16 22:39:33 2009 +0100
+++ b/MoinMoin/action/Load.py	Mon Mar 16 22:41:59 2009 +0100
@@ -41,23 +41,26 @@
         if not TextCha(request).check_answer_from_form():
             return status, _('TextCha: Wrong answer! Go back and try again...')
 
-        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()
+        file_upload = request.files.get('file')
+        if not file_upload:
+            # This might happen when trying to upload file names
+            # with non-ascii characters on Safari.
+            return False, _("No file content. Delete non ASCII characters from the file name and try again.")
+
+        filename = file_upload.filename
+        rename = form.get('rename', '').strip()
         if rename:
             target = rename
         else:
             target = filename
 
-        target = AttachFile.preprocess_filename(target)
         target = wikiutil.clean_input(target)
 
         if target:
-            filecontent = form['file'][0]
-            if hasattr(filecontent, 'read'): # a file-like object
-                filecontent = filecontent.read() # XXX reads complete file into memory!
+            filecontent = file_upload.stream.read() # XXX reads complete file into memory!
             filecontent = wikiutil.decodeUnknownInput(filecontent)
 
             self.pagename = target
--- a/MoinMoin/action/LocalSiteMap.py	Mon Mar 16 22:39:33 2009 +0100
+++ b/MoinMoin/action/LocalSiteMap.py	Mon Mar 16 22:41:59 2009 +0100
@@ -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	Mon Mar 16 22:39:33 2009 +0100
+++ b/MoinMoin/action/MyPages.py	Mon Mar 16 22:41:59 2009 +0100
@@ -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	Mon Mar 16 22:39:33 2009 +0100
+++ b/MoinMoin/action/PackagePages.py	Mon Mar 16 22:41:59 2009 +0100
@@ -7,9 +7,10 @@
     TODO: use ActionBase class
 
     @copyright: 2005 MoinMoin:AlexanderSchremmer
+                2007-2009 MoinMoin:ReimarBauer
     @license: GNU GPL, see COPYING for details.
 """
-
+import cStringIO
 import os
 import zipfile
 from datetime import datetime
@@ -44,9 +45,8 @@
         redirects to new page.
         """
         _ = self.request.getText
-        form = self.request.form
 
-        if 'cancel' in form:
+        if 'cancel' in self.request.values:
             # User canceled
             return self.page.send_page()
 
@@ -65,14 +65,13 @@
     def package(self):
         """ Calls collectpackage() with the arguments specified. """
         _ = self.request.getText
-        form = self.request.form
 
         # Get new name from form and normalize.
-        pagelist = form.get('pagelist', [u''])[0]
-        packagename = form.get('packagename', [u''])[0]
-        include_attachments = form.get('include_attachments', [False])[0]
+        pagelist = self.request.values.get('pagelist', u'')
+        packagename = self.request.values.get('packagename', u'')
+        include_attachments = self.request.values.get('include_attachments', False)
 
-        if not form.get('submit', [None])[0]:
+        if not self.request.values.get('submit'):
             self.request.theme.add_msg(self.makeform(), "dialog")
             raise ActionError
 
@@ -82,26 +81,14 @@
             self.request.theme.add_msg(self.makeform(_('Invalid filename "%s"!') % wikiutil.escape(packagename)), "error")
             raise ActionError
 
-        # get directory, and possibly create it
-        attach_dir = Page(self.request, self.page.page_name).getPagePath("attachments", check_create=1)
-        fpath = os.path.join(attach_dir, target).encode(config.charset)
-        if os.path.exists(fpath):
-            self.request.theme.add_msg(_("Attachment '%(target)s' (remote name '%(filename)s') already exists.") % {
-                'target': wikiutil.escape(target), 'filename': wikiutil.escape(target)}, "error")
-            raise ActionError
-
-        # Generate a package
-        output = open(fpath, "wb")
-        package = self.collectpackage(unpackLine(pagelist, ","), output, target, include_attachments)
-
-        if package:
-            self.request.theme.add_msg(self.makeform(), "dialog")
-            raise ActionError
-
-        _addLogEntry(self.request, 'ATTNEW', self.pagename, target)
-
-        self.request.theme.add_msg(_("Created the package %s containing the pages %s.") % (wikiutil.escape(target), wikiutil.escape(pagelist)))
-        raise ActionError
+        request = self.request
+        filelike = cStringIO.StringIO()
+        package = self.collectpackage(unpackLine(pagelist, ","), filelike, target, include_attachments)
+        request.content_type = 'application/zip'
+        request.content_length = filelike.tell()
+        request.headers.add('Content-Disposition', 'inline; filename="%s"' % target)
+        request.write(filelike.getvalue())
+        filelike.close()
 
     def makeform(self, error=""):
         """ Display a package page form
@@ -115,11 +102,10 @@
             error = u'<p class="error">%s</p>\n' % error
 
         d = {
-            'baseurl': self.request.getScriptname(),
+            'url': self.request.href(self.pagename),
             'error': error,
             'action': self.__class__.__name__,
             'pagename': wikiutil.escape(self.pagename, True),
-            'pagename_quoted': wikiutil.quoteWikinameURL(self.pagename),
             'include_attachments_label': _('Include all attachments?'),
             'package': _('Package pages'),
             'cancel': _('Cancel'),
@@ -128,7 +114,7 @@
         }
         form = '''
 %(error)s
-<form method="post" action="%(baseurl)s/%(pagename_quoted)s">
+<form method="post" action="%(url)s">
 <input type="hidden" name="action" value="%(action)s">
 <table>
     <tr>
@@ -196,7 +182,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	Mon Mar 16 22:39:33 2009 +0100
+++ b/MoinMoin/action/RenamePage.py	Mon Mar 16 22:41:59 2009 +0100
@@ -45,20 +45,19 @@
         """ Rename this page to "pagename" """
         _ = self._
         form = self.form
-        newpagename = form.get('newpagename', [u''])[0]
-        newpagename = self.request.normalizePagename(newpagename)
-        comment = form.get('comment', [u''])[0]
+        newpagename = form.get('newpagename', u'')
+        newpagename = wikiutil.normalize_pagename(newpagename, self.cfg)
+        comment = form.get('comment', u'')
         comment = wikiutil.clean_input(comment)
 
         self.page = PageEditor(self.request, self.pagename)
         success, msgs = self.page.renamePage(newpagename, comment)
 
         rename_subpages = 0
-        if 'rename_subpages' in form:
-            try:
-                rename_subpages = int(form['rename_subpages'][0])
-            except:
-                pass
+        try:
+            rename_subpages = int(form['rename_subpages'])
+        except:
+            pass
 
         if rename_subpages and self.subpages:
             for name in self.subpages:
@@ -84,7 +83,7 @@
 
             d = {
                 'subpage': subpages,
-                'subpages_checked': ('', 'checked')[self.request.form.get('subpages_checked', ['0'])[0] == '1'],
+                'subpages_checked': ('', 'checked')[self.request.form.get('subpages_checked', '0') == '1'],
                 'subpage_label': _('Rename all /subpages too?'),
                 'pagename': wikiutil.escape(self.pagename, True),
                 'newname_label': _("New name"),
--- a/MoinMoin/action/RenderAsDocbook.py	Mon Mar 16 22:39:33 2009 +0100
+++ b/MoinMoin/action/RenderAsDocbook.py	Mon Mar 16 22:41:59 2009 +0100
@@ -9,5 +9,5 @@
 
 def execute(pagename, request):
     url = Page(request, pagename).url(request, {'action': 'show', 'mimetype': 'text/docbook'})
-    request.http_redirect(url)
+    return request.http_redirect(url)
 
--- a/MoinMoin/action/SpellCheck.py	Mon Mar 16 22:39:33 2009 +0100
+++ b/MoinMoin/action/SpellCheck.py	Mon Mar 16 22:41:59 2009 +0100
@@ -102,7 +102,7 @@
     from MoinMoin.PageEditor import PageEditor
     # get the new words as a string (if any are marked at all)
     try:
-        newwords = request.form['newwords']
+        newwords = request.form.getlist('newwords')
     except KeyError:
         # no new words checked
         return
@@ -187,8 +187,8 @@
 
         # add a form containing the bad words
         if own_form:
-            msg = msg + ('<form method="post" action="%s/%s">\n'
-                         '<input type="hidden" name="action" value="%s">\n') % (request.getScriptname(), wikiutil.quoteWikinameURL(page.page_name), action_name)
+            msg = msg + ('<form method="post" action="%s">\n'
+                         '<input type="hidden" name="action" value="%s">\n') % (request.href(page.page_name), action_name)
 
         checkbox = '<input type="checkbox" name="newwords" value="%(word)s">%(word)s&nbsp;&nbsp;'
         msg = msg + (
--- a/MoinMoin/action/SubscribeUser.py	Mon Mar 16 22:39:33 2009 +0100
+++ b/MoinMoin/action/SubscribeUser.py	Mon Mar 16 22:41:59 2009 +0100
@@ -16,30 +16,28 @@
 
 def show_form(pagename, request):
     _ = request.getText
-    request.emit_http_headers()
     request.theme.send_title(_("Subscribe users to the page %s") % pagename, pagename=pagename)
 
     request.write("""
-<form action="%s/%s" method="POST" enctype="multipart/form-data">
+<form action="%s" method="POST" enctype="multipart/form-data">
 <input type="hidden" name="action" value="SubscribeUser">
 %s <input type="text" name="users" size="50">
 <input type="submit" value="Subscribe">
 </form>
-""" % (request.getScriptname(), wikiutil.quoteWikinameURL(pagename),
+""" % (request.href(pagename),
       _("Enter user names (comma separated):")))
     request.theme.send_footer(pagename)
     request.theme.send_closing_html()
 
 def show_result(pagename, request):
     _ = request.getText
-    request.emit_http_headers()
 
     request.theme.send_title(_("Subscribed for %s:") % pagename, pagename=pagename)
 
     from MoinMoin.formatter.text_html import Formatter
     formatter = Formatter(request)
 
-    result = subscribe_users(request, request.form['users'][0].split(","), pagename, formatter)
+    result = subscribe_users(request, request.form['users'].split(","), pagename, formatter)
     request.write(result)
 
     request.theme.send_footer(pagename)
@@ -116,12 +114,11 @@
     if len(args) > 3:
         request_url = args[3]
     else:
-        request_url = "localhost/"
+        request_url = None
 
     # Setup MoinMoin environment
-    from MoinMoin.request import request_cli
-    request = request_cli.Request(url=request_url)
-    request.form = request.args = request.setup_args()
+    from MoinMoin.web.contexts import ScriptContext
+    request = ScriptContext(url=request_url)
 
     from MoinMoin.formatter.text_plain import Formatter
     formatter = Formatter(request)
--- a/MoinMoin/action/SyncPages.py	Mon Mar 16 22:39:33 2009 +0100
+++ b/MoinMoin/action/SyncPages.py	Mon Mar 16 22:41:59 2009 +0100
@@ -122,8 +122,8 @@
     def fix_params(self, params):
         """ Does some fixup on the parameters. """
         # Load the password
-        if "password" in self.request.form:
-            params["password"] = self.request.form["password"][0]
+        if "password" in self.request.values:
+            params["password"] = self.request.values["password"]
 
         # merge the pageList case into the pageMatch case
         if params["pageList"] is not None:
@@ -178,7 +178,7 @@
         params = self.fix_params(self.parse_page())
 
         try:
-            if "cancel" in self.request.form:
+            if "cancel" in self.request.values:
                 raise ActionStatus(_("Operation was canceled."), "error")
 
             if params["direction"] == UP:
--- a/MoinMoin/action/__init__.py	Mon Mar 16 22:39:33 2009 +0100
+++ b/MoinMoin/action/__init__.py	Mon Mar 16 22:41:59 2009 +0100
@@ -18,16 +18,19 @@
     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.
 """
 
 from MoinMoin.util import pysupport
 from MoinMoin import config, wikiutil
 from MoinMoin.Page import Page
+from MoinMoin.support.python_compatibility import set
 
 # create a list of extension actions from the package directory
 modules = pysupport.getPackageModules(__file__)
@@ -88,7 +91,7 @@
             return True
         # Require a valid ticket. Make outside attacks harder by
         # requiring two full HTTP transactions
-        ticket = self.form.get('ticket', [''])[0]
+        ticket = self.form.get('ticket', '')
         return wikiutil.checkTicket(self.request, ticket)
 
     # UI ---------------------------------------------------------------------
@@ -134,19 +137,17 @@
 
         d = {
             'method': self.method,
-            'baseurl': self.request.getScriptname(),
+            'url': self.request.href(self.pagename),
             'enctype': self.enctype,
             'error_html': error_html,
             'actionname': self.actionname,
-            'pagename': self.pagename,
-            'pagename_quoted': wikiutil.quoteWikinameURL(self.pagename),
             'ticket_html': ticket_html,
             'user_html': self.get_form_html(buttons_html),
         }
 
         form_html = '''
 %(error_html)s
-<form action="%(baseurl)s/%(pagename_quoted)s" method="%(method)s" enctype="%(enctype)s">
+<form action="%(url)s" method="%(method)s" enctype="%(enctype)s">
 <div>
 <input type="hidden" name="action" value="%(actionname)s">
 %(ticket_html)s
@@ -235,15 +236,15 @@
         rev = request.rev or 0
         Page(request, pagename, rev=rev).send_raw()
 
-def do_show(pagename, request, content_only=0, count_hit=1, cacheable=1, print_mode=0):
-    """ show a page, either current revision or the revision given by rev form value.
+def do_show(pagename, request, content_only=0, count_hit=1, cacheable=1, print_mode=0, mimetype=u'text/html'):
+    """ show a page, either current revision or the revision given by "rev=" value.
         if count_hit is non-zero, we count the request for statistics.
     """
     # We must check if the current page has different ACLs.
     if not request.user.may.read(pagename):
         Page(request, pagename).send_page()
     else:
-        mimetype = request.form.get('mimetype', [u"text/html"])[0]
+        mimetype = request.values.get('mimetype', mimetype)
         rev = request.rev or 0
         if rev == 0:
             request.cacheable = cacheable
@@ -254,22 +255,19 @@
         )
 
 def do_format(pagename, request):
-    """ send a page using a specific formatter given by mimetype form key.
+    """ send a page using a specific formatter given by "mimetype=" value.
         Since 5.5.2006 this functionality is also done by do_show, but do_format
         has a default of text/plain when no format is given.
         It also does not count in statistics and also does not set the cacheable flag.
         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"]
-    do_show(pagename, request, count_hit=0, cacheable=0)
+    do_show(pagename, request, count_hit=0, cacheable=0, mimetype=u'text/plain')
 
 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 +281,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.values.get('arena', 'Page.py')
     if arena == 'Page.py':
         arena = Page(request, pagename)
-    key = request.form.get('key', ['text_html'])[0]
+    key = request.values.get('key', 'text_html')
 
     # Remove cache entry (if exists), and send the page
     from MoinMoin import caching
@@ -296,27 +294,71 @@
 
 def do_goto(pagename, request):
     """ redirect to another page """
-    target = request.form.get('target', [''])[0]
+    target = request.values.get('target', '')
     request.http_redirect(Page(request, target).url(request))
 
 # Dispatching ----------------------------------------------------------------
-def getNames(cfg):
-    if not hasattr(cfg.cache, 'action_names'):
-        lnames = names[:]
-        lnames.extend(wikiutil.getPlugins('action', cfg))
-        cfg.cache.action_names = lnames # remember it
-    return cfg.cache.action_names
+def get_names(config):
+    """ Get a list of known actions.
 
-def getHandler(request, action, identifier="execute"):
-    """ return a handler function for a given action or None """
+    @param config: a config object
+    @rtype: set
+    @return: set of known actions
+    """
+    if not hasattr(config.cache, 'action_names'):
+        actions = names[:]
+        actions.extend(wikiutil.getPlugins('action', config))
+        actions = set([action for action in actions
+                      if not action in config.actions_excluded])
+        config.cache.action_names = actions # remember it
+    return config.cache.action_names
+
+def getHandler(cfg, action, identifier="execute"):
+    """ return a handler function for a given action or None.
+
+    TODO: remove request dependency
+    """
     # check for excluded actions
-    if action in request.cfg.actions_excluded:
+    if action in cfg.actions_excluded:
         return None
 
     try:
-        handler = wikiutil.importPlugin(request.cfg, "action", action, identifier)
+        handler = wikiutil.importPlugin(cfg, "action", action, identifier)
     except wikiutil.PluginMissingError:
         handler = globals().get('do_' + action)
 
     return handler
 
+def get_available_actions(config, page, user):
+        """ Get a list of actions available on a particular page
+        for a particular user.
+
+        The set does not contain actions that starts with lower case.
+        Themes use this set to display the actions to the user.
+
+        @param config: a config object (for the per-wiki actions)
+        @param page: the page to which the actions should apply
+        @param user: the user which wants to apply an action
+        @rtype: set
+        @return: set of avaiable actions
+        """
+        if not user.may.read(page.page_name):
+            return []
+
+
+        actions = get_names(config)
+
+        # Filter non ui actions (starts with lower case letter)
+        actions = [action for action in actions if not action[0].islower()]
+
+        # Filter actions by page type, acl and user state
+        excluded = []
+        if (page.isUnderlayPage() and not page.isStandardPage()) or \
+                not user.may.write(page.page_name) or \
+                not user.may.delete(page.page_name):
+                # Prevent modification of underlay only pages, or pages
+                # the user can't write and can't delete
+                excluded = [u'RenamePage', u'DeletePage', ] # AttachFile must NOT be here!
+        return set([action for action in actions if not action in excluded])
+
+
--- a/MoinMoin/action/_tests/test_attachfile.py	Mon Mar 16 22:39:33 2009 +0100
+++ b/MoinMoin/action/_tests/test_attachfile.py	Mon Mar 16 22:41:59 2009 +0100
@@ -68,7 +68,7 @@
         Tests if AttachFile.getAttachUrl taints a filename
         """
         filename = "<test2.txt>"
-        expect = "rename=_test2.txt_&"
+        expect = "rename=_test2.txt_"
         result = AttachFile.getAttachUrl(self.pagename, filename, self.request, upload=True)
 
         assert expect in result
--- a/MoinMoin/action/_tests/test_cache.py	Mon Mar 16 22:39:33 2009 +0100
+++ b/MoinMoin/action/_tests/test_cache.py	Mon Mar 16 22:41:59 2009 +0100
@@ -70,8 +70,8 @@
                                         key=key+'.meta', use_pickle=True)
         meta = meta_cache.content()
         assert meta['httpdate_last_modified'].endswith(' GMT') # only a very rough check, it has used cache mtime as last_modified
-        assert "Content-Type: application/octet-stream" in meta['headers']
-        assert "Content-Length: %d" % len(data) in meta['headers']
+        assert ("Content-Type", "application/octet-stream") in meta['headers']
+        assert ("Content-Length", len(data)) in meta['headers']
 
     def test_put_cache_guess_ct_give_lm(self):
         """Test if put_cache() works, when we give filename (so it guesses content_type) and last_modified"""
@@ -89,8 +89,8 @@
                                         key=key+'.meta', use_pickle=True)
         meta = meta_cache.content()
         assert meta['httpdate_last_modified'] == 'Thu, 01 Jan 1970 00:00:01 GMT'
-        assert "Content-Type: image/png" in meta['headers']
-        assert "Content-Length: %d" % len(data) in meta['headers']
+        assert ("Content-Type", "image/png") in meta['headers']
+        assert ("Content-Length", len(data)) in meta['headers']
 
     def test_put_cache_file_like_data(self):
         """Test if put_cache() works when we give it a file like object for the content"""
@@ -109,8 +109,8 @@
                                         key=key+'.meta', use_pickle=True)
         meta = meta_cache.content()
         assert meta['httpdate_last_modified'].endswith(' GMT') # only a very rough check, it has used cache mtime as last_modified
-        assert "Content-Type: application/octet-stream" in meta['headers']
-        assert "Content-Length: %d" % len(data) in meta['headers']
+        assert ("Content-Type", "application/octet-stream") in meta['headers']
+        assert ("Content-Length", len(data)) in meta['headers']
 
         data_cache = caching.CacheEntry(request,
                                         arena=cache.cache_arena,
--- a/MoinMoin/action/backup.py	Mon Mar 16 22:39:33 2009 +0100
+++ b/MoinMoin/action/backup.py	Mon Mar 16 22:41:59 2009 +0100
@@ -37,10 +37,8 @@
     """ Send compressed tar file """
     dateStamp = time.strftime("%Y-%m-%d--%H-%M-%S-UTC", time.gmtime())
     filename = "%s-%s.tar.%s" % (request.cfg.siteid, dateStamp, request.cfg.backup_compression)
-    request.emit_http_headers([
-        'Content-Type: application/octet-stream',
-        'Content-Disposition: inline; filename="%s"' % filename, ])
-
+    request.content_type = 'application/octet-stream'
+    request.headers.add('Content-Disposition', 'inline; filename="%s"' % filename)
     tar = tarfile.open(fileobj=request, mode="w|%s" % request.cfg.backup_compression)
     # allow GNU tar's longer file/pathnames
     tar.posix = False
@@ -51,10 +49,9 @@
 
 def sendBackupForm(request, pagename):
     _ = request.getText
-    request.emit_http_headers()
     request.setContentLanguage(request.lang)
     title = _('Wiki Backup')
-    request.theme.send_title(title, form=request.form, pagename=pagename)
+    request.theme.send_title(title, pagename=pagename)
     request.write(request.formatter.startContent("content"))
 
     request.write(_("""= Downloading a backup =
@@ -74,7 +71,7 @@
 <input type="submit" value="%(backup_button)s">
 </form>
 """ % {
-    'baseurl': request.getScriptname(),
+    'baseurl': request.script_root,
     'pagename': wikiutil.quoteWikinameURL(pagename),
     'backup_button': _('Backup'),
 })
@@ -103,7 +100,7 @@
         return sendMsg(request, pagename,
                        msg=_('You are not allowed to do remote backup.'), msgtype="error")
 
-    dowhat = request.form.get('do', [None])[0]
+    dowhat = request.form.get('do')
     if dowhat == 'backup':
         sendBackup(request)
     elif dowhat is None:
--- a/MoinMoin/action/bookmark.py	Mon Mar 16 22:39:33 2009 +0100
+++ b/MoinMoin/action/bookmark.py	Mon Mar 16 22:41:59 2009 +0100
@@ -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.values.get('time')
     if timestamp is not None:
         if timestamp == 'del':
             tm = None
--- a/MoinMoin/action/cache.py	Mon Mar 16 22:39:33 2009 +0100
+++ b/MoinMoin/action/cache.py	Mon Mar 16 22:41:59 2009 +0100
@@ -41,7 +41,7 @@
 
 action_name = __name__.split('.')[-1]
 
-# Do NOT get this directly from request.form or user would be able to read any cache!
+# Do NOT get this directly from request.values or user would be able to read any cache!
 cache_arena = 'sendcache'  # just using action_name is maybe rather confusing
 
 # We maybe could use page local caching (not 'wiki' global) to have less directory entries.
@@ -146,15 +146,15 @@
     last_modified = last_modified or data_cache.mtime()
 
     httpdate_last_modified = timefuncs.formathttpdate(int(last_modified))
-    headers = ['Content-Type: %s' % content_type,
-               'Last-Modified: %s' % httpdate_last_modified,
-               'Content-Length: %s' % content_length,
+    headers = [('Content-Type', content_type),
+               ('Last-Modified', httpdate_last_modified),
+               ('Content-Length', content_length),
               ]
     if content_disposition and filename:
         # 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 = filename.encode(config.charset)
-        headers.append('Content-Disposition: %s; filename="%s"' % (content_disposition, filename))
+        headers.append(('Content-Disposition', '%s; filename="%s"' % (content_disposition, filename)))
 
     meta_cache = caching.CacheEntry(request, cache_arena, key+'.meta', cache_scope, do_locking=do_locking, use_pickle=True)
     meta_cache.update({
@@ -196,10 +196,7 @@
 
 def url(request, key, do='get'):
     """ return URL for the object cached for key """
-    return "%s/?%s" % (
-        request.getScriptname(),
-        wikiutil.makeQueryString(dict(action=action_name, do=do, key=key), want_unicode=False))
-
+    return request.href(action=action_name, do=do, key=key)
 
 def _get_headers(request, key):
     """ get last_modified and headers cached for key """
@@ -220,19 +217,27 @@
     try:
         last_modified, headers = _get_headers(request, key)
         if request.if_modified_since == last_modified:
-            request.emit_http_headers(["Status: 304 Not modified"])
+            request.status_code = 304
         else:
             data_file = _get_datafile(request, key)
-            request.emit_http_headers(headers)
+            for key, value in headers:
+                lkey = key.lower()
+                if lkey == 'content-type':
+                    request.content_type = value
+                elif lkey == 'last-modified':
+                    request.last_modified = value
+                elif lkey == 'content-length':
+                    request.content_length = value
+                else:
+                    request.headers.add(key, value)
             request.send_file(data_file)
     except caching.CacheError:
-        request.emit_http_headers(["Status: 404 Not found"])
+        request.status_code = 404
 
 
 def _do_remove(request, key):
     """ delete headers/data cache for key """
     remove(request, key)
-    request.emit_http_headers(["Status: 200 OK"])
 
 
 def _do(request, do, key):
@@ -242,7 +247,7 @@
         _do_remove(request, key)
 
 def execute(pagename, request):
-    do = request.form.get('do', [None])[0]
-    key = request.form.get('key', [None])[0]
+    do = request.values.get('do')
+    key = request.values.get('key')
     _do(request, do, key)
 
--- a/MoinMoin/action/chart.py	Mon Mar 16 22:39:33 2009 +0100
+++ b/MoinMoin/action/chart.py	Mon Mar 16 22:41:59 2009 +0100
@@ -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.values.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	Mon Mar 16 22:39:33 2009 +0100
+++ b/MoinMoin/action/diff.py	Mon Mar 16 22:41:59 2009 +0100
@@ -21,7 +21,7 @@
         return
 
     try:
-        date = request.form['date'][0]
+        date = request.values['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.values.get('rev1', -1))
     except StandardError:
         rev1 = 0
     try:
-        rev2 = int(request.form.get('rev2', [0])[0])
+        rev2 = int(request.values.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.values.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	Mon Mar 16 22:39:33 2009 +0100
+++ b/MoinMoin/action/dumpform.py	Mon Mar 16 22:41:59 2009 +0100
@@ -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	Mon Mar 16 22:39:33 2009 +0100
+++ b/MoinMoin/action/edit.py	Mon Mar 16 22:41:59 2009 +0100
@@ -33,7 +33,7 @@
     if editor not in valideditors:
         editor = request.cfg.editor_default
 
-    editorparam = request.form.get('editor', [editor])[0]
+    editorparam = request.values.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	Mon Mar 16 22:39:33 2009 +0100
+++ b/MoinMoin/action/fckdialog.py	Mon Mar 16 22:41:59 2009 +0100
@@ -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.values.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.values.get("pagename", "")
     if name:
         from MoinMoin import search
         # XXX error handling!
@@ -242,9 +239,7 @@
 
     # wiki url
     url_prefix_static = request.cfg.url_prefix_static
-    scriptname = request.getScriptname()
-    if not scriptname or scriptname[-1] != "/":
-        scriptname += "/"
+    scriptname = request.script_root + '/'
     action = scriptname
     basepage = request.page.page_name
     request.write(u'''
@@ -367,9 +362,8 @@
 ##############################################################################
 
 def attachment_dialog(request):
-    request.emit_http_headers()
     # list of wiki pages
-    name = request.form.get("pagename", [""])[0]
+    name = request.values.get("pagename", "")
     if name:
         from MoinMoin import search
         # XXX error handling!
@@ -393,9 +387,7 @@
 
     # wiki url
     url_prefix_static = request.cfg.url_prefix_static
-    scriptname = request.getScriptname()
-    if not scriptname or scriptname[-1] != "/":
-        scriptname += "/"
+    scriptname = request.script_root + '/'
     action = scriptname
     request.write('''
 <!--
@@ -461,7 +453,6 @@
 ##############################################################################
 
 def image_dialog(request):
-    request.emit_http_headers()
     url_prefix_static = request.cfg.url_prefix_static
     request.write('''
 <!--
@@ -524,7 +515,7 @@
 #############################################################################
 
 def execute(pagename, request):
-    dialog = request.form.get("dialog", [""])[0]
+    dialog = request.values.get("dialog", "")
 
     if dialog == "macro":
         macro_dialog(request)
--- a/MoinMoin/action/fullsearch.py	Mon Mar 16 22:39:33 2009 +0100
+++ b/MoinMoin/action/fullsearch.py	Mon Mar 16 22:41:59 2009 +0100
@@ -22,12 +22,12 @@
     'fullsearch' with localized string. If both missing, default to
     True (might happen with Safari) if this isn't an advanced search.
 """
-    form = request.form
+    form = request.values
     if 'titlesearch' in form and 'fullsearch' in form:
         ret = -1 # spammer / bot
     else:
         try:
-            ret = int(form['titlesearch'][0])
+            ret = int(form['titlesearch'])
         except ValueError:
             ret = 1
         except KeyError:
@@ -37,7 +37,7 @@
 def isAdvancedSearch(request):
     """ Return True if advanced search is requested """
     try:
-        return int(request.form['advancedsearch'][0])
+        return int(request.values['advancedsearch'])
     except KeyError:
         return False
 
@@ -66,38 +66,40 @@
 
     advancedsearch = isAdvancedSearch(request)
 
+    form = request.values
+
     # context is relevant only for full search
     if titlesearch:
         context = 0
     elif advancedsearch:
         context = 180 # XXX: hardcoded context count for advancedsearch
     else:
-        context = int(request.form.get('context', [0])[0])
+        context = int(form.get('context', 0))
 
     # Get other form parameters
-    needle = request.form.get(fieldname, [''])[0]
-    case = int(request.form.get('case', [0])[0])
-    regex = int(request.form.get('regex', [0])[0]) # no interface currently
-    hitsFrom = int(request.form.get('from', [0])[0])
+    needle = form.get(fieldname, '')
+    case = int(form.get('case', 0))
+    regex = int(form.get('regex', 0)) # no interface currently
+    hitsFrom = int(form.get('from', 0))
     mtime = None
     msg = ''
     historysearch = 0
 
     # if advanced search is enabled we construct our own search query
     if advancedsearch:
-        and_terms = request.form.get('and_terms', [''])[0].strip()
-        or_terms = request.form.get('or_terms', [''])[0].strip()
-        not_terms = request.form.get('not_terms', [''])[0].strip()
-        #xor_terms = request.form.get('xor_terms', [''])[0].strip()
-        categories = request.form.get('categories', [''])
-        timeframe = request.form.get('time', [''])[0].strip()
-        language = request.form.get('language', [''])
-        mimetype = request.form.get('mimetype', [0])
-        excludeunderlay = request.form.get('excludeunderlay', [0])[0]
-        nosystemitems = request.form.get('nosystemitems', [0])[0]
-        historysearch = request.form.get('historysearch', [0])[0]
+        and_terms = form.get('and_terms', '').strip()
+        or_terms = form.get('or_terms', '').strip()
+        not_terms = form.get('not_terms', '').strip()
+        #xor_terms = form.get('xor_terms', '').strip()
+        categories = form.getlist('categories') or ['']
+        timeframe = form.get('time', '').strip()
+        language = form.getlist('language') or ['']
+        mimetype = form.getlist('mimetype') or [0]
+        excludeunderlay = form.get('excludeunderlay', 0)
+        nosystemitems = form.get('nosystemitems', 0)
+        historysearch = form.get('historysearch', 0)
 
-        mtime = request.form.get('mtime', [''])[0]
+        mtime = form.get('mtime', '')
         if mtime:
             mtime_parsed = None
 
@@ -224,12 +226,10 @@
         Page(request, pagename).send_page()
         return
 
-    request.emit_http_headers()
-
     # This action generates data using the user language
     request.setContentLanguage(request.lang)
 
-    request.theme.send_title(title % needle, form=request.form, pagename=pagename)
+    request.theme.send_title(title % needle, pagename=pagename)
 
     # Start content (important for RTL support)
     request.write(request.formatter.startContent("content"))
--- a/MoinMoin/action/info.py	Mon Mar 16 22:39:33 2009 +0100
+++ b/MoinMoin/action/info.py	Mon Mar 16 22:41:59 2009 +0100
@@ -76,7 +76,7 @@
         _ = request.getText
         default_count, limit_max_count = request.cfg.history_count
         try:
-            max_count = int(request.form.get('max_count', [default_count])[0])
+            max_count = int(request.values.get('max_count', default_count))
         except:
             max_count = default_count
         max_count = min(max_count, limit_max_count)
@@ -143,7 +143,7 @@
                     if line.action == 'ATTNEW':
                         actions.append(render_action(_('view'), {'action': 'AttachFile', 'do': 'view', 'target': '%s' % filename}))
                     elif line.action == 'ATTDRW':
-                        actions.append(render_action(_('edit'), {'action': 'AttachFile', 'drawing': '%s' % filename.replace(".draw", "")}))
+                        actions.append(render_action(_('edit'), {'action': 'AttachFile', 'drawing': '%s' % filename.replace(".tdraw", "")}))
 
                     actions.append(render_action(_('get'), {'action': 'AttachFile', 'do': 'get', 'target': '%s' % filename}))
                     if request.user.may.delete(pagename):
@@ -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.values.get('hitcounts', 0)) != 0
+    show_general = int(request.values.get('general', 0)) != 0
 
     if show_hitcounts:
         from MoinMoin.stats import hitcounts
--- a/MoinMoin/action/links.py	Mon Mar 16 22:39:33 2009 +0100
+++ b/MoinMoin/action/links.py	Mon Mar 16 22:41:59 2009 +0100
@@ -11,15 +11,9 @@
 
 def execute(pagename, request):
     _ = request.getText
-    form = request.form
-
     # get the MIME type
-    if 'mimetype' in form:
-        mimetype = form['mimetype'][0]
-    else:
-        mimetype = "text/html"
-
-    request.emit_http_headers(["Content-Type: %s; charset=%s" % (mimetype, config.charset)])
+    mimetype = request.values.get('mimetype', 'text/html')
+    request.mimetype = mimetype
 
     if mimetype == "text/html":
         request.theme.send_title(_('Full Link List for "%s"') % request.cfg.sitename)
--- a/MoinMoin/action/login.py	Mon Mar 16 22:39:33 2009 +0100
+++ b/MoinMoin/action/login.py	Mon Mar 16 22:41:59 2009 +0100
@@ -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:
@@ -70,7 +69,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	Mon Mar 16 22:39:33 2009 +0100
+++ b/MoinMoin/action/newaccount.py	Mon Mar 16 22:41:59 2009 +0100
@@ -19,7 +19,7 @@
     _ = request.getText
     form = request.form
 
-    if request.request_method != 'POST':
+    if request.method != 'POST':
         return
 
     if not TextCha(request).check_answer_from_form():
@@ -30,7 +30,7 @@
 
     # Require non-empty name
     try:
-        theuser.name = form['name'][0]
+        theuser.name = form['name']
     except KeyError:
         return _("Empty user name. Please enter a user name.")
 
@@ -45,8 +45,8 @@
         return _("This user name already belongs to somebody else.")
 
     # try to get the password and pw repeat
-    password = form.get('password1', [''])[0]
-    password2 = form.get('password2', [''])[0]
+    password = form.get('password1', '')
+    password2 = form.get('password2', '')
 
     # Check if password is given and matches with password repeat
     if password != password2:
@@ -69,7 +69,7 @@
             return "Can't encode password: %s" % str(err)
 
     # try to get the email, for new users it is required
-    email = wikiutil.clean_input(form.get('email', [''])[0])
+    email = wikiutil.clean_input(form.get('email', ''))
     theuser.email = email.strip()
     if not theuser.email and 'email' not in request.cfg.user_form_remove:
         return _("Please provide your email address. If you lose your"
@@ -172,7 +172,6 @@
         request.theme.add_msg(_create_user(request), "dialog")
         return page.send_page()
     else: # show create form
-        request.emit_http_headers()
         request.theme.send_title(_("Create Account"), pagename=pagename)
 
         request.write(request.formatter.startContent("content"))
--- a/MoinMoin/action/newpage.py	Mon Mar 16 22:39:33 2009 +0100
+++ b/MoinMoin/action/newpage.py	Mon Mar 16 22:41:59 2009 +0100
@@ -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.values.get('pagename')
+        self.nametemplate = self.request.values.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.values.get('template', '')
             if template:
                 query['template'] = template
 
-            parent = self.request.form.get('parent', [''])[0]
+            parent = self.request.values.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	Mon Mar 16 22:39:33 2009 +0100
+++ b/MoinMoin/action/pollsistersites.py	Mon Mar 16 22:41:59 2009 +0100
@@ -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	Mon Mar 16 22:39:33 2009 +0100
+++ b/MoinMoin/action/recoverpass.py	Mon Mar 16 22:41:59 2009 +0100
@@ -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
@@ -152,7 +152,7 @@
 
     page = Page(request, pagename)
     _ = request.getText
-    form = request.form
+    form = request.values # link in mail -> GET request
 
     if not request.cfg.mail_enabled:
         request.theme.add_msg(_("""This wiki is not enabled for mail processing.
@@ -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	Mon Mar 16 22:39:33 2009 +0100
+++ b/MoinMoin/action/refresh.py	Mon Mar 16 22:41:59 2009 +0100
@@ -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.values.get('arena', 'Page.py')
     if arena == 'Page.py':
         arena = Page(request, pagename)
-    key = request.form.get('key', ['text_html'])[0]
+    key = request.values.get('key', 'text_html')
 
     # Remove cache entry (if exists), and send the page
     from MoinMoin import caching
--- a/MoinMoin/action/revert.py	Mon Mar 16 22:39:33 2009 +0100
+++ b/MoinMoin/action/revert.py	Mon Mar 16 22:41:59 2009 +0100
@@ -48,10 +48,10 @@
     def do_action(self):
         """ revert pagename """
         form = self.form
-        comment = form.get('comment', [u''])[0]
+        comment = form.get('comment', u'')
         comment = wikiutil.clean_input(comment)
 
-        if self.request.request_method != 'POST':
+        if self.request.method != 'POST':
             return False, u''
 
         rev = self.request.rev
--- a/MoinMoin/action/rss_rc.py	Mon Mar 16 22:39:33 2009 +0100
+++ b/MoinMoin/action/rss_rc.py	Mon Mar 16 22:41:59 2009 +0100
@@ -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.values['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.values.get('unique', 0))
     except ValueError:
         unique = 0
     try:
-        diffs = int(request.form.get('diffs', [0])[0])
+        diffs = int(request.values.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.values.get('ddiffs', 0))
     except ValueError:
         ddiffs = 0
 
@@ -86,31 +85,27 @@
     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 += '/'
+        baseurl = request.url_root
 
         logo = re.search(r'src="([^"]*)"', cfg.logo_string)
         if logo:
@@ -139,7 +134,7 @@
 
         # emit channel description
         handler.startNode('channel', {
-            (handler.xmlns['rdf'], 'about'): request.getBaseURL(),
+            (handler.xmlns['rdf'], 'about'): request.url_root,
             })
         handler.simpleNode('title', cfg.sitename)
         page = Page(request, pagename)
--- a/MoinMoin/action/serveopenid.py	Mon Mar 16 22:39:33 2009 +0100
+++ b/MoinMoin/action/serveopenid.py	Mon Mar 16 22:41:59 2009 +0100
@@ -18,7 +18,7 @@
 from openid.server import server
 from openid.message import IDENTIFIER_SELECT
 from MoinMoin.widget import html
-from MoinMoin.request import MoinMoinFinish
+from MoinMoin.web.request import MoinMoinFinish
 
 def execute(pagename, request):
     return MoinOpenIDServer(pagename, request).handle()
@@ -31,9 +31,8 @@
 
     def serveYadisEP(self, endpoint_url):
         request = self.request
-        hdrs = ['Content-type: application/xrds+xml']
+        request.content_type = 'application/xrds+xml'
 
-        request.emit_http_headers(hdrs)
         user_url = request.getQualifiedURL(request.page.url(request))
         self.request.write("""\
 <?xml version="1.0" encoding="UTF-8"?>
@@ -79,9 +78,8 @@
 
     def serveYadisIDP(self, endpoint_url):
         request = self.request
-        hdrs = ['Content-type: application/xrds+xml']
+        request.content_type = 'application/xrds+xml'
 
-        request.emit_http_headers(hdrs)
         user_url = request.getQualifiedURL(request.page.url(request))
         self.request.write("""\
 <?xml version="1.0" encoding="UTF-8"?>
@@ -190,7 +188,7 @@
         server_url = request.getQualifiedURL(
                          request.page.url(request, querystr={'action': 'serveopenid'}))
 
-        yadis_type = form.get('yadis', [None])[0]
+        yadis_type = form.get('yadis')
         if yadis_type == 'ep':
             return self.serveYadisEP(server_url)
         elif yadis_type == 'idp':
@@ -198,7 +196,7 @@
 
         # if the identity is set it must match the server URL
         # sort of arbitrary, but we have to have some restriction
-        identity = form.get('openid.identity', [None])[0]
+        identity = form.get('openid.identity')
         if identity == IDENTIFIER_SELECT:
             identity, server_url = self._make_identity()
             if not identity:
@@ -217,7 +215,7 @@
         openidsrv = server.Server(store, op_endpoint=server_url)
 
         answer = None
-        if form.has_key('dontapprove'):
+        if 'dontapprove' in form:
             answer = self.handle_response(False, username, identity)
             if answer is None:
                 return
@@ -227,8 +225,8 @@
                 return
         else:
             query = {}
-            for key in form.keys():
-                query[key] = form[key][0]
+            for key in form:
+                query[key] = form[key]
             try:
                 openidreq = openidsrv.decodeRequest(query)
             except Exception, e:
@@ -249,10 +247,9 @@
             else:
                 answer = openidsrv.handleRequest(openidreq)
         webanswer = openidsrv.encodeResponse(answer)
-        headers = ['Status: %d OpenID status' % webanswer.code]
+        request.status = '%d OpenID status' % webanswer.code
         for hdr in webanswer.headers:
-            headers += [hdr+': '+webanswer.headers[hdr]]
-        request.emit_http_headers(headers)
+            request.headers.add(hdr, webanswer.headers[hdr])
         request.write(webanswer.body)
         raise MoinMoinFinish
 
@@ -266,7 +263,7 @@
         if session_nonce is not None:
             del self.request.session['openidserver.nonce']
         # use empty string if nothing was sent
-        form_nonce = form.get('nonce', [''])[0]
+        form_nonce = form.get('nonce', '')
         if session_nonce != form_nonce:
             self.request.makeForbidden403()
             self.request.write('invalid nonce')
@@ -285,7 +282,7 @@
             return openidreq.answer(False)
 
 
-        if form.get('remember', ['no'])[0] == 'yes':
+        if form.get('remember', 'no') == 'yes':
             if not hasattr(request.user, 'openid_trusted_roots'):
                 request.user.openid_trusted_roots = []
             request.user.openid_trusted_roots.append(strbase64(openidreq.trust_root))
@@ -324,7 +321,6 @@
 Once you have logged in, simply reload this page.'''))
             return
 
-        request.emit_http_headers()
         request.theme.send_title(_("OpenID Trust verification"), pagename=request.page.page_name)
         # Start content (important for RTL support)
         request.write(request.formatter.startContent("content"))
@@ -397,7 +393,6 @@
         request = self.request
         _ = self._
 
-        request.emit_http_headers()
         request.theme.send_title(_("OpenID not served"), pagename=request.page.page_name)
         # Start content (important for RTL support)
         request.write(request.formatter.startContent("content"))
--- a/MoinMoin/action/showtags.py	Mon Mar 16 22:39:33 2009 +0100
+++ b/MoinMoin/action/showtags.py	Mon Mar 16 22:41:59 2009 +0100
@@ -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	Mon Mar 16 22:39:33 2009 +0100
+++ b/MoinMoin/action/sisterpages.py	Mon Mar 16 22:41:59 2009 +0100
@@ -32,31 +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 += '/'
-
         # Get list of user readable pages
         pages = request.rootpage.getPageList()
         pages.sort()
--- a/MoinMoin/action/sitemap.py	Mon Mar 16 22:39:33 2009 +0100
+++ b/MoinMoin/action/sitemap.py	Mon Mar 16 22:41:59 2009 +0100
@@ -62,10 +62,9 @@
 
 def execute(pagename, request):
     _ = request.getText
-    form = request.form
     request.user.datetime_fmt = datetime_fmt
 
-    request.emit_http_headers(["Content-Type: text/xml; charset=UTF-8"])
+    request.mimetype = 'text/xml'
 
     # we emit a piece of data so other side doesn't get bored:
     request.write("""<?xml version="1.0" encoding="UTF-8"?>\r\n""")
@@ -74,9 +73,7 @@
     result.append("""<urlset xmlns="http://www.google.com/schemas/sitemap/0.84">\n""")
 
     # we include the root url as an important and often changed URL
-    rooturl = request.getScriptname()
-    if not rooturl.endswith('/'):
-        rooturl += '/'
+    rooturl = request.script_root + '/'
     result.append(make_url_xml(request, {
         'url': rooturl,
         'lastmod': now(), # fake
@@ -86,7 +83,7 @@
 
     # Get page dict readable by current user
     try:
-        underlay = int(form.get('underlay', [1])[0])
+        underlay = int(request.values.get('underlay', 1))
     except ValueError:
         underlay = 1
     pages = request.rootpage.getPageDict(include_underlay=underlay)
--- a/MoinMoin/action/thread_monitor.py	Mon Mar 16 22:39:33 2009 +0100
+++ b/MoinMoin/action/thread_monitor.py	Mon Mar 16 22:41:59 2009 +0100
@@ -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	Mon Mar 16 22:39:33 2009 +0100
+++ b/MoinMoin/action/titleindex.py	Mon Mar 16 22:41:59 2009 +0100
@@ -14,15 +14,9 @@
 
 
 def execute(pagename, request):
-    form = request.form
-
     # get the MIME type
-    if 'mimetype' in form:
-        mimetype = form['mimetype'][0]
-    else:
-        mimetype = "text/plain"
-
-    request.emit_http_headers(["Content-Type: %s; charset=%s" % (mimetype, config.charset)])
+    mimetype = request.values.get('mimetype', "text/plain")
+    request.mimetype = mimetype
 
     # Get list of user readable pages
     pages = request.rootpage.getPageList()
--- a/MoinMoin/action/userprefs.py	Mon Mar 16 22:39:33 2009 +0100
+++ b/MoinMoin/action/userprefs.py	Mon Mar 16 22:41:59 2009 +0100
@@ -16,7 +16,7 @@
     Return error msg_class, msg tuple or None, None.
     """
     _ = request.getText
-    sub = request.form.get('handler', [None])[0]
+    sub = request.values.get('handler')
 
     if sub in request.cfg.userprefs_disabled:
         return None, None
@@ -63,12 +63,12 @@
     # returns text, title, msg_class, msg
     pagename = request.page.page_name
 
-    if 'handler' in request.form:
+    if 'handler' in request.values:
         msg_class, msg = _handle_submission(request)
     else:
         msg_class, msg = None, None
 
-    sub = request.form.get('sub', [''])[0]
+    sub = request.args.get('sub', '')
     cls = None
     if sub and sub not in request.cfg.userprefs_disabled:
         try:
@@ -101,7 +101,6 @@
         title = _("Settings") + ":" + title
     else:
         title = _("Settings")
-    request.emit_http_headers()
     request.theme.add_msg(msg, msg_class)
     request.theme.send_title(title, page=request.page, pagename=pagename)
     # Start content (important for RTL support)
--- a/MoinMoin/action/userprofile.py	Mon Mar 16 22:39:33 2009 +0100
+++ b/MoinMoin/action/userprofile.py	Mon Mar 16 22:41:59 2009 +0100
@@ -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	Mon Mar 16 22:39:33 2009 +0100
+++ b/MoinMoin/auth/__init__.py	Mon Mar 16 22:41:59 2009 +0100
@@ -128,7 +128,7 @@
     @copyright: 2005-2006 Bastian Blank, Florian Festi,
                           MoinMoin:AlexanderSchremmer, Nick Phillips,
                           MoinMoin:FrankieChow, MoinMoin:NirSoffer,
-                2005-2008 MoinMoin:ThomasWaldmann,
+                2005-2009 MoinMoin:ThomasWaldmann,
                 2007      MoinMoin:JohannesBerg
 
     @license: GNU GPL, see COPYING for details.
@@ -160,9 +160,7 @@
     if request.page:
         return request.page.url(request, querystr=fields)
     else:
-        qstr = wikiutil.makeQueryString(fields)
-        return ''.join([request.getBaseURL(), '?', qstr])
-
+        return request.abs_href(**fields)
 
 class LoginReturn(object):
     """ LoginReturn - base class for auth method login() return value"""
@@ -257,3 +255,204 @@
                'userprefslink': userprefslink,
                'sendmypasswordlink': sendmypasswordlink}
 
+
+class GivenAuth(BaseAuth):
+    """ reuse a given authentication, e.g. http basic auth (or any other auth)
+        done by the web server, that sets REMOTE_USER environment variable.
+        This is the default behaviour.
+        You can also specify to read another environment variable (env_var).
+        Alternatively you can directly give a fixed user name (user_name)
+        that will be considered as authenticated.
+    """
+    name = 'given' # was 'http' in 1.8.x and before
+
+    def __init__(self,
+                 env_var=None,  # environment variable we want to read (default: REMOTE_USER)
+                 user_name=None,