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'
{escape(content)}
\n' def render_text_html(_subtype, _content, path): if path: url = url_for( "read", folder=request.view_args.get("folder"), msgid=request.view_args["msgid"], format="raw", path=".".join(map(str, path)), ) else: url = url_for( "read", folder=request.view_args.get("folder"), msgid=request.view_args["msgid"], format="raw", ) return f'\n' def render_image(subtype, content, _path): return f'\n' def render_multipart_alternative(_subtype, content, path): parts = content["parts"] T = "
" return f'
\n{T}\n{C}\n
\n' def render_multipart(_subtype, content, path): parts = content["parts"] R = '
\n' for i, p in enumerate(parts): R += "
\n" if ( not p["head"].get("content_disposition") or p["head"]["content_disposition"].lower() == "none" or p["head"]["content_disposition"].lower() == "inline" ): R += mime_render(p["head"], p["body"], path + (i,)) elif p["head"]["content_disposition"].lower() == "attachment": R += _as_download(p, [*path, i]) else: current_app.log.warning( "unknown Content-Disposition %s", p["head"]["content_disposition"] ) R += f"

unknown Content-Disposition {p['head']['content_disposition']}

\n" R += "
\n" return R + "
\n" def _as_download(part, path): link_text = gettext("Attachment {filename} of type {filetype}").format( filename=part["head"]["filename"], filetype=to_mime_type(part["head"]) ) ref_url = url_for( "read", folder=request.view_args.get("folder"), msgid=request.view_args["msgid"], path=".".join(map(str, path)), format="raw", ) R = "

" R += f'\n' R += f"{escape(link_text)}\n" R += "

\n" return R def _format_header(category, value): R = "" if isinstance(value, list) and value: R += f"
{escape(category)}
\n" for v in value: value = ( f'"{v["display_name"]}" <{v["address"]}>' if v.get("display_name") else v["address"] ) R += f"
{escape(value)}
\n" return R def render_message(subtype, msg, path): if subtype != "rfc822": current_app.log.warning("unknown message mime-subtype %s", subtype) R = '
' R += '
' R += f"
{escape(gettext('Subject'))}
" R += f"
{escape(msg['head']['subject'])}
\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"
{escape(gettext('Date'))}
" date = escape(msg["head"]["date"]) R += f"
\n" R += f"
{escape(gettext('Content-Type'))}
" R += f"
{to_mime_type(msg['head']['mime'])}
\n" R += "
\n" R += mime_render(msg["head"]["mime"], msg["body"], path + (0,)) return R + "
\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(mime_header, content, path): maintype = escape(mime_header["content_maintype"]) subtype = escape(mime_header["content_subtype"]) renderer = MIMERenderSubs.get((maintype, subtype)) or MIMERenderSubs.get(maintype) if not renderer: typ = f"{maintype}/{subtype}" msg = gettext("Unsupported MIME type of {mime_type}.").format(mime_type=typ) return f'

{msg}

\n' return renderer(subtype, content, path) def to_mime_type(mime): return escape(f"{mime['content_maintype']}/{mime['content_subtype']}".lower()) def format_mail(mail): return Markup( mime_render( {"content_maintype": "message", "content_subtype": "rfc822"}, mail, tuple() ) )