128 lines
3.9 KiB
Rust
128 lines
3.9 KiB
Rust
use std::fmt::Write;
|
|
|
|
use email_encoding::headers::writer::EmailWriter;
|
|
|
|
use super::{Header, HeaderName, HeaderValue};
|
|
use crate::BoxError;
|
|
|
|
/// `Content-Disposition` of an attachment
|
|
///
|
|
/// Defined in [RFC2183](https://tools.ietf.org/html/rfc2183)
|
|
#[derive(Debug, Clone, PartialEq)]
|
|
pub struct ContentDisposition(HeaderValue);
|
|
|
|
impl ContentDisposition {
|
|
/// An attachment which should be displayed inline into the message
|
|
pub fn inline() -> Self {
|
|
Self(HeaderValue::dangerous_new_pre_encoded(
|
|
Self::name(),
|
|
"inline".to_owned(),
|
|
"inline".to_owned(),
|
|
))
|
|
}
|
|
|
|
/// An attachment which should be displayed inline into the message, but that also
|
|
/// species the filename in case it is downloaded
|
|
pub fn inline_with_name(file_name: &str) -> Self {
|
|
Self::with_name("inline", file_name)
|
|
}
|
|
|
|
/// An attachment which is separate from the body of the message, and can be downloaded separately
|
|
pub fn attachment(file_name: &str) -> Self {
|
|
Self::with_name("attachment", file_name)
|
|
}
|
|
|
|
fn with_name(kind: &str, file_name: &str) -> Self {
|
|
let raw_value = format!("{kind}; filename=\"{file_name}\"");
|
|
|
|
let mut encoded_value = String::new();
|
|
let line_len = "Content-Disposition: ".len();
|
|
{
|
|
let mut w = EmailWriter::new(&mut encoded_value, line_len, 0, false);
|
|
w.write_str(kind).expect("writing `kind` returned an error");
|
|
w.write_char(';').expect("writing `;` returned an error");
|
|
w.space();
|
|
|
|
email_encoding::headers::rfc2231::encode("filename", file_name, &mut w)
|
|
.expect("some Write implementation returned an error");
|
|
}
|
|
|
|
Self(HeaderValue::dangerous_new_pre_encoded(
|
|
Self::name(),
|
|
raw_value,
|
|
encoded_value,
|
|
))
|
|
}
|
|
}
|
|
|
|
impl Header for ContentDisposition {
|
|
fn name() -> HeaderName {
|
|
HeaderName::new_from_ascii_str("Content-Disposition")
|
|
}
|
|
|
|
fn parse(s: &str) -> Result<Self, BoxError> {
|
|
match (s.split_once(';'), s) {
|
|
(_, "inline") => Ok(Self::inline()),
|
|
(Some((kind @ ("inline" | "attachment"), file_name)), _) => file_name
|
|
.split_once(" filename=\"")
|
|
.and_then(|(_, file_name)| file_name.strip_suffix('"'))
|
|
.map(|file_name| Self::with_name(kind, file_name))
|
|
.ok_or_else(|| "Unsupported ContentDisposition value".into()),
|
|
_ => Err("Unsupported ContentDisposition value".into()),
|
|
}
|
|
}
|
|
|
|
fn display(&self) -> HeaderValue {
|
|
self.0.clone()
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod test {
|
|
use pretty_assertions::assert_eq;
|
|
|
|
use super::ContentDisposition;
|
|
use crate::message::header::{HeaderName, HeaderValue, Headers};
|
|
|
|
#[test]
|
|
fn format_content_disposition() {
|
|
let mut headers = Headers::new();
|
|
|
|
headers.set(ContentDisposition::inline());
|
|
|
|
assert_eq!(format!("{headers}"), "Content-Disposition: inline\r\n");
|
|
|
|
headers.set(ContentDisposition::attachment("something.txt"));
|
|
|
|
assert_eq!(
|
|
format!("{headers}"),
|
|
"Content-Disposition: attachment; filename=\"something.txt\"\r\n"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn parse_content_disposition() {
|
|
let mut headers = Headers::new();
|
|
|
|
headers.insert_raw(HeaderValue::new(
|
|
HeaderName::new_from_ascii_str("Content-Disposition"),
|
|
"inline".to_owned(),
|
|
));
|
|
|
|
assert_eq!(
|
|
headers.get::<ContentDisposition>(),
|
|
Some(ContentDisposition::inline())
|
|
);
|
|
|
|
headers.insert_raw(HeaderValue::new(
|
|
HeaderName::new_from_ascii_str("Content-Disposition"),
|
|
"attachment; filename=\"something.txt\"".to_owned(),
|
|
));
|
|
|
|
assert_eq!(
|
|
headers.get::<ContentDisposition>(),
|
|
Some(ContentDisposition::attachment("something.txt"))
|
|
);
|
|
}
|
|
}
|