Introduce HeaderValue (#729)

This commit is contained in:
Paolo Barbolini
2022-02-12 10:24:52 +01:00
committed by GitHub
parent 4c5f02b4f6
commit dbb135c533
9 changed files with 149 additions and 137 deletions

View File

@@ -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())
}
}

View File

@@ -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>(),

View File

@@ -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>(),

View File

@@ -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));
}

View File

@@ -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>(),

View File

@@ -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())));
}

View File

@@ -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;;;;;;;;;;;;;;;;fffeinmjggggggggg".to_string(),
);
));
assert_eq!(
headers.to_string(),

View File

@@ -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)));
}

View File

@@ -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>(),