1use std::fmt::{self, Write};
14
15use once_cell::sync::Lazy;
16#[cfg(feature = "unicode")]
17use regex::Regex;
18#[cfg(not(feature = "unicode"))]
19use regex_lite::Regex;
20
21use super::{ExtendedValue, Header, TryIntoHeaderValue, Writer};
22use crate::http::header;
23
24fn split_once(haystack: &str, needle: char) -> (&str, &str) {
26 haystack.find(needle).map_or_else(
27 || (haystack, ""),
28 |sc| {
29 let (first, last) = haystack.split_at(sc);
30 (first, last.split_at(1).1)
31 },
32 )
33}
34
35fn split_once_and_trim(haystack: &str, needle: char) -> (&str, &str) {
38 let (first, last) = split_once(haystack, needle);
39 (first.trim_end(), last.trim_start())
40}
41
42#[derive(Debug, Clone, PartialEq, Eq)]
44pub enum DispositionType {
45 Inline,
47
48 Attachment,
51
52 FormData,
56
57 Ext(String),
59}
60
61impl<'a> From<&'a str> for DispositionType {
62 fn from(origin: &'a str) -> DispositionType {
63 if origin.eq_ignore_ascii_case("inline") {
64 DispositionType::Inline
65 } else if origin.eq_ignore_ascii_case("attachment") {
66 DispositionType::Attachment
67 } else if origin.eq_ignore_ascii_case("form-data") {
68 DispositionType::FormData
69 } else {
70 DispositionType::Ext(origin.to_owned())
71 }
72 }
73}
74
75#[derive(Debug, Clone, PartialEq, Eq)]
86#[allow(clippy::large_enum_variant)]
87pub enum DispositionParam {
88 Name(String),
91
92 Filename(String),
99
100 FilenameExt(ExtendedValue),
103
104 Unknown(String, String),
110
111 UnknownExt(String, ExtendedValue),
118}
119
120impl DispositionParam {
121 #[inline]
123 pub fn is_name(&self) -> bool {
124 self.as_name().is_some()
125 }
126
127 #[inline]
129 pub fn is_filename(&self) -> bool {
130 self.as_filename().is_some()
131 }
132
133 #[inline]
135 pub fn is_filename_ext(&self) -> bool {
136 self.as_filename_ext().is_some()
137 }
138
139 #[inline]
141 pub fn is_unknown<T: AsRef<str>>(&self, name: T) -> bool {
143 self.as_unknown(name).is_some()
144 }
145
146 #[inline]
149 pub fn is_unknown_ext<T: AsRef<str>>(&self, name: T) -> bool {
150 self.as_unknown_ext(name).is_some()
151 }
152
153 #[inline]
155 pub fn as_name(&self) -> Option<&str> {
156 match self {
157 DispositionParam::Name(name) => Some(name.as_str()),
158 _ => None,
159 }
160 }
161
162 #[inline]
164 pub fn as_filename(&self) -> Option<&str> {
165 match self {
166 DispositionParam::Filename(filename) => Some(filename.as_str()),
167 _ => None,
168 }
169 }
170
171 #[inline]
173 pub fn as_filename_ext(&self) -> Option<&ExtendedValue> {
174 match self {
175 DispositionParam::FilenameExt(value) => Some(value),
176 _ => None,
177 }
178 }
179
180 #[inline]
183 pub fn as_unknown<T: AsRef<str>>(&self, name: T) -> Option<&str> {
184 match self {
185 DispositionParam::Unknown(ref ext_name, ref value)
186 if ext_name.eq_ignore_ascii_case(name.as_ref()) =>
187 {
188 Some(value.as_str())
189 }
190 _ => None,
191 }
192 }
193
194 #[inline]
197 pub fn as_unknown_ext<T: AsRef<str>>(&self, name: T) -> Option<&ExtendedValue> {
198 match self {
199 DispositionParam::UnknownExt(ref ext_name, ref value)
200 if ext_name.eq_ignore_ascii_case(name.as_ref()) =>
201 {
202 Some(value)
203 }
204 _ => None,
205 }
206 }
207}
208
209#[derive(Debug, Clone, PartialEq, Eq)]
314pub struct ContentDisposition {
315 pub disposition: DispositionType,
317
318 pub parameters: Vec<DispositionParam>,
320}
321
322impl ContentDisposition {
323 pub fn attachment(filename: impl Into<String>) -> Self {
335 Self {
336 disposition: DispositionType::Attachment,
337 parameters: vec![DispositionParam::Filename(filename.into())],
338 }
339 }
340
341 pub fn from_raw(hv: &header::HeaderValue) -> Result<Self, crate::error::ParseError> {
343 let hv = String::from_utf8(hv.as_bytes().to_vec())
346 .map_err(|_| crate::error::ParseError::Header)?;
347
348 let (disp_type, mut left) = split_once_and_trim(hv.as_str().trim(), ';');
349 if disp_type.is_empty() {
350 return Err(crate::error::ParseError::Header);
351 }
352
353 let mut cd = ContentDisposition {
354 disposition: disp_type.into(),
355 parameters: Vec::new(),
356 };
357
358 while !left.is_empty() {
359 let (param_name, new_left) = split_once_and_trim(left, '=');
360 if param_name.is_empty() || param_name == "*" || new_left.is_empty() {
361 return Err(crate::error::ParseError::Header);
362 }
363 left = new_left;
364 if let Some(param_name) = param_name.strip_suffix('*') {
365 let (ext_value, new_left) = split_once_and_trim(left, ';');
367 left = new_left;
368 let ext_value = header::parse_extended_value(ext_value)?;
369
370 let param = if param_name.eq_ignore_ascii_case("filename") {
371 DispositionParam::FilenameExt(ext_value)
372 } else {
373 DispositionParam::UnknownExt(param_name.to_owned(), ext_value)
374 };
375 cd.parameters.push(param);
376 } else {
377 let value = if left.starts_with('\"') {
379 let mut escaping = false;
381 let mut quoted_string = vec![];
382 let mut end = None;
383 for (i, &c) in left.as_bytes().iter().skip(1).enumerate() {
385 if escaping {
386 escaping = false;
387 quoted_string.push(c);
388 } else if c == 0x5c {
389 escaping = true;
391 } else if c == 0x22 {
392 end = Some(i + 1); break;
395 } else {
396 quoted_string.push(c);
397 }
398 }
399 left = &left[end.ok_or(crate::error::ParseError::Header)? + 1..];
400 left = split_once(left, ';').1.trim_start();
401 String::from_utf8(quoted_string)
403 .map_err(|_| crate::error::ParseError::Header)?
404 } else {
405 let (token, new_left) = split_once_and_trim(left, ';');
407 left = new_left;
408 if token.is_empty() {
409 return Err(crate::error::ParseError::Header);
411 }
412 token.to_owned()
413 };
414
415 let param = if param_name.eq_ignore_ascii_case("name") {
416 DispositionParam::Name(value)
417 } else if param_name.eq_ignore_ascii_case("filename") {
418 DispositionParam::Filename(value)
420 } else {
421 DispositionParam::Unknown(param_name.to_owned(), value)
422 };
423 cd.parameters.push(param);
424 }
425 }
426
427 Ok(cd)
428 }
429
430 pub fn is_inline(&self) -> bool {
432 matches!(self.disposition, DispositionType::Inline)
433 }
434
435 pub fn is_attachment(&self) -> bool {
437 matches!(self.disposition, DispositionType::Attachment)
438 }
439
440 pub fn is_form_data(&self) -> bool {
442 matches!(self.disposition, DispositionType::FormData)
443 }
444
445 pub fn is_ext(&self, disp_type: impl AsRef<str>) -> bool {
447 matches!(
448 self.disposition,
449 DispositionType::Ext(ref t) if t.eq_ignore_ascii_case(disp_type.as_ref())
450 )
451 }
452
453 pub fn get_name(&self) -> Option<&str> {
455 self.parameters.iter().find_map(DispositionParam::as_name)
456 }
457
458 pub fn get_filename(&self) -> Option<&str> {
460 self.parameters
461 .iter()
462 .find_map(DispositionParam::as_filename)
463 }
464
465 pub fn get_filename_ext(&self) -> Option<&ExtendedValue> {
467 self.parameters
468 .iter()
469 .find_map(DispositionParam::as_filename_ext)
470 }
471
472 pub fn get_unknown(&self, name: impl AsRef<str>) -> Option<&str> {
474 let name = name.as_ref();
475 self.parameters.iter().find_map(|p| p.as_unknown(name))
476 }
477
478 pub fn get_unknown_ext(&self, name: impl AsRef<str>) -> Option<&ExtendedValue> {
480 let name = name.as_ref();
481 self.parameters.iter().find_map(|p| p.as_unknown_ext(name))
482 }
483}
484
485impl TryIntoHeaderValue for ContentDisposition {
486 type Error = header::InvalidHeaderValue;
487
488 fn try_into_value(self) -> Result<header::HeaderValue, Self::Error> {
489 let mut writer = Writer::new();
490 let _ = write!(&mut writer, "{}", self);
491 header::HeaderValue::from_maybe_shared(writer.take())
492 }
493}
494
495impl Header for ContentDisposition {
496 fn name() -> header::HeaderName {
497 header::CONTENT_DISPOSITION
498 }
499
500 fn parse<T: crate::HttpMessage>(msg: &T) -> Result<Self, crate::error::ParseError> {
501 if let Some(h) = msg.headers().get(Self::name()) {
502 Self::from_raw(h)
503 } else {
504 Err(crate::error::ParseError::Header)
505 }
506 }
507}
508
509impl fmt::Display for DispositionType {
510 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
511 match self {
512 DispositionType::Inline => write!(f, "inline"),
513 DispositionType::Attachment => write!(f, "attachment"),
514 DispositionType::FormData => write!(f, "form-data"),
515 DispositionType::Ext(ref s) => write!(f, "{}", s),
516 }
517 }
518}
519
520impl fmt::Display for DispositionParam {
521 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
522 static RE: Lazy<Regex> =
558 Lazy::new(|| Regex::new("[\x00-\x08\x10-\x1F\x7F\"\\\\]").unwrap());
559
560 match self {
561 DispositionParam::Name(ref value) => write!(f, "name={}", value),
562
563 DispositionParam::Filename(ref value) => {
564 write!(f, "filename=\"{}\"", RE.replace_all(value, "\\$0").as_ref())
565 }
566
567 DispositionParam::Unknown(ref name, ref value) => {
568 write!(f, "{name}=\"{}\"", RE.replace_all(value, "\\$0").as_ref())
569 }
570
571 DispositionParam::FilenameExt(ref ext_value) => {
572 write!(f, "filename*={}", ext_value)
573 }
574
575 DispositionParam::UnknownExt(ref name, ref ext_value) => {
576 write!(f, "{}*={}", name, ext_value)
577 }
578 }
579 }
580}
581
582impl fmt::Display for ContentDisposition {
583 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
584 write!(f, "{}", self.disposition)?;
585 self.parameters
586 .iter()
587 .try_for_each(|param| write!(f, "; {}", param))
588 }
589}
590
591#[cfg(test)]
592mod tests {
593 use super::{ContentDisposition, DispositionParam, DispositionType};
594 use crate::http::header::{Charset, ExtendedValue, HeaderValue};
595
596 #[test]
597 fn test_from_raw_basic() {
598 assert!(ContentDisposition::from_raw(&HeaderValue::from_static("")).is_err());
599
600 let a =
601 HeaderValue::from_static("form-data; dummy=3; name=upload; filename=\"sample.png\"");
602 let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
603 let b = ContentDisposition {
604 disposition: DispositionType::FormData,
605 parameters: vec![
606 DispositionParam::Unknown("dummy".to_owned(), "3".to_owned()),
607 DispositionParam::Name("upload".to_owned()),
608 DispositionParam::Filename("sample.png".to_owned()),
609 ],
610 };
611 assert_eq!(a, b);
612
613 let a = HeaderValue::from_static("attachment; filename=\"image.jpg\"");
614 let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
615 let b = ContentDisposition {
616 disposition: DispositionType::Attachment,
617 parameters: vec![DispositionParam::Filename("image.jpg".to_owned())],
618 };
619 assert_eq!(a, b);
620
621 let a = HeaderValue::from_static("inline; filename=image.jpg");
622 let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
623 let b = ContentDisposition {
624 disposition: DispositionType::Inline,
625 parameters: vec![DispositionParam::Filename("image.jpg".to_owned())],
626 };
627 assert_eq!(a, b);
628
629 let a = HeaderValue::from_static(
630 "attachment; creation-date=\"Wed, 12 Feb 1997 16:29:51 -0500\"",
631 );
632 let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
633 let b = ContentDisposition {
634 disposition: DispositionType::Attachment,
635 parameters: vec![DispositionParam::Unknown(
636 String::from("creation-date"),
637 "Wed, 12 Feb 1997 16:29:51 -0500".to_owned(),
638 )],
639 };
640 assert_eq!(a, b);
641 }
642
643 #[test]
644 fn test_from_raw_extended() {
645 let a = HeaderValue::from_static(
646 "attachment; filename*=UTF-8''%c2%a3%20and%20%e2%82%ac%20rates",
647 );
648 let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
649 let b = ContentDisposition {
650 disposition: DispositionType::Attachment,
651 parameters: vec![DispositionParam::FilenameExt(ExtendedValue {
652 charset: Charset::Ext(String::from("UTF-8")),
653 language_tag: None,
654 value: vec![
655 0xc2, 0xa3, 0x20, b'a', b'n', b'd', 0x20, 0xe2, 0x82, 0xac, 0x20, b'r', b'a',
656 b't', b'e', b's',
657 ],
658 })],
659 };
660 assert_eq!(a, b);
661
662 let a = HeaderValue::from_static(
663 "attachment; filename*=UTF-8''%c2%a3%20and%20%e2%82%ac%20rates",
664 );
665 let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
666 let b = ContentDisposition {
667 disposition: DispositionType::Attachment,
668 parameters: vec![DispositionParam::FilenameExt(ExtendedValue {
669 charset: Charset::Ext(String::from("UTF-8")),
670 language_tag: None,
671 value: vec![
672 0xc2, 0xa3, 0x20, b'a', b'n', b'd', 0x20, 0xe2, 0x82, 0xac, 0x20, b'r', b'a',
673 b't', b'e', b's',
674 ],
675 })],
676 };
677 assert_eq!(a, b);
678 }
679
680 #[test]
681 fn test_from_raw_extra_whitespace() {
682 let a = HeaderValue::from_static(
683 "form-data ; du-mmy= 3 ; name =upload ; filename = \"sample.png\" ; ",
684 );
685 let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
686 let b = ContentDisposition {
687 disposition: DispositionType::FormData,
688 parameters: vec![
689 DispositionParam::Unknown("du-mmy".to_owned(), "3".to_owned()),
690 DispositionParam::Name("upload".to_owned()),
691 DispositionParam::Filename("sample.png".to_owned()),
692 ],
693 };
694 assert_eq!(a, b);
695 }
696
697 #[test]
698 fn test_from_raw_unordered() {
699 let a = HeaderValue::from_static(
700 "form-data; dummy=3; filename=\"sample.png\" ; name=upload;",
701 );
703 let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
704 let b = ContentDisposition {
705 disposition: DispositionType::FormData,
706 parameters: vec![
707 DispositionParam::Unknown("dummy".to_owned(), "3".to_owned()),
708 DispositionParam::Filename("sample.png".to_owned()),
709 DispositionParam::Name("upload".to_owned()),
710 ],
711 };
712 assert_eq!(a, b);
713
714 let a = HeaderValue::from_str(
715 "attachment; filename*=iso-8859-1''foo-%E4.html; filename=\"foo-ä.html\"",
716 )
717 .unwrap();
718 let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
719 let b = ContentDisposition {
720 disposition: DispositionType::Attachment,
721 parameters: vec![
722 DispositionParam::FilenameExt(ExtendedValue {
723 charset: Charset::Iso_8859_1,
724 language_tag: None,
725 value: b"foo-\xe4.html".to_vec(),
726 }),
727 DispositionParam::Filename("foo-ä.html".to_owned()),
728 ],
729 };
730 assert_eq!(a, b);
731 }
732
733 #[test]
734 fn test_from_raw_only_disp() {
735 let a = ContentDisposition::from_raw(&HeaderValue::from_static("attachment")).unwrap();
736 let b = ContentDisposition {
737 disposition: DispositionType::Attachment,
738 parameters: vec![],
739 };
740 assert_eq!(a, b);
741
742 let a = ContentDisposition::from_raw(&HeaderValue::from_static("inline ;")).unwrap();
743 let b = ContentDisposition {
744 disposition: DispositionType::Inline,
745 parameters: vec![],
746 };
747 assert_eq!(a, b);
748
749 let a =
750 ContentDisposition::from_raw(&HeaderValue::from_static("unknown-disp-param")).unwrap();
751 let b = ContentDisposition {
752 disposition: DispositionType::Ext(String::from("unknown-disp-param")),
753 parameters: vec![],
754 };
755 assert_eq!(a, b);
756 }
757
758 #[test]
759 fn from_raw_with_mixed_case() {
760 let a = HeaderValue::from_str(
761 "InLInE; fIlenAME*=iso-8859-1''foo-%E4.html; filEName=\"foo-ä.html\"",
762 )
763 .unwrap();
764 let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
765 let b = ContentDisposition {
766 disposition: DispositionType::Inline,
767 parameters: vec![
768 DispositionParam::FilenameExt(ExtendedValue {
769 charset: Charset::Iso_8859_1,
770 language_tag: None,
771 value: b"foo-\xe4.html".to_vec(),
772 }),
773 DispositionParam::Filename("foo-ä.html".to_owned()),
774 ],
775 };
776 assert_eq!(a, b);
777 }
778
779 #[test]
780 fn from_raw_with_unicode() {
781 let a = HeaderValue::from_str("form-data; name=upload; filename=\"文件.webp\"").unwrap();
790 let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
791 let b = ContentDisposition {
792 disposition: DispositionType::FormData,
793 parameters: vec![
794 DispositionParam::Name(String::from("upload")),
795 DispositionParam::Filename(String::from("文件.webp")),
796 ],
797 };
798 assert_eq!(a, b);
799
800 let a = HeaderValue::from_str(
801 "form-data; name=upload; filename=\"余固知謇謇之為患兮,忍而不能舍也.pptx\"",
802 )
803 .unwrap();
804 let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
805 let b = ContentDisposition {
806 disposition: DispositionType::FormData,
807 parameters: vec![
808 DispositionParam::Name(String::from("upload")),
809 DispositionParam::Filename(String::from("余固知謇謇之為患兮,忍而不能舍也.pptx")),
810 ],
811 };
812 assert_eq!(a, b);
813 }
814
815 #[test]
816 fn test_from_raw_escape() {
817 let a = HeaderValue::from_static(
818 "form-data; dummy=3; name=upload; filename=\"s\\amp\\\"le.png\"",
819 );
820 let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
821 let b = ContentDisposition {
822 disposition: DispositionType::FormData,
823 parameters: vec![
824 DispositionParam::Unknown("dummy".to_owned(), "3".to_owned()),
825 DispositionParam::Name("upload".to_owned()),
826 DispositionParam::Filename(
827 ['s', 'a', 'm', 'p', '\"', 'l', 'e', '.', 'p', 'n', 'g']
828 .iter()
829 .collect(),
830 ),
831 ],
832 };
833 assert_eq!(a, b);
834 }
835
836 #[test]
837 fn test_from_raw_semicolon() {
838 let a = HeaderValue::from_static("form-data; filename=\"A semicolon here;.pdf\"");
839 let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
840 let b = ContentDisposition {
841 disposition: DispositionType::FormData,
842 parameters: vec![DispositionParam::Filename(String::from(
843 "A semicolon here;.pdf",
844 ))],
845 };
846 assert_eq!(a, b);
847 }
848
849 #[test]
850 fn test_from_raw_unnecessary_percent_decode() {
851 let a = HeaderValue::from_static(
862 "form-data; name=photo; filename=\"%74%65%73%74%2e%70%6e%67\"",
863 );
864 let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
865 let b = ContentDisposition {
866 disposition: DispositionType::FormData,
867 parameters: vec![
868 DispositionParam::Name("photo".to_owned()),
869 DispositionParam::Filename(String::from("%74%65%73%74%2e%70%6e%67")),
870 ],
871 };
872 assert_eq!(a, b);
873
874 let a = HeaderValue::from_static("form-data; name=photo; filename=\"%74%65%73%74.png\"");
875 let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
876 let b = ContentDisposition {
877 disposition: DispositionType::FormData,
878 parameters: vec![
879 DispositionParam::Name("photo".to_owned()),
880 DispositionParam::Filename(String::from("%74%65%73%74.png")),
881 ],
882 };
883 assert_eq!(a, b);
884 }
885
886 #[test]
887 fn test_from_raw_param_value_missing() {
888 let a = HeaderValue::from_static("form-data; name=upload ; filename=");
889 assert!(ContentDisposition::from_raw(&a).is_err());
890
891 let a = HeaderValue::from_static("attachment; dummy=; filename=invoice.pdf");
892 assert!(ContentDisposition::from_raw(&a).is_err());
893
894 let a = HeaderValue::from_static("inline; filename= ");
895 assert!(ContentDisposition::from_raw(&a).is_err());
896
897 let a = HeaderValue::from_static("inline; filename=\"\"");
898 assert!(ContentDisposition::from_raw(&a)
899 .expect("parse cd")
900 .get_filename()
901 .expect("filename")
902 .is_empty());
903 }
904
905 #[test]
906 fn test_from_raw_param_name_missing() {
907 let a = HeaderValue::from_static("inline; =\"test.txt\"");
908 assert!(ContentDisposition::from_raw(&a).is_err());
909
910 let a = HeaderValue::from_static("inline; =diary.odt");
911 assert!(ContentDisposition::from_raw(&a).is_err());
912
913 let a = HeaderValue::from_static("inline; =");
914 assert!(ContentDisposition::from_raw(&a).is_err());
915 }
916
917 #[test]
918 fn test_display_extended() {
919 let as_string = "attachment; filename*=UTF-8'en'%C2%A3%20and%20%E2%82%AC%20rates";
920 let a = HeaderValue::from_static(as_string);
921 let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
922 let display_rendered = format!("{}", a);
923 assert_eq!(as_string, display_rendered);
924
925 let a = HeaderValue::from_static("attachment; filename=colourful.csv");
926 let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
927 let display_rendered = format!("{}", a);
928 assert_eq!(
929 "attachment; filename=\"colourful.csv\"".to_owned(),
930 display_rendered
931 );
932 }
933
934 #[test]
935 fn test_display_quote() {
936 let as_string = "form-data; name=upload; filename=\"Quote\\\"here.png\"";
937 as_string
938 .find(['\\', '\"'].iter().collect::<String>().as_str())
939 .unwrap(); let a = HeaderValue::from_static(as_string);
941 let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
942 let display_rendered = format!("{}", a);
943 assert_eq!(as_string, display_rendered);
944 }
945
946 #[test]
947 fn test_display_space_tab() {
948 let as_string = "form-data; name=upload; filename=\"Space here.png\"";
949 let a = HeaderValue::from_static(as_string);
950 let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
951 let display_rendered = format!("{}", a);
952 assert_eq!(as_string, display_rendered);
953
954 let a: ContentDisposition = ContentDisposition {
955 disposition: DispositionType::Inline,
956 parameters: vec![DispositionParam::Filename(String::from("Tab\there.png"))],
957 };
958 let display_rendered = format!("{}", a);
959 assert_eq!("inline; filename=\"Tab\x09here.png\"", display_rendered);
960 }
961
962 #[test]
963 fn test_display_control_characters() {
964 let a: ContentDisposition = ContentDisposition {
975 disposition: DispositionType::Inline,
976 parameters: vec![DispositionParam::Filename(String::from("bell\x07.png"))],
977 };
978 let display_rendered = format!("{}", a);
979 assert_eq!("inline; filename=\"bell\\\x07.png\"", display_rendered);
980 }
981
982 #[test]
983 fn test_param_methods() {
984 let param = DispositionParam::Filename(String::from("sample.txt"));
985 assert!(param.is_filename());
986 assert_eq!(param.as_filename().unwrap(), "sample.txt");
987
988 let param = DispositionParam::Unknown(String::from("foo"), String::from("bar"));
989 assert!(param.is_unknown("foo"));
990 assert_eq!(param.as_unknown("fOo"), Some("bar"));
991 }
992
993 #[test]
994 fn test_disposition_methods() {
995 let cd = ContentDisposition {
996 disposition: DispositionType::FormData,
997 parameters: vec![
998 DispositionParam::Unknown("dummy".to_owned(), "3".to_owned()),
999 DispositionParam::Name("upload".to_owned()),
1000 DispositionParam::Filename("sample.png".to_owned()),
1001 ],
1002 };
1003 assert_eq!(cd.get_name(), Some("upload"));
1004 assert_eq!(cd.get_unknown("dummy"), Some("3"));
1005 assert_eq!(cd.get_filename(), Some("sample.png"));
1006 assert_eq!(cd.get_unknown_ext("dummy"), None);
1007 assert_eq!(cd.get_unknown("duMMy"), Some("3"));
1008 }
1009}