use std::{borrow::Cow, collections::hash_map::Entry};
use crate::{HeaderName, HeaderOffset, HeaderOffsetName, HeaderValue, OtherHeaders, RfcHeaders};
use super::{
fields::{
address::parse_address, content_type::parse_content_type, date::parse_date, id::parse_id,
list::parse_comma_separared, raw::parse_raw, unstructured::parse_unstructured,
},
message::MessageStream,
};
#[derive(Debug, PartialEq)]
pub enum HeaderParserResult<'x> {
Rfc(HeaderName),
Other(Cow<'x, str>),
Lf,
Eof,
}
pub fn parse_headers<'x>(
headers_rfc: &mut RfcHeaders<'x>,
mut headers_other: Option<&mut OtherHeaders<'x>>,
mut headers_offsets: Option<&mut Vec<HeaderOffset<'x>>>,
stream: &mut MessageStream<'x>,
) -> bool {
loop {
let (bytes_read, result) = parse_header_name(&stream.data[stream.pos..]);
stream.pos += bytes_read;
match result {
HeaderParserResult::Rfc(name) => {
let (is_many, parser) = HDR_PARSER[name as usize];
let from_offset = stream.pos;
let value = parser(stream);
if let Some(headers_offsets) = headers_offsets.as_mut() {
headers_offsets.push(HeaderOffset {
name: HeaderOffsetName::Rfc(name),
start: from_offset,
end: stream.pos,
});
}
if !value.is_empty() {
if is_many {
match headers_rfc.entry(name) {
Entry::Occupied(mut e) => {
if let HeaderValue::Collection(col) = e.get_mut() {
col.push(value);
} else {
let old_value = e.remove();
headers_rfc.insert(
name,
HeaderValue::Collection(vec![old_value, value]),
);
}
}
Entry::Vacant(e) => {
e.insert(value);
}
}
} else {
headers_rfc.insert(name, value);
}
}
}
HeaderParserResult::Other(name) => {
if let Some(headers_other) = headers_other.as_mut() {
let from_offset = stream.pos;
let value = parse_raw(stream);
if let Some(headers_offsets) = headers_offsets.as_mut() {
headers_offsets.push(HeaderOffset {
name: HeaderOffsetName::Other(name.clone()),
start: from_offset,
end: stream.pos,
});
}
if !value.is_empty() {
match headers_other.entry(name.clone()) {
Entry::Occupied(mut e) => {
if let HeaderValue::Collection(col) = e.get_mut() {
col.push(value);
} else {
let old_value = e.remove();
headers_other.insert(
name,
HeaderValue::Collection(vec![old_value, value]),
);
}
}
Entry::Vacant(e) => {
e.insert(value);
}
}
}
}
}
HeaderParserResult::Lf => return true,
HeaderParserResult::Eof => return false,
}
}
}
pub fn parse_header_name(data: &[u8]) -> (usize, HeaderParserResult) {
let mut token_start: usize = 0;
let mut token_end: usize = 0;
let mut token_len: usize = 0;
let mut token_hash: usize = 0;
let mut last_ch: u8 = 0;
let mut bytes_read: usize = 0;
for ch in data.iter() {
bytes_read += 1;
match ch {
b':' => {
if token_start != 0 {
let field = &data[token_start - 1..token_end];
if (2..=25).contains(&token_len) {
token_hash +=
token_len + HDR_HASH[last_ch.to_ascii_lowercase() as usize] as usize;
if (4..=72).contains(&token_hash) {
let token_hash = token_hash - 4;
if field.eq_ignore_ascii_case(HDR_NAMES[token_hash]) {
return (bytes_read, HeaderParserResult::Rfc(HDR_MAP[token_hash]));
}
}
}
return (
bytes_read,
HeaderParserResult::Other(String::from_utf8_lossy(field)),
);
}
}
b'\n' => {
return (bytes_read - 1, HeaderParserResult::Lf);
}
_ => {
if !(*ch).is_ascii_whitespace() {
if token_start == 0 {
token_start = bytes_read;
token_end = token_start;
} else {
token_end = bytes_read;
last_ch = *ch;
}
if let 0 | 9 = token_len {
token_hash += HDR_HASH[(*ch).to_ascii_lowercase() as usize] as usize;
}
token_len += 1;
}
}
}
}
(bytes_read, HeaderParserResult::Eof)
}
impl From<HeaderName> for u8 {
fn from(name: HeaderName) -> Self {
name as u8
}
}
#[cfg(test)]
mod tests {
use crate::{parsers::header::parse_header_name, HeaderName};
use super::HeaderParserResult;
#[test]
fn header_name_parse() {
let inputs = [
("From: ", HeaderParserResult::Rfc(HeaderName::From)),
("receiVED: ", HeaderParserResult::Rfc(HeaderName::Received)),
(
" subject : ",
HeaderParserResult::Rfc(HeaderName::Subject),
),
(
"X-Custom-Field : ",
HeaderParserResult::Other("X-Custom-Field".into()),
),
(" T : ", HeaderParserResult::Other("T".into())),
(
"mal formed: ",
HeaderParserResult::Other("mal formed".into()),
),
(
"MIME-version : ",
HeaderParserResult::Rfc(HeaderName::MimeVersion),
),
];
for input in inputs {
let str = input.0.to_string();
let (_, result) = parse_header_name(str.as_bytes());
assert_eq!(input.1, result, "Failed to parse '{:?}'", input.0);
}
}
}
#[allow(clippy::type_complexity)]
static HDR_PARSER: &[(
bool,
for<'x, 'y> fn(&mut MessageStream<'x>) -> HeaderValue<'x>,
)] = &[
(false, parse_unstructured), (false, parse_address), (false, parse_address), (false, parse_address), (false, parse_date), (false, parse_address), (false, parse_address), (false, parse_address), (true, parse_unstructured), (false, parse_address), (true, parse_comma_separared), (true, parse_raw), (false, parse_id), (true, parse_id), (false, parse_id), (false, parse_raw), (false, parse_unstructured), (false, parse_id), (false, parse_comma_separared), (false, parse_unstructured), (false, parse_unstructured), (false, parse_content_type), (false, parse_content_type), (true, parse_address), (true, parse_address), (true, parse_address), (true, parse_address), (true, parse_address), (true, parse_date), (true, parse_id), (false, parse_address), (false, parse_address), (false, parse_address), (false, parse_address), (false, parse_address), (false, parse_address), (false, parse_address), (true, parse_raw), ];
static HDR_HASH: &[u8] = &[
73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73,
73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73,
73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73,
73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73,
73, 0, 20, 5, 0, 0, 25, 0, 5, 20, 73, 25, 25, 30, 10, 10, 5, 73, 0, 0, 15, 73, 73, 73, 73, 20,
73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73,
73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73,
73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73,
73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73,
73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73,
73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73,
];
static HDR_MAP: &[HeaderName] = &[
HeaderName::Date,
HeaderName::Other,
HeaderName::Sender,
HeaderName::Other,
HeaderName::Received,
HeaderName::Other,
HeaderName::References,
HeaderName::Other,
HeaderName::Cc,
HeaderName::Comments,
HeaderName::ResentCc,
HeaderName::ContentId,
HeaderName::Other,
HeaderName::ResentMessageId,
HeaderName::ReplyTo,
HeaderName::ResentTo,
HeaderName::ResentBcc,
HeaderName::ContentLanguage,
HeaderName::Subject,
HeaderName::ResentSender,
HeaderName::Other,
HeaderName::Other,
HeaderName::ResentDate,
HeaderName::To,
HeaderName::Bcc,
HeaderName::Other,
HeaderName::ContentTransferEncoding,
HeaderName::ReturnPath,
HeaderName::ListId,
HeaderName::Keywords,
HeaderName::ContentDescription,
HeaderName::ListOwner,
HeaderName::Other,
HeaderName::ContentType,
HeaderName::Other,
HeaderName::ListHelp,
HeaderName::MessageId,
HeaderName::ContentLocation,
HeaderName::Other,
HeaderName::Other,
HeaderName::ListSubscribe,
HeaderName::Other,
HeaderName::Other,
HeaderName::Other,
HeaderName::Other,
HeaderName::ListPost,
HeaderName::Other,
HeaderName::ResentFrom,
HeaderName::Other,
HeaderName::Other,
HeaderName::ContentDisposition,
HeaderName::Other,
HeaderName::InReplyTo,
HeaderName::ListArchive,
HeaderName::Other,
HeaderName::From,
HeaderName::Other,
HeaderName::ListUnsubscribe,
HeaderName::Other,
HeaderName::Other,
HeaderName::Other,
HeaderName::Other,
HeaderName::Other,
HeaderName::Other,
HeaderName::Other,
HeaderName::Other,
HeaderName::Other,
HeaderName::Other,
HeaderName::MimeVersion,
];
static HDR_NAMES: &[&[u8]] = &[
b"date",
b"",
b"sender",
b"",
b"received",
b"",
b"references",
b"",
b"cc",
b"comments",
b"resent-cc",
b"content-id",
b"",
b"resent-message-id",
b"reply-to",
b"resent-to",
b"resent-bcc",
b"content-language",
b"subject",
b"resent-sender",
b"",
b"",
b"resent-date",
b"to",
b"bcc",
b"",
b"content-transfer-encoding",
b"return-path",
b"list-id",
b"keywords",
b"content-description",
b"list-owner",
b"",
b"content-type",
b"",
b"list-help",
b"message-id",
b"content-location",
b"",
b"",
b"list-subscribe",
b"",
b"",
b"",
b"",
b"list-post",
b"",
b"resent-from",
b"",
b"",
b"content-disposition",
b"",
b"in-reply-to",
b"list-archive",
b"",
b"from",
b"",
b"list-unsubscribe",
b"",
b"",
b"",
b"",
b"",
b"",
b"",
b"",
b"",
b"",
b"mime-version",
];