Mercurial > moin > 1.9
changeset 3020:945368f271ef
Merge with main.
author | Karol 'grzywacz' Nowak <grzywacz@sul.uni.lodz.pl> |
---|---|
date | Sun, 06 Jan 2008 23:39:24 +0100 |
parents | d860ab45d438 (current diff) 5dfd26496da8 (diff) |
children | 313a1d430bcb |
files | wiki/server/moinwsgi.py |
diffstat | 46 files changed, 936 insertions(+), 334 deletions(-) [+] |
line wrap: on
line diff
--- a/Makefile Sun Jan 06 23:38:45 2008 +0100 +++ b/Makefile Sun Jan 06 23:39:24 2008 +0100 @@ -13,9 +13,9 @@ install-docs: -mkdir build - wget -U MoinMoin/Makefile -O build/INSTALL.html "http://moinmaster.wikiwikiweb.de/MoinMoin/InstallDocs?action=print" + wget -U MoinMoin/Makefile -O build/INSTALL.html "http://master.moinmo.in/MoinMoin/InstallDocs?action=print" sed \ - -e 's#href="/#href="http://moinmaster.wikiwikiweb.de/#g' \ + -e 's#href="/#href="http://master.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' \ @@ -28,7 +28,7 @@ -rmdir build interwiki: - wget -U MoinMoin/Makefile -O $(share)/data/intermap.txt "http://moinmaster.wikiwikiweb.de/InterWikiMap?action=raw" + wget -U MoinMoin/Makefile -O $(share)/data/intermap.txt "http://master.moinmo.in/InterWikiMap?action=raw" chmod 664 $(share)/data/intermap.txt check-tabs: @@ -42,8 +42,8 @@ # Should be used only on TW machine underlay: rm -rf $(share)/underlay - MoinMoin/script/moin.py --config-dir=/srv/de.wikiwikiweb.moinmaster/bin15 --wiki-url=moinmaster.wikiwikiweb.de/ maint globaledit - MoinMoin/script/moin.py --config-dir=/srv/de.wikiwikiweb.moinmaster/bin15 --wiki-url=moinmaster.wikiwikiweb.de/ maint reducewiki --target-dir=$(share)/underlay + MoinMoin/script/moin.py --config-dir=/srv/de.wikiwikiweb.moinmaster/bin16 --wiki-url=master.moinmo.in/ maint globaledit + MoinMoin/script/moin.py --config-dir=/srv/de.wikiwikiweb.moinmaster/bin16 --wiki-url=master.moinmo.in/ maint reducewiki --target-dir=$(share)/underlay rm -rf $(share)/underlay/pages/InterWikiMap/ echo -ne "#acl All:read\r\nSee MoinMoin:EditingOnMoinMaster.\r\n" > \ $(share)/underlay/pages/MoinPagesEditorGroup/revisions/00000001
--- a/MoinMoin/Page.py Sun Jan 06 23:38:45 2008 +0100 +++ b/MoinMoin/Page.py Sun Jan 06 23:39:24 2008 +0100 @@ -978,7 +978,7 @@ request.setHttpHeader("Status: 200 OK") request.setHttpHeader("Last-Modified: %s" % util.timefuncs.formathttpdate(os.path.getmtime(self._text_filename()))) text = self.encodeTextMimeType(self.body) - request.setHttpHeader("Content-Length: %d" % len(text)) + #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 @@ -1094,6 +1094,10 @@ request.setHttpHeader('Status: 404 NOTFOUND') request.emit_http_headers() + if not page_exists and self.request.isSpiderAgent: + # don't send any 404 content to bots + return + request.write(self.formatter.startDocument(self.page_name)) # send the page header
--- a/MoinMoin/PageEditor.py Sun Jan 06 23:38:45 2008 +0100 +++ b/MoinMoin/PageEditor.py Sun Jan 06 23:39:24 2008 +0100 @@ -399,6 +399,9 @@ <input type="hidden" name="editor" value="text"> ''' % (button_spellcheck, cancel_button_text, )) + from MoinMoin.security.textcha import TextCha + request.write(TextCha(request).render()) + # Add textarea with page text self.sendconfirmleaving()
--- a/MoinMoin/PageGraphicalEditor.py Sun Jan 06 23:38:45 2008 +0100 +++ b/MoinMoin/PageGraphicalEditor.py Sun Jan 06 23:39:24 2008 +0100 @@ -288,6 +288,9 @@ <input type="hidden" name="editor" value="gui"> ''' % (button_spellcheck, cancel_button_text, )) + from MoinMoin.security.textcha import TextCha + request.write(TextCha(request).render()) + self.sendconfirmleaving() # TODO update state of flgChange to make this work, see PageEditor # Add textarea with page text
--- a/MoinMoin/action/AttachFile.py Sun Jan 06 23:38:45 2008 +0100 +++ b/MoinMoin/action/AttachFile.py Sun Jan 06 23:39:24 2008 +0100 @@ -31,6 +31,7 @@ from MoinMoin import config, wikiutil, 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 import MoinMoin.events.notification as notification @@ -514,6 +515,7 @@ <dt>%(upload_label_overwrite)s</dt> <dd><input type="checkbox" name="overwrite" value="1" %(overwrite_checked)s></dd> </dl> +%(textcha)s <p> <input type="hidden" name="action" value="%(action_name)s"> <input type="hidden" name="do" value="upload"> @@ -530,6 +532,7 @@ 'upload_label_overwrite': _('Overwrite existing attachment of same name'), 'overwrite_checked': ('', 'checked')[request.form.get('overwrite', ['0'])[0] == '1'], 'upload_button': _('Upload'), + 'textcha': TextCha(request).render(), }) #<dt>%(upload_label_mime)s</dt> @@ -555,14 +558,59 @@ """ _ = request.getText - msg = None - do = request.form.get('do') - if do is not None: - do = do[0] if action_name in request.cfg.actions_excluded: msg = _('File attachments are not allowed in this wiki!') - elif 'do' not in request.form: + error_msg(pagename, request, msg) + return + + do = request.form.get('do') + if do is None: upload_form(pagename, request) + return + + msg = None + do = do[0] + + # First handle read-only access to attachments: + if do == 'get': + if request.user.may.read(pagename): + get_file(pagename, request) + else: + msg = _('You are not allowed to get attachments from this page.') + elif do == 'view': + if request.user.may.read(pagename): + view_file(pagename, request) + else: + msg = _('You are not allowed to view attachments of this page.') + elif do == 'move': + if request.user.may.delete(pagename): + send_moveform(pagename, request) + else: + msg = _('You are not allowed to move attachments from this page.') + + # Second handle write access: + elif do == 'upload': + # Currently we only check TextCha for upload (this is what spammers ususally do), + # but it could be extended to more/all attachment write access + if not TextCha(request).check_answer_from_form(): + msg = _('TextCha: Wrong answer! Go back and try again...', formatted=False) + else: + overwrite = 0 + if 'overwrite' in request.form: + try: + overwrite = int(request.form['overwrite'][0]) + except: + pass + if (not overwrite and request.user.may.write(pagename)) or \ + (overwrite and request.user.may.write(pagename) and request.user.may.delete(pagename)): + if 'file' in request.form: + do_upload(pagename, request, overwrite) + else: + # This might happen when trying to upload file names + # with non-ascii characters on Safari. + msg = _("No file content. Delete non ASCII characters from the file name and try again.") + else: + msg = _('You are not allowed to attach a file to this page.') elif do == 'savedrawing': if request.user.may.write(pagename): save_drawing(pagename, request) @@ -570,33 +618,11 @@ request.write("OK") else: msg = _('You are not allowed to save a drawing on this page.') - elif do == 'upload': - overwrite = 0 - if 'overwrite' in request.form: - try: - overwrite = int(request.form['overwrite'][0]) - except: - pass - if (not overwrite and request.user.may.write(pagename)) or \ - (overwrite and request.user.may.write(pagename) and request.user.may.delete(pagename)): - if 'file' in request.form: - do_upload(pagename, request, overwrite) - else: - # This might happen when trying to upload file names - # with non-ascii characters on Safari. - msg = _("No file content. Delete non ASCII characters from the file name and try again.") - else: - msg = _('You are not allowed to attach a file to this page.') elif do == 'del': if request.user.may.delete(pagename): del_file(pagename, request) else: msg = _('You are not allowed to delete attachments on this page.') - elif do == 'move': - if request.user.may.delete(pagename): - send_moveform(pagename, request) - else: - msg = _('You are not allowed to move attachments from this page.') elif do == 'attachment_move': if 'cancel' in request.form: msg = _('Move aborted!') @@ -610,11 +636,6 @@ attachment_move(pagename, request) else: msg = _('You are not allowed to move attachments from this page.') - elif do == 'get': - if request.user.may.read(pagename): - get_file(pagename, request) - else: - msg = _('You are not allowed to get attachments from this page.') elif do == 'unzip': if request.user.may.delete(pagename) and request.user.may.read(pagename) and request.user.may.write(pagename): unzip_file(pagename, request) @@ -625,13 +646,8 @@ install_package(pagename, request) else: msg = _('You are not allowed to install files.') - elif do == 'view': - if request.user.may.read(pagename): - view_file(pagename, request) - else: - msg = _('You are not allowed to view attachments of this page.') else: - msg = _('Unsupported upload action: %s') % (wikiutil.escape(do), ) + msg = _('Unsupported AttachFile sub-action: %s') % (wikiutil.escape(do), ) if msg: error_msg(pagename, request, msg)
--- a/MoinMoin/action/LikePages.py Sun Jan 06 23:38:45 2008 +0100 +++ b/MoinMoin/action/LikePages.py Sun Jan 06 23:39:24 2008 +0100 @@ -232,9 +232,9 @@ for key in keys: if matches[key] == match: request.write(request.formatter.listitem(1)) - request.write(request.formatter.pagelink(1, key)) + request.write(request.formatter.pagelink(1, key, generated=True)) request.write(request.formatter.text(key)) - request.write(request.formatter.pagelink(0, key)) + request.write(request.formatter.pagelink(0, key, generated=True)) request.write(request.formatter.listitem(0)) request.write(request.formatter.bullet_list(0))
--- a/MoinMoin/action/diff.py Sun Jan 06 23:38:45 2008 +0100 +++ b/MoinMoin/action/diff.py Sun Jan 06 23:39:24 2008 +0100 @@ -111,7 +111,7 @@ from MoinMoin.util import diff_text lines = diff_text.diff(oldpage.getlines(), newpage.getlines()) if not lines: - msg = f.text(_("No differences found!")) + msg = f.text(" - " + _("No differences found!", formatted=False)) if edit_count > 1: msg = msg + f.paragraph(1) + f.text(_('The page was saved %(count)d times, though!') % { 'count': edit_count}) + f.paragraph(0)
--- a/MoinMoin/action/edit.py Sun Jan 06 23:38:45 2008 +0100 +++ b/MoinMoin/action/edit.py Sun Jan 06 23:39:24 2008 +0100 @@ -158,6 +158,9 @@ # Save new text else: try: + from MoinMoin.security.textcha import TextCha + if not TextCha(request).check_answer_from_form(): + raise pg.SaveError(_('TextCha: Wrong answer! Go back and try again...', formatted=False)) savemsg = pg.saveText(savetext, rev, trivial=trivial, comment=comment) except pg.EditConflict, e: msg = e.message
--- a/MoinMoin/action/fullsearch.py Sun Jan 06 23:38:45 2008 +0100 +++ b/MoinMoin/action/fullsearch.py Sun Jan 06 23:39:24 2008 +0100 @@ -198,7 +198,12 @@ page = results.hits[0] if not page.attachment: # we did not find an attachment page = Page(request, page.page_name) - url = page.url(request, querystr={'highlight': query.highlight_re()}, relative=False) + highlight = query.highlight_re() + if highlight: + querydict = {'highlight': highlight} + else: + querydict = {} + url = page.url(request, querystr=querydict, relative=False) request.http_redirect(url) return elif not results.hits: # no hits?
--- a/MoinMoin/action/newaccount.py Sun Jan 06 23:38:45 2008 +0100 +++ b/MoinMoin/action/newaccount.py Sun Jan 06 23:39:24 2008 +0100 @@ -10,6 +10,7 @@ from MoinMoin.Page import Page from MoinMoin.widget import html import MoinMoin.events as events +from MoinMoin.security.textcha import TextCha _debug = False @@ -20,6 +21,10 @@ if request.request_method != 'POST': return _("Use UserPreferences to change your settings or create an account.") + + if not TextCha(request).check_answer_from_form(): + return _('TextCha: Wrong answer! Go back and try again...', formatted=False) + # Create user profile theuser = user.User(request, auth_method="new-user") @@ -43,18 +48,18 @@ password = form.get('password', [''])[0] password2 = form.get('password2', [''])[0] + # Check if password is given and matches with password repeat + if password != password2: + return _("Passwords don't match!") + if not password: + return _("Please specify a password!") + pw_checker = request.cfg.password_checker if pw_checker: pw_error = pw_checker(theuser.name, password) if pw_error: return _("Password not acceptable: %s") % pw_error - # Check if password is given and matches with password repeat - if password != password2: - return _("Passwords don't match!") - if not password: - return _("Please specify a password!") - # Encode password if password and not password.startswith('{SHA}'): try: @@ -127,6 +132,16 @@ row = html.TR() tbl.append(row) + row.append(html.TD().append(html.STRONG().append( + html.Text(_('TextCha (required)', formatted=False))))) + td = html.TD() + textcha = TextCha(request).render() + if textcha: + td.append(textcha) + row.append(td) + + row = html.TR() + tbl.append(row) row.append(html.TD()) td = html.TD() row.append(td)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/MoinMoin/action/userprofile.py Sun Jan 06 23:39:24 2008 +0100 @@ -0,0 +1,33 @@ +# -*- coding: iso-8859-1 -*- +""" + MoinMoin - set values in user profile + + @copyright: 2008 MoinMoin:ThomasWaldmann + @license: GNU GPL, see COPYING for details. +""" +from MoinMoin.Page import Page +from MoinMoin import user + +def execute(pagename, request): + """ set values in user profile """ + _ = request.getText + cfg = request.cfg + form = request.form + + if not request.user.isSuperUser(): + request.theme.add_msg(_("Only superuser is allowed to use this action."), "error") + else: + user_name = form.get('name', [''])[0] + key = form.get('key', [''])[0] + val = form.get('val', [''])[0] + if key in cfg.user_checkbox_fields: + val = int(val) + uid = user.getUserId(request, user_name) + theuser = user.User(request, uid) + oldval = getattr(theuser, key) + setattr(theuser, key, val) + theuser.save() + request.theme.add_msg('%s.%s: %s -> %s' % (user_name, key, oldval, val), "info") + + Page(request, pagename).send_page() +
--- a/MoinMoin/auth/ldap_login.py Sun Jan 06 23:38:45 2008 +0100 +++ b/MoinMoin/auth/ldap_login.py Sun Jan 06 23:39:24 2008 +0100 @@ -5,11 +5,18 @@ This code only creates a user object, the session has to be established by the auth.moin_session auth plugin. + python-ldap needs to be at least 2.0.0pre06 (available since mid 2002) for + ldaps support - some older debian installations (woody and older?) require + libldap2-tls and python2.x-ldap-tls, otherwise you get ldap.SERVER_DOWN: + "Can't contact LDAP server" - more recent debian installations have tls + support in libldap2 (see dependency on gnutls) and also in python-ldap. + TODO: migrate configuration items to constructor parameters, allow more configuration (alias name, ...) by using callables as parameters - @copyright: 2006 MoinMoin:ThomasWaldmann, Nick Phillips + @copyright: 2006-2007 MoinMoin:ThomasWaldmann, + 2006 Nick Phillips @license: GNU GPL, see COPYING for details. """ import sys @@ -18,11 +25,11 @@ from MoinMoin import user from MoinMoin.auth import BaseAuth, CancelLogin, ContinueLogin + class LDAPAuth(BaseAuth): - """ get authentication data from form, authenticate against LDAP (or Active Directory), - fetch some user infos from LDAP and create a user profile for that user that must - be used by subsequent auth plugins (like moin_cookie) as we never return a user - object from ldap_login. + """ get authentication data from form, authenticate against LDAP (or Active + Directory), fetch some user infos from LDAP and create a user object + for that user. The session is kept by the moin_session auth plugin. """ login_inputs = ['username', 'password'] @@ -48,23 +55,38 @@ dn = None coding = cfg.ldap_coding if verbose: request.log("LDAP: Setting misc. options...") - # needed for Active Directory: - ldap.set_option(ldap.OPT_REFERRALS, 0) + ldap.set_option(ldap.OPT_PROTOCOL_VERSION, ldap.VERSION3) # ldap v2 is outdated + ldap.set_option(ldap.OPT_REFERRALS, cfg.ldap_referrals) + ldap.set_option(ldap.OPT_NETWORK_TIMEOUT, cfg.ldap_timeout) + + starttls = cfg.ldap_start_tls + if ldap.TLS_AVAIL: + for option, value in ( + (ldap.OPT_X_TLS_CACERTDIR, cfg.ldap_tls_cacertdir), + (ldap.OPT_X_TLS_CACERTFILE, cfg.ldap_tls_cacertfile), + (ldap.OPT_X_TLS_CERTFILE, cfg.ldap_tls_certfile), + (ldap.OPT_X_TLS_KEYFILE, cfg.ldap_tls_keyfile), + (ldap.OPT_X_TLS_REQUIRE_CERT, cfg.ldap_tls_require_cert), + (ldap.OPT_X_TLS, starttls), + #(ldap.OPT_X_TLS_ALLOW, 1), + ): + if value: + ldap.set_option(option, value) server = cfg.ldap_uri - if server.startswith('ldaps:'): - # TODO: refactor into LDAPAuth() constructor arguments! - # this is needed for self-signed ssl certs: - ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_NEVER) - # more stuff to try: - #ldap.set_option(ldap.OPT_X_TLS_ALLOW, 1) - #ldap.set_option(ldap.OPT_X_TLS_CERTFILE, LDAP_CACERTFILE) - #ldap.set_option(ldap.OPT_X_TLS_CACERTFILE,'/etc/httpd/ssl.crt/myCA-cacerts.pem') - if verbose: request.log("LDAP: Trying to initialize %r." % server) l = ldap.initialize(server) if verbose: request.log("LDAP: Connected to LDAP server %r." % server) + if starttls and server.startswith('ldap:'): + if verbose: request.log("LDAP: Trying to start TLS to %r." % server) + try: + l.start_tls_s() + if verbose: request.log("LDAP: Using TLS to %r." % server) + except (ldap.SERVER_DOWN, ldap.CONNECT_ERROR), err: + if verbose: request.log("LDAP: Couldn't establish TLS to %r (err: %s)." % (server, str(err))) + raise + # you can use %(username)s and %(password)s here to get the stuff entered in the form: ldap_binddn = cfg.ldap_binddn % locals() ldap_bindpw = cfg.ldap_bindpw % locals() @@ -74,12 +96,14 @@ # you can use %(username)s here to get the stuff entered in the form: filterstr = cfg.ldap_filter % locals() if verbose: request.log("LDAP: Searching %r" % filterstr) + attrs = [getattr(cfg, attr) for attr in [ + 'ldap_email_attribute', + 'ldap_aliasname_attribute', + 'ldap_surname_attribute', + 'ldap_givenname_attribute', + ] if getattr(cfg, attr) is not None] lusers = l.search_st(cfg.ldap_base, cfg.ldap_scope, filterstr.encode(coding), - attrlist=[cfg.ldap_email_attribute, - cfg.ldap_aliasname_attribute, - cfg.ldap_surname_attribute, - cfg.ldap_givenname_attribute, - ], timeout=cfg.ldap_timeout) + attrlist=attrs, timeout=cfg.ldap_timeout) # we remove entries with dn == None to get the real result list: lusers = [(dn, ldap_dict) for dn, ldap_dict in lusers if dn is not None] if verbose: @@ -101,8 +125,11 @@ l.simple_bind_s(dn, password.encode(coding)) if verbose: request.log("LDAP: Bound with dn %r (username: %r)" % (dn, username)) - if getattr(cfg, "ldap_email_callback", None) is None: - email = ldap_dict.get(cfg.ldap_email_attribute, [''])[0].decode(coding) + if cfg.ldap_email_callback is None: + if cfg.ldap_email_attribute: + email = ldap_dict.get(cfg.ldap_email_attribute, [''])[0].decode(coding) + else: + email = None else: email = cfg.ldap_email_callback(ldap_dict) @@ -110,6 +137,8 @@ try: aliasname = ldap_dict[cfg.ldap_aliasname_attribute][0] except (KeyError, IndexError): + pass + if not aliasname: sn = ldap_dict.get(cfg.ldap_surname_attribute, [''])[0] gn = ldap_dict.get(cfg.ldap_givenname_attribute, [''])[0] if sn and gn: @@ -118,10 +147,13 @@ aliasname = sn aliasname = aliasname.decode(coding) - u = user.User(request, auth_username=username, password="{SHA}NotStored", auth_method=self.name, auth_attribs=('name', 'password', 'email', 'mailto_author', )) + if email: + u = user.User(request, auth_username=username, password="{SHA}NotStored", auth_method=self.name, auth_attribs=('name', 'password', 'email', 'mailto_author', )) + u.email = email + else: + u = user.User(request, auth_username=username, password="{SHA}NotStored", auth_method=self.name, auth_attribs=('name', 'password', 'mailto_author', )) u.name = username u.aliasname = aliasname - u.email = email u.remember_me = 0 # 0 enforces cookie_lifetime config param if verbose: request.log("LDAP: creating userprefs with name %r email %r alias %r" % (username, email, aliasname))
--- a/MoinMoin/config/multiconfig.py Sun Jan 06 23:38:45 2008 +0100 +++ b/MoinMoin/config/multiconfig.py Sun Jan 06 23:39:24 2008 +0100 @@ -11,6 +11,7 @@ import os import sys import time +import logging from MoinMoin import config, error, util, wikiutil import MoinMoin.auth as authmodule @@ -329,16 +330,65 @@ language_ignore_browser = False # ignore browser settings, use language_default # or user prefs + # ldap / active directory server URI + # use ldaps://server:636 url for ldaps, + # use ldap://server for ldap without tls (and set ldap_start_tls to 0), + # use ldap://server for ldap with tls (and set ldap_start_tls to 1 or 2). + ldap_uri = 'ldap://localhost' + + # We can either use some fixed user and password for binding to LDAP. + # Be careful if you need a % char in those strings - as they are used as + # a format string, you have to write %% to get a single % in the end. + #ldap_binddn = 'binduser@example.org' # (AD) + #ldap_binddn = 'cn=admin,dc=example,dc=org' # (OpenLDAP) + #ldap_bindpw = 'secret' + # or we can use the username and password we got from the user: + #ldap_binddn = '%(username)s@example.org' # DN we use for first bind (AD) + #ldap_bindpw = '%(password)s' # password we use for first bind + # or we can bind anonymously (if that is supported by your directory). + # In any case, ldap_binddn and ldap_bindpw must be defined. + ldap_binddn = '' + ldap_bindpw = '' + + # base DN we use for searching + #ldap_base = 'ou=SOMEUNIT,dc=example,dc=org' + ldap_base = '' + + # scope of the search we do (2 == ldap.SCOPE_SUBTREE) + ldap_scope = 2 # we do not want to import ldap for everybody just for that + + # LDAP REFERRALS + ldap_referrals = 0 # (0 needed for AD) + + # ldap filter used for searching: + #ldap_filter = '(sAMAccountName=%(username)s)' # (AD) + ldap_filter = '(uid=%(username)s)' # (OpenLDAP) + # you can also do more complex filtering like: + # "(&(cn=%(username)s)(memberOf=CN=WikiUsers,OU=Groups,DC=example,DC=org))" + + # some attribute names we use to extract information from LDAP: + ldap_givenname_attribute = None # ('givenName') ldap attribute we get the first name from + ldap_surname_attribute = None # ('sn') ldap attribute we get the family name from + ldap_aliasname_attribute = None # ('displayName') ldap attribute we get the aliasname from + ldap_email_attribute = None # ('mail') ldap attribute we get the email address from + ldap_email_callback = None # called to make up email address + + ldap_coding = 'utf-8' # coding used for ldap queries and result values + ldap_timeout = 10 # how long we wait for the ldap server [s] + ldap_verbose = True # if True, put lots of LDAP debug info into the log + + # TLS / SSL related defaults + ldap_start_tls = 0 # 0 = No, 1 = Try, 2 = Required + ldap_tls_cacertdir = '' + ldap_tls_cacertfile = '' + ldap_tls_certfile = '' + ldap_tls_keyfile = '' + ldap_tls_require_cert = 0 # 0 == ldap.OPT_X_TLS_NEVER (needed for self-signed certs) + log_reverse_dns_lookups = True # if we do reverse dns lookups for logging hostnames # instead of just IPs log_timing = False # update <data_dir>/timing.log? - xapian_search = False - xapian_index_dir = None - xapian_stemming = True - xapian_index_history = False - search_results_per_page = 10 - mail_login = None # or "user pwd" if you need to use SMTP AUTH mail_sendmail = None # "/usr/sbin/sendmail -t -i" to not use SMTP, but sendmail mail_smarthost = None @@ -531,6 +581,9 @@ } surge_lockout_time = 3600 # secs you get locked out when you ignore warnings + textchas = None + textchas_disabled_group = None # e.g. u'NoTextChasGroup' if you are a member of this group, you don't get textchas + theme_default = 'modern' theme_force = False @@ -546,9 +599,10 @@ # a regex of HTTP_USER_AGENTS that should be excluded from logging # and receive a FORBIDDEN for anything except viewing a page - ua_spiders = ('archiver|cfetch|crawler|curl|gigabot|googlebot|holmes|htdig|httrack|httpunit|jeeves|larbin|leech|' - 'linkbot|linkmap|linkwalk|mercator|mirror|msnbot|msrbot|neomo|nutbot|omniexplorer|puf|robot|scooter|seekbot|' - 'sherlock|slurp|sitecheck|spider|teleport|voyager|webreaper|wget') + ua_spiders = ('archiver|cfetch|charlotte|crawler|curl|gigabot|googlebot|heritrix|holmes|htdig|httrack|httpunit|' + 'intelix|java|jeeves|larbin|leech|libwww-perl|linkbot|linkmap|linkwalk|litefinder|mercator|' + 'microsoft.url.control|mirror| mj12bot|msnbot|msrbot|neomo|nutbot|omniexplorer|puf|robot|scooter|seekbot|' + 'sherlock|slurp|sitecheck|snoopy|spider|teleport|twiceler|voilabot|voyager|webreaper|wget|yeti') # Wiki identity sitename = u'Untitled Wiki' @@ -659,6 +713,12 @@ unzip_attachments_space = 200.0 * 1000 ** 2 unzip_attachments_count = 101 # 1 zip file + 100 files contained in it + xapian_search = False + xapian_index_dir = None + xapian_stemming = True + xapian_index_history = False + search_results_per_page = 10 + SecurityPolicy = None def __init__(self, siteid): @@ -734,6 +794,14 @@ self.navi_bar = [elem % self for elem in self.navi_bar] self.backup_exclude = [elem % self for elem in self.backup_exclude] + # check if python-xapian is installed + if self.xapian_search: + try: + import xapian + except ImportError, err: + self.xapian_search = False + logging.error("xapian_search was auto-disabled because python-xapian is not installed [%s]." % str(err)) + # list to cache xapian searcher objects self.xapian_searchers = []
--- a/MoinMoin/formatter/text_docbook.py Sun Jan 06 23:38:45 2008 +0100 +++ b/MoinMoin/formatter/text_docbook.py Sun Jan 06 23:39:24 2008 +0100 @@ -396,12 +396,12 @@ ### Attachments ###################################################### def attachment_link(self, on, url=None, **kw): - _ = self.request.getText - pagename, filename = AttachFile.absoluteName(url, self.page.page_name) - fname = wikiutil.taintfilename(filename) - target = AttachFile.getAttachUrl(pagename, filename, self.request) + assert on in (0, 1, False, True) # make sure we get called the new way, not like the 1.5 api was # we do not output a "upload link" when outputting docbook if on: + pagename, filename = AttachFile.absoluteName(url, self.page.page_name) + fname = wikiutil.taintfilename(filename) + target = AttachFile.getAttachUrl(pagename, filename, self.request) return self.url(1, target, title="attachment:%s" % url) else: return self.url(0)
--- a/MoinMoin/i18n/Makefile Sun Jan 06 23:38:45 2008 +0100 +++ b/MoinMoin/i18n/Makefile Sun Jan 06 23:39:24 2008 +0100 @@ -14,7 +14,6 @@ @lang=`echo $@ | sed -e 's/\.MoinMoin\.po-update$$//'`; \ echo "$$lang:"; \ tools/wiki2po.py $${lang}; \ - tools/markup15to16.py $${lang}; \ echo "msgmerge $$lang.$(DOMAIN).po $(DOMAIN).pot -o $$lang.$(DOMAIN).new.po"; \ if msgmerge $$lang.$(DOMAIN).po $(DOMAIN).pot -o $$lang.$(DOMAIN).new.po; then \ if cmp $$lang.$(DOMAIN).po $$lang.$(DOMAIN).new.po >/dev/null 2>&1; then \
--- a/MoinMoin/i18n/msgfmt.py Sun Jan 06 23:38:45 2008 +0100 +++ b/MoinMoin/i18n/msgfmt.py Sun Jan 06 23:39:24 2008 +0100 @@ -23,13 +23,13 @@ Display version information and exit. Written by Martin v. Löwis <loewis@informatik.hu-berlin.de>, -refactored by Thomas Waldmann <tw AT waldmann-edv DOT de>. +refactored / fixed by Thomas Waldmann <tw AT waldmann-edv DOT de>. """ import sys, os import getopt, struct, array -__version__ = "1.2" +__version__ = "1.3" class SyntaxErrorException(Exception): """raised when having trouble parsing the po file content""" @@ -80,6 +80,7 @@ if line.startswith('msgid'): if section == STR: self.add(msgid, msgstr, fuzzy) + fuzzy = False section = ID line = line[5:] msgid = msgstr = ''
--- a/MoinMoin/macro/MonthCalendar.py Sun Jan 06 23:38:45 2008 +0100 +++ b/MoinMoin/macro/MonthCalendar.py Sun Jan 06 23:39:24 2008 +0100 @@ -248,6 +248,13 @@ else: year, month = yearmonthplusoffset(parmyear, parmmonth, parmoffset) + if request.isSpiderAgent and abs(currentyear - year) > 1: + return '' # this is a bot and it didn't follow the rules (see below) + if currentyear == year: + attrs = {} + else: + attrs = {'rel': 'nofollow' } # otherwise even well-behaved bots will index forever + # get the calendar monthcal = calendar.monthcalendar(year, month) @@ -270,10 +277,11 @@ nextlink = p.url(request, querystr % (qpagenames, parmoffset2 + 1, qtemplate), relative=False) prevylink = p.url(request, querystr % (qpagenames, parmoffset2 - 12, qtemplate), relative=False) nextylink = p.url(request, querystr % (qpagenames, parmoffset2 + 12, qtemplate), relative=False) - prevmonth = formatter.url(1, prevlink, 'cal-link') + '<' + formatter.url(0) - nextmonth = formatter.url(1, nextlink, 'cal-link') + '>' + formatter.url(0) - prevyear = formatter.url(1, prevylink, 'cal-link') + '<<' + formatter.url(0) - nextyear = formatter.url(1, nextylink, 'cal-link') + '>>' + formatter.url(0) + + prevmonth = formatter.url(1, prevlink, 'cal-link', **attrs) + '<' + formatter.url(0) + nextmonth = formatter.url(1, nextlink, 'cal-link', **attrs) + '>' + formatter.url(0) + prevyear = formatter.url(1, prevylink, 'cal-link', **attrs) + '<<' + formatter.url(0) + nextyear = formatter.url(1, nextylink, 'cal-link', **attrs) + '>>' + formatter.url(0) if parmpagename != [thispage]: pagelinks = '' @@ -289,6 +297,7 @@ ch = parmpagename[0][st:st+chstep] r, g, b = cliprgb(r, g, b) link = Page(request, parmpagename[0]).link_to(request, ch, + rel='nofollow', style='background-color:#%02x%02x%02x;color:#000000;text-decoration:none' % (r, g, b)) pagelinks = pagelinks + link r, g, b = (r, g+colorstep, b) @@ -296,6 +305,7 @@ r, g, b = (255-colorstep, 255, 255-colorstep) for page in parmpagename[1:]: link = Page(request, page).link_to(request, page, + rel='nofollow', style='background-color:#%02x%02x%02x;color:#000000;text-decoration:none' % (r, g, b)) pagelinks = pagelinks + '*' + link showpagename = ' %s<BR>\n' % pagelinks @@ -359,8 +369,8 @@ tiptitle = link tiptext = '<br>'.join(titletext) maketip_js.append("maketip('%s','%s','%s');" % (tipname, tiptitle, tiptext)) - onmouse = {'onMouseOver': "tip('%s')" % tipname, - 'onMouseOut': "untip()"} + attrs = {'onMouseOver': "tip('%s')" % tipname, + 'onMouseOut': "untip()"} else: csslink = "cal-emptyday" if parmtemplate: @@ -370,7 +380,7 @@ r, g, b, u = (255, 255, 255, 0) if wkday in wkend: csslink = "cal-weekend" - onmouse = {} + attrs = {'rel': 'nofollow'} for otherpage in parmpagename[1:]: otherlink = "%s/%4d-%02d-%02d" % (otherpage, year, month, day) otherdaypage = Page(request, otherlink) @@ -382,7 +392,7 @@ r, g, b = (r, g+colorstep, b) r, g, b = cliprgb(r, g, b) style = 'background-color:#%02x%02x%02x' % (r, g, b) - fmtlink = formatter.url(1, daypage.url(request, query, relative=False), csslink, **onmouse) + str(day) + formatter.url(0) + fmtlink = formatter.url(1, daypage.url(request, query, relative=False), csslink, **attrs) + str(day) + formatter.url(0) if day == currentday and month == currentmonth and year == currentyear: cssday = "cal-today" fmtlink = "<b>%s</b>" % fmtlink # for browser with CSS probs
--- a/MoinMoin/request/__init__.py Sun Jan 06 23:38:45 2008 +0100 +++ b/MoinMoin/request/__init__.py Sun Jan 06 23:39:24 2008 +0100 @@ -3,12 +3,45 @@ MoinMoin - RequestBase Implementation @copyright: 2001-2003 Juergen Hermann <jh@web.de>, - 2003-2006 MoinMoin:ThomasWaldmann + 2003-2008 MoinMoin:ThomasWaldmann @license: GNU GPL, see COPYING for details. """ +# Support for remote IP address detection when using (reverse) proxy (or even proxies). +# If you exactly KNOW which (reverse) proxies you can trust, put them into the list +# below, so we can determine the "outside" IP as your trusted proxies see it. + +proxies_trusted = [] # trust noone! +#proxies_trusted = ['127.0.0.1', ] # can be a list of multiple IPs + +import logging +proxy_loglevel = logging.DEBUG # logging.NOTSET (never), logging.INFO (when not debugging) + +def find_remote_addr(addrs): + """ Find the last remote IP address before it hits our reverse proxies. + The LAST address in the <addrs> list is the remote IP as detected by the server + (not taken from some x-forwarded-for header). + The FIRST address in the <addrs> list might be the client's IP - if noone cheats + and everyone supports x-f-f header. + + See http://bob.pythonmac.org/archives/2005/09/23/apache-x-forwarded-for-caveat/ + + For debug loglevel, we log all <addrs>. + + TODO: refactor request code to first do some basic IP init, then load configuration, + TODO: then do proxy processing. + TODO: add wikiconfig configurability for proxies_trusted + TODO: later, make it possible to put multipe remote IP addrs into edit-log + """ + logging.log(proxy_loglevel, "request.find_remote_addr: addrs == %r" % addrs) + if proxies_trusted: + result = [addr for addr in addrs if addr not in proxies_trusted] + if result: + return result[-1] # last IP before it hit our trusted (reverse) proxies + return addrs[-1] # this is a safe remote_addr, not taken from x-f-f header + + import os, re, time, sys, cgi, StringIO -import logging import Cookie import traceback @@ -72,7 +105,8 @@ # Extra headers we support. Both standalone and twisted store # headers as lowercase. moin_location = 'x-moin-location' - proxy_host = 'x-forwarded-host' + proxy_host = 'x-forwarded-host' # original host: header as seen by the proxy (e.g. wiki.example.org) + proxy_xff = 'x-forwarded-for' # list of original remote_addrs as seen by the proxies (e.g. <clientip>,<proxy1>,<proxy2>,...) def __init__(self, properties={}): @@ -375,6 +409,7 @@ self.setIsSSL(env) self.setHost(env.get('HTTP_HOST')) self.fixURI(env) + self.setURL(env) #self.debugEnvironment(env) @@ -460,10 +495,10 @@ @param env: dict like object containing cgi meta variables or http headers. """ - # If we serve on localhost:8000 and use a proxy on - # example.com/wiki, our urls will be example.com/wiki/pagename - # Same for the wiki config - they must use the proxy url. + # proxy support + self.rewriteRemoteAddr(env) self.rewriteHost(env) + self.rewriteURI(env) if not self.request_uri: @@ -489,6 +524,27 @@ if proxy_host: self.http_host = proxy_host + def rewriteRemoteAddr(self, env): + """ Rewrite remote_addr transparently + + Get the proxy remote addr using 'X-Forwarded-For' header, added by + Apache 2 and other proxy software. + + TODO: Will not work for Apache 1 or others that don't add this header. + + TODO: If we want to add an option to disable this feature it + should be in the server script, because the config is not + loaded at this point, and must be loaded after url is set. + + @param env: dict like object containing cgi meta variables or http headers. + """ + xff = (env.get(self.proxy_xff) or + env.get(cgiMetaVariable(self.proxy_xff))) + if xff: + xff = [addr.strip() for addr in xff.split(',')] + xff.append(self.remote_addr) + self.remote_addr = find_remote_addr(xff) + def rewriteURI(self, env): """ Rewrite request_uri, script_name and path_info transparently @@ -805,15 +861,13 @@ indicator += '!1!' total = self.clock.value('total') # use + for existing pages, - for non-existing pages - indicator += self.page.exists() and '+' or '-' + if self.page is not None: + indicator += self.page.exists() and '+' or '-' if self.isSpiderAgent: indicator += "B" - # Add time stamp and process ID pid = os.getpid() - t = time.time() - timestr = time.strftime("%Y%m%d %H%M%S", time.gmtime(t)) - msg = '%s %5d %-6s %4s %-10s %s\n' % (timestr, pid, total, indicator, action, self.url) + msg = 'Timing %5d %-6s %4s %-10s %s\n' % (pid, total, indicator, action, self.url) self.log(msg) def write(self, *data): @@ -1106,19 +1160,21 @@ self.initTheme() action_name = self.action + if self.cfg.log_timing: + self.timing_log(True, action_name) + if action_name == 'xmlrpc': from MoinMoin import xmlrpc if self.query_string == 'action=xmlrpc': xmlrpc.xmlrpc(self) elif self.query_string == 'action=xmlrpc2': xmlrpc.xmlrpc2(self) + if self.cfg.log_timing: + self.timing_log(False, action_name) return self.finish() # parse request data try: - if self.cfg.log_timing: - self.timing_log(True, action) - # The last component in path_info is the page name, if any path = self.getPathinfo() @@ -1130,7 +1186,7 @@ if path.startswith(prefix): # remove prefix and action name path = path[len(prefix):] - action, path = path.split('/', 1) + action, path = (path.split('/', 1) + ['', ''])[:2] path = '/' + path if path.startswith('/'): @@ -1214,12 +1270,14 @@ except MoinMoinFinish: pass + except SystemExit: + raise # fcgi uses this to terminate a thread except Exception, err: self.fail(err) self.finish() if self.cfg.log_timing: - self.timing_log(False, action) + self.timing_log(False, action_name) return self.finish()
--- a/MoinMoin/request/request_modpython.py Sun Jan 06 23:38:45 2008 +0100 +++ b/MoinMoin/request/request_modpython.py Sun Jan 06 23:39:24 2008 +0100 @@ -12,7 +12,7 @@ class Request(RequestBase): """ specialized on mod_python requests """ - def __init__(self, req): + def __init__(self, req, properties={}): """ Saves mod_pythons request and sets basic variables using the req.subprocess_env, cause this provides a standard way to access the values we need here. @@ -33,7 +33,7 @@ else: env = req.subprocess_env self._setup_vars_from_std_env(env) - RequestBase.__init__(self) + RequestBase.__init__(self, properties) except Exception, err: self.fail(err) @@ -84,7 +84,9 @@ form = util.FieldStorage(self.mpyreq) args = {} - for key in form: + + # You cannot get rid of .keys() here + for key in form.keys(): if key is None: continue values = form[key]
--- a/MoinMoin/script/migration/_conv160.py Sun Jan 06 23:38:45 2008 +0100 +++ b/MoinMoin/script/migration/_conv160.py Sun Jan 06 23:39:24 2008 +0100 @@ -53,8 +53,15 @@ """ Convert the <text> content of page <pagename>, using <renames> dict to rename links correctly. Additionally, convert some changed markup. """ - if "#format wiki" not in text and "#format" in text: - return text # this is not a wiki page, leave it as is + if text.startswith('<?xml'): + # would be done with xslt processor + return text + + pis, body = wikiutil.get_processing_instructions(text) + for pi, val in pis: + if pi == 'format' and val != 'wiki': + # not wiki page + return text text = convert_wiki(request, pagename, text, renames) return text @@ -141,6 +148,7 @@ editlog = self.data.items() editlog.sort() f = file(fname, "w") + max_rev = 0 for key, fields in editlog: timestamp, rev, action, pagename, ip, hostname, userid, extra, comment = fields if action.startswith('ATT'): @@ -154,6 +162,8 @@ if ('PAGE', pagename) in self.renames: pagename = self.renames[('PAGE', pagename)] timestamp = str(timestamp) + if rev != 99999999: + max_rev = max(rev, max_rev) revstr = '%08d' % rev pagename = wikiutil.quoteWikinameFS(pagename) fields = timestamp, revstr, action, pagename, ip, hostname, userid, extra, comment @@ -161,7 +171,7 @@ f.write(log_str) if create_rev and not deleted: timestamp = str(wikiutil.timestamp2version(time.time())) - revstr = '%08d' % (rev + 1) + revstr = '%08d' % (max_rev + 1) action = 'SAVE' ip = '127.0.0.1' hostname = 'localhost' @@ -249,7 +259,11 @@ current_file = file(current_fname, "r") current_rev = current_file.read() current_file.close() - self.current = int(current_rev) + try: + self.current = int(current_rev) + except ValueError: + print "Error: invalid current file %s, SKIPPING THIS PAGE!" % current_fname + return # read edit-log editlog_fname = opj(page_dir, 'edit-log') if os.path.exists(editlog_fname): @@ -353,7 +367,11 @@ line = line.replace(u'\r', '').replace(u'\n', '') if not line.strip() or line.startswith(u'#'): # skip empty or comment lines continue - key, value = line.split(u'=', 1) + try: + key, value = line.split(u'=', 1) + except Exception, err: + print "Error: User reader can not parse line %r from profile %r (%s)" % (line, fname, str(err)) + continue self.profile[key] = value f.close() # read bookmarks
--- a/MoinMoin/script/migration/_conv160_wiki.py Sun Jan 06 23:38:45 2008 +0100 +++ b/MoinMoin/script/migration/_conv160_wiki.py Sun Jan 06 23:39:24 2008 +0100 @@ -162,7 +162,13 @@ macro_args = m.group('macro_args') if macro_name == 'ImageLink': fixed, kw, trailing = wikiutil.parse_quoted_separated(macro_args) + #print "macro_args=%r" % macro_args + #print "fixed=%r, kw=%r, trailing=%r" % (fixed, kw, trailing) image, target = (fixed + ['', ''])[:2] + if image is None: + image = '' + if target is None: + target = '' if '://' not in image: # if it is not a URL, it is meant as attachment image = u'attachment:%s' % image
--- a/MoinMoin/script/migration/migutil.py Sun Jan 06 23:38:45 2008 +0100 +++ b/MoinMoin/script/migration/migutil.py Sun Jan 06 23:39:24 2008 +0100 @@ -2,7 +2,7 @@ """ MoinMoin - utility functions used by the migration scripts - @copyright: 2005 MoinMoin:ThomasWaldmann + @copyright: 2005,2007 MoinMoin:ThomasWaldmann @license: GNU GPL, see COPYING for details. """ import os, sys, shutil @@ -35,9 +35,9 @@ fatalError("can't find '%s'. You must run this script from the directory where '%s' is located." % src) try: - os.rename(src, dst) - except OSError: - fatalError("can't rename '%s' to '%s'" % (src, dst)) + shutil.move(src, dst) + except: + fatalError("can't move '%s' to '%s'" % (src, dst)) try: os.mkdir(src) @@ -66,18 +66,15 @@ print "%s/ -> %s/" % (dir_from, dir_to) try: shutil.copytree(dir_from, dir_to) - except: - error("can't copy '%s' to '%s'" % (dir_from, dir_to)) + except Exception, err: + error("can't copy '%s' to '%s' (%s)" % (dir_from, dir_to, str(err))) def copy_file(fname_from, fname_to): """ Copy a single file """ print "%s -> %s" % (fname_from, fname_to) try: - data = open(fname_from).read() - open(fname_to, "w").write(data) - st = os.stat(fname_from) - os.utime(fname_to, (st.st_atime, st.st_mtime)) + shutil.copy2(fname_from, fname_to) # copies file data, mode, atime, mtime except: error("can't copy '%s' to '%s'" % (fname_from, fname_to)) @@ -86,7 +83,7 @@ """ Move a single file """ print "%s -> %s" % (fname_from, fname_to) try: - os.rename(fname_from, fname_to) + shutil.move(fname_from, fname_to) # moves file (even to different filesystem, including mode and atime/mtime) except: error("can't move '%s' to '%s'" % (fname_from, fname_to))
--- a/MoinMoin/search/queryparser.py Sun Jan 06 23:38:45 2008 +0100 +++ b/MoinMoin/search/queryparser.py Sun Jan 06 23:39:24 2008 +0100 @@ -424,7 +424,8 @@ return u'%s!"%s"' % (neg, unicode(self._pattern)) def highlight_re(self): - return u"(%s)" % self._pattern + return u'' # do not highlight text with stuff from titlesearch, + # was: return u"(%s)" % self._pattern def pageFilter(self): """ Page filter function for single title search """
--- a/MoinMoin/search/results.py Sun Jan 06 23:38:45 2008 +0100 +++ b/MoinMoin/search/results.py Sun Jan 06 23:39:24 2008 +0100 @@ -674,10 +674,11 @@ return self.request.page.url(self.request, querydict, escape=0, relative=False) - pages = float(hitsNum) / hitsPerPage - if pages - int(pages) > 0.0: - pages = int(pages) + 1 - cur_page = hitsFrom / hitsPerPage + pages = hitsNum // hitsPerPage + remainder = hitsNum % hitsPerPage + if remainder: + pages += 1 + cur_page = hitsFrom // hitsPerPage textlinks = [] @@ -741,9 +742,9 @@ size_str = '%.1fk' % (p.size()/1024.0) revisions = p.getRevList() if len(revisions) and rev == revisions[0]: - rev_str = 'rev: %d (%s)' % (rev, _('current')) + rev_str = '%s: %d (%s)' % (_('rev'), rev, _('current')) else: - rev_str = 'rev: %d' % (rev, ) + rev_str = '%s: %d' % (_('rev'), rev, ) lastmod_str = _('last modified: %s') % p.mtime_printable(request) result = f.paragraph(1, attr={'class': 'searchhitinfobar'}) + \ @@ -759,7 +760,9 @@ if querydict is None: querydict = {} if 'action' not in querydict or querydict['action'] == 'AttachFile': - querydict.update({'highlight': self.query.highlight_re()}) + highlight = self.query.highlight_re() + if highlight: + querydict.update({'highlight': highlight}) querystr = wikiutil.makeQueryString(querydict) return querystr
--- a/MoinMoin/security/antispam.py Sun Jan 06 23:38:45 2008 +0100 +++ b/MoinMoin/security/antispam.py Sun Jan 06 23:39:24 2008 +0100 @@ -9,8 +9,15 @@ # give some log entries to stderr debug = 0 -import re, sys, time, datetime -import sets +import re, time, datetime + +# needed for py 2.3 compat: +try: + frozenset +except NameError: + from sets import ImmutableSet as frozenset + +import logging from MoinMoin.security import Permissions from MoinMoin import caching, wikiutil @@ -42,7 +49,7 @@ if debug: if isinstance(s, unicode): s = s.encode('utf-8') - sys.stderr.write('%s\n' % s) + logging.debug('antispam: %s' % s) def makelist(text): @@ -157,7 +164,8 @@ blacklist = [] latest_mtime = 0 for pn in BLACKLISTPAGES: - do_update = (pn != "LocalBadContent") + do_update = (pn != "LocalBadContent" and + request.cfg.interwikiname != 'MoinMaster') # MoinMaster wiki shall not fetch updates from itself blacklist_mtime, blacklist_entries = getblacklist(request, pn, do_update) blacklist += blacklist_entries latest_mtime = max(latest_mtime, blacklist_mtime) @@ -180,10 +188,10 @@ page = Page(request, editor.page_name, rev=rev) oldtext = page.get_raw_body() - newset = sets.ImmutableSet(newtext.splitlines(1)) - oldset = sets.ImmutableSet(oldtext.splitlines(1)) - difference = newset.difference(oldset) - addedtext = ''.join(difference) + newset = frozenset(newtext.splitlines(1)) + oldset = frozenset(oldtext.splitlines(1)) + difference = newset - oldset + addedtext = kw.get('comment', u'') + u''.join(difference) for blacklist_re in request.cfg.cache.antispam_blacklist[1]: match = blacklist_re.search(addedtext)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/MoinMoin/security/textcha.py Sun Jan 06 23:39:24 2008 +0100 @@ -0,0 +1,170 @@ +# -*- coding: iso-8859-1 -*- +""" + MoinMoin - Text CAPTCHAs + + This is just asking some (admin configured) questions and + checking if the answer is as expected. It is up to the wiki + admin to setup questions that a bot can not easily answer, but + humans can. It is recommended to setup SITE SPECIFIC questions + and not to share the questions with other sites (if everyone + asks the same questions / expects the same answers, spammers + could adapt to that). + + TODO: + * roundtrip the question in some other way: + * use safe encoding / encryption for the q + * make sure a q/a pair in the POST is for the q in the GET before + * make some nice CSS + * make similar changes to GUI editor + + @copyright: 2007 by MoinMoin:ThomasWaldmann + @license: GNU GPL, see COPYING for details. +""" + +import re +import random +import logging + +from MoinMoin import wikiutil + +class TextCha(object): + """ Text CAPTCHA support """ + + def __init__(self, request, question=None): + """ Initialize the TextCha. + + @param request: the request object + @param question: see _init_qa() + """ + self.request = request + self.user_info = request.user.valid and request.user.name or request.remote_addr + self.textchas = self._get_textchas() + self._init_qa(question) + + def _get_textchas(self): + """ get textchas from the wiki config for the user's language (or default_language or en) """ + request = self.request + cfg = request.cfg + user = request.user + disabled_group = cfg.textchas_disabled_group + if disabled_group and user.name and request.dicts.has_member(disabled_group, user.name): + return None + textchas = cfg.textchas + if textchas: + lang = user.language or request.lang + #logging.debug(u"TextCha: user.language == '%s'." % lang) + if lang not in textchas: + lang = cfg.language_default + #logging.debug(u"TextCha: fallback to language_default == '%s'." % lang) + if lang not in textchas: + logging.error(u"TextCha: The textchas do not have content for language_default == '%s'! Falling back to English." % lang) + lang = 'en' + if lang not in textchas: + logging.error(u"TextCha: The textchas do not have content for 'en', auto-disabling textchas!") + cfg.textchas = None + lang = None + else: + lang = None + if lang is None: + return None + else: + #logging.debug(u"TextCha: using lang = '%s'" % lang) + return textchas[lang] + + def _init_qa(self, question=None): + """ Initialize the question / answer. + + @param question: If given, the given question will be used. + If None, a new question will be generated. + """ + if self.is_enabled(): + if question is None: + self.question = random.choice(self.textchas.keys()) + else: + self.question = question + try: + self.answer_regex = self.textchas[self.question] + self.answer_re = re.compile(self.answer_regex, re.U|re.I) + except KeyError: + # this question does not exist, thus there is no answer + self.answer_regex = ur"[^.]*" # this shall never match! + self.answer_re = re.compile(self.answer_regex, re.U|re.I) + logging.warning(u"TextCha: Non-existing question '%s'. User '%s' trying to cheat?" % ( + self.question, self.user_info)) + except re.error: + logging.error(u"TextCha: Invalid regex in answer for question '%s'" % self.question) + self._init_qa() + + def is_enabled(self): + """ check if textchas are enabled. + + They can be disabled for all languages if you use textchas = None or = {}, + also they can be disabled for some specific language, like: + textchas = { + 'en': { + 'some question': 'some answer', + # ... + }, + 'de': {}, # having no questions for 'de' means disabling textchas for 'de' + # ... + } + """ + return not not self.textchas # we don't want to return the dict + + def check_answer(self, given_answer): + """ check if the given answer to the question is correct """ + if self.is_enabled(): + success = self.answer_re.match(given_answer.strip()) is not None + success_status = success and u"success" or u"failure" + logging.info(u"TextCha: %s (u='%s', a='%s', re='%s', q='%s')" % ( + success_status, + self.user_info, + given_answer, + self.answer_regex, + self.question, + )) + return success + else: + return True + + def _make_form_values(self, question, given_answer): + question_form = wikiutil.escape(question, True) + given_answer_form = wikiutil.escape(given_answer, True) + return question_form, given_answer_form + + def _extract_form_values(self, form=None): + if form is None: + form = self.request.form + question = form.get('textcha-question', [None])[0] + given_answer = form.get('textcha-answer', [u''])[0] + return question, given_answer + + def render(self, form=None): + """ Checks if textchas are enabled and returns HTML for one, + or an empty string if they are not enabled. + + @return: unicode result html + """ + if self.is_enabled(): + question, given_answer = self._extract_form_values(form) + if question is None: + question = self.question + question_form, given_answer_form = self._make_form_values(question, given_answer) + result = u""" +<div id="textcha"> +<span id="textcha-question">%s</span> +<input type="hidden" name="textcha-question" value="%s"> +<input id="textcha-answer" type="text" name="textcha-answer" value="%s" size="20" maxlength="80"> +</div> +""" % (wikiutil.escape(question), question_form, given_answer_form) + else: + result = u'' + return result + + def check_answer_from_form(self, form=None): + if self.is_enabled(): + question, given_answer = self._extract_form_values(form) + self._init_qa(question) + return self.check_answer(given_answer) + else: + return True
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/MoinMoin/server/server_modpython.py Sun Jan 06 23:39:24 2008 +0100 @@ -0,0 +1,49 @@ +# -*- coding: iso-8859-1 -*- +""" + MoinMoin.server.server_modpython + + This is not really a server, it is just so that modpython stuff + (the real server is likely Apache2) fits the model we have for + Twisted and standalone server. + + Minimal usage: + + from MoinMoin.server.server_modpython import CgiConfig, run + + class Config(CgiConfig): + pass + + run(Config) + + See more options in CgiConfig class. + + @copyright: 2006 MoinMoin:ThomasWaldmann + @license: GNU GPL, see COPYING for details. +""" + +from MoinMoin.server import Config +from MoinMoin.request import request_modpython + +# Set threads flag, so other code can use proper locking. +# TODO: It seems that modpy does not use threads, so we don't need to +# set it here. Do we have another method to check this? +from MoinMoin import config +config.use_threads = 1 +del config + +# Server globals +config = None + +class ModpythonConfig(Config): + """ Set up default server """ + + logPath = None + properties = {} + + # Set up log handler to log to apache log! + +def modpythonHandler(request, ConfigClass=ModpythonConfig): + config = ConfigClass() + moinreq = request_modpython.Request(request, config.properties) + return moinreq.run(request) +
--- a/MoinMoin/server/server_wsgi.py Sun Jan 06 23:38:45 2008 +0100 +++ b/MoinMoin/server/server_wsgi.py Sun Jan 06 23:39:24 2008 +0100 @@ -1,12 +1,31 @@ """ MoinMoin - WSGI application - @copyright: 2005 Anakim Border <akborder@gmail.com> + Minimal code for using this: + + import logging + from MoinMoin.server.server_wsgi import WsgiConfig, moinmoinApp + + class Config(WsgiConfig): + logPath = 'moin.log' # define your log file here + #loglevel_file = logging.INFO # if you do not like the default + + config = Config() # you MUST create an instance to initialize logging! + # use moinmoinApp here with your WSGI server / gateway + + @copyright: 2005 Anakim Border <akborder@gmail.com>, + 2007 MoinMoin:ThomasWaldmann @license: GNU GPL, see COPYING for details. """ +from MoinMoin.server import Config from MoinMoin.request import request_wsgi +class WsgiConfig(Config): + """ WSGI default config """ + loglevel_stderr = None # we do not want to write to stderr! + + def moinmoinApp(environ, start_response): request = request_wsgi.Request(environ) request.run()
--- a/MoinMoin/stats/hitcounts.py Sun Jan 06 23:38:45 2008 +0100 +++ b/MoinMoin/stats/hitcounts.py Sun Jan 06 23:39:24 2008 +0100 @@ -8,16 +8,22 @@ A lot of code here is duplicated in stats.useragents. Maybe both can use same base class, maybe some parts are useful to other code. - @copyright: 2002-2004 Juergen Hermann <jh@web.de> + @copyright: 2002-2004 Juergen Hermann <jh@web.de>, + 2007 MoinMoin:ThomasWaldmann @license: GNU GPL, see COPYING for details. """ _debug = 0 +import time + from MoinMoin import caching, wikiutil, logfile from MoinMoin.Page import Page from MoinMoin.logfile import eventlog +# this is a CONSTANT used for on-disk caching, it must NOT be configurable and +# not depend on request.user! +DATE_FMT = '%04d-%02d-%02d' # % (y, m, d) def linkto(pagename, request, params=''): _ = request.getText @@ -52,14 +58,14 @@ # Get results from cache if filterpage: arena = Page(request, pagename) - cache = caching.CacheEntry(request, arena, 'hitcounts', scope='item') + cache = caching.CacheEntry(request, arena, 'hitcounts', scope='item', use_pickle=True) else: arena = 'charts' - cache = caching.CacheEntry(request, arena, 'hitcounts', scope='wiki') + cache = caching.CacheEntry(request, arena, 'hitcounts', scope='wiki', use_pickle=True) if cache.exists(): try: - cache_date, cache_days, cache_views, cache_edits = eval(cache.content()) + cache_date, cache_days, cache_views, cache_edits = cache.content() except: cache.remove() # cache gone bad @@ -79,34 +85,34 @@ if new_date is not None: log.set_filter(['VIEWPAGE', 'SAVEPAGE']) for event in log.reverse(): - #print ">>>", wikiutil.escape(repr(event)), "<br>" - - if event[0] <= cache_date: + event_usecs = event[0] + if event_usecs <= cache_date: break eventpage = event[2].get('pagename', '') if filterpage and eventpage != filterpage: continue - time_tuple = request.user.getTime(wikiutil.version2timestamp(event[0])) + event_secs = wikiutil.version2timestamp(event_usecs) + time_tuple = time.gmtime(event_secs) # must be UTC day = tuple(time_tuple[0:3]) if day != ratchet_day: # new day while ratchet_time: - ratchet_time -= 86400 - rday = tuple(request.user.getTime(ratchet_time)[0:3]) + ratchet_time -= 86400 # seconds per day + rday = tuple(time.gmtime(ratchet_time)[0:3]) # must be UTC if rday <= day: break - days.append(request.user.getFormattedDate(ratchet_time)) + days.append(DATE_FMT % rday) views.append(0) edits.append(0) - days.append(request.user.getFormattedDate(wikiutil.version2timestamp(event[0]))) + days.append(DATE_FMT % day) views.append(0) edits.append(0) ratchet_day = day - ratchet_time = wikiutil.version2timestamp(event[0]) + ratchet_time = event_secs if event[1] == 'VIEWPAGE': - views[-1] = views[-1] + 1 + views[-1] += 1 elif event[1] == 'SAVEPAGE': - edits[-1] = edits[-1] + 1 + edits[-1] += 1 days.reverse() views.reverse() @@ -123,8 +129,7 @@ cache_views.extend(views) cache_edits.extend(edits) if new_date is not None: - cache.update("(%r, %r, %r, %r)" % - (new_date, cache_days, cache_views, cache_edits)) + cache.update((new_date, cache_days, cache_views, cache_edits)) return cache_days, cache_views, cache_edits
--- a/MoinMoin/stats/useragents.py Sun Jan 06 23:38:45 2008 +0100 +++ b/MoinMoin/stats/useragents.py Sun Jan 06 23:39:24 2008 +0100 @@ -7,7 +7,8 @@ TODO: should be refactored after hitcounts. - @copyright: 2002-2004 Juergen Hermann <jh@web.de> + @copyright: 2002-2004 Juergen Hermann <jh@web.de>, + 2007 MoinMoin:ThomasWaldmann @license: GNU GPL, see COPYING for details. """ @@ -45,11 +46,11 @@ def get_data(request): # get results from cache - cache = caching.CacheEntry(request, 'charts', 'useragents', scope='wiki') + cache = caching.CacheEntry(request, 'charts', 'useragents', scope='wiki', use_pickle=True) cache_date, data = 0, {} if cache.exists(): try: - cache_date, data = eval(cache.content()) + cache_date, data = cache.content() except: cache.remove() # cache gone bad @@ -75,7 +76,7 @@ data[ua] = data.get(ua, 0) + 1 # write results to cache - cache.update("(%r, %r)" % (new_date, data)) + cache.update((new_date, data)) data = [(cnt, ua) for ua, cnt in data.items()] data.sort()
--- a/MoinMoin/support/thfcgi.py Sun Jan 06 23:38:45 2008 +0100 +++ b/MoinMoin/support/thfcgi.py Sun Jan 06 23:39:24 2008 +0100 @@ -11,7 +11,9 @@ Cleanup, fixed typos, PEP-8, support for limiting creation of threads, limited number of requests lifetime, configurable backlog for socket - .listen() by Thomas Waldmann <tw AT waldmann-edv DOT de> + .listen() by MoinMoin:ThomasWaldmann. + + 2007 Support for Python's logging module by MoinMoin:ThomasWaldmann. For code base see: http://cvs.lysator.liu.se/viewcvs/viewcvs.cgi/webkom/thfcgi.py?cvsroot=webkom @@ -33,7 +35,8 @@ # TODO: Compare compare the number of bytes received on FCGI_STDIN with # CONTENT_LENGTH and abort the update if the two numbers are not equal. -debug = False +import logging +LOGLEVEL = logging.DEBUG # logging.NOTSET to completely switch it off import os import sys @@ -96,12 +99,9 @@ FCGI_UnknownTypeBody = "!B7x" FCGI_EndRequestBody = "!IB3x" -LOGFILE = sys.stderr def log(s): - if debug: - LOGFILE.write(s) - LOGFILE.write('\n') + logging.log(LOGLEVEL, 'thfcgi: %s' % s) class SocketErrorOnWrite: """Is raised if a write fails in the socket code.""" @@ -584,11 +584,13 @@ def run(self): """Wait & serve. Calls request_handler on every request.""" self.sock.listen(self.backlog) - log("Starting Process") + pid = os.getpid() + log("Starting Process (PID=%d)" % pid) running = True while running: if not self.requests_left: # self.sock.shutdown(RDWR) here does NOT help with backlog + log("Maximum number of processed requests reached, terminating this worker process (PID=%d)..." % pid) running = False elif self.requests_left > 0: self.requests_left -= 1 @@ -596,13 +598,12 @@ conn, addr = self.sock.accept() threadcount = _threading.activeCount() if threadcount < self.max_threads: - log("Accepted connection, starting thread...") + log("Accepted connection, %d active threads, starting worker thread..." % threadcount) t = _threading.Thread(target=self.accept_handler, args=(conn, addr, True)) t.start() else: - log("Accepted connection, running in main-thread...") + log("Accepted connection, %d active threads, running in main thread..." % threadcount) self.accept_handler(conn, addr, False) - log("Active Threads: %d" % _threading.activeCount()) self.sock.close() - log("Ending Process") + log("Ending Process (PID=%d)" % pid)
--- a/MoinMoin/theme/__init__.py Sun Jan 06 23:38:45 2008 +0100 +++ b/MoinMoin/theme/__init__.py Sun Jan 06 23:39:24 2008 +0100 @@ -223,7 +223,7 @@ """ _ = self.request.getText content = [] - if d['title_text'] == d['page_name']: # just showing a page, no action + if d['title_text'] == d['page'].split_title(): # just showing a page, no action curpage = '' segments = d['page_name'].split('/') # was: title_text for s in segments[:-1]: @@ -369,6 +369,8 @@ else: page = Page(request, pagename) + pagename = page.page_name # can be different, due to i18n + if not title: title = page.split_title() title = self.shortenPagename(title) @@ -539,16 +541,18 @@ @rtype: string @return: html link tag """ + qs = {} querystr, title, icon = self.cfg.page_icons_table[which] + qs.update(querystr) # do not modify the querystr dict in the cfg! d['title'] = title % d d['i18ntitle'] = self.request.getText(d['title'], formatted=False) img_src = self.make_icon(icon, d) rev = d['rev'] if rev and which in ['raw', 'print', ]: - querystr['rev'] = str(rev) + qs['rev'] = str(rev) attrs = {'rel': 'nofollow', 'title': d['i18ntitle'], } page = d['page'] - return page.link_to_raw(self.request, text=img_src, querystr=querystr, **attrs) + return page.link_to_raw(self.request, text=img_src, querystr=qs, **attrs) def msg(self, d): """ Assemble the msg display @@ -1113,8 +1117,12 @@ """ Return whether the gui editor / converter can work for that page. The GUI editor currently only works for wiki format. + For simplicity, we also tell it does not work if the admin forces the text editor. """ - return page.pi['format'] == 'wiki' + is_wiki = page.pi['format'] == 'wiki' + gui_disallowed = self.cfg.editor_force and self.cfg.editor_default == 'text' + return is_wiki and not gui_disallowed + def editorLink(self, page): """ Return a link to the editor
--- a/MoinMoin/userform/admin.py Sun Jan 06 23:38:45 2008 +0100 +++ b/MoinMoin/userform/admin.py Sun Jan 06 23:39:24 2008 +0100 @@ -7,7 +7,7 @@ 2007 MoinMoin:ReimarBauer @license: GNU GPL, see COPYING for details. """ -from MoinMoin import user, wikidicts +from MoinMoin import user, wikidicts, wikiutil from MoinMoin.util.dataset import TupleDataset, Column from MoinMoin.Page import Page @@ -18,7 +18,6 @@ data = TupleDataset() data.columns = [ - #Column('id', label=('ID'), align='right'), Column('name', label=_('Username')), Column('acl groups', label=_('ACL Groups')), Column('email', label=_('Email')), @@ -44,29 +43,46 @@ if userhomepage.exists(): namelink = userhomepage.link_to(request) else: - namelink = account.name + namelink = wikiutil.escape(account.name) + + if account.disabled: + enable_disable_link = request.page.link_to( + request, text=_('Enable user'), + querystr={"action":"userprofile", + "name": account.name, + "key": "disabled", + "val": "0", + }, + rel='nofollow') + namelink += " (%s)" % _("disabled") + else: + enable_disable_link = request.page.link_to( + request, text=_('Disable user'), + querystr={"action":"userprofile", + "name": account.name, + "key": "disabled", + "val": "1", + }, + rel='nofollow') + + mail_link = request.page.link_to( + request, text=_('Mail account data'), + querystr={"action": "recoverpass", + "email": account.email, + "account_sendmail": "1", + "sysadm": "users", }, + rel='nofollow') data.addRow(( - #request.formatter.code(1) + uid + request.formatter.code(0), - # 0 request.formatter.rawHTML(namelink), - # 1 request.formatter.rawHTML(list_groups), - # 2 (request.formatter.url(1, 'mailto:' + account.email, css='mailto', do_escape=0) + request.formatter.text(account.email) + request.formatter.url(0)), - # 3 (request.formatter.url(1, 'xmpp:' + account.jid, css='mailto', do_escape=0) + request.formatter.text(account.jid) + request.formatter.url(0)), - # 4 - (request.page.link_to(request, text=_('Mail account data'), - querystr={"action": "recoverpass", - "email": account.email, - "account_sendmail": "1", - "sysadm": "users", }, - rel='nofollow')) + mail_link + " - " + enable_disable_link )) if data:
--- a/MoinMoin/userprefs/changepass.py Sun Jan 06 23:38:45 2008 +0100 +++ b/MoinMoin/userprefs/changepass.py Sun Jan 06 23:39:24 2008 +0100 @@ -50,6 +50,8 @@ # Check if password is given and matches with password repeat if password != password2: return _("Passwords don't match!") + if not password: + return _("Please specify a password!") pw_checker = request.cfg.password_checker if pw_checker:
--- a/MoinMoin/util/diff_html.py Sun Jan 06 23:38:45 2008 +0100 +++ b/MoinMoin/util/diff_html.py Sun Jan 06 23:39:24 2008 +0100 @@ -38,7 +38,7 @@ if len(seq1) == len(seq2) and linematch[0] == (0, 0, len(seq1)): # No differences. - return _("No differences found!") + return " - " + _("No differences found!", formatted=False) lastmatch = (0, 0)
--- a/MoinMoin/wikiutil.py Sun Jan 06 23:38:45 2008 +0100 +++ b/MoinMoin/wikiutil.py Sun Jan 06 23:39:24 2008 +0100 @@ -161,7 +161,10 @@ s = s.encode(config.charset) # ascii would also work s = urllib.unquote(s) if want_unicode: - s = s.decode(config.charset) + try: + s = decodeUserInput(s, [config.charset, 'iso-8859-1', ]) # try hard + except UnicodeError: + s = s.decode('ascii', 'replace') # better than crashing return s def parseQueryString(qstr, want_unicode=True):
--- a/README Sun Jan 06 23:38:45 2008 +0100 +++ b/README Sun Jan 06 23:39:24 2008 +0100 @@ -50,59 +50,20 @@ ACKNOWLEDGEMENTS ================ -We like to thank especially these people, groups and companies for their -valuable ideas and contributions: - - Ward Cunningham <ward@c2.com> for inventing WikiWiki in the first place. - - Martin Pool <mbp@humbug.org.au> for providing the base of what now is - MoinMoin, see also docs/licenses/pikipiki.txt. - - Marko Schulz <Marko.Schulz@gmx.de> for providing input on the first - versions of MoinMoin as it evolved. - - Tom Holroyd <tomh@po.crl.go.jp> provided the set of emoticons that come - with the standard distribution, and came up with the idea to add that - feature. - - Richard Jones <richard@bizarsoftware.com.au> who provided so much code - in so little time that I could not keep up with him. He works on company - time, though (unless I'm mistaken). Also found the nasty security hole - in release 0.6. - - Christian Bird <chris.bird@lineo.com> provided some nice ideas and some - code. +We have to thank a lots of people for their valuable ideas, time and +contributions - please see the MoinMoinAcknowledgements page there: - Peter Thoeny and all other people involved in creating TWikiDrawPlugin; - for copyright information see docs/licenses/twikidraw.txt; for more - information and a download link to get the source refer to - http://twiki.org/cgi-bin/view/TWiki/TWikiDrawPlugin - - Thomas Waldmann for the major effort of translating all the help and - system pages to German, and increasing volumes of code. - - Bernhard Rosenkraenzer and Andrew Bennetts for password login patches. - - Florian Festi, Bastian Blank, Oliver Graf and Nir Soffer for many code - contributions. - - Alexander Schremmer for much testing work, code contributions and - finally fixing the win32 stuff. - - Arcelor S.A. for sponsoring MoinMoin v1.5 GPL development. - - All language maintainers for translating MoinMoin to many languages and - keeping those translations up-to-date. - - And all the people we forgot or that are mentioned in the code. +http://moinmo.in/MoinMoinAcknowledgements ------------------------------------------------------------------------------ +LICENSE NOTES +============= -The slides in wiki\data\text\WikiSchulung* were written at WEB.DE AG and -licensed to MoinMoin users under the terms of "IFL-Text v1.0". +Martin Pool <mbp@humbug.org.au> provided the base of what now is MoinMoin, +see also docs/licenses/pikipiki.txt. -Copyright (c) 2003 by WEB.DE AG, Karlsruhe, Germany. +Peter Thoeny and all other people involved in creating it provided +TWikiDrawPlugin (see docs/licenses/twikidraw.txt). For more information +and a download link to get the source refer to: +http://twiki.org/cgi-bin/view/TWiki/TWikiDrawPlugin -IFL-Text v1.0: see http://www.ifross.de/ifross_html/ifl.html -
--- a/wiki/htdocs/classic/css/screen.css Sun Jan 06 23:38:45 2008 +0100 +++ b/wiki/htdocs/classic/css/screen.css Sun Jan 06 23:39:24 2008 +0100 @@ -361,6 +361,20 @@ background: url(../img/draft.png); } +#textcha { + font-size: 100%; + margin-top: 0.5em; + border: 2px solid #FF8888; + color: black; + vertical-align: middle; + padding: 3px 2px; +} + +#textcha-answer { + border: 2px solid #000000; + padding: 3px 2px; +} + .diff { width:99%; }
--- a/wiki/htdocs/modern/css/screen.css Sun Jan 06 23:38:45 2008 +0100 +++ b/wiki/htdocs/modern/css/screen.css Sun Jan 06 23:39:24 2008 +0100 @@ -383,6 +383,20 @@ margin-top: 0.5em; } +#textcha { + font-size: 100%; + margin-top: 0.5em; + border: 2px solid #FF8888; + color: black; + vertical-align: middle; + padding: 3px 2px; +} + +#textcha-answer { + border: 2px solid #000000; + padding: 3px 2px; +} + input.button { /* border: 1px solid #8cacbb;
--- a/wiki/htdocs/rightsidebar/css/screen.css Sun Jan 06 23:38:45 2008 +0100 +++ b/wiki/htdocs/rightsidebar/css/screen.css Sun Jan 06 23:39:24 2008 +0100 @@ -289,6 +289,20 @@ background: url(../img/draft.png); } +#textcha { + font-size: 100%; + margin-top: 0.5em; + border: 2px solid #FF8888; + color: black; + vertical-align: middle; + padding: 3px 2px; +} + +#textcha-answer { + border: 2px solid #000000; + padding: 3px 2px; +} + #pagebottom { clear: both; }
--- a/wiki/server/moin.fcg Sun Jan 06 23:38:45 2008 +0100 +++ b/wiki/server/moin.fcg Sun Jan 06 23:39:24 2008 +0100 @@ -3,61 +3,38 @@ """ MoinMoin - FastCGI Driver Script - TODO: this should be refactored so it uses MoinMoin.server package - (see how Twisted, WSGI and Standalone use it) - - @copyright: 2004-2005 by Oliver Graf <ograf@bitart.de> + @copyright: 2007 MoinMoin:ThomasWaldmann @license: GNU GPL, see COPYING for details. """ -# System path configuration +import sys, logging -import sys +# Path to MoinMoin package, needed if you installed with --prefix=PREFIX +# or if you did not use setup.py. +#sys.path.insert(0, 'PREFIX/lib/python2.3/site-packages') # Path of the directory where wikiconfig.py is located. # YOU NEED TO CHANGE THIS TO MATCH YOUR SETUP. sys.path.insert(0, '/path/to/wikiconfig') -# Path to MoinMoin package, needed if you installed with --prefix=PREFIX -# or if you did not use setup.py. -## sys.path.insert(0, 'PREFIX/lib/python2.3/site-packages') - # Path of the directory where farmconfig is located (if different). -## sys.path.insert(0, '/path/to/farmconfig') +#sys.path.insert(0, '/path/to/farmconfig') # Debug mode - show detailed error reports -## import os -## os.environ['MOIN_DEBUG'] = '1' - -# how many requests shall be handled by a moin fcgi process before it dies, -# -1 mean "unlimited lifetime": -max_requests = -1 +#import os +#os.environ['MOIN_DEBUG'] = '1' -# how many threads to use (1 means use only main program, non-threaded) -max_threads = 5 +from MoinMoin.server.server_fastcgi import FastCgiConfig, run -# backlog, use in socket.listen(backlog) call -backlog = 5 - - -# Code ------------------------------------------------------------------ +class Config(FastCgiConfig): + loglevel_file = logging.DEBUG + logPath = 'moin.log' -# Do not touch unless you know what you are doing! -# TODO: move to server package? - -# Set threads flag, so other code can use proper locking -from MoinMoin import config -config.use_threads = max_threads > 1 -del config + properties = {} + # properties = {'script_name': '/'} # use this instead of the line above if your wiki runs under "/" url -from MoinMoin.request import request_fcgi -from MoinMoin.support import thfcgi + # for backlog, we use a default of 5. if the listen(backlog) call crashes for you, try a smaller value! + # backlog = 1 -def handle_request(req, env, form): - request = request_fcgi.Request(req, env, form) - request.run() +run(Config) -if __name__ == '__main__': - fcg = thfcgi.FCGI(handle_request, max_requests=max_requests, backlog=backlog, max_threads=max_threads) - fcg.run() -
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/wiki/server/moin.wsgi Sun Jan 06 23:39:24 2008 +0100 @@ -0,0 +1,48 @@ +# -*- coding: iso-8859-1 -*- +""" + MoinMoin - mod_wsgi driver script + + To use this, add those statements to your Apache's VirtualHost definition: + + # this is for icons, css, js (and must match url_prefix from wiki config): + Alias /moin_static160/ /usr/share/moin/htdocs/ + + # this is the URL http://servername/moin/ you will use later to invoke moin: + WSGIScriptAlias /moin/ /some/path/moin.wsgi + + # create some wsgi daemons - use someuser.somegroup same as your data_dir: + WSGIDaemonProcess daemonname user=someuser group=somegroup processes=5 threads=10 maximum-requests=1000 + # umask=0007 does not work for mod_wsgi 1.0rc1, but will work later + + # use the daemons we defined above to process requests! + WSGIProcessGroup daemonname + + @copyright: 2007 by MoinMoin:ThomasWaldmann + @license: GNU GPL, see COPYING for details. +""" + +import sys + +# Path to MoinMoin package, needed if you installed with --prefix=PREFIX +# or if you did not use setup.py. +## sys.path.insert(0, 'PREFIX/lib/python2.3/site-packages') + +# Path of the directory where farmconfig.py is located (if different). +## sys.path.insert(0, '/path/to/farmconfig') + +# Path of the directory where wikiconfig.py is located. +# YOU NEED TO CHANGE THIS TO MATCH YOUR SETUP. +sys.path.insert(0, '/path/to/wikiconfig') + +import logging + +from MoinMoin.server.server_wsgi import WsgiConfig, moinmoinApp + +class Config(WsgiConfig): + logPath = 'moin.log' # adapt this to your needs! + #loglevel_file = logging.INFO # adapt this to your needs! + +config = Config() # MUST create an instance to init logging! + +application = moinmoinApp +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/wiki/server/moin_flup_wsgi.py Sun Jan 06 23:39:24 2008 +0100 @@ -0,0 +1,33 @@ +""" + MoinMoin - Moin as WSGI application with flup as fcgi gateway + + @copyright: 2005 by Anakim Border <akborder@gmail.com>, + 2007 by MoinMoin:ThomasWaldmann + @license: GNU GPL, see COPYING for details. +""" + +use_threads = True +unixSocketPath = '/tmp/moin.sock' + +import os +import logging + +# Set threads flag, so other code can use proper locking +from MoinMoin import config +config.use_threads = use_threads +del config + +from flup.server.fcgi import WSGIServer +from MoinMoin.server.server_wsgi import moinmoinApp, WsgiConfig + +class Config(WsgiConfig): + logPath = 'moin.log' # adapt to your needs! + #loglevel_file = logging.INFO # adapt to your needs! + +config = Config() # MUST create an instance to init logging + +if __name__ == '__main__': + server = WSGIServer(moinmoinApp, bindAddress=unixSocketPath) + server.run() + os.unlink(unixSocketPath) +
--- a/wiki/server/moinmodpy.htaccess Sun Jan 06 23:38:45 2008 +0100 +++ b/wiki/server/moinmodpy.htaccess Sun Jan 06 23:39:24 2008 +0100 @@ -39,6 +39,6 @@ # PythonPath "['/path/to/moin/lib/python','/path/to/wikiconfig']+sys.path" # # # choose the ModPy Request class as handler -# PythonHandler MoinMoin.request.MODPYTHON::Request.run +# PythonHandler MoinMoin.request.request_modpython::Request.run # #</Files>
--- a/wiki/server/moinmodpy.py Sun Jan 06 23:38:45 2008 +0100 +++ b/wiki/server/moinmodpy.py Sun Jan 06 23:39:24 2008 +0100 @@ -48,17 +48,23 @@ ## import os ## os.environ['MOIN_DEBUG'] = '1' -# Set threads flag, so other code can use proper locking. -# TODO: It seems that modpy does not use threads, so we don't need to -# set it here. Do we have another method to check this? -from MoinMoin import config -config.use_threads = 1 -del config +# Simple way +#from MoinMoin.server.server_modpython import modpythonHandler as handler + +# Complex way +from MoinMoin.server.server_modpython import ModpythonConfig, modpythonHandler + +class MyConfig(ModpythonConfig): + """ Set up local server-specific stuff here """ - -from MoinMoin.request import request_modpython + # Make sure moin will have permission to write to this file! + # Otherwise it will cause a server error. + logPath = "/var/log/apache2/moinlog" + + # Properties + # Allow overriding any request property by the value defined in + # this dict e.g properties = {'script_name': '/mywiki'}. + ## properties = {} def handler(request): - moinreq = request_modpython.Request(request) - return moinreq.run(request) - + return modpythonHandler(request, MyConfig)
--- a/wiki/server/moinwsgi.py Sun Jan 06 23:38:45 2008 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,24 +0,0 @@ -""" - MoinMoin - WSGI application - - @copyright: 2005 by Anakim Border <akborder@gmail.com> - @license: GNU GPL, see COPYING for details. -""" - -use_threads = True -unixSocketPath = '/tmp/moin.sock' - -# Set threads flag, so other code can use proper locking -from MoinMoin import config -config.use_threads = use_threads -del config - -from flup.server.fcgi import WSGIServer -from MoinMoin.server.server_wsgi import moinmoinApp -import os - -if __name__ == '__main__': - server = WSGIServer(moinmoinApp, bindAddress=unixSocketPath) - server.run() - os.unlink(unixSocketPath) -