mirror of
https://github.com/jwinterm/modoboa.git
synced 2026-10-18 05:51:26 +02:00
wip: big refactoring
Webmail still needs work.
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
from django.utils.translation import ugettext as _, ugettext_lazy
|
||||
from django.core.urlresolvers import reverse
|
||||
from modoboa.lib import events, parameters
|
||||
from modoboa.extensions import ModoExtension, exts_pool
|
||||
from modoboa.core.extensions import ModoExtension, exts_pool
|
||||
|
||||
|
||||
class Webmail(ModoExtension):
|
||||
@@ -13,7 +13,7 @@ class Webmail(ModoExtension):
|
||||
needs_media = True
|
||||
|
||||
def load(self):
|
||||
from app_settings import ParametersForm, UserSettings
|
||||
from .app_settings import ParametersForm, UserSettings
|
||||
|
||||
parameters.register(ParametersForm, "Webmail")
|
||||
parameters.register(UserSettings, "Webmail")
|
||||
@@ -24,22 +24,24 @@ class Webmail(ModoExtension):
|
||||
|
||||
exts_pool.register_extension(Webmail)
|
||||
|
||||
|
||||
@events.observe("UserMenuDisplay")
|
||||
def menu(target, user):
|
||||
if target != "top_menu":
|
||||
return []
|
||||
if not user.has_mailbox:
|
||||
if not user.mailbox_set.count():
|
||||
return []
|
||||
return [
|
||||
{"name" : "webmail",
|
||||
"label" : _("Webmail"),
|
||||
"url" : reverse("modoboa.extensions.webmail.views.index")},
|
||||
]
|
||||
{"name": "webmail",
|
||||
"label": _("Webmail"),
|
||||
"url": reverse("modoboa.extensions.webmail.views.index")},
|
||||
]
|
||||
|
||||
|
||||
@events.observe("UserLogout")
|
||||
def userlogout(request):
|
||||
from lib import IMAPconnector
|
||||
from exceptions import ImapError
|
||||
from .lib import IMAPconnector
|
||||
from .exceptions import ImapError
|
||||
|
||||
if not request.user.has_mailbox:
|
||||
return
|
||||
|
||||
@@ -8,6 +8,7 @@ import re
|
||||
from django.utils.translation import ugettext as _
|
||||
from modoboa.lib.exceptions import ModoboaException
|
||||
|
||||
|
||||
class WebmailError(ModoboaException):
|
||||
errorexpr = re.compile("\[([^\]]+)\]\s*([^\.]+)")
|
||||
|
||||
@@ -22,6 +23,7 @@ class WebmailError(ModoboaException):
|
||||
def __str__(self):
|
||||
return self.reason
|
||||
|
||||
|
||||
class ImapError(ModoboaException):
|
||||
def __init__(self, reason):
|
||||
self.reason = reason
|
||||
|
||||
@@ -10,7 +10,10 @@ retrieve multiple attributes of a message.
|
||||
This simple module tries to fix that *problem*.
|
||||
"""
|
||||
|
||||
class ParseError(Exception): pass
|
||||
|
||||
class ParseError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
def parse_next_token(buf):
|
||||
"""Look for the next *token*
|
||||
@@ -38,6 +41,7 @@ def parse_next_token(buf):
|
||||
raise ParseError("End of buffer reached while looking for a token end")
|
||||
return end
|
||||
|
||||
|
||||
def parse_bodystructure(buf, depth=0, prefix=""):
|
||||
"""Special parser for BODYSTRUCTURE response
|
||||
|
||||
@@ -49,7 +53,7 @@ def parse_bodystructure(buf, depth=0, prefix=""):
|
||||
character into ``buf``
|
||||
"""
|
||||
ret = []
|
||||
pos = 1 # skip the first ')'
|
||||
pos = 1 # skip the first ')'
|
||||
nb_bodystruct = 0
|
||||
pnum = 1
|
||||
while pos < len(buf):
|
||||
@@ -79,7 +83,7 @@ def parse_bodystructure(buf, depth=0, prefix=""):
|
||||
if type(ret[0]) == list or len(ret) >= 7:
|
||||
partnum = prefix
|
||||
if partnum is not None:
|
||||
return [{"partnum" : partnum, "struct" : ret}], pos
|
||||
return [{"partnum": partnum, "struct": ret}], pos
|
||||
return ret, pos
|
||||
if c == ' ':
|
||||
pos += 1
|
||||
@@ -91,6 +95,7 @@ def parse_bodystructure(buf, depth=0, prefix=""):
|
||||
|
||||
raise ParseError("End of buffer reached while looking for a BODY/BODYSTRUCTURE end")
|
||||
|
||||
|
||||
def parse_fetch_response(data):
|
||||
"""Parse a FETCH response, previously issued by a UID command
|
||||
|
||||
@@ -166,6 +171,7 @@ def parse_fetch_response(data):
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def dump_bodystructure(bs, depth=0):
|
||||
if depth:
|
||||
print " " * (depth * 4),
|
||||
|
||||
@@ -3,7 +3,9 @@
|
||||
:mod:`imaputils` --- Extra IMAPv4 utilities
|
||||
-------------------------------------------
|
||||
"""
|
||||
import imaplib, ssl, email
|
||||
import imaplib
|
||||
import ssl
|
||||
import email
|
||||
import re
|
||||
import time
|
||||
from functools import wraps
|
||||
@@ -17,6 +19,7 @@ from fetch_parser import parse_fetch_response
|
||||
|
||||
#imaplib.Debug = 4
|
||||
|
||||
|
||||
class capability(object):
|
||||
"""
|
||||
Simple decorator to check if the server presents the required
|
||||
@@ -38,6 +41,7 @@ class capability(object):
|
||||
|
||||
return wrapped_func
|
||||
|
||||
|
||||
class BodyStructure(object):
|
||||
"""
|
||||
BODYSTRUCTURE response parser.
|
||||
@@ -87,7 +91,7 @@ class BodyStructure(object):
|
||||
subtype = definition[1].lower()
|
||||
ftype = "%s/%s" % (definition[0].lower(), subtype)
|
||||
if ftype in ("text/plain", "text/html"):
|
||||
if not self.contents.has_key(subtype):
|
||||
if not subtype in self.contents:
|
||||
self.contents[subtype] = [params]
|
||||
else:
|
||||
self.contents[subtype].append(params)
|
||||
@@ -135,6 +139,7 @@ class BodyStructure(object):
|
||||
return att
|
||||
return None
|
||||
|
||||
|
||||
class IMAPconnector(object):
|
||||
__metaclass__ = ConnectionsManager
|
||||
|
||||
@@ -228,7 +233,7 @@ class IMAPconnector(object):
|
||||
if self.m is not None:
|
||||
try:
|
||||
self._cmd("CHECK")
|
||||
except ImapError, e:
|
||||
except ImapError:
|
||||
if hasattr(self, "current_mailbox"):
|
||||
del self.current_mailbox
|
||||
else:
|
||||
@@ -261,7 +266,7 @@ class IMAPconnector(object):
|
||||
|
||||
data = self._cmd("LOGIN", user, passwd)
|
||||
self.m.state = "AUTH"
|
||||
if self.m.untagged_responses.has_key("CAPABILITY"):
|
||||
if "CAPABILITY" in self.m.untagged_responses:
|
||||
self.capabilities = \
|
||||
self.m.untagged_responses.pop('CAPABILITY')[0].split()
|
||||
else:
|
||||
@@ -271,7 +276,7 @@ class IMAPconnector(object):
|
||||
def logout(self):
|
||||
try:
|
||||
self._cmd("CHECK")
|
||||
except ImapError, e:
|
||||
except ImapError:
|
||||
pass
|
||||
self._cmd("LOGOUT")
|
||||
del self.m
|
||||
@@ -353,7 +358,7 @@ class IMAPconnector(object):
|
||||
sdescr = d
|
||||
break
|
||||
if sdescr is None:
|
||||
sdescr = {"name" : parts[0], "path" : path, "sub" : []}
|
||||
sdescr = {"name": parts[0], "path": path, "sub": []}
|
||||
descr += [sdescr]
|
||||
if self._parse_mailbox_name(sdescr["sub"], path, delimiter, parts[1:]):
|
||||
sdescr["class"] = "subfolders"
|
||||
@@ -379,7 +384,7 @@ class IMAPconnector(object):
|
||||
|
||||
if re.search("\%s" % delimiter, name):
|
||||
parts = name.split(".")
|
||||
if not descr.has_key("path"):
|
||||
if not "path" in descr:
|
||||
descr["path"] = parts[0]
|
||||
descr["sub"] = []
|
||||
if self._parse_mailbox_name(descr["sub"], parts[0], delimiter,
|
||||
@@ -443,14 +448,16 @@ class IMAPconnector(object):
|
||||
if topmailbox:
|
||||
md_mailboxes = []
|
||||
else:
|
||||
md_mailboxes = [{"name" : "INBOX", "class" : "icon-inbox"},
|
||||
{"name" : parameters.get_user(user, "DRAFTS_FOLDER"),
|
||||
"class" : "icon-file"},
|
||||
{"name" : 'Junk', "class" : "icon-fire"},
|
||||
{"name" : parameters.get_user(user, "SENT_FOLDER"),
|
||||
"class" : "icon-envelope"},
|
||||
{"name" : parameters.get_user(user, "TRASH_FOLDER"),
|
||||
"class" : "icon-trash"}]
|
||||
md_mailboxes = [
|
||||
{"name": "INBOX", "class": "icon-inbox"},
|
||||
{"name": parameters.get_user(user, "DRAFTS_FOLDER"),
|
||||
"class": "icon-file"},
|
||||
{"name": 'Junk', "class": "icon-fire"},
|
||||
{"name": parameters.get_user(user, "SENT_FOLDER"),
|
||||
"class": "icon-envelope"},
|
||||
{"name": parameters.get_user(user, "TRASH_FOLDER"),
|
||||
"class": "icon-trash"}
|
||||
]
|
||||
if until_mailbox:
|
||||
name, parent = separate_mailbox(until_mailbox)
|
||||
if parent:
|
||||
@@ -459,10 +466,10 @@ class IMAPconnector(object):
|
||||
|
||||
if unseen_messages:
|
||||
for mb in md_mailboxes:
|
||||
if not mb.has_key("send_status"):
|
||||
if not "send_status" in mb:
|
||||
continue
|
||||
del mb["send_status"]
|
||||
key = mb.has_key("path") and "path" or "name"
|
||||
key = "path" if "path" in mb else "name"
|
||||
count = self.unseen_messages(mb[key])
|
||||
if count == 0:
|
||||
continue
|
||||
@@ -619,7 +626,7 @@ class IMAPconnector(object):
|
||||
if r'\Answered' in data[int(uid)]['FLAGS']:
|
||||
msg['img_flags'] = [static_url('pics/answered.png')]
|
||||
if r'$Forwarded' in data[int(uid)]['FLAGS']:
|
||||
if msg.has_key('img_flags'):
|
||||
if 'img_flags' in msg:
|
||||
msg['img_flags'] += [static_url('pics/forwarded.png')]
|
||||
else:
|
||||
msg['img_flags'] = [static_url('pics/forwarded.png')]
|
||||
@@ -650,9 +657,10 @@ class IMAPconnector(object):
|
||||
data = self._cmd(
|
||||
"FETCH", mailid,
|
||||
"(BODYSTRUCTURE %s[HEADER.FIELDS (%s)])" % (bcmd, " ".join(headers))
|
||||
)
|
||||
)
|
||||
return data[int(mailid)]
|
||||
|
||||
|
||||
def separate_mailbox(fullname, sep="."):
|
||||
"""Split a mailbox name
|
||||
|
||||
@@ -666,6 +674,7 @@ def separate_mailbox(fullname, sep="."):
|
||||
return name, parent
|
||||
return fullname, None
|
||||
|
||||
|
||||
def get_imapconnector(request):
|
||||
"""Simple shortcut to create a connector
|
||||
|
||||
@@ -679,4 +688,3 @@ def get_imapconnector(request):
|
||||
if __name__ == "__main__":
|
||||
import doctest
|
||||
doctest.testmod()
|
||||
|
||||
|
||||
@@ -1,24 +1,24 @@
|
||||
# coding: utf-8
|
||||
import os
|
||||
import re
|
||||
import time
|
||||
from datetime import datetime, timedelta
|
||||
import email
|
||||
import lxml
|
||||
import chardet
|
||||
|
||||
from django.core.files.uploadhandler import FileUploadHandler, SkipFile
|
||||
from django.utils.translation import ugettext as _, ugettext_lazy
|
||||
from django.conf import settings
|
||||
|
||||
import os
|
||||
import re
|
||||
from modoboa.lib import u2u_decode, tables, parameters
|
||||
from modoboa.lib.webutils import size2integer
|
||||
from modoboa.lib.email_listing import EmailListing
|
||||
from modoboa.extensions.webmail.exceptions import WebmailError
|
||||
from modoboa.extensions.webmail.imaputils import IMAPconnector, get_imapconnector, BodyStructure
|
||||
from modoboa.lib.emailutils import (
|
||||
EmailAddress, Email, prepare_addresses, set_email_headers
|
||||
)
|
||||
from modoboa.extensions.webmail.exceptions import WebmailError
|
||||
from modoboa.extensions.webmail.imaputils import (
|
||||
IMAPconnector, get_imapconnector, BodyStructure
|
||||
)
|
||||
|
||||
|
||||
class WMtable(tables.Table):
|
||||
@@ -32,8 +32,12 @@ class WMtable(tables.Table):
|
||||
)
|
||||
flags = tables.ImgColumn("flags", width="4%")
|
||||
withatts = tables.ImgColumn("withatts", width="2%")
|
||||
subject = tables.Column("subject", label=ugettext_lazy("Subject"), width="50%", limit=60)
|
||||
from_ = tables.Column("from", width="20%", label=ugettext_lazy("From"), limit=30)
|
||||
subject = tables.Column(
|
||||
"subject", label=ugettext_lazy("Subject"), width="50%", limit=60
|
||||
)
|
||||
from_ = tables.Column(
|
||||
"from", width="20%", label=ugettext_lazy("From"), limit=30
|
||||
)
|
||||
date = tables.Column("date", width="15%", label=ugettext_lazy("Date"))
|
||||
|
||||
cols_order = ["select", "withatts", "flags", "subject", "from_", "date"]
|
||||
@@ -138,7 +142,7 @@ class ImapListing(EmailListing):
|
||||
def __init__(self, user, password, **kwargs):
|
||||
self.user = user
|
||||
self.mbc = IMAPconnector(user=user.username, password=password)
|
||||
if kwargs.has_key("pattern"):
|
||||
if "pattern" in kwargs:
|
||||
self.parse_search_parameters(kwargs["criteria"],
|
||||
kwargs["pattern"])
|
||||
else:
|
||||
@@ -177,13 +181,14 @@ class ImapListing(EmailListing):
|
||||
def computequota(mbc):
|
||||
try:
|
||||
return int(float(mbc.quota_actual) \
|
||||
/ float(mbc.quota_limit) * 100)
|
||||
/ float(mbc.quota_limit) * 100)
|
||||
except (AttributeError, TypeError):
|
||||
return -1
|
||||
|
||||
def getquota(self):
|
||||
return ImapListing.computequota(self.mbc)
|
||||
|
||||
|
||||
class ImapEmail(Email):
|
||||
headernames = [
|
||||
('From', True),
|
||||
@@ -191,7 +196,7 @@ class ImapEmail(Email):
|
||||
('Cc', True),
|
||||
('Date', True),
|
||||
('Subject', True),
|
||||
]
|
||||
]
|
||||
|
||||
def __init__(self, mbox, mailid, request, dformat="DISPLAYMODE", addrfull=False,
|
||||
links=0):
|
||||
@@ -251,7 +256,7 @@ class ImapEmail(Email):
|
||||
except AttributeError:
|
||||
value = msg[name]
|
||||
if hdr[1]:
|
||||
self.headers += [{"name" : label, "value" : value}]
|
||||
self.headers += [{"name": label, "value": value}]
|
||||
try:
|
||||
label = re.sub("-", "_", label)
|
||||
setattr(self, label, value)
|
||||
@@ -260,11 +265,11 @@ class ImapEmail(Email):
|
||||
|
||||
@property
|
||||
def headers_as_list(self):
|
||||
return map(lambda hdr: hdr[0].upper(), self.headernames)
|
||||
return [hdr[0].upper() for hdr in self.headernames]
|
||||
|
||||
@property
|
||||
def headers_as_text(self):
|
||||
return " ".join(map(lambda hdr: hdr[0].upper(), self.headernames))
|
||||
return " ".join(self.headers_as_list())
|
||||
|
||||
def _find_content_charset(self, part):
|
||||
for pos, elem in enumerate(part["params"]):
|
||||
@@ -277,12 +282,12 @@ class ImapEmail(Email):
|
||||
attname = "part_%s" % att["pnum"]
|
||||
params = None
|
||||
key = None
|
||||
if att.has_key("params") and att["params"] != "NIL":
|
||||
if "params" in att and att["params"] != "NIL":
|
||||
params = att["params"]
|
||||
key = "name"
|
||||
|
||||
if key is None and \
|
||||
att.has_key("disposition") and len(att["disposition"]) > 1:
|
||||
"disposition" in att and len(att["disposition"]) > 1:
|
||||
params = att["disposition"][1]
|
||||
key = "filename"
|
||||
|
||||
@@ -308,13 +313,10 @@ class ImapEmail(Email):
|
||||
fp.write(decode_payload(params["encoding"], content))
|
||||
fp.close()
|
||||
|
||||
|
||||
def map_cid(self, url):
|
||||
import re
|
||||
|
||||
m = re.match(".*cid:(.+)", url)
|
||||
if m:
|
||||
if self.bs.inlines.has_key(m.group(1)):
|
||||
if m.group(1) in self.bs.inlines:
|
||||
return self.bs.inlines[m.group(1)]["fname"]
|
||||
return url
|
||||
|
||||
@@ -322,12 +324,13 @@ class ImapEmail(Email):
|
||||
from django.template.loader import render_to_string
|
||||
|
||||
res = render_to_string("webmail/headers.html", {
|
||||
"headers" : self.headers,
|
||||
"folder" : kwargs["folder"], "mail_id" : kwargs["mail_id"],
|
||||
"attachments" : self.attachments != {} and self.attachments or None
|
||||
})
|
||||
"headers": self.headers,
|
||||
"folder": kwargs["folder"], "mail_id": kwargs["mail_id"],
|
||||
"attachments": self.attachments != {} and self.attachments or None
|
||||
})
|
||||
return res
|
||||
|
||||
|
||||
class Modifier(ImapEmail):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(Modifier, self).__init__(*args, **kwargs)
|
||||
@@ -348,7 +351,7 @@ class ReplyModifier(Modifier):
|
||||
def __init__(self, mbox, mailid, request, form, **kwargs):
|
||||
super(ReplyModifier, self).__init__(
|
||||
mbox, mailid, request, dformat="EDITOR", **kwargs
|
||||
)
|
||||
)
|
||||
|
||||
self.textheader = "%s %s" % (self.From, _("wrote:"))
|
||||
|
||||
@@ -358,7 +361,7 @@ class ReplyModifier(Modifier):
|
||||
form.fields["to"].initial = self.From
|
||||
else:
|
||||
form.fields["to"].initial = self.Reply_To
|
||||
if request.GET.get("all", "0") == "1": # reply-all
|
||||
if request.GET.get("all", "0") == "1": # reply-all
|
||||
form.fields["cc"].initial = ""
|
||||
toparse = self.To.split(",")
|
||||
if hasattr(self, 'Cc'):
|
||||
@@ -386,11 +389,12 @@ class ReplyModifier(Modifier):
|
||||
body += ">%s" % l
|
||||
self.body = body
|
||||
|
||||
|
||||
class ForwardModifier(Modifier):
|
||||
def __init__(self, mbox, mailid, request, form, **kwargs):
|
||||
super(ForwardModifier, self).__init__(
|
||||
mbox, mailid, request, dformat="EDITOR", **kwargs
|
||||
)
|
||||
)
|
||||
|
||||
self._header()
|
||||
form.fields["subject"].initial = "Fwd: %s" % self.Subject
|
||||
@@ -433,6 +437,7 @@ class ForwardModifier(Modifier):
|
||||
def _header_end_html(self):
|
||||
return "</table>"
|
||||
|
||||
|
||||
class EmailSignature(object):
|
||||
"""User signature
|
||||
|
||||
@@ -459,6 +464,7 @@ class EmailSignature(object):
|
||||
def __repr__(self):
|
||||
return self._sig
|
||||
|
||||
|
||||
def decode_payload(encoding, payload):
|
||||
"""Decode the payload according to the given encoding
|
||||
|
||||
@@ -477,6 +483,7 @@ def decode_payload(encoding, payload):
|
||||
return quopri.decodestring(payload)
|
||||
return payload
|
||||
|
||||
|
||||
def find_images_in_body(body):
|
||||
"""Looks for images inside a HTML body
|
||||
|
||||
@@ -510,13 +517,14 @@ def find_images_in_body(body):
|
||||
fp.close()
|
||||
p["Content-ID"] = "<%s>" % cid
|
||||
ct = p["Content-Type"]
|
||||
p.replace_header("Content-Type", '%s; name="%s"' \
|
||||
% (ct, os.path.basename(fname)))
|
||||
p.replace_header("Content-Type", '%s; name="%s"'
|
||||
% (ct, os.path.basename(fname)))
|
||||
p["Content-Disposition"] = "inline"
|
||||
parts.append(p)
|
||||
|
||||
return lxml.html.tostring(html), parts
|
||||
|
||||
|
||||
def set_compose_session(request):
|
||||
"""Initialize a new "compose" session.
|
||||
|
||||
@@ -529,9 +537,10 @@ def set_compose_session(request):
|
||||
"""
|
||||
import uuid
|
||||
randid = str(uuid.uuid4()).replace("-", "")
|
||||
request.session["compose_mail"] = {"id" : randid, "attachments" : []}
|
||||
request.session["compose_mail"] = {"id": randid, "attachments": []}
|
||||
return randid
|
||||
|
||||
|
||||
def save_attachment(f):
|
||||
"""Save a new attachment to the filesystem.
|
||||
|
||||
@@ -554,6 +563,7 @@ def save_attachment(f):
|
||||
fp.close()
|
||||
return fp.name
|
||||
|
||||
|
||||
def clean_attachments(attlist):
|
||||
"""Remove all attachments from the filesystem
|
||||
|
||||
@@ -564,9 +574,10 @@ def clean_attachments(attlist):
|
||||
fullpath = os.path.join(settings.MEDIA_ROOT, "webmail", att["tmpname"])
|
||||
try:
|
||||
os.remove(fullpath)
|
||||
except OSError, e:
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
|
||||
def html2plaintext(content):
|
||||
"""HTML to plain text translation
|
||||
|
||||
@@ -586,8 +597,9 @@ def html2plaintext(content):
|
||||
|
||||
return plaintext
|
||||
|
||||
|
||||
def get_current_url(request):
|
||||
if not request.session.has_key("folder"):
|
||||
if not "folder" in request.session:
|
||||
return ""
|
||||
|
||||
res = "%s?page=%s" % (request.session["folder"], request.session["page"])
|
||||
@@ -596,6 +608,7 @@ def get_current_url(request):
|
||||
res += "&%s=%s" % (p, request.session[p])
|
||||
return res
|
||||
|
||||
|
||||
def create_mail_attachment(attdef):
|
||||
"""Create the MIME part corresponding to the given attachment.
|
||||
|
||||
@@ -629,7 +642,7 @@ def send_mail(request, posturl=None):
|
||||
:return: a 2-uple (True|False, HttpResponse)
|
||||
"""
|
||||
from email.mime.multipart import MIMEMultipart
|
||||
from forms import ComposeMailForm
|
||||
from .forms import ComposeMailForm
|
||||
from modoboa.lib.webutils import _render_to_string
|
||||
from modoboa.auth.lib import get_password
|
||||
|
||||
@@ -709,11 +722,12 @@ def send_mail(request, posturl=None):
|
||||
return True, dict(url=get_current_url(request))
|
||||
|
||||
listing = _render_to_string(request, "webmail/compose.html",
|
||||
{"form" : form, "noerrors" : True,
|
||||
"body" : request.POST["id_body"].strip(),
|
||||
"posturl" : posturl})
|
||||
{"form": form, "noerrors": True,
|
||||
"body": request.POST["id_body"].strip(),
|
||||
"posturl": posturl})
|
||||
return False, dict(status="ko", listing=listing, editor=editormode)
|
||||
|
||||
|
||||
class AttachmentUploadHandler(FileUploadHandler):
|
||||
"""
|
||||
Simple upload handler to limit the size of the attachments users
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
from modoboa.lib.webutils import ajax_response, _render_error
|
||||
from modoboa.extensions.webmail.exceptions import WebmailError, ImapError
|
||||
|
||||
|
||||
class WebmailErrorMiddleware(object):
|
||||
def process_exception(self, request, exception):
|
||||
if not isinstance(exception, (WebmailError, ImapError)):
|
||||
|
||||
@@ -1,25 +1,30 @@
|
||||
# coding: utf-8
|
||||
from django.conf import settings
|
||||
import os
|
||||
from django.conf import settings
|
||||
from django.http import HttpResponse
|
||||
from django.shortcuts import render
|
||||
from django.template import Template, Context
|
||||
from django.utils.translation import ugettext as _, ungettext
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
from modoboa.lib import parameters
|
||||
from modoboa.lib.webutils import _render, _render_to_string, \
|
||||
ajax_response, ajax_simple_response
|
||||
from modoboa.lib.decorators import needs_mailbox
|
||||
from exceptions import WebmailError
|
||||
from forms import FolderForm, AttachmentForm, ComposeMailForm
|
||||
from imaputils import get_imapconnector, IMAPconnector, separate_mailbox
|
||||
from lib import decode_payload, AttachmentUploadHandler, \
|
||||
save_attachment, ImapListing, EmailSignature, \
|
||||
clean_attachments, set_compose_session, send_mail, \
|
||||
from modoboa.lib.webutils import (
|
||||
_render_to_string, ajax_response, ajax_simple_response
|
||||
)
|
||||
from modoboa.extensions.admin.lib import needs_mailbox
|
||||
from .exceptions import WebmailError
|
||||
from .forms import FolderForm, AttachmentForm, ComposeMailForm
|
||||
from .imaputils import get_imapconnector, IMAPconnector, separate_mailbox
|
||||
from .lib import (
|
||||
decode_payload, AttachmentUploadHandler,
|
||||
save_attachment, ImapListing, EmailSignature,
|
||||
clean_attachments, set_compose_session, send_mail,
|
||||
ImapEmail, ReplyModifier, ForwardModifier
|
||||
)
|
||||
from templatetags import webextras
|
||||
|
||||
|
||||
@login_required
|
||||
@needs_mailbox()
|
||||
def getattachment(request):
|
||||
@@ -36,8 +41,6 @@ def getattachment(request):
|
||||
if not mbox or not mailid or not pnum:
|
||||
raise WebmailError(_("Invalid request"))
|
||||
|
||||
headers = {"Content-Type": "text/plain",
|
||||
"Content-Transfer-Encoding": None}
|
||||
imapc = get_imapconnector(request)
|
||||
partdef, payload = imapc.fetchpart(mailid, mbox, pnum)
|
||||
resp = HttpResponse(decode_payload(partdef["encoding"], payload))
|
||||
@@ -57,6 +60,7 @@ def getattachment(request):
|
||||
resp["Content-Length"] = partdef["size"]
|
||||
return resp
|
||||
|
||||
|
||||
@login_required
|
||||
@needs_mailbox()
|
||||
def move(request):
|
||||
@@ -69,6 +73,7 @@ def move(request):
|
||||
resp.update(status="ok")
|
||||
return ajax_simple_response(resp)
|
||||
|
||||
|
||||
@login_required
|
||||
@needs_mailbox()
|
||||
def delete(request):
|
||||
@@ -78,7 +83,8 @@ def delete(request):
|
||||
raise WebmailError(_("Invalid request"))
|
||||
selection = [item for item in selection if item.isdigit()]
|
||||
mbc = get_imapconnector(request)
|
||||
mbc.move(",".join(selection), mbox, parameters.get_user(request.user, "TRASH_FOLDER"))
|
||||
mbc.move(",".join(selection), mbox,
|
||||
parameters.get_user(request.user, "TRASH_FOLDER"))
|
||||
count = len(selection)
|
||||
message = ungettext("%(count)d message deleted",
|
||||
"%(count)d messages deleted",
|
||||
@@ -86,6 +92,7 @@ def delete(request):
|
||||
resp = dict(status="ok", respmsg=message)
|
||||
return ajax_simple_response(resp)
|
||||
|
||||
|
||||
@login_required
|
||||
@needs_mailbox()
|
||||
def mark(request, name):
|
||||
@@ -102,7 +109,8 @@ def mark(request, name):
|
||||
return ajax_simple_response(
|
||||
dict(status="ok", action=status, mbox=name,
|
||||
unseen=imapc.unseen_messages(name))
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@login_required
|
||||
@needs_mailbox()
|
||||
@@ -112,8 +120,9 @@ def empty(request, name):
|
||||
get_imapconnector(request).empty(name)
|
||||
content = "<div class='alert alert-info'>%s</div>" % _("Empty mailbox")
|
||||
return ajax_simple_response(dict(
|
||||
status="ok", listing=content, mailbox=name
|
||||
))
|
||||
status="ok", listing=content, mailbox=name
|
||||
))
|
||||
|
||||
|
||||
@login_required
|
||||
@needs_mailbox()
|
||||
@@ -122,6 +131,7 @@ def compact(request, name):
|
||||
imapc.compact(name)
|
||||
return ajax_simple_response(dict(status="ok"))
|
||||
|
||||
|
||||
@login_required
|
||||
@needs_mailbox()
|
||||
def newfolder(request, tplname="webmail/folder.html"):
|
||||
@@ -142,8 +152,9 @@ def newfolder(request, tplname="webmail/folder.html"):
|
||||
pf = request.POST.get("parent_folder", None)
|
||||
mbc.create_folder(form.cleaned_data["name"], pf)
|
||||
return ajax_simple_response(dict(
|
||||
status="ok", respmsg=_("Mailbox created"), newmb=form.cleaned_data["name"], parent=pf
|
||||
))
|
||||
status="ok", respmsg=_("Mailbox created"),
|
||||
newmb=form.cleaned_data["name"], parent=pf
|
||||
))
|
||||
|
||||
ctx["form"] = form
|
||||
ctx["selected"] = None
|
||||
@@ -151,20 +162,21 @@ def newfolder(request, tplname="webmail/folder.html"):
|
||||
|
||||
ctx["form"] = FolderForm()
|
||||
ctx["selected"] = None
|
||||
return _render(request, tplname, ctx)
|
||||
return render(request, tplname, ctx)
|
||||
|
||||
|
||||
@login_required
|
||||
@needs_mailbox()
|
||||
def editfolder(request, tplname="webmail/folder.html"):
|
||||
mbc = IMAPconnector(user=request.user.username,
|
||||
password=request.session["password"])
|
||||
ctx = {"title" : _("Edit mailbox"),
|
||||
"formid" : "mboxform",
|
||||
"action" : reverse(editfolder),
|
||||
"action_label" : _("Update"),
|
||||
"action_classes" : "submit",
|
||||
"withunseen" : False,
|
||||
"selectonly" : True}
|
||||
ctx = {"title": _("Edit mailbox"),
|
||||
"formid": "mboxform",
|
||||
"action": reverse(editfolder),
|
||||
"action_label": _("Update"),
|
||||
"action_classes": "submit",
|
||||
"withunseen": False,
|
||||
"selectonly": True}
|
||||
|
||||
if request.method == "POST":
|
||||
form = FolderForm(request.POST)
|
||||
@@ -199,7 +211,8 @@ def editfolder(request, tplname="webmail/folder.html"):
|
||||
ctx["form"].fields["oldname"].initial = name
|
||||
ctx["form"].fields["name"].initial = shortname
|
||||
ctx["selected"] = parent
|
||||
return _render(request, tplname, ctx)
|
||||
return render(request, tplname, ctx)
|
||||
|
||||
|
||||
@login_required
|
||||
@needs_mailbox()
|
||||
@@ -210,10 +223,11 @@ def delfolder(request):
|
||||
mbc = IMAPconnector(user=request.user.username,
|
||||
password=request.session["password"])
|
||||
mbc.delete_folder(name)
|
||||
if request.session.has_key("mbox"):
|
||||
if "mbox" in request.session:
|
||||
del request.session["mbox"]
|
||||
return ajax_response(request)
|
||||
|
||||
|
||||
@login_required
|
||||
@csrf_exempt
|
||||
@needs_mailbox()
|
||||
@@ -228,40 +242,41 @@ def attachments(request, tplname="webmail/attachments.html"):
|
||||
fobj = request.FILES["attachment"]
|
||||
tmpname = save_attachment(fobj)
|
||||
request.session["compose_mail"]["attachments"] \
|
||||
+= [{"fname" : str(fobj),
|
||||
"content-type" : fobj.content_type,
|
||||
"size" : fobj.size,
|
||||
"tmpname" : os.path.basename(tmpname)}]
|
||||
+= [{"fname": str(fobj),
|
||||
"content-type": fobj.content_type,
|
||||
"size": fobj.size,
|
||||
"tmpname": os.path.basename(tmpname)}]
|
||||
request.session.modified = True
|
||||
return _render(request, "webmail/upload_done.html", {
|
||||
"status" : "ok", "fname" : request.FILES["attachment"],
|
||||
"tmpname" : os.path.basename(tmpname)
|
||||
})
|
||||
return render(request, "webmail/upload_done.html", {
|
||||
"status": "ok", "fname": request.FILES["attachment"],
|
||||
"tmpname": os.path.basename(tmpname)
|
||||
})
|
||||
except WebmailError, inst:
|
||||
error = _("Failed to save attachment: ") + str(inst)
|
||||
|
||||
if csuploader.toobig:
|
||||
error = _("Attachment is too big (limit: %s)" \
|
||||
% parameters.get_admin("MAX_ATTACHMENT_SIZE"))
|
||||
return _render(request, "webmail/upload_done.html", {
|
||||
"status" : "ko", "error" : error
|
||||
})
|
||||
error = _("Attachment is too big (limit: %s)"
|
||||
% parameters.get_admin("MAX_ATTACHMENT_SIZE"))
|
||||
return render(request, "webmail/upload_done.html", {
|
||||
"status": "ko", "error": error
|
||||
})
|
||||
ctx = {
|
||||
"title" : _("Attachments"),
|
||||
"formid" : "uploadfile",
|
||||
"target" : "upload_target",
|
||||
"enctype" : "multipart/form-data",
|
||||
"form" : AttachmentForm(),
|
||||
"action" : reverse(attachments),
|
||||
"attachments" : request.session["compose_mail"]["attachments"]
|
||||
}
|
||||
return _render(request, tplname, ctx)
|
||||
"title": _("Attachments"),
|
||||
"formid": "uploadfile",
|
||||
"target": "upload_target",
|
||||
"enctype": "multipart/form-data",
|
||||
"form": AttachmentForm(),
|
||||
"action": reverse(attachments),
|
||||
"attachments": request.session["compose_mail"]["attachments"]
|
||||
}
|
||||
return render(request, tplname, ctx)
|
||||
|
||||
|
||||
@login_required
|
||||
@needs_mailbox()
|
||||
def delattachment(request):
|
||||
if not request.session.has_key("compose_mail") \
|
||||
or not request.GET.has_key("name") \
|
||||
if not "compose_mail" in request.session \
|
||||
or not "name" in request.GET \
|
||||
or not request.GET["name"]:
|
||||
return ajax_response(request, "ko", respmsg=_("Bad query"))
|
||||
|
||||
@@ -269,7 +284,9 @@ def delattachment(request):
|
||||
for att in request.session["compose_mail"]["attachments"]:
|
||||
if att["tmpname"] == request.GET["name"]:
|
||||
request.session["compose_mail"]["attachments"].remove(att)
|
||||
fullpath = os.path.join(settings.MEDIA_ROOT, "webmail", att["tmpname"])
|
||||
fullpath = os.path.join(
|
||||
settings.MEDIA_ROOT, "webmail", att["tmpname"]
|
||||
)
|
||||
try:
|
||||
os.remove(fullpath)
|
||||
except OSError, e:
|
||||
@@ -281,6 +298,7 @@ def delattachment(request):
|
||||
error = _("Unknown attachment")
|
||||
return ajax_response(request, "ko", respmsg=error)
|
||||
|
||||
|
||||
def render_mboxes_list(request, imapc):
|
||||
"""Return the HTML representation of a mailboxes list
|
||||
|
||||
@@ -290,19 +308,20 @@ def render_mboxes_list(request, imapc):
|
||||
"""
|
||||
curmbox = request.session.get("mbox", "INBOX")
|
||||
return _render_to_string(request, "webmail/folders.html", {
|
||||
"selected" : curmbox,
|
||||
"mboxes" : imapc.getmboxes(request.user),
|
||||
"withunseen" : True
|
||||
})
|
||||
"selected": curmbox,
|
||||
"mboxes": imapc.getmboxes(request.user),
|
||||
"withunseen": True
|
||||
})
|
||||
|
||||
|
||||
def set_nav_params(request):
|
||||
if not request.session.has_key("navparams"):
|
||||
if not "navparams" in request.session:
|
||||
request.session["navparams"] = {}
|
||||
|
||||
request.session["pageid"] = \
|
||||
int(request.GET.get("page", 1))
|
||||
if not request.GET.get("order", False):
|
||||
if not request.session["navparams"].has_key("order"):
|
||||
if not "order" in request.session["navparams"]:
|
||||
request.session["navparams"]["order"] = "-date"
|
||||
else:
|
||||
request.session["navparams"]["order"] = request.GET.get("order")
|
||||
@@ -310,9 +329,10 @@ def set_nav_params(request):
|
||||
for p in ["pattern", "criteria"]:
|
||||
if request.GET.get(p, False):
|
||||
request.session["navparams"][p] = request.GET[p]
|
||||
elif request.session["navparams"].has_key(p):
|
||||
elif p in request.session["navparams"]:
|
||||
del request.session["navparams"][p]
|
||||
|
||||
|
||||
def listmailbox(request, defmailbox="INBOX", update_session=True):
|
||||
"""Mailbox content listing
|
||||
|
||||
@@ -329,13 +349,16 @@ def listmailbox(request, defmailbox="INBOX", update_session=True):
|
||||
set_nav_params(request)
|
||||
request.session["mbox"] = mbox
|
||||
|
||||
lst = ImapListing(request.user, request.session["password"],
|
||||
baseurl="?action=listmailbox&mbox=%s&" % mbox,
|
||||
folder=mbox,
|
||||
elems_per_page=int(parameters.get_user(request.user, "MESSAGES_PER_PAGE")),
|
||||
**request.session["navparams"])
|
||||
lst = ImapListing(
|
||||
request.user, request.session["password"],
|
||||
baseurl="?action=listmailbox&mbox=%s&" % mbox,
|
||||
folder=mbox,
|
||||
elems_per_page=int(parameters.get_user(request.user, "MESSAGES_PER_PAGE")),
|
||||
**request.session["navparams"]
|
||||
)
|
||||
return lst.render(request, request.session["pageid"])
|
||||
|
||||
|
||||
def render_compose(request, form, posturl, email=None, insert_signature=False):
|
||||
editor = parameters.get_user(request.user, "EDITOR")
|
||||
if email is None:
|
||||
@@ -348,11 +371,11 @@ def render_compose(request, form, posturl, email=None, insert_signature=False):
|
||||
signature = EmailSignature(request.user)
|
||||
body += str(signature)
|
||||
randid = None
|
||||
if not request.GET.has_key("id"):
|
||||
if request.session.has_key("compose_mail"):
|
||||
if not "id" in request.GET:
|
||||
if "compose_mail" in request.session:
|
||||
clean_attachments(request.session["compose_mail"]["attachments"])
|
||||
randid = set_compose_session(request)
|
||||
elif not request.session.has_key("compose_mail") \
|
||||
elif not "compose_mail" in request.session \
|
||||
or request.session["compose_mail"]["id"] != request.GET["id"]:
|
||||
randid = set_compose_session(request)
|
||||
|
||||
@@ -360,21 +383,22 @@ def render_compose(request, form, posturl, email=None, insert_signature=False):
|
||||
if len(attachments):
|
||||
short_att_list = "(%s)" \
|
||||
% ", ".join(map(lambda att: att["fname"],
|
||||
attachments[:2] + [{"fname" : "..."}] \
|
||||
attachments[:2] + [{"fname": "..."}] \
|
||||
if len(attachments) > 2 else attachments))
|
||||
else:
|
||||
short_att_list = ""
|
||||
content = _render_to_string(request, "webmail/compose.html", {
|
||||
"form" : form, "bodyheader" : textheader,
|
||||
"body" : body, "posturl" : posturl,
|
||||
"attachments" : attachments, "short_att_list" : short_att_list
|
||||
})
|
||||
"form": form, "bodyheader": textheader,
|
||||
"body": body, "posturl": posturl,
|
||||
"attachments": attachments, "short_att_list": short_att_list
|
||||
})
|
||||
|
||||
ctx = dict(listing=content, editor=editor)
|
||||
if randid is not None:
|
||||
ctx["id"] = randid
|
||||
return ctx
|
||||
|
||||
|
||||
def compose(request):
|
||||
url = "?action=compose"
|
||||
if request.method == "POST":
|
||||
@@ -384,6 +408,7 @@ def compose(request):
|
||||
form = ComposeMailForm()
|
||||
return render_compose(request, form, url, insert_signature=True)
|
||||
|
||||
|
||||
def compose_and_send(request, action, callback=None):
|
||||
mbox = request.GET.get("mbox", None)
|
||||
mailid = request.GET.get("mailid", None)
|
||||
@@ -401,30 +426,35 @@ def compose_and_send(request, action, callback=None):
|
||||
email = modclass(mbox, mailid, request, form, addrfull=True, links="1")
|
||||
return render_compose(request, form, url, email)
|
||||
|
||||
|
||||
def reply(request):
|
||||
def msg_replied(mbox, mailid):
|
||||
get_imapconnector(request).msg_answered(mbox, mailid)
|
||||
|
||||
return compose_and_send(request, "reply", msg_replied)
|
||||
|
||||
|
||||
def forward(request):
|
||||
def msg_forwarded(mbox, mailid):
|
||||
get_imapconnector(request).msg_forwarded(mbox, mailid)
|
||||
|
||||
return compose_and_send(request, "forward", msg_forwarded)
|
||||
|
||||
|
||||
@login_required
|
||||
@needs_mailbox()
|
||||
def getmailcontent(request):
|
||||
mbox = request.GET.get("mbox", None)
|
||||
mailid = request.GET.get("mailid", None)
|
||||
if mbox is None or mailid is None:
|
||||
raise WebmailError(_("Invalid request"))
|
||||
raise WebmailError(_("Invalid request"))
|
||||
email = ImapEmail(mbox, mailid, request, links=int(request.GET["links"]))
|
||||
return _render(request, "common/viewmail.html", {
|
||||
"headers" : email.render_headers(folder=mbox, mail_id=mailid),
|
||||
"folder" : mbox, "imapid" : mailid, "mailbody" : email.body if email.body else ""
|
||||
})
|
||||
return render(request, "common/viewmail.html", {
|
||||
"headers": email.render_headers(folder=mbox, mail_id=mailid),
|
||||
"folder": mbox, "imapid": mailid,
|
||||
"mailbody": email.body if email.body else ""
|
||||
})
|
||||
|
||||
|
||||
def viewmail(request):
|
||||
mbox = request.GET.get("mbox", None)
|
||||
@@ -441,10 +471,11 @@ def viewmail(request):
|
||||
(mbox, mailid, links)
|
||||
content = Template("""
|
||||
<iframe src="{{ url }}" id="mailcontent"></iframe>
|
||||
""").render(Context({"url" : url}))
|
||||
""").render(Context({"url": url}))
|
||||
|
||||
return dict(listing=content, menuargs=dict(mail_id=mailid))
|
||||
|
||||
|
||||
@login_required
|
||||
@needs_mailbox()
|
||||
def submailboxes(request):
|
||||
@@ -452,6 +483,7 @@ def submailboxes(request):
|
||||
mboxes = get_imapconnector(request).getmboxes(request.user, topmailbox)
|
||||
return ajax_simple_response(dict(status="ok", mboxes=mboxes))
|
||||
|
||||
|
||||
@login_required
|
||||
@needs_mailbox()
|
||||
def check_unseen_messages(request):
|
||||
@@ -465,6 +497,7 @@ def check_unseen_messages(request):
|
||||
counters[mb] = imapc.unseen_messages(mb)
|
||||
return ajax_simple_response(dict(status="ok", counters=counters))
|
||||
|
||||
|
||||
@login_required
|
||||
@needs_mailbox()
|
||||
def index(request):
|
||||
@@ -492,7 +525,7 @@ def index(request):
|
||||
action = request.GET.get("action", None)
|
||||
|
||||
if action is not None:
|
||||
if not globals().has_key(action):
|
||||
if not action in globals():
|
||||
raise WebmailError(_("Undefined action"))
|
||||
response = globals()[action](request)
|
||||
else:
|
||||
@@ -517,13 +550,13 @@ def index(request):
|
||||
trash,
|
||||
parameters.get_user(request.user, "DRAFTS_FOLDER")
|
||||
]
|
||||
return _render(request, "webmail/index.html", response)
|
||||
return render(request, "webmail/index.html", response)
|
||||
|
||||
if action in ["reply", "forward"]:
|
||||
action = "compose"
|
||||
if request.session["lastaction"] != action:
|
||||
extra_args = {}
|
||||
if response.has_key("menuargs"):
|
||||
if "menuargs" in response:
|
||||
extra_args = response["menuargs"]
|
||||
del response["menuargs"]
|
||||
try:
|
||||
@@ -533,6 +566,6 @@ def index(request):
|
||||
pass
|
||||
|
||||
response.update(callback=action)
|
||||
if not response.has_key("status"):
|
||||
if not "status" in response:
|
||||
response.update(status="ok")
|
||||
return ajax_simple_response(response)
|
||||
|
||||
Reference in New Issue
Block a user