changeset 4954:043baa5879b3

merged moin/1.8
author Thomas Waldmann <tw AT waldmann-edv DOT de>
date Thu, 20 Aug 2009 15:08:50 +0200
parents 3be35ace8aca (diff) ca4d2f593508 (current diff)
children b16cea282fe0
files MoinMoin/conftest.py MoinMoin/macro/_tests/test_GetVal.py MoinMoin/userprefs/prefs.py contrib/googleimport/googlepush.py
diffstat 2036 files changed, 172527 insertions(+), 110980 deletions(-) [+]
line wrap: on
line diff
--- a/.hgignore	Thu Aug 20 14:42:36 2009 +0200
+++ b/.hgignore	Thu Aug 20 15:08:50 2009 +0200
@@ -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	Thu Aug 20 14:42:36 2009 +0200
+++ b/.hgtags	Thu Aug 20 15:08:50 2009 +0200
@@ -35,3 +35,6 @@
 1f0db10c207f697de8d496f298167d7de76f30ac 1.8.2
 f25e6286fe1306017cda1bc614f5a9f60b382670 1.8.3
 3010c1a941856920ee564297f16570126b0231c0 1.8.4
+d706f5d4f4ecc935a69b0c6c5b90d47a643e82c4 1.9.0beta1
+a04008fe123371f144707ac237196fd7cc37ae90 1.9.0beta2
+47679e758f79d215bd748d2f1a3ec48f46dbadb3 1.9.0beta3
--- a/MANIFEST.in	Thu Aug 20 14:42:36 2009 +0200
+++ b/MANIFEST.in	Thu Aug 20 15:08:50 2009 +0200
@@ -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	Thu Aug 20 14:42:36 2009 +0200
+++ b/Makefile	Thu Aug 20 15:08:50 2009 +0200
@@ -13,33 +13,25 @@
 
 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://master19.moinmo.in/moin_static.../#../MoinMoin/web/static/htdocs/#g' \
+		-e 's#http://static.moinmo.in/moin_static.../#../MoinMoin/web/static/htdocs/#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://master19.moinmo.in/moin_static.../#../MoinMoin/web/static/htdocs/#g' \
+		-e 's#http://static.moinmo.in/moin_static.../#../MoinMoin/web/static/htdocs/#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 +39,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	Thu Aug 20 14:42:36 2009 +0200
+++ b/MoinMoin/Page.py	Thu Aug 20 15:08:50 2009 +0200
@@ -109,7 +109,8 @@
             (for 'meta') or the complete cache ('pagelists').
             @param request: the request object
         """
-        elog = request.editlog
+        from MoinMoin.logfile import editlog
+        elog = editlog.EditLog(request)
         old_pos = self.log_pos
         new_pos, items = elog.news(old_pos)
         if items:
@@ -753,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),
@@ -1065,7 +1064,7 @@
         if 'deprecated' in pi:
             # deprecated page, append last backup version to current contents
             # (which should be a short reason why the page is deprecated)
-            request.theme.add_msg(_('The backed up content of this page is deprecated and will not be included in search results!'), "warning")
+            request.theme.add_msg(_('The backed up content of this page is deprecated and will rank lower in search results!'), "warning")
 
             revisions = self.getRevList()
             if len(revisions) >= 2: # XXX shouldn't that be ever the case!? Looks like not.
@@ -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")
@@ -1146,12 +1144,9 @@
                         openid_username = self.pi['openid.user']
                         userid = user.getUserId(request, openid_username)
 
-                    if request.cfg.openid_server_restricted_users_group:
-                        request.dicts.addgroup(request,
-                                               request.cfg.openid_server_restricted_users_group)
-
-                    if userid is not None and not request.cfg.openid_server_restricted_users_group or \
-                      request.dicts.has_member(request.cfg.openid_server_restricted_users_group, openid_username):
+                    openid_group_name = request.cfg.openid_server_restricted_users_group
+                    if userid is not None and not openid_group_name or \
+                            (openid_group_name in request.groups and openid_username in request.groups[openid_group_name]):
                         html_head = '<link rel="openid2.provider" href="%s">' % \
                                         wikiutil.escape(request.getQualifiedURL(self.url(request,
                                                                                 querystr={'action': 'serveopenid'})), True)
@@ -1604,12 +1599,6 @@
 
         return Page(self.request, self.page_name, rev=lastRevision).parseACL()
 
-    def clean_acl_cache(self):
-        """
-        Clean ACL cache entry of this page (used by PageEditor on save)
-        """
-        pass # should not be necessary any more as the new cache watches edit-log for changes
-
     # Text format -------------------------------------------------------
 
     def encodeTextMimeType(self, text):
@@ -1868,9 +1857,7 @@
             # WARNING: SLOW
             pages = self.getPageList(user='')
         else:
-            pages = self.request.pages
-            if not pages:
-                pages = self._listPages()
+            pages = self._listPages()
         count = len(pages)
         self.request.clock.stop('getPageCount')
 
--- a/MoinMoin/PageEditor.py	Thu Aug 20 14:42:36 2009 +0200
+++ b/MoinMoin/PageEditor.py	Thu Aug 20 15:08:50 2009 +0200
@@ -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()
 
@@ -784,8 +782,8 @@
             # Users can define their own variables via
             # UserHomepage/MyDict, which override the default variables.
             userDictPage = u.name + "/MyDict"
-            if request.dicts.has_dict(userDictPage):
-                variables.update(request.dicts.dict(userDictPage))
+            if userDictPage in request.dicts:
+                variables.update(request.dicts[userDictPage])
 
         for name in variables:
             text = text.replace('@%s@' % name, variables[name])
@@ -1122,7 +1120,6 @@
             trivial = kw.get('trivial', 0)
             # write the page file
             mtime_usecs, rev = self._write_file(newtext, action, comment, extra, deleted=deleted)
-            self.clean_acl_cache()
             self._save_draft(None, None) # everything fine, kill the draft for this page
 
             if notify:
--- a/MoinMoin/PageGraphicalEditor.py	Thu Aug 20 14:42:36 2009 +0200
+++ b/MoinMoin/PageGraphicalEditor.py	Thu Aug 20 15:08:50 2009 +0200
@@ -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/__init__.py	Thu Aug 20 14:42:36 2009 +0200
+++ b/MoinMoin/_tests/__init__.py	Thu Aug 20 15:08:50 2009 +0200
@@ -115,3 +115,9 @@
     p.form = request.form
     m = macro.Macro(p)
     return m
+
+def nuke_xapian_index(request):
+    """ completely delete everything in xapian index dir """
+    fpath = os.path.join(request.cfg.cache_dir, 'xapian')
+    if os.path.exists(fpath):
+        shutil.rmtree(fpath, True)
--- a/MoinMoin/_tests/ldap_testbase.py	Thu Aug 20 14:42:36 2009 +0200
+++ b/MoinMoin/_tests/ldap_testbase.py	Thu Aug 20 15:08:50 2009 +0200
@@ -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	Thu Aug 20 14:42:36 2009 +0200
+++ b/MoinMoin/_tests/ldap_testdata.py	Thu Aug 20 15:08:50 2009 +0200
@@ -78,14 +78,16 @@
 objectClass: account
 objectClass: simpleSecurityObject
 uid: usera
-userPassword: usera
+# this is md5 encoded 'usera' for password
+userPassword: {MD5}aXqgOSc5gSW7YoLi9BSmvg==
 
 dn: uid=userb,ou=Unit B,ou=Users,ou=testing,dc=example,dc=org
 cn: Vorname Nachname
 objectClass: inetOrgPerson
 sn: Nachname
 uid: userb
-userPassword: userb
+# this is md5 encoded 'userb' for password
+userPassword: {MD5}ThvfQsM7OQFjqSUQOX2XsA==
 
 dn: cn=Group A,ou=Groups,ou=testing,dc=example,dc=org
 cn: Group A
--- a/MoinMoin/_tests/test_PageEditor.py	Thu Aug 20 14:42:36 2009 +0200
+++ b/MoinMoin/_tests/test_PageEditor.py	Thu Aug 20 15:08:50 2009 +0200
@@ -147,13 +147,7 @@
 
     def deleteCaches(self):
         """ Force the wiki to scan the test page into the dicts """
-        from MoinMoin import caching
-        caching.CacheEntry(self.request, 'wikidicts', 'dicts_groups', scope='wiki').remove()
-        if hasattr(self.request, 'dicts'):
-            del self.request.dicts
-        if hasattr(self.request.cfg, 'DICTS_DATA'):
-            del self.request.cfg.DICTS_DATA
-        self.request.pages = {}
+        # New dicts does not require cache refresh.
 
     def deleteTestPage(self):
         """ Delete temporary page, bypass logs and notifications """
@@ -174,6 +168,7 @@
 
     def teardown_method(self, method):
         self.request.cfg.event_handlers = self.old_handlers
+        nuke_page(self.request, u'AutoCreatedMoinMoinTemporaryTestPageFortestSave')
 
     def testSaveAbort(self):
         """Test if saveText() is interrupted if PagePreSave event handler returns Abort"""
--- a/MoinMoin/_tests/test_packages.py	Thu Aug 20 14:42:36 2009 +0200
+++ b/MoinMoin/_tests/test_packages.py	Thu Aug 20 15:08:50 2009 +0200
@@ -17,7 +17,7 @@
 from MoinMoin.action import AttachFile
 from MoinMoin.action.PackagePages import PackagePages
 from MoinMoin.packages import Package, ScriptEngine, MOIN_PACKAGE_FILE, ZipPackage, packLine, unpackLine
-from MoinMoin._tests import become_superuser, create_page, nuke_page
+from MoinMoin._tests import become_trusted, become_superuser, create_page, nuke_page
 from MoinMoin.Page import Page
 from MoinMoin.PageEditor import PageEditor
 
@@ -91,7 +91,7 @@
 
     def testSearch(self):
         package = PackagePages(self.request.rootpage.page_name, self.request)
-        assert package.searchpackage(self.request, "BadCon") == [u'BadContent']
+        assert package.searchpackage(self.request, "title:BadCon") == [u'BadContent']
 
     def testListCreate(self):
         package = PackagePages(self.request.rootpage.page_name, self.request)
@@ -137,6 +137,7 @@
         return zip_file
 
     def testAttachments_after_page_creation(self):
+        become_trusted(self.request)
         pagename = u'PackageTestPageCreatedFirst'
         page = create_page(self.request, pagename, u"This page has not yet an attachments dir")
         script = u"""MoinMoinPackage|1
@@ -154,6 +155,7 @@
         os.unlink(zip_file)
 
     def testAttachments_without_page_creation(self):
+        become_trusted(self.request)
         pagename = u"PackageAttachmentAttachWithoutPageCreation"
         script = u"""MoinMoinPackage|1
 AddAttachment|1_attachment|my_test.txt|%(pagename)s
--- a/MoinMoin/_tests/test_sourcecode.py	Thu Aug 20 14:42:36 2009 +0200
+++ b/MoinMoin/_tests/test_sourcecode.py	Thu Aug 20 15:08:50 2009 +0200
@@ -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	Thu Aug 20 14:42:36 2009 +0200
+++ b/MoinMoin/_tests/test_user.py	Thu Aug 20 15:08:50 2009 +0200
@@ -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_wikidicts.py	Thu Aug 20 14:42:36 2009 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,231 +0,0 @@
-# -*- coding: iso-8859-1 -*-
-"""
-    MoinMoin - MoinMoin.wikidicts tests
-
-    @copyright: 2003-2004 by Juergen Hermann <jh@web.de>,
-                2007 by MoinMoin:ThomasWaldmann
-    @license: GNU GPL, see COPYING for details.
-"""
-
-import py
-import re
-import shutil
-
-from MoinMoin import wikidicts
-from MoinMoin import Page
-from MoinMoin.PageEditor import PageEditor
-from MoinMoin.user import User
-from MoinMoin._tests import append_page, become_trusted, create_page, create_random_string_list, nuke_page, nuke_user
-
-class TestGroupPage:
-
-    def testCamelCase(self):
-        """ wikidicts: initFromText: CamelCase links """
-        text = """
- * CamelCase
-"""
-        assert self.getMembers(text) == ['CamelCase']
-
-    def testExtendedName(self):
-        """ wikidicts: initFromText: extended names """
-        text = """
- * extended name
-"""
-        assert self.getMembers(text) == ['extended name']
-
-    def testExtendedLink(self):
-        """ wikidicts: initFromText: extended link """
-        text = """
- * [[extended link]]
-"""
-        assert self.getMembers(text) == ['extended link']
-
-    def testIgnoreSecondLevelList(self):
-        """ wikidicts: initFromText: ignore non first level items """
-        text = """
-  * second level
-   * third level
-    * forth level
-     * and then some...
-"""
-        assert self.getMembers(text) == []
-
-    def testIgnoreOther(self):
-        """ wikidicts: initFromText: ignore anything but first level list itmes """
-        text = """
-= ignore this =
- * take this
-
-Ignore previous line and this text.
-"""
-        assert self.getMembers(text) == ['take this']
-
-    def testStripWhitespace(self):
-        """ wikidicts: initFromText: strip whitespace around items """
-        text = """
- *   take this
-"""
-        assert self.getMembers(text) == ['take this']
-
-    def getMembers(self, text):
-        group = wikidicts.Group(self.request, '')
-        group.initFromText(text)
-        return group.members()
-
-
-class TestDictPage:
-
-    def testGroupMembers(self):
-        """ wikidicts: create dict from keys and values in text """
-        text = '''
-Text ignored
- * list items ignored
-  * Second level list ignored
- First:: first item
- text with spaces:: second item
-
-Empty lines ignored, so is this text
-Next line has key with empty value
- Empty string::\x20
- Last:: last item
-'''
-        d = wikidicts.Dict(self.request, '')
-        d.initFromText(text)
-        assert d['First'] == 'first item'
-        assert d['text with spaces'] == 'second item'
-        assert d['Empty string'] == '' # XXX fails if trailing blank is missing
-        assert d['Last'] == 'last item'
-        assert len(d) == 4
-
-class TestGroupDicts:
-
-    def testSystemPagesGroupInDicts(self):
-        """ wikidict: names in SystemPagesGroup should be in request.dicts
-
-        Get a list of all pages, and check that the dicts list all of them.
-
-        Assume that the SystemPagesGroup is in the data or the underlay dir.
-        """
-        assert Page.Page(self.request, 'SystemPagesGroup').exists(), "SystemPagesGroup is missing, Can't run test"
-        systemPages = wikidicts.Group(self.request, 'SystemPagesGroup')
-        #print repr(systemPages)
-        #print repr(self.request.dicts['SystemPagesGroup'])
-        for member in systemPages.members():
-            assert self.request.dicts.has_member('SystemPagesGroup', member), '%s should be in request.dict' % member
-
-        members, groups = self.request.dicts.expand_group('SystemPagesGroup')
-        assert 'SystemPagesInEnglishGroup' in groups
-        assert 'RecentChanges' in members
-        assert 'HelpContents' in members
-
-    def testRenameGroupPage(self):
-        """
-         tests if the dict cache for groups is refreshed after renaming a Group page
-        """
-        request = self.request
-        become_trusted(request)
-        page = create_page(request, u'SomeGroup', u" * ExampleUser")
-        page.renamePage('AnotherGroup')
-        group = wikidicts.Group(request, '')
-        isgroup = request.cfg.cache.page_group_regexact.search
-        grouppages = request.rootpage.getPageList(user='', filter=isgroup)
-        result = request.dicts.has_member(u'AnotherGroup', u'ExampleUser')
-        nuke_page(request, u'AnotherGroup')
-
-        assert result is True
-
-    def testCopyGroupPage(self):
-        """
-         tests if the dict cache for groups is refreshed after copying a Group page
-        """
-        request = self.request
-        become_trusted(request)
-        page = create_page(request, u'SomeGroup', u" * ExampleUser")
-        page.copyPage(u'OtherGroup')
-        group = wikidicts.Group(request, '')
-        isgroup = request.cfg.cache.page_group_regexact.search
-        grouppages = request.rootpage.getPageList(user='', filter=isgroup)
-        result = request.dicts.has_member(u'OtherGroup', u'ExampleUser')
-        nuke_page(request, u'OtherGroup')
-        nuke_page(request, u'SomeGroup')
-
-        assert result is True
-
-    def testAppendingGroupPage(self):
-        """
-         tests scalability by appending a name to a large list of group members
-        """
-        # long list of users
-        page_content = [u" * %s" % member for member in create_random_string_list(length=15, count=30000)]
-        request = self.request
-        become_trusted(request)
-        test_user = create_random_string_list(length=15, count=1)[0]
-        page = create_page(request, u'UserGroup', "\n".join(page_content))
-        page = append_page(request, u'UserGroup', u' * %s' % test_user)
-        result = request.dicts.has_member('UserGroup', test_user)
-        nuke_page(request, u'UserGroup')
-
-        assert result is True
-
-    def testUserAppendingGroupPage(self):
-        """
-         tests appending a username to a large list of group members and user creation
-        """
-        # long list of users
-        page_content = [u" * %s" % member for member in create_random_string_list()]
-        request = self.request
-        become_trusted(request)
-        test_user = create_random_string_list(length=15, count=1)[0]
-        page = create_page(request, u'UserGroup', "\n".join(page_content))
-        page = append_page(request, u'UserGroup', u' * %s' % test_user)
-
-        # now shortly later we create a user object
-        user = User(request, name=test_user)
-        if not user.exists():
-            User(request, name=test_user, password=test_user).save()
-
-        result = request.dicts.has_member('UserGroup', test_user)
-        nuke_page(request, u'UserGroup')
-        nuke_user(request, test_user)
-
-        assert result is True
-
-    def testMemberRemovedFromGroupPage(self):
-        """
-         tests appending a member to a large list of group members and recreating the page without the member
-        """
-        # long list of users
-        page_content = [u" * %s" % member for member in create_random_string_list()]
-        page_content = "\n".join(page_content)
-        request = self.request
-        become_trusted(request)
-        test_user = create_random_string_list(length=15, count=1)[0]
-        page = create_page(request, u'UserGroup', page_content)
-        page = append_page(request, u'UserGroup', u' * %s' % test_user)
-        # saves the text without test_user
-        page.saveText(page_content, 0)
-        result = request.dicts.has_member('UserGroup', test_user)
-        nuke_page(request, u'UserGroup')
-
-        assert result is False
-
-    def testGroupPageTrivialChange(self):
-        """
-         tests appending a username to a group page by trivial change
-        """
-        request = self.request
-        become_trusted(request)
-        test_user = create_random_string_list(length=15, count=1)[0]
-        member = u" * %s\n" % test_user
-        page = create_page(request, u'UserGroup', member)
-        # next member saved  as trivial change
-        test_user = create_random_string_list(length=15, count=1)[0]
-        member = u" * %s\n" % test_user
-        page.saveText(member, 0, trivial=1)
-        result = request.dicts.has_member('UserGroup', test_user)
-        nuke_page(request, u'UserGroup')
-
-        assert result is True
-
-coverage_modules = ['MoinMoin.wikidicts']
-
--- a/MoinMoin/_tests/test_wikiutil.py	Thu Aug 20 14:42:36 2009 +0200
+++ b/MoinMoin/_tests/test_wikiutil.py	Thu Aug 20 15:08:50 2009 +0200
@@ -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:
@@ -87,15 +85,8 @@
             assert wikiutil.join_wiki(baseurl, pagename) == url
 
 
-class TestSystemPagesGroup:
-    def testSystemPagesGroupNotEmpty(self):
-        assert self.request.dicts.members('SystemPagesGroup')
-
 class TestSystemPage:
     systemPages = (
-        # First level, on SystemPagesGroup
-        'SystemPagesInEnglishGroup',
-        # Second level, on one of the pages above
         'RecentChanges',
         'TitleIndex',
         )
@@ -973,4 +964,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	Thu Aug 20 15:08:50 2009 +0200
@@ -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	Thu Aug 20 14:42:36 2009 +0200
+++ b/MoinMoin/action/AttachFile.py	Thu Aug 20 15:08:50 2009 +0200
@@ -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))
@@ -409,14 +401,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
@@ -461,7 +454,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>
@@ -478,14 +471,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(),
 })
@@ -496,7 +488,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)
 
 
@@ -508,12 +500,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') % do[0]
+        msg = _('Unsupported AttachFile sub-action: %s') % do
     if msg:
         error_msg(pagename, request, msg)
 
@@ -527,7 +519,6 @@
         msg = wikiutil.escape(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")
@@ -539,17 +530,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),
@@ -558,9 +538,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
 
@@ -570,84 +556,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")
 
 
@@ -713,17 +737,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})
@@ -731,7 +755,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)
 
 
@@ -746,11 +770,10 @@
 
     # move file
     d = {'action': action_name,
-         'baseurl': request.getScriptname(),
+         'url': request.href(pagename),
          'do': 'attachment_move',
          'ticket': wikiutil.createTicket(request),
          'pagename': wikiutil.escape(pagename, 1),
-         'pagename_quoted': wikiutil.quoteWikinameURL(pagename),
          'attachment_name': wikiutil.escape(filename, 1),
          'move': _('Move'),
          'cancel': _('Cancel'),
@@ -758,7 +781,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">
@@ -790,6 +813,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
 
@@ -799,9 +862,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()
@@ -817,12 +881,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'))
@@ -1056,7 +1119,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	Thu Aug 20 14:42:36 2009 +0200
+++ b/MoinMoin/action/CopyPage.py	Thu Aug 20 15:08:50 2009 +0200
@@ -52,20 +52,19 @@
             return status, _('TextCha: Wrong answer! Go back and try again...')
 
         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:
@@ -92,7 +91,7 @@
             d = {
                 'textcha': TextCha(self.request).render(),
                 '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	Thu Aug 20 14:42:36 2009 +0200
+++ b/MoinMoin/action/DeletePage.py	Thu Aug 20 15:08:50 2009 +0200
@@ -44,7 +44,7 @@
     def do_action(self):
         """ Delete pagename """
         form = self.form
-        comment = form.get('comment', [u''])[0]
+        comment = form.get('comment', u'')
         comment = wikiutil.clean_input(comment)
 
         # Create a page editor that does not do editor backups, because
@@ -53,11 +53,10 @@
         success, msgs = self.page.deletePage(comment)
 
         delete_subpages = 0
-        if 'delete_subpages' in form:
-            try:
-                delete_subpages = int(form['delete_subpages'][0])
-            except:
-                pass
+        try:
+            delete_subpages = int(form['delete_subpages'])
+        except:
+            pass
 
         if delete_subpages and self.subpages:
             for name in self.subpages:
@@ -75,7 +74,7 @@
 
             d = {
                 'subpage': subpages,
-                'subpages_checked': ('', 'checked')[self.request.form.get('subpages_checked', ['0'])[0] == '1'],
+                'subpages_checked': ('', 'checked')[self.request.form.get('subpages_checked', '0') == '1'],
                 'subpage_label': _('Delete all /subpages too?'),
                 'comment_label': _("Optional reason for the deletion"),
                 'buttons_html': buttons_html,
--- a/MoinMoin/action/Despam.py	Thu Aug 20 14:42:36 2009 +0200
+++ b/MoinMoin/action/Despam.py	Thu Aug 20 15:08:50 2009 +0200
@@ -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	Thu Aug 20 14:42:36 2009 +0200
+++ b/MoinMoin/action/LikePages.py	Thu Aug 20 15:08:50 2009 +0200
@@ -41,8 +41,6 @@
         return
 
     # more than one match, list 'em
-    request.emit_http_headers()
-
     # This action generate data using the user language
     request.setContentLanguage(request.lang)
 
--- a/MoinMoin/action/Load.py	Thu Aug 20 14:42:36 2009 +0200
+++ b/MoinMoin/action/Load.py	Thu Aug 20 15:08:50 2009 +0200
@@ -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	Thu Aug 20 14:42:36 2009 +0200
+++ b/MoinMoin/action/LocalSiteMap.py	Thu Aug 20 15:08:50 2009 +0200
@@ -31,7 +31,6 @@
 
 def execute(pagename, request):
     _ = request.getText
-    request.emit_http_headers()
 
     # This action generate data using the user language
     request.setContentLanguage(request.lang)
--- a/MoinMoin/action/MyPages.py	Thu Aug 20 14:42:36 2009 +0200
+++ b/MoinMoin/action/MyPages.py	Thu Aug 20 15:08:50 2009 +0200
@@ -58,7 +58,6 @@
     pagecontent = pagecontent.replace('\n', '\r\n')
 
     from MoinMoin.parser.text_moin_wiki import Parser as WikiParser
-    request.emit_http_headers()
 
     # This action generate data using the user language
     request.setContentLanguage(request.lang)
--- a/MoinMoin/action/PackagePages.py	Thu Aug 20 14:42:36 2009 +0200
+++ b/MoinMoin/action/PackagePages.py	Thu Aug 20 15:08:50 2009 +0200
@@ -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
@@ -33,9 +34,7 @@
 
     def allowed(self):
         """ Check if user is allowed to do this. """
-        may = self.request.user.may
-        return (not self.__class__.__name__ in self.request.cfg.actions_excluded and
-                may.write(self.pagename))
+        return not self.__class__.__name__ in self.request.cfg.actions_excluded
 
     def render(self):
         """ Render action
@@ -44,9 +43,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 +63,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 +79,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 +100,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 +112,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 +180,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	Thu Aug 20 14:42:36 2009 +0200
+++ b/MoinMoin/action/RenamePage.py	Thu Aug 20 15:08:50 2009 +0200
@@ -45,20 +45,19 @@
         """ Rename this page to "pagename" """
         _ = self._
         form = self.form
-        newpagename = form.get('newpagename', [u''])[0]
-        newpagename = self.request.normalizePagename(newpagename)
-        comment = form.get('comment', [u''])[0]
+        newpagename = form.get('newpagename', u'')
+        newpagename = wikiutil.normalize_pagename(newpagename, self.cfg)
+        comment = form.get('comment', u'')
         comment = wikiutil.clean_input(comment)
 
         self.page = PageEditor(self.request, self.pagename)
         success, msgs = self.page.renamePage(newpagename, comment)
 
         rename_subpages = 0
-        if 'rename_subpages' in form:
-            try:
-                rename_subpages = int(form['rename_subpages'][0])
-            except:
-                pass
+        try:
+            rename_subpages = int(form['rename_subpages'])
+        except:
+            pass
 
         if rename_subpages and self.subpages:
             for name in self.subpages:
@@ -84,7 +83,7 @@
 
             d = {
                 'subpage': subpages,
-                'subpages_checked': ('', 'checked')[self.request.form.get('subpages_checked', ['0'])[0] == '1'],
+                'subpages_checked': ('', 'checked')[self.request.form.get('subpages_checked', '0') == '1'],
                 'subpage_label': _('Rename all /subpages too?'),
                 'pagename': wikiutil.escape(self.pagename, True),
                 'newname_label': _("New name"),
--- a/MoinMoin/action/RenderAsDocbook.py	Thu Aug 20 14:42:36 2009 +0200
+++ b/MoinMoin/action/RenderAsDocbook.py	Thu Aug 20 15:08:50 2009 +0200
@@ -9,5 +9,5 @@
 
 def execute(pagename, request):
     url = Page(request, pagename).url(request, {'action': 'show', 'mimetype': 'text/docbook'})
-    request.http_redirect(url)
+    return request.http_redirect(url)
 
--- a/MoinMoin/action/SpellCheck.py	Thu Aug 20 14:42:36 2009 +0200
+++ b/MoinMoin/action/SpellCheck.py	Thu Aug 20 15:08:50 2009 +0200
@@ -102,7 +102,7 @@
     from MoinMoin.PageEditor import PageEditor
     # get the new words as a string (if any are marked at all)
     try:
-        newwords = request.form['newwords']
+        newwords = request.form.getlist('newwords')
     except KeyError:
         # no new words checked
         return
@@ -187,8 +187,8 @@
 
         # add a form containing the bad words
         if own_form:
-            msg = msg + ('<form method="post" action="%s/%s">\n'
-                         '<input type="hidden" name="action" value="%s">\n') % (request.getScriptname(), wikiutil.quoteWikinameURL(page.page_name), action_name)
+            msg = msg + ('<form method="post" action="%s">\n'
+                         '<input type="hidden" name="action" value="%s">\n') % (request.href(page.page_name), action_name)
 
         checkbox = '<input type="checkbox" name="newwords" value="%(word)s">%(word)s&nbsp;&nbsp;'
         msg = msg + (
--- a/MoinMoin/action/SubscribeUser.py	Thu Aug 20 14:42:36 2009 +0200
+++ b/MoinMoin/action/SubscribeUser.py	Thu Aug 20 15:08:50 2009 +0200
@@ -16,30 +16,28 @@
 
 def show_form(pagename, request):
     _ = request.getText
-    request.emit_http_headers()
     request.theme.send_title(_("Subscribe users to the page %s") % pagename, pagename=pagename)
 
     request.write("""
-<form action="%s/%s" method="POST" enctype="multipart/form-data">
+<form action="%s" method="POST" enctype="multipart/form-data">
 <input type="hidden" name="action" value="SubscribeUser">
 %s <input type="text" name="users" size="50">
 <input type="submit" value="Subscribe">
 </form>
-""" % (request.getScriptname(), wikiutil.quoteWikinameURL(pagename),
+""" % (request.href(pagename),
       _("Enter user names (comma separated):")))
     request.theme.send_footer(pagename)
     request.theme.send_closing_html()
 
 def show_result(pagename, request):
     _ = request.getText
-    request.emit_http_headers()
 
     request.theme.send_title(_("Subscribed for %s:") % pagename, pagename=pagename)
 
     from MoinMoin.formatter.text_html import Formatter
     formatter = Formatter(request)
 
-    result = subscribe_users(request, request.form['users'][0].split(","), pagename, formatter)
+    result = subscribe_users(request, request.form['users'].split(","), pagename, formatter)
     request.write(result)
 
     request.theme.send_footer(pagename)
@@ -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	Thu Aug 20 14:42:36 2009 +0200
+++ b/MoinMoin/action/SyncPages.py	Thu Aug 20 15:08:50 2009 +0200
@@ -17,7 +17,6 @@
 from MoinMoin.packages import unpackLine, packLine
 from MoinMoin.PageEditor import PageEditor, conflict_markers
 from MoinMoin.Page import Page
-from MoinMoin.wikidicts import Dict
 from MoinMoin.wikisync import TagStore, UnsupportedWikiException, SyncPage, NotAllowedException
 from MoinMoin.wikisync import MoinLocalWiki, MoinRemoteWiki, UP, DOWN, BOTH, MIMETYPE_MOIN
 from MoinMoin.support.python_compatibility import set
@@ -107,7 +106,7 @@
             "password": None,
         }
 
-        options.update(Dict(self.request, self.pagename))
+        options.update(request.dicts[self.pagename])
 
         # Convert page and group list strings to lists
         if options["pageList"] is not None:
@@ -122,8 +121,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 +177,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	Thu Aug 20 14:42:36 2009 +0200
+++ b/MoinMoin/action/__init__.py	Thu Aug 20 15:08:50 2009 +0200
@@ -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	Thu Aug 20 14:42:36 2009 +0200
+++ b/MoinMoin/action/_tests/test_attachfile.py	Thu Aug 20 15:08:50 2009 +0200
@@ -56,7 +56,10 @@
         """
         Tests if AttachFile.getFilename creates the attachment dir on self.requesting
         """
+        become_trusted(self.request)
+
         filename = ""
+
         file_exists = os.path.exists(AttachFile.getFilename(self.request, self.pagename, filename))
 
         nuke_page(self.request, self.pagename)
@@ -68,7 +71,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	Thu Aug 20 14:42:36 2009 +0200
+++ b/MoinMoin/action/_tests/test_cache.py	Thu Aug 20 15:08:50 2009 +0200
@@ -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	Thu Aug 20 14:42:36 2009 +0200
+++ b/MoinMoin/action/backup.py	Thu Aug 20 15:08:50 2009 +0200
@@ -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	Thu Aug 20 14:42:36 2009 +0200
+++ b/MoinMoin/action/bookmark.py	Thu Aug 20 15:08:50 2009 +0200
@@ -19,7 +19,7 @@
         request.theme.add_msg(_("You must login to use this action: %(action)s.") % {"action": actname}, "error")
         return Page(request, pagename).send_page()
 
-    timestamp = request.form.get('time', [None])[0]
+    timestamp = request.values.get('time')
     if timestamp is not None:
         if timestamp == 'del':
             tm = None
--- a/MoinMoin/action/cache.py	Thu Aug 20 14:42:36 2009 +0200
+++ b/MoinMoin/action/cache.py	Thu Aug 20 15:08:50 2009 +0200
@@ -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	Thu Aug 20 14:42:36 2009 +0200
+++ b/MoinMoin/action/chart.py	Thu Aug 20 15:08:50 2009 +0200
@@ -19,7 +19,7 @@
         request.theme.add_msg(_("Charts are not available!"), "error")
         return request.page.send_page()
 
-    chart_type = request.form.get('type', [''])[0].strip()
+    chart_type = request.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	Thu Aug 20 14:42:36 2009 +0200
+++ b/MoinMoin/action/diff.py	Thu Aug 20 15:08:50 2009 +0200
@@ -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	Thu Aug 20 14:42:36 2009 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,17 +0,0 @@
-# -*- coding: iso-8859-1 -*-
-"""
-    MoinMoin - dump form data we received (debugging)
-
-    @copyright: 2000-2004 Juergen Hermann <jh@web.de>,
-                2006 MoinMoin:ThomasWaldmann
-    @license: GNU GPL, see COPYING for details.
-"""
-from MoinMoin import util
-
-def execute(pagename, request):
-    """ 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	Thu Aug 20 14:42:36 2009 +0200
+++ b/MoinMoin/action/edit.py	Thu Aug 20 15:08:50 2009 +0200
@@ -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	Thu Aug 20 14:42:36 2009 +0200
+++ b/MoinMoin/action/fckdialog.py	Thu Aug 20 15:08:50 2009 +0200
@@ -15,7 +15,6 @@
 
 def macro_dialog(request):
     help = get_macro_help(request)
-    request.emit_http_headers()
     request.write(
         '''<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
 <html>
@@ -164,13 +163,12 @@
 
 def page_list(request):
     from MoinMoin import search
-    name = request.form.get("pagename", [""])[0]
+    name = request.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	Thu Aug 20 14:42:36 2009 +0200
+++ b/MoinMoin/action/fullsearch.py	Thu Aug 20 15:08:50 2009 +0200
@@ -22,12 +22,12 @@
     'fullsearch' with localized string. If both missing, default to
     True (might happen with Safari) if this isn't an advanced search.
 """
-    form = request.form
+    form = request.values
     if 'titlesearch' in form and 'fullsearch' in form:
         ret = -1 # spammer / bot
     else:
         try:
-            ret = int(form['titlesearch'][0])
+            ret = int(form['titlesearch'])
         except ValueError:
             ret = 1
         except KeyError:
@@ -37,7 +37,7 @@
 def isAdvancedSearch(request):
     """ Return True if advanced search is requested """
     try:
-        return int(request.form['advancedsearch'][0])
+        return int(request.values['advancedsearch'])
     except KeyError:
         return False
 
@@ -66,38 +66,40 @@
 
     advancedsearch = isAdvancedSearch(request)
 
+    form = request.values
+
     # context is relevant only for full search
     if titlesearch:
         context = 0
     elif advancedsearch:
         context = 180 # XXX: hardcoded context count for advancedsearch
     else:
-        context = int(request.form.get('context', [0])[0])
+        context = int(form.get('context', 0))
 
     # Get other form parameters
-    needle = request.form.get(fieldname, [''])[0]
-    case = int(request.form.get('case', [0])[0])
-    regex = int(request.form.get('regex', [0])[0]) # no interface currently
-    hitsFrom = int(request.form.get('from', [0])[0])
+    needle = form.get(fieldname, '')
+    case = int(form.get('case', 0))
+    regex = int(form.get('regex', 0)) # no interface currently
+    hitsFrom = int(form.get('from', 0))
     mtime = None
     msg = ''
     historysearch = 0
 
     # if advanced search is enabled we construct our own search query
     if advancedsearch:
-        and_terms = request.form.get('and_terms', [''])[0].strip()
-        or_terms = request.form.get('or_terms', [''])[0].strip()
-        not_terms = request.form.get('not_terms', [''])[0].strip()
-        #xor_terms = request.form.get('xor_terms', [''])[0].strip()
-        categories = request.form.get('categories', [''])
-        timeframe = request.form.get('time', [''])[0].strip()
-        language = request.form.get('language', [''])
-        mimetype = request.form.get('mimetype', [0])
-        excludeunderlay = request.form.get('excludeunderlay', [0])[0]
-        nosystemitems = request.form.get('nosystemitems', [0])[0]
-        historysearch = request.form.get('historysearch', [0])[0]
+        and_terms = form.get('and_terms', '').strip()
+        or_terms = form.get('or_terms', '').strip()
+        not_terms = form.get('not_terms', '').strip()
+        #xor_terms = form.get('xor_terms', '').strip()
+        categories = form.getlist('categories') or ['']
+        timeframe = form.get('time', '').strip()
+        language = form.getlist('language') or ['']
+        mimetype = form.getlist('mimetype') or [0]
+        excludeunderlay = form.get('excludeunderlay', 0)
+        nosystemitems = form.get('nosystemitems', 0)
+        historysearch = form.get('historysearch', 0)
 
-        mtime = request.form.get('mtime', [''])[0]
+        mtime = form.get('mtime', '')
         if mtime:
             mtime_parsed = None
 
@@ -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	Thu Aug 20 14:42:36 2009 +0200
+++ b/MoinMoin/action/info.py	Thu Aug 20 15:08:50 2009 +0200
@@ -41,7 +41,7 @@
                       f.paragraph(0))
 
         # show attachments (if allowed)
-        attachment_info = action.getHandler(request, 'AttachFile', 'info')
+        attachment_info = action.getHandler(request.cfg, 'AttachFile', 'info')
         if attachment_info:
             request.write(attachment_info(pagename, request))
 
@@ -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,