document HeaderMap, body transfer-encoding, fix Examples headings
This commit is contained in:
+30
@@ -11,12 +11,21 @@ use std::mem;
|
||||
use email_encoding::body::{Encoding, base64};
|
||||
|
||||
/// 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)]
|
||||
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,
|
||||
/// `8bit`: unencoded bytes above 127, in lines short enough to transmit.
|
||||
EightBit,
|
||||
/// `quoted-printable`: mostly-ASCII content with the rest escaped as `=XX`.
|
||||
QuotedPrintable,
|
||||
/// `base64`: the choice for binary content and for text that is largely non-ASCII.
|
||||
Base64,
|
||||
/// `binary`: unencoded bytes with no line-length limit. Never chosen by [`encode`].
|
||||
Binary,
|
||||
}
|
||||
|
||||
@@ -39,7 +48,9 @@ impl Display for ContentTransferEncoding {
|
||||
/// always ends up as `base64`.
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum BodyContent {
|
||||
/// Valid UTF-8 text. Line endings are normalized to CRLF by [`encode`].
|
||||
Text(String),
|
||||
/// Arbitrary bytes, always base64-encoded.
|
||||
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`.
|
||||
///
|
||||
/// 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) {
|
||||
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
|
||||
/// provided. Keys match case-insensitively throughout, and an `index` argument
|
||||
/// 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> {
|
||||
/// The headers, in the order they appear in the message.
|
||||
fn headers(&self) -> &[H];
|
||||
@@ -18,6 +22,7 @@ pub trait HeaderMap<'a, H: Header<'a> + 'a> {
|
||||
/// removing go through it.
|
||||
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>
|
||||
where
|
||||
H: 'b,
|
||||
@@ -25,6 +30,7 @@ pub trait HeaderMap<'a, H: Header<'a> + 'a> {
|
||||
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>
|
||||
where
|
||||
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))
|
||||
}
|
||||
|
||||
/// Examples
|
||||
/// The decoded value of every header with this key, in message order.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use email_message::{CowMail, HeaderMap};
|
||||
/// 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())
|
||||
}
|
||||
|
||||
/// 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> {
|
||||
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> {
|
||||
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};
|
||||
/// 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())
|
||||
}
|
||||
|
||||
/// 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};
|
||||
/// 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))
|
||||
}
|
||||
|
||||
/// 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};
|
||||
/// let mut mail = CowMail::try_from(concat!(
|
||||
@@ -108,7 +143,15 @@ pub trait HeaderMap<'a, H: Header<'a> + 'a> {
|
||||
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};
|
||||
/// 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};
|
||||
/// 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};
|
||||
/// 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};
|
||||
/// 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> {
|
||||
fn headers(&self) -> &[H] {
|
||||
self
|
||||
@@ -243,6 +303,27 @@ pub(crate) fn build_headers<'a, H: Header<'a>>(
|
||||
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> {
|
||||
let (parsed, _) = mailparse::parse_headers(raw_data)?;
|
||||
build_headers(raw_data, &parsed)
|
||||
|
||||
@@ -4,9 +4,7 @@
|
||||
#![warn(missing_debug_implementations)]
|
||||
#![warn(rustdoc::broken_intra_doc_links)]
|
||||
|
||||
/// Body transfer-encoding: choosing one, and the types describing it.
|
||||
pub mod body;
|
||||
/// Header lines: the [`Header`] trait, [`CowHeader`](header::CowHeader), and header-value encoding.
|
||||
pub mod header;
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
Reference in New Issue
Block a user