continuwuity/src/core/utils/content_disposition.rs
strawberry 77e0b76408
apply new rustfmt.toml changes, fix some clippy lints
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-12-15 01:00:41 -05:00

139 lines
3.6 KiB
Rust
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

use std::borrow::Cow;
use ruma::http_headers::{ContentDisposition, ContentDispositionType};
use crate::debug_info;
/// as defined by MSC2702
const ALLOWED_INLINE_CONTENT_TYPES: [&str; 26] = [
// keep sorted
"application/json",
"application/ld+json",
"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",
"image/gif",
"image/jpeg",
"image/png",
"image/webp",
"text/css",
"text/csv",
"text/plain",
"video/mp4",
"video/ogg",
"video/quicktime",
"video/webm",
];
/// Returns a Content-Disposition of `attachment` or `inline`, depending on the
/// Content-Type against MSC2702 list of safe inline Content-Types
/// (`ALLOWED_INLINE_CONTENT_TYPES`)
#[must_use]
pub fn content_disposition_type(content_type: Option<&str>) -> ContentDispositionType {
let Some(content_type) = content_type else {
debug_info!("No Content-Type was given, assuming attachment for Content-Disposition");
return ContentDispositionType::Attachment;
};
debug_assert!(
ALLOWED_INLINE_CONTENT_TYPES.is_sorted(),
"ALLOWED_INLINE_CONTENT_TYPES is not sorted"
);
let content_type: Cow<'_, str> = content_type
.split(';')
.next()
.unwrap_or(content_type)
.to_ascii_lowercase()
.into();
if ALLOWED_INLINE_CONTENT_TYPES
.binary_search(&content_type.as_ref())
.is_ok()
{
ContentDispositionType::Inline
} else {
ContentDispositionType::Attachment
}
}
/// sanitises the file name for the Content-Disposition using
/// `sanitize_filename` crate
#[tracing::instrument(level = "debug")]
pub fn sanitise_filename(filename: &str) -> String {
sanitize_filename::sanitize_with_options(filename, sanitize_filename::Options {
truncate: false,
..Default::default()
})
}
/// creates the final Content-Disposition based on whether the filename exists
/// or not, or if a requested filename was specified (media download with
/// filename)
///
/// if filename exists:
/// `Content-Disposition: attachment/inline; filename=filename.ext`
///
/// else: `Content-Disposition: attachment/inline`
pub fn make_content_disposition(
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),
)
}
#[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
println!("{SAMPLE}");
println!("{}", sanitize_filename::sanitize_with_options(SAMPLE, options.clone()));
println!("{SAMPLE:?}");
println!("{:?}", sanitize_filename::sanitize_with_options(SAMPLE, options.clone()));
assert_eq!(SANITISED, sanitize_filename::sanitize_with_options(SAMPLE, options.clone()));
}
#[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);
}
}