lettre/message/header/
mod.rs

1//! Headers widely used in email messages
2
3use std::{
4    borrow::Cow,
5    error::Error,
6    fmt::{self, Display, Formatter, Write},
7    ops::Deref,
8};
9
10use email_encoding::headers::writer::EmailWriter;
11
12pub use self::{
13    content::*,
14    content_disposition::ContentDisposition,
15    content_type::{ContentType, ContentTypeErr},
16    date::Date,
17    mailbox::*,
18    special::*,
19    textual::*,
20};
21use crate::BoxError;
22
23mod content;
24mod content_disposition;
25mod content_type;
26mod date;
27mod mailbox;
28mod special;
29mod textual;
30
31/// Represents an email header
32///
33/// Email header as defined in [RFC5322](https://datatracker.ietf.org/doc/html/rfc5322) and extensions.
34pub trait Header: Clone {
35    fn name() -> HeaderName;
36
37    fn parse(s: &str) -> Result<Self, BoxError>;
38
39    fn display(&self) -> HeaderValue;
40}
41
42/// A set of email headers
43#[derive(Debug, Clone, Default)]
44pub struct Headers {
45    headers: Vec<HeaderValue>,
46}
47
48impl Headers {
49    /// Create an empty `Headers`
50    ///
51    /// This function does not allocate.
52    #[inline]
53    pub const fn new() -> Self {
54        Self {
55            headers: Vec::new(),
56        }
57    }
58
59    /// Create an empty `Headers` with a pre-allocated capacity
60    ///
61    /// Pre-allocates a capacity of at least `capacity`.
62    #[inline]
63    pub fn with_capacity(capacity: usize) -> Self {
64        Self {
65            headers: Vec::with_capacity(capacity),
66        }
67    }
68
69    /// Returns a copy of a `Header` present in `Headers`
70    ///
71    /// Returns `None` if `Header` isn't present in `Headers`.
72    pub fn get<H: Header>(&self) -> Option<H> {
73        self.get_raw(&H::name())
74            .and_then(|raw_value| H::parse(raw_value).ok())
75    }
76
77    /// Sets `Header` into `Headers`, overriding `Header` if it
78    /// was already present in `Headers`
79    pub fn set<H: Header>(&mut self, header: H) {
80        self.insert_raw(header.display());
81    }
82
83    /// Remove `Header` from `Headers`, returning it
84    ///
85    /// Returns `None` if `Header` isn't in `Headers`.
86    pub fn remove<H: Header>(&mut self) -> Option<H> {
87        self.remove_raw(&H::name())
88            .and_then(|value| H::parse(&value.raw_value).ok())
89    }
90
91    /// Clears `Headers`, removing all headers from it
92    ///
93    /// Any pre-allocated capacity is left untouched.
94    #[inline]
95    pub fn clear(&mut self) {
96        self.headers.clear();
97    }
98
99    /// Returns a reference to the raw value of header `name`
100    ///
101    /// Returns `None` if `name` isn't present in `Headers`.
102    pub fn get_raw(&self, name: &str) -> Option<&str> {
103        self.find_header(name).map(|value| value.raw_value.as_str())
104    }
105
106    /// Inserts a raw header into `Headers`, overriding `value` if it
107    /// was already present in `Headers`.
108    pub fn insert_raw(&mut self, value: HeaderValue) {
109        match self.find_header_mut(&value.name) {
110            Some(current_value) => {
111                *current_value = value;
112            }
113            None => {
114                self.headers.push(value);
115            }
116        }
117    }
118
119    /// Remove a raw header from `Headers`, returning it
120    ///
121    /// Returns `None` if `name` isn't present in `Headers`.
122    pub fn remove_raw(&mut self, name: &str) -> Option<HeaderValue> {
123        self.find_header_index(name).map(|i| self.headers.remove(i))
124    }
125
126    pub(crate) fn find_header(&self, name: &str) -> Option<&HeaderValue> {
127        self.headers.iter().find(|value| name == value.name)
128    }
129
130    fn find_header_mut(&mut self, name: &str) -> Option<&mut HeaderValue> {
131        self.headers.iter_mut().find(|value| name == value.name)
132    }
133
134    fn find_header_index(&self, name: &str) -> Option<usize> {
135        self.headers
136            .iter()
137            .enumerate()
138            .find(|(_i, value)| name == value.name)
139            .map(|(i, _)| i)
140    }
141}
142
143impl Display for Headers {
144    /// Formats `Headers`, ready to put them into an email
145    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
146        for value in &self.headers {
147            f.write_str(&value.name)?;
148            f.write_str(": ")?;
149            f.write_str(&value.encoded_value)?;
150            f.write_str("\r\n")?;
151        }
152
153        Ok(())
154    }
155}
156
157/// A possible error when converting a `HeaderName` from another type.
158// comes from `http` crate
159#[allow(missing_copy_implementations)]
160#[derive(Debug, Clone)]
161#[non_exhaustive]
162pub struct InvalidHeaderName;
163
164impl fmt::Display for InvalidHeaderName {
165    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
166        f.write_str("invalid header name")
167    }
168}
169
170impl Error for InvalidHeaderName {}
171
172/// A valid header name
173#[derive(Debug, Clone)]
174pub struct HeaderName(Cow<'static, str>);
175
176impl HeaderName {
177    /// Creates a new header name
178    pub fn new_from_ascii(ascii: String) -> Result<Self, InvalidHeaderName> {
179        if !ascii.is_empty() && ascii.len() <= 76 && ascii.is_ascii() && !ascii.contains([':', ' '])
180        {
181            Ok(Self(Cow::Owned(ascii)))
182        } else {
183            Err(InvalidHeaderName)
184        }
185    }
186
187    /// Creates a new header name, panics on invalid name
188    pub const fn new_from_ascii_str(ascii: &'static str) -> Self {
189        macro_rules! static_assert {
190            ($condition:expr) => {
191                let _ = [()][(!($condition)) as usize];
192            };
193        }
194
195        static_assert!(!ascii.is_empty());
196        static_assert!(ascii.len() <= 76);
197
198        let bytes = ascii.as_bytes();
199        let mut i = 0;
200        while i < bytes.len() {
201            static_assert!(bytes[i].is_ascii());
202            static_assert!(bytes[i] != b' ');
203            static_assert!(bytes[i] != b':');
204
205            i += 1;
206        }
207
208        Self(Cow::Borrowed(ascii))
209    }
210}
211
212impl Display for HeaderName {
213    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
214        f.write_str(self)
215    }
216}
217
218impl Deref for HeaderName {
219    type Target = str;
220
221    #[inline]
222    fn deref(&self) -> &Self::Target {
223        &self.0
224    }
225}
226
227impl AsRef<[u8]> for HeaderName {
228    #[inline]
229    fn as_ref(&self) -> &[u8] {
230        let s: &str = self.as_ref();
231        s.as_bytes()
232    }
233}
234
235impl AsRef<str> for HeaderName {
236    #[inline]
237    fn as_ref(&self) -> &str {
238        &self.0
239    }
240}
241
242impl PartialEq<HeaderName> for HeaderName {
243    fn eq(&self, other: &HeaderName) -> bool {
244        self.eq_ignore_ascii_case(other)
245    }
246}
247
248impl PartialEq<&str> for HeaderName {
249    fn eq(&self, other: &&str) -> bool {
250        self.eq_ignore_ascii_case(other)
251    }
252}
253
254impl PartialEq<HeaderName> for &str {
255    fn eq(&self, other: &HeaderName) -> bool {
256        self.eq_ignore_ascii_case(other)
257    }
258}
259
260/// A safe for use header value
261#[derive(Debug, Clone, PartialEq)]
262pub struct HeaderValue {
263    name: HeaderName,
264    raw_value: String,
265    encoded_value: String,
266}
267
268impl HeaderValue {
269    /// Construct a new `HeaderValue` and encode it
270    ///
271    /// Takes the header `name` and the `raw_value` and encodes
272    /// it via `RFC2047` and line folds it.
273    ///
274    /// [`RFC2047`]: https://datatracker.ietf.org/doc/html/rfc2047
275    pub fn new(name: HeaderName, raw_value: String) -> Self {
276        let mut encoded_value = String::with_capacity(raw_value.len());
277        HeaderValueEncoder::encode(&name, &raw_value, &mut encoded_value).unwrap();
278
279        Self {
280            name,
281            raw_value,
282            encoded_value,
283        }
284    }
285
286    /// Construct a new `HeaderValue` using a pre-encoded header value
287    ///
288    /// This method is _extremely_ dangerous as it opens up
289    /// the encoder to header injection attacks, but is sometimes
290    /// acceptable for use if `encoded_value` contains only ascii
291    /// printable characters and is already line folded.
292    ///
293    /// When in doubt, use [`HeaderValue::new`].
294    pub fn dangerous_new_pre_encoded(
295        name: HeaderName,
296        raw_value: String,
297        encoded_value: String,
298    ) -> Self {
299        Self {
300            name,
301            raw_value,
302            encoded_value,
303        }
304    }
305
306    #[cfg(feature = "dkim")]
307    pub(crate) fn get_raw(&self) -> &str {
308        &self.raw_value
309    }
310
311    #[cfg(feature = "dkim")]
312    pub(crate) fn get_encoded(&self) -> &str {
313        &self.encoded_value
314    }
315}
316
317/// [RFC 1522](https://tools.ietf.org/html/rfc1522) header value encoder
318struct HeaderValueEncoder<'a> {
319    writer: EmailWriter<'a>,
320    encode_buf: String,
321}
322
323impl<'a> HeaderValueEncoder<'a> {
324    fn encode(name: &str, value: &'a str, f: &'a mut impl fmt::Write) -> fmt::Result {
325        let encoder = Self::new(name, f);
326        encoder.format(value.split_inclusive(' '))
327    }
328
329    fn new(name: &str, writer: &'a mut dyn Write) -> Self {
330        let line_len = name.len() + ": ".len();
331        let writer = EmailWriter::new(writer, line_len, 0, false);
332
333        Self {
334            writer,
335            encode_buf: String::new(),
336        }
337    }
338
339    fn format(mut self, words_iter: impl Iterator<Item = &'a str>) -> fmt::Result {
340        for next_word in words_iter {
341            let allowed = allowed_str(next_word);
342
343            if allowed {
344                // This word only contains allowed characters
345
346                // the next word is allowed, but we may have accumulated some words to encode
347                self.flush_encode_buf()?;
348
349                self.writer.folding().write_str(next_word)?;
350            } else {
351                // This word contains unallowed characters
352                self.encode_buf.push_str(next_word);
353            }
354        }
355
356        self.flush_encode_buf()?;
357
358        Ok(())
359    }
360
361    fn flush_encode_buf(&mut self) -> fmt::Result {
362        if self.encode_buf.is_empty() {
363            // nothing to encode
364            return Ok(());
365        }
366
367        let prefix = self.encode_buf.trim_end_matches(' ');
368        email_encoding::headers::rfc2047::encode(prefix, &mut self.writer)?;
369
370        // TODO: add a better API for doing this in email-encoding
371        let spaces = self.encode_buf.len() - prefix.len();
372        for _ in 0..spaces {
373            self.writer.space();
374        }
375
376        self.encode_buf.clear();
377        Ok(())
378    }
379}
380
381fn allowed_str(s: &str) -> bool {
382    s.bytes().all(allowed_char)
383}
384
385const fn allowed_char(c: u8) -> bool {
386    c >= 1 && c <= 9 || c == 11 || c == 12 || c >= 14 && c <= 127
387}
388
389#[cfg(test)]
390mod tests {
391    use pretty_assertions::assert_eq;
392
393    use super::{HeaderName, HeaderValue, Headers, To};
394    use crate::message::Mailboxes;
395
396    #[test]
397    fn valid_headername() {
398        assert!(HeaderName::new_from_ascii(String::from("From")).is_ok());
399    }
400
401    #[test]
402    fn non_ascii_headername() {
403        assert!(HeaderName::new_from_ascii(String::from("🌎")).is_err());
404    }
405
406    #[test]
407    fn spaces_in_headername() {
408        assert!(HeaderName::new_from_ascii(String::from("From ")).is_err());
409    }
410
411    #[test]
412    fn colons_in_headername() {
413        assert!(HeaderName::new_from_ascii(String::from("From:")).is_err());
414    }
415
416    #[test]
417    fn empty_headername() {
418        assert!(HeaderName::new_from_ascii("".to_owned()).is_err());
419    }
420
421    #[test]
422    fn const_valid_headername() {
423        let _ = HeaderName::new_from_ascii_str("From");
424    }
425
426    #[test]
427    #[should_panic]
428    fn const_non_ascii_headername() {
429        let _ = HeaderName::new_from_ascii_str("🌎");
430    }
431
432    #[test]
433    #[should_panic]
434    fn const_spaces_in_headername() {
435        let _ = HeaderName::new_from_ascii_str("From ");
436    }
437
438    #[test]
439    #[should_panic]
440    fn const_colons_in_headername() {
441        let _ = HeaderName::new_from_ascii_str("From:");
442    }
443
444    #[test]
445    #[should_panic]
446    fn const_empty_headername() {
447        let _ = HeaderName::new_from_ascii_str("");
448    }
449
450    #[test]
451    fn headername_headername_eq() {
452        assert_eq!(
453            HeaderName::new_from_ascii_str("From"),
454            HeaderName::new_from_ascii_str("From")
455        );
456    }
457
458    #[test]
459    fn headername_str_eq() {
460        assert_eq!(HeaderName::new_from_ascii_str("From"), "From");
461    }
462
463    #[test]
464    fn str_headername_eq() {
465        assert_eq!("From", HeaderName::new_from_ascii_str("From"));
466    }
467
468    #[test]
469    fn headername_headername_eq_case_insensitive() {
470        assert_eq!(
471            HeaderName::new_from_ascii_str("From"),
472            HeaderName::new_from_ascii_str("from")
473        );
474    }
475
476    #[test]
477    fn headername_str_eq_case_insensitive() {
478        assert_eq!(HeaderName::new_from_ascii_str("From"), "from");
479    }
480
481    #[test]
482    fn str_headername_eq_case_insensitive() {
483        assert_eq!("from", HeaderName::new_from_ascii_str("From"));
484    }
485
486    #[test]
487    fn headername_headername_ne() {
488        assert_ne!(
489            HeaderName::new_from_ascii_str("From"),
490            HeaderName::new_from_ascii_str("To")
491        );
492    }
493
494    #[test]
495    fn headername_str_ne() {
496        assert_ne!(HeaderName::new_from_ascii_str("From"), "To");
497    }
498
499    #[test]
500    fn str_headername_ne() {
501        assert_ne!("From", HeaderName::new_from_ascii_str("To"));
502    }
503
504    // names taken randomly from https://it.wikipedia.org/wiki/Pinco_Pallino
505
506    #[test]
507    fn format_ascii() {
508        let mut headers = Headers::new();
509        headers.insert_raw(HeaderValue::new(
510            HeaderName::new_from_ascii_str("To"),
511            "John Doe <example@example.com>, Jean Dupont <jean@example.com>".to_owned(),
512        ));
513
514        assert_eq!(
515            headers.to_string(),
516            "To: John Doe <example@example.com>, Jean Dupont <jean@example.com>\r\n"
517        );
518    }
519
520    #[test]
521    fn format_ascii_with_folding() {
522        let mut headers = Headers::new();
523        headers.insert_raw(HeaderValue::new(
524            HeaderName::new_from_ascii_str("To"),
525            "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_owned(),
526        ));
527
528        assert_eq!(
529            headers.to_string(),
530            concat!(
531                "To: Ascii <example@example.com>, John Doe <johndoe@example.com, John Smith\r\n",
532                " <johnsmith@example.com>, Pinco Pallino <pincopallino@example.com>, Jemand\r\n",
533                " <jemand@example.com>, Jean Dupont <jean@example.com>\r\n"
534            )
535        );
536    }
537
538    #[test]
539    fn format_ascii_with_folding_long_line() {
540        let mut headers = Headers::new();
541        headers.insert_raw(HeaderValue::new(
542            HeaderName::new_from_ascii_str("Subject"),
543            "Hello! This is lettre, and this IsAVeryLongLineDoYouKnowWhatsGoingToHappenIGuessWeAreGoingToFindOut. Ok I guess that's it!".to_owned()
544        ));
545
546        assert_eq!(
547            headers.to_string(),
548            concat!(
549                "Subject: Hello! This is lettre, and this\r\n",
550                " IsAVeryLongLineDoYouKnowWhatsGoingToHappenIGuessWeAreGoingToFindOut. Ok I\r\n",
551                " guess that's it!\r\n"
552            )
553        );
554    }
555
556    #[test]
557    fn format_ascii_with_folding_very_long_line() {
558        let mut headers = Headers::new();
559        headers.insert_raw(
560            HeaderValue::new(
561            HeaderName::new_from_ascii_str("Subject"),
562            "Hello! IGuessTheLastLineWasntLongEnoughSoLetsTryAgainShallWeWhatDoYouThinkItsGoingToHappenIGuessWereAboutToFindOut! I don't know".to_owned()
563        ));
564
565        assert_eq!(
566            headers.to_string(),
567            concat!(
568                "Subject: Hello!\r\n",
569                " IGuessTheLastLineWasntLongEnoughSoLetsTryAgainShallWeWhatDoYouThinkItsGoingToHappenIGuessWereAboutToFindOut!\r\n",
570                " I don't know\r\n",
571            )
572        );
573    }
574
575    #[test]
576    fn format_ascii_with_folding_giant_word() {
577        let mut headers = Headers::new();
578        headers.insert_raw(HeaderValue::new(
579            HeaderName::new_from_ascii_str("Subject"),
580            "1abcdefghijklmnopqrstuvwxyz2abcdefghijklmnopqrstuvwxyz3abcdefghijklmnopqrstuvwxyz4abcdefghijklmnopqrstuvwxyz5abcdefghijklmnopqrstuvwxyz6abcdefghijklmnopqrstuvwxyz".to_owned()
581        ));
582
583        assert_eq!(
584            headers.to_string(),
585            "Subject: 1abcdefghijklmnopqrstuvwxyz2abcdefghijklmnopqrstuvwxyz3abcdefghijklmnopqrstuvwxyz4abcdefghijklmnopqrstuvwxyz5abcdefghijklmnopqrstuvwxyz6abcdefghijklmnopqrstuvwxyz\r\n",
586        );
587    }
588
589    #[test]
590    fn format_special() {
591        let mut headers = Headers::new();
592        headers.insert_raw(HeaderValue::new(
593            HeaderName::new_from_ascii_str("To"),
594            "Seán <sean@example.com>".to_owned(),
595        ));
596
597        assert_eq!(
598            headers.to_string(),
599            "To: =?utf-8?b?U2XDoW4=?= <sean@example.com>\r\n"
600        );
601    }
602
603    #[test]
604    fn format_special_emoji() {
605        let mut headers = Headers::new();
606        headers.insert_raw(HeaderValue::new(
607            HeaderName::new_from_ascii_str("To"),
608            "🌎 <world@example.com>".to_owned(),
609        ));
610
611        assert_eq!(
612            headers.to_string(),
613            "To: =?utf-8?b?8J+Mjg==?= <world@example.com>\r\n"
614        );
615    }
616
617    #[test]
618    fn format_special_with_folding() {
619        let mut headers = Headers::new();
620        let to = To::from(Mailboxes::from_iter([
621            "🌍 <world@example.com>".parse().unwrap(),
622            "🦆 Everywhere <ducks@example.com>".parse().unwrap(),
623            "Иванов Иван Иванович <ivanov@example.com>".parse().unwrap(),
624            "Jānis Bērziņš <janis@example.com>".parse().unwrap(),
625            "Seán Ó Rudaí <sean@example.com>".parse().unwrap(),
626        ]));
627        headers.set(to);
628
629        assert_eq!(
630            headers.to_string(),
631            concat!(
632                "To: =?utf-8?b?8J+MjQ==?= <world@example.com>, =?utf-8?b?8J+mhiBFdmVyeXdo?=\r\n",
633                " =?utf-8?b?ZXJl?= <ducks@example.com>, =?utf-8?b?0JjQstCw0L3QvtCyINCY0LI=?=\r\n",
634                " =?utf-8?b?0LDQvSDQmNCy0LDQvdC+0LLQuNGH?= <ivanov@example.com>,\r\n",
635                " =?utf-8?b?SsSBbmlzIELEk3J6acWGxaE=?= <janis@example.com>, =?utf-8?b?U2U=?=\r\n",
636                " =?utf-8?b?w6FuIMOTIFJ1ZGHDrQ==?= <sean@example.com>\r\n",
637            )
638        );
639    }
640
641    #[test]
642    fn format_special_with_folding_raw() {
643        let mut headers = Headers::new();
644        headers.insert_raw(HeaderValue::new(
645            HeaderName::new_from_ascii_str("To"),
646            "🌍 <world@example.com>, 🦆 Everywhere <ducks@example.com>, Иванов Иван Иванович <ivanov@example.com>, Jānis Bērziņš <janis@example.com>, Seán Ó Rudaí <sean@example.com>".to_owned(),
647        ));
648
649        assert_eq!(
650            headers.to_string(),
651            concat!(
652                "To: =?utf-8?b?8J+MjQ==?= <world@example.com>, =?utf-8?b?8J+mhg==?=\r\n",
653                " Everywhere <ducks@example.com>, =?utf-8?b?0JjQstCw0L3QvtCyINCY0LLQsNC9?=\r\n",
654                " =?utf-8?b?INCY0LLQsNC90L7QstC40Yc=?= <ivanov@example.com>,\r\n",
655                " =?utf-8?b?SsSBbmlzIELEk3J6acWGxaE=?= <janis@example.com>, =?utf-8?b?U2U=?=\r\n",
656                " =?utf-8?b?w6FuIMOTIFJ1ZGHDrQ==?= <sean@example.com>\r\n",
657            )
658        );
659    }
660
661    #[test]
662    fn format_slice_on_char_boundary_bug() {
663        let mut headers = Headers::new();
664        headers.insert_raw(
665            HeaderValue::new(
666            HeaderName::new_from_ascii_str("Subject"),
667            "🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳".to_owned(),)
668        );
669
670        assert_eq!(
671            headers.to_string(),
672            concat!(
673                "Subject: =?utf-8?b?8J+ls/CfpbPwn6Wz8J+ls/CfpbPwn6Wz8J+ls/CfpbPwn6Wz?=\r\n",
674                " =?utf-8?b?8J+ls/CfpbPwn6Wz8J+ls/CfpbPwn6Wz8J+ls/CfpbPwn6Wz8J+ls/CfpbM=?=\r\n",
675                " =?utf-8?b?8J+ls/CfpbPwn6Wz8J+ls/CfpbPwn6Wz8J+ls/CfpbPwn6Wz8J+ls/CfpbM=?=\r\n",
676                " =?utf-8?b?8J+ls/CfpbPwn6Wz8J+ls/CfpbPwn6Wz8J+ls/CfpbPwn6Wz8J+ls/CfpbM=?=\r\n",
677                " =?utf-8?b?8J+ls/CfpbPwn6Wz8J+ls/CfpbPwn6Wz8J+ls/CfpbPwn6Wz8J+ls/CfpbM=?=\r\n",
678                " =?utf-8?b?8J+ls/CfpbPwn6Wz8J+ls/CfpbPwn6Wz8J+lsw==?=\r\n"
679            )
680        );
681    }
682
683    #[test]
684    fn format_bad_stuff() {
685        let mut headers = Headers::new();
686        headers.insert_raw(HeaderValue::new(
687            HeaderName::new_from_ascii_str("Subject"),
688            "Hello! \r\n This is \" bad \0. 👋".to_owned(),
689        ));
690
691        assert_eq!(
692            headers.to_string(),
693            "Subject: Hello! =?utf-8?b?DQo=?= This is \" bad =?utf-8?b?AC4g8J+Riw==?=\r\n"
694        );
695    }
696
697    #[test]
698    fn format_everything() {
699        let mut headers = Headers::new();
700        headers.insert_raw(
701            HeaderValue::new(
702            HeaderName::new_from_ascii_str("Subject"),
703            "Hello! This is lettre, and this IsAVeryLongLineDoYouKnowWhatsGoingToHappenIGuessWeAreGoingToFindOut. Ok I guess that's it!".to_owned()
704            )
705        );
706        headers.insert_raw(
707            HeaderValue::new(
708            HeaderName::new_from_ascii_str("To"),
709            "🌍 <world@example.com>, 🦆 Everywhere <ducks@example.com>, Иванов Иван Иванович <ivanov@example.com>, Jānis Bērziņš <janis@example.com>, Seán Ó Rudaí <sean@example.com>".to_owned(),
710            )
711        );
712        headers.insert_raw(HeaderValue::new(
713            HeaderName::new_from_ascii_str("From"),
714            "Someone <somewhere@example.com>".to_owned(),
715        ));
716        headers.insert_raw(HeaderValue::new(
717            HeaderName::new_from_ascii_str("Content-Transfer-Encoding"),
718            "quoted-printable".to_owned(),
719        ));
720
721        assert_eq!(
722            headers.to_string(),
723            concat!(
724                "Subject: Hello! This is lettre, and this\r\n",
725                " IsAVeryLongLineDoYouKnowWhatsGoingToHappenIGuessWeAreGoingToFindOut. Ok I\r\n",
726                " guess that's it!\r\n",
727                "To: =?utf-8?b?8J+MjQ==?= <world@example.com>, =?utf-8?b?8J+mhg==?=\r\n",
728                " Everywhere <ducks@example.com>, =?utf-8?b?0JjQstCw0L3QvtCyINCY0LLQsNC9?=\r\n",
729                " =?utf-8?b?INCY0LLQsNC90L7QstC40Yc=?= <ivanov@example.com>,\r\n",
730                " =?utf-8?b?SsSBbmlzIELEk3J6acWGxaE=?= <janis@example.com>, =?utf-8?b?U2U=?=\r\n",
731                " =?utf-8?b?w6FuIMOTIFJ1ZGHDrQ==?= <sean@example.com>\r\n",
732                "From: Someone <somewhere@example.com>\r\n",
733                "Content-Transfer-Encoding: quoted-printable\r\n",
734            )
735        );
736    }
737
738    #[test]
739    fn issue_653() {
740        let mut headers = Headers::new();
741        headers.insert_raw(HeaderValue::new(
742            HeaderName::new_from_ascii_str("Subject"),
743            "+仮名 :a;go; ;;;;;s;;;;;;;;;;;;;;;;fffeinmjgggggggggfっ".to_owned(),
744        ));
745
746        assert_eq!(
747            headers.to_string(),
748            concat!(
749                "Subject: =?utf-8?b?77yL5Luu5ZCN?= :a;go; =?utf-8?b?Ozs7OztzOzs7Ozs7Ozs7?=\r\n",
750                " =?utf-8?b?Ozs7Ozs7O2ZmZmVpbm1qZ2dnZ2dnZ2dn772G44Gj?=\r\n",
751            )
752        );
753    }
754}