mirror of
https://github.com/jwinterm/modoboa.git
synced 2026-10-17 05:41:25 +02:00
fix: return receipts are now displayed correctly.
new: parameters module refactored. The API is more simple now, there is no need to provide a module name anymore, this last is automatically guessed. Also, a new 'label' field can be provide when declaring a new parameter. It will be used instead of the name. new: email parsing refactored. My first attempt was really ugly. I did not really studied the various RFC describing how messages are formatted or what IMAP fetch queries can do. Now, I hope this new code will face better unknown types. At least, It will be simpler to update.
This commit is contained in:
+6
-6
@@ -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"))
|
||||
|
||||
+8
-8
@@ -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):
|
||||
|
||||
@@ -97,10 +97,11 @@ def loadadminextmenu(user):
|
||||
@register.simple_tag
|
||||
def param(app, definition):
|
||||
result = """<div class='row'>
|
||||
<label>%s</label>""" % definition["name"]
|
||||
<label>%s</label>""" % (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 += """
|
||||
|
||||
+5
-5
@@ -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")
|
||||
|
||||
@@ -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, """
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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)"))
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"))
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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"))
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 \
|
||||
|
||||
+136
-21
@@ -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 -----<br/>
|
||||
<table border='0'>""" % _("Original message")
|
||||
|
||||
def _header_line_plain(self, key, value):
|
||||
return "%s: %s" % (key, value)
|
||||
|
||||
def _header_line_html(self, key, value):
|
||||
return " <tr><td>%s</td><td>%s</td></tr>" % (key, value)
|
||||
|
||||
def _header_end_plain(self):
|
||||
return "\n"
|
||||
|
||||
def _header_end_html(self):
|
||||
return "</table>"
|
||||
|
||||
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", "<br/>", content)
|
||||
self._sig = """<br/>
|
||||
---<br/>
|
||||
%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')
|
||||
|
||||
+22
-14
@@ -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)
|
||||
|
||||
@@ -7,8 +7,8 @@
|
||||
<th>{% trans "Attachments" %}</th>
|
||||
<td>
|
||||
{% url extensions.webmail.views.getattachment folder mail_id as test %}
|
||||
{% for attachment in attachments %}
|
||||
<a href="{{ test }}?fname={{ attachment }}">{{ attachment }}</a>
|
||||
{% for key, fname in attachments.items %}
|
||||
<a href="{{ test }}?fname={{ fname }}&partnumber={{ key }}">{{ fname }}</a>
|
||||
{% endfor %}
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -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"),
|
||||
|
||||
+51
-87
@@ -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 "<br/>---<br/>%s" % re.sub("\n", "<br/>", 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)
|
||||
|
||||
+5
-2
@@ -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)
|
||||
|
||||
+2
-2
@@ -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)
|
||||
|
||||
+98
-62
@@ -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 "<pre>%s</pre>" % 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"):
|
||||
|
||||
+30
-12
@@ -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")
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
{% if pre %}<pre>{% endif %}{{ body }}{% if pre %}</pre>{% endif %}
|
||||
@@ -49,6 +49,6 @@
|
||||
<div id="table-container">
|
||||
{{ headers }}
|
||||
</div>
|
||||
{% if pre %}<pre>{% endif %}{{ mailbody }}{% if pre %}</pre>{% endif %}
|
||||
{{ mailbody|safe }}
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -25,7 +25,7 @@ if settings.DEBUG:
|
||||
urlpatterns += patterns(
|
||||
'',
|
||||
(r'^img/stats/(?P<path>.*)/$', 'django.views.static.serve',
|
||||
{'document_root': parameters.get_admin("stats", "IMG_ROOTDIR")})
|
||||
{'document_root': parameters.get_admin("IMG_ROOTDIR", app="stats")})
|
||||
)
|
||||
|
||||
|
||||
|
||||
+5
-8
@@ -1,3 +1,4 @@
|
||||
import copy
|
||||
from django.shortcuts import get_object_or_404, render_to_response
|
||||
from django.template.loader import render_to_string
|
||||
from django.template import RequestContext
|
||||
@@ -55,13 +56,9 @@ def preferences(request):
|
||||
tmp = {"name" : app, "params" : []}
|
||||
for p in sorted(parameters._params[app]['U']):
|
||||
param_def = parameters._params[app]['U'][p]
|
||||
newdef = {"name" : p,
|
||||
"value" : parameters.get_user(request.user, app, p),
|
||||
"help" : param_def["help"],
|
||||
"default" : param_def["default"],
|
||||
"type" : param_def["type"]}
|
||||
if "values" in param_def.keys():
|
||||
newdef["values"] = param_def["values"]
|
||||
newdef = copy.deepcopy(param_def)
|
||||
newdef["name"] = p
|
||||
newdef["value"] = parameters.get_user(request.user, p, app=app)
|
||||
tmp["params"] += [newdef]
|
||||
gparams += [tmp]
|
||||
|
||||
@@ -75,7 +72,7 @@ def savepreferences(request):
|
||||
if pname == "update":
|
||||
continue
|
||||
app, name = pname.split('.')
|
||||
parameters.save_user(request.user, app, name, v)
|
||||
parameters.save_user(request.user, name, v, app=app)
|
||||
|
||||
ctx = getctx("ok")
|
||||
return HttpResponse(simplejson.dumps(ctx), mimetype="application/json")
|
||||
|
||||
Reference in New Issue
Block a user