use std::borrow::Cow;
use crate::{Header, HeaderName, HeaderValue, RfcHeader};
use super::{
fields::{
address::parse_address,
content_type::parse_content_type,
date::parse_date,
id::parse_id,
list::parse_comma_separared,
raw::{parse_and_ignore, parse_raw},
unstructured::parse_unstructured,
},
message::MessageStream,
};
#[derive(Debug, PartialEq, Eq)]
pub enum HeaderParserResult<'x> {
Rfc(RfcHeader),
Other(Cow<'x, str>),
Lf,
Eof,
}
pub fn parse_headers<'x>(headers: &mut Vec<Header<'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 (_, parser) = HDR_PARSER[name as usize];
let from_offset = stream.pos;
headers.push(Header {
name: HeaderName::Rfc(name),
value: parser(stream),
offset_start: from_offset,
offset_end: stream.pos,
});
}
HeaderParserResult::Other(name) => {
let from_offset = stream.pos;
parse_and_ignore(stream);
headers.push(Header {
name: HeaderName::Other(name),
value: HeaderValue::Text(String::from_utf8_lossy(
&stream.data[from_offset..stream.pos],
)),
offset_start: from_offset,
offset_end: stream.pos,
});
}
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 {
break;
}
}
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;
}
}
}
}
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)),
);
} else {
(bytes_read, HeaderParserResult::Eof)
}
}
impl From<RfcHeader> for u8 {
fn from(name: RfcHeader) -> Self {
name as u8
}
}
#[cfg(test)]
mod tests {
use crate::{parsers::header::parse_header_name, RfcHeader};
use super::HeaderParserResult;
#[test]
fn header_name_parse() {
let inputs = [
("From: ", HeaderParserResult::Rfc(RfcHeader::From)),
("receiVED: ", HeaderParserResult::Rfc(RfcHeader::Received)),
(" subject : ", HeaderParserResult::Rfc(RfcHeader::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(RfcHeader::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_id), (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: &[RfcHeader] = &[
RfcHeader::Date,
RfcHeader::MimeVersion, RfcHeader::Sender,
RfcHeader::MimeVersion, RfcHeader::Received,
RfcHeader::MimeVersion, RfcHeader::References,
RfcHeader::MimeVersion, RfcHeader::Cc,
RfcHeader::Comments,
RfcHeader::ResentCc,
RfcHeader::ContentId,
RfcHeader::MimeVersion, RfcHeader::ResentMessageId,
RfcHeader::ReplyTo,
RfcHeader::ResentTo,
RfcHeader::ResentBcc,
RfcHeader::ContentLanguage,
RfcHeader::Subject,
RfcHeader::ResentSender,
RfcHeader::MimeVersion, RfcHeader::MimeVersion, RfcHeader::ResentDate,
RfcHeader::To,
RfcHeader::Bcc,
RfcHeader::MimeVersion, RfcHeader::ContentTransferEncoding,
RfcHeader::ReturnPath,
RfcHeader::ListId,
RfcHeader::Keywords,
RfcHeader::ContentDescription,
RfcHeader::ListOwner,
RfcHeader::MimeVersion, RfcHeader::ContentType,
RfcHeader::MimeVersion, RfcHeader::ListHelp,
RfcHeader::MessageId,
RfcHeader::ContentLocation,
RfcHeader::MimeVersion, RfcHeader::MimeVersion, RfcHeader::ListSubscribe,
RfcHeader::MimeVersion, RfcHeader::MimeVersion, RfcHeader::MimeVersion, RfcHeader::MimeVersion, RfcHeader::ListPost,
RfcHeader::MimeVersion, RfcHeader::ResentFrom,
RfcHeader::MimeVersion, RfcHeader::MimeVersion, RfcHeader::ContentDisposition,
RfcHeader::MimeVersion, RfcHeader::InReplyTo,
RfcHeader::ListArchive,
RfcHeader::MimeVersion, RfcHeader::From,
RfcHeader::MimeVersion, RfcHeader::ListUnsubscribe,
RfcHeader::MimeVersion, RfcHeader::MimeVersion, RfcHeader::MimeVersion, RfcHeader::MimeVersion, RfcHeader::MimeVersion, RfcHeader::MimeVersion, RfcHeader::MimeVersion, RfcHeader::MimeVersion, RfcHeader::MimeVersion, RfcHeader::MimeVersion, RfcHeader::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",
];