#! /usr/bin/env python
"""Decode MIME message"""
-import sys, os
-from mimedecode_version import __version__, \
- __author__, __copyright__, __license__
+import os
+import subprocess
+import sys
+
+from mimedecode_version import __version__, __copyright__
+
+if sys.version_info[0] >= 3:
+ # Replace email.message._formatparam with _formatparam from Python 2.7
+ # to avoid re-encoding non-ascii params.
+ import formatparam_27 # noqa: F401: Imported for its side effect
me = os.path.basename(sys.argv[0])
sys.stdout.write("""\
Broytman mimedecode.py version %s, %s
""" % (__version__, __copyright__))
- if exit: sys.exit(0)
+ if exit:
+ sys.exit(0)
+
def usage(code=0, errormsg=''):
version(0)
sys.stdout.write("""\
Usage: %s [-h|--help] [-V|--version] [-cCDP] [-H|--host=hostname] [-f charset] [-d header1[,h2,...]|*[,-h1,...]] [-p header1[,h2,h3,...]:param1[,p2,p3,...]] [-r header1[,h2,...]|*[,-h1,...]] [-R header1[,h2,h3,...]:param1[,p2,p3,...]] [--set-header header:value] [--set-param header:param=value] [-Bbeit mask] [--save-headers|body|message mask] [-O dest_dir] [-o output_file] [input_file [output_file]]
-""" % me)
+""" % me) # noqa: E501
if errormsg:
sys.stderr.write(errormsg + os.linesep)
sys.exit(code)
output(";")
output(_decode_header(value[1], strip=False))
output(os.linesep)
- output(os.linesep) # End of headers
+ output(os.linesep) # End of headers
def recode_if_needed(s, charset):
- if bytes is str: # Python2
+ if bytes is str: # Python2
if isinstance(s, bytes) and \
charset and charset.lower() != g.default_encoding:
s = s.decode(charset, "replace").\
encode(g.default_encoding, "replace")
- else: # Python3
+ else: # Python3
if isinstance(s, bytes):
s = s.decode(charset, "replace")
return s
# together into the final string.
return ' '.join(rtn)
+
def decode_header(msg, header):
"Decode mail header (if exists) and put it back, if it was encoded"
if header in msg:
value = msg[header]
new_value = _decode_header(value)
- if new_value != value: # do not bother to touch msg if not changed
+ if new_value != value: # do not bother to touch msg if not changed
set_header(msg, header, new_value)
def decode_header_param(msg, header, param):
- "Decode mail header's parameter (if exists) and put it back, if it was encoded"
+ """Decode mail header's parameter
+ Decode mail header's parameter (if exists)
+ and put it back if it was encoded.
+ """
if header in msg:
value = msg.get_param(param, header=header)
if value:
new_value = recode_if_needed(value[2], value[0])
else:
new_value = _decode_header(value)
- if new_value != value: # do not bother to touch msg if not changed
+ if new_value != value: # do not bother to touch msg if not changed
msg.set_param(param, new_value, header)
def _get_exceptions(list):
- return [x[1:].lower() for x in list[1:] if x[0]=='-']
+ return [x[1:].lower() for x in list[1:] if x[0] == '-']
+
def _decode_headers_params(msg, header, decode_all_params, param_list):
if decode_all_params:
for param in param_list:
decode_header_param(msg, header, param)
+
def _remove_headers_params(msg, header, remove_all_params, param_list):
if remove_all_params:
params = msg.get_params(header=header)
msg.del_param(param, header)
else:
value = msg[header]
- if value is None: # No such header
+ if value is None: # No such header
return
- if ';' not in value: # There are no parameters
+ if ';' not in value: # There are no parameters
return
- del msg[header] # Delete all such headers
+ del msg[header] # Delete all such headers
# Get the value without parameters and set it back
msg[header] = value.split(';')[0].strip()
else:
for param in param_list:
msg.del_param(param, header)
+
def decode_headers(msg):
"Decode message headers according to global options"
for header_list in g.remove_headers:
header_list = header_list.split(',')
- if header_list[0] == '*': # Remove all headers except listed
+ if header_list[0] == '*': # Remove all headers except listed
header_list = _get_exceptions(header_list)
for header in msg.keys():
if header.lower() not in header_list:
del msg[header]
- else: # Remove listed headers
+ else: # Remove listed headers
for header in header_list:
del msg[header]
for header_list, param_list in g.remove_headers_params:
header_list = header_list.split(',')
param_list = param_list.split(',')
- remove_all_params = param_list[0] == '*' # Remove all params except listed
+ # Remove all params except listed.
+ remove_all_params = param_list[0] == '*'
if remove_all_params:
param_list = _get_exceptions(param_list)
- if header_list[0] == '*': # Remove for all headers except listed
+ if header_list[0] == '*': # Remove for all headers except listed
header_list = _get_exceptions(header_list)
for header in msg.keys():
if header.lower() not in header_list:
- _remove_headers_params(msg, header, remove_all_params, param_list)
- else: # Decode for listed headers
+ _remove_headers_params(
+ msg, header, remove_all_params, param_list)
+ else: # Decode for listed headers
for header in header_list:
- _remove_headers_params(msg, header, remove_all_params, param_list)
+ _remove_headers_params(
+ msg, header, remove_all_params, param_list)
for header_list in g.decode_headers:
header_list = header_list.split(',')
- if header_list[0] == '*': # Decode all headers except listed
+ if header_list[0] == '*': # Decode all headers except listed
header_list = _get_exceptions(header_list)
for header in msg.keys():
if header.lower() not in header_list:
decode_header(msg, header)
- else: # Decode listed headers
+ else: # Decode listed headers
for header in header_list:
decode_header(msg, header)
for header_list, param_list in g.decode_header_params:
header_list = header_list.split(',')
param_list = param_list.split(',')
- decode_all_params = param_list[0] == '*' # Decode all params except listed
+ # Decode all params except listed.
+ decode_all_params = param_list[0] == '*'
if decode_all_params:
param_list = _get_exceptions(param_list)
- if header_list[0] == '*': # Decode for all headers except listed
+ if header_list[0] == '*': # Decode for all headers except listed
header_list = _get_exceptions(header_list)
for header in msg.keys():
if header.lower() not in header_list:
- _decode_headers_params(msg, header, decode_all_params, param_list)
- else: # Decode for listed headers
+ _decode_headers_params(
+ msg, header, decode_all_params, param_list)
+ else: # Decode for listed headers
for header in header_list:
- _decode_headers_params(msg, header, decode_all_params, param_list)
+ _decode_headers_params(
+ msg, header, decode_all_params, param_list)
def set_header(msg, header, value):
msg.set_param("charset", charset, "Content-Type")
-caps = None # Globally stored mailcap database; initialized only if needed
+caps = None # Globally stored mailcap database; initialized only if needed
+
def decode_body(msg, s):
"Decode body to plain text using first copiousoutput filter from mailcap"
- import mailcap, tempfile
+ import mailcap
+ import tempfile
global caps
if caps is None:
return s
outfile = open(filename, 'wb')
- if charset and isinstance(s, bytes):
+ 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")
outfile.write(s)
outfile.close()
- pipe = os.popen(command, 'r')
- s = pipe.read()
- pipe.close()
+ 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])
os.remove(filename)
- 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])
-
return s
s = recode_if_needed(s, charset)
content_type = msg.get_content_type()
set_content_type(msg, content_type, g.default_encoding)
- msg["X-MIME-Autoconverted"] = "from %s to %s by %s id %s" % (save_charset, g.default_encoding, g.host_name, me)
+ msg["X-MIME-Autoconverted"] = \
+ "from %s to %s by %s id %s" \
+ % (save_charset, g.default_encoding, g.host_name, me)
return s
mimetypes = None
+
def _guess_extension(ctype):
global mimetypes
if mimetypes is None:
mimetypes._db.read(user_mime_type)
return mimetypes.guess_extension(ctype)
+
def _save_message(msg, outstring, save_headers=False, save_body=False):
for header, param in (
("Content-Disposition", "filename"),
fname = msg.get_param(param, header=header)
if fname:
if isinstance(fname, tuple):
- fname = fname[2] # Do not recode if it isn't recoded yet
+ fname = fname[2] # Do not recode if it isn't recoded yet
try:
for forbidden in chr(0), '/', '\\':
if forbidden in fname:
fname = str(g.save_counter) + fname
if '.' not in fname:
ext = _guess_extension(msg.get_content_type())
- if ext: fname += ext
+ if ext:
+ fname += ext
global output
save_output = output
outfile = open_output_file(fname)
+
def _output_bytes(s):
if not isinstance(s, bytes):
s = s.encode(g.default_encoding, "replace")
outfile.write(s)
+
output = _output_bytes
if save_headers:
output_headers(msg)
encoding = msg["Content-Transfer-Encoding"]
if left_binary or encoding in (None, '', '7bit', '8bit', 'binary'):
outstring = msg.get_payload()
- else: # Decode from transfer ecoding to text or binary form
+ else: # Decode from transfer ecoding to text or binary form
outstring = msg.get_payload(decode=1)
set_header(msg, "Content-Transfer-Encoding", "8bit")
- msg["X-MIME-Autoconverted"] = "from %s to 8bit by %s id %s" % (encoding, g.host_name, me)
+ msg["X-MIME-Autoconverted"] = \
+ "from %s to 8bit by %s id %s" % (encoding, g.host_name, me)
for content_type in masks:
if content_type in g.totext_mask:
outstring = totext(msg, outstring)
break
elif content_type in g.binary_mask or \
- content_type in g.decoded_binary_mask:
+ content_type in g.decoded_binary_mask:
output_headers(msg)
output(outstring)
break
elif content_type in g.ignore_mask:
output_headers(msg)
- output("%sMessage body of type %s skipped.%s" % (os.linesep, ctype, os.linesep))
+ output("%sMessage body of type %s skipped.%s"
+ % (os.linesep, ctype, os.linesep))
break
elif content_type in g.error_mask:
break
if content_type in g.error_mask:
raise ValueError("content type %s prohibited" % ctype)
+
def decode_multipart(msg):
"Decode multipart"
return
elif content_type in g.ignore_mask:
output_headers(msg)
- output("%sMessage body of type %s skipped.%s" % (os.linesep, ctype, os.linesep))
+ output("%sMessage body of type %s skipped.%s"
+ % (os.linesep, ctype, os.linesep))
if boundary:
output("%s--%s--%s" % (os.linesep, boundary, os.linesep))
return
output_headers(msg)
- if msg.preamble: # Preserve the first part, it is probably not a RFC822-message
- output(msg.preamble) # Usually it is just a few lines of text (MIME warning)
+ # Preserve the first part, it is probably not a RFC822-message.
+ if msg.preamble:
+ # Usually it is just a few lines of text (MIME warning).
+ output(msg.preamble)
if msg.preamble is not None:
output(os.linesep)
if msg.is_multipart():
decode_multipart(msg)
- elif len(msg): # Simple one-part message (there are headers) - decode it
+ elif len(msg): # Simple one-part message (there are headers) - decode it
decode_part(msg)
- else: # Not a message, just text - copy it literally
+ else: # Not a message, just text - copy it literally
output(msg.as_string())
class GlobalOptions:
from m_lib.defenc import default_encoding
- recode_charset = 1 # recode charset of message body
+ recode_charset = 1 # recode charset of message body
host_name = None
# A list of header/parameter/value triples to set
set_header_param = []
- totext_mask = [] # A list of content-types to decode
- binary_mask = [] # A list of content-types to pass through
- decoded_binary_mask = [] # A list of content-types to pass through (content-transfer-decoded)
- ignore_mask = [] # Ignore (do not decode and do not include into output) but output a warning instead of the body
- fully_ignore_mask = [] # Completely ignore - no headers, no body, no warning
+ totext_mask = [] # A list of content-types to decode
+ binary_mask = [] # A list of content-types to pass through
+ # A list of content-types to pass through (content-transfer-decoded).
+ decoded_binary_mask = []
+ # Ignore (do not decode and do not include into output)
+ # but output a warning instead of the body.
+ ignore_mask = []
+ # Completely ignore - no headers, no body, no warning.
+ fully_ignore_mask = []
error_mask = [] # Raise error if encounter one of these
save_counter = 0
output_filename = None
destination_dir = os.curdir
+
g = GlobalOptions
from getopt import getopt, GetoptError
try:
- options, arguments = getopt(sys.argv[1:],
+ options, arguments = getopt(
+ sys.argv[1:],
'hVcCDPH:f:d:p:r:R:b:B:e:I:i:t:O:o:',
['help', 'version', 'host=',
'save-headers=', 'save-body=', 'save-message=',