ceal 0.1.0

Opportunistic E-Mail Encryption - Stand-Alone Library and Extensions for Lettre
Documentation
//! This mod contains private utility for dealing with messages.

use log::warn;
use mail_parser::{Header, HeaderName, Message};

/// Check if the `Content-Type` header of the message indicates an encrypted message.
///
/// Note: This function must be called _before_ [`split_content_headers()`]!
pub(crate) fn is_encrypted(msg: &Message<'_>) -> bool {
	if let Some(content_type) = msg
		.header(HeaderName::ContentType)
		.and_then(|content_type| content_type.as_content_type())
	{
		(content_type.ctype() == "application"
			&& content_type.subtype() == Some("pkcs7-mime"))
			|| (content_type.ctype() == "multipart"
				&& content_type.subtype() == Some("encrypted"))
	} else {
		false
	}
}

/// Remove all `Content-Xxx` headers from the message and return them.
pub(crate) fn split_content_headers<'x>(msg: &mut Message<'x>) -> Vec<Header<'x>> {
	let mut i = 0;
	let headers = &mut msg.parts[0].headers;
	let mut content_headers = Vec::new();

	while i < headers.len() {
		if headers[i]
			.name()
			.to_ascii_lowercase()
			.starts_with("content-")
		{
			content_headers.push(headers.remove(i));
		} else {
			i += 1;
		}
	}

	content_headers
}

/// Extract the raw header from the message.
pub(crate) fn raw_header<'m>(msg: &'m Message<'_>, header: &Header<'_>) -> &'m [u8] {
	&msg.raw_message[header.offset_field as usize .. header.offset_end as usize]
}

/// Extract the raw message body from the message.
fn raw_body<'m>(msg: &'m Message<'_>) -> &'m [u8] {
	let part = msg.root_part();
	&msg.raw_message[part.offset_body as usize .. part.offset_end as usize]
}

/// Get the plaintext message, i.e. the part of the message that will then be encrypted.
///
/// This part contains all `Content-Xxx` headers and the message body.
pub(crate) fn plaintext_message(msg: &Message<'_>, headers: &[Header<'_>]) -> Vec<u8> {
	let mut plaintext = Vec::new();

	if headers.is_empty() {
		// Empty headers will lead to some mail clients (looking at you, Evolution)
		// displaying an empty message.
		warn!("Message with empty content headers, inserting Content-Type: text/plain");
		plaintext.extend_from_slice(b"Content-Type: text/plain\r\n");
	} else {
		for header in headers {
			plaintext.extend_from_slice(raw_header(msg, header));
		}
	}

	// Separator between headers and body
	plaintext.extend_from_slice(b"\r\n");

	plaintext.extend_from_slice(raw_body(msg));

	plaintext
}