// 原始邮件切分。
//
// DKIM 必须按原始字节做规范化,任何「先解析成 AST 再序列化」的做法都会丢信息,
// 所以这里保留每个头部字段的原始字节,只把行尾统一成 CRLF。
///|
pub struct HeaderField {
/// 原始大小写的字段名
name : String
/// 原始字段字节(不含末尾换行,内部折叠保留为 CRLF + WSP)
raw : Bytes
}
///|
pub struct Message {
headers : Array[HeaderField]
body : Bytes
}
///|
fn is_wsp(b : Byte) -> Bool {
b == b' ' || b == b'\t'
}
///|
fn slice_bytes(data : BytesView, start : Int, end : Int) -> Bytes {
let out : Array[Byte] = []
for i in start.. Bytes {
let out : Array[Byte] = []
let n = input.length()
let mut i = 0
while i < n {
let b = input[i]
if b == b'\n' && !(i > 0 && input[i - 1] == b'\r') {
out.push(b'\r')
}
out.push(b)
i = i + 1
}
Bytes::from_array(out)
}
///|
/// 解析一封邮件:头部按字段切开,正文保留原始字节。
pub fn parse_message(input : BytesView) -> Message {
let raw = normalize_crlf(input)
let n = raw.length()
let headers : Array[HeaderField] = []
let mut pos = 0
let mut body_start = n
while pos < n {
// 当前行范围
let mut line_end = pos
while line_end < n && raw[line_end] != b'\n' {
line_end = line_end + 1
}
let content_end = if line_end > pos && raw[line_end - 1] == b'\r' {
line_end - 1
} else {
line_end
}
if content_end == pos {
// 空行:头部结束
body_start = if line_end < n { line_end + 1 } else { line_end }
break
}
// 一个字段从 pos 开始;字段名到冒号为止
let mut name_end = pos
while name_end < content_end && raw[name_end] != b':' {
name_end = name_end + 1
}
let name = header_name(raw, pos, name_end)
let mut field_end = content_end
let mut next = if line_end < n { line_end + 1 } else { line_end }
// 继续吸收以 WSP 开头的折叠行
while next < n {
let mut folded_end = next
while folded_end < n && raw[folded_end] != b'\n' {
folded_end = folded_end + 1
}
let folded_content_end = if folded_end > next &&
raw[folded_end - 1] == b'\r' {
folded_end - 1
} else {
folded_end
}
if folded_content_end == next || !is_wsp(raw[next]) {
break
}
field_end = folded_content_end
next = if folded_end < n { folded_end + 1 } else { folded_end }
}
headers.push({ name, raw: slice_bytes(raw, pos, field_end), })
pos = next
}
let body = if body_start < n {
slice_bytes(raw, body_start, n)
} else {
Bytes::new(0)
}
{ headers, body, }
}
///|
fn header_name(raw : Bytes, start : Int, end : Int) -> String {
let name = StringBuilder()
for i in start.. String {
let out = StringBuilder()
for c in text {
if c >= 'A' && c <= 'Z' {
out.write_char(Int::unsafe_to_char(c.to_int() + 32))
} else {
out.write_char(c)
}
}
out.to_string()
}
///|
/// 去掉首尾的 WSP。
pub fn trim_wsp(text : StringView) -> String {
let chars : Array[Char] = []
for c in text {
chars.push(c)
}
let mut start = 0
let mut end = chars.length()
while start < end && (chars[start] == ' ' || chars[start] == '\t') {
start = start + 1
}
while end > start && (chars[end - 1] == ' ' || chars[end - 1] == '\t') {
end = end - 1
}
let out = StringBuilder()
for i in start.. Array[HeaderField] {
let target = ascii_lower(name)
let out : Array[HeaderField] = []
for field in self.headers {
if ascii_lower(field.name) == target {
out.push(field)
}
}
out
}