+ {{ afield }}{% for v in values %}{{ v }}{% endfor %}
+
+""")
+ return t.render(template.Context({
+ "afield" : action, "values" : values
+ }))
diff --git a/extensions/sievefilters/urls.py b/extensions/sievefilters/urls.py
index 273d9d871..d728be9c0 100644
--- a/extensions/sievefilters/urls.py
+++ b/extensions/sievefilters/urls.py
@@ -2,10 +2,21 @@ from django.conf.urls.defaults import *
urlpatterns = patterns('modoboa.extensions.sievefilters.views',
(r'^$', 'index'),
- (r'^getscript/$', 'getscript'),
- (r'^savescript/$', 'savescript'),
+ (r'^savefs/(?P.+)/$', 'savefs'),
(r'^newfs/$', 'new_filters_set'),
- (r'^deletefs/$', 'delete_filters_set'),
- (r'^activatefs/$', 'activate_filters_set'),
- (r'^downloadfs/$', 'download_filters_set'),
+ (r'^removefs/(?P.+)/$', 'remove_filters_set'),
+ (r'^activatefs/(?P.+)/$', 'activate_filters_set'),
+ (r'^downloadfs/(?P.+)/$', 'download_filters_set'),
+ (r'^templates/(?P\w+)/$', 'get_templates'),
+ (r'^userfolders/$', 'get_user_folders'),
+ (r'^(?P.+)/newfilter/$', 'newfilter'),
+ (r'^(?P.+)/editfilter/(?P.+)/$', 'editfilter'),
+ (r'^(?P.+)/removefilter/(?P.+)/$', 'removefilter'),
+ (r'^(?P.+)/togglestate/(?P.+)/$',
+ 'toggle_filter_state'),
+ (r'^(?P.+)/moveup/(?P.+)/$',
+ 'move_filter_up'),
+ (r'^(?P.+)/movedown/(?P.+)/$',
+ 'move_filter_down'),
+ (r'^(?P.+)/$', 'getfs'),
)
diff --git a/extensions/sievefilters/views.py b/extensions/sievefilters/views.py
index 15ea26a5b..6a1a70f48 100644
--- a/extensions/sievefilters/views.py
+++ b/extensions/sievefilters/views.py
@@ -4,7 +4,8 @@ from django.http import HttpResponse
from django.contrib.auth.decorators import login_required
from django.utils.translation import ugettext as _
from django.core.urlresolvers import reverse
-from modoboa.lib import _render, ajax_response
+from modoboa.lib import _render, _render_to_string, \
+ ajax_response, ajax_simple_response, parameters
from modoboa.auth.lib import get_password
from lib import *
from forms import *
@@ -14,38 +15,121 @@ def index(request, tplname="sievefilters/index.html"):
sc = SieveClient(user=request.user.username,
password=request.session["password"])
active_script, scripts = sc.listscripts()
+ if active_script is None:
+ active_script = ""
return _render(request, tplname, {
"active_script" : active_script,
- "scripts" : sorted(scripts)
+ "scripts" : sorted(scripts),
})
@login_required
-def getscript(request):
- if not request.GET.has_key("name") or request.GET["name"] == "":
- return
- sc = SieveClient(user=request.user.username,
- password=request.session["password"])
- try:
- content = sc.getscript(request.GET["name"])
- except SieveClientError, e:
- pass
- return ajax_response(request, template="sievefilters/rawfilter.html",
- name=request.GET["name"], scriptcontent=content)
+def get_templates(request, ftype):
+ if ftype == "condition":
+ return ajax_simple_response(FilterForm.cond_templates)
+ return ajax_simple_response(FilterForm.action_templates)
@login_required
-def savescript(request):
- if not request.POST.has_key("scriptname") or \
- not request.POST.has_key("scriptcontent"):
+def get_user_folders(request):
+ from modoboa.extensions.webmail.lib import IMAPconnector
+
+ mbc = IMAPconnector(user=request.user.username,
+ password=request.session["password"])
+ ret = mbc.getfolders(request.user, unseen_messages=False)
+ return ajax_simple_response(ret)
+
+@login_required
+def getfs(request, name):
+ sc = SieveClient(user=request.user.username,
+ password=request.session["password"])
+ editormode = parameters.get_user(request.user, "EDITOR_MODE")
+ try:
+ content = sc.getscript(name, format=editormode)
+ except SieveClientError, e:
+ return ajax_response(request, "ko", respmsg=str(e))
+
+ if editormode == "raw":
+ return ajax_response(request, template="sievefilters/rawfilter.html",
+ name=name, scriptcontent=content)
+ return ajax_response(request, template="sievefilters/guieditor.html",
+ fs=content)
+
+def build_filter_ctx(ctx, form):
+ ctx["form"] = form
+ ctx["conds_nb"] = range(form.conds_cnt)
+ ctx["actions_nb"] = range(form.actions_cnt)
+ return ctx
+
+def submitfilter(request, setname, okmsg, tplname, tplctx, update=False, sc=None):
+ form = build_filter_form_from_qdict(request)
+ if form.is_valid():
+ if sc is None:
+ sc = SieveClient(user=request.user.username,
+ password=request.session["password"])
+ fset = sc.getscript(setname, format="fset")
+ conditions, actions = form.tofilter()
+ if not update:
+ fset.addfilter(form.cleaned_data["name"], conditions, actions,
+ form.cleaned_data["match_type"])
+ else:
+ fset.updatefilter(request.POST["oldname"],
+ form.cleaned_data["name"], conditions, actions,
+ form.cleaned_data["match_type"])
+ sc.pushscript(fset.name, str(fset))
+ return ajax_response(request, respmsg=okmsg, ajaxnav=True)
+ tplctx = build_filter_ctx(tplctx, form)
+ return ajax_response(request, status="ko", template=tplname, **tplctx)
+
+@login_required
+def newfilter(request, setname, tplname="sievefilters/filter.html"):
+ ctx = {"title" : _("New filter"),
+ "actionurl" : reverse(newfilter, args=[setname])}
+ if request.method == "POST":
+ return submitfilter(request, setname, _("Filter created"), tplname, ctx)
+
+ conds = [("Subject", "contains", "")]
+ actions = [("fileinto", "")]
+ form = FilterForm(conds, actions, request)
+ ctx = build_filter_ctx(ctx, form)
+ return _render(request, tplname, ctx)
+
+@login_required
+def editfilter(request, setname, fname, tplname="sievefilters/filter.html"):
+ ctx = {"title" : _("Edit filter"),
+ "actionurl" : reverse(editfilter, args=[setname, fname])}
+ sc = SieveClient(user=request.user.username,
+ password=request.session["password"])
+ if request.method == "POST":
+ return submitfilter(request, setname, _("Filter modified"), tplname, ctx,
+ update=True, sc=sc)
+
+ fset = sc.getscript(setname, format="fset")
+ f = fset.getfilter(fname)
+ form = build_filter_form_from_filter(request, fname, f)
+ ctx = build_filter_ctx(ctx, form)
+ ctx["oldname"] = fname
+ return _render(request, tplname, ctx)
+
+@login_required
+def removefilter(request, setname, fname):
+ sc = SieveClient(user=request.user.username,
+ password=request.session["password"])
+ fset = sc.getscript(setname, format="fset")
+ if fset.removefilter(fname):
+ sc.pushscript(fset.name, str(fset))
+ return ajax_response(request, respmsg=_("Filter removed"))
+ return ajax_response(request, "ko", respmsg=_("Failed to remove filter"))
+
+@login_required
+def savefs(request, name):
+ if not request.POST.has_key("scriptcontent"):
return
sc = SieveClient(user=request.user.username,
password=request.session["password"])
try:
- sc.pushscript(request.POST["scriptname"],
- request.POST["scriptcontent"])
+ sc.pushscript(name, request.POST["scriptcontent"])
except SieveClientError, e:
error = str(e)
return ajax_response(request, "ko", respmsg=error)
-
return ajax_response(request, respmsg=_("Filters set saved"))
@login_required
@@ -67,7 +151,9 @@ def new_filters_set(request, tplname="sievefilters/newfiltersset.html"):
except SieveClientError, e:
error = str(e)
else:
- return ajax_response(request, url=reverse(index),
+ return ajax_response(request,
+ ajaxnav=True,
+ url=form.cleaned_data["name"],
respmsg=_("Filters set created"))
ctx["form"] = form
ctx["error"] = error
@@ -77,45 +163,75 @@ def new_filters_set(request, tplname="sievefilters/newfiltersset.html"):
return _render(request, tplname, ctx)
@login_required
-def delete_filters_set(request):
- if not request.GET.has_key("name") or \
- request.GET["name"] == "":
- return
+def remove_filters_set(request, name):
sc = SieveClient(user=request.user.username,
password=request.session["password"])
try:
- sc.deletescript(request.GET["name"])
+ sc.deletescript(name)
except SieveClientError, e:
return ajax_response(request, "ko", respmsg=str(e))
return ajax_response(request, respmsg=_("Filters set deleted"))
@login_required
-def activate_filters_set(request):
- if not request.GET.has_key("name") or \
- request.GET["name"] == "":
- return
+def activate_filters_set(request, name):
sc = SieveClient(user=request.user.username,
password=request.session["password"])
try:
- sc.activatescript(request.GET["name"])
+ sc.activatescript(name)
except SieveClientError, e:
return ajax_response(request, "ko", respmsg=str(e))
return ajax_response(request, respmsg=_("Filters set activated"))
@login_required
-def download_filters_set(request):
- if not request.GET.has_key("name") or \
- request.GET["name"] == "":
- return
+def download_filters_set(request, name):
sc = SieveClient(user=request.user.username,
password=request.session["password"])
try:
- script = sc.getscript(request.GET["name"])
+ script = sc.getscript(name)
except SieveClientError, e:
return ajax_response(request, "ko", respmsg=str(e))
resp = HttpResponse(script)
resp["Content-Type"] = "text/plain"
resp["Content-Length"] = len(script)
- resp["Content-Disposition"] = "attachment; filename=%s.txt" % request.GET["name"]
+ resp["Content-Disposition"] = 'attachment; filename="%s.txt"' % name
return resp
+
+@login_required
+def toggle_filter_state(request, setname, fname):
+ sc = SieveClient(user=request.user.username,
+ password=request.session["password"])
+ try:
+ fset = sc.getscript(setname, format="fset")
+ if fset.is_filter_disabled(fname):
+ ret = fset.enablefilter(fname)
+ newstate = _("yes")
+ else:
+ ret = fset.disablefilter(fname)
+ newstate = _("no")
+ if not ret:
+ pass
+ sc.pushscript(setname, str(fset))
+ except SieveClientError, e:
+ return ajax_response(request, "ko", respmsg=str(e))
+
+ return ajax_response(request, respmsg=newstate)
+
+def move_filter(request, setname, fname, direction):
+ sc = SieveClient(user=request.user.username,
+ password=request.session["password"])
+ try:
+ fset = sc.getscript(setname, format="fset")
+ fset.movefilter(fname, direction)
+ sc.pushscript(setname, str(fset))
+ except (SieveClientError), e:
+ return ajax_response(request, "ko", respmsg=str(e))
+ return ajax_response(request, template="sievefilters/filters.html", fs=fset)
+
+@login_required
+def move_filter_up(request, setname, fname):
+ return move_filter(request, setname, fname, "up")
+
+@login_required
+def move_filter_down(request, setname, fname):
+ return move_filter(request, setname, fname, "down")
diff --git a/extensions/webmail/lib.py b/extensions/webmail/lib.py
index 28a5eab52..be671607f 100644
--- a/extensions/webmail/lib.py
+++ b/extensions/webmail/lib.py
@@ -10,14 +10,15 @@ import socket
import imaplib
import ssl
from datetime import datetime, timedelta
-import email.utils
+import email
import lxml
from django.utils.translation import ugettext as _
from django.conf import settings
from modoboa.lib import decode, u2u_decode, tables, \
imap_utf7, static_url, parameters
from modoboa.lib.connections import ConnectionsManager
-from modoboa.lib.email_listing import MBconnector, EmailListing, Email
+from modoboa.lib.email_listing import MBconnector, EmailListing
+from modoboa.lib.emailutils import *
class WebmailError(Exception):
errorexpr = re.compile("\[([^\]]+)\]\s*([^\.]+)")
@@ -51,16 +52,6 @@ class WMtable(tables.Table):
except AttributeError:
return value
-class EmailAddress(object):
- def __init__(self, address):
- self.fulladdress = u2u_decode.u2u_decode(address).strip("\r\t\n")
- (self.name, self.address) = email.utils.parseaddr(self.fulladdress)
- if self.name == "":
- self.fulladdress = self.address
-
- def __str__(self):
- return self.fulladdress
-
class IMAPheader(object):
@staticmethod
def parse_address(value, **kwargs):
@@ -250,7 +241,7 @@ class IMAPconnector(object):
from operator import itemgetter
return sorted(result, key=itemgetter("name"))
- def getfolders(self, user):
+ def getfolders(self, user, unseen_messages=True):
md_folders = [{"name" : "INBOX", "class" : "inbox"},
{"name" : parameters.get_user(user, "DRAFTS_FOLDER"),
"class" : "drafts"},
@@ -259,12 +250,13 @@ class IMAPconnector(object):
{"name" : parameters.get_user(user, "TRASH_FOLDER"),
"class" : "trash"}]
md_folders += self._listfolders(md_folders=md_folders)
- for fd in md_folders:
- key = fd.has_key("path") and "path" or "name"
- count = self.unseen_messages(fd[key])
- if count == 0:
- continue
- fd["unseen"] = count
+ if unseen_messages:
+ for fd in md_folders:
+ key = fd.has_key("path") and "path" or "name"
+ count = self.unseen_messages(fd[key])
+ if count == 0:
+ continue
+ fd["unseen"] = count
return md_folders
def _add_flag(self, folder, mail_id, flag):
diff --git a/extensions/webmail/static/js/webmail.js b/extensions/webmail/static/js/webmail.js
index 56cc70a75..65b588bc3 100644
--- a/extensions/webmail/static/js/webmail.js
+++ b/extensions/webmail/static/js/webmail.js
@@ -119,7 +119,7 @@ function compose_callback(resp) {
onSuccess: function(resp) {
resp = JSON.decode(resp);
if (resp.status == "ko") {
- get_callback("compose")(resp);
+ current_anchor.get_callback("compose")(resp);
if ($defined(resp.error)) {
infobox.error(resp.error);
@@ -199,7 +199,7 @@ function refreshFolder() {
new Request.JSON({url: query, noCache : true, onSuccess: function(resp) {
var callback = ($defined(resp.callback)) ? resp.callback : "default";
- callbacks[callback](resp);
+ current_anchor.get_callback(callback)(resp);
infobox.info(gettext("Done"));
infobox.hide(1);
}}).get();
@@ -222,7 +222,7 @@ function simple_request(url, params) {
onSuccess : function(response) {
if (response.status == "ok") {
if (!$defined(response.next)) {
- get_callback("folder")(response);
+ current_anchor.get_callback("folder")(response);
}
infobox.info(gettext("Done"));
if ($defined(response.next)) {
diff --git a/extensions/webmail/templates/webmail/index.html b/extensions/webmail/templates/webmail/index.html
index 22a286fdc..07993d5aa 100644
--- a/extensions/webmail/templates/webmail/index.html
+++ b/extensions/webmail/templates/webmail/index.html
@@ -58,11 +58,11 @@ function wm_updatelisting(resp) {
displayID: "quotatext"
});
- register_callback("viewmail", viewmail_cb);
- register_callback("compose", compose_callback);
- register_callback("reply", compose_callback);
- register_callback("replyall", compose_callback);
- register_callback("forward", compose_callback);
+ current_anchor.register_callback("viewmail", viewmail_cb);
+ current_anchor.register_callback("compose", compose_callback);
+ current_anchor.register_callback("reply", compose_callback);
+ current_anchor.register_callback("replyall", compose_callback);
+ current_anchor.register_callback("forward", compose_callback);
window.addEvent("pageRefresh", function() {
if (rtimer) {
@@ -71,7 +71,7 @@ function wm_updatelisting(resp) {
}
});
- register_callback("folder", function(resp) {
+ current_anchor.register_callback("folder", function(resp) {
wm_updatelisting(resp);
rtimer = refreshFolder.periodical({{ refreshrate }});
searchbox_init();
diff --git a/lib/__init__.py b/lib/__init__.py
index 3b1b42e81..690b7e982 100644
--- a/lib/__init__.py
+++ b/lib/__init__.py
@@ -6,7 +6,7 @@ import hashlib, crypt, string
from random import Random
from django.http import HttpResponse
from django.conf import settings
-from django.template import RequestContext
+from django import template
from django.shortcuts import render_to_response
from django.template.loader import render_to_string
from django.utils.translation import ugettext as _
@@ -27,7 +27,7 @@ def _render(request, tpl, user_context):
(useful to use settings variables like MEDIA_URL inside templates)
"""
return render_to_response(tpl, user_context,
- context_instance=RequestContext(request))
+ context_instance=template.RequestContext(request))
def _render_to_string(request, tpl, user_context):
"""Custom rendering function
@@ -35,11 +35,24 @@ def _render_to_string(request, tpl, user_context):
Same as _render.
"""
return render_to_string(tpl, user_context,
- context_instance=RequestContext(request))
+ context_instance=template.RequestContext(request))
def _render_error(request, errortpl="error", user_context={}):
return render_to_response("common/%s.html" % errortpl, user_context,
- context_instance=RequestContext(request))
+ context_instance=template.RequestContext(request))
+
+def render_actions(actions):
+ t = template.Template("""
+{% for a in actions %}
+
+
+
+{% endfor %}
+""")
+ return t.render(template.Context({
+ "actions" : actions
+ }))
def exec_as_vuser(cmd):
code, output = exec_cmd("sudo -u %s %s" \
diff --git a/lib/email_listing.py b/lib/email_listing.py
index dc223b775..05fab0610 100644
--- a/lib/email_listing.py
+++ b/lib/email_listing.py
@@ -140,166 +140,6 @@ class EmailListing:
context[k] = v
return _render(request, self.tpl, context)
-class Email(object):
- def __init__(self, msg, mformat="plain", dformat="plain", links="0"):
- self.attached_map = {}
- self.contents = {"html" : "", "plain" : ""}
- self.headers = []
- self.attachments = {}
- self.mformat = mformat
- self.dformat = dformat
- self.links = links
-
- self.__parse(msg)
-
- if not self.contents.has_key(mformat) or self.contents[mformat] == "":
- # Fallback
- self.mformat = mformat == "html" and "plain" or "html"
-
- self.body = \
- getattr(self, "viewmail_%s" % self.mformat) \
- (self.contents[self.mformat], links=links)
-
- def __parse_default(self, msg, level):
- """Default parser
-
- All parts handled by this parser will be consireded as
- attachments.
- """
- fname = msg.get_filename()
- if fname is not None:
- decoded = decode_header(fname)
- value = decoded[0][1] is None and decoded[0][0] \
- or unicode(decoded[0][0], decoded[0][1])
- else:
- value = "part_%s" % level
- self.attachments[level] = value
-
- def _parse_text(self, msg, level):
- """Displayable content parser
-
- text, html, calendar, etc. All those contents can be displayed
- inside a navigator.
- """
- if msg.get_content_subtype() not in ["plain", "html"]:
- self.__parse_default(msg, level)
- target = "plain"
- else:
- target = msg.get_content_subtype()
- self.contents[target] += decode(msg.get_payload(decode=True),
- charset=msg.get_content_charset())
-
- def _parse_image(self, msg, level):
- """image/* parser
-
- The only reason to make a specific parser for images is that,
- sometimes, messages embark inline images, which means they
- must be displayed and not attached.
- """
- if self.dformat == "html" and self.links != "0" \
- and msg.has_key("Content-Disposition"):
- if msg["Content-Disposition"].startswith("inline"):
- cid = None
- if msg.has_key("Content-ID"):
- m = re.match("<(.+)>", msg["Content-ID"])
- cid = m is not None and m.group(1) or msg["Content-ID"]
- fname = msg.get_filename()
- if fname is None:
- if msg.has_key("Content-Location"):
- fname = msg["Content-Location"]
- elif cid is not None:
- fname = cid
- else:
- # I give up for now :p
- return
- self.attached_map[cid] = re.match("^http:", fname) and fname \
- or self.__save_image(fname, msg)
- return
- self.__parse_default(msg, level)
-
- def __parse(self, msg, level=None):
- """Recursive email parser
-
- A message structure can be complex. To correctly handle
- unknown MIME types, a simple rule is applied : if I don't know
- how to display a specific part, it becomes an attachment! If
- no name is specified for an attachment, the part number
- described in the RFC 3501 (which retrieves BODY sections) is
- used to build a file name (like part_1.1).
-
- :param msg: message (or part) to parse
- :param level: current part number
- """
- if msg.is_multipart() and msg.get_content_maintype() != "message":
- cpt = 1
- for part in msg.get_payload():
- nlevel = level is None and ("%d" % cpt) \
- or "%s.%d" % (level, cpt)
- self.__parse(part, nlevel)
- cpt += 1
- return
-
- if level is None:
- level = "1"
- try:
- getattr(self, "_parse_%s" % msg.get_content_maintype())(msg, level)
- except AttributeError:
- self.__parse_default(msg, level)
-
- def __save_image(self, fname, part):
- """Save an inline image on the filesystem.
-
- Some HTML messages are using inline images (attached images
- with links on them inside the body). In order to display them,
- images are saved on the filesystem and links contained in the
- message are modified.
-
- :param fname: the image associated filename
- :param part: the email part that contains the image payload
- """
- if re.search("\.\.", fname):
- return None
- path = "/static/tmp/" + fname
- fp = open(settings.MODOBOA_DIR + path, "wb")
- fp.write(part.get_payload(decode=True))
- fp.close()
- return path
-
- def map_cid(self, url):
- import re
-
- m = re.match(".*cid:(.+)", url)
- if m:
- if self.attached_map.has_key(m.group(1)):
- return self.attached_map[m.group(1)]
- return url
-
- def render_headers(self, **kwargs):
- return render_to_string("common/mailheaders.html", {
- "headers" : self.headers,
- })
-
- def viewmail_plain(self, content, **kwargs):
- return "
%s
" % content
-
- def viewmail_html(self, content, **kwargs):
- if content is None or content == "":
- return (False, "")
- links = kwargs.has_key("links") and kwargs["links"] or "0"
- html = lxml.html.fromstring(content)
- if links == "0":
- html.rewrite_links(lambda x: None)
- else:
- html.rewrite_links(self.map_cid)
- body = html.find("body")
- if body is None:
- body = lxml.html.tostring(html)
- else:
- body = lxml.html.tostring(body)
- body = re.sub("<(/?)body", lambda m: "<%sdiv" % m.group(1), body)
- body = Template(body).render(Context({}))
- return body
-
def parse_search_parameters(request):
if request.GET.has_key("pattern"):
request.session["pattern"] = re.escape(request.GET["pattern"])
diff --git a/lib/emailutils.py b/lib/emailutils.py
new file mode 100644
index 000000000..33552c025
--- /dev/null
+++ b/lib/emailutils.py
@@ -0,0 +1,178 @@
+# coding: utf-8
+
+import email
+import lxml
+from django.template import Template, Context
+from django.template.loader import render_to_string
+import u2u_decode
+from lib import decode
+
+class EmailAddress(object):
+ def __init__(self, address):
+ self.fulladdress = u2u_decode.u2u_decode(address).strip("\r\t\n")
+ (self.name, self.address) = email.utils.parseaddr(self.fulladdress)
+ if self.name == "":
+ self.fulladdress = self.address
+
+ def __str__(self):
+ return self.fulladdress
+
+class Email(object):
+ def __init__(self, msg, mformat="plain", dformat="plain", links="0"):
+ self.attached_map = {}
+ self.contents = {"html" : "", "plain" : ""}
+ self.headers = []
+ self.attachments = {}
+ self.mformat = mformat
+ self.dformat = dformat
+ self.links = links
+
+ self.__parse(msg)
+
+ if not self.contents.has_key(mformat) or self.contents[mformat] == "":
+ # Fallback
+ self.mformat = mformat == "html" and "plain" or "html"
+
+ self.body = \
+ getattr(self, "viewmail_%s" % self.mformat) \
+ (self.contents[self.mformat], links=links)
+
+ def __parse_default(self, msg, level):
+ """Default parser
+
+ All parts handled by this parser will be consireded as
+ attachments.
+ """
+ fname = msg.get_filename()
+ if fname is not None:
+ decoded = decode_header(fname)
+ value = decoded[0][1] is None and decoded[0][0] \
+ or unicode(decoded[0][0], decoded[0][1])
+ else:
+ value = "part_%s" % level
+ self.attachments[level] = value
+
+ def _parse_text(self, msg, level):
+ """Displayable content parser
+
+ text, html, calendar, etc. All those contents can be displayed
+ inside a navigator.
+ """
+ if msg.get_content_subtype() not in ["plain", "html"]:
+ self.__parse_default(msg, level)
+ target = "plain"
+ else:
+ target = msg.get_content_subtype()
+ self.contents[target] += decode(msg.get_payload(decode=True),
+ charset=msg.get_content_charset())
+
+ def _parse_image(self, msg, level):
+ """image/* parser
+
+ The only reason to make a specific parser for images is that,
+ sometimes, messages embark inline images, which means they
+ must be displayed and not attached.
+ """
+ if self.dformat == "html" and self.links != "0" \
+ and msg.has_key("Content-Disposition"):
+ if msg["Content-Disposition"].startswith("inline"):
+ cid = None
+ if msg.has_key("Content-ID"):
+ m = re.match("<(.+)>", msg["Content-ID"])
+ cid = m is not None and m.group(1) or msg["Content-ID"]
+ fname = msg.get_filename()
+ if fname is None:
+ if msg.has_key("Content-Location"):
+ fname = msg["Content-Location"]
+ elif cid is not None:
+ fname = cid
+ else:
+ # I give up for now :p
+ return
+ self.attached_map[cid] = re.match("^http:", fname) and fname \
+ or self.__save_image(fname, msg)
+ return
+ self.__parse_default(msg, level)
+
+ def __parse(self, msg, level=None):
+ """Recursive email parser
+
+ A message structure can be complex. To correctly handle
+ unknown MIME types, a simple rule is applied : if I don't know
+ how to display a specific part, it becomes an attachment! If
+ no name is specified for an attachment, the part number
+ described in the RFC 3501 (which retrieves BODY sections) is
+ used to build a file name (like part_1.1).
+
+ :param msg: message (or part) to parse
+ :param level: current part number
+ """
+ if msg.is_multipart() and msg.get_content_maintype() != "message":
+ cpt = 1
+ for part in msg.get_payload():
+ nlevel = level is None and ("%d" % cpt) \
+ or "%s.%d" % (level, cpt)
+ self.__parse(part, nlevel)
+ cpt += 1
+ return
+
+ if level is None:
+ level = "1"
+ try:
+ getattr(self, "_parse_%s" % msg.get_content_maintype())(msg, level)
+ except AttributeError:
+ self.__parse_default(msg, level)
+
+ def __save_image(self, fname, part):
+ """Save an inline image on the filesystem.
+
+ Some HTML messages are using inline images (attached images
+ with links on them inside the body). In order to display them,
+ images are saved on the filesystem and links contained in the
+ message are modified.
+
+ :param fname: the image associated filename
+ :param part: the email part that contains the image payload
+ """
+ if re.search("\.\.", fname):
+ return None
+ path = "/static/tmp/" + fname
+ fp = open(settings.MODOBOA_DIR + path, "wb")
+ fp.write(part.get_payload(decode=True))
+ fp.close()
+ return path
+
+ def map_cid(self, url):
+ import re
+
+ m = re.match(".*cid:(.+)", url)
+ if m:
+ if self.attached_map.has_key(m.group(1)):
+ return self.attached_map[m.group(1)]
+ return url
+
+ def render_headers(self, **kwargs):
+ return render_to_string("common/mailheaders.html", {
+ "headers" : self.headers,
+ })
+
+ def viewmail_plain(self, content, **kwargs):
+ return "