lettre/message/header/
content_disposition.rs1use std::fmt::Write;
2
3use email_encoding::headers::writer::EmailWriter;
4
5use super::{Header, HeaderName, HeaderValue};
6use crate::BoxError;
7
8#[derive(Debug, Clone, PartialEq)]
12pub struct ContentDisposition(HeaderValue);
13
14impl ContentDisposition {
15 pub fn inline() -> Self {
17 Self(HeaderValue::dangerous_new_pre_encoded(
18 Self::name(),
19 "inline".to_owned(),
20 "inline".to_owned(),
21 ))
22 }
23
24 pub fn inline_with_name(file_name: &str) -> Self {
27 Self::with_name("inline", file_name)
28 }
29
30 pub fn attachment(file_name: &str) -> Self {
32 Self::with_name("attachment", file_name)
33 }
34
35 fn with_name(kind: &str, file_name: &str) -> Self {
36 let raw_value = format!("{kind}; filename=\"{file_name}\"");
37
38 let mut encoded_value = String::new();
39 let line_len = "Content-Disposition: ".len();
40 {
41 let mut w = EmailWriter::new(&mut encoded_value, line_len, 0, false);
42 w.write_str(kind).expect("writing `kind` returned an error");
43 w.write_char(';').expect("writing `;` returned an error");
44 w.space();
45
46 email_encoding::headers::rfc2231::encode("filename", file_name, &mut w)
47 .expect("some Write implementation returned an error");
48 }
49
50 Self(HeaderValue::dangerous_new_pre_encoded(
51 Self::name(),
52 raw_value,
53 encoded_value,
54 ))
55 }
56}
57
58impl Header for ContentDisposition {
59 fn name() -> HeaderName {
60 HeaderName::new_from_ascii_str("Content-Disposition")
61 }
62
63 fn parse(s: &str) -> Result<Self, BoxError> {
64 match (s.split_once(';'), s) {
65 (_, "inline") => Ok(Self::inline()),
66 (Some((kind @ ("inline" | "attachment"), file_name)), _) => file_name
67 .split_once(" filename=\"")
68 .and_then(|(_, file_name)| file_name.strip_suffix('"'))
69 .map(|file_name| Self::with_name(kind, file_name))
70 .ok_or_else(|| "Unsupported ContentDisposition value".into()),
71 _ => Err("Unsupported ContentDisposition value".into()),
72 }
73 }
74
75 fn display(&self) -> HeaderValue {
76 self.0.clone()
77 }
78}
79
80#[cfg(test)]
81mod test {
82 use pretty_assertions::assert_eq;
83
84 use super::ContentDisposition;
85 use crate::message::header::{HeaderName, HeaderValue, Headers};
86
87 #[test]
88 fn format_content_disposition() {
89 let mut headers = Headers::new();
90
91 headers.set(ContentDisposition::inline());
92
93 assert_eq!(format!("{headers}"), "Content-Disposition: inline\r\n");
94
95 headers.set(ContentDisposition::attachment("something.txt"));
96
97 assert_eq!(
98 format!("{headers}"),
99 "Content-Disposition: attachment; filename=\"something.txt\"\r\n"
100 );
101 }
102
103 #[test]
104 fn parse_content_disposition() {
105 let mut headers = Headers::new();
106
107 headers.insert_raw(HeaderValue::new(
108 HeaderName::new_from_ascii_str("Content-Disposition"),
109 "inline".to_owned(),
110 ));
111
112 assert_eq!(
113 headers.get::<ContentDisposition>(),
114 Some(ContentDisposition::inline())
115 );
116
117 headers.insert_raw(HeaderValue::new(
118 HeaderName::new_from_ascii_str("Content-Disposition"),
119 "attachment; filename=\"something.txt\"".to_owned(),
120 ));
121
122 assert_eq!(
123 headers.get::<ContentDisposition>(),
124 Some(ContentDisposition::attachment("something.txt"))
125 );
126 }
127}