changeset 714:3128414a1380

Merge with main.
author Alexander Schremmer <alex AT alexanderweb DOT de>
date Tue, 23 May 2006 10:33:19 +0200
parents fd679c4f4665 (current diff) db1cc2a726b9 (diff)
children 1cecac2e6cfb
files
diffstat 11 files changed, 500 insertions(+), 48 deletions(-) [+]
line wrap: on
line diff
--- a/MoinMoin/action/AttachFile.py	Tue May 23 10:32:40 2006 +0200
+++ b/MoinMoin/action/AttachFile.py	Tue May 23 10:33:19 2006 +0200
@@ -41,6 +41,9 @@
 ### External interface - these are called from the core code
 #############################################################################
 
+class AttachmentAlreadyExists(Exception):
+    pass
+
 def getBasePath(request):
     """ Get base path where page dirs for attachments are stored.
     """
@@ -155,7 +158,42 @@
         }
     return "\n<p>\n%s\n</p>\n" % attach_info
 
+def add_attachment(request, pagename, target, filecontent):
+    # replace illegal chars
+    target = wikiutil.taintfilename(target)
 
+    # set mimetype from extension, or from given mimetype
+    #type, encoding = mimetypes.guess_type(target)
+    #if not type:
+    #    ext = None
+    #    if request.form.has_key('mime'):
+    #        ext = mimetypes.guess_extension(request.form['mime'][0])
+    #    if not ext:
+    #        type, encoding = mimetypes.guess_type(filename)
+    #        if type:
+    #            ext = mimetypes.guess_extension(type)
+    #        else:
+    #            ext = ''
+    #    target = target + ext
+
+    # 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)
+    if os.path.exists(fpath):
+        raise AttachmentAlreadyExists
+    else:
+        stream = open(fpath, 'wb')
+        try:
+            stream.write(filecontent)
+        finally:
+            stream.close()
+        os.chmod(fpath, 0666 & config.umask)
+
+        _addLogEntry(request, 'ATTNEW', pagename, target)
+        
+        return target
+    
 #############################################################################
 ### Internal helpers
 #############################################################################
@@ -536,49 +574,23 @@
     filecontent = request.form['file'][0]
 
     # preprocess the filename
-    # 1. strip leading drive and path (IE misbehaviour)
+    # strip leading drive and path (IE misbehaviour)
     if len(target) > 1 and (target[1] == ':' or target[0] == '\\'): # C:.... or \path... or \\server\...
         bsindex = target.rfind('\\')
         if bsindex >= 0:
             target = target[bsindex+1:]
-        
-    # 2. replace illegal chars
-    target = wikiutil.taintfilename(target)
-
-    # set mimetype from extension, or from given mimetype
-    #type, encoding = mimetypes.guess_type(target)
-    #if not type:
-    #    ext = None
-    #    if request.form.has_key('mime'):
-    #        ext = mimetypes.guess_extension(request.form['mime'][0])
-    #    if not ext:
-    #        type, encoding = mimetypes.guess_type(filename)
-    #        if type:
-    #            ext = mimetypes.guess_extension(type)
-    #        else:
-    #            ext = ''
-    #    target = target + ext
-
-    # 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)
-    if os.path.exists(fpath):
-        msg = _("Attachment '%(target)s' (remote name '%(filename)s') already exists.") % {
-            'target': target, 'filename': filename}
-    else:
-        stream = open(fpath, 'wb')
-        try:
-            stream.write(filecontent)
-        finally:
-            stream.close()
-        os.chmod(fpath, 0666 & config.umask)
+    
+    # add the attachment
+    try:
+        add_attachment(request, pagename, target, filecontent)
 
         bytes = len(filecontent)
         msg = _("Attachment '%(target)s' (remote name '%(filename)s')"
                 " with %(bytes)d bytes saved.") % {
                 'target': target, 'filename': filename, 'bytes': bytes}
-        _addLogEntry(request, 'ATTNEW', pagename, target)
+    except AttachmentAlreadyExists:
+        msg = _("Attachment '%(target)s' (remote name '%(filename)s') already exists.") % {
+            'target': target, 'filename': filename}
 
     # return attachment list
     upload_form(pagename, request, msg)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MoinMoin/mailimport.py	Tue May 23 10:33:19 2006 +0200
@@ -0,0 +1,270 @@
+"""
+    MoinMoin - E-Mail Import
+    
+    Just call this script with the URL of the wiki as a single argument
+    and feed the mail into stdin.
+
+    @copyright: 2006 by MoinMoin:AlexanderSchremmer
+    @license: GNU GPL, see COPYING for details.
+"""
+
+import os, sys, re, time
+import email
+from email.Utils import parseaddr, parsedate_tz, mktime_tz
+
+from MoinMoin import user, wikiutil, config
+from MoinMoin.action.AttachFile import add_attachment, AttachmentAlreadyExists
+from MoinMoin.Page import Page
+from MoinMoin.PageEditor import PageEditor
+from MoinMoin.request.CLI import Request as RequestCLI
+# python, at least up to 2.4, ships a broken parser for headers
+from MoinMoin.support.HeaderFixed import decode_header
+
+input = sys.stdin
+
+debug = False
+
+re_subject = re.compile(r"\[([^\]]*)\]")
+re_sigstrip = re.compile("\r?\n-- \r?\n.*$", re.S)
+
+class attachment(object):
+    """ Represents an attachment of a mail. """
+    def __init__(self, filename, mimetype, data):
+        self.filename = filename
+        self.mimetype = mimetype
+        self.data = data
+    
+    def __repr__(self):
+        return "<attachment filename=%r mimetype=%r size=%i bytes>" % (
+            self.filename, self.mimetype, len(self.data))
+
+class ProcessingError(Exception):
+    pass
+
+def log(text):
+    if debug:
+        print >>sys.stderr, text
+
+def decode_2044(header):
+    """ Decodes header field. See RFC 2044. """
+    chunks = decode_header(header)
+    chunks_decoded = []
+    for i in chunks:
+        chunks_decoded.append(i[0].decode(i[1] or 'ascii'))
+    return u''.join(chunks_decoded).strip()
+
+def process_message(message):
+    """ Processes the read message and decodes attachments. """
+    attachments = []
+    html_data = []
+    text_data = []
+   
+    to_addr = parseaddr(decode_2044(message['To']))
+    from_addr = parseaddr(decode_2044(message['From']))
+    cc_addr = parseaddr(decode_2044(message['Cc']))
+    bcc_addr = parseaddr(decode_2044(message['Bcc']))
+    
+    subject = decode_2044(message['Subject'])
+    date = time.strftime("%Y-%m-%d %H:%M", time.gmtime(mktime_tz(parsedate_tz(message['Date']))))
+    
+    log("Processing mail:\n To: %r\n From: %r\n Subject: %r" % (to_addr, from_addr, subject))
+    
+    for part in message.walk():
+        log(" Part " + repr((part.get_charsets(), part.get_content_charset(), part.get_content_type(), part.is_multipart(), )))
+        ct = part.get_content_type()
+        cs = part.get_content_charset() or "latin1"
+        payload = part.get_payload(None, True)
+    
+        fn = part.get_filename()
+        if fn is not None and fn.startswith("=?"): # heuristics ...
+            fn = decode_2044(fn)
+            
+        if fn is None and part["Content-Disposition"] is not None and "attachment" in part["Content-Disposition"]:
+            # this doesn't catch the case where there is no content-disposition but there is a file to offer to the user
+            # i hope that this can be only found in mails that are older than 10 years,
+            # so I won't care about it here
+            fn = part["Content-Description"] or "NoName"
+        if fn:
+            a = attachment(fn, ct, payload)
+            attachments.append(a)
+        else:
+            if ct == 'text/plain':
+                text_data.append(payload.decode(cs))
+                log(repr(payload.decode(cs)))
+            elif ct == 'text/html':
+                html_data.append(payload.decode(cs))
+            elif not part.is_multipart():
+                log("Unknown mail part " + repr((part.get_charsets(), part.get_content_charset(), part.get_content_type(), part.is_multipart(), )))
+
+    return {'text': u"".join(text_data), 'html': u"".join(html_data),
+            'attachments': attachments,
+            'to_addr': to_addr, 'from_addr': from_addr, 'cc_addr': cc_addr, 'bcc_addr': bcc_addr,
+            'subject': subject, 'date': date}
+
+def get_pagename_content(msg, email_subpage_template, wiki_address):
+    """ Generates pagename and content according to the specification
+        that can be found on MoinMoin:FeatureRequests/WikiEmailintegration """
+
+    generate_summary = False
+    choose_html = True
+    
+    pagename_tpl = ""
+    for addr in ('to_addr', 'cc_addr', 'bcc_addr'):
+        if msg[addr][1].strip().lower() == wiki_address:
+            pagename_tpl = msg[addr][0]
+
+    if not pagename_tpl:
+        m = re_subject.match(msg['subject'])
+        if m:
+            pagename_tpl = m.group(1)
+    else:
+        # special fix for outlook users :-)
+        if pagename_tpl[-1] == pagename_tpl[0] == "'":
+            pagename_tpl = pagename_tpl[1:-1]
+    
+    if pagename_tpl.endswith("/"):
+        pagename_tpl += email_subpage_template
+
+    # last resort
+    if not pagename_tpl:
+        pagename_tpl = email_subpage_template
+
+    # rewrite using string.formatter when python 2.4 is mandatory
+    pagename = (pagename_tpl.replace("$from", msg['from_addr'][0]).
+                replace("$date", msg['date']).
+                replace("$subj", msg['subject']))
+
+    if pagename.startswith("+ ") and "/" in pagename:
+        generate_summary = True
+        pagename = pagename[1:].lstrip()
+
+    if choose_html and msg['html']:
+        content = "{{{#!html\n%s\n}}}" % msg['html'].replace("}}}", "} } }")
+    else:
+        # strip signatures ...
+        content = re_sigstrip.sub("", msg['text'])
+
+    return {'pagename': pagename, 'content': content, 'generate_summary': generate_summary}
+
+def import_mail_from_string(request, string):
+    """ Reads an RFC 822 compliant message from a string and imports it
+        to the wiki. """
+    return import_mail_from_message(request, email.message_from_string(string))
+
+def import_mail_from_file(request, input):
+    """ Reads an RFC 822 compliant message from the file `input` and imports it to
+        the wiki. """
+
+    return import_mail_from_message(request, email.message_from_file(input))
+
+def import_mail_from_message(request, message):
+    """ Reads a message generated by the email package and imports it
+        to the wiki. """
+    msg = process_message(message)
+
+    email_subpage_template = request.cfg.email_subpage_template
+    wiki_address = request.cfg.email_wiki_address or request.cfg.mail_from
+
+    request.user = user.get_by_email_address(request, msg['from_addr'][1])
+    
+    if not request.user:
+        raise ProcessingError("No suitable user found for mail address %r" % (msg['from_addr'][1], ))
+
+    d = get_pagename_content(msg, email_subpage_template, wiki_address)
+    pagename = d['pagename']
+    generate_summary = d['generate_summary']
+
+    comment = u"Imported mail from '%s' re '%s'" % (msg['from_addr'][0], msg['subject'])
+    
+    page = PageEditor(request, pagename, do_editor_backup=0)
+    
+    if not request.user.may.save(page, "", 0):
+        raise ProcessingError("Access denied for page %r" % pagename)
+
+    attachments = []
+    
+    for att in msg['attachments']:
+        i = 0
+        while 1:
+            if i == 0:
+                fname = att.filename
+            else:
+                components = att.filename.split(".")
+                new_suffix = "-" + str(i)
+                # add the counter before the file extension
+                if len(components) > 1:
+                    fname = u"%s%s.%s" % (u".".join(components[:-1]), new_suffix, components[-1])
+                else:
+                    fname = att.filename + new_suffix
+            try:
+                # get the fname again, it might have changed
+                fname = add_attachment(request, pagename, fname, att.data)
+                attachments.append(fname)
+            except AttachmentAlreadyExists:
+                i += 1
+            else:
+                break
+
+    # build an attachment link table for the page with the e-mail
+    escape_link = lambda x: x.replace(" ", "%20")
+    attachment_links = [""] + [u"[attachment:%s attachment:%s]" % tuple([escape_link(u"%s/%s" % (pagename, x))] * 2) for x in attachments]
+
+    # assemble old page content and new mail body together
+    old_content = Page(request, pagename).get_raw_body()
+    if old_content:
+        new_content = u"%s\n-----\n%s" % (old_content, d['content'], )
+    else:
+        new_content = d['content']
+    new_content += "\n" + u"\n * ".join(attachment_links)
+
+    try:
+        page.saveText(new_content, 0, comment=comment)
+    except page.AccessDenied:
+        raise ProcessingError("Access denied for page %r" % pagename)
+    
+    if generate_summary and "/" in pagename:
+        parent_page = u"/".join(pagename.split("/")[:-1])
+        old_content = Page(request, parent_page).get_raw_body().splitlines()
+        
+        found_table = None
+        table_ends = None
+        for lineno, line in enumerate(old_content):
+            if line.startswith("## mail_overview") and old_content[lineno+1].startswith("||"):
+                found_table = lineno
+            elif found_table is not None and line.startswith("||"):
+                table_ends = lineno + 1
+            elif table_ends is not None and not line.startswith("||"):
+                break
+        
+        table_header = (u"\n\n## mail_overview (don't delete this line)\n" +
+                        u"|| '''[[GetText(From)]] ''' || '''[[GetText(To)]] ''' || '''[[GetText(Subject)]] ''' || '''[[GetText(Date)]] ''' || '''[[GetText(Link)]] ''' || '''[[GetText(Attachments)]] ''' ||\n"
+                       )
+        new_line = u'|| %s || %s || %s || %s || ["%s"] || %s ||' % (
+            msg['from_addr'][0] or msg['from_addr'][1],
+            msg['to_addr'][0] or msg['to_addr'][1],
+            msg['subject'],
+            msg['date'],
+            pagename,
+            " ".join(attachment_links),
+            )
+        if found_table:
+            content = "\n".join(old_content[:table_ends] + [new_line] + old_content[table_ends:])
+        else:
+            content = "\n".join(old_content) + table_header + new_line
+
+        page = PageEditor(request, parent_page, do_editor_backup=0)
+        page.saveText(content, 0, comment=comment)
+
+if __name__ == "__main__":
+    if len(sys.argv) > 1:
+        url = sys.argv[1]
+    else:
+        url = 'localhost/'
+
+    request = RequestCLI(url=url)
+
+    try:
+        import_mail_from_file(request, input)
+    except ProcessingError, e:
+        print >>sys.stderr, "An error occured while processing the message:", e.args
+
--- a/MoinMoin/multiconfig.py	Tue May 23 10:32:40 2006 +0200
+++ b/MoinMoin/multiconfig.py	Tue May 23 10:33:19 2006 +0200
@@ -2,7 +2,7 @@
 """
     MoinMoin - Multiple configuration handler and Configuration defaults class
 
-    @copyright: 2000-2004 by Jürgen Hermann <jh@web.de>
+    @copyright: 2000-2004 by Jrgen Hermann <jh@web.de>
     @license: GNU GPL, see COPYING for details.
 """
 
@@ -239,6 +239,9 @@
     }
     edit_locking = 'warn 10' # None, 'warn <timeout mins>', 'lock <timeout mins>'
     edit_rows = 20
+    email_subpage_template = u"$from-$date-$subj" # used for mail import
+    email_wiki_address = None # the e-mail address for e-mails that should go into the wiki
+    email_secret = ""
                 
     hacks = {} # { 'feature1': value1, ... }
                # Configuration for features still in development.
@@ -267,7 +270,7 @@
     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
-    mail_from = None # u'Jürgen Wiki <noreply@jhwiki.org>'
+    mail_from = None # u'Jrgen Wiki <noreply@jhwiki.org>'
     
     navi_bar = [u'RecentChanges', u'FindPage', u'HelpContents', ]
     nonexist_qm = 0
--- a/MoinMoin/script/__init__.py	Tue May 23 10:32:40 2006 +0200
+++ b/MoinMoin/script/__init__.py	Tue May 23 10:33:19 2006 +0200
@@ -24,7 +24,7 @@
 
 def fatal(msgtext, **kw):
     """ Print error msg to stderr and exit. """
-    sys.stderr.write("FATAL ERROR: " + msgtext + "\n")
+    sys.stderr.write("\n\nFATAL ERROR: " + msgtext + "\n")
     if kw.get('usage', 0):
         maindict = vars(sys.modules[script_module])
         if maindict.has_key('usage'):
@@ -58,12 +58,13 @@
         import optparse
         from MoinMoin import version
 
+        # what does this code do? at least it does not work.
         cmd = self.script_module.__name__.split('.')[-1].replace('_', '-')
         rev = "%s %s [%s]" % (version.project, version.release, version.revision)
-        sys.argv[0] = cmd
+        #sys.argv[0] = cmd
 
         self.parser = optparse.OptionParser(
-            usage="%(cmd)s %(usage)s\n\n" % {'cmd': cmd, 'usage': usage, },
+            usage="%(cmd)s [command] %(usage)s" % {'cmd': os.path.basename(sys.argv[0]), 'usage': usage, },
             version=rev)
         self.parser.allow_interspersed_args = False
         if def_values:
@@ -139,11 +140,14 @@
 
         args = self.args
         if len(args) < 2:
-            self.parser.error("you must specify a command module and name.")
-            sys.exit(1)
+            self.parser.print_help()
+            fatal("You must specify a command module and name.")
 
         cmd_module, cmd_name = args[:2]
         from MoinMoin import wikiutil
-        plugin_class = wikiutil.importBuiltinPlugin('script.%s' % cmd_module, cmd_name, 'PluginScript')
+        try:
+            plugin_class = wikiutil.importBuiltinPlugin('script.%s' % cmd_module, cmd_name, 'PluginScript')
+        except wikiutil.PluginMissingError:
+            fatal("Command plugin %r, command %r was not found." % (cmd_module, cmd_name))
         plugin_class(args[2:], self.options).run() # all starts again there
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MoinMoin/script/xmlrpc/__init__.py	Tue May 23 10:33:19 2006 +0200
@@ -0,0 +1,12 @@
+# -*- coding: iso-8859-1 -*-
+"""
+    MoinMoin - XMLRPC CLI Scripts
+
+    @copyright: 2006 by MoinMoin:AlexanderSchremmer
+    @license: GNU GPL, see COPYING for details.
+"""
+
+from MoinMoin.util import pysupport
+
+# create a list of extension scripts from the subpackage directory
+modules = pysupport.getPackageModules(__file__)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MoinMoin/script/xmlrpc/mailimport.py	Tue May 23 10:33:19 2006 +0200
@@ -0,0 +1,38 @@
+# -*- coding: iso-8859-1 -*-
+"""
+    MoinMoin - MailImport script
+
+    Imports a mail into the wiki.
+
+    @copyright: 2006 by MoinMoin:AlexanderSchremmer
+    @license: GNU GPL, see COPYING for details.
+"""
+
+import sys
+import xmlrpclib
+
+from MoinMoin.script import MoinScript, fatal
+
+input = sys.stdin
+
+class PluginScript(MoinScript):
+    """ Mail Importer """
+
+    def __init__(self, argv, def_values):
+        MoinScript.__init__(self, argv, def_values)
+    
+    def mainloop(self):
+        try:
+            import mailimportconf
+        except ImportError:
+            fatal("Could not find the file mailimportconf.py. Maybe you want to use the config param?")
+
+        secret = mailimportconf.mailimport_secret
+        url = mailimportconf.mailimport_url
+        
+        s = xmlrpclib.ServerProxy(url)
+
+        result = s.ProcessMail(secret, input.read())
+        
+        if result != "OK":
+            print >>sys.stderr, result
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MoinMoin/support/HeaderFixed.py	Tue May 23 10:33:19 2006 +0200
@@ -0,0 +1,75 @@
+# copied from email.Header because the original is broken
+
+# Copyright (C) 2002-2004 Python Software Foundation
+# Author: Ben Gertzfield, Barry Warsaw
+# Contact: email-sig@python.org
+
+import sys
+
+from email.Header import ecre
+
+import email.quopriMIME
+import email.base64MIME
+from email.Errors import HeaderParseError
+from email.Charset import Charset
+
+if sys.version_info[:3] < (2, 9, 0): # insert the version number
+                                     # of a fixed python here
+
+    def decode_header(header):
+        """Decode a message header value without converting charset.
+    
+        Returns a list of (decoded_string, charset) pairs containing each of the
+        decoded parts of the header.  Charset is None for non-encoded parts of the
+        header, otherwise a lower-case string containing the name of the character
+        set specified in the encoded string.
+    
+        An email.Errors.HeaderParseError may be raised when certain decoding error
+        occurs (e.g. a base64 decoding exception).
+        """
+        # If no encoding, just return the header
+        header = str(header)
+        if not ecre.search(header):
+            return [(header, None)]
+        decoded = []
+        dec = ''
+        for line in header.splitlines():
+            # This line might not have an encoding in it
+            if not ecre.search(line):
+                decoded.append((line, None))
+                continue
+            parts = ecre.split(line)
+            while parts:
+                unenc = parts.pop(0).rstrip()
+                if unenc:
+                    # Should we continue a long line?
+                    if decoded and decoded[-1][1] is None:
+                        decoded[-1] = (decoded[-1][0] + SPACE + unenc, None)
+                    else:
+                        decoded.append((unenc, None))
+                if parts:
+                    charset, encoding = [s.lower() for s in parts[0:2]]
+                    encoded = parts[2]
+                    dec = None
+                    if encoding == 'q':
+                        dec = email.quopriMIME.header_decode(encoded)
+                    elif encoding == 'b':
+                        try:
+                            dec = email.base64MIME.decode(encoded)
+                        except binascii.Error:
+                            # Turn this into a higher level exception.  BAW: Right
+                            # now we throw the lower level exception away but
+                            # when/if we get exception chaining, we'll preserve it.
+                            raise HeaderParseError
+                    if dec is None:
+                        dec = encoded
+    
+                    if decoded and decoded[-1][1] == charset:
+                        decoded[-1] = (decoded[-1][0] + dec, decoded[-1][1])
+                    else:
+                        decoded.append((dec, charset))
+                del parts[0:3]
+        return decoded
+
+else:
+    from email.Header import decode_header
--- a/MoinMoin/user.py	Tue May 23 10:32:40 2006 +0200
+++ b/MoinMoin/user.py	Tue May 23 10:33:19 2006 +0200
@@ -33,6 +33,13 @@
     userlist = [f for f in files if user_re.match(f)]
     return userlist
 
+def get_by_email_address(request, email_address):
+    """ Searches for a user with a particular e-mail address and
+        returns it."""
+    for uid in getUserList(request):
+        theuser = User(request, uid)
+        if theuser.valid and theuser.email.lower() == email_address.lower():
+            return theuser
 
 def getUserId(request, searchName):
     """
--- a/MoinMoin/userform.py	Tue May 23 10:32:40 2006 +0200
+++ b/MoinMoin/userform.py	Tue May 23 10:33:19 2006 +0200
@@ -76,12 +76,10 @@
             except KeyError:
                 return _("Please provide a valid email address!")
     
-            users = user.getUserList(self.request)
-            for uid in users:
-                theuser = user.User(self.request, uid)
-                if theuser.valid and theuser.email.lower() == email:
-                    msg = theuser.mailAccountData()
-                    return wikiutil.escape(msg)
+            u = user.get_by_email_address(self.request, email)
+            if u:
+                msg = u.mailAccountData()
+                return wikiutil.escape(msg)
 
             return _("Found no account matching the given email address '%(email)s'!") % {'email': wikiutil.escape(email)}
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MoinMoin/xmlrpc/ProcessMail.py	Tue May 23 10:33:19 2006 +0200
@@ -0,0 +1,26 @@
+# -*- coding: iso-8859-1 -*-
+"""
+    MoinMoin - This plugin is used for multi-tier mail processing
+
+    @copyright: 2006 by MoinMoin:AlexanderSchremmer
+    @license: GNU GPL, see COPYING for details.
+"""
+
+from MoinMoin import mailimport
+
+def execute(xmlrpcobj, secret, mail):
+    request = xmlrpcobj.request
+    secret = xmlrpcobj._instr(secret)
+    # hmm, repeated re-encoding looks suboptimal in terms of speed
+    mail = xmlrpcobj._instr(mail).encode("utf-8") 
+    
+    if request.cfg.email_secret != secret:
+        return u"Invalid password"
+    
+    try:
+        mailimport.import_mail_from_string(request, mail)
+    except mailimport.ProcessingError, e:
+        err = u"An error occured while processing the message: " + str(e.args)
+        request.log(err)
+        return xmlrpcobj._outstr(err)
+    return xmlrpcobj._outstr(u"OK")
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/wiki/config/mailimportconf.py	Tue May 23 10:33:19 2006 +0200
@@ -0,0 +1,7 @@
+# This is the configuration file for the mail import client
+
+# This secret has to be known by the wiki server
+mailimport_secret = u"foo"
+
+# Only needed for wiki farms
+mailimport_url = u"http://localhost:81/?action=xmlrpc2"