C#:MailBee:使用 mailbee 的 tnef 实用程序卸载 winmail.dat 内的附件

C#: MailBee: Unload attachments inside a winmail.dat using mailbee's tnef utilities

我正在尝试使用 Gmail 收件箱中的 mailbee 阅读电子邮件。 我们的想法是获取任何 winmail.dat 并提取其附件并将它们添加到附件池中,这样我们就不必手动执行此操作。 MailBee 无法识别我用测试邮件发送的 winmail.dat 作为 Tnef 文件(文件类型为 Tnef 附件类型)。 IsTnef 结果是假的。

我找不到很多关于 mailbee tnef 附件的信息,有什么想法吗?

这是我得到的:

var attachments = message.Attachments.Cast<MailBeeAttachment>().ToList();
var encapsulated = attachments.Where(a => a.IsTnef).SelectMany(a => a.GetAttachmentsFromTnef().Cast<MailBeeAttachment>());
// Add encapsulated attachments
attachments.AddRange(encapsulated);

您可能想继续使用 MailBee...但如果您愿意接受其他选择,让我来回答这个问题,就好像您正在使用我的开源 MimeKit/MailKit 库一样:

当 MimeKit 的 MIME 解析器遇到带有匹配 application/vnd.ms-tnefapplication/ms-tnefContent-Type header 的附件时,它会自动使用 special-purpose TnefPart class 代表那个附件。

要从中提取封装的附件,您只需使用 ExtractAttachments() 方法可能很像 MailBee 的 GetAttachmentsFromTnef() 方法。

将您的代码从 MailBee 转换为 MimeKit 如下所示:

var attachments = message.Attachments.ToList();
var encapsulated = attachments.OfType<TnefPart>().SelectMany(a => a.ExtractAttachments ());
// Add encapsulated attachments
attachments.AddRange(encapsulated);

最有可能的原因是 MailBee 的 IsTnef 属性 为您返回 false,但是,可能是因为 Content-Type header 与 tnef mime-types 我之前提到过。如果确实如此,那么我上面贴出的翻译代码在 MimeKit 下也会失败。

然而...

MimeKit 还提供 lower-level TNEF 支持 class 如果您决定使用替代方法来确定包含 TNEF 内容的 MIME 部分,您可以使用它。

static void ExtractAttachments (MimePart attachment, IList<MimeEntity> attachments)
{
    using (var reader = new TnefReader (attachment.ContentObject.Open (), 0, TnefComplianceMode.Loose)) {
        // skip past the non-attachment tnef content...
        while (reader.ReadNextAttribute ()) {
            if (reader.AttributeLevel == TnefAttributeLevel.Attachment)
                break;
        }

        if (reader.AttributeLevel == TnefAttributeLevel.Attachment)
            ExtractAttachments (reader, attachments);
    }
}

static void ExtractAttachments (TnefReader reader, IList<MimeEntity> attachments)
{
    var attachMethod = TnefAttachMethod.ByValue;
    var filter = new BestEncodingFilter ();
    var prop = reader.TnefPropertyReader;
    MimePart attachment = null;
    int outIndex, outLength;
    TnefAttachFlags flags;
    string[] mimeType;
    byte[] attachData;
    string text;

    do {
        if (reader.AttributeLevel != TnefAttributeLevel.Attachment)
            break;

        switch (reader.AttributeTag) {
        case TnefAttributeTag.AttachRenderData:
            attachMethod = TnefAttachMethod.ByValue;
            attachment = new MimePart ();
            break;
        case TnefAttributeTag.Attachment:
            if (attachment == null)
                break;

            while (prop.ReadNextProperty ()) {
                switch (prop.PropertyTag.Id) {
                case TnefPropertyId.AttachLongFilename:
                    attachment.FileName = prop.ReadValueAsString ();
                    break;
                case TnefPropertyId.AttachFilename:
                    if (attachment.FileName == null)
                        attachment.FileName = prop.ReadValueAsString ();
                    break;
                case TnefPropertyId.AttachContentLocation:
                    text = prop.ReadValueAsString ();
                    if (Uri.IsWellFormedUriString (text, UriKind.Absolute))
                        attachment.ContentLocation = new Uri (text, UriKind.Absolute);
                    else if (Uri.IsWellFormedUriString (text, UriKind.Relative))
                        attachment.ContentLocation = new Uri (text, UriKind.Relative);
                    break;
                case TnefPropertyId.AttachContentBase:
                    text = prop.ReadValueAsString ();
                    attachment.ContentBase = new Uri (text, UriKind.Absolute);
                    break;
                case TnefPropertyId.AttachContentId:
                    attachment.ContentId = prop.ReadValueAsString ();
                    break;
                case TnefPropertyId.AttachDisposition:
                    text = prop.ReadValueAsString ();
                    if (attachment.ContentDisposition == null)
                        attachment.ContentDisposition = new ContentDisposition (text);
                    else
                        attachment.ContentDisposition.Disposition = text;
                    break;
                case TnefPropertyId.AttachData:
                    var stream = prop.GetRawValueReadStream ();
                    var content = new MemoryStream ();
                    var guid = new byte[16];

                    if (attachMethod == TnefAttachMethod.EmbeddedMessage) {
                        var tnef = new TnefPart ();

                        foreach (var param in attachment.ContentType.Parameters)
                            tnef.ContentType.Parameters[param.Name] = param.Value;

                        if (attachment.ContentDisposition != null)
                            tnef.ContentDisposition = attachment.ContentDisposition;

                        attachment = tnef;
                    }

                    // read the GUID
                    stream.Read (guid, 0, 16);

                    // the rest is content
                    using (var filtered = new FilteredStream (content)) {
                        filtered.Add (filter);
                        stream.CopyTo (filtered, 4096);
                        filtered.Flush ();
                    }

                    content.Position = 0;

                    attachment.ContentTransferEncoding = filter.GetBestEncoding (EncodingConstraint.SevenBit);
                    attachment.ContentObject = new ContentObject (content);
                    filter.Reset ();

                    attachments.Add (attachment);
                    break;
                case TnefPropertyId.AttachMethod:
                    attachMethod = (TnefAttachMethod) prop.ReadValueAsInt32 ();
                    break;
                case TnefPropertyId.AttachMimeTag:
                    mimeType = prop.ReadValueAsString ().Split ('/');
                    if (mimeType.Length == 2) {
                        attachment.ContentType.MediaType = mimeType[0].Trim ();
                        attachment.ContentType.MediaSubtype = mimeType[1].Trim ();
                    }
                    break;
                case TnefPropertyId.AttachFlags:
                    flags = (TnefAttachFlags) prop.ReadValueAsInt32 ();
                    if ((flags & TnefAttachFlags.RenderedInBody) != 0) {
                        if (attachment.ContentDisposition == null)
                            attachment.ContentDisposition = new ContentDisposition (ContentDisposition.Inline);
                        else
                            attachment.ContentDisposition.Disposition = ContentDisposition.Inline;
                    }
                    break;
                case TnefPropertyId.AttachSize:
                    if (attachment.ContentDisposition == null)
                        attachment.ContentDisposition = new ContentDisposition ();

                    attachment.ContentDisposition.Size = prop.ReadValueAsInt64 ();
                    break;
                case TnefPropertyId.DisplayName:
                    attachment.ContentType.Name = prop.ReadValueAsString ();
                    break;
                }
            }
            break;
        case TnefAttributeTag.AttachCreateDate:
            if (attachment != null) {
                if (attachment.ContentDisposition == null)
                    attachment.ContentDisposition = new ContentDisposition ();

                attachment.ContentDisposition.CreationDate = prop.ReadValueAsDateTime ();
            }
            break;
        case TnefAttributeTag.AttachModifyDate:
            if (attachment != null) {
                if (attachment.ContentDisposition == null)
                    attachment.ContentDisposition = new ContentDisposition ();

                attachment.ContentDisposition.ModificationDate = prop.ReadValueAsDateTime ();
            }
            break;
        case TnefAttributeTag.AttachTitle:
            if (attachment != null && string.IsNullOrEmpty (attachment.FileName))
                attachment.FileName = prop.ReadValueAsString ();
            break;
        case TnefAttributeTag.AttachMetaFile:
            if (attachment == null)
                break;

            // TODO: what to do with the meta data?
            break;
        case TnefAttributeTag.AttachData:
            if (attachment == null || attachMethod != TnefAttachMethod.ByValue)
                break;

            attachData = prop.ReadValueAsBytes ();
            filter.Flush (attachData, 0, attachData.Length, out outIndex, out outLength);
            attachment.ContentTransferEncoding = filter.GetBestEncoding (EncodingConstraint.EightBit);
            attachment.ContentObject = new ContentObject (new MemoryStream (attachData, false));
            filter.Reset ();

            attachments.Add (attachment);
            break;
        }
    } while (reader.ReadNextAttribute ());
}

当然……你也可以这样作弊:

var tnef = new TnefPart { ContentObject = attachment.ContentObject };
attachments.AddRange (tnef.ExtractAttachments ());

因此最终结果可能如下所示(如果您决定改为匹配文件名):

var attachments = message.Attachments.ToList();
var encapsulated = attachments.OfType<MimePart>()
    .Where(x => (x is TnefPart) || x.FileName == "winmail.dat")
    .SelectMany(x => {
        var tnef = (x as TnefPart) ?? new TnefPart { ContentObject = x.ContentObject };
        return tnef.ExtractAttachments ();
    });
// Add encapsulated attachments
attachments.AddRange(encapsulated);

您不应在测试时手动添加 winmail.dat。它始终由 MS Exchange 或 Outlook 以特殊方式生成(并附加)。 IsTnef 将 return 来自真实(Outlook 或 Exchange 生成的)电子邮件的真实附件。