diff --git a/src/header/headers.rs b/src/header/headers.rs index ffb5bff..52bb187 100644 --- a/src/header/headers.rs +++ b/src/header/headers.rs @@ -3,71 +3,129 @@ use mailparse::MailHeader; use super::{Header, build_header}; use crate::{CowMailError, extend_over_newline}; +/// Find, iterate and edit a collection of headers by key. +/// +/// Implementors only have to hand out their headers; everything else is +/// provided. Keys match case-insensitively throughout, and an `index` argument +/// counts occurrences of `key`, not positions in the collection. pub trait HeaderMap<'a, H: Header<'a> + 'a> { - fn iter_headers(&'a self, key: &str) -> impl Iterator; - fn iter_headers_mut(&'a mut self, key: &str) -> impl Iterator; - fn iter_values(&'a self, key: &str) -> impl Iterator; - fn find_header(&self, key: &str) -> Option<&H>; - fn find_header_mut(&mut self, key: &str) -> Option<&mut H>; - fn find_value(&self, key: &str) -> Option; - fn add_header(&mut self, key: K, value: V); - fn insert_header(&mut self, key: K, value: V, index: usize); - fn replace_header(&mut self, key: K, value: V, index: usize); - fn replace_all_headers(&mut self, key: K, value: V); - fn remove_header(&mut self, key: &str, index: usize); - fn remove_all_headers(&mut self, key: &str); -} + /// The headers, in the order they appear in the message. + fn headers(&self) -> &[H]; -impl<'a, H: Header<'a> + 'a> HeaderMap<'a, H> for Vec { - fn iter_headers(&'a self, key: &str) -> impl Iterator { - self.iter().filter(|h| h.key_eq(key)) + /// The headers, mutably. A `Vec` rather than a slice, because inserting and + /// removing go through it. + fn headers_mut(&mut self) -> &mut Vec; + + fn iter_headers<'b>(&'b self, key: &str) -> impl Iterator + where + H: 'b, + { + self.headers().iter().filter(move |h| h.key_eq(key)) } - fn iter_headers_mut(&'a mut self, key: &str) -> impl Iterator { - self.iter_mut() - .filter(|h| h.key_eq(key)) + fn iter_headers_mut<'b>(&'b mut self, key: &str) -> impl Iterator + where + H: 'b, + { + self.headers_mut().iter_mut().filter(move |h| h.key_eq(key)) } - fn iter_values(&'a self, key: &str) -> impl Iterator { + /// Examples + /// ``` + /// use email_message::{CowMail, HeaderMap}; + /// let mail = CowMail::try_from(concat!( + /// "Subject: Test\n", + /// "Subject: Test1\n", + /// "\n", + /// "This is a test message").as_bytes()) + /// .unwrap(); + /// let mut values = mail.iter_values("Subject"); + /// assert_eq!(values.next().unwrap(), "Test"); + /// assert_eq!(values.next().unwrap(), "Test1"); + /// assert!(values.next().is_none()); + /// ``` + fn iter_values<'b>(&'b self, key: &str) -> impl Iterator + where + H: 'b, + { self.iter_headers(key).map(|h| h.get_value()) } fn find_header(&self, key: &str) -> Option<&H> { - self.iter().find(|h| h.key_eq(key)) + self.headers().iter().find(|h| h.key_eq(key)) } fn find_header_mut(&mut self, key: &str) -> Option<&mut H> { - self.iter_mut().find(|h| h.key_eq(key)) + self.headers_mut().iter_mut().find(|h| h.key_eq(key)) } + /// Examples + /// ``` + /// use email_message::{CowMail, HeaderMap}; + /// let mail = CowMail::try_from(concat!( + /// "Subject: Test\n", + /// "Subject: Test1", + /// "\n", + /// "This is a test message").as_bytes()) + /// .unwrap(); + /// assert_eq!(mail.find_value("Subject").unwrap(), "Test"); + /// ``` fn find_value(&self, key: &str) -> Option { self.find_header(key).map(|h| h.get_value()) } + /// Examples + /// ``` + /// use email_message::{CowMail, HeaderMap}; + /// let mut mail = CowMail::default(); + /// mail.add_header("Subject", "Test"); + /// assert_eq!(mail.find_value("Subject").unwrap(), "Test"); + /// ``` fn add_header(&mut self, key: K, value: V) { - self.push(H::new(key, value)) + self.headers_mut().push(H::new(key, value)) } + /// Examples + /// ``` + /// use email_message::{CowMail, Header, HeaderMap}; + /// let mut mail = CowMail::try_from(concat!( + /// "Subject: Test\n", + /// "To: \"John Doe\" \n", + /// "\n", + /// "This is a test message").as_bytes()) + /// .unwrap(); + /// mail.insert_header("From", "\"me\" ", 1); + /// assert_eq!(mail.headers[0].get_key(), "Subject"); + /// assert_eq!(mail.headers[1].get_key(), "From"); + /// assert_eq!(mail.headers[2].get_key(), "To"); + /// ``` fn insert_header(&mut self, key: K, value: V, index: usize) { - let index = std::cmp::min(index, self.len()); + let headers = self.headers_mut(); + let index = std::cmp::min(index, headers.len()); let header = H::new(key, value); - self.insert(index, header); - } - - fn replace_all_headers(&mut self, key: K, value: V) { - let key = key.to_string(); - for header in self { - if !header.get_key().eq_ignore_ascii_case(&key) { - continue; - } - header.set_value(value.to_string()); - } + headers.insert(index, header); } + /// Examples + /// ``` + /// use email_message::{CowMail, Header, HeaderMap}; + /// let mut mail = CowMail::try_from(concat!( + /// "Subject: Test1\n", + /// "To: \"John Doe\" \n", + /// "Subject: Test2\n", + /// "\n", + /// "This is a test message").as_bytes()) + /// .unwrap(); + /// mail.replace_header("Subject", "Test3", 0); + /// mail.replace_header("Subject", "Test4", 1); + /// assert_eq!(mail.headers[0].get_value_ref(), "Test3"); + /// assert_eq!(mail.headers[1].get_value_ref(), "\"John Doe\" "); + /// assert_eq!(mail.headers[2].get_value_ref(), "Test4"); + /// ``` fn replace_header(&mut self, key: K, value: V, index: usize) { let key = key.to_string(); let mut count: usize = 0; - for header in self { + for header in self.headers_mut() { if !header.get_key().eq_ignore_ascii_case(&key) { continue; } @@ -79,20 +137,86 @@ impl<'a, H: Header<'a> + 'a> HeaderMap<'a, H> for Vec { } } - fn remove_all_headers(&mut self, key: &str) { - self.retain(|h| !h.key_eq(key)); + /// Examples + /// ``` + /// use email_message::{CowMail, Header, HeaderMap}; + /// let mut mail = CowMail::try_from(concat!( + /// "Subject: Test\n", + /// "To: \"John Doe\" \n", + /// "Subject: Test1\n", + /// "\n", + /// "This is a test message").as_bytes()) + /// .unwrap(); + /// mail.replace_all_headers("Subject", "Test2"); + /// assert_eq!(mail.headers[0].get_value_ref(), "Test2"); + /// assert_eq!(mail.headers[1].get_value_ref(), "\"John Doe\" "); + /// assert_eq!(mail.headers[2].get_value_ref(), "Test2"); + /// ``` + fn replace_all_headers(&mut self, key: K, value: V) { + let key = key.to_string(); + for header in self.headers_mut() { + if !header.get_key().eq_ignore_ascii_case(&key) { + continue; + } + header.set_value(value.to_string()); + } } + /// Examples + /// ``` + /// use email_message::{CowMail, Header, HeaderMap}; + /// let mut mail = CowMail::try_from(concat!( + /// "Subject: Test1\n", + /// "To: \"John Doe\" \n", + /// "Subject: Test2\n", + /// "\n", + /// "This is a test message").as_bytes()) + /// .unwrap(); + /// mail.remove_header("Subject", 1); + /// assert_eq!(mail.headers.len(), 2); + /// assert_eq!(mail.headers[0].get_value_ref(), "Test1"); + /// ``` fn remove_header(&mut self, key: &str, index: usize) { - if let Some((index, _)) = self + let found = self + .headers() .iter() .enumerate() - .filter(move |h| h.1.key_eq(key)) + .filter(|h| h.1.key_eq(key)) .nth(index) - { - self.remove(index); + .map(|(index, _)| index); + + if let Some(index) = found { + self.headers_mut().remove(index); } } + + /// Examples + /// ``` + /// use email_message::{CowMail, Header, HeaderMap}; + /// let mut mail = CowMail::try_from(concat!( + /// "Subject: Test1\n", + /// "To: \"John Doe\" \n", + /// "Subject: Test2\n", + /// "\n", + /// "This is a test message").as_bytes()) + /// .unwrap(); + /// mail.remove_all_headers("Subject"); + /// assert_eq!(mail.headers.len(), 1); + /// assert_eq!(mail.headers[0].get_value_ref(), "\"John Doe\" "); + /// ``` + fn remove_all_headers(&mut self, key: &str) { + self.headers_mut().retain(|h| !h.key_eq(key)); + } +} + +impl<'a, H: Header<'a> + 'a> HeaderMap<'a, H> for Vec { + fn headers(&self) -> &[H] { + self + } + + fn headers_mut(&mut self) -> &mut Vec { + self + } } /// Rebuild the header lines mailparse has already scanned, so that a part's diff --git a/src/lib.rs b/src/lib.rs index ce446f9..73e22fb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -666,157 +666,12 @@ impl<'a, H: Header<'a> + 'a> CowMail<'a, H> { } impl<'a, H: Header<'a> + 'a> HeaderMap<'a, H> for CowMail<'a, H> { - fn iter_headers(&'a self, key: &str) -> impl Iterator { - self.headers.iter_headers(key) + fn headers(&self) -> &[H] { + &self.headers } - fn iter_headers_mut(&'a mut self, key: &str) -> impl Iterator { - self.headers.iter_headers_mut(key) - } - - fn find_header(&self, key: &str) -> Option<&H> { - self.headers.find_header(key) - } - - fn find_header_mut(&mut self, key: &str) -> Option<&mut H> { - self.headers.find_header_mut(key) - } - - /// Examples - /// ``` - /// use email_message::{CowMail, HeaderMap}; - /// let mail = CowMail::try_from(concat!( - /// "Subject: Test\n", - /// "Subject: Test1\n", - /// "\n", - /// "This is a test message").as_bytes()) - /// .unwrap(); - /// let mut values = mail.iter_values("Subject"); - /// assert_eq!(values.next().unwrap(), "Test"); - /// assert_eq!(values.next().unwrap(), "Test1"); - /// assert!(values.next().is_none()); - /// ``` - fn iter_values(&'a self, key: &str) -> impl Iterator { - self.headers.iter_values(key) - } - - /// Examples - /// ``` - /// use email_message::{CowMail, HeaderMap}; - /// let mail = CowMail::try_from(concat!( - /// "Subject: Test\n", - /// "Subject: Test1", - /// "\n", - /// "This is a test message").as_bytes()) - /// .unwrap(); - /// assert_eq!(mail.find_value("Subject").unwrap(), "Test"); - /// ``` - fn find_value(&self, key: &str) -> Option { - self.headers.find_value(key) - } - - /// Examples - /// ``` - /// use email_message::{CowMail, HeaderMap}; - /// let mut mail = CowMail::default(); - /// mail.add_header("Subject", "Test"); - /// assert_eq!(mail.find_value("Subject").unwrap(), "Test"); - /// ``` - fn add_header(&mut self, key: K, value: V) { - self.headers.add_header(key, value) - } - - /// Examples - /// ``` - /// use email_message::{CowMail, Header, HeaderMap}; - /// let mut mail = CowMail::try_from(concat!( - /// "Subject: Test\n", - /// "To: \"John Doe\" \n", - /// "\n", - /// "This is a test message").as_bytes()) - /// .unwrap(); - /// mail.insert_header("From", "\"me\" ", 1); - /// assert_eq!(mail.headers[0].get_key(), "Subject"); - /// assert_eq!(mail.headers[1].get_key(), "From"); - /// assert_eq!(mail.headers[2].get_key(), "To"); - /// ``` - fn insert_header(&mut self, key: K, value: V, index: usize) { - self.headers.insert_header(key, value, index) - } - - /// Examples - /// ``` - /// use email_message::{CowMail, Header, HeaderMap}; - /// let mut mail = CowMail::try_from(concat!( - /// "Subject: Test\n", - /// "To: \"John Doe\" \n", - /// "Subject: Test1\n", - /// "\n", - /// "This is a test message").as_bytes()) - /// .unwrap(); - /// mail.replace_all_headers("Subject", "Test2"); - /// assert_eq!(mail.headers[0].get_value_ref(), "Test2"); - /// assert_eq!(mail.headers[1].get_value_ref(), "\"John Doe\" "); - /// assert_eq!(mail.headers[2].get_value_ref(), "Test2"); - /// ``` - fn replace_all_headers(&mut self, key: K, value: V) { - self.headers.replace_all_headers(key, value) - } - - /// Examples - /// ``` - /// use email_message::{CowMail, Header, HeaderMap}; - /// let mut mail = CowMail::try_from(concat!( - /// "Subject: Test1\n", - /// "To: \"John Doe\" \n", - /// "Subject: Test2\n", - /// "\n", - /// "This is a test message").as_bytes()) - /// .unwrap(); - /// mail.replace_header("Subject", "Test3", 0); - /// mail.replace_header("Subject", "Test4", 1); - /// assert_eq!(mail.headers[0].get_value_ref(), "Test3"); - /// assert_eq!(mail.headers[1].get_value_ref(), "\"John Doe\" "); - /// assert_eq!(mail.headers[2].get_value_ref(), "Test4"); - /// ``` - fn replace_header(&mut self, key: K, value: V, index: usize) { - self.headers.replace_header(key, value, index) - } - - /// Examples - /// ``` - /// use email_message::{CowMail, Header, HeaderMap}; - /// let mut mail = CowMail::try_from(concat!( - /// "Subject: Test1\n", - /// "To: \"John Doe\" \n", - /// "Subject: Test2\n", - /// "\n", - /// "This is a test message").as_bytes()) - /// .unwrap(); - /// mail.remove_all_headers("Subject"); - /// assert_eq!(mail.headers.len(), 1); - /// assert_eq!(mail.headers[0].get_value_ref(), "\"John Doe\" "); - /// ``` - fn remove_all_headers(&mut self, key: &str) { - self.headers.remove_all_headers(key) - } - - /// Examples - /// ``` - /// use email_message::{CowMail, Header, HeaderMap}; - /// let mut mail = CowMail::try_from(concat!( - /// "Subject: Test1\n", - /// "To: \"John Doe\" \n", - /// "Subject: Test2\n", - /// "\n", - /// "This is a test message").as_bytes()) - /// .unwrap(); - /// mail.remove_header("Subject", 1); - /// assert_eq!(mail.headers.len(), 2); - /// assert_eq!(mail.headers[0].get_value_ref(), "Test1"); - /// ``` - fn remove_header(&mut self, key: &str, index: usize) { - self.headers.remove_header(key, index) + fn headers_mut(&mut self) -> &mut Vec { + &mut self.headers } }