如何使用 OpenPop 以 HTML 阅读电子邮件?

How to use OpenPop to read e-mail as HTML?

我们使用 OpenPop 来接收电子邮件。我必须阅读消息的主题并将其显示给用户。当用户点击消息时,我想在占位符中显示消息的内容。
这是我显示内容的代码,但效果不是很好:

plhMessage.Controls.Add(new LiteralControl(Encoding.UTF8.GetString(client.GetMessage(0).RawMessage)));

它returns头上的这段文字:

Return-Path: Bounce@adspackages.com 
Received: from adspackages.com (Unknown [185.81.96.156]) by ip-30.afaghhost.com ; Mon, 7 Mar 2016 07:13:06 +0330 
DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=adspackages.com; 
    s=default; h=Content-Transfer-Encoding:Content-Type: 
    MIME-Version:Message-ID:Subject:Reply-To:From:To:Date;
    bh=MBNWLlH5FJIZMrbe+SG0izGjV9d9fR6eupDkfvgklSw=; 
    b=DLYlNuMd7ZAPCpQTQvGCi7yBiX aRhjlqQ8zGLPWcmDDX159frMVPiTh652Os3xwxWZ/iS4BQeOA5cFXdZSwcCxIO9hEaGr7ogZXVM5k blpgoO3htf9GAPJSDOxxVWIjEYgN3+m7UE1N7azmtvUPrrZkl9H8JwCIXLbI0SRpmHQ2ebA7QMgQM /Jl8u+5dLmWo4eYLxFgyTjZwTPPmLKwfSADjET3bb9ZfBTcK/KHsAjfx7Miy7zgP5vSE1n+p ....

并在没有图片的情况下显示消息。

如何让电子邮件的 HTML 显示在占位符中? 另外,如何显示邮件中的图片?

在我开始之前,我应该注意你不能在 client.GetMessage(0).RawMessage 上可靠地使用 Encoding.UTF8.GetBytes() 因为它(错误地)假设原始消息数据是 UTF-8 并且不能保证是这样的。西欧的大多数电子邮件将采用其中一种拉丁编码,俄罗斯电子邮件通常采用 iso-8859-5、windows-1251 或 koi8-r。中文、日文和韩文电子邮件几乎总是使用它们的一种语言环境字符集(通常分别为 gbk/big5、iso-2022-jp 和 euc-kr)。

既然已经解决了...让我们继续您的主要问题。

坦率地说,使用 MailKit 比使用 OpenPOP 更容易做到,所以我将向您展示如何使用 MailKit 来完成:

要获取邮件,MailKit 的工作方式与 OpenPOP 非常相似:

using System;

using MailKit.Net.Pop3;
using MailKit;
using MimeKit;

namespace TestClient {
    class Program
    {
        public static void Main (string[] args)
        {
            using (var client = new Pop3Client ()) {
                client.Connect ("pop.gmail.com", 995, true);

                // Note: since we don't have an OAuth2 token, disable
                // the XOAUTH2 authentication mechanism.
                client.AuthenticationMechanisms.Remove ("XOAUTH2");

                client.Authenticate ("user.name@gmail.com", "password");

                for (int i = 0; i < client.Count; i++) {
                    var message = client.GetMessage (i);
                    // TODO: render the message
                }

                client.Disconnect (true);
            }
        }
    }
}

要使用 MailKit 呈现邮件,最简单的方法是编写您自己的 MimeVisitor,如下所示:

/// <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> ();
    string body;

    /// <summary>
    /// Creates a new HtmlPreviewVisitor.
    /// </summary>
    public HtmlPreviewVisitor ()
    {
    }

    /// <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;
        else
            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)
                continue;

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

        image = null;

        return false;
    }

    // Save the image to our temp directory and return a "data:" url suitable for
    // the browser control to load.
    string GetDataImageSrc (MimePart image)
    {
        using (var output = new MemoryStream ()) {
            image.ContentObject.DecodeTo (output);
            return string.Format ("data:{0};base64,{1}", image.ContentType.MimeType, Convert.ToBase64String (output.GetBuffer (), 0, (int) output.Length));
        }
    }

    // Replaces <img src=...> urls that refer to images embedded within the message with
    // "data:" 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);
                        continue;
                    }

                    url = GetDataImageSrc (image);

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

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

使用这个 HtmlPreviewVisitor 的方法是这样的:

void Render (MimeMessage message)
{
    var visitor = new HtmlPreviewVisitor ();

    message.Accept (visitor);

    plhMessage.Controls.Add (new LiteralControl (visitor.HtmlBody));
}

Note: if the HTML in the messages you plan to render all reference web URLs for their images, you can get away with just rendering the message.HtmlBody string. The HtmlPreviewVisitor solution, however, will work even if the images are embedded within the message itself.