2024-08-12 22:53:07 +00:00
|
|
|
|
use std::borrow::Cow;
|
|
|
|
|
|
|
|
|
|
|
|
use ruma::http_headers::{ContentDisposition, ContentDispositionType};
|
2024-06-05 03:24:24 -04:00
|
|
|
|
|
2024-08-12 22:53:07 +00:00
|
|
|
|
use crate::debug_info;
|
2024-05-12 14:50:27 -04:00
|
|
|
|
|
2024-05-30 23:03:59 -04:00
|
|
|
|
/// as defined by MSC2702
|
|
|
|
|
|
const ALLOWED_INLINE_CONTENT_TYPES: [&str; 26] = [
|
2024-06-03 18:13:48 -04:00
|
|
|
|
// keep sorted
|
2024-05-30 23:03:59 -04:00
|
|
|
|
"application/json",
|
|
|
|
|
|
"application/ld+json",
|
2024-06-03 18:13:48 -04:00
|
|
|
|
"audio/aac",
|
|
|
|
|
|
"audio/flac",
|
|
|
|
|
|
"audio/mp4",
|
|
|
|
|
|
"audio/mpeg",
|
|
|
|
|
|
"audio/ogg",
|
|
|
|
|
|
"audio/wav",
|
|
|
|
|
|
"audio/wave",
|
|
|
|
|
|
"audio/webm",
|
|
|
|
|
|
"audio/x-flac",
|
|
|
|
|
|
"audio/x-pn-wav",
|
|
|
|
|
|
"audio/x-wav",
|
|
|
|
|
|
"image/apng",
|
|
|
|
|
|
"image/avif",
|
2024-05-30 23:03:59 -04:00
|
|
|
|
"image/gif",
|
2024-06-03 18:13:48 -04:00
|
|
|
|
"image/jpeg",
|
2024-05-30 23:03:59 -04:00
|
|
|
|
"image/png",
|
|
|
|
|
|
"image/webp",
|
2024-06-03 18:13:48 -04:00
|
|
|
|
"text/css",
|
|
|
|
|
|
"text/csv",
|
|
|
|
|
|
"text/plain",
|
2024-05-30 23:03:59 -04:00
|
|
|
|
"video/mp4",
|
|
|
|
|
|
"video/ogg",
|
|
|
|
|
|
"video/quicktime",
|
2024-06-03 18:13:48 -04:00
|
|
|
|
"video/webm",
|
2024-05-30 23:03:59 -04:00
|
|
|
|
];
|
|
|
|
|
|
|
2024-05-07 21:32:06 -04:00
|
|
|
|
/// Returns a Content-Disposition of `attachment` or `inline`, depending on the
|
2024-06-03 23:14:31 -04:00
|
|
|
|
/// Content-Type against MSC2702 list of safe inline Content-Types
|
|
|
|
|
|
/// (`ALLOWED_INLINE_CONTENT_TYPES`)
|
2024-05-09 15:59:08 -07:00
|
|
|
|
#[must_use]
|
2024-08-12 22:53:07 +00:00
|
|
|
|
pub fn content_disposition_type(content_type: Option<&str>) -> ContentDispositionType {
|
2024-06-03 23:14:31 -04:00
|
|
|
|
let Some(content_type) = content_type else {
|
2024-06-05 03:24:24 -04:00
|
|
|
|
debug_info!("No Content-Type was given, assuming attachment for Content-Disposition");
|
2024-08-12 22:53:07 +00:00
|
|
|
|
return ContentDispositionType::Attachment;
|
2024-05-07 21:32:06 -04:00
|
|
|
|
};
|
|
|
|
|
|
|
2024-11-15 09:40:04 -05:00
|
|
|
|
debug_assert!(
|
|
|
|
|
|
ALLOWED_INLINE_CONTENT_TYPES.is_sorted(),
|
|
|
|
|
|
"ALLOWED_INLINE_CONTENT_TYPES is not sorted"
|
|
|
|
|
|
);
|
2024-06-05 03:24:24 -04:00
|
|
|
|
|
2024-08-12 22:53:07 +00:00
|
|
|
|
let content_type: Cow<'_, str> = content_type
|
2024-06-05 03:24:24 -04:00
|
|
|
|
.split(';')
|
|
|
|
|
|
.next()
|
|
|
|
|
|
.unwrap_or(content_type)
|
2024-08-12 22:53:07 +00:00
|
|
|
|
.to_ascii_lowercase()
|
|
|
|
|
|
.into();
|
2024-06-05 03:24:24 -04:00
|
|
|
|
|
|
|
|
|
|
if ALLOWED_INLINE_CONTENT_TYPES
|
2024-08-12 22:53:07 +00:00
|
|
|
|
.binary_search(&content_type.as_ref())
|
2024-06-05 03:24:24 -04:00
|
|
|
|
.is_ok()
|
|
|
|
|
|
{
|
2024-08-12 22:53:07 +00:00
|
|
|
|
ContentDispositionType::Inline
|
2024-05-30 23:03:59 -04:00
|
|
|
|
} else {
|
2024-08-12 22:53:07 +00:00
|
|
|
|
ContentDispositionType::Attachment
|
2024-05-12 14:50:27 -04:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-05-07 21:32:06 -04:00
|
|
|
|
/// sanitises the file name for the Content-Disposition using
|
|
|
|
|
|
/// `sanitize_filename` crate
|
2024-07-07 19:03:15 +00:00
|
|
|
|
#[tracing::instrument(level = "debug")]
|
2024-08-12 22:53:07 +00:00
|
|
|
|
pub fn sanitise_filename(filename: &str) -> String {
|
|
|
|
|
|
sanitize_filename::sanitize_with_options(
|
|
|
|
|
|
filename,
|
|
|
|
|
|
sanitize_filename::Options {
|
|
|
|
|
|
truncate: false,
|
|
|
|
|
|
..Default::default()
|
|
|
|
|
|
},
|
|
|
|
|
|
)
|
2024-05-07 21:32:06 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// creates the final Content-Disposition based on whether the filename exists
|
2024-05-27 14:43:13 -04:00
|
|
|
|
/// or not, or if a requested filename was specified (media download with
|
|
|
|
|
|
/// filename)
|
2024-05-07 21:32:06 -04:00
|
|
|
|
///
|
2024-05-12 14:50:27 -04:00
|
|
|
|
/// if filename exists:
|
|
|
|
|
|
/// `Content-Disposition: attachment/inline; filename=filename.ext`
|
|
|
|
|
|
///
|
|
|
|
|
|
/// else: `Content-Disposition: attachment/inline`
|
2024-05-09 15:59:08 -07:00
|
|
|
|
pub fn make_content_disposition(
|
2024-08-12 22:53:07 +00:00
|
|
|
|
content_disposition: Option<&ContentDisposition>, content_type: Option<&str>, filename: Option<&str>,
|
|
|
|
|
|
) -> ContentDisposition {
|
|
|
|
|
|
ContentDisposition::new(content_disposition_type(content_type)).with_filename(
|
|
|
|
|
|
filename
|
|
|
|
|
|
.or_else(|| content_disposition.and_then(|content_disposition| content_disposition.filename.as_deref()))
|
|
|
|
|
|
.map(sanitise_filename),
|
|
|
|
|
|
)
|
2024-05-07 21:32:06 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
|
|
mod tests {
|
|
|
|
|
|
#[test]
|
|
|
|
|
|
fn string_sanitisation() {
|
|
|
|
|
|
const SAMPLE: &str =
|
|
|
|
|
|
"🏳️⚧️this\\r\\n įs \r\\n ä \\r\nstrïng 🥴that\n\r ../../../../../../../may be\r\n malicious🏳️⚧️";
|
|
|
|
|
|
const SANITISED: &str = "🏳️⚧️thisrn įs n ä rstrïng 🥴that ..............may be malicious🏳️⚧️";
|
|
|
|
|
|
|
|
|
|
|
|
let options = sanitize_filename::Options {
|
|
|
|
|
|
windows: true,
|
|
|
|
|
|
truncate: true,
|
|
|
|
|
|
replacement: "",
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// cargo test -- --nocapture
|
2024-06-02 00:15:02 +00:00
|
|
|
|
println!("{SAMPLE}");
|
2024-05-07 21:32:06 -04:00
|
|
|
|
println!("{}", sanitize_filename::sanitize_with_options(SAMPLE, options.clone()));
|
2024-06-02 00:15:02 +00:00
|
|
|
|
println!("{SAMPLE:?}");
|
2024-05-07 21:32:06 -04:00
|
|
|
|
println!("{:?}", sanitize_filename::sanitize_with_options(SAMPLE, options.clone()));
|
|
|
|
|
|
|
|
|
|
|
|
assert_eq!(SANITISED, sanitize_filename::sanitize_with_options(SAMPLE, options.clone()));
|
|
|
|
|
|
}
|
2024-08-12 22:53:07 +00:00
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
|
fn empty_sanitisation() {
|
|
|
|
|
|
use crate::utils::string::EMPTY;
|
|
|
|
|
|
|
|
|
|
|
|
let result = sanitize_filename::sanitize_with_options(
|
|
|
|
|
|
EMPTY,
|
|
|
|
|
|
sanitize_filename::Options {
|
|
|
|
|
|
windows: true,
|
|
|
|
|
|
truncate: true,
|
|
|
|
|
|
replacement: "",
|
|
|
|
|
|
},
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
assert_eq!(EMPTY, result);
|
|
|
|
|
|
}
|
2024-05-07 21:32:06 -04:00
|
|
|
|
}
|