EML邮件查看器
查看和解析EML邮件文件内容。
概述
.eml 文件保存的是邮件在传输途中的原样:先是邮件头,一个空行,然后是正文。邮件
客户端把其中绝大部分藏起来了——平时这没什么问题,直到你需要确认这封邮件实际装了
什么:一个客户端不显示的头字段、一个到达时已成乱码的主题,或者一封渲染异常的邮件
究竟是什么结构。
这个查看器在头与正文的分界处切开文件,把折行的头字段还原成完整值,按声明的字符集 解码 MIME encoded-word,然后同时给出关键字段和原始正文。
使用方法
- 把邮件另存为
.eml,或者复制它的原始源码。Thunderbird 里直接把邮件拖到桌面; Apple Mail 是 显示 → 邮件 → 原始源码;Outlook 用 另存为 → Outlook 邮件 格式 后导出,或者 文件 → 另存为 → 文本。 - 粘贴内容。头字段在前,一个空行,然后是正文——整个格式就这么简单。
- 先读摘要字段,再扫一遍原始正文,看清结构。
encoded-word 与主题乱码的成因
按规范,邮件头只允许 ASCII。非 ASCII 文本要以 encoded-word 的形式携带:
=?charset?encoding?data?=,其中 encoding 取 B(base64)或 Q
(quoted-printable 的一个变体)。
中间那段字符集才是关键,而多数工具恰恰在这里出错。日文主题至今仍普遍以
ISO-2022-JP 发送——那是一种早于 Unicode 的转义序列编码,在若干邮件客户端里依然
是默认值。西欧的邮件也还在用 ISO-8859-1 和 windows-1252。一个默认按 UTF-8
解码的实现会把上面这些统统变成乱码,而结果看上去像文件损坏,不像一次解码失误。
本工具读取声明的字符集并据此解码,于是 =?ISO-2022-JP?B?…?= 会还原成可读的日文,
=?windows-1252?Q?it=92s?= 会还原成带正确排版撇号的 it's。字符集未知或拼错时
回退到 UTF-8,而不是直接失败。
头字段的折行
过长的头字段会被拆成多行,续行以空白字符开头。一个 Received 头,或者一个包含
二十个地址的 To 列表,在文件里会横跨很多行;这种换行本身没有含义——它是传输层的
规则,不是值的一部分。
解析之前会先把折行还原,因此折过行的值会被当成一整个字符串读取。当你自己直接读
原始文本时这点值得记住:用 grep 找某个头字段的值,凡是碰巧换了行的都会漏掉。
正文为什么保持原样
正文严格按它在文件里的样子展示,这是刻意的。
现实中的邮件大多是 multipart/alternative(同一内容的纯文本版本与 HTML 版本),
或者 multipart/mixed(内容加附件)。每个部分都有自己的 Content-Type、自己的
字符集和自己的 Content-Transfer-Encoding,各部分之间由顶层头字段声明的 boundary
字符串分隔。
把其中某一个解码后的部分当作「这封邮件」展示,就会把这套结构藏起来——而结构通常 正是你要看的东西:客户端渲染的是哪一部分、纯文本版与 HTML 版是否一致、附件是否在 你以为的位置。所以原始正文就保持原始。
代价是:quoted-printable 的正文里 = 会显示成 =3D,软换行显示为行尾的 =,
base64 的部分就显示成 base64。如果你确实要解开其中某一段,把那一部分复制出来交给
Base64 解码 或 URL / 百分号解码 处理。
客户端不会给你看的头字段
摘要区列出的是你通常想要的七个字段。其余部分都在你粘贴的原始文本里,其中有好几个 能回答客户端刻意隐藏的问题。
Return-Path是信封发件人——退信寄回的地方。它由发送服务器设置,经常与人眼 看到的From:头不一致。SPF 通过而 DMARC 失败时,通常就是这处不一致造成的。List-Unsubscribe和List-Unsubscribe-Post决定 Gmail 里那个一键退订 按钮是否出现。批量邮件缺了它们,被标为垃圾邮件的概率明显更高。Auto-Submitted标记机器生成的邮件。自动邮件缺这个头,就会触发外出自动回复 和邮件循环。In-Reply-To和References是客户端组织会话线程的依据。一封回复被显示 成新会话,就是少了其中之一。Content-Language和Accept-Language解释了多语言发送方为什么挑了这个 版本。X-*头字段 是发送侧基础设施自行添加的东西。垃圾邮件评分、投放活动 id、 内部队列名都出现在这里,它们也是判定「究竟哪套系统发出了这封邮件」的最快途径。
示例
- 主题变成一串乱码。 把邮件粘进来。如果主题在这里可读,说明发送方没问题,是 接收端的客户端把字符集处理错了。
- 「这封邮件在 Outlook 里显示得不一样。」 去看 boundary 结构。这类反馈绝大多数
都能用一个两部分内容互相不一致的
multipart/alternative解释掉。 - 某个头字段找不到。 客户端只显示经过挑选的一小部分。
List-Unsubscribe、Auto-Submitted、X-Failed-Recipients之类即使界面上一处都不显示,也确实在文件 里躺着。 - 确认自己到底发出了什么。 从自己的已发送文件夹里存一封回来读。编码上的意外, 在原始形态下要好发现得多。
说明
头与正文的分界就是第一个空行,这是格式规定的。完全没有空行的文件会被当成只有头 字段来处理——当有人只粘贴了头部区块时,这正是你想要的行为。
这里不做任何校验。一封 Date 格式错误、缺少 Message-ID、或者 boundary 在正文里
从未出现过的 .eml,都会被原样显示,因为目的是看文件里有什么,而不是看它本应有
什么。
想看投递路径、逐跳延迟以及 SPF、DKIM、DMARC 的结论,用 邮件头解析。它读的是同一批头字段,但带着另一个问题。