如何使用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
相关产品推荐
相关产品推荐

