diff --git a/actix-web/CHANGES.md b/actix-web/CHANGES.md index d11108c2a..9ded67e35 100644 --- a/actix-web/CHANGES.md +++ b/actix-web/CHANGES.md @@ -1,6 +1,10 @@ # Changelog ## Unreleased - 2022-xx-xx +### Added +- Add `ContentDisposition::attachment` constructor. [#2867] + +[#2867]: https://github.com/actix/actix-web/pull/2867 ## 4.2.1 - 2022-09-12 diff --git a/actix-web/src/http/header/content_disposition.rs b/actix-web/src/http/header/content_disposition.rs index 0bb459193..f743302a2 100644 --- a/actix-web/src/http/header/content_disposition.rs +++ b/actix-web/src/http/header/content_disposition.rs @@ -79,7 +79,7 @@ impl<'a> From<&'a str> for DispositionType { /// assert!(param.is_filename()); /// assert_eq!(param.as_filename().unwrap(), "sample.txt"); /// ``` -#[derive(Clone, Debug, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq)] #[allow(clippy::large_enum_variant)] pub enum DispositionParam { /// For [`DispositionType::FormData`] (i.e. *multipart/form-data*), the name of an field from @@ -302,7 +302,7 @@ impl DispositionParam { /// change to match local file system conventions if applicable, and do not use directory path /// information that may be present. /// See [RFC 2183 ยง2.3](https://datatracker.ietf.org/doc/html/rfc2183#section-2.3). -#[derive(Clone, Debug, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct ContentDisposition { /// The disposition type pub disposition: DispositionType, @@ -312,16 +312,36 @@ pub struct ContentDisposition { } impl ContentDisposition { + /// Constructs a Content-Disposition header suitable for downloads. + /// + /// # Examples + /// ``` + /// use actix_web::http::header::{ContentDisposition, TryIntoHeaderValue as _}; + /// + /// let cd = ContentDisposition::attachment("files.zip"); + /// + /// let cd_val = cd.try_into_value().unwrap(); + /// assert_eq!(cd_val, "attachment; filename=\"files.zip\""); + /// ``` + pub fn attachment(filename: impl Into) -> Self { + Self { + disposition: DispositionType::Attachment, + parameters: vec![DispositionParam::Filename(filename.into())], + } + } + /// Parse a raw Content-Disposition header value. pub fn from_raw(hv: &header::HeaderValue) -> Result { // `header::from_one_raw_str` invokes `hv.to_str` which assumes `hv` contains only visible // ASCII characters. So `hv.as_bytes` is necessary here. let hv = String::from_utf8(hv.as_bytes().to_vec()) .map_err(|_| crate::error::ParseError::Header)?; + let (disp_type, mut left) = split_once_and_trim(hv.as_str().trim(), ';'); if disp_type.is_empty() { return Err(crate::error::ParseError::Header); } + let mut cd = ContentDisposition { disposition: disp_type.into(), parameters: Vec::new(),