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        assert!(!ascii.is_empty());
190        assert!(ascii.len() <= 76);
191        assert!(ascii.is_ascii());
192
193        let bytes = ascii.as_bytes();
194        let mut i = 0;
195        while i < bytes.len() {
196            assert!(bytes[i] != b' ');
197            assert!(bytes[i] != b':');
198
199            i += 1;
200        }
201
202        Self(Cow::Borrowed(ascii))
203    }
204}
205
206impl Display for HeaderName {
207    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
208        f.write_str(self)
209    }
210}
211
212impl Deref for HeaderName {
213    type Target = str;
214
215    #[inline]
216    fn deref(&self) -> &Self::Target {
217        &self.0
218    }
219}
220
221impl AsRef<[u8]> for HeaderName {
222    #[inline]
223    fn as_ref(&self) -> &[u8] {
224        let s: &str = self.as_ref();
225        s.as_bytes()
226    }
227}
228
229impl AsRef<str> for HeaderName {
230    #[inline]
231    fn as_ref(&self) -> &str {
232        &self.0
233    }
234}
235
236impl PartialEq<HeaderName> for HeaderName {
237    fn eq(&self, other: &HeaderName) -> bool {
238        self.eq_ignore_ascii_case(other)
239    }
240}
241
242impl PartialEq<&str> for HeaderName {
243    fn eq(&self, other: &&str) -> bool {
244        self.eq_ignore_ascii_case(other)
245    }
246}
247
248impl PartialEq<HeaderName> for &str {
249    fn eq(&self, other: &HeaderName) -> bool {
250        self.eq_ignore_ascii_case(other)
251    }
252}
253
254/// A safe for use header value
255#[derive(Debug, Clone, PartialEq)]
256pub struct HeaderValue {
257    name: HeaderName,
258    raw_value: String,
259    encoded_value: String,
260}
261
262impl HeaderValue {
263    /// Construct a new `HeaderValue` and encode it
264    ///
265    /// Takes the header `name` and the `raw_value` and encodes
266    /// it via `RFC2047` and line folds it.
267    ///
268    /// [`RFC2047`]: https://datatracker.ietf.org/doc/html/rfc2047
269    pub fn new(name: HeaderName, raw_value: String) -> Self {
270        let mut encoded_value = String::with_capacity(raw_value.len());
271        HeaderValueEncoder::encode(&name, &raw_value, &mut encoded_value).unwrap();
272
273        Self {
274            name,
275            raw_value,
276            encoded_value,
277        }
278    }
279
280    /// Construct a new `HeaderValue` using a pre-encoded header value
281    ///
282    /// This method is _extremely_ dangerous as it opens up
283    /// the encoder to header injection attacks, but is sometimes
284    /// acceptable for use if `encoded_value` contains only ascii
285    /// printable characters and is already line folded.
286    ///
287    /// When in doubt, use [`HeaderValue::new`].
288    pub fn dangerous_new_pre_encoded(
289        name: HeaderName,
290        raw_value: String,
291        encoded_value: String,
292    ) -> Self {
293        Self {
294            name,
295            raw_value,
296            encoded_value,
297        }
298    }
299
300    #[cfg(feature = "dkim")]
301    pub(crate) fn get_raw(&self) -> &str {
302        &self.raw_value
303    }
304
305    #[cfg(feature = "dkim")]
306    pub(crate) fn get_encoded(&self) -> &str {
307        &self.encoded_value
308    }
309}
310
311/// [RFC 1522](https://tools.ietf.org/html/rfc1522) header value encoder
312struct HeaderValueEncoder<'a> {
313    writer: EmailWriter<'a>,
314    encode_buf: String,
315}
316
317impl<'a> HeaderValueEncoder<'a> {
318    fn encode(name: &str, value: &'a str, f: &'a mut impl fmt::Write) -> fmt::Result {
319        let encoder = Self::new(name, f);
320        encoder.format(value.split_inclusive(' '))
321    }
322
323    fn new(name: &str, writer: &'a mut dyn Write) -> Self {
324        let line_len = name.len() + ": ".len();
325        let writer = EmailWriter::new(writer, line_len, 0, false);
326
327        Self {
328            writer,
329            encode_buf: String::new(),
330        }
331    }
332
333    fn format(mut self, words_iter: impl Iterator<Item = &'a str>) -> fmt::Result {
334        for next_word in words_iter {
335            let allowed = allowed_str(next_word);
336
337            if allowed {
338                // This word only contains allowed characters
339
340                // the next word is allowed, but we may have accumulated some words to encode
341                self.flush_encode_buf()?;
342
343                self.writer.folding().write_str(next_word)?;
344            } else {
345                // This word contains unallowed characters
346                self.encode_buf.push_str(next_word);
347            }
348        }
349
350        self.flush_encode_buf()?;
351
352        Ok(())
353    }
354
355    fn flush_encode_buf(&mut self) -> fmt::Result {
356        if self.encode_buf.is_empty() {
357            // nothing to encode
358            return Ok(());
359        }
360
361        let prefix = self.encode_buf.trim_end_matches(' ');
362        email_encoding::headers::rfc2047::encode(prefix, &mut self.writer)?;
363
364        // TODO: add a better API for doing this in email-encoding
365        let spaces = self.encode_buf.len() - prefix.len();
366        for _ in 0..spaces {
367            self.writer.space();
368        }
369
370        self.encode_buf.clear();
371        Ok(())
372    }
373}
374
375fn allowed_str(s: &str) -> bool {
376    s.bytes().all(allowed_char)
377}
378
379const fn allowed_char(c: u8) -> bool {
380    c >= 1 && c <= 9 || c == 11 || c == 12 || c >= 14 && c <= 127
381}
382
383#[cfg(test)]
384mod tests {
385    use pretty_assertions::assert_eq;
386
387    use super::{HeaderName, HeaderValue, Headers, To};
388    use crate::message::Mailboxes;
389
390    #[test]
391    fn valid_headername() {
392        assert!(HeaderName::new_from_ascii(String::from("From")).is_ok());
393    }
394
395    #[test]
396    fn non_ascii_headername() {
397        assert!(HeaderName::new_from_ascii(String::from("🌎")).is_err());
398    }
399
400    #[test]
401    fn spaces_in_headername() {
402        assert!(HeaderName::new_from_ascii(String::from("From ")).is_err());
403    }
404
405    #[test]
406    fn colons_in_headername() {
407        assert!(HeaderName::new_from_ascii(String::from("From:")).is_err());
408    }
409
410    #[test]
411    fn empty_headername() {
412        assert!(HeaderName::new_from_ascii("".to_owned()).is_err());
413    }
414
415    #[test]
416    fn const_valid_headername() {
417        let _ = HeaderName::new_from_ascii_str("From");
418    }
419
420    #[test]
421    #[should_panic]
422    fn const_non_ascii_headername() {
423        let _ = HeaderName::new_from_ascii_str("🌎");
424    }
425
426    #[test]
427    #[should_panic]
428    fn const_spaces_in_headername() {
429        let _ = HeaderName::new_from_ascii_str("From ");
430    }
431
432    #[test]
433    #[should_panic]
434    fn const_colons_in_headername() {
435        let _ = HeaderName::new_from_ascii_str("From:");
436    }
437
438    #[test]
439    #[should_panic]
440    fn const_empty_headername() {
441        let _ = HeaderName::new_from_ascii_str("");
442    }
443
444    #[test]
445    fn headername_headername_eq() {
446        assert_eq!(
447            HeaderName::new_from_ascii_str("From"),
448            HeaderName::new_from_ascii_str("From")
449        );
450    }
451
452    #[test]
453    fn headername_str_eq() {
454        assert_eq!(HeaderName::new_from_ascii_str("From"), "From");
455    }
456
457    #[test]
458    fn str_headername_eq() {
459        assert_eq!("From", HeaderName::new_from_ascii_str("From"));
460    }
461
462    #[test]
463    fn headername_headername_eq_case_insensitive() {
464        assert_eq!(
465            HeaderName::new_from_ascii_str("From"),
466            HeaderName::new_from_ascii_str("from")
467        );
468    }
469
470    #[test]
471    fn headername_str_eq_case_insensitive() {
472        assert_eq!(HeaderName::new_from_ascii_str("From"), "from");
473    }
474
475    #[test]
476    fn str_headername_eq_case_insensitive() {
477        assert_eq!("from", HeaderName::new_from_ascii_str("From"));
478    }
479
480    #[test]
481    fn headername_headername_ne() {
482        assert_ne!(
483            HeaderName::new_from_ascii_str("From"),
484            HeaderName::new_from_ascii_str("To")
485        );
486    }
487
488    #[test]
489    fn headername_str_ne() {
490        assert_ne!(HeaderName::new_from_ascii_str("From"), "To");
491    }
492
493    #[test]
494    fn str_headername_ne() {
495        assert_ne!("From", HeaderName::new_from_ascii_str("To"));
496    }
497
498    // names taken randomly from https://it.wikipedia.org/wiki/Pinco_Pallino
499
500    #[test]
501    fn format_ascii() {
502        let mut headers = Headers::new();
503        headers.insert_raw(HeaderValue::new(
504            HeaderName::new_from_ascii_str("To"),
505            "John Doe <example@example.com>, Jean Dupont <jean@example.com>".to_owned(),
506        ));
507
508        assert_eq!(
509            headers.to_string(),
510            "To: John Doe <example@example.com>, Jean Dupont <jean@example.com>\r\n"
511        );
512    }
513
514    #[test]
515    fn format_ascii_with_folding() {
516        let mut headers = Headers::new();
517        headers.insert_raw(HeaderValue::new(
518            HeaderName::new_from_ascii_str("To"),
519            "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(),
520        ));
521
522        assert_eq!(
523            headers.to_string(),
524            concat!(
525                "To: Ascii <example@example.com>, John Doe <johndoe@example.com, John Smith\r\n",
526                " <johnsmith@example.com>, Pinco Pallino <pincopallino@example.com>, Jemand\r\n",
527                " <jemand@example.com>, Jean Dupont <jean@example.com>\r\n"
528            )
529        );
530    }
531
532    #[test]
533    fn format_ascii_with_folding_long_line() {
534        let mut headers = Headers::new();
535        headers.insert_raw(HeaderValue::new(
536            HeaderName::new_from_ascii_str("Subject"),
537            "Hello! This is lettre, and this IsAVeryLongLineDoYouKnowWhatsGoingToHappenIGuessWeAreGoingToFindOut. Ok I guess that's it!".to_owned()
538        ));
539
540        assert_eq!(
541            headers.to_string(),
542            concat!(
543                "Subject: Hello! This is lettre, and this\r\n",
544                " IsAVeryLongLineDoYouKnowWhatsGoingToHappenIGuessWeAreGoingToFindOut. Ok I\r\n",
545                " guess that's it!\r\n"
546            )
547        );
548    }
549
550    #[test]
551    fn format_ascii_with_folding_very_long_line() {
552        let mut headers = Headers::new();
553        headers.insert_raw(
554            HeaderValue::new(
555            HeaderName::new_from_ascii_str("Subject"),
556            "Hello! IGuessTheLastLineWasntLongEnoughSoLetsTryAgainShallWeWhatDoYouThinkItsGoingToHappenIGuessWereAboutToFindOut! I don't know".to_owned()
557        ));
558
559        assert_eq!(
560            headers.to_string(),
561            concat!(
562                "Subject: Hello!\r\n",
563                " IGuessTheLastLineWasntLongEnoughSoLetsTryAgainShallWeWhatDoYouThinkItsGoingToHappenIGuessWereAboutToFindOut!\r\n",
564                " I don't know\r\n",
565            )
566        );
567    }
568
569    #[test]
570    fn format_ascii_with_folding_giant_word() {
571        let mut headers = Headers::new();
572        headers.insert_raw(HeaderValue::new(
573            HeaderName::new_from_ascii_str("Subject"),
574            "1abcdefghijklmnopqrstuvwxyz2abcdefghijklmnopqrstuvwxyz3abcdefghijklmnopqrstuvwxyz4abcdefghijklmnopqrstuvwxyz5abcdefghijklmnopqrstuvwxyz6abcdefghijklmnopqrstuvwxyz".to_owned()
575        ));
576
577        assert_eq!(
578            headers.to_string(),
579            "Subject: 1abcdefghijklmnopqrstuvwxyz2abcdefghijklmnopqrstuvwxyz3abcdefghijklmnopqrstuvwxyz4abcdefghijklmnopqrstuvwxyz5abcdefghijklmnopqrstuvwxyz6abcdefghijklmnopqrstuvwxyz\r\n",
580        );
581    }
582
583    #[test]
584    fn format_special() {
585        let mut headers = Headers::new();
586        headers.insert_raw(HeaderValue::new(
587            HeaderName::new_from_ascii_str("To"),
588            "Seán <sean@example.com>".to_owned(),
589        ));
590
591        assert_eq!(
592            headers.to_string(),
593            "To: =?utf-8?b?U2XDoW4=?= <sean@example.com>\r\n"
594        );
595    }
596
597    #[test]
598    fn format_special_emoji() {
599        let mut headers = Headers::new();
600        headers.insert_raw(HeaderValue::new(
601            HeaderName::new_from_ascii_str("To"),
602            "🌎 <world@example.com>".to_owned(),
603        ));
604
605        assert_eq!(
606            headers.to_string(),
607            "To: =?utf-8?b?8J+Mjg==?= <world@example.com>\r\n"
608        );
609    }
610
611    #[test]
612    fn format_special_with_folding() {
613        let mut headers = Headers::new();
614        let to = To::from(Mailboxes::from_iter([
615            "🌍 <world@example.com>".parse().unwrap(),
616            "🦆 Everywhere <ducks@example.com>".parse().unwrap(),
617            "Иванов Иван Иванович <ivanov@example.com>".parse().unwrap(),
618            "Jānis Bērziņš <janis@example.com>".parse().unwrap(),
619            "Seán Ó Rudaí <sean@example.com>".parse().unwrap(),
620        ]));
621        headers.set(to);
622
623        assert_eq!(
624            headers.to_string(),
625            concat!(
626                "To: =?utf-8?b?8J+MjQ==?= <world@example.com>, =?utf-8?b?8J+mhiBFdmVyeXdo?=\r\n",
627                " =?utf-8?b?ZXJl?= <ducks@example.com>, =?utf-8?b?0JjQstCw0L3QvtCyINCY0LI=?=\r\n",
628                " =?utf-8?b?0LDQvSDQmNCy0LDQvdC+0LLQuNGH?= <ivanov@example.com>,\r\n",
629                " =?utf-8?b?SsSBbmlzIELEk3J6acWGxaE=?= <janis@example.com>, =?utf-8?b?U2U=?=\r\n",
630                " =?utf-8?b?w6FuIMOTIFJ1ZGHDrQ==?= <sean@example.com>\r\n",
631            )
632        );
633    }
634
635    #[test]
636    fn format_special_with_folding_raw() {
637        let mut headers = Headers::new();
638        headers.insert_raw(HeaderValue::new(
639            HeaderName::new_from_ascii_str("To"),
640            "🌍 <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(),
641        ));
642
643        assert_eq!(
644            headers.to_string(),
645            concat!(
646                "To: =?utf-8?b?8J+MjQ==?= <world@example.com>, =?utf-8?b?8J+mhg==?=\r\n",
647                " Everywhere <ducks@example.com>, =?utf-8?b?0JjQstCw0L3QvtCyINCY0LLQsNC9?=\r\n",
648                " =?utf-8?b?INCY0LLQsNC90L7QstC40Yc=?= <ivanov@example.com>,\r\n",
649                " =?utf-8?b?SsSBbmlzIELEk3J6acWGxaE=?= <janis@example.com>, =?utf-8?b?U2U=?=\r\n",
650                " =?utf-8?b?w6FuIMOTIFJ1ZGHDrQ==?= <sean@example.com>\r\n",
651            )
652        );
653    }
654
655    #[test]
656    fn format_slice_on_char_boundary_bug() {
657        let mut headers = Headers::new();
658        headers.insert_raw(
659            HeaderValue::new(
660            HeaderName::new_from_ascii_str("Subject"),
661            "🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳".to_owned(),)
662        );
663
664        assert_eq!(
665            headers.to_string(),
666            concat!(
667                "Subject: =?utf-8?b?8J+ls/CfpbPwn6Wz8J+ls/CfpbPwn6Wz8J+ls/CfpbPwn6Wz?=\r\n",
668                " =?utf-8?b?8J+ls/CfpbPwn6Wz8J+ls/CfpbPwn6Wz8J+ls/CfpbPwn6Wz8J+ls/CfpbM=?=\r\n",
669                " =?utf-8?b?8J+ls/CfpbPwn6Wz8J+ls/CfpbPwn6Wz8J+ls/CfpbPwn6Wz8J+ls/CfpbM=?=\r\n",
670                " =?utf-8?b?8J+ls/CfpbPwn6Wz8J+ls/CfpbPwn6Wz8J+ls/CfpbPwn6Wz8J+ls/CfpbM=?=\r\n",
671                " =?utf-8?b?8J+ls/CfpbPwn6Wz8J+ls/CfpbPwn6Wz8J+ls/CfpbPwn6Wz8J+ls/CfpbM=?=\r\n",
672                " =?utf-8?b?8J+ls/CfpbPwn6Wz8J+ls/CfpbPwn6Wz8J+lsw==?=\r\n"
673            )
674        );
675    }
676
677    #[test]
678    fn format_bad_stuff() {
679        let mut headers = Headers::new();
680        headers.insert_raw(HeaderValue::new(
681            HeaderName::new_from_ascii_str("Subject"),
682            "Hello! \r\n This is \" bad \0. 👋".to_owned(),
683        ));
684
685        assert_eq!(
686            headers.to_string(),
687            "Subject: Hello! =?utf-8?b?DQo=?= This is \" bad =?utf-8?b?AC4g8J+Riw==?=\r\n"
688        );
689    }
690
691    #[test]
692    fn format_everything() {
693        let mut headers = Headers::new();
694        headers.insert_raw(
695            HeaderValue::new(
696            HeaderName::new_from_ascii_str("Subject"),
697            "Hello! This is lettre, and this IsAVeryLongLineDoYouKnowWhatsGoingToHappenIGuessWeAreGoingToFindOut. Ok I guess that's it!".to_owned()
698            )
699        );
700        headers.insert_raw(
701            HeaderValue::new(
702            HeaderName::new_from_ascii_str("To"),
703            "🌍 <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(),
704            )
705        );
706        headers.insert_raw(HeaderValue::new(
707            HeaderName::new_from_ascii_str("From"),
708            "Someone <somewhere@example.com>".to_owned(),
709        ));
710        headers.insert_raw(HeaderValue::new(
711            HeaderName::new_from_ascii_str("Content-Transfer-Encoding"),
712            "quoted-printable".to_owned(),
713        ));
714
715        assert_eq!(
716            headers.to_string(),
717            concat!(
718                "Subject: Hello! This is lettre, and this\r\n",
719                " IsAVeryLongLineDoYouKnowWhatsGoingToHappenIGuessWeAreGoingToFindOut. Ok I\r\n",
720                " guess that's it!\r\n",
721                "To: =?utf-8?b?8J+MjQ==?= <world@example.com>, =?utf-8?b?8J+mhg==?=\r\n",
722                " Everywhere <ducks@example.com>, =?utf-8?b?0JjQstCw0L3QvtCyINCY0LLQsNC9?=\r\n",
723                " =?utf-8?b?INCY0LLQsNC90L7QstC40Yc=?= <ivanov@example.com>,\r\n",
724                " =?utf-8?b?SsSBbmlzIELEk3J6acWGxaE=?= <janis@example.com>, =?utf-8?b?U2U=?=\r\n",
725                " =?utf-8?b?w6FuIMOTIFJ1ZGHDrQ==?= <sean@example.com>\r\n",
726                "From: Someone <somewhere@example.com>\r\n",
727                "Content-Transfer-Encoding: quoted-printable\r\n",
728            )
729        );
730    }
731
732    #[test]
733    fn issue_653() {
734        let mut headers = Headers::new();
735        headers.insert_raw(HeaderValue::new(
736            HeaderName::new_from_ascii_str("Subject"),
737            "+仮名 :a;go; ;;;;;s;;;;;;;;;;;;;;;;fffeinmjgggggggggfっ".to_owned(),
738        ));
739
740        assert_eq!(
741            headers.to_string(),
742            concat!(
743                "Subject: =?utf-8?b?77yL5Luu5ZCN?= :a;go; =?utf-8?b?Ozs7OztzOzs7Ozs7Ozs7?=\r\n",
744                " =?utf-8?b?Ozs7Ozs7O2ZmZmVpbm1qZ2dnZ2dnZ2dn772G44Gj?=\r\n",
745            )
746        );
747    }
748}