from flask import current_app, request, url_for from flask_babel import gettext from markupsafe import Markup, escape def render_text_plain(_subtype, content, _path): return f'<div class="jwm-mail-body-text-plain"><pre>{escape(content)}</pre></div>\n' def render_text_html(_subtype, _content, path): if path: url = url_for( "read", msgid=request.view_args["msgid"], format="raw", path=".".join(map(str, path)), ) else: url = url_for("read", msgid=request.view_args["msgid"], format="raw") return f'<iframe src="{url}" class="jwm-mail-body-text-html" ></iframe>\n' def render_image(subtype, content, _path): return f'<img src="data:image/{subtype};base64,{escape(content)}" />\n' def render_multipart_alternative(_subtype, content, path): parts = content["parts"] T = "<div class=tabs><ul>\n" C = "<div class=jwm-mail-body-multipart-alternative-bodies>" init, *rest = reversed(parts) T += f"<li class=is-active data=0><a>{to_mime_type(init['head'])}</a></li>" C += "<div class=jwm-mail-body-multipart-alternative-body>\n" C += mime_render( *to_mime_types(init["head"]), init["body"], path + (len(parts) - 1,) ) C += "</div>\n" for i, r in enumerate(rest, 1): T += f"<li data={i}><a>{to_mime_type(r['head'])}</a></li>\n" C += '<div class="jwm-mail-body-multipart-alternative-body is-hidden">\n' C += mime_render( *to_mime_types(r["head"]), r["body"], path + (len(parts) - 1 - i,) ) C += "</div>\n" C += "</div>" T += "</ul></div>" script_url = url_for("static", filename="src/rendermail.js") return f'<script src="{script_url}"></script><div class="jwm-mail-body jwm-mail-body-multipart-alternative">\n{T}\n{C}\n</div>\n' def render_multipart(_subtype, content, path): parts = content["parts"] R = '<div class="jwm-mail-body jwm-mail-body-multipart">\n' for i, p in enumerate(parts): R += "<div class=media><div class=media-content>\n" if ( not p["head"]["content_disposition"] or p["head"]["content_disposition"].lower() == "none" or p["head"]["content_disposition"].lower() == "inline" ): R += mime_render(*to_mime_types(p["head"]), p["body"], path + (i,)) elif p["head"]["content_disposition"].lower() == "attachment": link_text = gettext("Attachment {filename} of type {filetype}").format( filename=p["head"]["filename"], filetype=to_mime_type(p["head"]) ) ref_url = url_for( "read", msgid=request.view_args["msgid"], path=".".join(map(str, [*path, i])), format="raw", ) R += "<p>" R += f'<a href="{ref_url}" download="{escape(p["head"]["filename"])}">\n' R += f"{escape(link_text)}</a>\n" R += "</p>\n" else: current_app.log.warning( "unknown Content-Disposition %s", p["head"]["content_disposition"] ) R += f"<p>unknown Content-Disposition {p['head']['content_disposition']}</p>\n" R += "</div></div>\n" return R + "</div>\n" def _format_header(category, value): R = "" if isinstance(value, list) and value: R += f"<dt>{escape(category)}</dt>\n" for v in value: value = ( f'"{v["display_name"]}" <{v["address"]}>' if v["display_name"] else v["address"] ) R += f"<dd>{escape(value)}<dd>\n" return R def render_message(subtype, msg, path): if subtype != "rfc822": current_app.log.warning("unknown message mime-subtype %s", subtype) R = '<div class="jwm-mail">' R += '<dl class="jwm-mail-header">' R += f"<dt>{escape(gettext('Subject'))}</dt>" R += f"<dd>{escape(msg['head']['subject'])}</dd>\n" R += _format_header(gettext("From"), msg["head"].get("from")) R += _format_header(gettext("To"), msg["head"].get("to")) R += _format_header(gettext("CC"), msg["head"].get("cc")) R += _format_header(gettext("BCC"), msg["head"].get("bcc")) R += f"<dt>{escape(gettext('Date'))}</dt>" R += f"<dd>{escape(msg['head']['date'])}</dd>\n" R += f"<dt>{escape(gettext('Content-Type'))}</dt>" R += f"<dd>{to_mime_type(msg['head']['mime'])}</dd>\n" R += "</dl>\n" R += mime_render(*to_mime_types(msg["head"]["mime"]), msg["body"], path + (0,)) return R + "</div>\n" MIMERenderSubs = { ("text", "plain"): render_text_plain, ("text", "html"): render_text_html, ("multipart", "alternative"): render_multipart_alternative, "multipart": render_multipart, "message": render_message, "image": render_image, } def mime_render(maintype, subtype, content, path): renderer = MIMERenderSubs.get((maintype, subtype)) or MIMERenderSubs.get(maintype) if not renderer: typ = f"<code>{maintype}/{subtype}</code>" msg = gettext("Unsupported MIME type of {mime_type}.").format(mime_type=typ) return f'<p class="jwm-body-unsupported">{msg}</p>\n' return renderer(subtype, content, path) def to_mime_type(mime): return escape(f"{mime['content_maintype']}/{mime['content_subtype']}".lower()) def to_mime_types(mime): return escape(mime["content_maintype"]), escape(mime["content_subtype"]) def format_mail(mail): return Markup(mime_render("message", "rfc822", mail, tuple()))