diff --git a/admin/__init__.py b/admin/__init__.py
index 198800420..2e5ab907b 100644
--- a/admin/__init__.py
+++ b/admin/__init__.py
@@ -3,17 +3,17 @@
from modoboa.lib import parameters
from django.utils.translation import ugettext as _
-parameters.register_admin("admin","STORAGE_PATH", type="string", deflt="/var/vmail",
+parameters.register_admin("STORAGE_PATH", type="string", deflt="/var/vmail",
help=_("Path to the root directory where messages are stored"))
-parameters.register_admin("admin", "VIRTUAL_UID", type="string", deflt="vmail",
+parameters.register_admin("VIRTUAL_UID", type="string", deflt="vmail",
help=_("UID of the virtual user which owns domains/mailboxes/messages on the filesystem"))
-parameters.register_admin("admin", "VIRTUAL_GID", type="string", deflt="vmail",
+parameters.register_admin("VIRTUAL_GID", type="string", deflt="vmail",
help=_("GID of the virtual user which owns domains/mailboxes/messages on the filesystem"))
-parameters.register_admin("admin", "MAILBOX_TYPE", type="list", deflt="maildir",
+parameters.register_admin("MAILBOX_TYPE", type="list", deflt="maildir",
values=[("maildir", "maildir"), ("mbox", "mbox")],
help=_("Mailboxes storage format"))
-parameters.register_admin("admin", "MAILDIR_ROOT", type="string", deflt=".maildir",
+parameters.register_admin("MAILDIR_ROOT", type="string", deflt=".maildir",
help=_("Sub-directory (inside the mailbox) where messages are stored when using the maildir format"))
-parameters.register_admin("admin", "PASSWORD_SCHEME", type="list", deflt="crypt",
+parameters.register_admin("PASSWORD_SCHEME", type="list", deflt="crypt",
values=[("crypt", "crypt"), ("md5", "md5"), ("clear", "clear")],
help=_("Scheme used to crypt mailbox passwords"))
diff --git a/admin/models.py b/admin/models.py
index 9a80433ea..92c2b1618 100644
--- a/admin/models.py
+++ b/admin/models.py
@@ -19,17 +19,17 @@ class Domain(models.Model):
)
def create_dir(self):
- path = "%s/%s" % (parameters.get_admin("admin", "STORAGE_PATH"), self.name)
+ path = "%s/%s" % (parameters.get_admin("STORAGE_PATH"), self.name)
return exec_as_vuser("mkdir -p %s" % path)
def rename_dir(self, newname):
- stpath = parameters.get_admin("admin", "STORAGE_PATH")
+ stpath = parameters.get_admin("STORAGE_PATH")
return exec_as_vuser("mv %s/%s %s/%s" \
% (stpath, self.name, stpath, newname))
def delete_dir(self):
return exec_as_vuser("rm -r %s/%s" \
- % (parameters.get_admin("admin", "STORAGE_PATH"),
+ % (parameters.get_admin("STORAGE_PATH"),
self.name))
def __str__(self):
@@ -57,14 +57,14 @@ class Mailbox(models.Model):
def __init__(self, *args, **kwargs):
super(Mailbox, self).__init__(*args, **kwargs)
- self.mbtype = parameters.get_admin("admin", "MAILBOX_TYPE")
- self.mdirroot = parameters.get_admin("admin", "MAILDIR_ROOT")
+ self.mbtype = parameters.get_admin("MAILBOX_TYPE")
+ self.mdirroot = parameters.get_admin("MAILDIR_ROOT")
def __str__(self):
return "%s" % (self.address)
def create_dir(self, domain):
- path = "%s/%s/%s" % (parameters.get_admin("admin", "STORAGE_PATH"),
+ path = "%s/%s/%s" % (parameters.get_admin("STORAGE_PATH"),
domain.name, self.address)
if os.path.exists(path):
return True
@@ -86,7 +86,7 @@ class Mailbox(models.Model):
return True
def rename_dir(self, domain, newaddress):
- path = "%s/%s" % (parameters.get_admin("admin", "STORAGE_PATH"), domain)
+ path = "%s/%s" % (parameters.get_admin("STORAGE_PATH"), domain)
code = exec_as_vuser("mv %s/%s %s/%s" \
% (path, self.address, path, newaddress))
if code:
@@ -95,7 +95,7 @@ class Mailbox(models.Model):
def delete_dir(self):
return exec_as_vuser("rm -r %s/%s/%s" \
- % (parameters.get_admin("admin", "STORAGE_PATH"),
+ % (parameters.get_admin("STORAGE_PATH"),
self.domain.name, self.address))
class Alias(models.Model):
diff --git a/admin/templatetags/admin_extras.py b/admin/templatetags/admin_extras.py
index c6448e6be..fcba18626 100644
--- a/admin/templatetags/admin_extras.py
+++ b/admin/templatetags/admin_extras.py
@@ -97,10 +97,11 @@ def loadadminextmenu(user):
@register.simple_tag
def param(app, definition):
result = """
-
""" % definition["name"]
+
""" % (definition.has_key("label") \
+ and definition["label"] or definition["name"])
name = "%s.%s" % (app, definition["name"])
value = definition.has_key("value") \
- and definition["value"] or definition["default"]
+ and definition["value"] or definition["deflt"]
if definition["type"] in ["string", "int"]:
result += """
diff --git a/admin/views.py b/admin/views.py
index 6be08a981..65edc96c3 100644
--- a/admin/views.py
+++ b/admin/views.py
@@ -182,8 +182,8 @@ def newmailbox(request, dom_id=None):
mb.password = crypt_password(request.POST["password1"])
- mb.uid = pwd.getpwnam(parameters.get_admin("admin", "VIRTUAL_UID")).pw_uid
- mb.gid = pwd.getpwnam(parameters.get_admin("admin", "VIRTUAL_GID")).pw_gid
+ mb.uid = pwd.getpwnam(parameters.get_admin("VIRTUAL_UID")).pw_uid
+ mb.gid = pwd.getpwnam(parameters.get_admin("VIRTUAL_GID")).pw_gid
mb.domain = domain
mb.quota = request.POST["quota"]
if not mb.quota:
@@ -422,9 +422,9 @@ def viewparameters(request):
for p in sorted(parameters._params[app]['A']):
param_def = parameters._params[app]['A'][p]
newdef = {"name" : p,
- "value" : parameters.get_admin(app, p),
+ "value" : parameters.get_admin(p, app=app),
"help" : param_def["help"],
- "default" : param_def["default"],
+ "default" : param_def["deflt"],
"type" : param_def["type"]}
if "values" in param_def.keys():
newdef["values"] = param_def["values"]
@@ -442,6 +442,6 @@ def saveparameters(request):
if pname == "update":
continue
app, name = pname.split('.')
- parameters.save_admin(app, name, v)
+ parameters.save_admin(name, v, app=app)
ctx = getctx("ok")
return HttpResponse(simplejson.dumps(ctx), mimetype="application/json")
diff --git a/extensions/amavis_quarantine/cleanup.py b/extensions/amavis_quarantine/cleanup.py
index f1fe261b1..13c0adab3 100755
--- a/extensions/amavis_quarantine/cleanup.py
+++ b/extensions/amavis_quarantine/cleanup.py
@@ -8,7 +8,7 @@ if __name__ == "__main__":
main.init()
conn = db.getconnection("amavis_quarantine")
- max_messages_age = int(parameters.get_admin("amavis_quarantine", "MAX_MESSAGES_AGE"))
+ max_messages_age = int(parameters.get_admin("MAX_MESSAGES_AGE"))
print "Deleting messages older than %d days..." % max_messages_age
db.execute(conn, """
diff --git a/extensions/amavis_quarantine/lib.py b/extensions/amavis_quarantine/lib.py
index 96909f12f..afc5e29d5 100644
--- a/extensions/amavis_quarantine/lib.py
+++ b/extensions/amavis_quarantine/lib.py
@@ -6,14 +6,14 @@ from modoboa.lib import parameters
class AMrelease(object):
def __init__(self):
- mode = parameters.get_admin("amavis_quarantine", "AM_PDP_MODE")
+ mode = parameters.get_admin("AM_PDP_MODE")
if mode == "inet":
- host = parameters.get_admin("amavis_quarantine", 'AM_PDP_HOST')
- port = parameters.get_admin("amavis_quarantine", 'AM_PDP_PORT')
+ host = parameters.get_admin('AM_PDP_HOST')
+ port = parameters.get_admin('AM_PDP_PORT')
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.sock.connect((host, int(port)))
else:
- path = parameters.get_admin("amavis_quarantine", 'AM_PDP_SOCKET')
+ path = parameters.get_admin('AM_PDP_SOCKET')
self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
self.sock.connect(path)
diff --git a/extensions/amavis_quarantine/main.py b/extensions/amavis_quarantine/main.py
index 3b5b7f3c7..35632f1f9 100644
--- a/extensions/amavis_quarantine/main.py
+++ b/extensions/amavis_quarantine/main.py
@@ -16,20 +16,20 @@ def infos():
def init():
events.register("UserMenuDisplay", menu)
- parameters.register_admin("amavis_quarantine", "MAX_MESSAGES_AGE", type="int",
+ parameters.register_admin("MAX_MESSAGES_AGE", type="int",
deflt=14,
help=_("Quarantine messages maximum age (in days) before deletion"))
- parameters.register_admin("amavis_quarantine", "AM_PDP_MODE", type="list",
+ parameters.register_admin("AM_PDP_MODE", type="list",
deflt="unix",
values=[("inet", "inet"), ("unix", "unix")],
help=_("Mode used to access the PDP server"))
- parameters.register_admin("amavis_quarantine", "AM_PDP_HOST", type="string",
+ parameters.register_admin("AM_PDP_HOST", type="string",
deflt="localhost",
help=_("PDP server address (if inet mode)"))
- parameters.register_admin("amavis_quarantine", "AM_PDP_PORT", type="int",
+ parameters.register_admin("AM_PDP_PORT", type="int",
deflt=9998,
help=_("PDP server port (if inet mode)"))
- parameters.register_admin("amavis_quarantine", "AM_PDP_SOCKET", type="string",
+ parameters.register_admin("AM_PDP_SOCKET", type="string",
deflt="/var/amavis/amavisd.sock",
help=_("Path to the PDP server socket (if unix mode)"))
diff --git a/extensions/amavis_quarantine/views.py b/extensions/amavis_quarantine/views.py
index 9305287aa..5fb2ca2d5 100644
--- a/extensions/amavis_quarantine/views.py
+++ b/extensions/amavis_quarantine/views.py
@@ -89,8 +89,7 @@ WHERE quarantine.mail_id='%s'
mail = SQLemail(msg, mode, links)
return _render(request, "common/viewmail.html", {
"headers" : mail.render_headers(),
- "mailbody" : mail.body,
- "pre" : mail.pre
+ "mailbody" : mail.body
})
@login_required
diff --git a/extensions/postfix_autoreply/main.py b/extensions/postfix_autoreply/main.py
index b21eea112..49de5f02d 100644
--- a/extensions/postfix_autoreply/main.py
+++ b/extensions/postfix_autoreply/main.py
@@ -28,7 +28,7 @@ def init():
events.register("ModifyMailbox", onModifyMailbox)
events.register("UserMenuDisplay", menu)
- parameters.register_admin("postfix_autoreply", "AUTOREPLIES_TIMEOUT",
+ parameters.register_admin("AUTOREPLIES_TIMEOUT",
type="int", deflt=86400,
help=_("Timeout in seconds between two auto-replies to the same recipient"))
diff --git a/extensions/postfix_autoreply/scripts/autoreply.py b/extensions/postfix_autoreply/scripts/autoreply.py
index 506ab3a6e..759766b89 100755
--- a/extensions/postfix_autoreply/scripts/autoreply.py
+++ b/extensions/postfix_autoreply/scripts/autoreply.py
@@ -31,7 +31,8 @@ if __name__ == "__main__":
try:
lastar = ARhistoric.objects.get(armessage=armessage.id, sender=sender)
main.init()
- timeout = parameters.get_admin("postfix_autoreply", "AUTOREPLIES_TIMEOUT")
+ timeout = parameters.get_admin("AUTOREPLIES_TIMEOUT",
+ app="postfix_autoreply")
delta = datetime.timedelta(seconds=int(timeout))
if lastar.last_sent + delta > datetime.datetime.now():
sys.exit(0)
diff --git a/extensions/stats/grapher.py b/extensions/stats/grapher.py
index 5b8006fa2..b71c7fa84 100644
--- a/extensions/stats/grapher.py
+++ b/extensions/stats/grapher.py
@@ -73,8 +73,8 @@ def str2Time(y, M, d, h ="0", m ="0", s="0"):
class Grapher(object):
def __init__(self):
- self.rrd_rootdir = parameters.get_admin("stats", "RRD_ROOTDIR")
- self.img_rootdir = parameters.get_admin("stats", "IMG_ROOTDIR")
+ self.rrd_rootdir = parameters.get_admin("RRD_ROOTDIR")
+ self.img_rootdir = parameters.get_admin("IMG_ROOTDIR")
def process(self, target, suffix, start, end, tpl=traffic_avg_template):
rrdfile = "%s/%s.rrd" % (self.rrd_rootdir, target)
diff --git a/extensions/stats/main.py b/extensions/stats/main.py
index 653ec23a1..99703afbd 100644
--- a/extensions/stats/main.py
+++ b/extensions/stats/main.py
@@ -21,13 +21,13 @@ def infos():
def init():
events.register("AdminMenuDisplay", menu)
- parameters.register_admin("stats", "LOGFILE", type="string",
+ parameters.register_admin("LOGFILE", type="string",
deflt="/var/log/mail.log",
help=_("Path to log file used to collect statistics"))
- parameters.register_admin("stats", "RRD_ROOTDIR", type="string",
+ parameters.register_admin("RRD_ROOTDIR", type="string",
deflt="/tmp/modoboa",
help=_("Path to directory where RRD files are stored"))
- parameters.register_admin("stats", "IMG_ROOTDIR", type="string",
+ parameters.register_admin("IMG_ROOTDIR", type="string",
deflt="/tmp/modoboa",
help=_("Path to directory where PNG files are stored"))
diff --git a/extensions/stats/scripts/logparser.py b/extensions/stats/scripts/logparser.py
index ab581922e..9c1258dd4 100755
--- a/extensions/stats/scripts/logparser.py
+++ b/extensions/stats/scripts/logparser.py
@@ -261,8 +261,8 @@ if __name__ == "__main__":
main.init()
- log_file = parameters.get_admin("stats", "LOGFILE")
- rrd_rootdir = parameters.get_admin("stats", "RRD_ROOTDIR")
+ log_file = parameters.get_admin("LOGFILE")
+ rrd_rootdir = parameters.get_admin("RRD_ROOTDIR")
parser = OptionParser()
parser.add_option("-l","--logfile", default=log_file,
diff --git a/extensions/stats/views.py b/extensions/stats/views.py
index 2453f15e5..7a1308214 100644
--- a/extensions/stats/views.py
+++ b/extensions/stats/views.py
@@ -5,7 +5,7 @@ from django.core.urlresolvers import reverse
from django.utils.translation import ugettext as _
from django.utils import simplejson
from django.template.loader import render_to_string
-from modoboa.lib import parameters, _render, getctx
+from modoboa.lib import _render, getctx
from modoboa.admin.views import good_domain
from modoboa.admin.models import Domain, Mailbox
from django.contrib.auth.decorators \
diff --git a/extensions/webmail/lib.py b/extensions/webmail/lib.py
index 3e4e993b8..a7bbe25a1 100644
--- a/extensions/webmail/lib.py
+++ b/extensions/webmail/lib.py
@@ -154,8 +154,8 @@ class IMAPconnector(object):
def __init__(self, user=None, password=None):
self.criterions = []
- self.address = parameters.get_admin("webmail", "IMAP_SERVER")
- self.port = int(parameters.get_admin("webmail", "IMAP_PORT"))
+ self.address = parameters.get_admin("IMAP_SERVER")
+ self.port = int(parameters.get_admin("IMAP_PORT"))
status, msg = self.login(user, password)
if not status:
raise Exception(msg)
@@ -177,7 +177,7 @@ class IMAPconnector(object):
def login(self, user, passwd):
import imaplib
try:
- secured = parameters.get_admin("webmail", "IMAP_SECURED")
+ secured = parameters.get_admin("IMAP_SECURED")
if secured == "yes":
self.m = imaplib.IMAP4_SSL(self.address, self.port)
else:
@@ -278,10 +278,8 @@ class IMAPconnector(object):
md_folders = [{"name" : "INBOX", "class" : "inbox"},
{"name" : 'Drafts'},
{"name" : 'Junk'},
- {"name" : parameters.get_user(user, "webmail",
- "SENT_FOLDER")},
- {"name" : parameters.get_user(user, "webmail",
- "TRASH_FOLDER"),
+ {"name" : parameters.get_user(user, "SENT_FOLDER")},
+ {"name" : parameters.get_user(user, "TRASH_FOLDER"),
"class" : "trash"}]
md_folders += self._listfolders(md_folders=md_folders)
for fd in md_folders:
@@ -341,6 +339,15 @@ class IMAPconnector(object):
self.quota_limit = int(m.group(2))
self.quota_actual = int(m.group(1))
+ def fetchpart(self, uid, folder, part):
+ self.m.select(self._encodefolder(folder), True)
+ typ, data = self.m.fetch(uid, "(BODY[%(p)s.MIME] BODY[%(p)s])" \
+ % {"p" : part})
+ if typ != "OK":
+ return None
+ msg = email.message_from_string(data[0][1] + data[1][1])
+ return msg
+
def fetch(self, start=None, stop=None, folder=None, all=False):
if not start and not stop:
return []
@@ -360,6 +367,7 @@ class IMAPconnector(object):
if not folder:
folder = "INBOX"
tmpdict = {}
+
for response_part in data:
if isinstance(response_part, tuple):
imapid = response_part[0].split()[0]
@@ -392,7 +400,7 @@ class ImapListing(EmailListing):
self.mbc.criterions = []
EmailListing.__init__(self, **kwargs)
self.extravars["refreshrate"] = \
- int(parameters.get_user(user, "webmail", "REFRESH_INTERVAL")) * 1000
+ int(parameters.get_user(user, "REFRESH_INTERVAL")) * 1000
def parse_search_parameters(self, criterion, pattern):
def or_criterion(old, c):
@@ -422,8 +430,11 @@ class ImapListing(EmailListing):
class ImapEmail(Email):
- def __init__(self, msg, addrfull=False, *args, **kwargs):
- Email.__init__(self, msg, *args, **kwargs)
+ def __init__(self, msg, user, dformat="DISPLAYMODE", addrfull=False,
+ *args, **kwargs):
+ mformat = parameters.get_user(user, "DISPLAYMODE")
+ dformat = parameters.get_user(user, dformat)
+ Email.__init__(self, msg, mformat, dformat, *args, **kwargs)
fields = ["Subject", "From", "To", "Reply-To", "Cc", "Date"]
for f in fields:
@@ -447,19 +458,124 @@ class ImapEmail(Email):
def render_headers(self, **kwargs):
from django.template.loader import render_to_string
- attachments = []
- for part in self.attachments:
- decoded = decode_header(part.get_filename())
- if decoded[0][1] is None:
- attachments += [decoded[0][0]]
- else:
- attachments += [unicode(decoded[0][0], decoded[0][1])]
return render_to_string("webmail/headers.html", {
"headers" : self.headers,
"folder" : kwargs["folder"], "mail_id" : kwargs["mail_id"],
- "attachments" : attachments != [] and attachments or None
+ "attachments" : self.attachments != {} and self.attachments or None
})
+class ReplyModifier(ImapEmail):
+ def __init__(self, msg, user, form, all=False, **kwargs):
+ ImapEmail.__init__(self, msg, user, dformat="EDITOR", **kwargs)
+
+ self.textheader = "%s %s" % (self.From, _("wrote:"))
+ getattr(self, "_modify_%s" % self.dformat)()
+
+ form.fields["from_"].initial = user.username
+ if not "Reply-To" in msg.keys():
+ form.fields["to"].initial = self.From
+ else:
+ form.fields["to"].initial = self.Reply_To
+ if all:
+ form.fields["cc"].initial = ""
+ toparse = msg["To"].split(",")
+ if "Cc" in msg.keys():
+ toparse += msg["Cc"].split(",")
+ for addr in toparse:
+ tmp = EmailAddress(addr)
+ if tmp.address and tmp.address == request.user.username:
+ continue
+ if form.fields["cc"].initial != "":
+ form.fields["cc"].initial += ", "
+ form.fields["cc"].initial += tmp.fulladdress
+ m = re.match("re\s*:\s*.+", self.Subject.lower())
+ if m:
+ form.fields["subject"].initial = self.Subject
+ else:
+ form.fields["subject"].initial = "Re: %s" % self.Subject
+
+ def _modify_plain(self):
+ lines = self.body.split('\n')
+ body = ""
+ for l in lines:
+ if body != "":
+ body += "\n"
+ body += ">%s" % l
+ self.body = body
+
+ def _modify_html(self):
+ pass
+
+class ForwardModifier(ImapEmail):
+ def __init__(self, msg, user, form, **kwargs):
+ ImapEmail.__init__(self, msg, user, dformat="EDITOR", **kwargs)
+
+ self._header(msg)
+ form.fields["from_"].initial = user.username
+ form.fields["subject"].initial = "Fwd: %s" % self.Subject
+
+ def __getfunc(self, name):
+ return getattr(self, "%s_%s" % (name, self.dformat))
+
+ def _header(self, msg):
+ self.textheader = self.__getfunc("_header_begin")() + "\n"
+ self.textheader += \
+ self.__getfunc("_header_line")(_("Subject"), self.Subject) + "\n"
+ self.textheader += \
+ self.__getfunc("_header_line")(_("Date"), msg["Date"]) + "\n"
+ for hdr in ["From", "To", "Reply-To"]:
+ try:
+ key = re.sub("-", "_", hdr)
+ value = getattr(self, key)
+ self.textheader += \
+ self.__getfunc("_header_line")(_(hdr), value) + "\n"
+ except:
+ pass
+ self.textheader += self.__getfunc("_header_end")()
+
+ def _header_begin_plain(self):
+ return "----- %s -----" % _("Original message")
+
+ def _header_begin_html(self):
+ return """----- %s -----
+
""" % _("Original message")
+
+ def _header_line_plain(self, key, value):
+ return "%s: %s" % (key, value)
+
+ def _header_line_html(self, key, value):
+ return " | %s | %s |
" % (key, value)
+
+ def _header_end_plain(self):
+ return "\n"
+
+ def _header_end_html(self):
+ return "
"
+
+class EmailSignature(object):
+ """User signature
+
+ :param user: User object
+ """
+ def __init__(self, user):
+ dformat = parameters.get_user(user, "EDITOR")
+ content = parameters.get_user(user, "SIGNATURE")
+ getattr(self, "_format_sig_%s" % dformat)(content)
+
+ def _format_sig_plain(self, content):
+ self._sig = """
+---
+%s""" % content
+
+ def _format_sig_html(self, content):
+ content = re.sub("\n", "
", content)
+ self._sig = """
+---
+%s""" % content
+
+ def __repr__(self):
+ return self._sig
+
def find_images_in_body(body):
"""Looks for images inside a HTML body
@@ -497,7 +613,7 @@ def encrypt(clear):
from Crypto.Cipher import AES
import base64
- obj = AES.new(parameters.get_admin("webmail", "SECRET_KEY"),
+ obj = AES.new(parameters.get_admin("SECRET_KEY"),
AES.MODE_ECB)
if len(clear) % AES.block_size:
clear += "\0" * (AES.block_size - len(clear) % AES.block_size)
@@ -509,8 +625,7 @@ def decrypt(ciph):
from Crypto.Cipher import AES
import base64
- obj = AES.new(parameters.get_admin("webmail", "SECRET_KEY"),
- AES.MODE_ECB)
+ obj = AES.new(parameters.get_admin("SECRET_KEY"), AES.MODE_ECB)
ciph = base64.b64decode(ciph)
clear = obj.decrypt(ciph)
return clear.rstrip('\0')
diff --git a/extensions/webmail/main.py b/extensions/webmail/main.py
index b81733ca6..82308ebb7 100644
--- a/extensions/webmail/main.py
+++ b/extensions/webmail/main.py
@@ -9,40 +9,48 @@ def init():
events.register("UserLogin", userlogin)
events.register("UserLogout", userlogout)
- parameters.register_admin("webmail", "SECRET_KEY", type="string",
- deflt="abcdefghijklmnop",
+ parameters.register_admin("SECRET_KEY", type="string", deflt="abcdefghijklmnop",
help=_("Key used to encrypt/decrypt passwords"))
- parameters.register_admin("webmail", "IMAP_SERVER", type="string",
+ parameters.register_admin("IMAP_SERVER", type="string",
deflt="127.0.0.1",
help=_("Address of your IMAP server"))
- parameters.register_admin("webmail", "IMAP_SECURED", type="list_yesno",
+ parameters.register_admin("IMAP_SECURED", type="list_yesno",
deflt="no",
help=_("Use a secured connection to access IMAP server"))
- parameters.register_admin("webmail", "IMAP_PORT", type="int", deflt="143",
+ parameters.register_admin("IMAP_PORT", type="int", deflt="143",
help=_("Listening port of your IMAP server"))
- parameters.register_admin("webmail", "SMTP_SERVER", type="string",
+ parameters.register_admin("SMTP_SERVER", type="string",
deflt="127.0.0.1",
help=_("Address of your SMTP server"))
- parameters.register_admin("webmail", "SMTP_PORT", type="int", deflt="25",
+ parameters.register_admin("SMTP_PORT", type="int", deflt="25",
help=_("Listening port of your SMTP server"))
- parameters.register_admin("webmail", "SMTP_AUTHENTICATION", type="list_yesno",
+ parameters.register_admin("SMTP_AUTHENTICATION", type="list_yesno",
deflt="no",
help=_("Server needs authentication"))
- parameters.register_admin("webmail", "SMTP_SECURED", type="list_yesno",
+ parameters.register_admin("SMTP_SECURED", type="list_yesno",
deflt="no",
help=_("Use a secured connection to access SMTP server"))
- parameters.register_user("webmail", "REFRESH_INTERVAL", type="int", deflt=300,
+ parameters.register_user("REFRESH_INTERVAL", type="int", deflt=300,
+ label=_("Listing refresh rate"),
help=_("Automatic folder refresh rate (in seconds)"))
- parameters.register_user("webmail", "TRASH_FOLDER", type="string", deflt="Trash",
+ parameters.register_user("TRASH_FOLDER", type="string", deflt="Trash",
+ label=_("Trash folder"),
help=_("Folder where deleted messages go"))
- parameters.register_user("webmail", "SENT_FOLDER", type="string", deflt="Sent",
+ parameters.register_user("SENT_FOLDER", type="string", deflt="Sent",
+ label=_("Sent folder"),
help=_("Folder where copies of sent messages go"))
- parameters.register_user("webmail", "SIGNATURE", type="text", default="",
+ parameters.register_user("SIGNATURE", type="text", deflt="",
+ label=_("Signature text"),
help=_("User defined email signature"))
- parameters.register_user("webmail", "EDITOR", type="list", deflt="text",
+ parameters.register_user("EDITOR", type="list", deflt="plain",
+ label=_("Default editor"),
values=[("html", "html"), ("plain", "text")],
help=_("The default editor to use when composing a message"))
+ parameters.register_user("DISPLAYMODE", type="list", deflt="plain",
+ label=_("Default message display mode"),
+ values=[("html", "html"), ("plain", "text")],
+ help=_("The default mode used when displaying a message"))
def destroy():
events.unregister("UserMenuDisplay", menu)
diff --git a/extensions/webmail/templates/headers.html b/extensions/webmail/templates/headers.html
index 1dc5c534f..2afddd1da 100644
--- a/extensions/webmail/templates/headers.html
+++ b/extensions/webmail/templates/headers.html
@@ -7,8 +7,8 @@
{% trans "Attachments" %} |
{% url extensions.webmail.views.getattachment folder mail_id as test %}
- {% for attachment in attachments %}
- {{ attachment }}
+ {% for key, fname in attachments.items %}
+ {{ fname }}
{% endfor %}
|
diff --git a/extensions/webmail/templatetags/webextras.py b/extensions/webmail/templatetags/webextras.py
index 90939ab57..7f434dc96 100644
--- a/extensions/webmail/templatetags/webextras.py
+++ b/extensions/webmail/templatetags/webextras.py
@@ -87,7 +87,7 @@ def listing_menu(selection, folder, user):
]
}
]
- if folder == parameters.get_user(user, "webmail", "TRASH_FOLDER"):
+ if folder == parameters.get_user(user, "TRASH_FOLDER"):
entries[2]["menu"] += [
{"name" : "fdaction",
"label" : _("Empty folder"),
diff --git a/extensions/webmail/views.py b/extensions/webmail/views.py
index fcd5b3b31..7f3d2c0a3 100644
--- a/extensions/webmail/views.py
+++ b/extensions/webmail/views.py
@@ -116,43 +116,48 @@ def getmailcontent(request, folder, mail_id):
if "class" in msg.keys() and msg["class"] == "unseen":
IMAPconnector(user=request.user.username,
password=request.session["password"]).msg_read(folder, mail_id)
- email = ImapEmail(msg, mode="html", links="1")
+ email = ImapEmail(msg, request.user, links="1")
try:
pageid = request.session["page"]
except KeyError:
pageid = "1"
return _render(request, "common/viewmail.html", {
"headers" : email.render_headers(folder=folder, mail_id=mail_id),
- "folder" : folder, "imapid" : mail_id, "mailbody" : email.body,
- "pre" : email.pre
+ "folder" : folder, "imapid" : mail_id, "mailbody" : email.body
})
@login_required
@is_not_localadmin()
def getattachment(request, folder, mail_id):
- msg = fetchmail(request, folder, mail_id, True)
- for part in msg.walk():
- fname = part.get_filename()
- if fname is None:
- continue
- decoded = decode_header(fname)
- if decoded[0][1] is None:
- fname = unicode(decoded[0][0])
- else:
- fname = unicode(decoded[0][0], decoded[0][1])
- if fname == request.GET["fname"]:
- resp = HttpResponse(part.get_payload(decode=1))
- for hdr in ["Content-Type", "Content-Transfer-Encoding"]:
+ if request.GET.has_key("partnumber"):
+ headers = {"Content-Type" : "text/plain",
+ "Content-Transfer-Encoding" : None}
+ icon = IMAPconnector(user=request.user.username,
+ password=request.session["password"])
+ part = icon.fetchpart(mail_id, folder, request.GET["partnumber"])
+ if part is not None:
+ if part.get_content_maintype() == "message":
+ payload = part.get_payload(0)
+ else:
+ payload = part.get_payload(decode=True)
+ resp = HttpResponse(payload)
+ for hdr, default in headers.iteritems():
if not part.has_key(hdr):
- continue
+ if default is not None:
+ resp[hdr] = default
+ else:
+ continue
resp[hdr] = re.sub("\s", "", part[hdr])
+ # I would add this part into the previous loop if was able
+ # to use functions as default values... but I'm a bit lazy
+ # :p
if part.has_key("Content-Disposition"):
resp["Content-Disposition"] = \
re.sub("\s", "", part["Content-Disposition"])
else:
resp["Content-Disposition"] = \
- "attachment; filename=%s" % fname
- resp["Content-Length"] = len(resp.content)
+ "attachment; filename=%s" % request.GET["fname"]
+ resp["Content-Length"] = len(payload)
return resp
raise Http404
@@ -172,8 +177,7 @@ def move(request):
def delete(request, fdname, mail_id):
mbc = IMAPconnector(user=request.user.username,
password=request.session["password"])
- mbc.move(mail_id, fdname, parameters.get_user(request.user, "webmail",
- "TRASH_FOLDER"))
+ mbc.move(mail_id, fdname, parameters.get_user(request.user, "TRASH_FOLDER"))
ctx = getctx("ok", next=__get_current_url(request))
return HttpResponse(simplejson.dumps(ctx), mimetype="application/json")
@@ -193,7 +197,7 @@ def mark(request, name):
@login_required
@is_not_localadmin()
def empty(request, name):
- if name == parameters.get_user(request.user, "webmail", "TRASH_FOLDER"):
+ if name == parameters.get_user(request.user, "TRASH_FOLDER"):
mbc = IMAPconnector(user=request.user.username,
password=request.session["password"])
mbc.empty(name)
@@ -207,22 +211,23 @@ def compact(request, name):
mbc.compact(name)
return folder(request, name, False)
-def render_compose(request, form, posturl, bodyheader=None, body=None,
- insert_signature=False):
+def render_compose(request, form, posturl, email=None, insert_signature=False):
menu = compose_menu("", __get_current_url(request),
request.user.get_all_permissions())
- editor = parameters.get_user(request.user, "webmail", "EDITOR")
+ editor = parameters.get_user(request.user, "EDITOR")
+ if email is None:
+ body = ""
+ textheader = ""
+ else:
+ body = email.body
+ textheader = email.textheader
if insert_signature:
- if body == None:
- body = ""
- signature = parameters.get_user(request.user, "webmail", "SIGNATURE")
- if signature != "":
- body += editor == "plain" and "\n---\n%s" % signature \
- or "
---
%s" % re.sub("\n", "
", signature)
+ signature = EmailSignature(request.user)
+ body += str(signature)
content = render_to_string("webmail/compose.html", {
- "form" : form, "bodyheader" : bodyheader, "body" : body,
- "posturl" : posturl
+ "form" : form, "bodyheader" : textheader,
+ "body" : body, "posturl" : posturl
})
mbc = IMAPconnector(user=request.user.username,
password=request.session["password"])
@@ -240,7 +245,7 @@ def send_mail(request, withctx=False, origmsg=None, posturl=None):
from email.utils import make_msgid, formatdate
import smtplib
- subtype = parameters.get_user(request.user, "webmail", "EDITOR")
+ subtype = parameters.get_user(request.user, "EDITOR")
body = request.POST["id_body"]
if subtype == "html":
from email.mime.multipart import MIMEMultipart
@@ -267,17 +272,17 @@ def send_mail(request, withctx=False, origmsg=None, posturl=None):
rcpts += msg["Cc"].split(",")
error = None
try:
- s = smtplib.SMTP(parameters.get_admin("webmail", "SMTP_SERVER"))
- if parameters.get_admin("webmail", "SMTP_SECURED") == "yes":
+ s = smtplib.SMTP(parameters.get_admin("SMTP_SERVER"))
+ if parameters.get_admin("SMTP_SECURED") == "yes":
s.starttls()
except (smtplib.SMTPException, ssl.SSLError), text:
error = text
if error is None:
- if parameters.get_admin("webmail", "SMTP_AUTHENTICATION") == "yes":
+ if parameters.get_admin("SMTP_AUTHENTICATION") == "yes":
s.login(request.user.username, decrypt(request.session["password"]))
s.sendmail(msg['From'], rcpts, msg.as_string())
s.quit()
- sentfolder = parameters.get_user(request.user, "webmail", "SENT_FOLDER")
+ sentfolder = parameters.get_user(request.user, "SENT_FOLDER")
IMAPconnector(user=request.user.username,
password=request.session["password"]).push_mail(sentfolder, msg)
ctx = getctx("ok", url=__get_current_url(request))
@@ -315,39 +320,12 @@ def reply(request, folder, mail_id):
password=request.session["password"]).msg_answered(folder,
mail_id)
return r
- email = ImapEmail(msg, True)
- lines = email.body.split('\n')
- body = ""
- for l in lines:
- if body != "":
- body += "\n"
- body += ">%s" % l
- form = ComposeMailForm()
- form.fields["from_"].initial = request.user.username
- if not "Reply-To" in msg.keys():
- form.fields["to"].initial = email.From
- else:
- form.fields["to"].initial = email.Reply_To
- if request.GET.has_key("all"):
- form.fields["cc"].initial = ""
- toparse = msg["To"].split(",")
- if "Cc" in msg.keys():
- toparse += msg["Cc"].split(",")
- for addr in toparse:
- tmp = EmailAddress(addr)
- if tmp.address and tmp.address == request.user.username:
- continue
- if form.fields["cc"].initial != "":
- form.fields["cc"].initial += ", "
- form.fields["cc"].initial += tmp.fulladdress
- m = re.match("re\s*:\s*.+", email.Subject.lower())
- if m:
- form.fields["subject"].initial = email.Subject
- else:
- form.fields["subject"].initial = "Re: %s" % email.Subject
- textheader = "%s %s" % (email.From, _("wrote:"))
+
+ form = ComposeMailForm()
+ email = ReplyModifier(msg, request.user, form, request.GET.has_key("full"),
+ addrfull=True)
return render_compose(request, form, reverse(reply, args=[folder, mail_id]),
- textheader, body)
+ email)
@login_required
@is_not_localadmin()
@@ -360,23 +338,9 @@ def forward(request, folder, mail_id):
password=request.session["password"]).msgforwarded(folder,
mail_id)
return response
-
+
msg = fetchmail(request, folder, mail_id, True)
- email = ImapEmail(msg, True)
- textheader = "----- %s -----" % _("Original message")
- textheader += "\n%s: %s" % (_("Subject"), email.Subject)
- textheader += "\n%s: %s" % (_("Date"), msg["Date"])
- for hdr in ["From", "To", "Reply-To"]:
- try:
- key = re.sub("-", "_", hdr)
- value = getattr(email, key)
- textheader += "\n%s: %s" % (_(hdr), value)
- except:
- pass
- textheader += "\n"
-
form = ComposeMailForm()
- form.fields["from_"].initial = request.user.username
- form.fields["subject"].initial = "Fwd: %s" % email.Subject
+ email = ForwardModifier(msg, request.user, form, addrfull=True)
return render_compose(request, form, reverse(forward, args=[folder, mail_id]),
- textheader, email.body)
+ email)
diff --git a/lib/__init__.py b/lib/__init__.py
index a74d4ef36..bb8dd1c3f 100644
--- a/lib/__init__.py
+++ b/lib/__init__.py
@@ -26,7 +26,7 @@ def _render_error(request, errortpl="error", user_context={}):
def exec_as_vuser(cmd):
code, output = exec_cmd("sudo -u %s %s" \
- % (parameters.get_admin("admin", "VIRTUAL_UID"), cmd))
+ % (parameters.get_admin("VIRTUAL_UID", app="admin"), cmd))
if code:
exec_cmd("echo '%s' >> /tmp/vmail.log" % output)
return False
@@ -57,7 +57,10 @@ def getctx(status, level=1, callback=None, **kwargs):
ctx[kw] = v
return ctx
-def decode(s, encodings=('utf8', 'latin1', 'windows-1252', 'ascii')):
+def decode(s, encodings=('utf8', 'latin1', 'windows-1252', 'ascii'), charset=None):
+ if charset is not None:
+ return s.decode(charset, 'ignore')
+
for encoding in encodings:
try:
return s.decode(encoding)
diff --git a/lib/authbackends.py b/lib/authbackends.py
index 4505d563b..b63c9f943 100644
--- a/lib/authbackends.py
+++ b/lib/authbackends.py
@@ -27,7 +27,7 @@ class SimpleBackend:
return None
def _check_password(password, crypted):
- scheme = parameters.get_admin("admin", "PASSWORD_SCHEME")
+ scheme = parameters.get_admin("PASSWORD_SCHEME", app="admin")
if scheme == "crypt":
return crypt.crypt(password, crypted) == crypted
if scheme == "md5":
@@ -35,7 +35,7 @@ def _check_password(password, crypted):
return password
def crypt_password(password):
- scheme = parameters.get_admin("admin", "PASSWORD_SCHEME")
+ scheme = parameters.get_admin("PASSWORD_SCHEME", app="admin")
if scheme == "crypt":
salt = ''.join(Random().sample(string.letters + string.digits, 2))
return crypt.crypt(password, salt)
diff --git a/lib/email_listing.py b/lib/email_listing.py
index b1cb9c326..25da6cb0d 100644
--- a/lib/email_listing.py
+++ b/lib/email_listing.py
@@ -140,67 +140,109 @@ class EmailListing:
return _render(request, self.tpl, context)
class Email(object):
- def __init__(self, msg, mode="plain", links="0"):
+ def __init__(self, msg, mformat="plain", dformat="plain", links="0"):
self.attached_map = {}
- contents = {"html" : "", "plain" : ""}
+ self.contents = {"html" : "", "plain" : ""}
self.headers = []
- self.attachments = []
- for part in msg.walk():
- if part.get_content_maintype() == 'multipart':
- continue
- if part.has_key("Content-Disposition"):
- if re.match("^attachment", part["Content-Disposition"]):
- self.attachments += [part]
- continue
- if part.get_content_type() == "text/calendar":
- contents["plain"] += part.get_payload(decode=True)
- continue
- if part.get_content_type() in ("text/html", "text/plain"):
- contents[part.get_content_subtype()] += part.get_payload(decode=True)
- if mode != "html" or links == "0":
- continue
- if part.get_content_maintype() == "application":
- self.attachments += [part]
- continue
+ self.attachments = {}
+ self.mformat = mformat
+ self.dformat = dformat
+ self.links = links
- if part.get_content_maintype() == "image":
- m = re.match("<(.+)>", part["Content-ID"])
- cid = m is not None and m.group(1) or part["Content-ID"]
+ self.__parse(msg)
- if part.has_key("Content-Location"):
- fname = part["Content-Location"]
- elif part.has_key("Content-Disposition"):
- if not part["Content-Disposition"].startswith("inline"):
- continue
- params = part["Content-Disposition"].split(";")
- fname = None
- for p in params:
- lst = p.strip().split("=")
- if len(p) and p[0] == "filename":
- fname = p[1]
- break
- if fname is None:
+ 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() == "calendar":
+ 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
- elif part.has_key("Content-Type"):
- m = re.match(".*name=[\"'](.+)[\"']", part["Content-Type"])
- if m is None:
- continue
- fname = m.group(1)
- else:
- continue
-
+ else:
+ # I give up for now :p
+ return
self.attached_map[cid] = re.match("^http:", fname) and fname \
- or self.__save_image(fname, part)
+ or self.__save_image(fname, msg)
+ return
+ self.__parse_default(msg, level)
+ def __parse(self, msg, level=None):
+ """Recursive email parser
- if not contents.has_key(mode) or contents[mode] == "":
- if mode == "html":
- mode = "plain"
- else:
- mode = "html"
+ 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).
- self.pre, self.body = \
- getattr(self, "viewmail_%s" % mode)(contents[mode], links=links)
+ :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.
@@ -226,7 +268,6 @@ class Email(object):
m = re.match(".*cid:(.+)", url)
if m:
- print self.attached_map
if self.attached_map.has_key(m.group(1)):
return self.attached_map[m.group(1)]
return url
@@ -236,14 +277,9 @@ class Email(object):
"headers" : self.headers,
})
- def render(self, request):
- return _render(request, 'common/getmailcontent.html', {
- "body" : self.body, "pre" : self.pre
- })
-
def viewmail_plain(self, content, **kwargs):
- body = decode(content)
- return (True, body)
+ return self.dformat == "html" and "
%s
" % content \
+ or content
def viewmail_html(self, content, **kwargs):
if content is None or content == "":
@@ -260,8 +296,8 @@ class Email(object):
else:
body = lxml.html.tostring(body)
body = re.sub("<(/?)body", lambda m: "<%sdiv" % m.group(1), body)
- body = Template(decode(body)).render(Context({}))
- return (False, body)
+ body = Template(body).render(Context({}))
+ return body
def parse_search_parameters(request):
if request.GET.has_key("pattern"):
diff --git a/lib/parameters.py b/lib/parameters.py
index dbf567505..040778464 100644
--- a/lib/parameters.py
+++ b/lib/parameters.py
@@ -1,6 +1,8 @@
# -*- coding: utf-8 -*-
import string
+import inspect
+import re
from modoboa.lib.models import *
"""
@@ -31,25 +33,34 @@ def __is_defined(app, level, name):
or not name in _params[app][level].keys():
raise NotDefined(app, name)
-def __register(app, level, name, type="string", deflt=None, help=None, **kwargs):
+def __register(app, level, name, **kwargs):
"""Register a new parameter.
app corresponds to a core component (admin, main) or an extension.
- """
+ """
if not app in _params.keys():
_params[app] = {}
for lvl in _levels.keys():
_params[app][lvl] = {}
if not level in _levels.keys():
return
- _params[app][level][name] = {"type" : type, "default" : deflt, "help" : help}
+ _params[app][level][name] = kwargs
for k, v in kwargs.iteritems():
_params[app][level][name][k] = v
-def register_admin(app, name, **kwargs):
+def __guess_extension():
+ modname = inspect.getmodule(inspect.stack()[2][0]).__name__
+ m = re.match("(?:modoboa\.)?(?:extensions\.)?([^\.$]+)", modname)
+ if m:
+ return m.group(1)
+ return None
+
+def register_admin(name, **kwargs):
+ app = kwargs.has_key("app") and kwargs["app"] or __guess_extension()
return __register(app, 'A', name, **kwargs)
-def register_user(app, name, **kwargs):
+def register_user(name, **kwargs):
+ app = kwargs.has_key("app") and kwargs["app"] or __guess_extension()
return __register(app, 'U', name, **kwargs)
def unregister_app(app):
@@ -58,9 +69,10 @@ def unregister_app(app):
del _params[app]
return True
-def save_admin(app, name, value):
+def save_admin(name, value, app=None):
+ if app is None:
+ app = __guess_extension()
__is_defined(app, 'A', name)
-
fullname = "%s.%s" % (app, name)
try:
p = Parameter.objects.get(name=fullname)
@@ -72,7 +84,9 @@ def save_admin(app, name, value):
p.save()
return True
-def save_user(user, app, name, value):
+def save_user(user, name, value, app=None):
+ if app is None:
+ app = __guess_extension()
__is_defined(app, 'U', name)
fullname = "%s.%s" % (app, name)
try:
@@ -86,18 +100,22 @@ def save_user(user, app, name, value):
p.save()
return True
-def get_admin(app, name):
+def get_admin(name, app=None):
+ if app is None:
+ app = __guess_extension()
__is_defined(app, "A", name)
try:
p = Parameter.objects.get(name="%s.%s" % (app, name))
except Parameter.DoesNotExist:
- return _params[app]["A"][name]["default"]
+ return _params[app]["A"][name]["deflt"]
return p.value.decode("string_escape")
-def get_user(user, app, name):
+def get_user(user, name, app=None):
+ if app is None:
+ app = __guess_extension()
__is_defined(app, "U", name)
try:
p = UserParameter.objects.get(user=user, name="%s.%s" % (app, name))
except UserParameter.DoesNotExist:
- return _params[app]["U"][name]["default"]
+ return _params[app]["U"][name]["deflt"]
return p.value.decode("string_escape")
diff --git a/templates/common/getmailcontent.html b/templates/common/getmailcontent.html
deleted file mode 100644
index 872bdaf88..000000000
--- a/templates/common/getmailcontent.html
+++ /dev/null
@@ -1 +0,0 @@
-{% if pre %}
{% endif %}{{ body }}{% if pre %}{% endif %}
diff --git a/templates/common/viewmail.html b/templates/common/viewmail.html
index fff25b5d3..7f64d2539 100644
--- a/templates/common/viewmail.html
+++ b/templates/common/viewmail.html
@@ -49,6 +49,6 @@
{{ headers }}
- {% if pre %}
{% endif %}{{ mailbody }}{% if pre %}{% endif %}
+ {{ mailbody|safe }}