summaryrefslogtreecommitdiff
path: root/alot/db
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/db
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/db')
-rw-r--r--alot/db/message.py67
1 files changed, 23 insertions, 44 deletions
diff --git a/alot/db/message.py b/alot/db/message.py
index ff8cca70..e10e29a5 100644
--- a/alot/db/message.py
+++ b/alot/db/message.py
@@ -2,77 +2,54 @@
# This file is released under the GNU GPL, version 3 or a later revision.
# For further details see the COPYING file
-from contextlib import ExitStack
import email
import email.charset as charset
import email.policy
import logging
-import mailcap
import os
-import tempfile
+import subprocess
from datetime import datetime
+from urwid.util import detected_encoding
+
from .attachment import Attachment
from .. import crypto
from .. import helper
from ..errors import GPGProblem
-from ..helper import parse_mailcap_nametemplate
-from ..helper import split_commandstring
from ..settings.const import settings
+from ..utils.mailcap import MailcapHandler
charset.add_charset('utf-8', charset.QP, charset.QP, 'utf-8')
_APP_PGP_SIG = 'application/pgp-signature'
_APP_PGP_ENC = 'application/pgp-encrypted'
-def _render_part_external(raw_payload, ctype, params, field_key='copiousoutput'):
+def _render_part_external(payload, ctype, params, filename):
"""
renders a non-multipart email part into displayable plaintext by piping its
payload through an external script. The handler itself is determined by
the mailcap entry for this part's ctype.
"""
- rendered_payload = None
- # get mime handler
- _, entry = settings.mailcap_find_match(ctype, key=field_key)
- if entry is None:
- return None
-
- if isinstance(raw_payload, str):
- raw_payload = raw_payload.encode('utf-8')
-
- with ExitStack() as stack:
- # read parameter, create handler command
- parms = 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
- if '%s' in entry['view']:
- # open tempfile, respect mailcaps nametemplate
- nametemplate = entry.get('nametemplate', '%s')
- prefix, suffix = parse_mailcap_nametemplate(nametemplate)
+ h = MailcapHandler(payload, ctype, params, filename, 'copiousoutput')
+ if not h or h.needs_terminal:
+ return
- tmpfile = stack.enter_context(tempfile.NamedTemporaryFile(prefix = prefix, suffix = suffix))
+ def decode(buf):
+ return buf.decode(detected_encoding, errors = 'backslashreplace')
- tmpfile.write(raw_payload)
- tmpfile.flush()
+ with h:
+ logging.debug('Rendering part %s: %s', ctype, h.cmd)
- tempfile_name = tmpfile.name
- stdin = None
- else:
- tempfile_name = None
- stdin = raw_payload
-
- # create and call external command
- cmd = mailcap.subst(entry['view'], ctype,
- filename = tempfile_name, plist = parms)
- logging.debug('command: %s', cmd)
- logging.debug('parms: %s', str(parms))
- cmdlist = split_commandstring(cmd)
- # call handler
- stdout, _, _ = helper.call_cmd(cmdlist, stdin=stdin)
+ try:
+ result = subprocess.run(h.cmd, shell = True, check = True,
+ capture_output = True, input = h.stdin)
+ except subprocess.CalledProcessError as e:
+ logging.error('Calling mailcap handler "%s" failed with code %d: %s',
+ h.cmd, e.returncode, decode(e.stderr))
+ return None
- return stdout
+ return decode(result.stdout)
class _MessageHeaders:
_msg = None
@@ -170,7 +147,9 @@ class _MimeTree:
content = self._part.get_content()
# try procesing content with an external program
- rendered = _render_part_external(content, self.content_type, self._part.get_params())
+ rendered = _render_part_external(content, self.content_type,
+ self._part.get_params(),
+ 'copiousoutput')
if rendered:
return rendered