1use 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
31pub 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#[derive(Debug, Clone, Default)]
44pub struct Headers {
45 headers: Vec<HeaderValue>,
46}
47
48impl Headers {
49 #[inline]
53 pub const fn new() -> Self {
54 Self {
55 headers: Vec::new(),
56 }
57 }
58
59 #[inline]
63 pub fn with_capacity(capacity: usize) -> Self {
64 Self {
65 headers: Vec::with_capacity(capacity),
66 }
67 }
68
69 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 pub fn set<H: Header>(&mut self, header: H) {
80 self.insert_raw(header.display());
81 }
82
83 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 #[inline]
95 pub fn clear(&mut self) {
96 self.headers.clear();
97 }
98
99 pub fn get_raw(&self, name: &str) -> Option<&str> {
103 self.find_header(name).map(|value| value.raw_value.as_str())
104 }
105
106 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 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 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#[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#[derive(Debug, Clone)]
174pub struct HeaderName(Cow<'static, str>);
175
176impl HeaderName {
177 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 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#[derive(Debug, Clone, PartialEq)]
256pub struct HeaderValue {
257 name: HeaderName,
258 raw_value: String,
259 encoded_value: String,
260}
261
262impl HeaderValue {
263 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 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
311struct 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 self.flush_encode_buf()?;
342
343 self.writer.folding().write_str(next_word)?;
344 } else {
345 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 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 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 #[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}