1.1 --- a/MoinMessageSupport.py Mon Jan 27 19:20:33 2014 +0100
1.2 +++ b/MoinMessageSupport.py Mon Jan 27 21:57:58 2014 +0100
1.3 @@ -6,19 +6,27 @@
1.4 @license: GNU GPL (v2 or later), see COPYING.txt for details.
1.5 """
1.6
1.7 +from MoinMoin import config
1.8 from MoinMoin.Page import Page
1.9 +from MoinMoin.action import AttachFile
1.10 +from MoinMoin.formatter import text_html
1.11 from MoinMoin.log import getLogger
1.12 from MoinMoin.user import User
1.13 -from MoinMoin import wikiutil
1.14 -from MoinSupport import getHeader, getMetadata, getWikiDict, writeHeaders, \
1.15 - parseDictEntry
1.16 -from ItemSupport import ItemStore
1.17 -from TokenSupport import getIdentifiers
1.18 +from MoinMoin.wikiutil import parseQueryString, taintfilename, \
1.19 + version2timestamp, getInterwikiHomePage
1.20 +
1.21 from MoinMessage import GPG, Message, MoinMessageError, \
1.22 MoinMessageMissingPart, MoinMessageBadContent, \
1.23 is_signed, is_encrypted, getContentAndSignature
1.24 +from MoinSupport import getHeader, getMetadata, getWikiDict, writeHeaders, \
1.25 + parseDictEntry, getStaticContentDirectory
1.26 +from ItemSupport import ItemStore
1.27 +from TokenSupport import getIdentifiers
1.28 +
1.29 from email.parser import Parser
1.30 +from os.path import abspath, exists, join
1.31 import time
1.32 +import urllib
1.33
1.34 RECIPIENT_PARAMETERS = ("type", "location", "fingerprint")
1.35
1.36 @@ -63,16 +71,21 @@
1.37 try:
1.38 parameters = get_recipient_details(request, message["To"], main=True)
1.39 except MoinMessageRecipientError, exc:
1.40 - writeHeaders(request, "text/plain", getMetadata(self.page), "403 Forbidden")
1.41 - request.write("The recipient indicated in the message is not known to this site. "
1.42 - "Details: %s" % exc.message)
1.43 - return
1.44 +
1.45 + # Reject missing recipients if being strict and not relying only
1.46 + # on signatures and user actions.
1.47 +
1.48 + if getattr(request, "moinmessage_reject_missing_global_recipients", False):
1.49 + writeHeaders(request, "text/plain", getMetadata(self.page), "403 Forbidden")
1.50 + request.write("The recipient indicated in the message is not known to this site. "
1.51 + "Details: %s" % exc.message)
1.52 + return
1.53 else:
1.54 if parameters["type"] == "page":
1.55 self.page = Page(request, parameters["location"])
1.56 self.init_store()
1.57
1.58 - # NOTE: Support "url".
1.59 + # NOTE: Support "url" for message forwarding.
1.60
1.61 # Handle the parsed message.
1.62
1.63 @@ -137,7 +150,7 @@
1.64 request.write("Encrypted data must be provided as application/octet-stream.")
1.65 return
1.66
1.67 - # Reject any unencryptable message.
1.68 + # Reject any undecryptable message.
1.69
1.70 except MoinMessageError:
1.71 writeHeaders(request, "text/plain", getMetadata(self.page), "403 Forbidden")
1.72 @@ -190,7 +203,7 @@
1.73
1.74 if message.date:
1.75 store_date = time.gmtime(self.store.mtime())
1.76 - page_date = time.gmtime(wikiutil.version2timestamp(self.page.mtime_usecs()))
1.77 + page_date = time.gmtime(version2timestamp(self.page.mtime_usecs()))
1.78 last_date = max(store_date, page_date)
1.79
1.80 # Reject messages older than the page date.
1.81 @@ -308,7 +321,7 @@
1.82 subpage = getattr(request.cfg, "moinmessage_gpg_recipients_page", "MoinMessageRecipientsDict")
1.83
1.84 if not main:
1.85 - homedetails = wikiutil.getInterwikiHomePage(request)
1.86 + homedetails = getInterwikiHomePage(request)
1.87
1.88 if homedetails:
1.89 homewiki, homepage = homedetails
1.90 @@ -424,4 +437,137 @@
1.91
1.92 return result
1.93
1.94 +# Access to static Moin content.
1.95 +
1.96 +htdocs = None
1.97 +
1.98 +def get_htdocs(request):
1.99 +
1.100 + "Use the 'request' to find the htdocs directory."
1.101 +
1.102 + global htdocs
1.103 + htdocs = getStaticContentDirectory(request)
1.104 +
1.105 + if not htdocs:
1.106 + htdocs_in_cfg = getattr(request.cfg, "moinmessage_static_files", None)
1.107 + if htdocs_in_cfg and exists(htdocs_in_cfg):
1.108 + htdocs = htdocs_in_cfg
1.109 + return htdocs
1.110 +
1.111 + return htdocs
1.112 +
1.113 +# Special message formatters.
1.114 +
1.115 +def unquoteWikinameURL(url, charset=config.charset):
1.116 +
1.117 + """
1.118 + The inverse of wikiutil.quoteWikinameURL, returning the page name referenced
1.119 + by the given 'url', with the page name assumed to be encoded using the given
1.120 + 'charset' (or default charset if omitted).
1.121 + """
1.122 +
1.123 + return unicode(urllib.unquote(url), encoding=charset)
1.124 +
1.125 +def getAttachmentFromURL(url, request):
1.126 +
1.127 + """
1.128 + Return a (full path, attachment filename) tuple for the attachment
1.129 + referenced by the given 'url', using the 'request' to interpret the
1.130 + structure of 'url'.
1.131 +
1.132 + If 'url' does not refer to an attachment on this wiki, None is returned.
1.133 + """
1.134 +
1.135 + # Detect static resources.
1.136 +
1.137 + htdocs_dir = get_htdocs(request)
1.138 +
1.139 + if htdocs_dir:
1.140 + prefix = request.cfg.url_prefix_static
1.141 +
1.142 + # Normalise the
1.143 +
1.144 + if not prefix.endswith("/"):
1.145 + prefix += "/"
1.146 +
1.147 + if url.startswith(prefix):
1.148 + filename = url[len(prefix):]
1.149 +
1.150 + # Obtain the resource path.
1.151 +
1.152 + path = abspath(join(htdocs_dir, filename))
1.153 +
1.154 + if exists(path):
1.155 + return path, taintfilename(filename)
1.156 +
1.157 + # Detect attachments and other resources.
1.158 +
1.159 + script = request.getScriptname()
1.160 +
1.161 + # Normalise the URL.
1.162 +
1.163 + if not script.endswith("/"):
1.164 + script += "/"
1.165 +
1.166 + # Reject URLs outside the wiki.
1.167 +
1.168 + if not url.startswith(script):
1.169 + return None
1.170 +
1.171 + path = url[len(script):].lstrip("/")
1.172 + try:
1.173 + qpagename, qs = path.split("?", 1)
1.174 + except ValueError:
1.175 + qpagename = path
1.176 + qs = None
1.177 +
1.178 + pagename = unquoteWikinameURL(qpagename)
1.179 + qs = qs and parseQueryString(qs) or {}
1.180 +
1.181 + filename = qs.get("target") or qs.get("drawing")
1.182 + filename = taintfilename(filename)
1.183 +
1.184 + # Obtain the attachment path.
1.185 +
1.186 + path = AttachFile.getFilename(request, pagename, filename)
1.187 + return path, filename
1.188 +
1.189 +class OutgoingHTMLFormatter(text_html.Formatter):
1.190 +
1.191 + """
1.192 + Handle outgoing HTML content by identifying attachments and rewriting their
1.193 + locations. References to bundled attachments are done using RFC 2111:
1.194 +
1.195 + https://tools.ietf.org/html/rfc2111
1.196 +
1.197 + Messages employing references between parts are meant to comply with RFC
1.198 + 2387:
1.199 +
1.200 + https://tools.ietf.org/html/rfc2387
1.201 + """
1.202 +
1.203 + def __init__(self, request, **kw):
1.204 + text_html.Formatter.__init__(self, request, **kw)
1.205 + self.attachments = []
1.206 +
1.207 + def add_attachment(self, location):
1.208 + details = getAttachmentFromURL(location, self.request)
1.209 + if details:
1.210 + pos = len(self.attachments)
1.211 + self.attachments.append(details)
1.212 + return "cid:attachment%d" % pos
1.213 + else:
1.214 + return None
1.215 +
1.216 + def image(self, src=None, **kw):
1.217 + src = src or kw.get("src")
1.218 + ref = src and self.add_attachment(src)
1.219 + return text_html.Formatter.image(self, ref or src, **kw)
1.220 +
1.221 + def transclusion(self, on, **kw):
1.222 + if on:
1.223 + data = kw.get("data")
1.224 + kw["data"] = data and self.add_attachment(data)
1.225 + return text_html.Formatter.transclusion(self, on, **kw)
1.226 +
1.227 # vim: tabstop=4 expandtab shiftwidth=4