Add MIME support

- Wrap encrypted mails into a mime structure
- Add 'protected headers'
This commit is contained in:
Malte Meiboom
2026-06-08 10:58:25 +02:00
parent e43f627e1b
commit d24aaf83e4
12 changed files with 638 additions and 18 deletions
+128
View File
@@ -0,0 +1,128 @@
//! CHeader struct
//!
//! This struct is for collecting header lines from the milter.
use std::ffi::CString;
use std::fmt;
pub struct CHeader {
lines: Vec<(CString, CString)>
}
impl CHeader {
pub fn new() -> Self {
CHeader {
lines: Vec::new()
}
}
pub fn add(mut self, key: CString, value: CString) -> Self {
self.lines.push((key, value));
self
}
pub fn append(&mut self, key: CString, value: CString) {
self.lines.push((key, value));
}
pub fn find(&self, key: &str) -> Option<(&str, &str)> {
let lower_key = key.to_lowercase();
for (line_key, line_value) in self.lines.iter() {
if let Ok(line_compare) = line_key.to_str() {
if line_compare.to_ascii_lowercase() == lower_key {
if let Ok(line_value_as_str) = line_value.to_str() {
return Some(
(line_compare, line_value_as_str)
);
}
}
}
}
None
}
pub fn get_value_or<'ch>(&'ch self, key: &str, default: &'ch str) -> &'ch str {
if let Some((_, value)) = self.find(key) {
value
} else {
default
}
}
pub fn iter_lines(&self) -> std::slice::Iter<'_, (CString, CString)> {
self.lines.iter()
}
}
impl fmt::Display for CHeader {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", [
self.iter_lines()
.filter_map(|(k, v)| {
match (k.to_str(), v.to_str()) {
(Ok(k), Ok(v)) => Some([k, v].join(": ")),
_ => None,
}
})
.collect::<Vec<String>>()
.join("\r\n"),
"\r\n".to_string()]
.join("")
)
}
}
#[cfg(test)]
pub mod tests {
use std::ffi::CString;
use super::CHeader;
#[test]
pub fn basic_functionality() {
let cheader = CHeader::new()
.add(CString::new("Content-Type").unwrap(), CString::new("text/plain").unwrap())
.add(CString::new("Content-Transfer-Encoding").unwrap(), CString::new("7bit").unwrap());
assert_eq!("Content-Type: text/plain\r\nContent-Transfer-Encoding: 7bit\r\n", cheader.to_string());
}
#[test]
pub fn find() {
let cheader = CHeader::new()
.add(CString::new("Content-Type").unwrap(), CString::new("text/plain").unwrap())
.add(CString::new("Content-Transfer-Encoding").unwrap(), CString::new("7bit").unwrap());
let line = cheader.find("content-type");
assert!(line.is_some());
let (key, value) = line.unwrap();
assert_eq!("Content-Type", key);
assert_eq!("text/plain", value);
}
#[test]
pub fn find_none() {
let cheader = CHeader::new()
.add(CString::new("Content-Type").unwrap(), CString::new("text/plain").unwrap())
.add(CString::new("Content-Transfer-Encoding").unwrap(), CString::new("7bit").unwrap());
let line = cheader.find("foo-bar");
assert!(line.is_none());
}
#[test]
pub fn find_with_default() {
let cheader = CHeader::new()
.add(CString::new("Content-Type").unwrap(), CString::new("text/plain").unwrap())
.add(CString::new("Content-Transfer-Encoding").unwrap(), CString::new("7bit").unwrap());
let value = cheader.get_value_or("foo-bar", "baaz");
assert_eq!(value, "baaz");
}
}