is there way convert mimekit.mimemessage html can rendered in web browser? i'm not concerned message attachments, able display message body, complete embedded images, in browser. i'm new mimekit , couldn't locate in api docs this. info appreciated.
edit: didn't find way natively mimekit, combined htmlagilitypack parse mimemessage.htmbody , fix inline images. seems work , i'll go unless has better idea. reference, here's code:
////////////////////////////////////////////////////////////////////////////////////////// // use mimekit parse message ////////////////////////////////////////////////////////////////////////////////////////// mimekit.mimemessage msg = mimekit.mimemessage.load(stream); ////////////////////////////////////////////////////////////////////////////////////////// // use htmlagilitypack parse resulting html in order fix inline images ////////////////////////////////////////////////////////////////////////////////////////// htmlagilitypack.htmldocument hdoc = new htmlagilitypack.htmldocument(); hdoc.loadhtml(msg.htmlbody); // find image nodes var images = hdoc.documentnode.descendants("img"); foreach (var img in images) { // check inline image string cid = img.attributes["src"].value; if (cid.startswith("cid:")) { // remove cid part of attribute cid = cid.remove(0, 4); // find image object in mimemessage mimekit.mimepart part = msg.bodyparts.first(x => x.contentid == cid) mimekit.mimepart; if (part != null) { using (memorystream mstream = new memorystream()) { // raw image content part.contentobject.writeto(mstream); mstream.flush(); byte[] imgbytes = mstream.toarray(); // fix image source making embedded image img.attributes["src"].value = "data:" + part.contenttype.mimetype + ";" + part.contenttransferencoding.tostring().tolower() + "," + system.text.asciiencoding.ascii.getstring(imgbytes); } } } } // write resulting html output stream hdoc.save(outputstream);
your solution similar logic used use in mimekit's messagereader sample, mimekit provides better solution:
/// <summary> /// visits mimemessage , generates html suitable rendered browser control. /// </summary> class htmlpreviewvisitor : mimevisitor { list<multipartrelated> stack = new list<multipartrelated> (); list<mimeentity> attachments = new list<mimeentity> (); readonly string tempdir; string body; /// <summary> /// creates new htmlpreviewvisitor. /// </summary> /// <param name="tempdirectory">a temporary directory used storing image files.</param> public htmlpreviewvisitor (string tempdirectory) { tempdir = tempdirectory; } /// <summary> /// list of attachments in mimemessage. /// </summary> public ilist<mimeentity> attachments { { return attachments; } } /// <summary> /// html string can set on browsercontrol. /// </summary> public string htmlbody { { return body ?? string.empty; } } protected override void visitmultipartalternative (multipartalternative alternative) { // walk multipart/alternative children backwards greatest level of faithfulness least faithful (int = alternative.count - 1; >= 0 && body == null; i--) alternative[i].accept (this); } protected override void visitmultipartrelated (multipartrelated related) { var root = related.root; // push multipart/related onto our stack stack.add (related); // visit root document root.accept (this); // pop multipart/related off our stack stack.removeat (stack.count - 1); } // image based on img src url within our multipart/related stack bool trygetimage (string url, out mimepart image) { urikind kind; int index; uri uri; if (uri.iswellformeduristring (url, urikind.absolute)) kind = urikind.absolute; else if (uri.iswellformeduristring (url, urikind.relative)) kind = urikind.relative; else kind = urikind.relativeorabsolute; try { uri = new uri (url, kind); } catch { image = null; return false; } (int = stack.count - 1; >= 0; i--) { if ((index = stack[i].indexof (uri)) == -1) continue; image = stack[i][index] mimepart; return image != null; } image = null; return false; } // save image our temp directory , return "file://" url suitable // browser control load. // note: if you'd rather embed image data html, can construct // "data:" url instead. string saveimage (mimepart image, string url) { string filename = url.replace (':', '_').replace ('\\', '_').replace ('/', '_'); string path = path.combine (tempdir, filename); if (!file.exists (path)) { using (var output = file.create (path)) image.contentobject.decodeto (output); } return "file://" + path.replace ('\\', '/'); } // replaces <img src=...> urls refer images embedded within message // "file://" urls browser control able load. void htmltagcallback (htmltagcontext ctx, htmlwriter htmlwriter) { if (ctx.tagid == htmltagid.image && !ctx.isendtag && stack.count > 0) { ctx.writetag (htmlwriter, false); // replace src attribute file:// url foreach (var attribute in ctx.attributes) { if (attribute.id == htmlattributeid.src) { mimepart image; string url; if (!trygetimage (attribute.value, out image)) { htmlwriter.writeattribute (attribute); continue; } url = saveimage (image, attribute.value); htmlwriter.writeattributename (attribute.name); htmlwriter.writeattributevalue (url); } else { htmlwriter.writeattribute (attribute); } } } else if (ctx.tagid == htmltagid.body && !ctx.isendtag) { ctx.writetag (htmlwriter, false); // add and/or replace oncontextmenu="return false;" foreach (var attribute in ctx.attributes) { if (attribute.name.tolowerinvariant () == "oncontextmenu") continue; htmlwriter.writeattribute (attribute); } htmlwriter.writeattribute ("oncontextmenu", "return false;"); } else { // pass tag through output ctx.writetag (htmlwriter, true); } } protected override void visittextpart (textpart entity) { textconverter converter; if (body != null) { // since we've found body, treat attachment attachments.add (entity); return; } if (entity.ishtml) { converter = new htmltohtml { htmltagcallback = htmltagcallback }; } else if (entity.isflowed) { var flowed = new flowedtohtml (); string delsp; if (entity.contenttype.parameters.trygetvalue ("delsp", out delsp)) flowed.deletespace = delsp.tolowerinvariant () == "yes"; converter = flowed; } else { converter = new texttohtml (); } string text = entity.text; body = converter.convert (entity.text); } protected override void visittnefpart (tnefpart entity) { // extract attachments in ms-tnef part attachments.addrange (entity.extractattachments ()); } protected override void visitmessagepart (messagepart entity) { // treat message/rfc822 parts attachments attachments.add (entity); } protected override void visitmimepart (mimepart entity) { // realistically, if we've gotten far, can treat attachment // if isattachment property false. attachments.add (entity); } } and use custom htmlpreviewvisitor class, you'd have method this:
void render (webbrowser browser, mimemessage message) { var tmpdir = path.combine (path.gettemppath (), message.messageid); var visitor = new htmlpreviewvisitor (tmpdir); directory.createdirectory (tmpdir); message.accept (visitor); browser.documenttext = visitor.htmlbody; } i know seems lot of code, it's covering lot more simple cases. you'll notice handles rendering text/plain text/plain; format=flowed bodies if html not available. correctly uses images part of encapsulating multipart/related tree.
one way modify code embed images img tags instead of using temp directory. that, you'd modify saveimage method (be warned, next segment of code untested):
string saveimage (mimepart image, string url) { using (var output = new memorystream ()) { image.contentobject.decodeto (output); var buffer = output.getbuffer (); int length = (int) output.length; return string.format ("data:{0};base64,{1}", image.contenttype.mimetype, convert.tobase64string (buffer, 0, length)); } }
Comments
Post a Comment