summaryrefslogtreecommitdiff
path: root/alot/utils
diff options
context:
space:
mode:
authorAnton Khirnov <anton@khirnov.net>2021-01-26 10:26:27 +0100
committerAnton Khirnov <anton@khirnov.net>2021-01-26 10:29:24 +0100
commit5cb88d559ae462dad80d346d44f12dcc9eb3ec10 (patch)
treed6971568c4f026be6ccdf6d93042c5d0c2770acc /alot/utils
parent6a22c9f8026f255c5e6eed5916168552138581e2 (diff)
Rewrite mailcap handling.
Add a class that encapsulates the handler and is responsible for managing the temporary file, if one is needed. Use this class for both rendering inline content and displaying attachments externally. External attachments are now wrapped in an asyncio task that is added to a pool of tasks managed by ui.
Diffstat (limited to 'alot/utils')
-rw-r--r--alot/utils/mailcap.py111
1 files changed, 111 insertions, 0 deletions
diff --git a/alot/utils/mailcap.py b/alot/utils/mailcap.py
new file mode 100644
index 00000000..31a50d70
--- /dev/null
+++ b/alot/utils/mailcap.py
@@ -0,0 +1,111 @@
+# Copyright (C) 2011-2012 Patrick Totzke <patricktotzke@gmail.com>
+# Copyright (C) 2021 Anton Khirnov <anton@khirnov.net>
+# This file is released under the GNU GPL, version 3 or a later revision.
+# For further details see the COPYING file
+
+import mailcap
+from tempfile import NamedTemporaryFile
+
+from urwid.util import detected_encoding
+
+from ..settings.const import settings
+
+def _parse_nametemplate(template):
+ """this returns a prefix and suffix to be used
+ in the tempfile module for a given mailcap nametemplate string"""
+ nt_list = template.split('%s')
+ template_prefix = ''
+ template_suffix = ''
+ if len(nt_list) == 2:
+ template_suffix = nt_list[1]
+ template_prefix = nt_list[0]
+ else:
+ template_suffix = template
+ return (template_prefix, template_suffix)
+
+class MailcapHandler:
+ """
+ A handler for externally processing a MIME part with given payload and
+ content-type ctype. Must be used as a context manager, since it may create a
+ temporary file that needs to be deleted.
+
+ If the handler requires a file, then this function will create a temporary
+ file and write the payload into it. Otherwise, the payload needs to be
+ provided on stdin.
+ """
+
+ _entry = None
+
+ _payload = None
+ _ctype = None
+ _params = None
+
+ _need_tmpfile = None
+ _tmpfile = None
+
+ def __init__(self, payload, ctype, params, filename, field_key):
+ # find the mime handler
+ _, self._entry = settings.mailcap_find_match(ctype, key = field_key)
+ if self._entry is None:
+ return
+
+ if isinstance(payload, str):
+ payload = payload.encode(detected_encoding, errors = 'backslashreplace')
+ self._payload = payload
+
+ self._payload = payload
+ self._ctype = ctype
+ self._params = tuple('='.join(p) for p in params)
+
+ # in case the mailcap defined command contains no '%s',
+ # we pipe the files content to the handling command via stdin
+ self._need_tmpfile = '%s' in self._entry['view']
+
+ def __bool__(self):
+ return self._entry is not None
+
+ def __enter__(self):
+ """
+ The context manager manages the temporary file, if one is needed.
+ """
+ if not self._need_tmpfile:
+ return
+
+ nametemplate = self._entry.get('nametemplate', '%s')
+ prefix, suffix = _parse_nametemplate(nametemplate)
+
+ fn_hook = settings.get_hook('sanitize_attachment_filename')
+ if fn_hook:
+ prefix, suffix = fn_hook(filename, prefix, suffix)
+
+ tmpfile = NamedTemporaryFile(prefix = prefix, suffix = suffix)
+
+ try:
+ tmpfile.write(self._payload)
+ tmpfile.flush()
+ self._tmpfile = tmpfile
+ except:
+ tmpfile.close()
+ raise
+
+ def __exit__(self, exc_type, exc_value, tb):
+ if self._tmpfile:
+ self._tmpfile.close()
+ self._tmpfile = None
+
+ @property
+ def cmd(self):
+ """ Shell command to run (str) """
+ fname = self._tmpfile.name if self._need_tmpfile else None
+ return mailcap.subst(self._entry['view'], self._ctype,
+ plist = self._params, filename = fname)
+
+ @property
+ def stdin(self):
+ if self._need_tmpfile:
+ return None
+ return self._payload
+
+ @property
+ def needs_terminal(self):
+ return self._entry.get('needsterminal') is not None