郵件結構分析
Content-Type: multipart/mixed; boundary="----=..."
// 部分 1: text/plain; charset="gb18030"; encoding=base64
// 部分 2: text/html; charset="gb18030"; encoding=base64
解碼步驟
- 按
boundary 分割多部分。
- 解析每部分 Header,獲取
Content-Transfer-Encoding、charset。
- Body 解碼:
Base64 → Byte[] → Encoding.GetString(Byte[])。
- 優先選擇
text/html,回退 text/plain。
完整代碼
using System;
using System.Text;
using System.Text.RegularExpressions;
public static string DecodePop3Body(string rawMessage) {
// 1. 獲取 boundary
var boundaryMatch = Regex.Match(rawMessage, @"boundary=""([^""]+)""");
if (!boundaryMatch.Success) return DecodeSinglePart(rawMessage);
string boundary = "--" + boundaryMatch.Groups[1].Value;
var parts = rawMessage.Split(new[] { boundary }, StringSplitOptions.RemoveEmptyEntries);
string html = null, plain = null;
foreach (var part in parts) {
var headerEnd = part.IndexOf("\r\n\r\n");
if (headerEnd < 0) continue;
string header = part.Substring(0, headerEnd);
string body = part.Substring(headerEnd + 4).TrimEnd('\r', '\n', '-');
// 解析 encoding & charset
var encMatch = Regex.Match(header, @"Content-Transfer-Encoding:\s*(\w+)", RegexOptions.IgnoreCase);
var charsetMatch = Regex.Match(header, @"charset\s*=\s*[""']?([^""'\s;]+)", RegexOptions.IgnoreCase);
var typeMatch = Regex.Match(header, @"Content-Type:\s*([^;]+)", RegexOptions.IgnoreCase);
string encoding = encMatch.Success ? encMatch.Groups[1].Value.ToLower() : "7bit";
string charset = charsetMatch.Success ? charsetMatch.Groups[1].Value : "utf-8";
string type = typeMatch.Success ? typeMatch.Groups[1].Value.Trim().ToLower() : "text/plain";
string decoded = DecodeContent(body, encoding, charset);
if (type == "text/html") html = decoded;
else if (type == "text/plain") plain = decoded;
}
return html ?? plain ?? "";
}
static string DecodeContent(string body, string encoding, string charset) {
Encoding enc = Encoding.GetEncoding(charset);
byte[] bytes;
if (encoding == "base64") bytes = Convert.FromBase64String(body);
else if (encoding == "quoted-printable") bytes = QuotedPrintableDecode(body);
else bytes = enc.GetBytes(body); // 7bit/8bit
return enc.GetString(bytes);
}
static byte[] QuotedPrintableDecode(string input) {
using (var ms = new MemoryStream()) {
for (int i = 0; i < input.Length; i++) {
char c = input[i];
if (c == '=' && i + 2 < input.Length) {
string hex = input.Substring(i + 1, 2);
if (byte.TryParse(hex, System.Globalization.NumberStyles.HexNumber, null, out byte b)) {
ms.WriteByte(b);
i += 2;
continue;
}
}
ms.WriteByte((byte)c);
}
return ms.ToArray();
}
}
推薦庫
- MimeKit:標準化 MIME 解析,自動處理編碼、邊界、嵌套。
var message = MimeMessage.Load(stream);
- OpenPop.NET:專注 POP3 客戶端,內建 MIME 解析。
編碼陷阱
| 問題 | 解決 |
| gb18030 不識別 | .NET Core 需註冊 Encoding.RegisterProvider(CodePagesEncodingProvider.Instance) (NuGet: System.Text.Encoding.CodePages) |
| Base64 換行符干擾 | 解碼前 body.Replace("\r\n", "").Replace("\n", "") |
| 多層嵌套 multipart | 遞歸解析 boundary,或直接用 MimeKit |