#![feature(test)]
#[cfg(test)] mod tests;
#[macro_use] extern crate lazy_static;
use regex::Regex;
use std::fmt::Write;
#[derive(Debug,PartialEq)]
pub enum Error {
Unknown,
InvalidString,
ParseError,
}
impl std::error::Error for Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
Some(self)
}
}
impl std::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Error::Unknown => write!(f, "Error parsing message: Unknown error"),
Error::InvalidString => write!(f, "Error parsing message: Invalid string"),
Error::ParseError => write!(f, "Error parsing message: Invalid document"),
}
}
}
#[derive(Debug,PartialEq)]
pub struct Header {
pub key: String,
pub value: String,
}
impl Header {
pub fn new(key: &str, value: &str) -> Header {
Header {
key: key.to_string(),
value: value.to_string(),
}
}
}
impl std::string::ToString for Header {
fn to_string(&self) -> String {
format!("{}: {}", self.key, self.value)
}
}
trait Search {
fn search(&self, needle: &str) -> Option<String>;
}
impl Search for Vec<Header> {
fn search(&self, needle: &str) -> Option<String> {
for pair in self {
if pair.key.to_lowercase() == needle.to_lowercase() {
return Some(pair.value.to_string())
}
}
None
}
}
trait Boundary {
fn boundary(&self) -> Option<String>;
}
impl Boundary for Vec<Header> {
fn boundary(&self) -> Option<String> {
match self.search("Content-Type") {
None => return None,
Some(ct) => {
let tmp: Vec<&str> = ct.splitn(2, ";").collect();
if tmp.len() > 1 {
let tmp = tmp[1];
let tmp: Vec<&str> = tmp.split(";").collect();
for p in tmp {
let parameter: Vec<&str> = p.split("=").collect();
let key = parameter[0].trim().to_lowercase();
let value = parameter[1].trim();
let value = value.replace("\"", "");
let value = value.trim();
if key == "boundary" {
return Some(value.to_string())
}
}
}
return None
}
}
}
}
trait ToString {
fn to_string(&self) -> String;
}
impl ToString for Vec<Header> {
fn to_string(&self) -> String {
let tmp: Vec<String> = self.iter().map(|x| x.to_string()).collect();
let tmp = tmp.join("\n");
String::from(tmp)
}
}
#[derive(Debug, PartialEq)]
pub enum Section {
Plain {body: Vec<u8>},
Multipart {
headers: Vec<Header>,
body: Vec<Box<Section>>,
},
Empty,
}
impl std::string::ToString for Section {
fn to_string(&self) -> String {
let mut section_string = String::new();
match self {
Section::Plain {body} => write!(&mut section_string, "{}", std::str::from_utf8(body).unwrap()).expect("Error constructing string."),
Section::Multipart {headers, body} => {
write!(&mut section_string, "{}\n", headers.to_string()).expect("Error constructing string.");
let boundary = headers.boundary();
match &boundary {
None => (),
Some(b) => write!(&mut section_string, "--{}", b).expect("Error constructing string."),
};
for section in body {
write!(&mut section_string, "\n{}\n", section.to_string()).expect("Error constructing string.");
match &boundary {
None => (),
Some(b) => write!(&mut section_string, "--{}", b).expect("Error constructing string."),
};
}
match &boundary {
None => (),
Some(_b) => write!(&mut section_string, "--").expect("Error constructing string."),
};
},
Section::Empty => (),
}
section_string
}
}
impl Section {
fn new(raw_section: &str) -> Result<Section, Box<dyn std::error::Error + 'static>> {
if raw_section == "--\n" {
return Ok(Section::Empty);
}
if raw_section == "--" {
return Ok(Section::Empty);
}
if Section::has_headers(raw_section)? {
Section::parse_multipart(raw_section)
} else {
Ok(Section::Plain {body: raw_section.as_bytes().to_vec()})
}
}
fn has_headers(raw_message: &str) -> Result<bool, Box<dyn std::error::Error + 'static>> {
lazy_static! {
static ref RE: Regex = Regex::new(r"(Content-Type|Content-type|content-type): .+?").unwrap();
}
if raw_message.len() > 3000 {
Ok(RE.is_match(&raw_message[0..3000]))
} else {
Ok(RE.is_match(raw_message))
}
}
fn has_boundary(raw_message: &str) -> Result<bool, Box<dyn std::error::Error + 'static>> {
lazy_static! {
static ref RE: Regex = Regex::new(r"(boundary|Boundary)=.+?").unwrap();
}
if raw_message.len() > 3000 {
Ok(RE.is_match(&raw_message[0..3000]))
} else {
Ok(RE.is_match(raw_message))
}
}
fn parse_multipart(raw_section: &str) -> Result<Section, Box<dyn std::error::Error + 'static>> {
if Section::has_boundary(raw_section)? {
lazy_static! {
static ref RE: Regex = Regex::new(r#"(?m)(boundary|Boundary)=(("|')(?P<boundary1>[[:print:]]+?)("|')|(?P<boundary2>[[:print:]]+?($|;)))"#).unwrap();
}
let boundary = match RE.captures(raw_section) {
Some(c) => {
let boundary1 = match c.name("boundary1") {
Some(s) => s.as_str().to_string(),
None => String::new()
};
let boundary2 = match c.name("boundary2") {
Some(s) => s.as_str().to_string(),
None => String::new()
};
if boundary1.len() > 0 {
boundary1
} else if boundary2.len() > 0 {
boundary2
} else {
String::new()
}
},
None => String::new(),
};
if boundary.len() == 0 {
return Err(Box::new(Error::ParseError));
}
let boundary = format!("--{}", boundary);
let raw_sections: Vec<&str> = raw_section.split(boundary.as_str()).collect();
let raw_headers = raw_sections[0];
let headers = parse_headers(raw_headers)?;
let mut sections = Vec::new();
let raw_sections = &raw_sections[1..raw_sections.len() - 1];
for section in raw_sections {
let section = Section::new(§ion)?;
sections.push(Box::new(section));
}
Ok(Section::Multipart {
headers: headers,
body: sections,
})
} else {
lazy_static! {
static ref RE: Regex = Regex::new(r"\n{2,}|\r{2,}").unwrap();
}
let split: Vec<&str> = RE.splitn(raw_section, 2).collect();
let raw_headers = split[0];
let headers = parse_headers(raw_headers)?;
let body = split[1];
let body = Section::new(&body)?;
let sections = vec![Box::new(body)];
Ok(Section::Multipart {
headers: headers,
body: sections,
})
}
}
}
#[derive(Debug)]
pub struct Message {
pub headers: Vec<Header>,
pub sections: Vec<Section>,
}
impl std::string::ToString for Message {
fn to_string(&self) -> String {
let mut message = String::new();
write!(&mut message, "{}\n\n", self.headers.to_string()).expect("Error constructing string.");
let boundary = self.headers.boundary();
match &boundary {
None => (),
Some(b) => write!(&mut message, "--{}", b).expect("Error constructing string."),
};
for section in &self.sections {
write!(&mut message, "\n{}\n", section.to_string()).expect("Error constructing string.");
match &boundary {
None => (),
Some(b) => write!(&mut message, "--{}", b).expect("Error constructing string."),
};
}
match boundary {
None => (),
Some(_b) => write!(&mut message, "--").expect("Error constructing string."),
};
message
}
}
impl Message {
pub fn new(raw_message: &str) -> Result<Message, Box<dyn std::error::Error + 'static>> {
if Message::is_multipart(raw_message)? {
Message::parse_multipart(raw_message)
} else {
Message::parse_plain(raw_message)
}
}
fn is_multipart(raw_message: &str) -> Result<bool, Box<dyn std::error::Error + 'static>> {
lazy_static! {
static ref RE: Regex = Regex::new(r"(Content-Type|Content-type|content-type): multipart.+?").unwrap();
}
Ok(RE.is_match(&raw_message))
}
fn parse_plain(raw_message: &str) -> Result<Message, Box<dyn std::error::Error + 'static>> {
lazy_static! {
static ref RE: Regex = Regex::new(r"\n{2,}|\r{2,}").unwrap();
}
let split: Vec<&str> = RE.splitn(raw_message, 2).collect();
if split.len() != 2 || split[0].len() == 0 || split[1].len() == 0 {
return Err(Box::new(Error::InvalidString));
}
let raw_headers = split[0];
let headers = parse_headers(raw_headers)?;
let tmp = split[1];
let sections = vec![Section::new(&tmp)?];
Ok(Message {
headers: headers,
sections: sections,
})
}
fn parse_multipart(raw_message: &str) -> Result<Message, Box<dyn std::error::Error + 'static>> {
lazy_static! {
static ref RE: Regex = Regex::new(r#"(Boundary|boundary)=("|')(?P<boundary>[[:print:]]+?)("|')"#).unwrap();
}
let b = match RE.captures(raw_message) {
Some(c) => c["boundary"].to_string(),
None => return Err(Box::new(Error::InvalidString)),
};
let boundary = format!("--{}", b);
let raw_parts: Vec<&str> = raw_message.split(boundary.as_str()).collect();
let raw_headers = raw_parts[0];
let headers = parse_headers(raw_headers)?;
let mut sections = Vec::new();
let raw_parts = &raw_parts[1..raw_parts.len()];
for section in raw_parts {
let section = Section::new(section)?;
sections.push(section);
}
Ok(Message {
headers: headers,
sections: sections,
})
}
}
fn parse_headers(raw_headers: &str) -> Result<Vec<Header>, Box<dyn std::error::Error + 'static>> {
lazy_static! {
static ref RE: Regex = Regex::new(r"(?m)^[0-9A-Za-z_\-]+:").unwrap();
}
let mut header_indices: Vec<(usize,usize)> = Vec::new();
for header in RE.find_iter(raw_headers) {
header_indices.push((header.start(), header.end() - 1));
}
let mut headers: Vec<Header> = Vec::new();
for (index, header) in header_indices.iter().enumerate() {
let key = String::from(&raw_headers[header.0..header.1]);
let key = key.trim();
let key = String::from(key);
let key = key.to_lowercase();
if index < header_indices.len() - 1 {
let value = String::from(&raw_headers[header.1 + 2..header_indices[index + 1].0]);
let value = value.trim();
headers.push(Header::new(&key, &value));
} else {
let value = String::from(&raw_headers[header.1 + 2..]);
let value = value.trim();
headers.push(Header::new(&key, &value));
}
}
Ok(headers)
}