1#![no_std]
35#![forbid(
36 unsafe_code,
37 future_incompatible,
38 missing_copy_implementations,
39 missing_debug_implementations,
40 missing_docs
41)]
42#![allow(clippy::map_clone)]
44
45#[cfg(feature = "alloc")]
46extern crate alloc;
47
48#[cfg(feature = "std")]
49extern crate std;
50
51#[rustfmt::skip]
52mod segments;
53pub use segments::constants;
54use segments::{SubtypeIntern, SuffixIntern, TypeIntern};
55
56use core::cell::Cell;
57use core::cmp;
58use core::convert::{TryFrom, TryInto};
59use core::fmt::{self, Write};
60use core::hash::{Hash, Hasher};
61use core::iter::FusedIterator;
62use core::str::from_utf8;
63use core::write;
64
65use memchr::{memchr, memchr2};
66
67macro_rules! matches {
68 ($expr: expr, $($pat:pat)|+) => {{
69 match ($expr) {
70 $($pat)|+ => true,
71 _ => false,
72 }
73 }}
74}
75
76#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
78#[non_exhaustive]
79pub enum ParseError {
80 NoSlash,
82
83 MissingType,
85
86 MissingSubtype,
88
89 NonHttpCodepoints,
91}
92
93impl fmt::Display for ParseError {
94 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
95 match self {
96 ParseError::NoSlash => write!(f, "no slash in MIME type"),
97 ParseError::MissingType => write!(f, "missing MIME type"),
98 ParseError::MissingSubtype => write!(f, "missing MIME subtype"),
99 ParseError::NonHttpCodepoints => write!(f, "MIME type contains non-HTTP codepoints"),
100 }
101 }
102}
103
104#[cfg(feature = "std")]
105impl std::error::Error for ParseError {}
106
107#[derive(Clone, Copy)]
111pub struct Mime<'a> {
112 ty: Type<'a>,
114
115 subtype: Subtype<'a>,
117
118 suffix: Option<Suffix<'a>>,
120
121 parameters: Parameters<'a>,
123}
124
125impl<'a> fmt::Display for Mime<'a> {
126 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
127 write!(f, "{}/{}", self.r#type(), self.subtype())?;
128
129 if let Some(suffix) = self.suffix() {
130 write!(f, "+{}", suffix)?;
131 }
132
133 for (key, value) in self.parameters() {
134 write!(f, ";{}={}", key, FormatQuotedString(value))?;
135 }
136
137 Ok(())
138 }
139}
140
141impl<'a> fmt::Debug for Mime<'a> {
142 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
143 struct Parameter<'a>(&'a str, &'a [u8]);
144
145 impl<'a> fmt::Debug for Parameter<'a> {
146 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
147 f.debug_struct("Parameter")
148 .field("key", &self.0)
149 .field("value", &FormatQuotedString(self.1))
150 .finish()
151 }
152 }
153
154 struct Parameters<I>(Cell<Option<I>>);
155
156 impl<'a, 'b, I: Iterator<Item = (&'a str, &'b [u8])>> fmt::Debug for Parameters<I> {
157 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
158 let iter = self.0.take().unwrap();
159 f.debug_list()
160 .entries(iter.map(|(k, v)| Parameter(k, v)))
161 .finish()
162 }
163 }
164
165 f.debug_struct("Mime")
166 .field("type", &self.r#type())
167 .field("subtype", &self.subtype())
168 .field("suffix", &self.suffix())
169 .field(
170 "parameters",
171 &Parameters(Cell::new(Some(self.parameters()))),
172 )
173 .finish()
174 }
175}
176
177impl<'a> Mime<'a> {
178 pub fn new(
189 ty: Type<'a>,
190 subtype: Subtype<'a>,
191 suffix: Option<Suffix<'a>>,
192 parameters: &'a [(&'a str, &'a [u8])],
193 ) -> Self {
194 Self {
195 ty,
196 subtype,
197 suffix,
198 parameters: Parameters::Slice(parameters),
199 }
200 }
201
202 pub fn parse_bytes(source: &'a [u8]) -> Result<Self, ParseError> {
213 let slash = memchr(b'/', source).ok_or(ParseError::NoSlash)?;
214 let plus = memchr(b'+', source);
215 let semicolon = memchr(b';', source);
216
217 let subtype_end = plus.or(semicolon).unwrap_or(source.len());
219 if slash == 0 {
220 return Err(ParseError::MissingType);
221 } else if slash == subtype_end - 1 {
222 return Err(ParseError::MissingSubtype);
223 }
224
225 let ty = &source[..slash];
227 let ty = Type::from_bytes(trim_start(ty)).ok_or(ParseError::NonHttpCodepoints)?;
228
229 let subtype = &source[slash + 1..subtype_end];
231 let subtype =
232 Subtype::from_bytes(trim_end(subtype)).ok_or(ParseError::NonHttpCodepoints)?;
233
234 let suffix = plus
236 .map(|plus| {
237 let suffix = &source[plus + 1..semicolon.unwrap_or(source.len())];
238 Suffix::from_bytes(trim_end(suffix)).ok_or(ParseError::NonHttpCodepoints)
239 })
240 .transpose()?;
241
242 let parameters = match semicolon {
244 None => Parameters::Slice(&[]),
245 Some(semicolon) => {
246 let buffer = &source[semicolon + 1..];
248 for (key, value) in parameter_iter(buffer) {
249 let key_valid = key.iter().all(|&b| is_http_codepoint(b));
251
252 let value_valid = if let Some(b'"') = value.first() {
254 value.iter().all(|&b| is_http_quoted_codepoint(b))
255 } else {
256 value.iter().all(|&b| is_http_codepoint(b))
257 };
258
259 if !key_valid || !value_valid {
260 return Err(ParseError::NonHttpCodepoints);
261 }
262 }
263
264 Parameters::Buffer(buffer)
265 }
266 };
267
268 Ok(Self {
269 ty,
270 subtype,
271 suffix,
272 parameters,
273 })
274 }
275
276 pub fn parse(source: &'a str) -> Result<Self, ParseError> {
278 Self::parse_bytes(source.as_bytes())
279 }
280
281 pub fn r#type(&self) -> Type<'_> {
291 self.ty
292 }
293
294 pub fn subtype(&self) -> Subtype<'_> {
304 self.subtype
305 }
306
307 pub fn suffix(&self) -> Option<Suffix<'_>> {
318 self.suffix
319 }
320
321 pub fn parameters(&self) -> impl Iterator<Item = (&str, &[u8])> {
333 match self.parameters {
334 Parameters::Slice(slice) => Either::Left(slice.iter().map(|&(k, v)| (k, v))),
335 Parameters::Buffer(buffer) => {
336 Either::Right(parameter_iter(buffer).map(|(key, value)| {
337 (from_utf8(key).unwrap(), value)
339 }))
340 }
341 }
342 }
343
344 pub fn essence(&self) -> Mime<'a> {
358 Mime {
359 ty: self.ty,
360 subtype: self.subtype,
361 suffix: None,
362 parameters: Parameters::Slice(&[]),
363 }
364 }
365
366 pub fn len(&self) -> usize {
372 let suffix_length = match self.suffix() {
373 Some(s) => s.into_str().len() + 1,
374 _ => 0,
375 };
376
377 let param_length: usize = self
378 .parameters()
379 .map(|(k, v)| k.len() + FormatQuotedString(v).len() + 2)
380 .sum();
381
382 self.r#type().into_str().len()
383 + self.subtype().into_str().len()
384 + 1 + suffix_length
386 + param_length
387 }
388
389 pub fn is_empty(&self) -> bool {
394 false
395 }
396}
397
398#[cfg(test)]
399mod mime_test {
400 use super::*;
401
402 #[test]
403 fn mime_len_handles_basic_type() {
404 assert_eq!(constants::TEXT_PLAIN.len(), "text/plain".len());
405 }
406
407 #[test]
408 fn mime_len_handles_suffix() {
409 assert_eq!(constants::IMAGE_SVG_XML.len(), "image/svg+xml".len());
410 }
411
412 #[test]
413 fn mime_len_handles_param() {
414 assert_eq!(
415 Mime::parse("text/html; charset=utf-8").unwrap().len(),
416 "text/html;charset=utf-8".len()
417 );
418 }
419
420 #[test]
421 fn mime_len_handles_multiple_params() {
422 assert_eq!(
423 Mime::parse("text/html; charset=utf-8; foo=bar")
424 .unwrap()
425 .len(),
426 "text/html;charset=utf-8;foo=bar".len()
427 );
428 }
429
430 #[test]
431 fn mime_len_handles_suffixes_and_params() {
432 assert_eq!(
433 Mime::parse("image/svg+xml; charset=utf-8; foo=bar")
434 .unwrap()
435 .len(),
436 "image/svg+xml;charset=utf-8;foo=bar".len()
437 );
438 }
439}
440
441impl Mime<'static> {
442 pub fn guess(extension: &str) -> impl ExactSizeIterator<Item = Mime<'static>> + FusedIterator {
461 segments::guess_mime_type(extension)
462 .unwrap_or(&[])
463 .iter()
464 .map(|&c| c)
465 }
466}
467
468impl PartialEq<str> for Mime<'_> {
469 fn eq(&self, mut other: &str) -> bool {
479 let ty = self.r#type().into_str();
481 let ty_len = ty.len();
482
483 if !other.starts_with(ty) {
484 return false;
485 }
486
487 if other.as_bytes()[ty_len] != b'/' {
489 return false;
490 }
491
492 other = &other[ty_len + 1..];
494 let subtype = self.subtype().into_str();
495 let subtype_len = subtype.len();
496
497 if !other.starts_with(subtype) {
498 return false;
499 }
500
501 if let Some(suffix) = self.suffix() {
503 let suffix = suffix.into_str();
504 if other.as_bytes()[subtype_len] != b'+' {
505 return false;
506 }
507
508 other = &other[subtype_len + 1..];
510 let suffix_len = suffix.len();
511
512 if !other.starts_with(suffix) {
513 return false;
514 }
515
516 other = &other[suffix_len..];
517 } else {
518 other = &other[subtype_len..];
519 }
520
521 for (key, value) in self.parameters() {
523 if other.as_bytes()[0] != b';' {
525 return false;
526 }
527
528 other = &other[1..];
530 let key_len = key.len();
531
532 if !other.eq_ignore_ascii_case(key) {
533 return false;
534 }
535
536 if other.as_bytes()[key_len] != b'=' {
538 return false;
539 }
540
541 other = &other[key_len + 1..];
543 let value_len = value.len();
544
545 if &other.as_bytes()[..value_len] != value {
546 return false;
547 }
548
549 other = &other[value_len..];
551 }
552
553 true
554 }
555}
556
557impl<'a, 'b> PartialEq<&'a str> for Mime<'b> {
558 fn eq(&self, other: &&'a str) -> bool {
559 self.eq(*other)
560 }
561}
562
563impl<'a, 'b> PartialEq<Mime<'a>> for Mime<'b> {
564 fn eq(&self, other: &Mime<'a>) -> bool {
565 (self.r#type() == other.r#type())
567 .and_then(|| self.subtype() == other.subtype())
568 .and_then(|| self.suffix() == other.suffix())
569 .and_then(|| {
570 cmp_params_ignore_case(self.parameters(), other.parameters())
571 == cmp::Ordering::Equal
572 })
573 }
574}
575
576impl<'a> Eq for Mime<'a> {}
577
578impl<'a, 'b> PartialOrd<Mime<'a>> for Mime<'b> {
579 fn partial_cmp(&self, other: &Mime<'a>) -> Option<cmp::Ordering> {
580 Some(self.cmp(other))
581 }
582}
583
584impl<'a> Ord for Mime<'a> {
585 fn cmp(&self, other: &Self) -> cmp::Ordering {
586 self.r#type()
587 .cmp(&other.r#type())
588 .and_then(|| self.subtype().cmp(&other.subtype()))
589 .and_then(|| self.suffix().cmp(&other.suffix()))
590 .and_then(|| cmp_params_ignore_case(self.parameters(), other.parameters()))
591 }
592}
593
594impl<'a> Hash for Mime<'a> {
595 fn hash<H: Hasher>(&self, state: &mut H) {
596 self.r#type().hash(state);
597 self.subtype().hash(state);
598 self.suffix().hash(state);
599 for (key, value) in self.parameters() {
600 hash_ignore_case(key, state);
601 value.hash(state);
602 }
603 }
604}
605
606macro_rules! name_wrappers {
608 (
609 $(
610 $(#[$outer:meta])*
611 $name: ident <'a> => Name<'a, $ty: ty>
612 ),* $(,)?
613 ) => {
614 $(
615 $(#[$outer])*
616 #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
617 pub struct $name <'a> ( Name<'a, $ty> );
618
619 impl<'a> $name<'a> {
620 pub fn new(s: &'a str) -> Option<Self> {
622 Name::new(s).map($name)
623 }
624
625 pub fn from_bytes(s: &'a [u8]) -> Option<Self> {
627 Name::from_bytes(s).map($name)
628 }
629
630 pub fn into_str(self) -> &'a str {
632 self.0.into_str()
633 }
634 }
635
636 impl fmt::Debug for $name <'_> {
637 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
638 f.debug_tuple(stringify!($name))
639 .field(&self.0.into_str())
640 .finish()
641 }
642 }
643
644 impl fmt::Display for $name <'_> {
645 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
646 f.write_str(self.0.into_str())
647 }
648 }
649
650 impl AsRef<str> for $name <'_> {
651 fn as_ref(&self) -> &str {
652 self.0.into_str()
653 }
654 }
655
656 impl<'a> From<Name<'a, $ty>> for $name<'a> {
657 fn from(name: Name<'a, $ty>) -> Self {
658 $name(name)
659 }
660 }
661
662 impl PartialEq<&str> for $name<'_> {
663 fn eq(&self, other: &&str) -> bool {
664 self.0.into_str().eq_ignore_ascii_case(other)
665 }
666 }
667
668 impl<'a> TryFrom<&'a str> for $name<'a> {
669 type Error = ParseError;
670
671 fn try_from(s: &'a str) -> Result<Self, Self::Error> {
672 Self::new(s).ok_or(ParseError::NonHttpCodepoints)
673 }
674 }
675
676 impl<'a> TryFrom<&'a [u8]> for $name<'a> {
677 type Error = ParseError;
678
679 fn try_from(s: &'a [u8]) -> Result<Self, Self::Error> {
680 Self::from_bytes(s).ok_or(ParseError::NonHttpCodepoints)
681 }
682 }
683 )*
684 }
685}
686
687name_wrappers! {
688 Type<'a> => Name<'a, TypeIntern>,
690 Subtype<'a> => Name<'a, SubtypeIntern>,
692 Suffix<'a> => Name<'a, SuffixIntern>
694}
695
696#[derive(Clone, Copy)]
698enum Parameters<'a> {
699 Slice(&'a [(&'a str, &'a [u8])]),
701
702 Buffer(&'a [u8]),
704}
705
706#[derive(Debug, Clone, Copy)]
708enum Name<'a, Intern> {
709 Interned(Intern),
711
712 Dynamic(&'a str),
714}
715
716impl<'a, T: Into<&'static str>> Name<'a, T> {
717 fn into_str(self) -> &'a str {
718 match self {
719 Name::Interned(interned) => interned.into(),
720 Name::Dynamic(dynamic) => dynamic,
721 }
722 }
723}
724
725impl<'a, T> From<T> for Name<'a, T> {
726 fn from(item: T) -> Self {
727 Name::Interned(item)
728 }
729}
730
731impl<'a, T: AsRef<str>> AsRef<str> for Name<'a, T> {
732 fn as_ref(&self) -> &str {
733 match self {
734 Name::Interned(interned) => interned.as_ref(),
735 Name::Dynamic(dynamic) => dynamic,
736 }
737 }
738}
739
740impl<'a, T: TryFrom<&'a [u8]>> Name<'a, T> {
741 fn from_bytes(name: &'a [u8]) -> Option<Self> {
742 match name.try_into() {
743 Ok(interned) => Some(Name::Interned(interned)),
744 Err(_) => {
745 if !name.iter().all(|&c| is_http_codepoint(c)) {
747 return None;
748 }
749
750 Some(Name::Dynamic(from_utf8(name).unwrap()))
752 }
753 }
754 }
755
756 fn new(name: &'a str) -> Option<Self> {
757 Self::from_bytes(name.as_bytes())
758 }
759}
760
761impl<'a, T: AsRef<str> + PartialEq> PartialEq for Name<'a, T> {
762 fn eq(&self, other: &Self) -> bool {
763 match (self, other) {
764 (Name::Interned(this), Name::Interned(other)) => this == other,
765 (Name::Dynamic(s), Name::Interned(i)) | (Name::Interned(i), Name::Dynamic(s)) => {
766 s.eq_ignore_ascii_case(i.as_ref())
767 }
768 (Name::Dynamic(this), Name::Dynamic(other)) => this.eq_ignore_ascii_case(other),
769 }
770 }
771}
772
773impl<'a, T: AsRef<str> + Eq> Eq for Name<'a, T> {}
774
775impl<'a, T: AsRef<str> + PartialOrd> PartialOrd for Name<'a, T> {
777 fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
778 match (self, other) {
779 (Name::Interned(this), Name::Interned(other)) => this.partial_cmp(other),
780 (Name::Dynamic(s), Name::Interned(i)) | (Name::Interned(i), Name::Dynamic(s)) => {
781 Some(cmp_str_ignore_case(s, i.as_ref()))
782 }
783 (Name::Dynamic(this), Name::Dynamic(other)) => Some(cmp_str_ignore_case(this, other)),
784 }
785 }
786}
787
788impl<'a, T: AsRef<str> + Ord> Ord for Name<'a, T> {
789 fn cmp(&self, other: &Self) -> core::cmp::Ordering {
790 match (self, other) {
791 (Name::Interned(this), Name::Interned(other)) => this.cmp(other),
792 (Name::Dynamic(s), Name::Interned(i)) | (Name::Interned(i), Name::Dynamic(s)) => {
793 cmp_str_ignore_case(s, i.as_ref())
794 }
795 (Name::Dynamic(this), Name::Dynamic(other)) => cmp_str_ignore_case(this, other),
796 }
797 }
798}
799
800impl<'a, T: AsRef<str> + Hash> Hash for Name<'a, T> {
801 fn hash<H: Hasher>(&self, state: &mut H) {
802 hash_ignore_case(self.as_ref(), state)
803 }
804}
805
806fn parameter_iter(bytes: &[u8]) -> impl Iterator<Item = (&[u8], &[u8])> {
810 ParameterIter { bytes }
811}
812
813struct ParameterIter<'a> {
814 bytes: &'a [u8],
816}
817
818impl<'a> Iterator for ParameterIter<'a> {
819 type Item = (&'a [u8], &'a [u8]);
820
821 fn next(&mut self) -> Option<Self::Item> {
822 loop {
823 if self.bytes.is_empty() {
824 return None;
825 }
826
827 let posn = memchr2(b';', b'=', self.bytes).unwrap_or(self.bytes.len());
829
830 let name = trim_start(&self.bytes[..posn]);
832 let symbol = self.bytes.get(posn);
833 self.bytes = self.bytes.get(posn + 1..).unwrap_or(&[]);
834
835 if let Some(b';') | None = symbol {
836 if name.is_empty() {
839 continue;
841 }
842
843 return Some((name, &[]));
844 }
845
846 if let Some(b'"') = self.bytes.first() {
848 let mut start = 1;
849 loop {
850 let posn =
852 memchr2(b'"', b'\\', &self.bytes[start..]).unwrap_or(self.bytes.len());
853
854 match self.bytes.get(posn) {
856 Some(b'"') | None => {
857 let value = &self.bytes[1..posn];
859 self.bytes = self.bytes.get(posn + 1..).unwrap_or(&[]);
860 return Some((name, value));
861 }
862 Some(b'\\') => {
863 start = posn + 2;
865 }
866 _ => unreachable!(),
867 }
868 }
869 } else {
870 let posn = memchr(b';', self.bytes).unwrap_or(self.bytes.len());
872 let value = &self.bytes[..posn];
873 self.bytes = self.bytes.get(posn + 1..).unwrap_or(&[]);
874
875 return Some((name, value));
876 }
877 }
878 }
879}
880
881fn cmp_str_ignore_case(a: &str, b: &str) -> cmp::Ordering {
883 let common_len = cmp::min(a.len(), b.len());
884
885 let a_part = &a[..common_len];
887 let b_part = &b[..common_len];
888
889 for (ac, bc) in a_part.chars().zip(b_part.chars()) {
891 let ac = ac.to_ascii_lowercase();
892 let bc = bc.to_ascii_lowercase();
893
894 match ac.cmp(&bc) {
895 cmp::Ordering::Equal => continue,
896 other => return other,
897 }
898 }
899
900 a.len().cmp(&b.len())
902}
903
904fn cmp_params_ignore_case<'a, 'b, 'c, 'd>(
906 left: impl Iterator<Item = (&'a str, &'b [u8])>,
907 right: impl Iterator<Item = (&'c str, &'d [u8])>,
908) -> cmp::Ordering {
909 let mut left = left.fuse();
910 let mut right = right.fuse();
911
912 for (left, right) in left.by_ref().zip(right.by_ref()) {
913 match cmp_str_ignore_case(left.0, right.0) {
914 cmp::Ordering::Equal => {}
915 other => return other,
916 }
917
918 match left.1.cmp(right.1) {
919 cmp::Ordering::Equal => {}
920 other => return other,
921 }
922 }
923
924 if left.next().is_some() {
925 cmp::Ordering::Greater
926 } else if right.next().is_some() {
927 cmp::Ordering::Less
928 } else {
929 cmp::Ordering::Equal
930 }
931}
932
933fn hash_ignore_case(a: &str, state: &mut impl Hasher) {
935 #[cfg(feature = "alloc")]
936 use alloc::string::String;
937
938 const MAX_LEN: usize = 128;
940
941 let mut stack_space = [0u8; MAX_LEN];
943 #[cfg(feature = "alloc")]
944 let mut heap_space;
945
946 let copied_str = if a.len() > MAX_LEN {
947 #[cfg(not(feature = "alloc"))]
948 panic!("MIME type string cannot be hashed longer than 128 characters");
949
950 #[cfg(feature = "alloc")]
951 {
952 heap_space = String::from(a);
953 &mut heap_space
954 }
955 } else {
956 stack_space[..a.len()].copy_from_slice(a.as_bytes());
957 core::str::from_utf8_mut(&mut stack_space[..a.len()]).unwrap()
958 };
959
960 copied_str.make_ascii_lowercase();
961
962 copied_str.hash(state);
964}
965
966fn is_http_codepoint(b: u8) -> bool {
968 matches!(
969 b,
970 b'!'
971 | b'#'
972 | b'$'
973 | b'%'
974 | b'&'
975 | b'\''
976 | b'*'
977 | b'+'
978 | b'-'
979 | b'.'
980 | b'^'
981 | b'_'
982 | b'`'
983 | b'|'
984 | b'~'
985 | b'a'..=b'z'
986 | b'A'..=b'Z'
987 | b'0'..=b'9'
988 )
989}
990
991fn is_http_whitespace(b: u8) -> bool {
993 matches!(b, b' ' | b'\t' | b'\r' | b'\n')
994}
995
996fn is_http_quoted_codepoint(b: u8) -> bool {
998 matches!(b, b'\t' | b' '..=b'~' | 0x80..=0xFF)
999}
1000
1001fn trim_start(mut s: &[u8]) -> &[u8] {
1003 while let Some((b, rest)) = s.split_first() {
1004 if !is_http_whitespace(*b) {
1005 break;
1006 }
1007
1008 s = rest;
1009 }
1010
1011 s
1012}
1013
1014fn trim_end(mut s: &[u8]) -> &[u8] {
1016 while let Some((b, rest)) = s.split_last() {
1017 if !is_http_whitespace(*b) {
1018 break;
1019 }
1020
1021 s = rest;
1022 }
1023
1024 s
1025}
1026
1027trait Comparison: Sized {
1029 fn and_then(self, other: impl FnOnce() -> Self) -> Self;
1031}
1032
1033impl Comparison for bool {
1034 fn and_then(self, other: impl FnOnce() -> Self) -> Self {
1035 match self {
1036 true => other(),
1037 false => false,
1038 }
1039 }
1040}
1041
1042impl Comparison for Option<cmp::Ordering> {
1043 fn and_then(self, other: impl FnOnce() -> Self) -> Self {
1044 match self {
1045 Some(cmp::Ordering::Greater) => other(),
1046 this => this,
1047 }
1048 }
1049}
1050
1051impl Comparison for cmp::Ordering {
1052 fn and_then(self, other: impl FnOnce() -> Self) -> Self {
1053 if let cmp::Ordering::Equal = self {
1054 other()
1055 } else {
1056 self
1057 }
1058 }
1059}
1060
1061#[derive(Debug, PartialEq, Eq, Clone, Copy)]
1063struct InvalidName;
1064
1065#[derive(Debug)]
1066enum Either<A, B> {
1067 Left(A),
1068 Right(B),
1069}
1070
1071impl<A, B> Iterator for Either<A, B>
1072where
1073 A: Iterator,
1074 B: Iterator<Item = A::Item>,
1075{
1076 type Item = A::Item;
1077
1078 fn next(&mut self) -> Option<Self::Item> {
1079 match self {
1080 Either::Left(a) => a.next(),
1081 Either::Right(b) => b.next(),
1082 }
1083 }
1084
1085 fn size_hint(&self) -> (usize, Option<usize>) {
1086 match self {
1087 Either::Left(a) => a.size_hint(),
1088 Either::Right(b) => b.size_hint(),
1089 }
1090 }
1091
1092 fn fold<Closure, F>(self, init: Closure, f: F) -> Closure
1093 where
1094 Self: Sized,
1095 F: FnMut(Closure, Self::Item) -> Closure,
1096 {
1097 match self {
1098 Either::Left(a) => a.fold(init, f),
1099 Either::Right(b) => b.fold(init, f),
1100 }
1101 }
1102
1103 fn nth(&mut self, n: usize) -> Option<Self::Item> {
1104 match self {
1105 Either::Left(a) => a.nth(n),
1106 Either::Right(b) => b.nth(n),
1107 }
1108 }
1109
1110 fn last(self) -> Option<Self::Item>
1111 where
1112 Self: Sized,
1113 {
1114 match self {
1115 Either::Left(a) => a.last(),
1116 Either::Right(b) => b.last(),
1117 }
1118 }
1119}
1120
1121impl<A, B> FusedIterator for Either<A, B>
1122where
1123 A: FusedIterator,
1124 B: FusedIterator<Item = A::Item>,
1125{
1126}
1127
1128impl<A, B> ExactSizeIterator for Either<A, B>
1129where
1130 A: ExactSizeIterator,
1131 B: ExactSizeIterator<Item = A::Item>,
1132{
1133}
1134
1135impl<A, B> DoubleEndedIterator for Either<A, B>
1136where
1137 A: DoubleEndedIterator,
1138 B: DoubleEndedIterator<Item = A::Item>,
1139{
1140 fn next_back(&mut self) -> Option<Self::Item> {
1141 match self {
1142 Either::Left(a) => a.next_back(),
1143 Either::Right(b) => b.next_back(),
1144 }
1145 }
1146
1147 fn rfold<Closure, F>(self, init: Closure, f: F) -> Closure
1148 where
1149 Self: Sized,
1150 F: FnMut(Closure, Self::Item) -> Closure,
1151 {
1152 match self {
1153 Either::Left(a) => a.rfold(init, f),
1154 Either::Right(b) => b.rfold(init, f),
1155 }
1156 }
1157
1158 fn nth_back(&mut self, n: usize) -> Option<Self::Item> {
1159 match self {
1160 Either::Left(a) => a.nth_back(n),
1161 Either::Right(b) => b.nth_back(n),
1162 }
1163 }
1164}
1165
1166struct FormatQuotedString<'a>(&'a [u8]);
1171
1172impl<'a> FormatQuotedString<'a> {
1173 pub fn len(&self) -> usize {
1174 self.0
1175 .iter()
1176 .flat_map(|&c| core::ascii::escape_default(c))
1177 .count()
1178 }
1179}
1180
1181impl<'a> fmt::Display for FormatQuotedString<'a> {
1182 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1183 for ch in self.0.iter().flat_map(|&c| core::ascii::escape_default(c)) {
1184 f.write_char(ch as char)?;
1185 }
1186
1187 Ok(())
1188 }
1189}
1190
1191impl<'a> fmt::Debug for FormatQuotedString<'a> {
1192 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1193 fmt::Display::fmt(self, f)
1194 }
1195}
1196
1197#[cfg(test)]
1198mod fqs_test {
1199 use super::*;
1200
1201 #[test]
1202 fn fqs_len_handles_empty_array() {
1203 assert_eq!(FormatQuotedString(&[]).len(), 0);
1204 }
1205
1206 #[test]
1207 fn fqs_len_handles_quoted_string() {
1208 let input =
1209 b"this%20is%20http%20encoded%E2%80%A6%20or%20is%20it%3F%20%C5%B6%C4%99%C5%A1%20it%20is";
1210 assert_eq!(FormatQuotedString(input).len(), 84);
1211 }
1212
1213 #[test]
1214 fn fqs_len_handles_standard_ascii() {
1215 let input = b"this is not encoded or special at all";
1216 assert_eq!(FormatQuotedString(input).len(), 37);
1217 }
1218
1219 #[test]
1220 fn fqs_len_handles_utf8() {
1221 let input = b"\xC5\xB6'\"\\";
1222 assert_eq!(FormatQuotedString(input).len(), 14);
1223 }
1224}