Switch branches/tags
Nothing to show
Find file
9567475 Jan 16, 2017
@akkana @hvoigt
executable file 145 lines (120 sloc) 5.4 KB
#! /usr/bin/env python
# Take an mbox HTML message (e.g. from mutt), split it
# and rewrite it so it can be viewed in an external browser.
# Can be run from within a mailer like mutt, or independently
# on a single message file.
# Usage: viewhtmlmail
# Inspired by John Eikenberry <>'s
# which sadly no longer works, at least with mail from current Apple Mail.
# Copyright 2013 by Akkana Peck. Share and enjoy under the GPL v2 or later.
# Changes:
# Holger Klawitter 2014: create a secure temp file and avoid temp mbox
# To use it from mutt, put the following lines in your .muttrc:
# macro index <F10> "<pipe-message>~/bin/viewhtmlmail\n" "View HTML in browser"
# macro pager <F10> "<pipe-message>~/bin/viewhtmlmail\n" "View HTML in browser"
import os, sys
import re
import time
import shutil
import email, mimetypes
import tempfile
def view_html_message(f, tmpdir):
if f:
with open(f) as fp:
msg = email.message_from_string(
msg = email.message_from_string(
html_part = None
counter = 1
subfiles = []
filenames = set()
for part in msg.walk():
# print ""
# part has, for example:
# items: [('Content-Type', 'image/jpeg'), ('Content-Transfer-Encoding', 'base64'), ('Content-ID', '<>'), ('Content-Disposition', 'attachment; filename="ATT0001414.jpg"')]
# keys: ['Content-Type', 'Content-Transfer-Encoding', 'Content-ID', 'Content-Disposition']
# values: ['image/jpeg', 'base64', '<>', 'attachment; filename="ATT0001414.jpg"']
# multipart/* are just containers
#if part.get_content_maintype() == 'multipart':
if part.is_multipart() or part.get_content_type == 'message/rfc822':
if part.get_content_subtype() == 'html':
if html_part:
print("Eek, more than one html part!")
html_part = part
# Save it to a file in the temp dir.
filename = part.get_filename()
if not filename:
print("No filename; making one up")
ext = mimetypes.guess_extension(part.get_content_type())
if not ext:
# Use a generic bag-of-bits extension
ext = '.bin'
filename = 'part-%03d%s' % (counter, ext)
# Applications should really sanitize the given filename so that an
# email message can't be used to overwrite important files.
# As a first step, guard against ../
if '../' in filename:
print("Eek! Possible security problem in filename %s" % filename)
# Some mailers, apparently including gmail, will attach multiple
# images to the same mail all with the same name, like "image.png".
# So check whether we have to uniquify the names.
if filename in filenames:
orig_basename, orig_ext = os.path.splitext(filename)
counter = 0
while filename in filenames:
counter += 1
filename = "%s-%d%s" % (orig_basename, counter, orig_ext)
filename = os.path.join(tmpdir, filename)
# print "%10s %5s %s" % (part.get_content_type(), ext, filename)
# Mailers may use Content-Id or Content-ID (or, presumably, various
# other capitalizations). So we can't just look it up simply.
content_id = None
for k in list(part.keys()):
if k.lower() == 'content-id':
# Remove angle brackets, if present.
# part['Content-Id'] is unmutable -- attempts to change it
# are just ignored -- so copy it to a local mutable string.
content_id = part[k]
if content_id.startswith('<') and content_id.endswith('>'):
content_id = content_id[1:-1]
subfiles.append({ 'filename': filename,
'Content-Id': content_id })
counter += 1
fp = open(filename, 'wb')
# print "wrote", os.path.join(tmpdir, filename)
break # no need to look at other keys
if not content_id:
print("%s doesn't have a Content-Id, not saving" % filename)
# print("keys: %s" % str(part.keys()))
# for sf in subfiles:
# print sf
# We're done saving the parts. It's time to save the HTML part.
htmlfile = os.path.join(tmpdir, "viewhtml.html")
fp = open(htmlfile, 'wb')
htmlsrc = html_part.get_payload(decode=True)
# Substitute all the filenames for CIDs:
for sf in subfiles:
htmlsrc = re.sub('cid: ?' + sf['Content-Id'],
'file://' + sf['filename'],
htmlsrc, flags=re.IGNORECASE)
# Now we have the file. Call a browser on it.
print("Calling browser for file://%s" % htmlfile)
# os.system("firefox -new-window file://%s" % htmlfile)
os.system("quickbrowse file://%s" % htmlfile)
# Wait a while to make sure firefox has loaded the images, then clean up.
# time.sleep(6)
# shutil.rmtree(tmpdir)
if __name__ == '__main__':
tmpdir = tempfile.mkdtemp()
for f in sys.argv[1:]:
view_html_message(f, tmpdir)