def decode_body(msg, s): "Decode body to plain text using first copiousoutput filter from mailcap" import mailcap import tempfile global caps if caps is None: caps = mailcap.getcaps() content_type = msg.get_content_type() if content_type.startswith('text/'): charset = msg.get_content_charset() else: charset = None tmpfile = tempfile.NamedTemporaryFile() command = None entries = mailcap.lookup(caps, content_type, "view") for entry in entries: if 'copiousoutput' in entry: if 'test' in entry: test = mailcap.subst(entry['test'], content_type, tmpfile.name) if test and os.system(test) != 0: continue command = mailcap.subst(entry["view"], content_type, tmpfile.name) break if not command: return s if charset and bytes is not str and isinstance(s, bytes): # Python3 s = s.decode(charset, "replace") if not isinstance(s, bytes): s = s.encode(g.default_encoding, "replace") tmpfile.write(s) tmpfile.flush() pipe = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE) new_s = pipe.stdout.read() pipe.stdout.close() if pipe.wait() == 0: # result=0, Ok s = new_s if bytes is not str and isinstance(s, bytes): # Python3 s = s.decode(g.default_encoding, "replace") if charset and not isinstance(s, bytes): s = s.encode(charset, "replace") set_content_type(msg, "text/plain") msg["X-MIME-Autoconverted"] = \ "from %s to text/plain by %s id %s" \ % (content_type, g.host_name, command.split()[0]) else: msg["X-MIME-Autoconverted"] = \ "failed conversion from %s to text/plain by %s id %s" \ % (content_type, g.host_name, command.split()[0]) tmpfile.close() # Will be removed on close return s
async def apply(self, ui): logging.info('open attachment') mimetype = self.attachment.get_content_type() # returns pair of preliminary command string and entry dict containing # more info. We only use the dict and construct the command ourselves _, entry = settings.mailcap_find_match(mimetype) if entry: afterwards = None # callback, will rm tempfile if used handler_stdin = None tempfile_name = None handler_raw_commandstring = entry['view'] # read parameter part = self.attachment.get_mime_representation() parms = tuple('='.join(p) for p in part.get_params()) # in case the mailcap defined command contains no '%s', # we pipe the files content to the handling command via stdin if '%s' in handler_raw_commandstring: nametemplate = entry.get('nametemplate', '%s') prefix, suffix = parse_mailcap_nametemplate(nametemplate) fn_hook = settings.get_hook('sanitize_attachment_filename') if fn_hook: # get filename filename = self.attachment.get_filename() prefix, suffix = fn_hook(filename, prefix, suffix) with tempfile.NamedTemporaryFile(delete=False, prefix=prefix, suffix=suffix) as tmpfile: tempfile_name = tmpfile.name self.attachment.write(tmpfile) def afterwards(): os.unlink(tempfile_name) else: handler_stdin = BytesIO() self.attachment.write(handler_stdin) # create handler command list handler_cmd = mailcap.subst(handler_raw_commandstring, mimetype, filename=tempfile_name, plist=parms) handler_cmdlist = split_commandstring(handler_cmd) # 'needsterminal' makes handler overtake the terminal # XXX: could this be repalced with "'needsterminal' not in entry"? overtakes = entry.get('needsterminal') is None await ui.apply_command( ExternalCommand(handler_cmdlist, stdin=handler_stdin, on_success=afterwards, thread=overtakes)) else: ui.notify('unknown mime type')
def apply(self, ui): logging.info('open attachment') mimetype = self.attachment.get_content_type() # returns pair of preliminary command string and entry dict containing # more info. We only use the dict and construct the command ourselves _, entry = settings.mailcap_find_match(mimetype) if entry: afterwards = None # callback, will rm tempfile if used handler_stdin = None tempfile_name = None handler_raw_commandstring = entry['view'] # read parameter part = self.attachment.get_mime_representation() parms = tuple(map('='.join, part.get_params())) # in case the mailcap defined command contains no '%s', # we pipe the files content to the handling command via stdin if '%s' in handler_raw_commandstring: nametemplate = entry.get('nametemplate', '%s') prefix, suffix = parse_mailcap_nametemplate(nametemplate) fn_hook = settings.get_hook('sanitize_attachment_filename') if fn_hook: # get filename filename = self.attachment.get_filename() prefix, suffix = fn_hook(filename, prefix, suffix) tmpfile = tempfile.NamedTemporaryFile(delete=False, prefix=prefix, suffix=suffix) tempfile_name = tmpfile.name self.attachment.write(tmpfile) tmpfile.close() def afterwards(): os.unlink(tempfile_name) else: handler_stdin = StringIO() self.attachment.write(handler_stdin) # create handler command list handler_cmd = mailcap.subst(handler_raw_commandstring, mimetype, filename=tempfile_name, plist=parms) handler_cmdlist = split_commandstring(handler_cmd) # 'needsterminal' makes handler overtake the terminal nt = entry.get('needsterminal', None) overtakes = (nt is None) ui.apply_command(ExternalCommand(handler_cmdlist, stdin=handler_stdin, on_success=afterwards, thread=overtakes)) else: ui.notify('unknown mime type')
def apply(self, ui): logging.info('open attachment') mimetype = self.attachment.get_content_type() # returns pair of preliminary command string and entry dict containing # more info. We only use the dict and construct the command ourselves _, entry = settings.mailcap_find_match(mimetype) if entry: afterwards = None # callback, will rm tempfile if used handler_stdin = None tempfile_name = None handler_raw_commandstring = entry['view'] # read parameter part = self.attachment.get_mime_representation() parms = tuple(map('='.join, part.get_params())) # in case the mailcap defined command contains no '%s', # we pipe the files content to the handling command via stdin if '%s' in handler_raw_commandstring: nametemplate = entry.get('nametemplate', '%s') prefix, suffix = parse_mailcap_nametemplate(nametemplate) tmpfile = tempfile.NamedTemporaryFile(delete=False, prefix=prefix, suffix=suffix) tempfile_name = tmpfile.name self.attachment.write(tmpfile) tmpfile.close() def afterwards(): os.remove(tempfile_name) else: handler_stdin = StringIO() self.attachment.write(handler_stdin) # create handler command list handler_cmd = mailcap.subst(handler_raw_commandstring, mimetype, filename=tempfile_name, plist=parms) handler_cmdlist = split_commandstring(handler_cmd) # 'needsterminal' makes handler overtake the terminal nt = entry.get('needsterminal', None) overtakes = (nt is None) ui.apply_command( ExternalCommand(handler_cmdlist, stdin=handler_stdin, on_success=afterwards, thread=overtakes)) else: ui.notify('unknown mime type')
def test_subst(self): plist = ['id=1', 'number=2', 'total=3'] test_cases = [(['', 'audio/*', 'foo.txt'], ''), (['echo foo', 'audio/*', 'foo.txt'], 'echo foo'), (['echo %s', 'audio/*', 'foo.txt'], 'echo foo.txt'), (['echo %t', 'audio/*', 'foo.txt'], 'echo audio/*'), (['echo \\%t', 'audio/*', 'foo.txt'], 'echo %t'), (['echo foo', 'audio/*', 'foo.txt', plist], 'echo foo'), (['echo %{total}', 'audio/*', 'foo.txt', plist], 'echo 3')] for tc in test_cases: self.assertEqual(mailcap.subst(*tc[0]), tc[1])
def test_subst(self): plist = ['id=1', 'number=2', 'total=3'] # test case: ([field, MIMEtype, filename, plist=[]], <expected string>) test_cases = [(["", "audio/*", "foo.txt"], ""), (["echo foo", "audio/*", "foo.txt"], "echo foo"), (["echo %s", "audio/*", "foo.txt"], "echo foo.txt"), (["echo %t", "audio/*", "foo.txt"], "echo audio/*"), (["echo \\%t", "audio/*", "foo.txt"], "echo %t"), (["echo foo", "audio/*", "foo.txt", plist], "echo foo"), (["echo %{total}", "audio/*", "foo.txt", plist], "echo 3")] for tc in test_cases: self.assertEqual(mailcap.subst(*tc[0]), tc[1])
def test_subst(self): plist = ['id=1', 'number=2', 'total=3'] # test case: ([field, MIMEtype, filename, plist=[]], <expected string>) test_cases = [ (["", "audio/*", "foo.txt"], ""), (["echo foo", "audio/*", "foo.txt"], "echo foo"), (["echo %s", "audio/*", "foo.txt"], "echo foo.txt"), (["echo %t", "audio/*", "foo.txt"], "echo audio/*"), (["echo \%t", "audio/*", "foo.txt"], "echo %t"), (["echo foo", "audio/*", "foo.txt", plist], "echo foo"), (["echo %{total}", "audio/*", "foo.txt", plist], "echo 3") ] for tc in test_cases: self.assertEqual(mailcap.subst(*tc[0]), tc[1])
def render_part(part, field_key='copiousoutput'): """ 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. """ ctype = part.get_content_type() raw_payload = remove_cte(part) rendered_payload = None # get mime handler _, entry = settings.mailcap_find_match(ctype, key=field_key) if entry is not None: tempfile_name = None stdin = None handler_raw_commandstring = entry['view'] # in case the mailcap defined command contains no '%s', # we pipe the files content to the handling command via stdin if '%s' in handler_raw_commandstring: # open tempfile, respect mailcaps nametemplate nametemplate = entry.get('nametemplate', '%s') prefix, suffix = parse_mailcap_nametemplate(nametemplate) with tempfile.NamedTemporaryFile( delete=False, prefix=prefix, suffix=suffix) \ as tmpfile: tmpfile.write(raw_payload) tempfile_name = tmpfile.name else: stdin = raw_payload # read parameter, create handler command parms = tuple('='.join(p) for p in part.get_params()) # 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) if stdout: rendered_payload = stdout # remove tempfile if tempfile_name: os.unlink(tempfile_name) return rendered_payload
def extract_body(mail, types=None, field_key='copiousoutput'): """Returns a string view of a Message. If the `types` argument is set then any encoding types there will be used as the prefered encoding to extract. If `types` is None then :ref:`prefer_plaintext <prefer-plaintext>` will be consulted; if it is True then text/plain parts will be returned, if it is false then text/html will be returned if present or text/plain if there are no text/html parts. :param mail: the mail to use :type mail: :class:`email.Message` :param types: mime content types to use for body string :type types: list[str] :returns: The combined text of any parts to be used :rtype: str """ preferred = 'text/plain' if settings.get( 'prefer_plaintext') else 'text/html' has_preferred = False # see if the mail has our preferred type if types is None: has_preferred = list(typed_subpart_iterator( mail, *preferred.split('/'))) body_parts = [] for part in mail.walk(): ctype = part.get_content_type() if types is not None: if ctype not in types: continue cd = part.get('Content-Disposition', '') if cd.startswith('attachment'): continue # if the mail has our preferred type, we only keep this type # note that if types != None, has_preferred always stays False if has_preferred and ctype != preferred: continue enc = part.get_content_charset() or 'ascii' raw_payload = part.get_payload(decode=True) if ctype == 'text/plain': raw_payload = string_decode(raw_payload, enc) body_parts.append(string_sanitize(raw_payload)) else: # get mime handler _, entry = settings.mailcap_find_match(ctype, key=field_key) tempfile_name = None stdin = None if entry: handler_raw_commandstring = entry['view'] # in case the mailcap defined command contains no '%s', # we pipe the files content to the handling command via stdin if '%s' in handler_raw_commandstring: # open tempfile, respect mailcaps nametemplate nametemplate = entry.get('nametemplate', '%s') prefix, suffix = parse_mailcap_nametemplate(nametemplate) with tempfile.NamedTemporaryFile( delete=False, prefix=prefix, suffix=suffix) \ as tmpfile: tmpfile.write(raw_payload) tempfile_name = tmpfile.name else: stdin = raw_payload # read parameter, create handler command parms = tuple('='.join(p) for p in part.get_params()) # 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 rendered_payload, _, _ = helper.call_cmd(cmdlist, stdin=stdin) # remove tempfile if tempfile_name: os.unlink(tempfile_name) if rendered_payload: # handler had output body_parts.append(string_sanitize(rendered_payload)) return u'\n\n'.join(body_parts)
def extract_body(mail, types=None): """ returns a body text string for given mail. If types is `None`, `text/*` is used: The exact preferred type is specified by the prefer_plaintext config option which defaults to text/html. :param mail: the mail to use :type mail: :class:`email.Message` :param types: mime content types to use for body string :type types: list of str """ preferred = 'text/plain' if settings.get('prefer_plaintext') else 'text/html' has_preferred = False # see if the mail has our preferred type if types == None: has_preferred = list(typed_subpart_iterator(mail, *preferred.split('/'))) body_parts = [] for part in mail.walk(): ctype = part.get_content_type() if types is not None: if ctype not in types: continue cd = part.get('Content-Disposition', '') if cd.startswith('attachment'): continue # if the mail has our preferred type, we only keep this type # note that if types != None, has_preferred always stays False if has_preferred and ctype != preferred: continue enc = part.get_content_charset() or 'ascii' raw_payload = part.get_payload(decode=True) if ctype == 'text/plain': raw_payload = string_decode(raw_payload, enc) body_parts.append(string_sanitize(raw_payload)) else: #get mime handler key = 'copiousoutput' handler, entry = settings.mailcap_find_match(ctype, key=key) tempfile_name = None stdin = None if entry: handler_raw_commandstring = entry['view'] # in case the mailcap defined command contains no '%s', # we pipe the files content to the handling command via stdin if '%s' in handler_raw_commandstring: # open tempfile, respect mailcaps nametemplate nametemplate = entry.get('nametemplate', '%s') prefix, suffix = parse_mailcap_nametemplate(nametemplate) tmpfile = tempfile.NamedTemporaryFile(delete=False, prefix=prefix, suffix=suffix) # write payload to tmpfile tmpfile.write(raw_payload) tmpfile.close() tempfile_name = tmpfile.name else: stdin = raw_payload # read parameter, create handler command parms = tuple(map('='.join, part.get_params())) # 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 rendered_payload, errmsg, retval = helper.call_cmd(cmdlist, stdin=stdin) # remove tempfile if tempfile_name: os.unlink(tempfile_name) if rendered_payload: # handler had output body_parts.append(string_sanitize(rendered_payload)) return u'\n\n'.join(body_parts)
def extract_body(mail, types=None): """ returns a body text string for given mail. If types is `None`, `text/*` is used: In case mail has a `text/html` part, it is prefered over `text/plain` parts. :param mail: the mail to use :type mail: :class:`email.Message` :param types: mime content types to use for body string :type types: list of str """ html = list(typed_subpart_iterator(mail, 'text', 'html')) # if no specific types are given, we favor text/html over text/plain drop_plaintext = False if html and not types: drop_plaintext = True body_parts = [] for part in mail.walk(): ctype = part.get_content_type() if types is not None: if ctype not in types: continue cd = part.get('Content-Disposition', '') if cd.startswith('attachment'): continue enc = part.get_content_charset() or 'ascii' raw_payload = part.get_payload(decode=True) if ctype == 'text/plain' and not drop_plaintext: raw_payload = string_decode(raw_payload, enc) body_parts.append(string_sanitize(raw_payload)) else: #get mime handler key = 'copiousoutput' handler, entry = settings.mailcap_find_match(ctype, key=key) if entry: # open tempfile, respect mailcaps nametemplate nametemplate = entry.get('nametemplate', '%s') prefix, suffix = parse_mailcap_nametemplate(nametemplate) tmpfile = tempfile.NamedTemporaryFile(delete=False, prefix=prefix, suffix=suffix) # write payload to tmpfile tmpfile.write(raw_payload) tmpfile.close() # read parameter, create handler command parms = tuple(map('='.join, part.get_params())) # create and call external command cmd = mailcap.subst(entry['view'], ctype, filename=tmpfile.name, plist=parms) logging.debug('command: %s' % cmd) logging.debug('parms: %s' % str(parms)) cmdlist = split_commandstring(cmd) # call handler rendered_payload, errmsg, retval = helper.call_cmd(cmdlist) # remove tempfile os.unlink(tmpfile.name) if rendered_payload: # handler had output body_parts.append(string_sanitize(rendered_payload)) return u'\n\n'.join(body_parts)
def extract_body(mail, types=None): """ returns a body text string for given mail. If types is `None`, `text/*` is used: In case mail has a `text/html` part, it is prefered over `text/plain` parts. :param mail: the mail to use :type mail: :class:`email.Message` :param types: mime content types to use for body string :type types: list of str """ html = list(typed_subpart_iterator(mail, 'text', 'html')) # if no specific types are given, we favor text/html over text/plain drop_plaintext = False if html and not types: drop_plaintext = True body_parts = [] for part in mail.walk(): ctype = part.get_content_type() logging.debug(ctype) if types is not None: if ctype not in types: continue cd = part.get('Content-Disposition', '') if cd.startswith('attachment'): continue enc = part.get_content_charset() or 'ascii' raw_payload = part.get_payload(decode=True) if ctype == 'text/plain' and not drop_plaintext: raw_payload = string_decode(raw_payload, enc) body_parts.append(string_sanitize(raw_payload)) else: #get mime handler key = 'copiousoutput' handler, entry = settings.mailcap_find_match(ctype, key=key) if entry: # open tempfile, respect mailcaps nametemplate nametemplate = entry.get('nametemplate', '%s') prefix, suffix = parse_mailcap_nametemplate(nametemplate) tmpfile = tempfile.NamedTemporaryFile(delete=False, prefix=prefix, suffix=suffix) # write payload to tmpfile tmpfile.write(raw_payload) tmpfile.close() # read parameter, create handler command parms = tuple(map('='.join, part.get_params())) # create and call external command cmd = mailcap.subst(entry['view'], ctype, filename=tmpfile.name, plist=parms) logging.debug('command: %s' % cmd) logging.debug('parms: %s' % str(parms)) cmdlist = split_commandstring(cmd) # call handler rendered_payload, errmsg, retval = helper.call_cmd(cmdlist) # remove tempfile os.unlink(tmpfile.name) if rendered_payload: # handler had output body_parts.append(string_sanitize(rendered_payload)) return u'\n\n'.join(body_parts)
def extract_body(mail, types=None): """ returns a body text string for given mail. If types is `None`, `text/*` is used: The exact preferred type is specified by the prefer_plaintext config option which defaults to text/html. :param mail: the mail to use :type mail: :class:`email.Message` :param types: mime content types to use for body string :type types: list of str """ preferred = 'text/plain' if settings.get( 'prefer_plaintext') else 'text/html' has_preferred = False # see if the mail has our preferred type if types is None: has_preferred = list( typed_subpart_iterator(mail, *preferred.split('/'))) body_parts = [] for part in mail.walk(): ctype = part.get_content_type() if types is not None: if ctype not in types: continue cd = part.get('Content-Disposition', '') if cd.startswith('attachment'): continue # if the mail has our preferred type, we only keep this type # note that if types != None, has_preferred always stays False if has_preferred and ctype != preferred: continue enc = part.get_content_charset() or 'ascii' raw_payload = part.get_payload(decode=True) if ctype == 'text/plain': raw_payload = string_decode(raw_payload, enc) body_parts.append(string_sanitize(raw_payload)) else: # get mime handler key = 'copiousoutput' handler, entry = settings.mailcap_find_match(ctype, key=key) tempfile_name = None stdin = None if entry: handler_raw_commandstring = entry['view'] # in case the mailcap defined command contains no '%s', # we pipe the files content to the handling command via stdin if '%s' in handler_raw_commandstring: # open tempfile, respect mailcaps nametemplate nametemplate = entry.get('nametemplate', '%s') prefix, suffix = parse_mailcap_nametemplate(nametemplate) tmpfile = tempfile.NamedTemporaryFile(delete=False, prefix=prefix, suffix=suffix) # write payload to tmpfile tmpfile.write(raw_payload) tmpfile.close() tempfile_name = tmpfile.name else: stdin = raw_payload # read parameter, create handler command parms = tuple(map('='.join, part.get_params())) # 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 rendered_payload, errmsg, retval = helper.call_cmd(cmdlist, stdin=stdin) # remove tempfile if tempfile_name: os.unlink(tempfile_name) if rendered_payload: # handler had output body_parts.append(string_sanitize(rendered_payload)) return u'\n\n'.join(body_parts)
def update_event(self, inp=-1): self.set_output_val( 0, mailcap.subst(self.input(0), self.input(1), self.input(2), self.input(3)))