Click or drag to resize

MimeVisitor Class

Represents a visitor for MIME trees.
Inheritance Hierarchy

Namespace:  MimeKit
Assembly:  MimeKit (in MimeKit.dll) Version: 3.0.0
public abstract class MimeVisitor

The MimeVisitor type exposes the following members.

Protected methodMimeVisitor
Initializes a new instance of the MimeVisitor class
Public methodEquals
Determines whether the specified object is equal to the current object.
(Inherited from Object.)
Protected methodFinalize
Allows an object to try to free resources and perform other cleanup operations before it is reclaimed by garbage collection.
(Inherited from Object.)
Public methodGetHashCode
Serves as the default hash function.
(Inherited from Object.)
Public methodGetType
Gets the Type of the current instance.
(Inherited from Object.)
Protected methodMemberwiseClone
Creates a shallow copy of the current Object.
(Inherited from Object.)
Public methodToString
Returns a string that represents the current object.
(Inherited from Object.)
Public methodVisit(MimeEntity)
Dispatches the entity to one of the more specialized visit methods in this class.
Public methodVisit(MimeMessage)
Dispatches the message to one of the more specialized visit methods in this class.
Protected methodVisitApplicationPgpEncrypted
Visit the application/pgp-encrypted MIME entity.
Protected methodVisitApplicationPgpSignature
Visit the application/pgp-signature MIME entity.
Protected methodVisitApplicationPkcs7Mime
Visit the application/pkcs7-mime MIME entity.
Protected methodVisitApplicationPkcs7Signature
Visit the application/pkcs7-signature MIME entity.
Protected methodVisitBody
Visit the body of the message.
Protected methodVisitChildren
Visit the children of a Multipart.
Protected methodVisitMessage
Visit the message contained within a message/rfc822 or message/news MIME entity.
Protected methodVisitMessageDeliveryStatus
Visit the message/delivery-status MIME entity.
Protected methodVisitMessageDispositionNotification
Visit the message/disposition-notification MIME entity.
Protected methodCode exampleVisitMessagePart
Visit the message/rfc822 or message/news MIME entity.
Protected methodVisitMessagePartial
Visit the message/partial MIME entity.
Protected methodVisitMimeEntity
Visit the abstract MIME entity.
Protected methodVisitMimeMessage
Visit the MIME message.
Protected methodCode exampleVisitMimePart
Visit the abstract MIME part entity.
Protected methodVisitMultipart
Visit the abstract multipart MIME entity.
Protected methodCode exampleVisitMultipartAlternative
Visit the multipart/alternative MIME entity.
Protected methodVisitMultipartEncrypted
Visit the multipart/encrypted MIME entity.
Protected methodCode exampleVisitMultipartRelated
Visit the multipart/related MIME entity.
Protected methodCode exampleVisitMultipartReport
Visit the multipart/report MIME entity.
Protected methodVisitMultipartSigned
Visit the multipart/signed MIME entity.
Protected methodCode exampleVisitTextPart
Visit the text-based MIME part entity.
Protected methodCode exampleVisitTextRfc822Headers
Visit the text/rfc822-headers MIME entity.
Protected methodCode exampleVisitTnefPart
Visit the Microsoft TNEF MIME part entity.
This class is designed to be inherited to create more specialized classes whose functionality requires traversing, examining or copying a MIME tree.
/// <summary>
/// Visits a MimeMessage and generates HTML suitable to be rendered by a 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 a new HtmlPreviewVisitor.
    /// </summary>
    /// <param name="tempDirectory">A temporary directory used for storing image files.</param>
    public HtmlPreviewVisitor (string tempDirectory)
        tempDir = tempDirectory;

    /// <summary>
    /// The list of attachments that were in the MimeMessage.
    /// </summary>
    public IList<MimeEntity> Attachments {
        get { return attachments; }

    /// <summary>
    /// The HTML string that can be set on the BrowserControl.
    /// </summary>
    public string HtmlBody {
        get { return body ?? string.Empty; }

    protected override void VisitMultipartAlternative (MultipartAlternative alternative)
        // walk the multipart/alternative children backwards from greatest level of faithfulness to the least faithful
        for (int i = alternative.Count - 1; i >= 0 && body == null; i--)
            alternative[i].Accept (this);

    protected override void VisitMultipartRelated (MultipartRelated related)
        var root = related.Root;

        // push this multipart/related onto our stack
        stack.Add (related);

        // visit the root document
        root.Accept (this);

        // pop this multipart/related off our stack
        stack.RemoveAt (stack.Count - 1);

    // look up the image based on the 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;
            kind = UriKind.RelativeOrAbsolute;

        try {
            uri = new Uri (url, kind);
        } catch {
            image = null;
            return false;

        for (int i = stack.Count - 1; i >= 0; i--) {
            if ((index = stack[i].IndexOf (uri)) == -1)

            image = stack[i][index] as MimePart;
            return image != null;

        image = null;

        return false;

    // Save the image to our temp directory and return a "file://" url suitable for
    // the browser control to load.
    // Note: if you'd rather embed the image data into the HTML, you can construct a
    // "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.Content.DecodeTo (output);

        return "file://" + path.Replace ('\\', '/');

    // Replaces <img src=...> urls that refer to images embedded within the message with
    // "file://" urls that the browser control will actually be able to load.
    void HtmlTagCallback (HtmlTagContext ctx, HtmlWriter htmlWriter)
        if (ctx.TagId == HtmlTagId.Image && !ctx.IsEndTag && stack.Count > 0) {
            ctx.WriteTag (htmlWriter, false);

            // replace the src attribute with a 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);

                    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")

                htmlWriter.WriteAttribute (attribute);

            htmlWriter.WriteAttribute ("oncontextmenu", "return false;");
        } else {
            // pass the tag through to the output
            ctx.WriteTag (htmlWriter, true);

    protected override void VisitTextPart (TextPart entity)
        TextConverter converter;

        if (body != null) {
            // since we've already found the body, treat this as an attachment
            attachments.Add (entity);

        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 ();

        body = converter.Convert (entity.Text);

    protected override void VisitTnefPart (TnefPart entity)
        // extract any attachments in the MS-TNEF part
        attachments.AddRange (entity.ExtractAttachments ());

    protected override void VisitMessagePart (MessagePart entity)
        // treat message/rfc822 parts as attachments
        attachments.Add (entity);

    protected override void VisitMimePart (MimePart entity)
        // realistically, if we've gotten this far, then we can treat this as an attachment
        // even if the IsAttachment property is false.
        attachments.Add (entity);
See Also