use percent_encoding::{percent_encode, PATH_SEGMENT_ENCODE_SET, EncodeSet};
use std::fmt;
use std::borrow::Cow;
#[derive(PartialEq, Debug)]
pub enum DispositionType {
Inline,
Attachment,
}
#[derive(Debug)]
pub enum Filename {
Name(Option<String>),
Extended(String, Option<String>, Vec<u8>)
}
impl Filename {
pub fn new() -> Self {
Filename::Name(None)
}
pub fn with_name(name: String) -> Self {
Filename::Name(Some(name))
}
pub fn with_encoded_name(name: Cow<str>) -> Self {
let is_non_ascii = name.as_bytes().iter().any(|byte| PATH_SEGMENT_ENCODE_SET.contains(*byte));
match is_non_ascii {
false => Self::with_name(name.into_owned()),
true => {
let bytes = match name {
Cow::Borrowed(name) => name.as_bytes().into(),
Cow::Owned(name) => name.into_bytes(),
};
Filename::Extended("utf-8".to_owned(), None, bytes)
}
}
}
pub fn with_extended(charset: String, lang: Option<String>, name: Vec<u8>) -> Self {
Filename::Extended(charset, lang, name)
}
#[inline]
pub fn is_extended(&self) -> bool {
match self {
Filename::Extended(_, _, _) => true,
_ => false
}
}
}
#[derive(Debug)]
pub enum ContentDisposition {
Inline,
Attachment(Filename),
}
impl fmt::Display for ContentDisposition {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
ContentDisposition::Inline => write!(f, "inline"),
ContentDisposition::Attachment(file) => match file {
Filename::Name(Some(name)) => write!(f, "attachment; filename=\"{}\"", name),
Filename::Name(None) => write!(f, "attachment"),
Filename::Extended(charset, lang, value) => {
write!(f, "attachment; filename*={}'{}'{}",
charset,
lang.as_ref().map(|lang| lang.as_str()).unwrap_or(""),
percent_encode(&value, PATH_SEGMENT_ENCODE_SET).to_string())
},
},
}
}
}
#[cfg(test)]
mod tests {
use super::{ContentDisposition, Filename};
#[test]
fn parse_file_name_extended_ascii() {
const INPUT: &'static str = "rori.mp4";
let file_name = Filename::with_encoded_name(INPUT.into());
assert!(!file_name.is_extended());
}
#[test]
fn parse_file_name_extended_non_ascii() {
const INPUT: &'static str = "ロリへんたい.mp4";
let file_name = Filename::with_encoded_name(INPUT.into());
assert!(file_name.is_extended());
}
#[test]
fn verify_content_disposition_display() {
let cd = ContentDisposition::Inline;
let cd = format!("{}", cd);
assert_eq!(cd, "inline");
let cd = ContentDisposition::Attachment(Filename::new());
let cd = format!("{}", cd);
assert_eq!(cd, "attachment");
let cd = ContentDisposition::Attachment(Filename::with_name("lolka".to_string()));
let cd = format!("{}", cd);
assert_eq!(cd, "attachment; filename=\"lolka\"");
let cd = ContentDisposition::Attachment(Filename::with_encoded_name("lolka".into()));
let cd = format!("{}", cd);
assert_eq!(cd, "attachment; filename=\"lolka\"");
let cd = ContentDisposition::Attachment(Filename::with_encoded_name("ロリ".into()));
let cd = format!("{}", cd);
assert_eq!(cd, "attachment; filename*=utf-8\'\'%E3%83%AD%E3%83%AA");
}
}