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 {
338 let filename = filename.into();
339 let is_ascii = filename.is_ascii();
340
341 let mut parameters = Vec::new();
342
343 if is_ascii {
344 parameters.push(DispositionParam::Filename(filename));
345 } else {
346 parameters.push(DispositionParam::Filename(filename.clone()));
347 parameters.push(DispositionParam::FilenameExt(ExtendedValue {
348 charset: header::Charset::Ext(String::from("UTF-8")),
349 language_tag: None,
350 value: filename.into_bytes(),
351 }));
352 }
353
354 Self {
355 disposition: DispositionType::Attachment,
356 parameters,
357 }
358 }
359
360 pub fn from_raw(hv: &header::HeaderValue) -> Result<Self, crate::error::ParseError> {
362 let hv = String::from_utf8(hv.as_bytes().to_vec())
365 .map_err(|_| crate::error::ParseError::Header)?;
366
367 let (disp_type, mut left) = split_once_and_trim(hv.as_str().trim(), ';');
368 if disp_type.is_empty() {
369 return Err(crate::error::ParseError::Header);
370 }
371
372 let mut cd = ContentDisposition {
373 disposition: disp_type.into(),
374 parameters: Vec::new(),
375 };
376
377 while !left.is_empty() {
378 let (param_name, new_left) = split_once_and_trim(left, '=');
379 if param_name.is_empty() || param_name == "*" || new_left.is_empty() {
380 return Err(crate::error::ParseError::Header);
381 }
382 left = new_left;
383 if let Some(param_name) = param_name.strip_suffix('*') {
384 let (ext_value, new_left) = split_once_and_trim(left, ';');
386 left = new_left;
387 let ext_value = header::parse_extended_value(ext_value)?;
388
389 let param = if param_name.eq_ignore_ascii_case("filename") {
390 DispositionParam::FilenameExt(ext_value)
391 } else {
392 DispositionParam::UnknownExt(param_name.to_owned(), ext_value)
393 };
394 cd.parameters.push(param);
395 } else {
396 let value = if left.starts_with('\"') {
398 let mut escaping = false;
400 let mut quoted_string = vec![];
401 let mut end = None;
402 for (i, &c) in left.as_bytes().iter().skip(1).enumerate() {
404 if escaping {
405 escaping = false;
406 quoted_string.push(c);
407 } else if c == 0x5c {
408 escaping = true;
410 } else if c == 0x22 {
411 end = Some(i + 1); break;
414 } else {
415 quoted_string.push(c);
416 }
417 }
418 left = &left[end.ok_or(crate::error::ParseError::Header)? + 1..];
419 left = split_once(left, ';').1.trim_start();
420 String::from_utf8(quoted_string)
422 .map_err(|_| crate::error::ParseError::Header)?
423 } else {
424 let (token, new_left) = split_once_and_trim(left, ';');
426 left = new_left;
427 if token.is_empty() {
428 return Err(crate::error::ParseError::Header);
430 }
431 token.to_owned()
432 };
433
434 let param = if param_name.eq_ignore_ascii_case("name") {
435 DispositionParam::Name(value)
436 } else if param_name.eq_ignore_ascii_case("filename") {
437 DispositionParam::Filename(value)
439 } else {
440 DispositionParam::Unknown(param_name.to_owned(), value)
441 };
442 cd.parameters.push(param);
443 }
444 }
445
446 Ok(cd)
447 }
448
449 pub fn is_inline(&self) -> bool {
451 matches!(self.disposition, DispositionType::Inline)
452 }
453
454 pub fn is_attachment(&self) -> bool {
456 matches!(self.disposition, DispositionType::Attachment)
457 }
458
459 pub fn is_form_data(&self) -> bool {
461 matches!(self.disposition, DispositionType::FormData)
462 }
463
464 pub fn is_ext(&self, disp_type: impl AsRef<str>) -> bool {
466 matches!(
467 self.disposition,
468 DispositionType::Ext(ref t) if t.eq_ignore_ascii_case(disp_type.as_ref())
469 )
470 }
471
472 pub fn get_name(&self) -> Option<&str> {
474 self.parameters.iter().find_map(DispositionParam::as_name)
475 }
476
477 pub fn get_filename(&self) -> Option<&str> {
479 self.parameters
480 .iter()
481 .find_map(DispositionParam::as_filename)
482 }
483
484 pub fn get_filename_ext(&self) -> Option<&ExtendedValue> {
486 self.parameters
487 .iter()
488 .find_map(DispositionParam::as_filename_ext)
489 }
490
491 pub fn get_unknown(&self, name: impl AsRef<str>) -> Option<&str> {
493 let name = name.as_ref();
494 self.parameters.iter().find_map(|p| p.as_unknown(name))
495 }
496
497 pub fn get_unknown_ext(&self, name: impl AsRef<str>) -> Option<&ExtendedValue> {
499 let name = name.as_ref();
500 self.parameters.iter().find_map(|p| p.as_unknown_ext(name))
501 }
502}
503
504impl TryIntoHeaderValue for ContentDisposition {
505 type Error = header::InvalidHeaderValue;
506
507 fn try_into_value(self) -> Result<header::HeaderValue, Self::Error> {
508 let mut writer = Writer::new();
509 let _ = write!(&mut writer, "{}", self);
510 header::HeaderValue::from_maybe_shared(writer.take())
511 }
512}
513
514impl Header for ContentDisposition {
515 fn name() -> header::HeaderName {
516 header::CONTENT_DISPOSITION
517 }
518
519 fn parse<T: crate::HttpMessage>(msg: &T) -> Result<Self, crate::error::ParseError> {
520 if let Some(h) = msg.headers().get(Self::name()) {
521 Self::from_raw(h)
522 } else {
523 Err(crate::error::ParseError::Header)
524 }
525 }
526}
527
528impl fmt::Display for DispositionType {
529 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
530 match self {
531 DispositionType::Inline => write!(f, "inline"),
532 DispositionType::Attachment => write!(f, "attachment"),
533 DispositionType::FormData => write!(f, "form-data"),
534 DispositionType::Ext(ref s) => write!(f, "{}", s),
535 }
536 }
537}
538
539impl fmt::Display for DispositionParam {
540 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
541 static RE: Lazy<Regex> =
577 Lazy::new(|| Regex::new("[\x00-\x08\x10-\x1F\x7F\"\\\\]").unwrap());
578
579 match self {
580 DispositionParam::Name(ref value) => write!(f, "name={}", value),
581
582 DispositionParam::Filename(ref value) => {
583 write!(f, "filename=\"{}\"", RE.replace_all(value, "\\$0").as_ref())
584 }
585
586 DispositionParam::Unknown(ref name, ref value) => {
587 write!(f, "{name}=\"{}\"", RE.replace_all(value, "\\$0").as_ref())
588 }
589
590 DispositionParam::FilenameExt(ref ext_value) => {
591 write!(f, "filename*={}", ext_value)
592 }
593
594 DispositionParam::UnknownExt(ref name, ref ext_value) => {
595 write!(f, "{}*={}", name, ext_value)
596 }
597 }
598 }
599}
600
601impl fmt::Display for ContentDisposition {
602 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
603 write!(f, "{}", self.disposition)?;
604 self.parameters
605 .iter()
606 .try_for_each(|param| write!(f, "; {}", param))
607 }
608}
609
610#[cfg(test)]
611mod tests {
612 use super::{ContentDisposition, DispositionParam, DispositionType};
613 use crate::http::header::{Charset, ExtendedValue, HeaderValue, TryIntoHeaderValue as _};
614
615 #[test]
616 fn test_attachment_ascii() {
617 let header = ContentDisposition::attachment("file name.zip")
618 .try_into_value()
619 .unwrap();
620
621 assert_eq!(header, "attachment; filename=\"file name.zip\"");
622 }
623
624 #[test]
625 fn test_attachment_non_ascii() {
626 let header = ContentDisposition::attachment("café.pdf")
627 .try_into_value()
628 .unwrap();
629
630 assert_eq!(
631 header.as_bytes(),
632 "attachment; filename=\"café.pdf\"; filename*=UTF-8''caf%C3%A9.pdf".as_bytes()
633 );
634 }
635
636 #[test]
637 fn test_from_raw_basic() {
638 assert!(ContentDisposition::from_raw(&HeaderValue::from_static("")).is_err());
639
640 let a =
641 HeaderValue::from_static("form-data; dummy=3; name=upload; filename=\"sample.png\"");
642 let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
643 let b = ContentDisposition {
644 disposition: DispositionType::FormData,
645 parameters: vec![
646 DispositionParam::Unknown("dummy".to_owned(), "3".to_owned()),
647 DispositionParam::Name("upload".to_owned()),
648 DispositionParam::Filename("sample.png".to_owned()),
649 ],
650 };
651 assert_eq!(a, b);
652
653 let a = HeaderValue::from_static("attachment; filename=\"image.jpg\"");
654 let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
655 let b = ContentDisposition {
656 disposition: DispositionType::Attachment,
657 parameters: vec![DispositionParam::Filename("image.jpg".to_owned())],
658 };
659 assert_eq!(a, b);
660
661 let a = HeaderValue::from_static("inline; filename=image.jpg");
662 let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
663 let b = ContentDisposition {
664 disposition: DispositionType::Inline,
665 parameters: vec![DispositionParam::Filename("image.jpg".to_owned())],
666 };
667 assert_eq!(a, b);
668
669 let a = HeaderValue::from_static(
670 "attachment; creation-date=\"Wed, 12 Feb 1997 16:29:51 -0500\"",
671 );
672 let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
673 let b = ContentDisposition {
674 disposition: DispositionType::Attachment,
675 parameters: vec![DispositionParam::Unknown(
676 String::from("creation-date"),
677 "Wed, 12 Feb 1997 16:29:51 -0500".to_owned(),
678 )],
679 };
680 assert_eq!(a, b);
681 }
682
683 #[test]
684 fn test_from_raw_extended() {
685 let a = HeaderValue::from_static(
686 "attachment; filename*=UTF-8''%c2%a3%20and%20%e2%82%ac%20rates",
687 );
688 let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
689 let b = ContentDisposition {
690 disposition: DispositionType::Attachment,
691 parameters: vec![DispositionParam::FilenameExt(ExtendedValue {
692 charset: Charset::Ext(String::from("UTF-8")),
693 language_tag: None,
694 value: vec![
695 0xc2, 0xa3, 0x20, b'a', b'n', b'd', 0x20, 0xe2, 0x82, 0xac, 0x20, b'r', b'a',
696 b't', b'e', b's',
697 ],
698 })],
699 };
700 assert_eq!(a, b);
701
702 let a = HeaderValue::from_static(
703 "attachment; filename*=UTF-8''%c2%a3%20and%20%e2%82%ac%20rates",
704 );
705 let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
706 let b = ContentDisposition {
707 disposition: DispositionType::Attachment,
708 parameters: vec![DispositionParam::FilenameExt(ExtendedValue {
709 charset: Charset::Ext(String::from("UTF-8")),
710 language_tag: None,
711 value: vec![
712 0xc2, 0xa3, 0x20, b'a', b'n', b'd', 0x20, 0xe2, 0x82, 0xac, 0x20, b'r', b'a',
713 b't', b'e', b's',
714 ],
715 })],
716 };
717 assert_eq!(a, b);
718 }
719
720 #[test]
721 fn test_from_raw_extra_whitespace() {
722 let a = HeaderValue::from_static(
723 "form-data ; du-mmy= 3 ; name =upload ; filename = \"sample.png\" ; ",
724 );
725 let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
726 let b = ContentDisposition {
727 disposition: DispositionType::FormData,
728 parameters: vec![
729 DispositionParam::Unknown("du-mmy".to_owned(), "3".to_owned()),
730 DispositionParam::Name("upload".to_owned()),
731 DispositionParam::Filename("sample.png".to_owned()),
732 ],
733 };
734 assert_eq!(a, b);
735 }
736
737 #[test]
738 fn test_from_raw_unordered() {
739 let a = HeaderValue::from_static(
740 "form-data; dummy=3; filename=\"sample.png\" ; name=upload;",
741 );
743 let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
744 let b = ContentDisposition {
745 disposition: DispositionType::FormData,
746 parameters: vec![
747 DispositionParam::Unknown("dummy".to_owned(), "3".to_owned()),
748 DispositionParam::Filename("sample.png".to_owned()),
749 DispositionParam::Name("upload".to_owned()),
750 ],
751 };
752 assert_eq!(a, b);
753
754 let a = HeaderValue::from_str(
755 "attachment; filename*=iso-8859-1''foo-%E4.html; filename=\"foo-ä.html\"",
756 )
757 .unwrap();
758 let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
759 let b = ContentDisposition {
760 disposition: DispositionType::Attachment,
761 parameters: vec![
762 DispositionParam::FilenameExt(ExtendedValue {
763 charset: Charset::Iso_8859_1,
764 language_tag: None,
765 value: b"foo-\xe4.html".to_vec(),
766 }),
767 DispositionParam::Filename("foo-ä.html".to_owned()),
768 ],
769 };
770 assert_eq!(a, b);
771 }
772
773 #[test]
774 fn test_from_raw_only_disp() {
775 let a = ContentDisposition::from_raw(&HeaderValue::from_static("attachment")).unwrap();
776 let b = ContentDisposition {
777 disposition: DispositionType::Attachment,
778 parameters: vec![],
779 };
780 assert_eq!(a, b);
781
782 let a = ContentDisposition::from_raw(&HeaderValue::from_static("inline ;")).unwrap();
783 let b = ContentDisposition {
784 disposition: DispositionType::Inline,
785 parameters: vec![],
786 };
787 assert_eq!(a, b);
788
789 let a =
790 ContentDisposition::from_raw(&HeaderValue::from_static("unknown-disp-param")).unwrap();
791 let b = ContentDisposition {
792 disposition: DispositionType::Ext(String::from("unknown-disp-param")),
793 parameters: vec![],
794 };
795 assert_eq!(a, b);
796 }
797
798 #[test]
799 fn from_raw_with_mixed_case() {
800 let a = HeaderValue::from_str(
801 "InLInE; fIlenAME*=iso-8859-1''foo-%E4.html; filEName=\"foo-ä.html\"",
802 )
803 .unwrap();
804 let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
805 let b = ContentDisposition {
806 disposition: DispositionType::Inline,
807 parameters: vec![
808 DispositionParam::FilenameExt(ExtendedValue {
809 charset: Charset::Iso_8859_1,
810 language_tag: None,
811 value: b"foo-\xe4.html".to_vec(),
812 }),
813 DispositionParam::Filename("foo-ä.html".to_owned()),
814 ],
815 };
816 assert_eq!(a, b);
817 }
818
819 #[test]
820 fn from_raw_with_unicode() {
821 let a = HeaderValue::from_str("form-data; name=upload; filename=\"文件.webp\"").unwrap();
830 let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
831 let b = ContentDisposition {
832 disposition: DispositionType::FormData,
833 parameters: vec![
834 DispositionParam::Name(String::from("upload")),
835 DispositionParam::Filename(String::from("文件.webp")),
836 ],
837 };
838 assert_eq!(a, b);
839
840 let a = HeaderValue::from_str(
841 "form-data; name=upload; filename=\"余固知謇謇之為患兮,忍而不能舍也.pptx\"",
842 )
843 .unwrap();
844 let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
845 let b = ContentDisposition {
846 disposition: DispositionType::FormData,
847 parameters: vec![
848 DispositionParam::Name(String::from("upload")),
849 DispositionParam::Filename(String::from("余固知謇謇之為患兮,忍而不能舍也.pptx")),
850 ],
851 };
852 assert_eq!(a, b);
853 }
854
855 #[test]
856 fn test_from_raw_escape() {
857 let a = HeaderValue::from_static(
858 "form-data; dummy=3; name=upload; filename=\"s\\amp\\\"le.png\"",
859 );
860 let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
861 let b = ContentDisposition {
862 disposition: DispositionType::FormData,
863 parameters: vec![
864 DispositionParam::Unknown("dummy".to_owned(), "3".to_owned()),
865 DispositionParam::Name("upload".to_owned()),
866 DispositionParam::Filename(
867 ['s', 'a', 'm', 'p', '\"', 'l', 'e', '.', 'p', 'n', 'g']
868 .iter()
869 .collect(),
870 ),
871 ],
872 };
873 assert_eq!(a, b);
874 }
875
876 #[test]
877 fn test_from_raw_semicolon() {
878 let a = HeaderValue::from_static("form-data; filename=\"A semicolon here;.pdf\"");
879 let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
880 let b = ContentDisposition {
881 disposition: DispositionType::FormData,
882 parameters: vec![DispositionParam::Filename(String::from(
883 "A semicolon here;.pdf",
884 ))],
885 };
886 assert_eq!(a, b);
887 }
888
889 #[test]
890 fn test_from_raw_unnecessary_percent_decode() {
891 let a = HeaderValue::from_static(
902 "form-data; name=photo; filename=\"%74%65%73%74%2e%70%6e%67\"",
903 );
904 let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
905 let b = ContentDisposition {
906 disposition: DispositionType::FormData,
907 parameters: vec![
908 DispositionParam::Name("photo".to_owned()),
909 DispositionParam::Filename(String::from("%74%65%73%74%2e%70%6e%67")),
910 ],
911 };
912 assert_eq!(a, b);
913
914 let a = HeaderValue::from_static("form-data; name=photo; filename=\"%74%65%73%74.png\"");
915 let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
916 let b = ContentDisposition {
917 disposition: DispositionType::FormData,
918 parameters: vec![
919 DispositionParam::Name("photo".to_owned()),
920 DispositionParam::Filename(String::from("%74%65%73%74.png")),
921 ],
922 };
923 assert_eq!(a, b);
924 }
925
926 #[test]
927 fn test_from_raw_param_value_missing() {
928 let a = HeaderValue::from_static("form-data; name=upload ; filename=");
929 assert!(ContentDisposition::from_raw(&a).is_err());
930
931 let a = HeaderValue::from_static("attachment; dummy=; filename=invoice.pdf");
932 assert!(ContentDisposition::from_raw(&a).is_err());
933
934 let a = HeaderValue::from_static("inline; filename= ");
935 assert!(ContentDisposition::from_raw(&a).is_err());
936
937 let a = HeaderValue::from_static("inline; filename=\"\"");
938 assert!(ContentDisposition::from_raw(&a)
939 .expect("parse cd")
940 .get_filename()
941 .expect("filename")
942 .is_empty());
943 }
944
945 #[test]
946 fn test_from_raw_param_name_missing() {
947 let a = HeaderValue::from_static("inline; =\"test.txt\"");
948 assert!(ContentDisposition::from_raw(&a).is_err());
949
950 let a = HeaderValue::from_static("inline; =diary.odt");
951 assert!(ContentDisposition::from_raw(&a).is_err());
952
953 let a = HeaderValue::from_static("inline; =");
954 assert!(ContentDisposition::from_raw(&a).is_err());
955 }
956
957 #[test]
958 fn test_display_extended() {
959 let as_string = "attachment; filename*=UTF-8'en'%C2%A3%20and%20%E2%82%AC%20rates";
960 let a = HeaderValue::from_static(as_string);
961 let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
962 let display_rendered = format!("{}", a);
963 assert_eq!(as_string, display_rendered);
964
965 let a = HeaderValue::from_static("attachment; filename=colourful.csv");
966 let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
967 let display_rendered = format!("{}", a);
968 assert_eq!(
969 "attachment; filename=\"colourful.csv\"".to_owned(),
970 display_rendered
971 );
972 }
973
974 #[test]
975 fn test_display_quote() {
976 let as_string = "form-data; name=upload; filename=\"Quote\\\"here.png\"";
977 as_string
978 .find(['\\', '\"'].iter().collect::<String>().as_str())
979 .unwrap(); let a = HeaderValue::from_static(as_string);
981 let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
982 let display_rendered = format!("{}", a);
983 assert_eq!(as_string, display_rendered);
984 }
985
986 #[test]
987 fn test_display_space_tab() {
988 let as_string = "form-data; name=upload; filename=\"Space here.png\"";
989 let a = HeaderValue::from_static(as_string);
990 let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
991 let display_rendered = format!("{}", a);
992 assert_eq!(as_string, display_rendered);
993
994 let a: ContentDisposition = ContentDisposition {
995 disposition: DispositionType::Inline,
996 parameters: vec![DispositionParam::Filename(String::from("Tab\there.png"))],
997 };
998 let display_rendered = format!("{}", a);
999 assert_eq!("inline; filename=\"Tab\x09here.png\"", display_rendered);
1000 }
1001
1002 #[test]
1003 fn test_display_control_characters() {
1004 let a: ContentDisposition = ContentDisposition {
1015 disposition: DispositionType::Inline,
1016 parameters: vec![DispositionParam::Filename(String::from("bell\x07.png"))],
1017 };
1018 let display_rendered = format!("{}", a);
1019 assert_eq!("inline; filename=\"bell\\\x07.png\"", display_rendered);
1020 }
1021
1022 #[test]
1023 fn test_param_methods() {
1024 let param = DispositionParam::Filename(String::from("sample.txt"));
1025 assert!(param.is_filename());
1026 assert_eq!(param.as_filename().unwrap(), "sample.txt");
1027
1028 let param = DispositionParam::Unknown(String::from("foo"), String::from("bar"));
1029 assert!(param.is_unknown("foo"));
1030 assert_eq!(param.as_unknown("fOo"), Some("bar"));
1031 }
1032
1033 #[test]
1034 fn test_disposition_methods() {
1035 let cd = ContentDisposition {
1036 disposition: DispositionType::FormData,
1037 parameters: vec![
1038 DispositionParam::Unknown("dummy".to_owned(), "3".to_owned()),
1039 DispositionParam::Name("upload".to_owned()),
1040 DispositionParam::Filename("sample.png".to_owned()),
1041 ],
1042 };
1043 assert_eq!(cd.get_name(), Some("upload"));
1044 assert_eq!(cd.get_unknown("dummy"), Some("3"));
1045 assert_eq!(cd.get_filename(), Some("sample.png"));
1046 assert_eq!(cd.get_unknown_ext("dummy"), None);
1047 assert_eq!(cd.get_unknown("duMMy"), Some("3"));
1048 }
1049}