mirror of
https://github.com/jwinterm/modoboa.git
synced 2026-10-17 05:41:25 +02:00
wip: UI refactoring
* Possibility to dowload message attachemnts see #204
This commit is contained in:
@@ -93,6 +93,7 @@ class BodyStructure(object):
|
||||
self.inlines[params["cid"].strip("<>")] = params
|
||||
return
|
||||
|
||||
params["Content-Type"] = ftype
|
||||
if len(definition) > 7:
|
||||
extensions = ["md5", "disposition", "language", "location"]
|
||||
if mtype == "text":
|
||||
@@ -124,6 +125,12 @@ class BodyStructure(object):
|
||||
def has_attachments(self):
|
||||
return len(self.attachments)
|
||||
|
||||
def find_attachment(self, pnum):
|
||||
for att in self.attachments:
|
||||
if pnum == att["pnum"]:
|
||||
return att
|
||||
return None
|
||||
|
||||
class IMAPconnector(object):
|
||||
__metaclass__ = ConnectionsManager
|
||||
|
||||
@@ -551,9 +558,21 @@ class IMAPconnector(object):
|
||||
self.quota_actual = int(m.group(1))
|
||||
|
||||
def fetchpart(self, uid, mbox, partnum):
|
||||
"""Retrieve a specific message part
|
||||
|
||||
Useful to fetch attachments from the server. Part headers and
|
||||
the payload are returned separatly.
|
||||
|
||||
:param uid: a message UID
|
||||
:param mbox: the mailbox containing the message
|
||||
:param partnum: the part number
|
||||
:return: a 2uple (dict, string)
|
||||
"""
|
||||
self.select_mailbox(mbox, False)
|
||||
data = self._cmd("FETCH", uid, "(BODY[%s])" % partnum)
|
||||
return data[int(uid)]["BODY[%s]" % partnum]
|
||||
data = self._cmd("FETCH", uid, "(BODYSTRUCTURE BODY[%s])" % partnum)
|
||||
bs = BodyStructure(data[int(uid)]["BODYSTRUCTURE"])
|
||||
attdef = bs.find_attachment(partnum)
|
||||
return attdef, data[int(uid)]["BODY[%s]" % partnum]
|
||||
|
||||
def fetch(self, start, stop=None, mbox=None, **kwargs):
|
||||
"""Retrieve information about messages from the server
|
||||
@@ -604,7 +623,6 @@ class IMAPconnector(object):
|
||||
:param readonly:
|
||||
:param extraheaders:
|
||||
"""
|
||||
print mbox
|
||||
self.select_mailbox(mbox, readonly)
|
||||
if headers is None:
|
||||
headers = ['DATE', 'FROM', 'TO', 'CC', 'SUBJECT']
|
||||
|
||||
+23
-14
@@ -182,8 +182,8 @@ class ImapEmail(Email):
|
||||
|
||||
pnum = self.bs.contents[mformat]['pnum']
|
||||
data = self.imapc._cmd("FETCH", mailid, "(BODY.PEEK[%s])" % pnum)
|
||||
content = self._decode_content(self.bs.contents[mformat]['encoding'],
|
||||
data[int(mailid)]['BODY[%s]' % pnum])
|
||||
content = decode_payload(self.bs.contents[mformat]['encoding'],
|
||||
data[int(mailid)]['BODY[%s]' % pnum])
|
||||
charset = self._find_content_charset(mformat)
|
||||
if charset is not None:
|
||||
content = content.decode(charset)
|
||||
@@ -229,16 +229,6 @@ class ImapEmail(Email):
|
||||
return self.bs.contents[subtype]["params"][pos + 1]
|
||||
return None
|
||||
|
||||
def _decode_content(self, encoding, content):
|
||||
encoding = encoding.lower()
|
||||
if encoding == "base64":
|
||||
import base64
|
||||
return base64.b64decode(content)
|
||||
elif encoding == "quoted-printable":
|
||||
import quopri
|
||||
return quopri.decodestring(content)
|
||||
return content
|
||||
|
||||
def _find_attachments(self):
|
||||
for att in self.bs.attachments:
|
||||
attname = "part_%s" % att["pnum"]
|
||||
@@ -270,9 +260,9 @@ class ImapEmail(Email):
|
||||
if os.path.exists(path):
|
||||
continue
|
||||
|
||||
content = self.imapc.fetchpart(self.mailid, self.mbox, params["pnum"])
|
||||
pdef, content = self.imapc.fetchpart(self.mailid, self.mbox, params["pnum"])
|
||||
fp = open(path, "wb")
|
||||
fp.write(self._decode_content(params["encoding"], content))
|
||||
fp.write(decode_payload(params["encoding"], content))
|
||||
fp.close()
|
||||
|
||||
|
||||
@@ -428,6 +418,25 @@ class EmailSignature(object):
|
||||
def __repr__(self):
|
||||
return self._sig
|
||||
|
||||
def decode_payload(encoding, payload):
|
||||
"""Decode the payload according to the given encoding
|
||||
|
||||
Supported encodings: base64, quoted-printable.
|
||||
|
||||
:param encoding: the encoding's name
|
||||
:param payload: the value to decode
|
||||
:return: a string
|
||||
"""
|
||||
print payload
|
||||
encoding = encoding.lower()
|
||||
if encoding == "base64":
|
||||
import base64
|
||||
return base64.b64decode(payload)
|
||||
elif encoding == "quoted-printable":
|
||||
import quopri
|
||||
return quopri.decodestring(payload)
|
||||
return payload
|
||||
|
||||
def find_images_in_body(body):
|
||||
"""Looks for images inside a HTML body
|
||||
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
{% extends "common/mailheaders.html" %}
|
||||
{% load i18n %}
|
||||
{% extends "common/mailheaders.html" %}{% load i18n %}
|
||||
{% block extraheaders %}
|
||||
{% if attachments %}
|
||||
<tr>
|
||||
<th>{% trans "Attachments" %}</th>
|
||||
<td>
|
||||
{% url modoboa.extensions.webmail.views.getattachment folder mail_id as test %}
|
||||
<td>{% url modoboa.extensions.webmail.views.getattachment as url %}
|
||||
{% for key, fname in attachments.items %}
|
||||
<a href="{{ test }}?fname={{ fname }}&partnumber={{ key }}">{{ fname }}</a>
|
||||
<a href="{{ url }}?mbox={{ folder }}&mailid={{ mail_id }}&fname={{ fname }}&partnumber={{ key }}">{{ fname }}</a>
|
||||
{% endfor %}
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -18,6 +18,5 @@ urlpatterns = patterns(
|
||||
|
||||
(r'^attachments/$', 'attachments'),
|
||||
(r'^delattachment/$', 'delattachment'),
|
||||
|
||||
(r'^(?P<folder>.+)/(?P<mail_id>[\w\-\+]+)/part/$', 'getattachment'),
|
||||
(r'^getattachment/$', 'getattachment'),
|
||||
)
|
||||
|
||||
+19
-33
@@ -16,49 +16,35 @@ from templatetags import webextras
|
||||
|
||||
@login_required
|
||||
@needs_mailbox()
|
||||
def getattachment(request, folder, mail_id):
|
||||
def getattachment(request):
|
||||
"""Fetch a message attachment
|
||||
|
||||
Problème lié aux optimisations apportées : la méthode fetchpart du
|
||||
client IMAP ne renvoie que le payload. Pour obtenir les headers,
|
||||
deux solutions :
|
||||
FIXME: par manque de caching, le bodystructure du message est
|
||||
redemandé pour accéder aux headers de cette pièce jointe.
|
||||
|
||||
* Conserver le bodystructure parsé précédemment (nécessite du caching)
|
||||
|
||||
* Reparser le bodystructure :p
|
||||
:param request: a ``Request`` object
|
||||
:param folder
|
||||
"""
|
||||
mbox = request.GET.get("mbox", None)
|
||||
mailid = request.GET.get("mailid", None)
|
||||
pnum = request.GET.get("partnumber", None)
|
||||
if not pnum:
|
||||
raise Http404
|
||||
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)
|
||||
part = imapc.fetchpart(mail_id, folder, pnum)
|
||||
if part is None:
|
||||
raise Http404
|
||||
if part.get_content_maintype() == "message":
|
||||
payload = part.get_payload(0)
|
||||
partdef, payload = imapc.fetchpart(mailid, mbox, pnum)
|
||||
resp = HttpResponse(decode_payload(partdef["encoding"], payload))
|
||||
resp["Content-Type"] = partdef["Content-Type"]
|
||||
resp["Content-Transfer-Encoding"] = partdef["encoding"]
|
||||
if partdef["disposition"] != 'NIL':
|
||||
disp = partdef["disposition"]
|
||||
cd = "%s; %s=%s" % (disp[0], disp[1][0], disp[1][1])
|
||||
else:
|
||||
payload = part.get_payload(decode=True)
|
||||
resp = HttpResponse(payload)
|
||||
for hdr, default in headers.iteritems():
|
||||
if not part.has_key(hdr):
|
||||
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 I 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" % request.GET["fname"]
|
||||
resp["Content-Length"] = len(payload)
|
||||
cd = "attachment; filename=%s" % request.GET["fname"]
|
||||
resp["Content-Disposition"] = cd
|
||||
resp["Content-Length"] = partdef["size"]
|
||||
return resp
|
||||
|
||||
@login_required
|
||||
|
||||
Reference in New Issue
Block a user