use std::collections::HashMap;
use std::collections::hash_map::Entry;
use std::fmt;
use std::ops::Deref;
use std::slice::Iter as SliceIter;
use std::sync::Arc;
use chrono::{
DateTime,
FixedOffset,
UTC,
};
use super::rfc2047::decode_rfc2047;
use super::rfc822::Rfc822DateParser;
use super::results::{ParsingResult,ParsingError};
pub trait FromHeader: Sized {
fn from_header(value: String) -> ParsingResult<Self>;
}
pub trait ToHeader {
fn to_header(value: Self) -> ParsingResult<String>;
}
pub trait ToFoldedHeader {
fn to_folded_header(start_pos: usize, value: Self) -> ParsingResult<String>;
}
impl<T: ToHeader> ToFoldedHeader for T {
fn to_folded_header(_: usize, value: T) -> ParsingResult<String> {
ToHeader::to_header(value)
}
}
impl FromHeader for String {
fn from_header(value: String) -> ParsingResult<String> {
#[derive(Debug,Clone,Copy)]
enum ParseState {
Normal(usize),
SeenEquals(usize),
SeenQuestion(usize, usize),
}
let mut state = ParseState::Normal(0);
let mut decoded = String::new();
let value_slice = &value[..];
for (pos, c) in value.char_indices() {
state = match (state, c) {
(ParseState::SeenQuestion(start_pos, 4), '=') => {
let next_pos = pos + c.len_utf8();
let part_decoded = decode_rfc2047(&value_slice[start_pos..next_pos]);
let to_push = match part_decoded {
Some(ref s) => &s[..],
None => &value_slice[start_pos..pos],
};
decoded.push_str(to_push);
ParseState::Normal(next_pos)
},
(ParseState::SeenQuestion(start_pos, count), '?') => {
ParseState::SeenQuestion(start_pos, count + 1)
},
(ParseState::SeenQuestion(start_pos, count), _) => {
if count > 4 {
ParseState::Normal(start_pos)
} else {
state
}
}
(ParseState::SeenEquals(start_pos), '?') => {
ParseState::SeenQuestion(start_pos, 1)
},
(ParseState::SeenEquals(start_pos), _) => {
ParseState::Normal(start_pos)
}
(ParseState::Normal(start_pos), '=') => {
if start_pos != pos {
decoded.push_str(&value_slice[start_pos..pos]);
}
ParseState::SeenEquals(pos)
},
(ParseState::Normal(_), _) => state,
};
}
let last_start = match state {
ParseState::Normal(start_pos) => start_pos,
ParseState::SeenEquals(start_pos) => start_pos,
ParseState::SeenQuestion(start_pos, _) => start_pos,
};
decoded.push_str(&value_slice[last_start..]);
Ok(decoded)
}
}
impl FromHeader for DateTime<FixedOffset> {
fn from_header(value: String) -> ParsingResult<DateTime<FixedOffset>> {
let mut parser = Rfc822DateParser::new(&value[..]);
parser.consume_datetime()
}
}
impl FromHeader for DateTime<UTC> {
fn from_header(value: String) -> ParsingResult<DateTime<UTC>> {
let dt: ParsingResult<DateTime<FixedOffset>> = FromHeader::from_header(value);
dt.map(|i| i.with_timezone(&UTC))
}
}
impl ToHeader for String {
fn to_header(value: String) -> ParsingResult<String> {
Ok(value)
}
}
impl<'a> ToHeader for &'a str {
fn to_header(value: &'a str) -> ParsingResult<String> {
Ok(value.to_string())
}
}
#[derive(PartialEq, Eq, Clone, Debug, Hash)]
pub struct Header {
pub name: String,
value: String,
}
impl Header {
pub fn new(name: String, value: String) -> Header {
Header {
name: name,
value: value,
}
}
pub fn new_with_value<T: ToFoldedHeader>(name: String, value: T) -> ParsingResult<Header> {
let header_len = name.len() + 2;
ToFoldedHeader::to_folded_header(header_len, value).map(|val| { Header::new(name.clone(), val) })
}
pub fn get_value<T: FromHeader>(&self) -> ParsingResult<T> {
FromHeader::from_header(self.value.clone())
}
}
impl fmt::Display for Header {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
write!(fmt, "{}: {}", self.name, self.value)
}
}
pub struct HeaderIter<'s> {
iter: SliceIter<'s, Arc<Header>>
}
impl<'s> HeaderIter<'s> {
fn new(iter: SliceIter<'s, Arc<Header>>) -> HeaderIter<'s> {
HeaderIter {
iter: iter
}
}
}
impl<'s> Iterator for HeaderIter<'s> {
type Item = &'s Header;
fn next(&mut self) -> Option<&'s Header> {
match self.iter.next() {
Some(s) => Some(s.deref()),
None => None,
}
}
}
#[derive(Eq,PartialEq,Debug,Clone)]
pub struct HeaderMap {
ordered_headers: Vec<Arc<Header>>,
headers: HashMap<String, Vec<Arc<Header>>>,
}
impl HeaderMap {
pub fn new() -> HeaderMap {
HeaderMap {
ordered_headers: Vec::new(),
headers: HashMap::new(),
}
}
pub fn insert(&mut self, header: Header) {
let header_name = header.name.clone();
let rc = Arc::new(header);
self.ordered_headers.push(rc.clone());
match self.headers.entry(header_name) {
Entry::Occupied(mut entry) => {
entry.get_mut().push(rc.clone());
},
Entry::Vacant(entry) => {
let mut header_list = Vec::new();
header_list.push(rc.clone());
entry.insert(header_list);
},
};
}
pub fn iter(&self) -> HeaderIter {
HeaderIter::new(self.ordered_headers.iter())
}
pub fn get(&self, name: String) -> Option<&Header> {
self.headers.get(&name).map(|headers| { headers.last().unwrap() })
.map(|rc| { rc.deref() })
}
pub fn get_value<T: FromHeader>(&self, name: String) -> ParsingResult<T> {
match self.get(name) {
Some(ref header) => header.get_value(),
None => Err(ParsingError::new("Couldn't find header value.".to_string())),
}
}
pub fn len(&self) -> usize {
self.ordered_headers.len()
}
pub fn find(&self, name: &String) -> Option<Vec<&Header>> {
self.headers.get(name)
.map(|rcs| rcs.iter().map(|rc| rc.deref()).collect())
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::collections::HashSet;
use chrono::{
DateTime,
FixedOffset,
UTC,
};
use chrono::offset::TimeZone;
static SAMPLE_HEADERS: [(&'static str, &'static str); 4] = [
("Test", "Value"),
("Test", "Value 2"),
("Test-2", "Value 3"),
("Test-Multiline", "Foo\nBar"),
];
fn make_sample_headers() -> Vec<Header> {
SAMPLE_HEADERS.iter().map(|&(name, value)| {
Header::new(name.to_string(), value.to_string())
}).collect()
}
#[test]
fn test_header_to_string() {
let header = Header::new("Test".to_string(), "Value".to_string());
assert_eq!(header.to_string(), "Test: Value".to_string());
}
#[test]
fn test_string_get_value() {
struct HeaderTest<'s> {
input: &'s str,
result: Option<&'s str>,
}
let tests = vec![
HeaderTest {
input: "Value",
result: Some("Value"),
},
HeaderTest {
input: "=?ISO-8859-1?Q?Test=20text?=",
result: Some("Test text"),
},
HeaderTest {
input: "=?ISO-8859-1?Q?Multiple?= =?utf-8?b?ZW5jb2Rpbmdz?=",
result: Some("Multiple encodings"),
},
HeaderTest {
input: "Some things with =?utf-8?b?ZW5jb2Rpbmdz?=, other things without.",
result: Some("Some things with encodings, other things without."),
},
HeaderTest {
input: "Encoding =?utf-8?q?fail",
result: Some("Encoding =?utf-8?q?fail"),
},
];
for test in tests.into_iter() {
let header = Header::new("Test".to_string(), test.input.to_string());
let string_value = header.get_value::<String>().ok();
assert_eq!(string_value, test.result.map(|s| { s.to_string() }));
}
}
#[test]
fn test_datetime_get_value() {
let header = Header::new("Date".to_string(), "Wed, 17 Dec 2014 09:35:07 +0100".to_string());
let dt_value = header.get_value::<DateTime<FixedOffset>>().unwrap();
assert_eq!(dt_value, FixedOffset::east(3600).ymd(2014, 12, 17).and_hms(9, 35, 7));
}
#[test]
fn test_datetime_utc_get_value() {
let header = Header::new("Date".to_string(), "Wed, 17 Dec 2014 09:35:07 +0100".to_string());
let dt_value = header.get_value::<DateTime<UTC>>().unwrap();
assert_eq!(dt_value, UTC.ymd(2014, 12, 17).and_hms(8, 35, 7));
}
#[test]
fn test_to_header_string() {
let header = Header::new_with_value("Test".to_string(), "Value".to_string()).unwrap();
let header_value = header.get_value::<String>().unwrap();
assert_eq!(header_value, "Value".to_string());
}
#[test]
fn test_to_header_str() {
let header = Header::new_with_value("Test".to_string(), "Value").unwrap();
let header_value = header.get_value::<String>().unwrap();
assert_eq!(header_value, "Value".to_string());
}
#[test]
fn test_header_map_len() {
let mut headers = HeaderMap::new();
for (i, header) in make_sample_headers().into_iter().enumerate() {
headers.insert(header);
assert_eq!(headers.len(), i + 1);
}
}
#[test]
fn test_header_map_iter() {
let mut headers = HeaderMap::new();
let mut expected_headers = HashSet::new();
for header in make_sample_headers().into_iter() {
headers.insert(header.clone());
expected_headers.insert(header);
}
let mut count = 0;
for header in headers.iter() {
assert!(expected_headers.contains(header));
count += 1;
}
assert_eq!(count, expected_headers.len());
}
}