pub fn canonicalize_headers_simple(headers: &[(&str, &str, &str)], signed_headers: &[String]) -> String {
let mut canonicalized_headers = String::new();
let mut already_used = Vec::new();
for signed_header in signed_headers {
for (idx, (name, separator, value)) in headers
.iter()
.enumerate()
.filter(|(idx, _)| !already_used.contains(idx))
{
if unicase::eq_ascii(signed_header.as_str(), name) {
canonicalized_headers.push_str(name);
canonicalized_headers.push_str(separator);
canonicalized_headers.push_str(value);
canonicalized_headers.push_str("\r\n");
already_used.push(idx);
break;
}
}
}
canonicalized_headers
}
pub fn canonicalize_body_simple(mut body: &str) -> &str {
if body.is_empty() {
return "\r\n";
}
while body.ends_with("\r\n\r\n") {
body = &body[..body.len() - 2];
}
body
}
pub fn canonicalize_header_relaxed(mut value: String) -> String {
value = value.replace('\t', " ");
value = value.replace("\r\n", "");
while value.ends_with(' ') {
value.remove(value.len() - 1);
}
while value.starts_with(' ') {
value.remove(0);
}
let mut previous = false;
value.retain(|c| {
if c == ' ' {
if previous {
false
} else {
previous = true;
true
}
} else {
previous = false;
true
}
});
value
}
pub fn canonicalize_headers_relaxed(headers: &[(&str, &str, &str)], signed_headers: &[String]) -> String {
let mut canonicalized_headers = String::new();
let mut already_used = Vec::new();
for signed_header in signed_headers {
for (idx, (name, _separator, value)) in headers
.iter()
.enumerate()
.filter(|(idx, _)| !already_used.contains(idx))
{
if unicase::eq_ascii(signed_header.as_str(), name) {
canonicalized_headers.push_str(&name.to_lowercase());
canonicalized_headers.push_str(":");
canonicalized_headers.push_str(&canonicalize_header_relaxed(value.to_string()));
canonicalized_headers.push_str("\r\n");
already_used.push(idx);
break;
}
}
}
canonicalized_headers
}
pub fn canonicalize_body_relaxed(mut body: String) -> String {
body = body.replace('\t', " ");
let mut previous = false;
body.retain(|c| {
if c == ' ' {
if previous {
false
} else {
previous = true;
true
}
} else {
previous = false;
true
}
});
while let Some(idx) = body.find(" \r\n") {
body.remove(idx);
}
while body.ends_with("\r\n\r\n") {
body.remove(body.len() - 1);
body.remove(body.len() - 1);
}
if !body.is_empty() && !body.ends_with("\r\n") {
body.push_str("\r\n");
}
body
}
#[cfg(test)]
mod test {
use super::*;
use pretty_assertions::assert_eq;
use std::convert::TryFrom;
use string_tools::get_all_after;
use crate::email::Email;
const MAIL: &str = "A: X\r\nB : Y\t\r\n\tZ \r\n\r\n C \r\nD \t E\r\n\r\n\r\n";
#[test]
fn canonicalize_body_relaxed_test() {
assert_eq!(
canonicalize_body_relaxed(get_all_after(MAIL, "\r\n\r\n").to_string()),
" C\r\nD E\r\n"
);
}
#[test]
fn canonicalize_headers_relaxed_test() {
let mail = Email::try_from(MAIL).unwrap();
assert_eq!(
canonicalize_headers_relaxed(&mail.parsed.0, &["a".to_string(), "b".to_string()]),
"a:X\r\nb:Y Z\r\n"
);
assert_eq!(
canonicalize_headers_relaxed(&mail.parsed.0, &["b".to_string(), "a".to_string()]),
"b:Y Z\r\na:X\r\n"
);
}
#[test]
fn canonicalize_body_simple_test() {
assert_eq!(
canonicalize_body_simple(get_all_after(MAIL, "\r\n\r\n")),
" C \r\nD \t E\r\n"
);
}
#[test]
fn canonicalize_headers_simple_test() {
let mail = Email::try_from(MAIL).unwrap();
assert_eq!(
canonicalize_headers_simple(&mail.parsed.0, &["a".to_string(), "b".to_string()]),
"A: X\r\nB : Y\t\r\n\tZ \r\n"
);
assert_eq!(
canonicalize_headers_simple(&mail.parsed.0, &["b".to_string(), "a".to_string()]),
"B : Y\t\r\n\tZ \r\nA: X\r\n"
);
}
}