document HeaderMap, body transfer-encoding, fix Examples headings
This commit is contained in:
@@ -85,4 +85,4 @@ assert!(mail.is_multipart());
|
|||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
GPL-3.0. See [LICENSE](LICENSE).
|
GPL-3.0. See the `LICENSE` file in the repository.
|
||||||
|
|||||||
+30
@@ -11,12 +11,21 @@ use std::mem;
|
|||||||
use email_encoding::body::{Encoding, base64};
|
use email_encoding::body::{Encoding, base64};
|
||||||
|
|
||||||
/// The `Content-Transfer-Encoding` of a body, as defined in RFC 2045 section 6.
|
/// The `Content-Transfer-Encoding` of a body, as defined in RFC 2045 section 6.
|
||||||
|
///
|
||||||
|
/// [`Display`] writes the value as it belongs in the header. The values are case-insensitive per
|
||||||
|
/// RFC 2045 section 6.1, which is why decoding lowercases the header before matching on it.
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub enum ContentTransferEncoding {
|
pub enum ContentTransferEncoding {
|
||||||
|
/// `7bit`: ASCII, short lines, no encoding applied. The default when no header is present, and
|
||||||
|
/// the only one [`crate::CowMail::set_content_raw`] leaves undeclared.
|
||||||
SevenBit,
|
SevenBit,
|
||||||
|
/// `8bit`: unencoded bytes above 127, in lines short enough to transmit.
|
||||||
EightBit,
|
EightBit,
|
||||||
|
/// `quoted-printable`: mostly-ASCII content with the rest escaped as `=XX`.
|
||||||
QuotedPrintable,
|
QuotedPrintable,
|
||||||
|
/// `base64`: the choice for binary content and for text that is largely non-ASCII.
|
||||||
Base64,
|
Base64,
|
||||||
|
/// `binary`: unencoded bytes with no line-length limit. Never chosen by [`encode`].
|
||||||
Binary,
|
Binary,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -39,7 +48,9 @@ impl Display for ContentTransferEncoding {
|
|||||||
/// always ends up as `base64`.
|
/// always ends up as `base64`.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum BodyContent {
|
pub enum BodyContent {
|
||||||
|
/// Valid UTF-8 text. Line endings are normalized to CRLF by [`encode`].
|
||||||
Text(String),
|
Text(String),
|
||||||
|
/// Arbitrary bytes, always base64-encoded.
|
||||||
Binary(Vec<u8>),
|
Binary(Vec<u8>),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -79,6 +90,25 @@ impl From<BodyContent> for Vec<u8> {
|
|||||||
/// Encode `content`, choosing the most compact of `7bit`, `quoted-printable` and `base64`.
|
/// Encode `content`, choosing the most compact of `7bit`, `quoted-printable` and `base64`.
|
||||||
///
|
///
|
||||||
/// Line endings of text content are normalized to CRLF.
|
/// Line endings of text content are normalized to CRLF.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use email_message::{ContentTransferEncoding, body::encode};
|
||||||
|
///
|
||||||
|
/// // Plain ASCII needs no encoding at all.
|
||||||
|
/// let (bytes, encoding) = encode("hello");
|
||||||
|
/// assert_eq!(bytes, b"hello");
|
||||||
|
/// assert_eq!(encoding, ContentTransferEncoding::SevenBit);
|
||||||
|
///
|
||||||
|
/// // Line endings are normalized on the way through.
|
||||||
|
/// let (bytes, _) = encode("one\ntwo");
|
||||||
|
/// assert_eq!(bytes, b"one\r\ntwo");
|
||||||
|
///
|
||||||
|
/// // Bytes are always base64.
|
||||||
|
/// let (_, encoding) = encode(&b"\x89PNG\r\n\x1a\n"[..]);
|
||||||
|
/// assert_eq!(encoding, ContentTransferEncoding::Base64);
|
||||||
|
/// ```
|
||||||
pub fn encode(content: impl Into<BodyContent>) -> (Vec<u8>, ContentTransferEncoding) {
|
pub fn encode(content: impl Into<BodyContent>) -> (Vec<u8>, ContentTransferEncoding) {
|
||||||
let mut content = content.into();
|
let mut content = content.into();
|
||||||
|
|
||||||
|
|||||||
+89
-8
@@ -10,6 +10,10 @@ use crate::{CowMailError, extend_over_newline};
|
|||||||
/// Implementors only have to hand out their headers; everything else is
|
/// Implementors only have to hand out their headers; everything else is
|
||||||
/// provided. Keys match case-insensitively throughout, and an `index` argument
|
/// provided. Keys match case-insensitively throughout, and an `index` argument
|
||||||
/// counts occurrences of `key`, not positions in the collection.
|
/// counts occurrences of `key`, not positions in the collection.
|
||||||
|
///
|
||||||
|
/// [`headers_mut`](Self::headers_mut) hands out a `&mut Vec<H>` rather than a slice, because
|
||||||
|
/// inserting and removing go through it — the one constraint this trait puts on an implementor's
|
||||||
|
/// storage. Implemented for `Vec<H>` and for [`CowMail`](crate::CowMail).
|
||||||
pub trait HeaderMap<'a, H: Header<'a> + 'a> {
|
pub trait HeaderMap<'a, H: Header<'a> + 'a> {
|
||||||
/// The headers, in the order they appear in the message.
|
/// The headers, in the order they appear in the message.
|
||||||
fn headers(&self) -> &[H];
|
fn headers(&self) -> &[H];
|
||||||
@@ -18,6 +22,7 @@ pub trait HeaderMap<'a, H: Header<'a> + 'a> {
|
|||||||
/// removing go through it.
|
/// removing go through it.
|
||||||
fn headers_mut(&mut self) -> &mut Vec<H>;
|
fn headers_mut(&mut self) -> &mut Vec<H>;
|
||||||
|
|
||||||
|
/// Every header with this key, in message order.
|
||||||
fn iter_headers<'b>(&'b self, key: &str) -> impl Iterator<Item = &'b H>
|
fn iter_headers<'b>(&'b self, key: &str) -> impl Iterator<Item = &'b H>
|
||||||
where
|
where
|
||||||
H: 'b,
|
H: 'b,
|
||||||
@@ -25,6 +30,7 @@ pub trait HeaderMap<'a, H: Header<'a> + 'a> {
|
|||||||
self.headers().iter().filter(move |h| h.key_eq(key))
|
self.headers().iter().filter(move |h| h.key_eq(key))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Every header with this key, mutably, in message order.
|
||||||
fn iter_headers_mut<'b>(&'b mut self, key: &str) -> impl Iterator<Item = &'b mut H>
|
fn iter_headers_mut<'b>(&'b mut self, key: &str) -> impl Iterator<Item = &'b mut H>
|
||||||
where
|
where
|
||||||
H: 'b,
|
H: 'b,
|
||||||
@@ -32,7 +38,9 @@ pub trait HeaderMap<'a, H: Header<'a> + 'a> {
|
|||||||
self.headers_mut().iter_mut().filter(move |h| h.key_eq(key))
|
self.headers_mut().iter_mut().filter(move |h| h.key_eq(key))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Examples
|
/// The decoded value of every header with this key, in message order.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
/// ```
|
/// ```
|
||||||
/// use email_message::{CowMail, HeaderMap};
|
/// use email_message::{CowMail, HeaderMap};
|
||||||
/// let mail = CowMail::try_from(concat!(
|
/// let mail = CowMail::try_from(concat!(
|
||||||
@@ -53,15 +61,24 @@ pub trait HeaderMap<'a, H: Header<'a> + 'a> {
|
|||||||
self.iter_headers(key).map(|h| h.get_value())
|
self.iter_headers(key).map(|h| h.get_value())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The first header with this key.
|
||||||
|
///
|
||||||
|
/// Use [`iter_headers`](Self::iter_headers) when a key can occur more than once, as `Received`
|
||||||
|
/// does.
|
||||||
fn find_header(&self, key: &str) -> Option<&H> {
|
fn find_header(&self, key: &str) -> Option<&H> {
|
||||||
self.headers().iter().find(|h| h.key_eq(key))
|
self.headers().iter().find(|h| h.key_eq(key))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The first header with this key, mutably.
|
||||||
fn find_header_mut(&mut self, key: &str) -> Option<&mut H> {
|
fn find_header_mut(&mut self, key: &str) -> Option<&mut H> {
|
||||||
self.headers_mut().iter_mut().find(|h| h.key_eq(key))
|
self.headers_mut().iter_mut().find(|h| h.key_eq(key))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Examples
|
/// The decoded value of the first header with this key.
|
||||||
|
///
|
||||||
|
/// Unfolded and RFC 2047-decoded — see [`Header::get_value`](crate::Header::get_value).
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
/// ```
|
/// ```
|
||||||
/// use email_message::{CowMail, HeaderMap};
|
/// use email_message::{CowMail, HeaderMap};
|
||||||
/// let mail = CowMail::try_from(concat!(
|
/// let mail = CowMail::try_from(concat!(
|
||||||
@@ -76,7 +93,16 @@ pub trait HeaderMap<'a, H: Header<'a> + 'a> {
|
|||||||
self.find_header(key).map(|h| h.get_value())
|
self.find_header(key).map(|h| h.get_value())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Examples
|
/// Append a header, encoding `value` as needed.
|
||||||
|
///
|
||||||
|
/// Duplicates are not checked for: adding a key that is already there leaves two of them. Use
|
||||||
|
/// [`replace_header`](Self::replace_header) to change an existing one.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// If `key` or `value` converts to an empty string.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
/// ```
|
/// ```
|
||||||
/// use email_message::{CowMail, HeaderMap};
|
/// use email_message::{CowMail, HeaderMap};
|
||||||
/// let mut mail = CowMail::default();
|
/// let mut mail = CowMail::default();
|
||||||
@@ -87,7 +113,16 @@ pub trait HeaderMap<'a, H: Header<'a> + 'a> {
|
|||||||
self.headers_mut().push(H::new(key, value))
|
self.headers_mut().push(H::new(key, value))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Examples
|
/// Insert a header at `index` in the header list, encoding `value` as needed.
|
||||||
|
///
|
||||||
|
/// Unlike everywhere else in this trait, `index` is a position in the list, not a count of
|
||||||
|
/// occurrences of `key`. An index past the end appends.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// If `key` or `value` converts to an empty string.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
/// ```
|
/// ```
|
||||||
/// use email_message::{CowMail, Header, HeaderMap};
|
/// use email_message::{CowMail, Header, HeaderMap};
|
||||||
/// let mut mail = CowMail::try_from(concat!(
|
/// let mut mail = CowMail::try_from(concat!(
|
||||||
@@ -108,7 +143,15 @@ pub trait HeaderMap<'a, H: Header<'a> + 'a> {
|
|||||||
headers.insert(index, header);
|
headers.insert(index, header);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Examples
|
/// Replace the value of the `index`th header with this key, counting from the first one.
|
||||||
|
///
|
||||||
|
/// Does nothing when there is no such occurrence. The key keeps the spelling it had.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// If `value` converts to an empty string.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
/// ```
|
/// ```
|
||||||
/// use email_message::{CowMail, Header, HeaderMap};
|
/// use email_message::{CowMail, Header, HeaderMap};
|
||||||
/// let mut mail = CowMail::try_from(concat!(
|
/// let mut mail = CowMail::try_from(concat!(
|
||||||
@@ -139,7 +182,15 @@ pub trait HeaderMap<'a, H: Header<'a> + 'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Examples
|
/// Replace the value of every header with this key.
|
||||||
|
///
|
||||||
|
/// Does nothing when there is none.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// If `value` converts to an empty string.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
/// ```
|
/// ```
|
||||||
/// use email_message::{CowMail, Header, HeaderMap};
|
/// use email_message::{CowMail, Header, HeaderMap};
|
||||||
/// let mut mail = CowMail::try_from(concat!(
|
/// let mut mail = CowMail::try_from(concat!(
|
||||||
@@ -164,7 +215,11 @@ pub trait HeaderMap<'a, H: Header<'a> + 'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Examples
|
/// Remove the `index`th header with this key, counting from the first one.
|
||||||
|
///
|
||||||
|
/// Does nothing when there is no such occurrence.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
/// ```
|
/// ```
|
||||||
/// use email_message::{CowMail, Header, HeaderMap};
|
/// use email_message::{CowMail, Header, HeaderMap};
|
||||||
/// let mut mail = CowMail::try_from(concat!(
|
/// let mut mail = CowMail::try_from(concat!(
|
||||||
@@ -192,7 +247,9 @@ pub trait HeaderMap<'a, H: Header<'a> + 'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Examples
|
/// Remove every header with this key.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
/// ```
|
/// ```
|
||||||
/// use email_message::{CowMail, Header, HeaderMap};
|
/// use email_message::{CowMail, Header, HeaderMap};
|
||||||
/// let mut mail = CowMail::try_from(concat!(
|
/// let mut mail = CowMail::try_from(concat!(
|
||||||
@@ -211,6 +268,9 @@ pub trait HeaderMap<'a, H: Header<'a> + 'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Implemented for the header list itself, which is what makes `mail.headers.add_header(..)` work
|
||||||
|
/// alongside `mail.add_header(..)` — useful when the borrow checker objects to going through the
|
||||||
|
/// message.
|
||||||
impl<'a, H: Header<'a> + 'a> HeaderMap<'a, H> for Vec<H> {
|
impl<'a, H: Header<'a> + 'a> HeaderMap<'a, H> for Vec<H> {
|
||||||
fn headers(&self) -> &[H] {
|
fn headers(&self) -> &[H] {
|
||||||
self
|
self
|
||||||
@@ -243,6 +303,27 @@ pub(crate) fn build_headers<'a, H: Header<'a>>(
|
|||||||
Ok((headers, extend_over_newline(raw_data, line_end)))
|
Ok((headers, extend_over_newline(raw_data, line_end)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Parse a header block, borrowing from `raw_data`.
|
||||||
|
///
|
||||||
|
/// Returns the headers and the index the body starts at, i.e. just past the blank line that ends
|
||||||
|
/// the block — or the end of the buffer, for a message that stops at its headers.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// [`CowMailError::MailParseError`] when [`mailparse::parse_headers`] rejects the block.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use email_message::{CowHeader, HeaderMap, parse_headers};
|
||||||
|
///
|
||||||
|
/// let raw = b"Subject: Hi\r\nTo: nobody\r\n\r\nbody\r\n";
|
||||||
|
/// let (headers, body_idx) = parse_headers::<CowHeader>(&raw[..]).unwrap();
|
||||||
|
///
|
||||||
|
/// assert_eq!(headers.len(), 2);
|
||||||
|
/// assert_eq!(headers.find_value("Subject").unwrap(), "Hi");
|
||||||
|
/// assert_eq!(&raw[body_idx..], b"body\r\n");
|
||||||
|
/// ```
|
||||||
pub fn parse_headers<'a, H: Header<'a>>(raw_data: &'a [u8]) -> Result<(Vec<H>, usize), CowMailError> {
|
pub fn parse_headers<'a, H: Header<'a>>(raw_data: &'a [u8]) -> Result<(Vec<H>, usize), CowMailError> {
|
||||||
let (parsed, _) = mailparse::parse_headers(raw_data)?;
|
let (parsed, _) = mailparse::parse_headers(raw_data)?;
|
||||||
build_headers(raw_data, &parsed)
|
build_headers(raw_data, &parsed)
|
||||||
|
|||||||
@@ -4,9 +4,7 @@
|
|||||||
#![warn(missing_debug_implementations)]
|
#![warn(missing_debug_implementations)]
|
||||||
#![warn(rustdoc::broken_intra_doc_links)]
|
#![warn(rustdoc::broken_intra_doc_links)]
|
||||||
|
|
||||||
/// Body transfer-encoding: choosing one, and the types describing it.
|
|
||||||
pub mod body;
|
pub mod body;
|
||||||
/// Header lines: the [`Header`] trait, [`CowHeader`](header::CowHeader), and header-value encoding.
|
|
||||||
pub mod header;
|
pub mod header;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|||||||
Reference in New Issue
Block a user