Introduce HeaderValue (#729)
This commit is contained in:
@@ -1,4 +1,7 @@
|
||||
use crate::message::{header::HeaderName, Headers, Message};
|
||||
use crate::message::{
|
||||
header::{HeaderName, HeaderValue},
|
||||
Headers, Message,
|
||||
};
|
||||
use base64::{decode, encode};
|
||||
use ed25519_dalek::Signer;
|
||||
use once_cell::sync::Lazy;
|
||||
@@ -194,7 +197,7 @@ fn dkim_header_format(
|
||||
let header_name =
|
||||
dkim_canonicalize_header_tag("DKIM-Signature".to_string(), config.canonicalization.header);
|
||||
let header_name = HeaderName::new_from_ascii(header_name).unwrap();
|
||||
headers.append_raw(header_name, format!("v=1; a={signing_algorithm}-sha256; d={domain}; s={selector}; c={canon}; q=dns/txt; t={timestamp}; h={headers_list}; bh={body_hash}; b={signature}",domain=config.domain, selector=config.selector,canon=config.canonicalization,timestamp=timestamp,headers_list=headers_list,body_hash=body_hash,signature=signature,signing_algorithm=config.private_key.get_signing_algorithm()));
|
||||
headers.insert_raw(HeaderValue::new(header_name, format!("v=1; a={signing_algorithm}-sha256; d={domain}; s={selector}; c={canon}; q=dns/txt; t={timestamp}; h={headers_list}; bh={body_hash}; b={signature}",domain=config.domain, selector=config.selector,canon=config.canonicalization,timestamp=timestamp,headers_list=headers_list,body_hash=body_hash,signature=signature,signing_algorithm=config.private_key.get_signing_algorithm())));
|
||||
headers
|
||||
}
|
||||
|
||||
@@ -255,10 +258,10 @@ fn dkim_canonicalize_headers(
|
||||
let h = dkim_canonicalize_header_tag(h, canonicalization);
|
||||
if let Some(value) = mail_headers.get_raw(&h) {
|
||||
match canonicalization {
|
||||
DkimCanonicalizationType::Simple => signed_headers.append_raw(
|
||||
DkimCanonicalizationType::Simple => signed_headers.insert_raw(HeaderValue::new(
|
||||
HeaderName::new_from_ascii(h).unwrap(),
|
||||
dkim_canonicalize_header_value(value, canonicalization),
|
||||
),
|
||||
)),
|
||||
DkimCanonicalizationType::Relaxed => write!(
|
||||
&mut signed_headers_relaxed,
|
||||
"{}:{}",
|
||||
@@ -329,16 +332,16 @@ pub fn dkim_sign(message: &mut Message, dkim_config: &DkimConfig) {
|
||||
};
|
||||
let dkim_header =
|
||||
dkim_header_format(dkim_config, timestamp, signed_headers_list, bh, signature);
|
||||
message.headers.append_raw(
|
||||
message.headers.insert_raw(HeaderValue::new(
|
||||
HeaderName::new_from_ascii_str("DKIM-Signature"),
|
||||
dkim_header.get_raw("DKIM-Signature").unwrap().to_string(),
|
||||
);
|
||||
));
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::{
|
||||
super::header::HeaderName,
|
||||
super::header::{HeaderName, HeaderValue},
|
||||
super::{Header, Message},
|
||||
dkim_canonicalize_body, dkim_canonicalize_header_value, dkim_canonicalize_headers,
|
||||
DkimCanonicalizationType, DkimConfig, DkimSigningAlgorithm, DkimSigningKey,
|
||||
@@ -359,8 +362,8 @@ mod test {
|
||||
Ok(Self(s.into()))
|
||||
}
|
||||
|
||||
fn display(&self) -> String {
|
||||
self.0.clone()
|
||||
fn display(&self) -> HeaderValue {
|
||||
HeaderValue::new(Self::name(), self.0.clone())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ use std::{
|
||||
str::FromStr,
|
||||
};
|
||||
|
||||
use super::{Header, HeaderName};
|
||||
use super::{Header, HeaderName, HeaderValue};
|
||||
use crate::BoxError;
|
||||
|
||||
/// `Content-Transfer-Encoding` of the body
|
||||
@@ -35,8 +35,8 @@ impl Header for ContentTransferEncoding {
|
||||
Ok(s.parse()?)
|
||||
}
|
||||
|
||||
fn display(&self) -> String {
|
||||
self.to_string()
|
||||
fn display(&self) -> HeaderValue {
|
||||
HeaderValue::new(Self::name(), self.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,7 +75,7 @@ impl Default for ContentTransferEncoding {
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::ContentTransferEncoding;
|
||||
use crate::message::header::{HeaderName, Headers};
|
||||
use crate::message::header::{HeaderName, HeaderValue, Headers};
|
||||
|
||||
#[test]
|
||||
fn format_content_transfer_encoding() {
|
||||
@@ -94,20 +94,20 @@ mod test {
|
||||
fn parse_content_transfer_encoding() {
|
||||
let mut headers = Headers::new();
|
||||
|
||||
headers.insert_raw(
|
||||
headers.insert_raw(HeaderValue::new(
|
||||
HeaderName::new_from_ascii_str("Content-Transfer-Encoding"),
|
||||
"7bit".to_string(),
|
||||
);
|
||||
));
|
||||
|
||||
assert_eq!(
|
||||
headers.get::<ContentTransferEncoding>(),
|
||||
Some(ContentTransferEncoding::SevenBit)
|
||||
);
|
||||
|
||||
headers.insert_raw(
|
||||
headers.insert_raw(HeaderValue::new(
|
||||
HeaderName::new_from_ascii_str("Content-Transfer-Encoding"),
|
||||
"base64".to_string(),
|
||||
);
|
||||
));
|
||||
|
||||
assert_eq!(
|
||||
headers.get::<ContentTransferEncoding>(),
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use super::{Header, HeaderName};
|
||||
use super::{Header, HeaderName, HeaderValue};
|
||||
use crate::BoxError;
|
||||
|
||||
/// `Content-Disposition` of an attachment
|
||||
@@ -36,15 +36,15 @@ impl Header for ContentDisposition {
|
||||
Ok(Self(s.into()))
|
||||
}
|
||||
|
||||
fn display(&self) -> String {
|
||||
self.0.clone()
|
||||
fn display(&self) -> HeaderValue {
|
||||
HeaderValue::new(Self::name(), self.0.clone())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::ContentDisposition;
|
||||
use crate::message::header::{HeaderName, Headers};
|
||||
use crate::message::header::{HeaderName, HeaderValue, Headers};
|
||||
|
||||
#[test]
|
||||
fn format_content_disposition() {
|
||||
@@ -66,20 +66,20 @@ mod test {
|
||||
fn parse_content_disposition() {
|
||||
let mut headers = Headers::new();
|
||||
|
||||
headers.insert_raw(
|
||||
headers.insert_raw(HeaderValue::new(
|
||||
HeaderName::new_from_ascii_str("Content-Disposition"),
|
||||
"inline".to_string(),
|
||||
);
|
||||
));
|
||||
|
||||
assert_eq!(
|
||||
headers.get::<ContentDisposition>(),
|
||||
Some(ContentDisposition::inline())
|
||||
);
|
||||
|
||||
headers.insert_raw(
|
||||
headers.insert_raw(HeaderValue::new(
|
||||
HeaderName::new_from_ascii_str("Content-Disposition"),
|
||||
"attachment; filename=\"something.txt\"".to_string(),
|
||||
);
|
||||
));
|
||||
|
||||
assert_eq!(
|
||||
headers.get::<ContentDisposition>(),
|
||||
|
||||
@@ -6,7 +6,7 @@ use std::{
|
||||
|
||||
use mime::Mime;
|
||||
|
||||
use super::{Header, HeaderName};
|
||||
use super::{Header, HeaderName, HeaderValue};
|
||||
use crate::BoxError;
|
||||
|
||||
/// `Content-Type` of the body
|
||||
@@ -54,8 +54,8 @@ impl Header for ContentType {
|
||||
Ok(Self(s.parse()?))
|
||||
}
|
||||
|
||||
fn display(&self) -> String {
|
||||
self.0.to_string()
|
||||
fn display(&self) -> HeaderValue {
|
||||
HeaderValue::new(Self::name(), self.0.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -148,7 +148,7 @@ mod serde {
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::ContentType;
|
||||
use crate::message::header::{HeaderName, Headers};
|
||||
use crate::message::header::{HeaderName, HeaderValue, Headers};
|
||||
|
||||
#[test]
|
||||
fn format_content_type() {
|
||||
@@ -173,17 +173,17 @@ mod test {
|
||||
fn parse_content_type() {
|
||||
let mut headers = Headers::new();
|
||||
|
||||
headers.insert_raw(
|
||||
headers.insert_raw(HeaderValue::new(
|
||||
HeaderName::new_from_ascii_str("Content-Type"),
|
||||
"text/plain; charset=utf-8".to_string(),
|
||||
);
|
||||
));
|
||||
|
||||
assert_eq!(headers.get::<ContentType>(), Some(ContentType::TEXT_PLAIN));
|
||||
|
||||
headers.insert_raw(
|
||||
headers.insert_raw(HeaderValue::new(
|
||||
HeaderName::new_from_ascii_str("Content-Type"),
|
||||
"text/html; charset=utf-8".to_string(),
|
||||
);
|
||||
));
|
||||
|
||||
assert_eq!(headers.get::<ContentType>(), Some(ContentType::TEXT_HTML));
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ use std::time::SystemTime;
|
||||
|
||||
use httpdate::HttpDate;
|
||||
|
||||
use super::{Header, HeaderName};
|
||||
use super::{Header, HeaderName, HeaderValue};
|
||||
use crate::BoxError;
|
||||
|
||||
/// Message `Date` header
|
||||
@@ -43,7 +43,7 @@ impl Header for Date {
|
||||
Ok(Self(s.parse::<HttpDate>()?))
|
||||
}
|
||||
|
||||
fn display(&self) -> String {
|
||||
fn display(&self) -> HeaderValue {
|
||||
let mut s = self.0.to_string();
|
||||
if s.ends_with(" GMT") {
|
||||
// The httpdate crate always appends ` GMT` to the end of the string,
|
||||
@@ -54,7 +54,7 @@ impl Header for Date {
|
||||
s.push_str("-0000");
|
||||
}
|
||||
|
||||
s
|
||||
HeaderValue::new(Self::name(), s)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,7 +75,7 @@ mod test {
|
||||
use std::time::{Duration, SystemTime};
|
||||
|
||||
use super::Date;
|
||||
use crate::message::header::{HeaderName, Headers};
|
||||
use crate::message::header::{HeaderName, HeaderValue, Headers};
|
||||
|
||||
#[test]
|
||||
fn format_date() {
|
||||
@@ -106,10 +106,10 @@ mod test {
|
||||
fn parse_date() {
|
||||
let mut headers = Headers::new();
|
||||
|
||||
headers.insert_raw(
|
||||
headers.insert_raw(HeaderValue::new(
|
||||
HeaderName::new_from_ascii_str("Date"),
|
||||
"Tue, 15 Nov 1994 08:12:31 -0000".to_string(),
|
||||
);
|
||||
));
|
||||
|
||||
assert_eq!(
|
||||
headers.get::<Date>(),
|
||||
@@ -118,10 +118,10 @@ mod test {
|
||||
))
|
||||
);
|
||||
|
||||
headers.insert_raw(
|
||||
headers.insert_raw(HeaderValue::new(
|
||||
HeaderName::new_from_ascii_str("Date"),
|
||||
"Tue, 15 Nov 1994 08:12:32 -0000".to_string(),
|
||||
);
|
||||
));
|
||||
|
||||
assert_eq!(
|
||||
headers.get::<Date>(),
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use super::{Header, HeaderName};
|
||||
use super::{Header, HeaderName, HeaderValue};
|
||||
use crate::{
|
||||
message::mailbox::{Mailbox, Mailboxes},
|
||||
BoxError,
|
||||
@@ -25,8 +25,8 @@ macro_rules! mailbox_header {
|
||||
Ok(Self(mailbox))
|
||||
}
|
||||
|
||||
fn display(&self) -> String {
|
||||
self.0.to_string()
|
||||
fn display(&self) -> HeaderValue {
|
||||
HeaderValue::new(Self::name(),self.0.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,9 +68,9 @@ macro_rules! mailboxes_header {
|
||||
Ok(Self(mailbox))
|
||||
}
|
||||
|
||||
fn display(&self) -> String {
|
||||
self.0.to_string()
|
||||
}
|
||||
fn display(&self) -> HeaderValue {
|
||||
HeaderValue::new(Self::name(),self.0.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl std::convert::From<Mailboxes> for $type_name {
|
||||
@@ -161,7 +161,7 @@ mailboxes_header! {
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::{From, Mailbox, Mailboxes};
|
||||
use crate::message::header::{HeaderName, Headers};
|
||||
use crate::message::header::{HeaderName, HeaderValue, Headers};
|
||||
|
||||
#[test]
|
||||
fn format_single_without_name() {
|
||||
@@ -232,10 +232,10 @@ mod test {
|
||||
let from = vec!["kayo@example.com".parse().unwrap()].into();
|
||||
|
||||
let mut headers = Headers::new();
|
||||
headers.insert_raw(
|
||||
headers.insert_raw(HeaderValue::new(
|
||||
HeaderName::new_from_ascii_str("From"),
|
||||
"kayo@example.com".to_string(),
|
||||
);
|
||||
));
|
||||
|
||||
assert_eq!(headers.get::<From>(), Some(From(from)));
|
||||
}
|
||||
@@ -245,10 +245,10 @@ mod test {
|
||||
let from = vec!["K. <kayo@example.com>".parse().unwrap()].into();
|
||||
|
||||
let mut headers = Headers::new();
|
||||
headers.insert_raw(
|
||||
headers.insert_raw(HeaderValue::new(
|
||||
HeaderName::new_from_ascii_str("From"),
|
||||
"K. <kayo@example.com>".to_string(),
|
||||
);
|
||||
));
|
||||
|
||||
assert_eq!(headers.get::<From>(), Some(From(from)));
|
||||
}
|
||||
@@ -261,10 +261,10 @@ mod test {
|
||||
];
|
||||
|
||||
let mut headers = Headers::new();
|
||||
headers.insert_raw(
|
||||
headers.insert_raw(HeaderValue::new(
|
||||
HeaderName::new_from_ascii_str("From"),
|
||||
"kayo@example.com, pony@domain.tld".to_string(),
|
||||
);
|
||||
));
|
||||
|
||||
assert_eq!(headers.get::<From>(), Some(From(from.into())));
|
||||
}
|
||||
@@ -277,10 +277,10 @@ mod test {
|
||||
];
|
||||
|
||||
let mut headers = Headers::new();
|
||||
headers.insert_raw(
|
||||
headers.insert_raw(HeaderValue::new(
|
||||
HeaderName::new_from_ascii_str("From"),
|
||||
"K. <kayo@example.com>, Pony P. <pony@domain.tld>".to_string(),
|
||||
);
|
||||
));
|
||||
|
||||
assert_eq!(headers.get::<From>(), Some(From(from.into())));
|
||||
}
|
||||
|
||||
@@ -34,13 +34,13 @@ pub trait Header: Clone {
|
||||
|
||||
fn parse(s: &str) -> Result<Self, BoxError>;
|
||||
|
||||
fn display(&self) -> String;
|
||||
fn display(&self) -> HeaderValue;
|
||||
}
|
||||
|
||||
/// A set of email headers
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct Headers {
|
||||
headers: Vec<(HeaderName, String)>,
|
||||
headers: Vec<HeaderValue>,
|
||||
}
|
||||
|
||||
impl Headers {
|
||||
@@ -68,13 +68,14 @@ impl Headers {
|
||||
///
|
||||
/// Returns `None` if `Header` isn't present in `Headers`.
|
||||
pub fn get<H: Header>(&self) -> Option<H> {
|
||||
self.get_raw(&H::name()).and_then(|raw| H::parse(raw).ok())
|
||||
self.get_raw(&H::name())
|
||||
.and_then(|raw_value| H::parse(raw_value).ok())
|
||||
}
|
||||
|
||||
/// Sets `Header` into `Headers`, overriding `Header` if it
|
||||
/// was already present in `Headers`
|
||||
pub fn set<H: Header>(&mut self, header: H) {
|
||||
self.insert_raw(H::name(), header.display());
|
||||
self.insert_raw(header.display());
|
||||
}
|
||||
|
||||
/// Remove `Header` from `Headers`, returning it
|
||||
@@ -82,7 +83,7 @@ impl Headers {
|
||||
/// Returns `None` if `Header` isn't in `Headers`.
|
||||
pub fn remove<H: Header>(&mut self) -> Option<H> {
|
||||
self.remove_raw(&H::name())
|
||||
.and_then(|(_name, raw)| H::parse(&raw).ok())
|
||||
.and_then(|value| H::parse(&value.raw_value).ok())
|
||||
}
|
||||
|
||||
/// Clears `Headers`, removing all headers from it
|
||||
@@ -97,62 +98,46 @@ impl Headers {
|
||||
///
|
||||
/// Returns `None` if `name` isn't present in `Headers`.
|
||||
pub fn get_raw(&self, name: &str) -> Option<&str> {
|
||||
self.find_header(name).map(|(_name, value)| value)
|
||||
self.find_header(name).map(|value| value.raw_value.as_str())
|
||||
}
|
||||
|
||||
/// Inserts a raw header into `Headers`, overriding `value` if it
|
||||
/// was already present in `Headers`.
|
||||
pub fn insert_raw(&mut self, name: HeaderName, value: String) {
|
||||
match self.find_header_mut(&name) {
|
||||
Some((_, current_value)) => {
|
||||
pub fn insert_raw(&mut self, value: HeaderValue) {
|
||||
match self.find_header_mut(&value.name) {
|
||||
Some(current_value) => {
|
||||
*current_value = value;
|
||||
}
|
||||
None => {
|
||||
self.headers.push((name, value));
|
||||
self.headers.push(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Appends a raw header into `Headers`
|
||||
///
|
||||
/// If a header with a name of `name` is already present,
|
||||
/// appends `, ` + `value` to it's current value.
|
||||
pub fn append_raw(&mut self, name: HeaderName, value: String) {
|
||||
match self.find_header_mut(&name) {
|
||||
Some((_name, prev_value)) => {
|
||||
prev_value.push_str(", ");
|
||||
prev_value.push_str(&value);
|
||||
}
|
||||
None => self.headers.push((name, value)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Remove a raw header from `Headers`, returning it
|
||||
///
|
||||
/// Returns `None` if `name` isn't present in `Headers`.
|
||||
pub fn remove_raw(&mut self, name: &str) -> Option<(HeaderName, String)> {
|
||||
pub fn remove_raw(&mut self, name: &str) -> Option<HeaderValue> {
|
||||
self.find_header_index(name).map(|i| self.headers.remove(i))
|
||||
}
|
||||
|
||||
fn find_header(&self, name: &str) -> Option<(&HeaderName, &str)> {
|
||||
fn find_header(&self, name: &str) -> Option<&HeaderValue> {
|
||||
self.headers
|
||||
.iter()
|
||||
.find(|&(name_, _value)| name.eq_ignore_ascii_case(name_))
|
||||
.map(|t| (&t.0, t.1.as_str()))
|
||||
.find(|value| name.eq_ignore_ascii_case(&value.name))
|
||||
}
|
||||
|
||||
fn find_header_mut(&mut self, name: &str) -> Option<(&HeaderName, &mut String)> {
|
||||
fn find_header_mut(&mut self, name: &str) -> Option<&mut HeaderValue> {
|
||||
self.headers
|
||||
.iter_mut()
|
||||
.find(|(name_, _value)| name.eq_ignore_ascii_case(name_))
|
||||
.map(|t| (&t.0, &mut t.1))
|
||||
.find(|value| name.eq_ignore_ascii_case(&value.name))
|
||||
}
|
||||
|
||||
fn find_header_index(&self, name: &str) -> Option<usize> {
|
||||
self.headers
|
||||
.iter()
|
||||
.enumerate()
|
||||
.find(|&(_i, (name_, _value))| name.eq_ignore_ascii_case(name_))
|
||||
.find(|(_i, value)| name.eq_ignore_ascii_case(&value.name))
|
||||
.map(|(i, _)| i)
|
||||
}
|
||||
}
|
||||
@@ -160,10 +145,10 @@ impl Headers {
|
||||
impl Display for Headers {
|
||||
/// Formats `Headers`, ready to put them into an email
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
for (name, value) in &self.headers {
|
||||
Display::fmt(name, f)?;
|
||||
for value in &self.headers {
|
||||
f.write_str(&value.name)?;
|
||||
f.write_str(": ")?;
|
||||
HeaderValueEncoder::encode(name, value, f)?;
|
||||
f.write_str(&value.encoded_value)?;
|
||||
f.write_str("\r\n")?;
|
||||
}
|
||||
|
||||
@@ -290,6 +275,26 @@ impl PartialEq<HeaderName> for &str {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct HeaderValue {
|
||||
name: HeaderName,
|
||||
raw_value: String,
|
||||
encoded_value: String,
|
||||
}
|
||||
|
||||
impl HeaderValue {
|
||||
pub fn new(name: HeaderName, raw_value: String) -> Self {
|
||||
let mut encoded_value = String::with_capacity(raw_value.len());
|
||||
HeaderValueEncoder::encode(&name, &raw_value, &mut encoded_value).unwrap();
|
||||
|
||||
Self {
|
||||
name,
|
||||
raw_value,
|
||||
encoded_value,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const ENCODING_START_PREFIX: &str = "=?utf-8?b?";
|
||||
const ENCODING_END_SUFFIX: &str = "?=";
|
||||
const MAX_LINE_LEN: usize = 76;
|
||||
@@ -301,7 +306,7 @@ struct HeaderValueEncoder {
|
||||
}
|
||||
|
||||
impl HeaderValueEncoder {
|
||||
fn encode(name: &str, value: &str, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
fn encode(name: &str, value: &str, f: &mut impl fmt::Write) -> fmt::Result {
|
||||
let (words_iter, encoder) = Self::new(name, value);
|
||||
encoder.format(words_iter, f)
|
||||
}
|
||||
@@ -319,7 +324,7 @@ impl HeaderValueEncoder {
|
||||
fn format(
|
||||
mut self,
|
||||
words_iter: WordsPlusFillIterator<'_>,
|
||||
f: &mut fmt::Formatter<'_>,
|
||||
f: &mut impl fmt::Write,
|
||||
) -> fmt::Result {
|
||||
/// Estimate if an encoded string of `len` would fix in an empty line
|
||||
fn would_fit_new_line(len: usize) -> bool {
|
||||
@@ -448,11 +453,9 @@ impl HeaderValueEncoder {
|
||||
|
||||
fn flush_encode_buf(
|
||||
&mut self,
|
||||
f: &mut fmt::Formatter<'_>,
|
||||
f: &mut impl fmt::Write,
|
||||
switching_to_allowed: bool,
|
||||
) -> fmt::Result {
|
||||
use std::fmt::Write;
|
||||
|
||||
if self.encode_buf.is_empty() {
|
||||
// nothing to encode
|
||||
return Ok(());
|
||||
@@ -477,7 +480,7 @@ impl HeaderValueEncoder {
|
||||
self.encode_buf.as_bytes(),
|
||||
base64::STANDARD,
|
||||
);
|
||||
Display::fmt(&encoded, f)?;
|
||||
write!(f, "{}", encoded)?;
|
||||
f.write_str(ENCODING_END_SUFFIX)?;
|
||||
|
||||
self.line_len += ENCODING_START_PREFIX.len();
|
||||
@@ -493,7 +496,7 @@ impl HeaderValueEncoder {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn new_line(&mut self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
fn new_line(&mut self, f: &mut impl fmt::Write) -> fmt::Result {
|
||||
f.write_str("\r\n ")?;
|
||||
self.line_len = 1;
|
||||
|
||||
@@ -546,7 +549,7 @@ const fn allowed_char(c: char) -> bool {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{HeaderName, Headers};
|
||||
use super::{HeaderName, HeaderValue, Headers};
|
||||
|
||||
#[test]
|
||||
fn valid_headername() {
|
||||
@@ -607,10 +610,10 @@ mod tests {
|
||||
#[test]
|
||||
fn format_ascii() {
|
||||
let mut headers = Headers::new();
|
||||
headers.insert_raw(
|
||||
headers.insert_raw(HeaderValue::new(
|
||||
HeaderName::new_from_ascii_str("To"),
|
||||
"John Doe <example@example.com>, Jean Dupont <jean@example.com>".to_string(),
|
||||
);
|
||||
));
|
||||
|
||||
assert_eq!(
|
||||
headers.to_string(),
|
||||
@@ -621,10 +624,10 @@ mod tests {
|
||||
#[test]
|
||||
fn format_ascii_with_folding() {
|
||||
let mut headers = Headers::new();
|
||||
headers.insert_raw(
|
||||
headers.insert_raw(HeaderValue::new(
|
||||
HeaderName::new_from_ascii_str("To"),
|
||||
"Ascii <example@example.com>, John Doe <johndoe@example.com, John Smith <johnsmith@example.com>, Pinco Pallino <pincopallino@example.com>, Jemand <jemand@example.com>, Jean Dupont <jean@example.com>".to_string(),
|
||||
);
|
||||
));
|
||||
|
||||
assert_eq!(
|
||||
headers.to_string(),
|
||||
@@ -639,10 +642,10 @@ mod tests {
|
||||
#[test]
|
||||
fn format_ascii_with_folding_long_line() {
|
||||
let mut headers = Headers::new();
|
||||
headers.insert_raw(
|
||||
headers.insert_raw(HeaderValue::new(
|
||||
HeaderName::new_from_ascii_str("Subject"),
|
||||
"Hello! This is lettre, and this IsAVeryLongLineDoYouKnowWhatsGoingToHappenIGuessWeAreGoingToFindOut. Ok I guess that's it!".to_string()
|
||||
);
|
||||
));
|
||||
|
||||
assert_eq!(
|
||||
headers.to_string(),
|
||||
@@ -658,9 +661,10 @@ mod tests {
|
||||
fn format_ascii_with_folding_very_long_line() {
|
||||
let mut headers = Headers::new();
|
||||
headers.insert_raw(
|
||||
HeaderValue::new(
|
||||
HeaderName::new_from_ascii_str("Subject"),
|
||||
"Hello! IGuessTheLastLineWasntLongEnoughSoLetsTryAgainShallWeWhatDoYouThinkItsGoingToHappenIGuessWereAboutToFindOut! I don't know".to_string()
|
||||
);
|
||||
));
|
||||
|
||||
assert_eq!(
|
||||
headers.to_string(),
|
||||
@@ -674,10 +678,10 @@ mod tests {
|
||||
#[test]
|
||||
fn format_ascii_with_folding_giant_word() {
|
||||
let mut headers = Headers::new();
|
||||
headers.insert_raw(
|
||||
headers.insert_raw(HeaderValue::new(
|
||||
HeaderName::new_from_ascii_str("Subject"),
|
||||
"1abcdefghijklmnopqrstuvwxyz2abcdefghijklmnopqrstuvwxyz3abcdefghijklmnopqrstuvwxyz4abcdefghijklmnopqrstuvwxyz5abcdefghijklmnopqrstuvwxyz6abcdefghijklmnopqrstuvwxyz".to_string()
|
||||
);
|
||||
));
|
||||
|
||||
assert_eq!(
|
||||
headers.to_string(),
|
||||
@@ -692,10 +696,10 @@ mod tests {
|
||||
#[test]
|
||||
fn format_special() {
|
||||
let mut headers = Headers::new();
|
||||
headers.insert_raw(
|
||||
headers.insert_raw(HeaderValue::new(
|
||||
HeaderName::new_from_ascii_str("To"),
|
||||
"Seán <sean@example.com>".to_string(),
|
||||
);
|
||||
));
|
||||
|
||||
assert_eq!(
|
||||
headers.to_string(),
|
||||
@@ -706,10 +710,10 @@ mod tests {
|
||||
#[test]
|
||||
fn format_special_emoji() {
|
||||
let mut headers = Headers::new();
|
||||
headers.insert_raw(
|
||||
headers.insert_raw(HeaderValue::new(
|
||||
HeaderName::new_from_ascii_str("To"),
|
||||
"🌎 <world@example.com>".to_string(),
|
||||
);
|
||||
));
|
||||
|
||||
assert_eq!(
|
||||
headers.to_string(),
|
||||
@@ -720,10 +724,10 @@ mod tests {
|
||||
#[test]
|
||||
fn format_special_with_folding() {
|
||||
let mut headers = Headers::new();
|
||||
headers.insert_raw(
|
||||
headers.insert_raw(HeaderValue::new(
|
||||
HeaderName::new_from_ascii_str("To"),
|
||||
"🌍 <world@example.com>, 🦆 Everywhere <ducks@example.com>, Иванов Иван Иванович <ivanov@example.com>, Jānis Bērziņš <janis@example.com>, Seán Ó Rudaí <sean@example.com>".to_string(),
|
||||
);
|
||||
) );
|
||||
|
||||
assert_eq!(
|
||||
headers.to_string(),
|
||||
@@ -741,8 +745,9 @@ mod tests {
|
||||
fn format_slice_on_char_boundary_bug() {
|
||||
let mut headers = Headers::new();
|
||||
headers.insert_raw(
|
||||
HeaderValue::new(
|
||||
HeaderName::new_from_ascii_str("Subject"),
|
||||
"🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳".to_string(),
|
||||
"🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳".to_string(),)
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
@@ -754,10 +759,10 @@ mod tests {
|
||||
#[test]
|
||||
fn format_bad_stuff() {
|
||||
let mut headers = Headers::new();
|
||||
headers.insert_raw(
|
||||
headers.insert_raw(HeaderValue::new(
|
||||
HeaderName::new_from_ascii_str("Subject"),
|
||||
"Hello! \r\n This is \" bad \0. 👋".to_string(),
|
||||
);
|
||||
));
|
||||
|
||||
assert_eq!(
|
||||
headers.to_string(),
|
||||
@@ -769,21 +774,25 @@ mod tests {
|
||||
fn format_everything() {
|
||||
let mut headers = Headers::new();
|
||||
headers.insert_raw(
|
||||
HeaderValue::new(
|
||||
HeaderName::new_from_ascii_str("Subject"),
|
||||
"Hello! This is lettre, and this IsAVeryLongLineDoYouKnowWhatsGoingToHappenIGuessWeAreGoingToFindOut. Ok I guess that's it!".to_string()
|
||||
)
|
||||
);
|
||||
headers.insert_raw(
|
||||
HeaderValue::new(
|
||||
HeaderName::new_from_ascii_str("To"),
|
||||
"🌍 <world@example.com>, 🦆 Everywhere <ducks@example.com>, Иванов Иван Иванович <ivanov@example.com>, Jānis Bērziņš <janis@example.com>, Seán Ó Rudaí <sean@example.com>".to_string(),
|
||||
)
|
||||
);
|
||||
headers.insert_raw(
|
||||
headers.insert_raw(HeaderValue::new(
|
||||
HeaderName::new_from_ascii_str("From"),
|
||||
"Someone <somewhere@example.com>".to_string(),
|
||||
);
|
||||
headers.insert_raw(
|
||||
));
|
||||
headers.insert_raw(HeaderValue::new(
|
||||
HeaderName::new_from_ascii_str("Content-Transfer-Encoding"),
|
||||
"quoted-printable".to_string(),
|
||||
);
|
||||
));
|
||||
|
||||
assert_eq!(
|
||||
headers.to_string(),
|
||||
@@ -805,10 +814,10 @@ mod tests {
|
||||
#[test]
|
||||
fn issue_653() {
|
||||
let mut headers = Headers::new();
|
||||
headers.insert_raw(
|
||||
headers.insert_raw(HeaderValue::new(
|
||||
HeaderName::new_from_ascii_str("Subject"),
|
||||
"+仮名 :a;go; ;;;;;s;;;;;;;;;;;;;;;;fffeinmjgggggggggfっ".to_string(),
|
||||
);
|
||||
));
|
||||
|
||||
assert_eq!(
|
||||
headers.to_string(),
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use crate::{
|
||||
message::header::{Header, HeaderName},
|
||||
message::header::{Header, HeaderName, HeaderValue},
|
||||
BoxError,
|
||||
};
|
||||
|
||||
@@ -50,8 +50,8 @@ impl Header for MimeVersion {
|
||||
Ok(MimeVersion::new(major, minor))
|
||||
}
|
||||
|
||||
fn display(&self) -> String {
|
||||
format!("{}.{}", self.major, self.minor)
|
||||
fn display(&self) -> HeaderValue {
|
||||
HeaderValue::new(Self::name(), format!("{}.{}", self.major, self.minor))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,7 +64,7 @@ impl Default for MimeVersion {
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::{MimeVersion, MIME_VERSION_1_0};
|
||||
use crate::message::header::{HeaderName, Headers};
|
||||
use crate::message::header::{HeaderName, HeaderValue, Headers};
|
||||
|
||||
#[test]
|
||||
fn format_mime_version() {
|
||||
@@ -83,17 +83,17 @@ mod test {
|
||||
fn parse_mime_version() {
|
||||
let mut headers = Headers::new();
|
||||
|
||||
headers.insert_raw(
|
||||
headers.insert_raw(HeaderValue::new(
|
||||
HeaderName::new_from_ascii_str("MIME-Version"),
|
||||
"1.0".to_string(),
|
||||
);
|
||||
));
|
||||
|
||||
assert_eq!(headers.get::<MimeVersion>(), Some(MIME_VERSION_1_0));
|
||||
|
||||
headers.insert_raw(
|
||||
headers.insert_raw(HeaderValue::new(
|
||||
HeaderName::new_from_ascii_str("MIME-Version"),
|
||||
"0.1".to_string(),
|
||||
);
|
||||
));
|
||||
|
||||
assert_eq!(headers.get::<MimeVersion>(), Some(MimeVersion::new(0, 1)));
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use super::{Header, HeaderName};
|
||||
use super::{Header, HeaderName, HeaderValue};
|
||||
use crate::BoxError;
|
||||
|
||||
macro_rules! text_header {
|
||||
@@ -16,8 +16,8 @@ macro_rules! text_header {
|
||||
Ok(Self(s.into()))
|
||||
}
|
||||
|
||||
fn display(&self) -> String {
|
||||
self.0.clone()
|
||||
fn display(&self) -> HeaderValue {
|
||||
HeaderValue::new(Self::name(), self.0.clone())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,7 +86,7 @@ text_header! {
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::Subject;
|
||||
use crate::message::header::{HeaderName, Headers};
|
||||
use crate::message::header::{HeaderName, HeaderValue, Headers};
|
||||
|
||||
#[test]
|
||||
fn format_ascii() {
|
||||
@@ -110,10 +110,10 @@ mod test {
|
||||
#[test]
|
||||
fn parse_ascii() {
|
||||
let mut headers = Headers::new();
|
||||
headers.insert_raw(
|
||||
headers.insert_raw(HeaderValue::new(
|
||||
HeaderName::new_from_ascii_str("Subject"),
|
||||
"Sample subject".to_string(),
|
||||
);
|
||||
));
|
||||
|
||||
assert_eq!(
|
||||
headers.get::<Subject>(),
|
||||
|
||||
Reference in New Issue
Block a user