MimeKit 从电子邮件中删除 gif 图像

MimeKit Remove gif images from Emails

我正在尝试从电子邮件中删除 gif 图像以节省 Outlook 和我们的文档管理系统中的存储空间 space。

例如,假设您有一封大小约为 2MB 的电子邮件,而 gif 文件为 1MB。我期望电子邮件的文件大小为 1MB。

第一部分使用MimeKit去除gif。我在这段代码中发现的问题是,如果您不进行调试,它不会按我的预期减少文件大小。我发现这是因为图像仍在 MimeMessage 的 html 属性中。

        MimeMessage mimeMessage = MimeMessage.Load(testFile);
                   
        var bodyParts = mimeMessage.BodyParts.ToList();
        if (bodyParts.Any())
        {
            var multipart = mimeMessage.Body as Multipart;
            if (multipart != null)
            {
                MimeEntity bodyPartToRemove = null;
                foreach (var bodyPart in bodyParts)
                {
                    var mimeBodyPart = bodyPart as MimePart;
                    if (mimeBodyPart == null)
                    {
                        continue;
                    }
                    if (mimeBodyPart.ContentType.MimeType == "image/gif")
                    {
                        bodyPartToRemove = mimeBodyPart;
                    }
                }
                
                if (bodyPartToRemove != null)
                {
                    multipart.Remove(bodyPartToRemove);
                }
            }
            
            mimeMessage.Body = multipart;
        }

所以在这之后我想我会使用 HtmlAgilityPack 从 html 中删除 img 标签,然后使用 MimeKit.BodyBuilder 正确设置 MimeMessage。

        var builder = new BodyBuilder();

        // Set the plain-text version of the message text
        builder.TextBody = mimeMessage.TextBody;

        // Set the html version of the message text
        builder.HtmlBody = StripHtml(mimeMessage.HtmlBody);
        
        // Attachments
        foreach (var blah in mimeMessage.Attachments)
            builder.Attachments.Add(blah);

        mimeMessage.Body = builder.ToMessageBody();



    private string StripHtml(string html)
    {
        HtmlAgilityPack.HtmlDocument htmlDoc = new HtmlAgilityPack.HtmlDocument();
        htmlDoc.LoadHtml(html);

        var nodes = htmlDoc.DocumentNode.SelectNodes("//img");

        foreach (var node in nodes)
        {
            if (node.OuterHtml.Contains(".gif"))
                node.Remove();
        }

        return htmlDoc.DocumentNode.InnerHtml;
    }

此解决方案的问题是,通过使用 builder.ToMessageBody(),它不会显示可能包含在电子邮件中的其他非 gif 图像,也不会像表情符号一样正确呈现电子邮件的其他部分.

有人遇到过这个吗?

你有 2 个问题,我将分别回答。

为什么删除gif附件后邮件的大小没有缩小?

MIME 可以包含 嵌套 多部分,在你的情况下,它可能是因为 HTML 带有图像的邮件通常在 multipart/related 中,而 multipart/related 通常在一个 multipart/alternative,像这样:

multipart/mixed
  multipart/alternative
    text/plain   <-- text-only version of the message body
    multipart/related
      text/html  <-- html version of the message body
      image/jpeg <-- an image "embedded" in the html body
      image/gif  <-- a gif image embedded in the html body
  application/pdf  <-- an attachment

MimeMessage.BodyParts 递归列出邮件中的所有 MIME 部分,在上面的示例中是所有文本部分、图像部分和 pdf。

问题是您的代码假定 top-level 多部分是所有这些部分的直接 parent 而事实并非如此,因此删除上面示例中的 gif 将是 no-op。这可能就是邮件大小没有像您预期的那样减少的原因。

我应该有一份 FAQ about this because it's not likely easy to find the example of how to remove attachments in the MimeIterator 文档。也就是说,这是该代码片段的自定义版本,可以满足您的需要:

var multiparts = new List<Multipart> ();
var gifs = new List<MimePart> ();

using (var iter = new MimeIterator (message)) {
    // collect our list of attachments and their parent multiparts
    while (iter.MoveNext ()) {
        var multipart = iter.Parent as Multipart;
        var part = iter.Current as MimePart;

        if (multipart != null && part != null && part.ContentType.IsMimeType ("Image", "gif")) {
            // keep track of each gif's parent multipart
            multiparts.Add (multipart);
            gifs.Add (part);
        }
    }
}

// now remove each gif from its parent multipart...
for (int i = 0; i < gifs.Count; i++)
    multiparts[i].Remove (gifs[i]);

为什么用BodyBuilder替换消息后所有表情都消失了body?

当您构建 BodyBuilder 时,您当然会添加所有 Attachments,但您不会添加任何 inline body 部分。表情符号 可能 inline body 部分而不是附件,所以你正在丢失它们。

如果您参考本答案上一节中的示例 MIME 消息结构,通常只有外部 multipart/mixed MIME 部分中包含的 body 部分将被标记为 attachment,而 multipart/related 中的图像将是 inline,因为它们将与消息 body.

内联显示(也称为嵌入)

在我们继续之前,我应该注意消息的大小不会受到 HTML 中的 <img> 标签的显着影响,除非它们包含以 base64 或其他方式编码的原始图像数据(这完全有可能,但不太可能)。

我可能不会费心去篡改 HTML,但是...如果您真的想要,快速破解可能看起来像这样:

var multiparts = new List<Multipart> ();
var gifs = new List<MimePart> ();

using (var iter = new MimeIterator (message)) {
    // collect our list of attachments and their parent multiparts
    while (iter.MoveNext ()) {
        var multipart = iter.Parent as Multipart;
        var part = iter.Current as MimePart;

        if (part == null)
            continue;

        if (multipart != null && part.ContentType.IsMimeType ("Image", "gif")) {
            // keep track of each gif's parent multipart
            multiparts.Add (multipart);
            gifs.Add (part);
        } else if (part is TextPart text && text.IsHtml) {
            text.Text = StripHtml (text.Text);
        }
    }
}

// now remove each gif from its parent multipart...
for (int i = 0; i < gifs.Count; i++)
    multiparts[i].Remove (gifs[i]);

最后,不要忘记使用 message.WriteTo(fileName) 写回消息。我提到这一点是因为有些人似乎认为当对消息进行任何更改时事情都是 auto-saved。