You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何使用MimeKit获取MIME邮件中附件的文件偏移位置?

MimeKit获取附件在原MIME文件中的偏移位置

MimeKit本身没有直接提供类似LumiSoft的PositionSectionStart/PositionBodyStart这类现成属性,但可以通过自定义流跟踪+MimeVisitor的方式实现需求,以下是具体方案:

核心思路

通过包装原始数据流,实时跟踪解析过程中的字节位置;再借助MimeKit的MimeVisitor机制,在解析到附件实体时记录其在原文件中的起始和结束偏移。

实现步骤与代码示例

1. 实现位置跟踪流

创建一个包装流,实时记录当前读取位置:

public class PositionTrackingStream : Stream
{
    private readonly Stream _baseStream;
    public long CurrentPosition { get; private set; }

    public PositionTrackingStream(Stream baseStream)
    {
        _baseStream = baseStream;
        CurrentPosition = baseStream.Position;
    }

    public override bool CanRead => _baseStream.CanRead;
    public override bool CanSeek => _baseStream.CanSeek;
    public override bool CanWrite => _baseStream.CanWrite;
    public override long Length => _baseStream.Length;

    public override long Position
    {
        get => CurrentPosition;
        set
        {
            CurrentPosition = _baseStream.Seek(value, SeekOrigin.Begin);
        }
    }

    public override void Flush() => _baseStream.Flush();

    public override int Read(byte[] buffer, int offset, int count)
    {
        int bytesRead = _baseStream.Read(buffer, offset, count);
        CurrentPosition += bytesRead;
        return bytesRead;
    }

    public override long Seek(long offset, SeekOrigin origin)
    {
        CurrentPosition = _baseStream.Seek(offset, origin);
        return CurrentPosition;
    }

    public override void SetLength(long value) => _baseStream.SetLength(value);

    public override void Write(byte[] buffer, int offset, int count)
    {
        _baseStream.Write(buffer, offset, count);
        CurrentPosition += count;
    }
}

2. 用MimeVisitor跟踪附件位置

自定义MimeVisitor,遍历MIME结构时记录每个附件的位置信息:

// 存储附件与位置的模型类
public class AttachmentPositionInfo
{
    public MimeEntity Attachment { get; set; } = null!;
    public long StartOffset { get; set; }
    public long EndOffset { get; set; }
}

public class AttachmentPositionTracker : MimeVisitor
{
    private readonly PositionTrackingStream _trackingStream;
    public List<AttachmentPositionInfo> AttachmentPositions { get; } = new();
    private long _currentEntityStart;

    public AttachmentPositionTracker(PositionTrackingStream trackingStream)
    {
        _trackingStream = trackingStream;
    }

    protected override void VisitMultipart(Multipart multipart)
    {
        // 遍历子实体前记录当前位置作为实体起始点
        foreach (var entity in multipart)
        {
            _currentEntityStart = _trackingStream.CurrentPosition;
            Visit(entity);
        }
    }

    protected override void VisitAttachment(MimeEntity entity)
    {
        long startOffset = _currentEntityStart;
        
        // 读取附件内容(写入空流),推进流位置到附件结束处
        if (entity is MimePart mimePart)
        {
            mimePart.Content.WriteTo(Stream.Null);
        }
        else if (entity is MessagePart msgPart)
        {
            msgPart.Message.WriteTo(Stream.Null);
        }
        
        long endOffset = _trackingStream.CurrentPosition;

        AttachmentPositions.Add(new AttachmentPositionInfo
        {
            Attachment = entity,
            StartOffset = startOffset,
            EndOffset = endOffset
        });

        base.VisitAttachment(entity);
    }
}

3. 实际使用示例

// 假设decodedDataStream是已解码的可Seek MIME文件流
using var trackingStream = new PositionTrackingStream(decodedDataStream);
var mimeMessage = MimeMessage.Load(trackingStream);

var positionTracker = new AttachmentPositionTracker(trackingStream);
positionTracker.Visit(mimeMessage);

// 遍历附件及对应位置,同时执行原有保存逻辑
int attachmentCount = 0;
foreach (var info in positionTracker.AttachmentPositions)
{
    attachmentCount++;
    string fileName = $"-a{attachmentCount}";
    
    Console.WriteLine($"附件: {info.Attachment.ContentDisposition?.FileName}");
    Console.WriteLine($"起始偏移: {info.StartOffset}, 结束偏移: {info.EndOffset}");

    using (FileStream stream = File.Create(Path.Combine(message.Id + fileName)))
    {
        if (info.Attachment is MessagePart part)
        {
            part.Message.WriteTo(stream);
        }
        else
        {
            var part = (MimePart)info.Attachment;
            part.Content.WriteTo(stream);
        }
    }
}

注意事项

  • 原始流decodedDataStream必须是可Seek的(如本地文件流),若为网络流等不可Seek流,需先缓存到内存流或本地文件后再处理。
  • 上述代码记录的是整个附件实体(含MIME头部和内容)的偏移范围,若仅需附件内容的起始位置,需额外解析MIME头部的字节长度,可通过读取实体头部并计算字节数实现。

内容的提问来源于stack exchange,提问作者Milos

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.16 09:05:37