Follow RFC 2231 in order to properly encode the Content-Disposition header (#685)

Uses RFC 2231 to encode the Content-Disposition header
This commit is contained in:
Paolo Barbolini
2022-02-12 11:17:44 +01:00
committed by GitHub
parent 3eed80ef30
commit 8c370e28c9
3 changed files with 57 additions and 10 deletions

View File

@@ -29,6 +29,7 @@ fastrand = { version = "1.4", optional = true }
quoted_printable = { version = "0.4", optional = true }
base64 = { version = "0.13", optional = true }
regex = { version = "1", default-features = false, features = ["std", "unicode-case"] }
email-encoding = { git = "https://github.com/lettre/email-encoding.git", rev = "9b55bae0ed02ca52f6fb86a82b4814baa712e45a", optional = true }
# file transport
uuid = { version = "0.8", features = ["v4"], optional = true }
@@ -81,7 +82,7 @@ name = "transport_smtp"
[features]
default = ["smtp-transport", "pool", "native-tls", "hostname", "builder"]
builder = ["httpdate", "mime", "base64", "fastrand", "quoted_printable"]
builder = ["httpdate", "mime", "base64", "fastrand", "quoted_printable", "email-encoding"]
mime03 = ["mime"]
# transports

View File

@@ -1,3 +1,7 @@
use std::fmt::Write;
use email_encoding::headers::EmailWriter;
use super::{Header, HeaderName, HeaderValue};
use crate::BoxError;
@@ -5,25 +9,47 @@ use crate::BoxError;
///
/// Defined in [RFC2183](https://tools.ietf.org/html/rfc2183)
#[derive(Debug, Clone, PartialEq)]
pub struct ContentDisposition(String);
pub struct ContentDisposition(HeaderValue);
impl ContentDisposition {
/// An attachment which should be displayed inline into the message
pub fn inline() -> Self {
Self("inline".into())
Self(HeaderValue::dangerous_new_pre_encoded(
Self::name(),
"inline".to_string(),
"inline".to_string(),
))
}
/// An attachment which should be displayed inline into the message, but that also
/// species the filename in case it were to be downloaded
pub fn inline_with_name(file_name: &str) -> Self {
debug_assert!(!file_name.contains('"'), "file_name shouldn't contain '\"'");
Self(format!("inline; filename=\"{}\"", file_name))
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 {
debug_assert!(!file_name.contains('"'), "file_name shouldn't contain '\"'");
Self(format!("attachment; filename=\"{}\"", file_name))
Self::with_name("attachment", file_name)
}
fn with_name(kind: &str, file_name: &str) -> Self {
let raw_value = format!("{}; filename=\"{}\"", kind, file_name);
let mut encoded_value = String::new();
let line_len = "Content-Disposition: ".len();
let mut w = EmailWriter::new(&mut encoded_value, line_len, 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,
))
}
}
@@ -33,11 +59,19 @@ impl Header for ContentDisposition {
}
fn parse(s: &str) -> Result<Self, BoxError> {
Ok(Self(s.into()))
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 {
HeaderValue::new(Self::name(), self.0.clone())
self.0.clone()
}
}

View File

@@ -275,7 +275,7 @@ impl PartialEq<HeaderName> for &str {
}
}
#[derive(Debug, Clone)]
#[derive(Debug, Clone, PartialEq)]
pub struct HeaderValue {
name: HeaderName,
raw_value: String,
@@ -293,6 +293,18 @@ impl HeaderValue {
encoded_value,
}
}
pub fn dangerous_new_pre_encoded(
name: HeaderName,
raw_value: String,
encoded_value: String,
) -> Self {
Self {
name,
raw_value,
encoded_value,
}
}
}
const ENCODING_START_PREFIX: &str = "=?utf-8?b?";