summaryrefslogtreecommitdiff
path: root/alot/db
diff options
context:
space:
mode:
authorAnton Khirnov <anton@khirnov.net>2021-01-26 12:30:28 +0100
committerAnton Khirnov <anton@khirnov.net>2021-01-26 12:30:28 +0100
commitfe1450cba0cc808dd843f327c4383930a78effca (patch)
treea3ca9b5bc89d7af3d93213d02f7384cb8c12b4d7 /alot/db
parente688d4876a761f9c89e427e9c451fd2968123909 (diff)
helper: move mimewrap() to the only place where it is called
Diffstat (limited to 'alot/db')
-rw-r--r--alot/db/envelope.py113
1 files changed, 112 insertions, 1 deletions
diff --git a/alot/db/envelope.py b/alot/db/envelope.py
index d5b84b3e..8340ba4e 100644
--- a/alot/db/envelope.py
+++ b/alot/db/envelope.py
@@ -8,6 +8,9 @@ import re
import email
import email.policy
from email.encoders import encode_7or8bit
+from email.mime.audio import MIMEAudio
+from email.mime.base import MIMEBase
+from email.mime.image import MIMEImage
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email.mime.application import MIMEApplication
@@ -15,6 +18,7 @@ import email.charset as charset
from urllib.parse import unquote
import gpg
+import magic
from .attachment import Attachment
from .. import __version__
@@ -25,6 +29,113 @@ from ..errors import GPGProblem, GPGCode
charset.add_charset('utf-8', charset.QP, charset.QP, 'utf-8')
+def _libmagic_version_at_least(version):
+ """
+ checks if the libmagic library installed is more recent than a given
+ version.
+
+ :param version: minimum version expected in the form XYY (i.e. 5.14 -> 514)
+ with XYY >= 513
+ """
+ if hasattr(magic, 'open'):
+ magic_wrapper = magic._libraries['magic']
+ elif hasattr(magic, 'from_buffer'):
+ magic_wrapper = magic.libmagic
+ else:
+ raise Exception('Unknown magic API')
+
+ if not hasattr(magic_wrapper, 'magic_version'):
+ # The magic_version function has been introduced in libmagic 5.13,
+ # if it's not present, we can't guess right, so let's assume False
+ return False
+
+ # Depending on the libmagic/ctypes version, magic_version is a function or
+ # a callable:
+ if callable(magic_wrapper.magic_version):
+ return magic_wrapper.magic_version() >= version
+
+ return magic_wrapper.magic_version >= version
+
+def _guess_encoding(blob):
+ """
+ uses file magic to determine the encoding of the given data blob.
+
+ :param blob: file content as read by file.read()
+ :type blob: data
+ :returns: encoding
+ :rtype: str
+ """
+ # this is a bit of a hack to support different versions of python magic.
+ # Hopefully at some point this will no longer be necessary
+ #
+ # the version with open() is the bindings shipped with the file source from
+ # http://darwinsys.com/file/ - this is what is used by the python-magic
+ # package on Debian/Ubuntu. However it is not available on pypi/via pip.
+ #
+ # the version with from_buffer() is available at
+ # https://github.com/ahupp/python-magic and directly installable via pip.
+ #
+ # for more detail see https://github.com/pazz/alot/pull/588
+ if hasattr(magic, 'open'):
+ m = magic.open(magic.MAGIC_MIME_ENCODING)
+ m.load()
+ return m.buffer(blob)
+ elif hasattr(magic, 'from_buffer'):
+ m = magic.Magic(mime_encoding=True)
+ return m.from_buffer(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:
"""
@@ -170,7 +281,7 @@ class Envelope:
self.attachments.append(attachment)
elif isinstance(attachment, str):
path = os.path.expanduser(attachment)
- part = helper.mimewrap(path, filename, ctype)
+ part = _mimewrap(path, filename, ctype)
self.attachments.append(Attachment(part))
else:
raise TypeError('attach accepts an Attachment or str')