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 {
20 content_id: String,
21 name: Option<String>,
22 },
23}
24
25impl Attachment {
26 /// Create a new attachment
27 ///
28 /// This attachment will be displayed as a normal attachment,
29 /// with the chosen `filename` appearing as the file name.
30 ///
31 /// ```rust
32 /// # use std::error::Error;
33 /// use std::fs;
34 ///
35 /// use lettre::message::{header::ContentType, Attachment};
36 ///
37 /// # fn main() -> Result<(), Box<dyn Error>> {
38 /// let filename = String::from("invoice.pdf");
39 /// # if false {
40 /// let filebody = fs::read("invoice.pdf")?;
41 /// # }
42 /// # let filebody = fs::read("docs/lettre.png")?;
43 /// let content_type = ContentType::parse("application/pdf").unwrap();
44 /// let attachment = Attachment::new(filename).body(filebody, content_type);
45 ///
46 /// // The document `attachment` will show up as a normal attachment.
47 /// # Ok(())
48 /// # }
49 /// ```
50 pub fn new(filename: String) -> Self {
51 Attachment {
52 disposition: Disposition::Attached(filename),
53 }
54 }
55
56 /// Create a new inline attachment
57 ///
58 /// This attachment should be displayed inline into the message
59 /// body:
60 ///
61 /// ```html
62 /// <img src="cid:123">
63 /// ```
64 ///
65 ///
66 /// ```rust
67 /// # use std::error::Error;
68 /// use std::fs;
69 ///
70 /// use lettre::message::{header::ContentType, Attachment};
71 ///
72 /// # fn main() -> Result<(), Box<dyn Error>> {
73 /// let content_id = String::from("123");
74 /// # if false {
75 /// let filebody = fs::read("image.jpg")?;
76 /// # }
77 /// # let filebody = fs::read("docs/lettre.png")?;
78 /// let content_type = ContentType::parse("image/jpeg").unwrap();
79 /// let attachment = Attachment::new_inline(content_id).body(filebody, content_type);
80 ///
81 /// // The image `attachment` will display inline into the email.
82 /// # Ok(())
83 /// # }
84 /// ```
85 pub fn new_inline(content_id: String) -> Self {
86 Attachment {
87 disposition: Disposition::Inline {
88 content_id,
89 name: None,
90 },
91 }
92 }
93
94 /// Create a new inline attachment giving it a name
95 ///
96 /// This attachment should be displayed inline into the message
97 /// body:
98 ///
99 /// ```html
100 /// <img src="cid:123">
101 /// ```
102 ///
103 ///
104 /// ```rust
105 /// # use std::error::Error;
106 /// use std::fs;
107 ///
108 /// use lettre::message::{header::ContentType, Attachment};
109 ///
110 /// # fn main() -> Result<(), Box<dyn Error>> {
111 /// let content_id = String::from("123");
112 /// let file_name = String::from("image.jpg");
113 /// # if false {
114 /// let filebody = fs::read(&file_name)?;
115 /// # }
116 /// # let filebody = fs::read("docs/lettre.png")?;
117 /// let content_type = ContentType::parse("image/jpeg").unwrap();
118 /// let attachment =
119 /// Attachment::new_inline_with_name(content_id, file_name).body(filebody, content_type);
120 ///
121 /// // The image `attachment` will display inline into the email.
122 /// # Ok(())
123 /// # }
124 /// ```
125 pub fn new_inline_with_name(content_id: String, name: String) -> Self {
126 Attachment {
127 disposition: Disposition::Inline {
128 content_id,
129 name: Some(name),
130 },
131 }
132 }
133
134 /// Build the attachment into a [`SinglePart`] which can then be used to build the rest of the email
135 ///
136 /// Look at the [Complex MIME body example](crate::message#complex-mime-body)
137 /// to see how [`SinglePart`] can be put into the email.
138 pub fn body<T: IntoBody>(self, content: T, content_type: ContentType) -> SinglePart {
139 let mut builder = SinglePart::builder();
140 builder = match self.disposition {
141 Disposition::Attached(filename) => {
142 builder.header(header::ContentDisposition::attachment(&filename))
143 }
144 Disposition::Inline {
145 content_id,
146 name: None,
147 } => builder
148 .header(header::ContentId::from(format!("<{content_id}>")))
149 .header(header::ContentDisposition::inline()),
150 Disposition::Inline {
151 content_id,
152 name: Some(name),
153 } => builder
154 .header(header::ContentId::from(format!("<{content_id}>")))
155 .header(header::ContentDisposition::inline_with_name(&name)),
156 };
157 builder = builder.header(content_type);
158 builder.body(content)
159 }
160}
161
162#[cfg(test)]
163mod tests {
164 use crate::message::header::ContentType;
165
166 #[test]
167 fn attachment() {
168 let part = super::Attachment::new(String::from("test.txt")).body(
169 String::from("Hello world!"),
170 ContentType::parse("text/plain").unwrap(),
171 );
172 assert_eq!(
173 &String::from_utf8_lossy(&part.formatted()),
174 concat!(
175 "Content-Disposition: attachment; filename=\"test.txt\"\r\n",
176 "Content-Type: text/plain\r\n",
177 "Content-Transfer-Encoding: 7bit\r\n\r\n",
178 "Hello world!\r\n",
179 )
180 );
181 }
182
183 #[test]
184 fn attachment_inline() {
185 let part = super::Attachment::new_inline(String::from("id")).body(
186 String::from("Hello world!"),
187 ContentType::parse("text/plain").unwrap(),
188 );
189 assert_eq!(
190 &String::from_utf8_lossy(&part.formatted()),
191 concat!(
192 "Content-ID: <id>\r\n",
193 "Content-Disposition: inline\r\n",
194 "Content-Type: text/plain\r\n",
195 "Content-Transfer-Encoding: 7bit\r\n\r\n",
196 "Hello world!\r\n"
197 )
198 );
199 }
200
201 #[test]
202 fn attachment_inline_with_name() {
203 let id = String::from("id");
204 let name = String::from("test");
205 let part = super::Attachment::new_inline_with_name(id, name).body(
206 String::from("Hello world!"),
207 ContentType::parse("text/plain").unwrap(),
208 );
209 assert_eq!(
210 &String::from_utf8_lossy(&part.formatted()),
211 concat!(
212 "Content-ID: <id>\r\n",
213 "Content-Disposition: inline; filename=\"test\"\r\n",
214 "Content-Type: text/plain\r\n",
215 "Content-Transfer-Encoding: 7bit\r\n\r\n",
216 "Hello world!\r\n"
217 )
218 );
219 }
220}