use crate::error::DkimError;
pub fn find_body_offset(raw: &[u8]) -> Option<usize> {
let mut i = 0;
while i < raw.len() {
if i + 3 < raw.len() && &raw[i..i + 4] == b"\r\n\r\n" {
return Some(i + 4);
}
if i + 1 < raw.len() && raw[i] == b'\n' && raw[i + 1] == b'\n' {
return Some(i + 2);
}
i += 1;
}
None
}
pub fn body_offset_minus_blank(body_offset: usize, raw: &[u8]) -> usize {
if body_offset >= 2 && &raw[body_offset - 2..body_offset] == b"\r\n" {
body_offset - 2
} else if body_offset >= 1 && raw[body_offset - 1] == b'\n' {
body_offset - 1
} else {
body_offset
}
}
pub fn find_header_value_in_raw(headers: &[u8], name: &[u8]) -> Result<String, DkimError> {
let mut i = 0;
while i < headers.len() {
if i + name.len() < headers.len()
&& headers[i..i + name.len()].eq_ignore_ascii_case(name)
&& headers[i + name.len()] == b':'
{
let value_start = i + name.len() + 1;
let mut j = value_start;
while j < headers.len() {
if headers[j] == b'\n' {
let after = j + 1;
if after < headers.len() && matches!(headers[after], b' ' | b'\t') {
j += 1;
continue;
}
return Ok(String::from_utf8_lossy(&headers[value_start..j]).into_owned());
}
j += 1;
}
return Ok(String::from_utf8_lossy(&headers[value_start..j]).into_owned());
}
while i < headers.len() && headers[i] != b'\n' {
i += 1;
}
i += 1;
}
Err(DkimError::MissingHeader)
}
pub fn find_header_value<'a>(headers: &'a [u8], name: &str) -> Option<&'a str> {
let bytes = name.as_bytes();
let mut i = 0;
while i < headers.len() {
if i + bytes.len() < headers.len()
&& headers[i..i + bytes.len()].eq_ignore_ascii_case(bytes)
&& headers[i + bytes.len()] == b':'
{
let value_start = i + bytes.len() + 1;
let mut j = value_start;
while j < headers.len() {
if headers[j] == b'\n' {
let after = j + 1;
if after < headers.len() && matches!(headers[after], b' ' | b'\t') {
j += 1;
continue;
}
let end = if j > value_start && headers[j - 1] == b'\r' {
j - 1
} else {
j
};
return std::str::from_utf8(&headers[value_start..end]).ok();
}
j += 1;
}
return std::str::from_utf8(&headers[value_start..j]).ok();
}
while i < headers.len() && headers[i] != b'\n' {
i += 1;
}
i += 1;
}
None
}
pub fn clear_b_value(value: &str) -> String {
let bytes = value.as_bytes();
let mut out = Vec::with_capacity(bytes.len());
let mut i = 0;
while i < bytes.len() {
let is_b_start = if i + 1 < bytes.len() && bytes[i] == b'b' && bytes[i + 1] == b'=' {
let mut k = i;
while k > 0 {
k -= 1;
if !matches!(bytes[k], b' ' | b'\t' | b'\r' | b'\n') {
break;
}
}
k == 0 || bytes[k] == b';'
} else {
false
};
if is_b_start {
out.extend_from_slice(b"b=");
i += 2;
while i < bytes.len() && bytes[i] != b';' {
i += 1;
}
continue;
}
out.push(bytes[i]);
i += 1;
}
String::from_utf8_lossy(&out).into_owned()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn body_offset_simple_crlf_crlf() {
let raw = b"From: a@b\r\n\r\nbody";
let off = find_body_offset(raw).unwrap();
assert_eq!(&raw[off..], b"body");
}
#[test]
fn body_offset_lf_lf() {
let raw = b"From: a@b\n\nbody";
let off = find_body_offset(raw).unwrap();
assert_eq!(&raw[off..], b"body");
}
#[test]
fn find_header_extracts_value() {
let headers = b"From: alice@example.com\r\nTo: bob@example.com\r\n";
let v = find_header_value(headers, "From");
assert_eq!(v, Some(" alice@example.com"));
}
#[test]
fn find_header_handles_folded() {
let headers = b"Subject: line1\r\n line2\r\nFrom: a@b\r\n";
let v = find_header_value(headers, "Subject");
assert_eq!(v, Some(" line1\r\n line2"));
}
#[test]
fn clear_b_replaces_value_only() {
let v = "i=1; a=rsa-sha256; d=ex.com; s=mail; h=From; bh=BH; b=SIG/+abc==";
assert_eq!(
clear_b_value(v),
"i=1; a=rsa-sha256; d=ex.com; s=mail; h=From; bh=BH; b="
);
}
#[test]
fn find_header_value_in_raw_returns_owned() {
let headers = b"DKIM-Signature: v=1; a=rsa-sha256\r\nFrom: a@b\r\n";
let v = find_header_value_in_raw(headers, b"DKIM-Signature").unwrap();
assert_eq!(v, " v=1; a=rsa-sha256\r");
}
}