pub(crate) fn extract_filename(content_disposition: &str) -> Option<String> {
let mut fallback_filename: Option<String> = None;
for part in content_disposition.split(';') {
let part = part.trim();
if let Some(rest) = part.strip_prefix("filename*=") {
let rest = rest.trim_matches('"');
let mut it = rest.splitn(3, '\'');
let _charset = it.next();
let _lang = it.next();
if let Some(value) = it.next() {
return Some(value.to_string());
}
continue;
}
if let Some(filename) = part.strip_prefix("filename=") {
if fallback_filename.is_none() {
fallback_filename = Some(filename.trim_matches('"').to_string());
}
}
}
fallback_filename
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn extract_filename_utf8_star() {
let raw = "attachment; filename=\"upload_all.rs\"; filename*=UTF-8''upload_all.rs";
assert_eq!(extract_filename(raw).as_deref(), Some("upload_all.rs"));
}
#[test]
fn extract_filename_star_missing_charset() {
let raw = "attachment; filename*=''missing_utf8.txt";
assert_eq!(extract_filename(raw).as_deref(), Some("missing_utf8.txt"));
}
#[test]
fn extract_filename_star_malformed() {
let raw = "attachment; filename*=UTF-8";
assert_eq!(extract_filename(raw).as_deref(), None);
}
#[test]
fn extract_filename_quoted() {
let raw = "attachment; filename=\"simple.txt\"";
assert_eq!(extract_filename(raw).as_deref(), Some("simple.txt"));
}
#[test]
fn extract_filename_unquoted() {
let raw = "attachment; filename=simple.txt";
assert_eq!(extract_filename(raw).as_deref(), Some("simple.txt"));
}
#[test]
fn extract_filename_multiple_parts() {
let raw = "attachment; charset=utf-8; filename*=UTF-8''complex%20name.txt; other=value";
assert_eq!(extract_filename(raw).as_deref(), Some("complex%20name.txt"));
}
#[test]
fn extract_filename_empty() {
assert_eq!(extract_filename("").as_deref(), None);
}
}