Initial commit: WpywMail 自建邮件系统:.NET 8 原生 SMTP/IMAP 服务端(DKIM 签名、SPF/DKIM/DMARC 入站校验、SQLite 存储、完整账号体系)、Node 服务端、WinUI 3 客户端与 Web 前端

This commit is contained in:
WpyQwq
2026-09-19 11:19:40 +08:00
commit b8814a7615
84 changed files with 21197 additions and 0 deletions
+97
View File
@@ -0,0 +1,97 @@
namespace WpywMail.Native;
/// <summary>
/// SMTP DATA 段的线上编码与还原。
///
/// 为什么要单独抽出来:
/// DKIM 是对「签名那一刻的确切字节」做的哈希。任何在签名之后改写报文的行为
/// (例如把裸 LF 换成 CRLF)都会让接收方算出的正文哈希对不上,签名直接失效。
/// 因此约定:**先规范化行尾 → 再签名 → 传输阶段除 dot-stuffing 外不得改动任何字节**。
/// 本类同时被发送侧与自检使用,保证两边行为一致。
/// </summary>
internal static class SmtpDataEncoder
{
private static readonly byte[] Crlf = [13, 10];
/// <summary>把报文规范化为统一的 CRLF 行尾形式(不加密、不加终止行)。应在 DKIM 签名之前调用。</summary>
public static byte[] Normalize(byte[] message)
{
using var output = new MemoryStream(message.Length + 16);
var index = 0;
while (index < message.Length)
{
var current = message[index];
if (current == (byte)'\r')
{
output.Write(Crlf);
index += index + 1 < message.Length && message[index + 1] == (byte)'\n' ? 2 : 1;
continue;
}
if (current == (byte)'\n')
{
output.Write(Crlf);
index++;
continue;
}
output.WriteByte(current);
index++;
}
var bytes = output.ToArray();
if (bytes.Length == 0 || !EndsWithCrlf(bytes))
{
var padded = new byte[bytes.Length + 2];
Buffer.BlockCopy(bytes, 0, padded, 0, bytes.Length);
padded[^2] = 13;
padded[^1] = 10;
return padded;
}
return bytes;
}
/// <summary>
/// 生成 DATA 段实际要写出的字节:行首的点做 dot-stuffing,结尾补 CRLF 与单独一行的 "."。
/// 除 dot-stuffing 外不改动任何字节,以保证与 DKIM 签名一致。
/// </summary>
public static byte[] Encode(byte[] normalizedMessage)
{
using var output = new MemoryStream(normalizedMessage.Length + 16);
var atLineStart = true;
foreach (var current in normalizedMessage)
{
if (atLineStart && current == (byte)'.') output.WriteByte((byte)'.'); // dot-stuffing
output.WriteByte(current);
atLineStart = current == (byte)'\n';
}
if (output.Length == 0 || output.GetBuffer()[output.Length - 1] != (byte)'\n') output.Write(Crlf);
output.Write([(byte)'.', 13, 10]);
return output.ToArray();
}
/// <summary>接收侧还原:去掉终止行并做 dot-unstuffing(自检用于模拟收件端)。</summary>
public static byte[] Decode(byte[] wire)
{
// 去掉结尾的 ".\r\n"
var end = wire.Length;
if (end >= 3 && wire[end - 3] == (byte)'.' && wire[end - 2] == 13 && wire[end - 1] == 10)
end -= 3;
else if (end >= 2 && wire[end - 2] == (byte)'.' && wire[end - 1] == 10)
end -= 2;
using var output = new MemoryStream(end);
var atLineStart = true;
for (var index = 0; index < end; index++)
{
var current = wire[index];
if (atLineStart && current == (byte)'.' && index + 1 < end && wire[index + 1] == (byte)'.')
{
index++; // 去掉填充的点
}
output.WriteByte(wire[index]);
atLineStart = wire[index] == (byte)'\n';
}
return output.ToArray();
}
private static bool EndsWithCrlf(byte[] bytes) =>
bytes.Length >= 2 && bytes[^2] == 13 && bytes[^1] == 10;
}