Mercurial > moin > 1.9
changeset 5700:80fb5c4ed169
merge moin/1.8
line wrap: on
line diff
--- a/.hgignore Sat Jun 05 18:12:09 2010 +0200 +++ b/.hgignore Sat Jun 05 18:28:13 2010 +0200 @@ -4,9 +4,15 @@ ^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 .DS_Store
--- a/.hgtags Sat Jun 05 18:12:09 2010 +0200 +++ b/.hgtags Sat Jun 05 18:28:13 2010 +0200 @@ -38,3 +38,12 @@ 294b97b991d3b394aa7cf16ce18b01d8a64e6ef0 1.8.5 137fcd650f26acbca8a964e0ed21aa378747b71c 1.8.6 753e29c6309862f84f520fe0777dd49afab11708 1.8.7 +d706f5d4f4ecc935a69b0c6c5b90d47a643e82c4 1.9.0beta1 +a04008fe123371f144707ac237196fd7cc37ae90 1.9.0beta2 +47679e758f79d215bd748d2f1a3ec48f46dbadb3 1.9.0beta3 +f105598176f5be79b55a6a131f6936be8b66ca74 1.9.0beta4 +00ca621ffbc25413a6c81f9144edf34b283de606 1.9.0rc1 +9161bf60a745e979b90e04cec7b1507d0b94dd48 1.9.0rc2 +006173cad39c31b1d447418c049fd03ee9928aa0 1.9.0 +1ecb884c0deb7d73aa743a0cd1ea0875935f9c2d 1.9.1 +ced05deb11ae935c2da7b315ae8aa8eb498a92a5 1.9.2
--- a/MANIFEST.in Sat Jun 05 18:12:09 2010 +0200 +++ b/MANIFEST.in Sat Jun 05 18:28:13 2010 +0200 @@ -1,6 +1,6 @@ # MoinMoin - Distutils distribution files # -# Copyright (c) 2001, 2002 by Jürgen Hermann <jh@web.de> +# Copyright (c) 2001, 2002 by Juergen Hermann <jh@web.de> # All rights reserved, see COPYING for details. # additional files not known by setup.py @@ -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 Sat Jun 05 18:12:09 2010 +0200 +++ b/Makefile Sat Jun 05 18:28:13 2010 +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 --parse-only -o ../html-1.9 --name=MoinMoin --url=http://moinmo.in/ 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 Sat Jun 05 18:12:09 2010 +0200 +++ b/MoinMoin/Page.py Sat Jun 05 18:28:13 2010 +0200 @@ -108,7 +108,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: @@ -752,6 +753,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) @@ -759,8 +763,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): @@ -958,33 +960,33 @@ pi['acl'] = security.AccessControlList(request.cfg, acl) return pi - def send_raw(self, content_disposition=None): + def send_raw(self, content_disposition=None, mimetype=None): """ Output the raw page data (action=raw). With no content_disposition, the browser usually just displays the data on the screen, with content_disposition='attachment', it will offer a dialogue to save it to disk (used by Save action). + Supplied mimetype overrides default text/plain. """ request = self.request - request.setHttpHeader("Content-type: text/plain; charset=%s" % config.charset) + request.mimetype = mimetype or '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! + #request.headers['Content-Length'] = 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['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): @@ -1009,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): @@ -1024,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 @@ -1053,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': wikiutil.escape(self.hilite_re), 'error': wikiutil.escape(str(err)), @@ -1064,12 +1064,12 @@ 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. oldpage = Page(request, self.page_name, rev=revisions[1]) - body += oldpage.get_raw_body() + body += oldpage.get_data() del oldpage lang = self.pi.get('language', request.cfg.language_default) @@ -1079,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) @@ -1096,11 +1096,10 @@ # if it does, we must not use the page file mtime as last modified value # The following code is commented because it is incorrect for dynamic pages: #lastmod = os.path.getmtime(self._text_filename()) - #request.setHttpHeader("Last-Modified: %s" % util.timefuncs.formathttpdate(lastmod)) + #request.headers['Last-Modified'] = 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 @@ -1119,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") @@ -1145,12 +1144,11 @@ 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) @@ -1609,12 +1607,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): @@ -1873,9 +1865,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 Sat Jun 05 18:12:09 2010 +0200 +++ b/MoinMoin/PageEditor.py Sat Jun 05 18:28:13 2010 +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']) template_page_escaped = wikiutil.escape(template_page) if request.user.may.read(template_page): raw_body = Page(request, template_page).get_raw_body() @@ -324,6 +323,7 @@ 'lock_secs': lock_secs, }) or '', editor_mode=1, + allow_doubleclick=1, ) request.write(request.formatter.startContent("content")) @@ -333,10 +333,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... @@ -352,7 +351,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))) @@ -411,7 +410,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"), }) @@ -423,6 +422,15 @@ lang = self.pi.get('language', request.cfg.language_default) + if not text_rows: + # if no specific value is given for editor height, but 0, we + # compute the rows from the raw_body line count plus some + # extra rows for adding new text in the editor. Maybe this helps + # with the "double slider" usability issue, esp. for devices like + # the iphone where you can't operate both sliders. + current_rows = len(raw_body.split('\n')) + text_rows = max(10, int(current_rows * 1.5)) + request.write( u'''\ <textarea id="editor-textarea" name="savetext" lang="%(lang)s" dir="%(dir)s" rows="%(rows)d" cols="80" @@ -461,7 +469,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"), }) @@ -470,7 +478,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>") @@ -518,10 +526,10 @@ 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)) + request.http_redirect(pg.url(request)) else: request.theme.add_msg(_('Edit was cancelled.'), "error") self.send_page() @@ -676,7 +684,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() @@ -773,6 +781,7 @@ signature = u.signature() variables = { 'PAGE': self.page_name, + 'TIMESTAMP': now, 'TIME': "<<DateTime(%s)>>" % now, 'DATE': "<<Date(%s)>>" % now, 'ME': u.name, @@ -788,8 +797,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]) @@ -1126,7 +1135,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 Sat Jun 05 18:12:09 2010 +0200 +++ b/MoinMoin/PageGraphicalEditor.py Sat Jun 05 18:28:13 2010 +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']) template_page_escaped = wikiutil.escape(template_page) if request.user.may.read(template_page): raw_body = Page(request, template_page).get_raw_body() @@ -216,6 +215,7 @@ 'lock_secs': lock_secs, }) or '', editor_mode=1, + allow_doubleclick=1, ) request.write(request.formatter.startContent("content")) @@ -225,9 +225,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 @@ -248,7 +247,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))) @@ -299,7 +298,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"), }) @@ -316,13 +315,20 @@ 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! - editor_size = int(request.user.edit_rows) * 22 # 22 height_pixels/line + text_rows = int(request.user.edit_rows) + if not text_rows: + # if no specific value is given for editor height, but 0, we + # compute the rows from the raw_body line count plus some + # extra rows for adding new text in the editor. Maybe this helps + # with the "double slider" usability issue, esp. for devices like + # the iphone where you can't operate both sliders. + current_rows = len(raw_body.split('\n')) + text_rows = max(10, int(current_rows * 1.5)) + editor_size = text_rows * 22 # 22 height_pixels/line word_rule = self.word_rule() request.write(""" @@ -378,7 +384,7 @@ <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"), }) @@ -387,7 +393,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 Sat Jun 05 18:12:09 2010 +0200 +++ b/MoinMoin/_tests/__init__.py Sat Jun 05 18:28:13 2010 +0200 @@ -15,6 +15,7 @@ from MoinMoin.PageEditor import PageEditor from MoinMoin.util import random_string from MoinMoin import caching, user +from MoinMoin.action import AttachFile # Promoting the test user ------------------------------------------- # Usually the tests run as anonymous user, but for some stuff, you @@ -93,6 +94,9 @@ def nuke_page(request, pagename): """ completely delete a page, everything in the pagedir """ + attachments = AttachFile._get_files(request, pagename) + for attachment in attachments: + AttachFile.remove_attachment(request, pagename, attachment) page = PageEditor(request, pagename, do_editor_backup=False) page.deletePage() # really get rid of everything there: @@ -115,3 +119,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/_test_template.py Sat Jun 05 18:12:09 2010 +0200 +++ b/MoinMoin/_tests/_test_template.py Sat Jun 05 18:28:13 2010 +0200 @@ -6,6 +6,7 @@ @copyright: 2003-2004 by Juergen Hermann <jh@web.de>, 2007 MoinMoin:AlexanderSchremmer + 2009 MoinMoin:ReimarBauer @license: GNU GPL, see COPYING for details. """ @@ -13,7 +14,7 @@ from MoinMoin import module_tested -class TestSimpleStuff: +class TestSimpleStuff(object): """ The simplest MoinMoin test class Class name must start with 'Test' to be included in @@ -34,7 +35,7 @@ assert result == expected -class TestComplexStuff: +class TestComplexStuff(object): """ Describe these tests here... Some tests may have a list of tests related to this test case. You
--- a/MoinMoin/_tests/ldap_testbase.py Sat Jun 05 18:12:09 2010 +0200 +++ b/MoinMoin/_tests/ldap_testbase.py Sat Jun 05 18:28:13 2010 +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 Sat Jun 05 18:12:09 2010 +0200 +++ b/MoinMoin/_tests/ldap_testdata.py Sat Jun 05 18:28:13 2010 +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 Sat Jun 05 18:12:09 2010 +0200 +++ b/MoinMoin/_tests/test_PageEditor.py Sat Jun 05 18:28:13 2010 +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 Sat Jun 05 18:12:09 2010 +0200 +++ b/MoinMoin/_tests/test_packages.py Sat Jun 05 18:28:13 2010 +0200 @@ -18,7 +18,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 @@ -86,7 +86,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) @@ -132,6 +132,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 @@ -149,6 +150,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 Sat Jun 05 18:12:09 2010 +0200 +++ b/MoinMoin/_tests/test_sourcecode.py Sat Jun 05 18:28:13 2010 +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 Sat Jun 05 18:12:09 2010 +0200 +++ b/MoinMoin/_tests/test_user.py Sat Jun 05 18:28:13 2010 +0200 @@ -38,11 +38,11 @@ def setup_method(self, method): # Save original user and cookie - self.saved_cookie = self.request.saved_cookie + self.saved_cookie = self.request.cookies self.saved_user = self.request.user # Create anon user for the tests - self.request.saved_cookie = '' + self.request.cookies = {} self.request.user = user.User(self.request) self.user = None @@ -62,7 +62,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 Sat Jun 05 18:12:09 2010 +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 Sat Jun 05 18:12:09 2010 +0200 +++ b/MoinMoin/_tests/test_wikiutil.py Sat Jun 05 18:28:13 2010 +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,127 @@ 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 + +class TestVersion(object): + def test_Version(self): + Version = wikiutil.Version + # test properties + assert Version(1, 2, 3).major == 1 + assert Version(1, 2, 3).minor == 2 + assert Version(1, 2, 3).release == 3 + assert Version(1, 2, 3, '4.5alpha6').additional == '4.5alpha6' + # test Version init and Version to str conversion + assert str(Version(1)) == "1.0.0" + assert str(Version(1, 2)) == "1.2.0" + assert str(Version(1, 2, 3)) == "1.2.3" + assert str(Version(1, 2, 3, '4.5alpha6')) == "1.2.3-4.5alpha6" + assert str(Version(version='1.2.3')) == "1.2.3" + assert str(Version(version='1.2.3-4.5alpha6')) == "1.2.3-4.5alpha6" + # test Version comparison, trivial cases + assert Version() == Version() + assert Version(1) == Version(1) + assert Version(1, 2) == Version(1, 2) + assert Version(1, 2, 3) == Version(1, 2, 3) + assert Version(1, 2, 3, 'foo') == Version(1, 2, 3, 'foo') + assert Version(1) != Version(2) + assert Version(1, 2) != Version(1, 3) + assert Version(1, 2, 3) != Version(1, 2, 4) + assert Version(1, 2, 3, 'foo') != Version(1, 2, 3, 'bar') + assert Version(1) < Version(2) + assert Version(1, 2) < Version(1, 3) + assert Version(1, 2, 3) < Version(1, 2, 4) + assert Version(1, 2, 3, 'bar') < Version(1, 2, 3, 'foo') + assert Version(2) > Version(1) + assert Version(1, 3) > Version(1, 2) + assert Version(1, 2, 4) > Version(1, 2, 3) + assert Version(1, 2, 3, 'foo') > Version(1, 2, 3, 'bar') + # test Version comparison, more delicate cases + assert Version(1, 12) > Version(1, 9) + assert Version(1, 12) > Version(1, 1, 2) + assert Version(1, 0, 0, '0.0a2') > Version(1, 0, 0, '0.0a1') + assert Version(1, 0, 0, '0.0b1') > Version(1, 0, 0, '0.0a9') + assert Version(1, 0, 0, '0.0b2') > Version(1, 0, 0, '0.0b1') + assert Version(1, 0, 0, '0.0c1') > Version(1, 0, 0, '0.0b9') + assert Version(1, 0, 0, '1') > Version(1, 0, 0, '0.0c9') + # test Version playing nice with tuples + assert Version(1, 2, 3) == (1, 2, 3, '') + assert Version(1, 2, 4) > (1, 2, 3) + + coverage_modules = ['MoinMoin.wikiutil'] +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/MoinMoin/_tests/test_wsgiapp.py Sat Jun 05 18:28:13 2010 +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 Sat Jun 05 18:12:09 2010 +0200 +++ b/MoinMoin/action/AttachFile.py Sat Jun 05 18:28:13 2010 +0200 @@ -27,16 +27,24 @@ @license: GNU GPL, see COPYING for details. """ -import os, time, zipfile, mimetypes, errno +import os, time, zipfile, errno, datetime +from StringIO import StringIO + +from werkzeug import http_date 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.events import FileAttachedEvent, FileRemovedEvent, send_event +from MoinMoin.support import tarfile action_name = __name__.split('.')[-1] @@ -77,44 +85,49 @@ 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 not (kw.get('do') in ['get', 'view', None] - and - kw.get('rename') is None): - # create a ticket for the not so harmless operations - # we need action= here because the current action (e.g. "show" page - # with a macro AttachList) may not be the linked-to action, e.g. - # "AttachFile". Also, AttachList can list attachments of another page, - # thus we need to give pagename= also. - kw['ticket'] = wikiutil.createTicket(request, - pagename=pagename, action=action_name) - if kw: - qs = '?%s' % wikiutil.makeQueryString(kw, want_unicode=False) - else: - qs = '' - return "%s/%s%s" % (request.getScriptname(), wikiutil.quoteWikinameURL(pagename), qs) +def get_action(request, filename, do): + generic_do_mapping = { + # do -> action + 'get': action_name, + 'view': action_name, + 'move': action_name, + 'del': action_name, + 'unzip': action_name, + 'install': action_name, + 'upload_form': action_name, + } + basename, ext = os.path.splitext(filename) + do_mapping = request.cfg.extensions_mapping.get(ext, {}) + action = do_mapping.get(do, None) + if action is None: + # we have no special support for this, + # look up whether we have generic support: + action = generic_do_mapping.get(do, None) + return action -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) - else: - url = attachUrl(request, pagename, filename, - 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) - else: - url = attachUrl(request, pagename, filename, - drawing=drawing, action=action_name) - if escaped: - url = wikiutil.escape(url) - return url +def getAttachUrl(pagename, filename, request, addts=0, do='get'): + """ Get URL that points to attachment `filename` of page `pagename`. + For upload url, call with do='upload_form'. + Returns the URL to do the specified "do" action or None, + if this action is not supported. + """ + action = get_action(request, filename, do) + if action: + args = dict(action=action, do=do, target=filename) + if do not in ['get', 'view', # harmless + 'modify', # just renders the applet html, which has own ticket + 'move', # renders rename form, which has own ticket + ]: + # create a ticket for the not so harmless operations + # we need action= here because the current action (e.g. "show" page + # with a macro AttachList) may not be the linked-to action, e.g. + # "AttachFile". Also, AttachList can list attachments of another page, + # thus we need to give pagename= also. + args['ticket'] = wikiutil.createTicket(request, + pagename=pagename, action=action_name) + url = request.href(pagename, **args) + return url def getIndicator(request, pagename): @@ -133,7 +146,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 @@ -197,34 +210,56 @@ filecontent can be either a str (in memory file content), or an open file object (file content in e.g. a tempfile). """ - _ = request.getText - # replace illegal chars target = wikiutil.taintfilename(target) # get directory, and possibly create it attach_dir = getAttachDir(request, pagename, create=1) - # save file fpath = os.path.join(attach_dir, target).encode(config.charset) + exists = os.path.exists(fpath) - if exists and not overwrite: - raise AttachmentAlreadyExists + if exists: + if overwrite: + remove_attachment(request, pagename, target) + else: + raise AttachmentAlreadyExists + + # save file + stream = open(fpath, 'wb') + try: + _write_stream(filecontent, stream) + finally: + stream.close() + + _addLogEntry(request, 'ATTNEW', pagename, target) + + filesize = os.path.getsize(fpath) + event = FileAttachedEvent(request, pagename, target, filesize) + send_event(event) + + return target, filesize + + +def remove_attachment(request, pagename, target): + """ remove attachment <target> of page <pagename> + """ + # replace illegal chars + target = wikiutil.taintfilename(target) + + # get directory, do not create it + attach_dir = getAttachDir(request, pagename, create=0) + # remove file + fpath = os.path.join(attach_dir, target).encode(config.charset) + try: + filesize = os.path.getsize(fpath) + os.remove(fpath) + except: + # either it is gone already or we have no rights - not much we can do about it + filesize = 0 else: - if exists: - try: - os.remove(fpath) - except: - pass - stream = open(fpath, 'wb') - try: - _write_stream(filecontent, stream) - finally: - stream.close() + _addLogEntry(request, 'ATTDEL', pagename, target) - _addLogEntry(request, 'ATTNEW', pagename, target) - - filesize = os.path.getsize(fpath) - event = FileAttachedEvent(request, pagename, target, filesize) + event = FileRemovedEvent(request, pagename, target, filesize) send_event(event) return target, filesize @@ -241,7 +276,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) @@ -261,10 +296,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): @@ -305,6 +340,10 @@ label_unzip = _("unzip") label_install = _("install") + may_read = request.user.may.read(pagename) + may_write = request.user.may.write(pagename) + may_delete = request.user.may.delete(pagename) + html.append(fmt.bullet_list(1)) for file in files: mt = wikiutil.MimeType(filename=file) @@ -317,7 +356,6 @@ } links = [] - may_delete = request.user.may.delete(pagename) if may_delete and not readonly: links.append(fmt.url(1, getAttachUrl(pagename, file, request, do='del')) + fmt.text(label_del) + @@ -332,14 +370,16 @@ fmt.text(label_get) + fmt.url(0)) - if ext == '.draw': - links.append(fmt.url(1, getAttachUrl(pagename, file, request, drawing=base)) + - fmt.text(label_edit) + - fmt.url(0)) - else: - links.append(fmt.url(1, getAttachUrl(pagename, file, request, do='view')) + - fmt.text(label_view) + - fmt.url(0)) + links.append(fmt.url(1, getAttachUrl(pagename, file, request, do='view')) + + fmt.text(label_view) + + fmt.url(0)) + + if may_write and not readonly: + edit_url = getAttachUrl(pagename, file, request, do='modify') + if edit_url: + links.append(fmt.url(1, edit_url) + + fmt.text(label_edit) + + fmt.url(0)) try: is_zipfile = zipfile.is_zipfile(fullpath) @@ -350,9 +390,7 @@ fmt.text(label_install) + fmt.url(0)) elif (not is_package and mt.minor == 'zip' and - may_delete and - request.user.may.read(pagename) and - request.user.may.write(pagename)): + may_read and may_write and may_delete): links.append(fmt.url(1, getAttachUrl(pagename, file, request, do='unzip')) + fmt.text(label_unzip) + fmt.url(0)) @@ -409,49 +447,10 @@ def send_link_rel(request, pagename): files = _get_files(request, pagename) for fname in files: - url = getAttachUrl(pagename, fname, request, do='view', escaped=1) + url = getAttachUrl(pagename, fname, request, do='view') request.write(u'<link rel="Appendix" title="%s" href="%s">\n' % ( - wikiutil.escape(fname, 1), url)) - - -def send_hotdraw(pagename, request): - _ = request.getText - - 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) - helplink = Page(request, "HelpOnActions/AttachFile").url(request) - savelink = attachUrl(request, pagename, '', action=action_name, do='savedrawing') - #savelink = Page(request, pagename).url(request) # XXX include target filename param here for twisted - # request, {'savename': request.form['drawing'][0]+'.draw'} - #savelink = '/cgi-bin/dumpform.bat' - - timestamp = '&ts=%s' % now - - request.write('<h2>' + _("Edit drawing") + '</h2>') - request.write(""" -<p> -<img src="%(pngpath)s%(timestamp)s"> -<applet code="CH.ifa.draw.twiki.TWikiDraw.class" - archive="%(pubpath)s/twikidraw.jar" width="640" height="480"> -<param name="drawpath" value="%(drawpath)s"> -<param name="pngpath" value="%(pngpath)s"> -<param name="savepath" value="%(savelink)s"> -<param name="basename" value="%(basename)s"> -<param name="viewpath" value="%(pagelink)s"> -<param name="helppath" value="%(helplink)s"> -<strong>NOTE:</strong> You need a Java enabled browser to edit the drawing example. -</applet> -</p>""" % { - 'pngpath': pngpath, 'timestamp': timestamp, - 'pubpath': pubpath, 'drawpath': drawpath, - 'savelink': savelink, 'pagelink': pagelink, 'helplink': helplink, - 'basename': wikiutil.escape(basename, 1), -}) - + wikiutil.escape(fname, 1), + wikiutil.escape(url, 1))) def send_uploadform(pagename, request): """ Send the HTML code for the list of already stored attachments and @@ -471,12 +470,12 @@ 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> -<dt>%(upload_label_rename)s</dt> -<dd><input type="text" name="rename" size="50" value="%(rename)s"></dd> +<dt>%(upload_label_target)s</dt> +<dd><input type="text" name="target" size="50" value="%(target)s"></dd> <dt>%(upload_label_overwrite)s</dt> <dd><input type="checkbox" name="overwrite" value="1" %(overwrite_checked)s></dd> </dl> @@ -489,14 +488,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), + 'upload_label_target': _('Rename to'), + 'target': wikiutil.escape(request.values.get('target', ''), 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(), 'ticket': wikiutil.createTicket(request), @@ -508,10 +506,6 @@ 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]: - send_hotdraw(pagename, request) - - ############################################################################# ### Web interface for file upload, viewing and deletion ############################################################################# @@ -520,12 +514,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) @@ -539,7 +533,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") @@ -551,21 +544,10 @@ 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 - if not wikiutil.checkTicket(request, request.form.get('ticket', [''])[0]): + if not wikiutil.checkTicket(request, request.form.get('ticket', '')): return _('Please use the interactive user interface to use action %(actionname)s!') % {'actionname': 'AttachFile.upload' } # Currently we only check TextCha for upload (this is what spammers ususally do), @@ -574,9 +556,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 @@ -586,94 +574,86 @@ 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() - if rename: - target = rename - else: - target = filename + target = form.get('target', u'').strip() + if not target: + 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) -def _do_savedrawing(pagename, request): - _ = request.getText - - if not wikiutil.checkTicket(request, request.form.get('ticket', [''])[0]): - return _('Please use the interactive user interface to use action %(actionname)s!') % {'actionname': 'AttachFile.savedrawing' } - - 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] - - 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) +class ContainerItem: + """ A storage container (multiple objects in 1 tarfile) """ - if ext == '.draw': - _addLogEntry(request, 'ATTDRW', pagename, basename + ext) - filecontent = filecontent.read() # read file completely into memory - filecontent = filecontent.replace("\r", "") - elif ext == '.map': - filecontent = filecontent.read() # read file completely into memory - filecontent = filecontent.strip() + def __init__(self, request, pagename, containername): + self.request = request + self.pagename = pagename + self.containername = containername + self.container_filename = getFilename(request, pagename, containername) - 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 + 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 - # touch attachment directory to invalidate cache if new map is saved - if ext == '.map': - os.utime(attach_dir, None) + def get(self, member): + """ return a file-like object with the member file data + """ + tf = tarfile.TarFile(self.container_filename) + return tf.extractfile(member) - request.emit_http_headers() - request.write("OK") + 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_del(pagename, request): _ = request.getText - if not wikiutil.checkTicket(request, request.form.get('ticket', [''])[0]): + if not wikiutil.checkTicket(request, request.args.get('ticket', '')): return _('Please use the interactive user interface to use action %(actionname)s!') % {'actionname': 'AttachFile.del' } pagename, filename, fpath = _access_file(pagename, request) @@ -682,15 +662,7 @@ if not filename: return # error msg already sent in _access_file - # delete file - os.remove(fpath) - _addLogEntry(request, 'ATTDEL', pagename, filename) - - if request.cfg.xapian_search: - from MoinMoin.search.Xapian import Index - index = Index(request) - if index.exists: - index.remove_item(pagename, filename) + remove_attachment(request, pagename, filename) upload_form(pagename, request, msg=_("Attachment '%(filename)s' deleted.") % {'filename': filename}) @@ -713,10 +685,14 @@ return if new_attachment_path != attachment_path: - # move file + filesize = os.path.getsize(attachment_path) filesys.rename(attachment_path, new_attachment_path) _addLogEntry(request, 'ATTDEL', pagename, attachment) + event = FileRemovedEvent(request, pagename, attachment, filesize) + send_event(event) _addLogEntry(request, 'ATTNEW', new_pagename, new_attachment) + event = FileAttachedEvent(request, new_pagename, new_attachment, filesize) + send_event(event) upload_form(pagename, request, msg=_("Attachment '%(pagename)s/%(filename)s' moved to '%(new_pagename)s/%(new_filename)s'.") % { 'pagename': pagename, @@ -735,17 +711,17 @@ if 'cancel' in request.form: return _('Move aborted!') - if not wikiutil.checkTicket(request, request.form.get('ticket', [''])[0]): + if not wikiutil.checkTicket(request, request.form.get('ticket', '')): return _('Please use the interactive user interface to use action %(actionname)s!') % {'actionname': 'AttachFile.move' } 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}) @@ -753,7 +729,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) @@ -768,11 +744,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'), @@ -780,7 +755,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"> @@ -812,6 +787,49 @@ 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' + + now = time.time() + request.headers['Date'] = http_date(now) + request.headers['Content-Type'] = content_type + request.headers['Last-Modified'] = http_date(timestamp) + request.headers['Expires'] = http_date(now - 365 * 24 * 3600) + #request.headers['Content-Length'] = os.path.getsize(fpath) + content_dispo_string = '%s; filename="%s"' % (content_dispo, filename_enc) + request.headers['Content-Disposition'] = content_dispo_string + + # send data + request.send_file(ci.get(filename)) + + def _do_get(pagename, request): _ = request.getText @@ -821,9 +839,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() @@ -839,12 +858,14 @@ 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), - ]) + now = time.time() + request.headers['Date'] = http_date(now) + request.headers['Content-Type'] = content_type + request.headers['Last-Modified'] = http_date(timestamp) + request.headers['Expires'] = http_date(now - 365 * 24 * 3600) + request.headers['Content-Length'] = os.path.getsize(fpath) + content_dispo_string = '%s; filename="%s"' % (content_dispo, filename_enc) + request.headers['Content-Disposition'] = content_dispo_string # send data request.send_file(open(fpath, 'rb')) @@ -853,7 +874,7 @@ def _do_install(pagename, request): _ = request.getText - if not wikiutil.checkTicket(request, request.form.get('ticket', [''])[0]): + if not wikiutil.checkTicket(request, request.args.get('ticket', '')): return _('Please use the interactive user interface to use action %(actionname)s!') % {'actionname': 'AttachFile.install' } pagename, target, targetpath = _access_file(pagename, request) @@ -880,7 +901,7 @@ def _do_unzip(pagename, request, overwrite=False): _ = request.getText - if not wikiutil.checkTicket(request, request.form.get('ticket', [''])[0]): + if not wikiutil.checkTicket(request, request.args.get('ticket', '')): return _('Please use the interactive user interface to use action %(actionname)s!') % {'actionname': 'AttachFile.unzip' } pagename, filename, fpath = _access_file(pagename, request) @@ -998,12 +1019,17 @@ fmt.url(0)) request.write('%s<br><br>' % link) + if filename.endswith('.tdraw') or filename.endswith('.adraw'): + request.write(fmt.attachment_drawing(filename, '')) + return + mt = wikiutil.MimeType(filename=filename) # destinguishs if browser need a plugin in place if mt.major == 'image' and mt.minor in config.browser_supported_images: + url = getAttachUrl(pagename, filename, request) request.write('<img src="%s" alt="%s">' % ( - getAttachUrl(pagename, filename, request, escaped=1), + wikiutil.escape(url, 1), wikiutil.escape(filename, 1))) return elif mt.major == 'text': @@ -1083,8 +1109,9 @@ if not filename: return + request.formatter.page = Page(request, pagename) + # 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') % { @@ -1128,7 +1155,8 @@ for filename in files: filepath = os.path.join(page_dir, filename) data.addRow(( - Page(request, pagename).link_to(request, querystr="action=AttachFile"), + (Page(request, pagename).link_to(request, + querystr="action=AttachFile"), wikiutil.escape(pagename, 1)), wikiutil.escape(filename.decode(config.charset)), os.path.getsize(filepath), )) @@ -1137,7 +1165,7 @@ from MoinMoin.widget.browser import DataBrowserWidget browser = DataBrowserWidget(request) - browser.setData(data) + browser.setData(data, sort_columns=[0, 1]) return browser.render(method="GET") return ''
--- a/MoinMoin/action/CopyPage.py Sat Jun 05 18:12:09 2010 +0200 +++ b/MoinMoin/action/CopyPage.py Sat Jun 05 18:28:13 2010 +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.args.get('subpages_checked', '0') == '1'], 'subpage_label': _('Copy all /subpages too?'), 'pagename': wikiutil.escape(self.pagename, True), 'newname_label': _("New name")