1use lazy_static::lazy_static;
10use regex::Regex;
11use std::fmt::{self, Write};
12
13use crate::header::{self, ExtendedValue, Header, IntoHeaderValue, Writer};
14
15fn split_once(haystack: &str, needle: char) -> (&str, &str) {
17 haystack.find(needle).map_or_else(
18 || (haystack, ""),
19 |sc| {
20 let (first, last) = haystack.split_at(sc);
21 (first, last.split_at(1).1)
22 },
23 )
24}
25
26fn split_once_and_trim(haystack: &str, needle: char) -> (&str, &str) {
29 let (first, last) = split_once(haystack, needle);
30 (first.trim_end(), last.trim_start())
31}
32
33#[derive(Clone, Debug, PartialEq)]
35pub enum DispositionType {
36 Inline,
38 Attachment,
41 FormData,
44 Ext(String),
46}
47
48impl<'a> From<&'a str> for DispositionType {
49 fn from(origin: &'a str) -> DispositionType {
50 if origin.eq_ignore_ascii_case("inline") {
51 DispositionType::Inline
52 } else if origin.eq_ignore_ascii_case("attachment") {
53 DispositionType::Attachment
54 } else if origin.eq_ignore_ascii_case("form-data") {
55 DispositionType::FormData
56 } else {
57 DispositionType::Ext(origin.to_owned())
58 }
59 }
60}
61
62#[derive(Clone, Debug, PartialEq)]
73#[allow(clippy::large_enum_variant)]
74pub enum DispositionParam {
75 Name(String),
78 Filename(String),
85 FilenameExt(ExtendedValue),
88 Unknown(String, String),
93 UnknownExt(String, ExtendedValue),
98}
99
100impl DispositionParam {
101 #[inline]
103 pub fn is_name(&self) -> bool {
104 self.as_name().is_some()
105 }
106
107 #[inline]
109 pub fn is_filename(&self) -> bool {
110 self.as_filename().is_some()
111 }
112
113 #[inline]
115 pub fn is_filename_ext(&self) -> bool {
116 self.as_filename_ext().is_some()
117 }
118
119 #[inline]
121 pub fn is_unknown<T: AsRef<str>>(&self, name: T) -> bool {
123 self.as_unknown(name).is_some()
124 }
125
126 #[inline]
129 pub fn is_unknown_ext<T: AsRef<str>>(&self, name: T) -> bool {
130 self.as_unknown_ext(name).is_some()
131 }
132
133 #[inline]
135 pub fn as_name(&self) -> Option<&str> {
136 match self {
137 DispositionParam::Name(ref name) => Some(name.as_str()),
138 _ => None,
139 }
140 }
141
142 #[inline]
144 pub fn as_filename(&self) -> Option<&str> {
145 match self {
146 DispositionParam::Filename(ref filename) => Some(filename.as_str()),
147 _ => None,
148 }
149 }
150
151 #[inline]
153 pub fn as_filename_ext(&self) -> Option<&ExtendedValue> {
154 match self {
155 DispositionParam::FilenameExt(ref value) => Some(value),
156 _ => None,
157 }
158 }
159
160 #[inline]
163 pub fn as_unknown<T: AsRef<str>>(&self, name: T) -> Option<&str> {
164 match self {
165 DispositionParam::Unknown(ref ext_name, ref value)
166 if ext_name.eq_ignore_ascii_case(name.as_ref()) =>
167 {
168 Some(value.as_str())
169 }
170 _ => None,
171 }
172 }
173
174 #[inline]
177 pub fn as_unknown_ext<T: AsRef<str>>(&self, name: T) -> Option<&ExtendedValue> {
178 match self {
179 DispositionParam::UnknownExt(ref ext_name, ref value)
180 if ext_name.eq_ignore_ascii_case(name.as_ref()) =>
181 {
182 Some(value)
183 }
184 _ => None,
185 }
186 }
187}
188
189#[derive(Clone, Debug, PartialEq)]
292pub struct ContentDisposition {
293 pub disposition: DispositionType,
295 pub parameters: Vec<DispositionParam>,
297}
298
299impl ContentDisposition {
300 pub fn from_raw(hv: &header::HeaderValue) -> Result<Self, crate::error::ParseError> {
302 let hv = String::from_utf8(hv.as_bytes().to_vec())
305 .map_err(|_| crate::error::ParseError::Header)?;
306 let (disp_type, mut left) = split_once_and_trim(hv.as_str().trim(), ';');
307 if disp_type.is_empty() {
308 return Err(crate::error::ParseError::Header);
309 }
310 let mut cd = ContentDisposition {
311 disposition: disp_type.into(),
312 parameters: Vec::new(),
313 };
314
315 while !left.is_empty() {
316 let (param_name, new_left) = split_once_and_trim(left, '=');
317 if param_name.is_empty() || param_name == "*" || new_left.is_empty() {
318 return Err(crate::error::ParseError::Header);
319 }
320 left = new_left;
321 if param_name.ends_with('*') {
322 let param_name = ¶m_name[..param_name.len() - 1]; let (ext_value, new_left) = split_once_and_trim(left, ';');
325 left = new_left;
326 let ext_value = header::parse_extended_value(ext_value)?;
327
328 let param = if param_name.eq_ignore_ascii_case("filename") {
329 DispositionParam::FilenameExt(ext_value)
330 } else {
331 DispositionParam::UnknownExt(param_name.to_owned(), ext_value)
332 };
333 cd.parameters.push(param);
334 } else {
335 let value = if left.starts_with('\"') {
337 let mut escaping = false;
339 let mut quoted_string = vec![];
340 let mut end = None;
341 for (i, &c) in left.as_bytes().iter().skip(1).enumerate() {
343 if escaping {
344 escaping = false;
345 quoted_string.push(c);
346 } else if c == 0x5c {
347 escaping = true;
349 } else if c == 0x22 {
350 end = Some(i + 1); break;
353 } else {
354 quoted_string.push(c);
355 }
356 }
357 left = &left[end.ok_or(crate::error::ParseError::Header)? + 1..];
358 left = split_once(left, ';').1.trim_start();
359 String::from_utf8(quoted_string)
361 .map_err(|_| crate::error::ParseError::Header)?
362 } else {
363 let (token, new_left) = split_once_and_trim(left, ';');
365 left = new_left;
366 if token.is_empty() {
367 return Err(crate::error::ParseError::Header);
369 }
370 token.to_owned()
371 };
372
373 let param = if param_name.eq_ignore_ascii_case("name") {
374 DispositionParam::Name(value)
375 } else if param_name.eq_ignore_ascii_case("filename") {
376 DispositionParam::Filename(value)
378 } else {
379 DispositionParam::Unknown(param_name.to_owned(), value)
380 };
381 cd.parameters.push(param);
382 }
383 }
384
385 Ok(cd)
386 }
387
388 pub fn is_inline(&self) -> bool {
390 match self.disposition {
391 DispositionType::Inline => true,
392 _ => false,
393 }
394 }
395
396 pub fn is_attachment(&self) -> bool {
398 match self.disposition {
399 DispositionType::Attachment => true,
400 _ => false,
401 }
402 }
403
404 pub fn is_form_data(&self) -> bool {
406 match self.disposition {
407 DispositionType::FormData => true,
408 _ => false,
409 }
410 }
411
412 pub fn is_ext<T: AsRef<str>>(&self, disp_type: T) -> bool {
414 match self.disposition {
415 DispositionType::Ext(ref t)
416 if t.eq_ignore_ascii_case(disp_type.as_ref()) =>
417 {
418 true
419 }
420 _ => false,
421 }
422 }
423
424 pub fn get_name(&self) -> Option<&str> {
426 self.parameters.iter().filter_map(|p| p.as_name()).nth(0)
427 }
428
429 pub fn get_filename(&self) -> Option<&str> {
431 self.parameters
432 .iter()
433 .filter_map(|p| p.as_filename())
434 .nth(0)
435 }
436
437 pub fn get_filename_ext(&self) -> Option<&ExtendedValue> {
439 self.parameters
440 .iter()
441 .filter_map(|p| p.as_filename_ext())
442 .nth(0)
443 }
444
445 pub fn get_unknown<T: AsRef<str>>(&self, name: T) -> Option<&str> {
447 let name = name.as_ref();
448 self.parameters
449 .iter()
450 .filter_map(|p| p.as_unknown(name))
451 .nth(0)
452 }
453
454 pub fn get_unknown_ext<T: AsRef<str>>(&self, name: T) -> Option<&ExtendedValue> {
456 let name = name.as_ref();
457 self.parameters
458 .iter()
459 .filter_map(|p| p.as_unknown_ext(name))
460 .nth(0)
461 }
462}
463
464impl IntoHeaderValue for ContentDisposition {
465 type Error = header::InvalidHeaderValue;
466
467 fn try_into(self) -> Result<header::HeaderValue, Self::Error> {
468 let mut writer = Writer::new();
469 let _ = write!(&mut writer, "{}", self);
470 header::HeaderValue::from_maybe_shared(writer.take())
471 }
472}
473
474impl Header for ContentDisposition {
475 fn name() -> header::HeaderName {
476 header::CONTENT_DISPOSITION
477 }
478
479 fn parse<T: crate::HttpMessage>(msg: &T) -> Result<Self, crate::error::ParseError> {
480 if let Some(h) = msg.headers().get(&Self::name()) {
481 Self::from_raw(&h)
482 } else {
483 Err(crate::error::ParseError::Header)
484 }
485 }
486}
487
488impl fmt::Display for DispositionType {
489 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
490 match self {
491 DispositionType::Inline => write!(f, "inline"),
492 DispositionType::Attachment => write!(f, "attachment"),
493 DispositionType::FormData => write!(f, "form-data"),
494 DispositionType::Ext(ref s) => write!(f, "{}", s),
495 }
496 }
497}
498
499impl fmt::Display for DispositionParam {
500 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
501 lazy_static! {
534 static ref RE: Regex = Regex::new("[\x00-\x08\x10-\x1F\x7F\"\\\\]").unwrap();
535 }
536 match self {
537 DispositionParam::Name(ref value) => write!(f, "name={}", value),
538 DispositionParam::Filename(ref value) => {
539 write!(f, "filename=\"{}\"", RE.replace_all(value, "\\$0").as_ref())
540 }
541 DispositionParam::Unknown(ref name, ref value) => write!(
542 f,
543 "{}=\"{}\"",
544 name,
545 &RE.replace_all(value, "\\$0").as_ref()
546 ),
547 DispositionParam::FilenameExt(ref ext_value) => {
548 write!(f, "filename*={}", ext_value)
549 }
550 DispositionParam::UnknownExt(ref name, ref ext_value) => {
551 write!(f, "{}*={}", name, ext_value)
552 }
553 }
554 }
555}
556
557impl fmt::Display for ContentDisposition {
558 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
559 write!(f, "{}", self.disposition)?;
560 self.parameters
561 .iter()
562 .map(|param| write!(f, "; {}", param))
563 .collect()
564 }
565}
566
567#[cfg(test)]
568mod tests {
569 use super::{ContentDisposition, DispositionParam, DispositionType};
570 use crate::header::shared::Charset;
571 use crate::header::{ExtendedValue, HeaderValue};
572
573 #[test]
574 fn test_from_raw_basic() {
575 assert!(ContentDisposition::from_raw(&HeaderValue::from_static("")).is_err());
576
577 let a = HeaderValue::from_static(
578 "form-data; dummy=3; name=upload; filename=\"sample.png\"",
579 );
580 let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
581 let b = ContentDisposition {
582 disposition: DispositionType::FormData,
583 parameters: vec![
584 DispositionParam::Unknown("dummy".to_owned(), "3".to_owned()),
585 DispositionParam::Name("upload".to_owned()),
586 DispositionParam::Filename("sample.png".to_owned()),
587 ],
588 };
589 assert_eq!(a, b);
590
591 let a = HeaderValue::from_static("attachment; filename=\"image.jpg\"");
592 let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
593 let b = ContentDisposition {
594 disposition: DispositionType::Attachment,
595 parameters: vec![DispositionParam::Filename("image.jpg".to_owned())],
596 };
597 assert_eq!(a, b);
598
599 let a = HeaderValue::from_static("inline; filename=image.jpg");
600 let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
601 let b = ContentDisposition {
602 disposition: DispositionType::Inline,
603 parameters: vec![DispositionParam::Filename("image.jpg".to_owned())],
604 };
605 assert_eq!(a, b);
606
607 let a = HeaderValue::from_static(
608 "attachment; creation-date=\"Wed, 12 Feb 1997 16:29:51 -0500\"",
609 );
610 let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
611 let b = ContentDisposition {
612 disposition: DispositionType::Attachment,
613 parameters: vec![DispositionParam::Unknown(
614 String::from("creation-date"),
615 "Wed, 12 Feb 1997 16:29:51 -0500".to_owned(),
616 )],
617 };
618 assert_eq!(a, b);
619 }
620
621 #[test]
622 fn test_from_raw_extended() {
623 let a = HeaderValue::from_static(
624 "attachment; filename*=UTF-8''%c2%a3%20and%20%e2%82%ac%20rates",
625 );
626 let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
627 let b = ContentDisposition {
628 disposition: DispositionType::Attachment,
629 parameters: vec![DispositionParam::FilenameExt(ExtendedValue {
630 charset: Charset::Ext(String::from("UTF-8")),
631 language_tag: None,
632 value: vec![
633 0xc2, 0xa3, 0x20, b'a', b'n', b'd', 0x20, 0xe2, 0x82, 0xac, 0x20,
634 b'r', b'a', b't', b'e', b's',
635 ],
636 })],
637 };
638 assert_eq!(a, b);
639
640 let a = HeaderValue::from_static(
641 "attachment; filename*=UTF-8''%c2%a3%20and%20%e2%82%ac%20rates",
642 );
643 let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
644 let b = ContentDisposition {
645 disposition: DispositionType::Attachment,
646 parameters: vec![DispositionParam::FilenameExt(ExtendedValue {
647 charset: Charset::Ext(String::from("UTF-8")),
648 language_tag: None,
649 value: vec![
650 0xc2, 0xa3, 0x20, b'a', b'n', b'd', 0x20, 0xe2, 0x82, 0xac, 0x20,
651 b'r', b'a', b't', b'e', b's',
652 ],
653 })],
654 };
655 assert_eq!(a, b);
656 }
657
658 #[test]
659 fn test_from_raw_extra_whitespace() {
660 let a = HeaderValue::from_static(
661 "form-data ; du-mmy= 3 ; name =upload ; filename = \"sample.png\" ; ",
662 );
663 let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
664 let b = ContentDisposition {
665 disposition: DispositionType::FormData,
666 parameters: vec![
667 DispositionParam::Unknown("du-mmy".to_owned(), "3".to_owned()),
668 DispositionParam::Name("upload".to_owned()),
669 DispositionParam::Filename("sample.png".to_owned()),
670 ],
671 };
672 assert_eq!(a, b);
673 }
674
675 #[test]
676 fn test_from_raw_unordered() {
677 let a = HeaderValue::from_static(
678 "form-data; dummy=3; filename=\"sample.png\" ; name=upload;",
679 );
681 let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
682 let b = ContentDisposition {
683 disposition: DispositionType::FormData,
684 parameters: vec![
685 DispositionParam::Unknown("dummy".to_owned(), "3".to_owned()),
686 DispositionParam::Filename("sample.png".to_owned()),
687 DispositionParam::Name("upload".to_owned()),
688 ],
689 };
690 assert_eq!(a, b);
691
692 let a = HeaderValue::from_str(
693 "attachment; filename*=iso-8859-1''foo-%E4.html; filename=\"foo-ä.html\"",
694 )
695 .unwrap();
696 let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
697 let b = ContentDisposition {
698 disposition: DispositionType::Attachment,
699 parameters: vec![
700 DispositionParam::FilenameExt(ExtendedValue {
701 charset: Charset::Iso_8859_1,
702 language_tag: None,
703 value: b"foo-\xe4.html".to_vec(),
704 }),
705 DispositionParam::Filename("foo-ä.html".to_owned()),
706 ],
707 };
708 assert_eq!(a, b);
709 }
710
711 #[test]
712 fn test_from_raw_only_disp() {
713 let a = ContentDisposition::from_raw(&HeaderValue::from_static("attachment"))
714 .unwrap();
715 let b = ContentDisposition {
716 disposition: DispositionType::Attachment,
717 parameters: vec![],
718 };
719 assert_eq!(a, b);
720
721 let a =
722 ContentDisposition::from_raw(&HeaderValue::from_static("inline ;")).unwrap();
723 let b = ContentDisposition {
724 disposition: DispositionType::Inline,
725 parameters: vec![],
726 };
727 assert_eq!(a, b);
728
729 let a = ContentDisposition::from_raw(&HeaderValue::from_static(
730 "unknown-disp-param",
731 ))
732 .unwrap();
733 let b = ContentDisposition {
734 disposition: DispositionType::Ext(String::from("unknown-disp-param")),
735 parameters: vec![],
736 };
737 assert_eq!(a, b);
738 }
739
740 #[test]
741 fn from_raw_with_mixed_case() {
742 let a = HeaderValue::from_str(
743 "InLInE; fIlenAME*=iso-8859-1''foo-%E4.html; filEName=\"foo-ä.html\"",
744 )
745 .unwrap();
746 let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
747 let b = ContentDisposition {
748 disposition: DispositionType::Inline,
749 parameters: vec![
750 DispositionParam::FilenameExt(ExtendedValue {
751 charset: Charset::Iso_8859_1,
752 language_tag: None,
753 value: b"foo-\xe4.html".to_vec(),
754 }),
755 DispositionParam::Filename("foo-ä.html".to_owned()),
756 ],
757 };
758 assert_eq!(a, b);
759 }
760
761 #[test]
762 fn from_raw_with_unicode() {
763 let a = HeaderValue::from_str("form-data; name=upload; filename=\"文件.webp\"")
772 .unwrap();
773 let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
774 let b = ContentDisposition {
775 disposition: DispositionType::FormData,
776 parameters: vec![
777 DispositionParam::Name(String::from("upload")),
778 DispositionParam::Filename(String::from("文件.webp")),
779 ],
780 };
781 assert_eq!(a, b);
782
783 let a = HeaderValue::from_str(
784 "form-data; name=upload; filename=\"余固知謇謇之為患兮,忍而不能舍也.pptx\"",
785 )
786 .unwrap();
787 let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
788 let b = ContentDisposition {
789 disposition: DispositionType::FormData,
790 parameters: vec![
791 DispositionParam::Name(String::from("upload")),
792 DispositionParam::Filename(String::from(
793 "余固知謇謇之為患兮,忍而不能舍也.pptx",
794 )),
795 ],
796 };
797 assert_eq!(a, b);
798 }
799
800 #[test]
801 fn test_from_raw_escape() {
802 let a = HeaderValue::from_static(
803 "form-data; dummy=3; name=upload; filename=\"s\\amp\\\"le.png\"",
804 );
805 let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
806 let b = ContentDisposition {
807 disposition: DispositionType::FormData,
808 parameters: vec![
809 DispositionParam::Unknown("dummy".to_owned(), "3".to_owned()),
810 DispositionParam::Name("upload".to_owned()),
811 DispositionParam::Filename(
812 ['s', 'a', 'm', 'p', '\"', 'l', 'e', '.', 'p', 'n', 'g']
813 .iter()
814 .collect(),
815 ),
816 ],
817 };
818 assert_eq!(a, b);
819 }
820
821 #[test]
822 fn test_from_raw_semicolon() {
823 let a =
824 HeaderValue::from_static("form-data; filename=\"A semicolon here;.pdf\"");
825 let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
826 let b = ContentDisposition {
827 disposition: DispositionType::FormData,
828 parameters: vec![DispositionParam::Filename(String::from(
829 "A semicolon here;.pdf",
830 ))],
831 };
832 assert_eq!(a, b);
833 }
834
835 #[test]
836 fn test_from_raw_uncessary_percent_decode() {
837 let a = HeaderValue::from_static(
848 "form-data; name=photo; filename=\"%74%65%73%74%2e%70%6e%67\"",
849 );
850 let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
851 let b = ContentDisposition {
852 disposition: DispositionType::FormData,
853 parameters: vec![
854 DispositionParam::Name("photo".to_owned()),
855 DispositionParam::Filename(String::from("%74%65%73%74%2e%70%6e%67")),
856 ],
857 };
858 assert_eq!(a, b);
859
860 let a = HeaderValue::from_static(
861 "form-data; name=photo; filename=\"%74%65%73%74.png\"",
862 );
863 let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
864 let b = ContentDisposition {
865 disposition: DispositionType::FormData,
866 parameters: vec![
867 DispositionParam::Name("photo".to_owned()),
868 DispositionParam::Filename(String::from("%74%65%73%74.png")),
869 ],
870 };
871 assert_eq!(a, b);
872 }
873
874 #[test]
875 fn test_from_raw_param_value_missing() {
876 let a = HeaderValue::from_static("form-data; name=upload ; filename=");
877 assert!(ContentDisposition::from_raw(&a).is_err());
878
879 let a = HeaderValue::from_static("attachment; dummy=; filename=invoice.pdf");
880 assert!(ContentDisposition::from_raw(&a).is_err());
881
882 let a = HeaderValue::from_static("inline; filename= ");
883 assert!(ContentDisposition::from_raw(&a).is_err());
884
885 let a = HeaderValue::from_static("inline; filename=\"\"");
886 assert!(ContentDisposition::from_raw(&a)
887 .expect("parse cd")
888 .get_filename()
889 .expect("filename")
890 .is_empty());
891 }
892
893 #[test]
894 fn test_from_raw_param_name_missing() {
895 let a = HeaderValue::from_static("inline; =\"test.txt\"");
896 assert!(ContentDisposition::from_raw(&a).is_err());
897
898 let a = HeaderValue::from_static("inline; =diary.odt");
899 assert!(ContentDisposition::from_raw(&a).is_err());
900
901 let a = HeaderValue::from_static("inline; =");
902 assert!(ContentDisposition::from_raw(&a).is_err());
903 }
904
905 #[test]
906 fn test_display_extended() {
907 let as_string =
908 "attachment; filename*=UTF-8'en'%C2%A3%20and%20%E2%82%AC%20rates";
909 let a = HeaderValue::from_static(as_string);
910 let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
911 let display_rendered = format!("{}", a);
912 assert_eq!(as_string, display_rendered);
913
914 let a = HeaderValue::from_static("attachment; filename=colourful.csv");
915 let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
916 let display_rendered = format!("{}", a);
917 assert_eq!(
918 "attachment; filename=\"colourful.csv\"".to_owned(),
919 display_rendered
920 );
921 }
922
923 #[test]
924 fn test_display_quote() {
925 let as_string = "form-data; name=upload; filename=\"Quote\\\"here.png\"";
926 as_string
927 .find(['\\', '\"'].iter().collect::<String>().as_str())
928 .unwrap(); let a = HeaderValue::from_static(as_string);
930 let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
931 let display_rendered = format!("{}", a);
932 assert_eq!(as_string, display_rendered);
933 }
934
935 #[test]
936 fn test_display_space_tab() {
937 let as_string = "form-data; name=upload; filename=\"Space here.png\"";
938 let a = HeaderValue::from_static(as_string);
939 let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
940 let display_rendered = format!("{}", a);
941 assert_eq!(as_string, display_rendered);
942
943 let a: ContentDisposition = ContentDisposition {
944 disposition: DispositionType::Inline,
945 parameters: vec![DispositionParam::Filename(String::from("Tab\there.png"))],
946 };
947 let display_rendered = format!("{}", a);
948 assert_eq!("inline; filename=\"Tab\x09here.png\"", display_rendered);
949 }
950
951 #[test]
952 fn test_display_control_characters() {
953 let a: ContentDisposition = ContentDisposition {
964 disposition: DispositionType::Inline,
965 parameters: vec![DispositionParam::Filename(String::from("bell\x07.png"))],
966 };
967 let display_rendered = format!("{}", a);
968 assert_eq!("inline; filename=\"bell\\\x07.png\"", display_rendered);
969 }
970
971 #[test]
972 fn test_param_methods() {
973 let param = DispositionParam::Filename(String::from("sample.txt"));
974 assert!(param.is_filename());
975 assert_eq!(param.as_filename().unwrap(), "sample.txt");
976
977 let param = DispositionParam::Unknown(String::from("foo"), String::from("bar"));
978 assert!(param.is_unknown("foo"));
979 assert_eq!(param.as_unknown("fOo"), Some("bar"));
980 }
981
982 #[test]
983 fn test_disposition_methods() {
984 let cd = ContentDisposition {
985 disposition: DispositionType::FormData,
986 parameters: vec![
987 DispositionParam::Unknown("dummy".to_owned(), "3".to_owned()),
988 DispositionParam::Name("upload".to_owned()),
989 DispositionParam::Filename("sample.png".to_owned()),
990 ],
991 };
992 assert_eq!(cd.get_name(), Some("upload"));
993 assert_eq!(cd.get_unknown("dummy"), Some("3"));
994 assert_eq!(cd.get_filename(), Some("sample.png"));
995 assert_eq!(cd.get_unknown_ext("dummy"), None);
996 assert_eq!(cd.get_unknown("duMMy"), Some("3"));
997 }
998}