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) => write!(
568 f,
569 "{}=\"{}\"",
570 name,
571 &RE.replace_all(value, "\\$0").as_ref()
572 ),
573
574 DispositionParam::FilenameExt(ref ext_value) => {
575 write!(f, "filename*={}", ext_value)
576 }
577
578 DispositionParam::UnknownExt(ref name, ref ext_value) => {
579 write!(f, "{}*={}", name, ext_value)
580 }
581 }
582 }
583}
584
585impl fmt::Display for ContentDisposition {
586 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
587 write!(f, "{}", self.disposition)?;
588 self.parameters
589 .iter()
590 .try_for_each(|param| write!(f, "; {}", param))
591 }
592}
593
594#[cfg(test)]
595mod tests {
596 use super::{ContentDisposition, DispositionParam, DispositionType};
597 use crate::http::header::{Charset, ExtendedValue, HeaderValue};
598
599 #[test]
600 fn test_from_raw_basic() {
601 assert!(ContentDisposition::from_raw(&HeaderValue::from_static("")).is_err());
602
603 let a =
604 HeaderValue::from_static("form-data; dummy=3; name=upload; filename=\"sample.png\"");
605 let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
606 let b = ContentDisposition {
607 disposition: DispositionType::FormData,
608 parameters: vec![
609 DispositionParam::Unknown("dummy".to_owned(), "3".to_owned()),
610 DispositionParam::Name("upload".to_owned()),
611 DispositionParam::Filename("sample.png".to_owned()),
612 ],
613 };
614 assert_eq!(a, b);
615
616 let a = HeaderValue::from_static("attachment; filename=\"image.jpg\"");
617 let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
618 let b = ContentDisposition {
619 disposition: DispositionType::Attachment,
620 parameters: vec![DispositionParam::Filename("image.jpg".to_owned())],
621 };
622 assert_eq!(a, b);
623
624 let a = HeaderValue::from_static("inline; filename=image.jpg");
625 let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
626 let b = ContentDisposition {
627 disposition: DispositionType::Inline,
628 parameters: vec![DispositionParam::Filename("image.jpg".to_owned())],
629 };
630 assert_eq!(a, b);
631
632 let a = HeaderValue::from_static(
633 "attachment; creation-date=\"Wed, 12 Feb 1997 16:29:51 -0500\"",
634 );
635 let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
636 let b = ContentDisposition {
637 disposition: DispositionType::Attachment,
638 parameters: vec![DispositionParam::Unknown(
639 String::from("creation-date"),
640 "Wed, 12 Feb 1997 16:29:51 -0500".to_owned(),
641 )],
642 };
643 assert_eq!(a, b);
644 }
645
646 #[test]
647 fn test_from_raw_extended() {
648 let a = HeaderValue::from_static(
649 "attachment; filename*=UTF-8''%c2%a3%20and%20%e2%82%ac%20rates",
650 );
651 let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
652 let b = ContentDisposition {
653 disposition: DispositionType::Attachment,
654 parameters: vec![DispositionParam::FilenameExt(ExtendedValue {
655 charset: Charset::Ext(String::from("UTF-8")),
656 language_tag: None,
657 value: vec![
658 0xc2, 0xa3, 0x20, b'a', b'n', b'd', 0x20, 0xe2, 0x82, 0xac, 0x20, b'r', b'a',
659 b't', b'e', b's',
660 ],
661 })],
662 };
663 assert_eq!(a, b);
664
665 let a = HeaderValue::from_static(
666 "attachment; filename*=UTF-8''%c2%a3%20and%20%e2%82%ac%20rates",
667 );
668 let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
669 let b = ContentDisposition {
670 disposition: DispositionType::Attachment,
671 parameters: vec![DispositionParam::FilenameExt(ExtendedValue {
672 charset: Charset::Ext(String::from("UTF-8")),
673 language_tag: None,
674 value: vec![
675 0xc2, 0xa3, 0x20, b'a', b'n', b'd', 0x20, 0xe2, 0x82, 0xac, 0x20, b'r', b'a',
676 b't', b'e', b's',
677 ],
678 })],
679 };
680 assert_eq!(a, b);
681 }
682
683 #[test]
684 fn test_from_raw_extra_whitespace() {
685 let a = HeaderValue::from_static(
686 "form-data ; du-mmy= 3 ; name =upload ; filename = \"sample.png\" ; ",
687 );
688 let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
689 let b = ContentDisposition {
690 disposition: DispositionType::FormData,
691 parameters: vec![
692 DispositionParam::Unknown("du-mmy".to_owned(), "3".to_owned()),
693 DispositionParam::Name("upload".to_owned()),
694 DispositionParam::Filename("sample.png".to_owned()),
695 ],
696 };
697 assert_eq!(a, b);
698 }
699
700 #[test]
701 fn test_from_raw_unordered() {
702 let a = HeaderValue::from_static(
703 "form-data; dummy=3; filename=\"sample.png\" ; name=upload;",
704 );
706 let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
707 let b = ContentDisposition {
708 disposition: DispositionType::FormData,
709 parameters: vec![
710 DispositionParam::Unknown("dummy".to_owned(), "3".to_owned()),
711 DispositionParam::Filename("sample.png".to_owned()),
712 DispositionParam::Name("upload".to_owned()),
713 ],
714 };
715 assert_eq!(a, b);
716
717 let a = HeaderValue::from_str(
718 "attachment; filename*=iso-8859-1''foo-%E4.html; filename=\"foo-ä.html\"",
719 )
720 .unwrap();
721 let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
722 let b = ContentDisposition {
723 disposition: DispositionType::Attachment,
724 parameters: vec![
725 DispositionParam::FilenameExt(ExtendedValue {
726 charset: Charset::Iso_8859_1,
727 language_tag: None,
728 value: b"foo-\xe4.html".to_vec(),
729 }),
730 DispositionParam::Filename("foo-ä.html".to_owned()),
731 ],
732 };
733 assert_eq!(a, b);
734 }
735
736 #[test]
737 fn test_from_raw_only_disp() {
738 let a = ContentDisposition::from_raw(&HeaderValue::from_static("attachment")).unwrap();
739 let b = ContentDisposition {
740 disposition: DispositionType::Attachment,
741 parameters: vec![],
742 };
743 assert_eq!(a, b);
744
745 let a = ContentDisposition::from_raw(&HeaderValue::from_static("inline ;")).unwrap();
746 let b = ContentDisposition {
747 disposition: DispositionType::Inline,
748 parameters: vec![],
749 };
750 assert_eq!(a, b);
751
752 let a =
753 ContentDisposition::from_raw(&HeaderValue::from_static("unknown-disp-param")).unwrap();
754 let b = ContentDisposition {
755 disposition: DispositionType::Ext(String::from("unknown-disp-param")),
756 parameters: vec![],
757 };
758 assert_eq!(a, b);
759 }
760
761 #[test]
762 fn from_raw_with_mixed_case() {
763 let a = HeaderValue::from_str(
764 "InLInE; fIlenAME*=iso-8859-1''foo-%E4.html; filEName=\"foo-ä.html\"",
765 )
766 .unwrap();
767 let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
768 let b = ContentDisposition {
769 disposition: DispositionType::Inline,
770 parameters: vec![
771 DispositionParam::FilenameExt(ExtendedValue {
772 charset: Charset::Iso_8859_1,
773 language_tag: None,
774 value: b"foo-\xe4.html".to_vec(),
775 }),
776 DispositionParam::Filename("foo-ä.html".to_owned()),
777 ],
778 };
779 assert_eq!(a, b);
780 }
781
782 #[test]
783 fn from_raw_with_unicode() {
784 let a = HeaderValue::from_str("form-data; name=upload; filename=\"文件.webp\"").unwrap();
793 let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
794 let b = ContentDisposition {
795 disposition: DispositionType::FormData,
796 parameters: vec![
797 DispositionParam::Name(String::from("upload")),
798 DispositionParam::Filename(String::from("文件.webp")),
799 ],
800 };
801 assert_eq!(a, b);
802
803 let a = HeaderValue::from_str(
804 "form-data; name=upload; filename=\"余固知謇謇之為患兮,忍而不能舍也.pptx\"",
805 )
806 .unwrap();
807 let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
808 let b = ContentDisposition {
809 disposition: DispositionType::FormData,
810 parameters: vec![
811 DispositionParam::Name(String::from("upload")),
812 DispositionParam::Filename(String::from("余固知謇謇之為患兮,忍而不能舍也.pptx")),
813 ],
814 };
815 assert_eq!(a, b);
816 }
817
818 #[test]
819 fn test_from_raw_escape() {
820 let a = HeaderValue::from_static(
821 "form-data; dummy=3; name=upload; filename=\"s\\amp\\\"le.png\"",
822 );
823 let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
824 let b = ContentDisposition {
825 disposition: DispositionType::FormData,
826 parameters: vec![
827 DispositionParam::Unknown("dummy".to_owned(), "3".to_owned()),
828 DispositionParam::Name("upload".to_owned()),
829 DispositionParam::Filename(
830 ['s', 'a', 'm', 'p', '\"', 'l', 'e', '.', 'p', 'n', 'g']
831 .iter()
832 .collect(),
833 ),
834 ],
835 };
836 assert_eq!(a, b);
837 }
838
839 #[test]
840 fn test_from_raw_semicolon() {
841 let a = HeaderValue::from_static("form-data; filename=\"A semicolon here;.pdf\"");
842 let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
843 let b = ContentDisposition {
844 disposition: DispositionType::FormData,
845 parameters: vec![DispositionParam::Filename(String::from(
846 "A semicolon here;.pdf",
847 ))],
848 };
849 assert_eq!(a, b);
850 }
851
852 #[test]
853 fn test_from_raw_unnecessary_percent_decode() {
854 let a = HeaderValue::from_static(
865 "form-data; name=photo; filename=\"%74%65%73%74%2e%70%6e%67\"",
866 );
867 let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
868 let b = ContentDisposition {
869 disposition: DispositionType::FormData,
870 parameters: vec![
871 DispositionParam::Name("photo".to_owned()),
872 DispositionParam::Filename(String::from("%74%65%73%74%2e%70%6e%67")),
873 ],
874 };
875 assert_eq!(a, b);
876
877 let a = HeaderValue::from_static("form-data; name=photo; filename=\"%74%65%73%74.png\"");
878 let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
879 let b = ContentDisposition {
880 disposition: DispositionType::FormData,
881 parameters: vec![
882 DispositionParam::Name("photo".to_owned()),
883 DispositionParam::Filename(String::from("%74%65%73%74.png")),
884 ],
885 };
886 assert_eq!(a, b);
887 }
888
889 #[test]
890 fn test_from_raw_param_value_missing() {
891 let a = HeaderValue::from_static("form-data; name=upload ; filename=");
892 assert!(ContentDisposition::from_raw(&a).is_err());
893
894 let a = HeaderValue::from_static("attachment; dummy=; filename=invoice.pdf");
895 assert!(ContentDisposition::from_raw(&a).is_err());
896
897 let a = HeaderValue::from_static("inline; filename= ");
898 assert!(ContentDisposition::from_raw(&a).is_err());
899
900 let a = HeaderValue::from_static("inline; filename=\"\"");
901 assert!(ContentDisposition::from_raw(&a)
902 .expect("parse cd")
903 .get_filename()
904 .expect("filename")
905 .is_empty());
906 }
907
908 #[test]
909 fn test_from_raw_param_name_missing() {
910 let a = HeaderValue::from_static("inline; =\"test.txt\"");
911 assert!(ContentDisposition::from_raw(&a).is_err());
912
913 let a = HeaderValue::from_static("inline; =diary.odt");
914 assert!(ContentDisposition::from_raw(&a).is_err());
915
916 let a = HeaderValue::from_static("inline; =");
917 assert!(ContentDisposition::from_raw(&a).is_err());
918 }
919
920 #[test]
921 fn test_display_extended() {
922 let as_string = "attachment; filename*=UTF-8'en'%C2%A3%20and%20%E2%82%AC%20rates";
923 let a = HeaderValue::from_static(as_string);
924 let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
925 let display_rendered = format!("{}", a);
926 assert_eq!(as_string, display_rendered);
927
928 let a = HeaderValue::from_static("attachment; filename=colourful.csv");
929 let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
930 let display_rendered = format!("{}", a);
931 assert_eq!(
932 "attachment; filename=\"colourful.csv\"".to_owned(),
933 display_rendered
934 );
935 }
936
937 #[test]
938 fn test_display_quote() {
939 let as_string = "form-data; name=upload; filename=\"Quote\\\"here.png\"";
940 as_string
941 .find(['\\', '\"'].iter().collect::<String>().as_str())
942 .unwrap(); let a = HeaderValue::from_static(as_string);
944 let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
945 let display_rendered = format!("{}", a);
946 assert_eq!(as_string, display_rendered);
947 }
948
949 #[test]
950 fn test_display_space_tab() {
951 let as_string = "form-data; name=upload; filename=\"Space here.png\"";
952 let a = HeaderValue::from_static(as_string);
953 let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
954 let display_rendered = format!("{}", a);
955 assert_eq!(as_string, display_rendered);
956
957 let a: ContentDisposition = ContentDisposition {
958 disposition: DispositionType::Inline,
959 parameters: vec![DispositionParam::Filename(String::from("Tab\there.png"))],
960 };
961 let display_rendered = format!("{}", a);
962 assert_eq!("inline; filename=\"Tab\x09here.png\"", display_rendered);
963 }
964
965 #[test]
966 fn test_display_control_characters() {
967 let a: ContentDisposition = ContentDisposition {
978 disposition: DispositionType::Inline,
979 parameters: vec![DispositionParam::Filename(String::from("bell\x07.png"))],
980 };
981 let display_rendered = format!("{}", a);
982 assert_eq!("inline; filename=\"bell\\\x07.png\"", display_rendered);
983 }
984
985 #[test]
986 fn test_param_methods() {
987 let param = DispositionParam::Filename(String::from("sample.txt"));
988 assert!(param.is_filename());
989 assert_eq!(param.as_filename().unwrap(), "sample.txt");
990
991 let param = DispositionParam::Unknown(String::from("foo"), String::from("bar"));
992 assert!(param.is_unknown("foo"));
993 assert_eq!(param.as_unknown("fOo"), Some("bar"));
994 }
995
996 #[test]
997 fn test_disposition_methods() {
998 let cd = ContentDisposition {
999 disposition: DispositionType::FormData,
1000 parameters: vec![
1001 DispositionParam::Unknown("dummy".to_owned(), "3".to_owned()),
1002 DispositionParam::Name("upload".to_owned()),
1003 DispositionParam::Filename("sample.png".to_owned()),
1004 ],
1005 };
1006 assert_eq!(cd.get_name(), Some("upload"));
1007 assert_eq!(cd.get_unknown("dummy"), Some("3"));
1008 assert_eq!(cd.get_filename(), Some("sample.png"));
1009 assert_eq!(cd.get_unknown_ext("dummy"), None);
1010 assert_eq!(cd.get_unknown("duMMy"), Some("3"));
1011 }
1012}