Skip to main content
 首页 » 编程设计

c#之如何区分电子邮件Imap中的内联图像和签名以及其他空白图像

2024年02月05日31bluestorm

我正在使用 Mailkit 从邮箱中获取电子邮件并将其保存到数据库以显示在我的 MVC 应用程序中。

我将 html 电子邮件作为纯文本保存在数据库中,我可以获取附件并将​​其保存在文件系统中,但是当电子邮件中有内联图像时,我遇到了问题,因为签名和其他空白图像也被保存为附件在文件系统中。

有没有办法区分内联附件和签名或其他空白图像?

提前致谢

请您参考如下方法:

无论您使用哪个 IMAP 库,它们都没有可以帮助您做您想做的事情的功能,因为要解决这个问题并非易事,您将需要使用一些独创性来解决解决。

您可以从 HtmlPreviewVisitor 开始来自 FAQ 的示例并稍微修改它以将附件分成 2 个列表:

  1. 实际附件列表
  2. HTML 实际引用的图像列表(通过遍历 HTML 并跟踪引用了哪些图像)

代码:

/// <summary> 
/// Visits a MimeMessage and splits attachments into those that are 
/// referenced by the HTML body vs regular attachments. 
/// </summary> 
class AttachmentVisitor : MimeVisitor 
{ 
    List<MultipartRelated> stack = new List<MultipartRelated> (); 
    List<MimeEntity> attachments = new List<MimeEntity> (); 
    List<MimePart> embedded = new List<MimePart> (); 
    bool foundBody; 
 
    /// <summary> 
    /// Creates a new AttachmentVisitor. 
    /// </summary> 
    public AttachmentVisitor () 
    { 
    } 
 
    /// <summary> 
    /// The list of attachments that were in the MimeMessage. 
    /// </summary> 
    public IList<MimeEntity> Attachments { 
        get { return attachments; } 
    } 
 
    /// <summary> 
    /// The list of embedded images that were in the MimeMessage. 
    /// </summary> 
    public IList<MimePart> EmbeddedImages { 
        get { return embedded; } 
    } 
 
    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 && !foundBody; 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; 
    } 
 
    // called when an HTML tag is encountered 
    void HtmlTagCallback (HtmlTagContext ctx, HtmlWriter htmlWriter) 
    { 
        if (ctx.TagId == HtmlTagId.Image && !ctx.IsEndTag && stack.Count > 0) { 
            // search for the src= attribute 
            foreach (var attribute in ctx.Attributes) { 
                if (attribute.Id == HtmlAttributeId.Src) { 
                    MimePart image; 
 
                    if (!TryGetImage (attribute.Value, out image)) 
                        continue; 
 
                    if (!embedded.Contains (image)) 
                        embedded.Add (image); 
                } 
            } 
        } 
    } 
 
    protected override void VisitTextPart (TextPart entity) 
    { 
        TextConverter converter; 
 
        if (foundBody) { 
            // since we've already found the body, treat this as an 
            // attachment 
            attachments.Add (entity); 
            return; 
        } 
 
        if (entity.IsHtml) { 
            converter = new HtmlToHtml { 
                HtmlTagCallback = HtmlTagCallback 
            }; 
 
            converter.Convert (entity.Text); 
        } 
 
        foundBody = true; 
    } 
 
    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); 
    } 
} 

使用方法:

var visitor = new AttachmentVisitor (); 
 
message.Accept (visitor); 
 
// Now you can use visitor.Attachments and visitor.EmbeddedImages 

一个更简单的方法,虽然不太容易出错(因为它实际上并不验证图像是否被 HTML 引用),方法是这样的:

var embeddedImages = message.BodyParts.OfType<MimePart> (). 
    Where (x => x.ContentType.IsMimeType ("image", "*") && 
           x.ContentDisposition != null && 
           x.ContentDisposition.Disposition.Equals ("inline" StringComparison.OrdinalIgnoreCase)); 

现在您有了 embeddedImages 的列表,您必须找到一种方法来确定它们是仅用于签名还是用于 HTML 的其他地方。

很可能您还必须分析 HTML 本身。

可能还值得注意的是,某些 HTML 邮件将引用位于 Web 上的图像,这些图像未嵌入到邮件的 MIME 中。如果您还想要这些 图像,则需要修改 TryGetImage如果我提供的代码无法在消息的 MIME 中找到图像,则回退到从 Web 下载图像。

对于文本/普通消息(根本不能使用图像),将签名与消息正文的其余部分分开的常见约定是一行只有 2 个破折号和一个空格:-- .

根据我对带有签名的 HTML 消息的有限经验,它们似乎没有遵循类似的约定。查看我从使用 Outlook 的 Microsoft 同事那里收到的一些 HTML 消息,它们似乎在 <table> 中。在消息的末尾。但是,这假设消息不是回复。一旦您开始解析消息回复,这 <table>最终在消息的中间某处结束,因为被回复的原始消息在末尾。

由于每个人的签名也不一样,我不确定这个<table>相似性是 Outlook 的惯例,或者如果人们手动构建他们的签名并且他们只是出于巧合而使用表格(我也只看到了一些,大多数不使用签名,所以我的样本量非常小)。