From 309fb25e9b089618c37f1a741fa6009cce54ac9e Mon Sep 17 00:00:00 2001 From: Anton Khirnov Date: Sat, 30 Jan 2021 16:33:56 +0100 Subject: db/attachment: simplify the Attachment class Make it a plain container around raw data and a few bits of metadata, rather than around a whole MIME part. --- alot/mail/envelope.py | 109 ++++++++++++++------------------------------------ 1 file changed, 30 insertions(+), 79 deletions(-) (limited to 'alot/mail') diff --git a/alot/mail/envelope.py b/alot/mail/envelope.py index 5a798f19..540c0342 100644 --- a/alot/mail/envelope.py +++ b/alot/mail/envelope.py @@ -7,12 +7,7 @@ import os import re import email import email.policy -from email.encoders import encode_7or8bit from email.message import MIMEPart -from email.mime.audio import MIMEAudio -from email.mime.base import MIMEBase -from email.mime.image import MIMEImage -from email.mime.text import MIMEText import email.charset as charset from urllib.parse import unquote @@ -85,57 +80,6 @@ def _guess_encoding(blob): else: raise Exception('Unknown magic API') -# TODO: make this work on blobs, not paths -def _mimewrap(path, filename, ctype): - """Take the contents of the given path and wrap them into an email MIME - part according to the content type. The content type is auto detected from - the actual file contents and the file name if it is not given. - - :param path: the path to the file contents - :type path: str - :param filename: the file name to use in the generated MIME part - :type filename: str or None - :param ctype: the content type of the file contents in path - :type ctype: str or None - :returns: the message MIME part storing the data from path - :rtype: subclasses of email.mime.base.MIMEBase - """ - - with open(path, 'rb') as f: - content = f.read() - if not ctype: - ctype = helper.guess_mimetype(content) - # libmagic < 5.12 incorrectly detects excel/powerpoint files as - # 'application/msword' (see #179 and #186 in libmagic bugtracker) - # This is a workaround, based on file extension, useful as long - # as distributions still ship libmagic 5.11. - if (ctype == 'application/msword' and - not _libmagic_version_at_least(513)): - mimetype, _ = mimetypes.guess_type(path) - if mimetype: - ctype = mimetype - - maintype, subtype = ctype.split('/', 1) - if maintype == 'text': - part = MIMEText(content.decode(_guess_encoding(content), 'replace'), - _subtype=subtype, - _charset='utf-8') - elif maintype == 'image': - part = MIMEImage(content, _subtype=subtype) - elif maintype == 'audio': - part = MIMEAudio(content, _subtype=subtype) - else: - part = MIMEBase(maintype, subtype) - part.set_payload(content) - # Encode the payload using Base64 - email.encoders.encode_base64(part) - # Set the filename parameter - if not filename: - filename = os.path.basename(path) - part.add_header('Content-Disposition', 'attachment', - filename=filename) - return part - class Envelope: """ a message that is not yet sent and still editable. @@ -263,28 +207,37 @@ class Envelope: if self.sent_time: self.modified_since_sent = True - def attach(self, attachment, filename=None, ctype=None): + def attach_file(self, path, filename = None): + with open(path, 'rb') as f: + data = f.read() + + ctype = helper.guess_mimetype(data) + # libmagic < 5.12 incorrectly detects excel/powerpoint files as + # 'application/msword' (see #179 and #186 in libmagic bugtracker) + # This is a workaround, based on file extension, useful as long + # as distributions still ship libmagic 5.11. + if (ctype == 'application/msword' and + not _libmagic_version_at_least(513)): + mimetype, _ = mimetypes.guess_type(path) + if mimetype: + ctype = mimetype + + # Set the filename parameter + if not filename: + filename = os.path.basename(path) + + attachment = Attachment(data, ctype, filename, ()) + self.attach(attachment) + + def attach(self, attachment): """ attach a file :param attachment: File to attach, given as - :class:`~alot.db.attachment.Attachment` object or path to a file. - :type attachment: :class:`~alot.db.attachment.Attachment` or str - :param filename: filename to use in content-disposition. - Will be ignored if `path` matches multiple files - :param ctype: force content-type to be used for this attachment - :type ctype: str + :class:`~alot.db.attachment.Attachment` object + :type attachment: :class:`~alot.db.attachment.Attachment` """ - - if isinstance(attachment, Attachment): - self.attachments.append(attachment) - elif isinstance(attachment, str): - path = os.path.expanduser(attachment) - part = _mimewrap(path, filename, ctype) - self.attachments.append(Attachment(part)) - else: - raise TypeError('attach accepts an Attachment or str') - + self.attachments.append(attachment) if self.sent_time: self.modified_since_sent = True @@ -305,11 +258,9 @@ class Envelope: # add attachments for a in self.attachments: - maintype, _, subtype = a.get_content_type().partition('/') - fname = a.get_filename() - data = a.get_data() - mail.add_attachment(data, filename = fname, - maintype = maintype, subtype = subtype) + mail.add_attachment(a.data, filename = a.filename, + maintype = a.content_maintype, + subtype = a.content_subtype) if self.sign: to_sign = mail @@ -456,7 +407,7 @@ class Envelope: if os.path.isfile(g)] logging.debug('Attaching: %s', to_attach) for path in to_attach: - self.attach(path) + self.attach_file(path) del self['Attach'] self.body = raw[headerEndPos:].strip() -- cgit v1.2.3