lettre/message/attachment.rs
1use crate::message::{
2 header::{self, ContentType},
3 IntoBody, SinglePart,
4};
5
6/// `SinglePart` builder for attachments
7///
8/// Allows building attachment parts easily.
9#[derive(Clone)]
10pub struct Attachment {
11 disposition: Disposition,
12}
13
14#[derive(Clone)]
15enum Disposition {
16 /// File name
17 Attached(String),
18 /// Content id
19 Inline(String),
20}
21
22impl Attachment {
23 /// Create a new attachment
24 ///
25 /// This attachment will be displayed as a normal attachment,
26 /// with the chosen `filename` appearing as the file name.
27 ///
28 /// ```rust
29 /// # use std::error::Error;
30 /// use std::fs;
31 ///
32 /// use lettre::message::{header::ContentType, Attachment};
33 ///
34 /// # fn main() -> Result<(), Box<dyn Error>> {
35 /// let filename = String::from("invoice.pdf");
36 /// # if false {
37 /// let filebody = fs::read("invoice.pdf")?;
38 /// # }
39 /// # let filebody = fs::read("docs/lettre.png")?;
40 /// let content_type = ContentType::parse("application/pdf").unwrap();
41 /// let attachment = Attachment::new(filename).body(filebody, content_type);
42 ///
43 /// // The document `attachment` will show up as a normal attachment.
44 /// # Ok(())
45 /// # }
46 /// ```
47 pub fn new(filename: String) -> Self {
48 Attachment {
49 disposition: Disposition::Attached(filename),
50 }
51 }
52
53 /// Create a new inline attachment
54 ///
55 /// This attachment should be displayed inline into the message
56 /// body:
57 ///
58 /// ```html
59 /// <img src="cid:123">
60 /// ```
61 ///
62 ///
63 /// ```rust
64 /// # use std::error::Error;
65 /// use std::fs;
66 ///
67 /// use lettre::message::{header::ContentType, Attachment};
68 ///
69 /// # fn main() -> Result<(), Box<dyn Error>> {
70 /// let content_id = String::from("123");
71 /// # if false {
72 /// let filebody = fs::read("image.jpg")?;
73 /// # }
74 /// # let filebody = fs::read("docs/lettre.png")?;
75 /// let content_type = ContentType::parse("image/jpeg").unwrap();
76 /// let attachment = Attachment::new_inline(content_id).body(filebody, content_type);
77 ///
78 /// // The image `attachment` will display inline into the email.
79 /// # Ok(())
80 /// # }
81 /// ```
82 pub fn new_inline(content_id: String) -> Self {
83 Attachment {
84 disposition: Disposition::Inline(content_id),
85 }
86 }
87
88 /// Build the attachment into a [`SinglePart`] which can then be used to build the rest of the email
89 ///
90 /// Look at the [Complex MIME body example](crate::message#complex-mime-body)
91 /// to see how [`SinglePart`] can be put into the email.
92 pub fn body<T: IntoBody>(self, content: T, content_type: ContentType) -> SinglePart {
93 let mut builder = SinglePart::builder();
94 builder = match self.disposition {
95 Disposition::Attached(filename) => {
96 builder.header(header::ContentDisposition::attachment(&filename))
97 }
98 Disposition::Inline(content_id) => builder
99 .header(header::ContentId::from(format!("<{content_id}>")))
100 .header(header::ContentDisposition::inline()),
101 };
102 builder = builder.header(content_type);
103 builder.body(content)
104 }
105}
106
107#[cfg(test)]
108mod tests {
109 use crate::message::header::ContentType;
110
111 #[test]
112 fn attachment() {
113 let part = super::Attachment::new(String::from("test.txt")).body(
114 String::from("Hello world!"),
115 ContentType::parse("text/plain").unwrap(),
116 );
117 assert_eq!(
118 &String::from_utf8_lossy(&part.formatted()),
119 concat!(
120 "Content-Disposition: attachment; filename=\"test.txt\"\r\n",
121 "Content-Type: text/plain\r\n",
122 "Content-Transfer-Encoding: 7bit\r\n\r\n",
123 "Hello world!\r\n",
124 )
125 );
126 }
127
128 #[test]
129 fn attachment_inline() {
130 let part = super::Attachment::new_inline(String::from("id")).body(
131 String::from("Hello world!"),
132 ContentType::parse("text/plain").unwrap(),
133 );
134 assert_eq!(
135 &String::from_utf8_lossy(&part.formatted()),
136 concat!(
137 "Content-ID: <id>\r\n",
138 "Content-Disposition: inline\r\n",
139 "Content-Type: text/plain\r\n",
140 "Content-Transfer-Encoding: 7bit\r\n\r\n",
141 "Hello world!\r\n"
142 )
143 );
144 }
145}