#![no_std]
#![forbid(
unsafe_code,
future_incompatible,
missing_copy_implementations,
missing_debug_implementations,
missing_docs
)]
#![allow(clippy::map_clone)]
#[cfg(all(feature = "alloc", not(mr_mime_no_alloc)))]
extern crate alloc;
#[cfg(all(feature = "alloc", mr_mime_no_alloc))]
extern crate std as alloc;
#[cfg(feature = "std")]
extern crate std;
#[rustfmt::ignore]
mod segments;
pub use segments::constants;
use segments::{SubtypeIntern, SuffixIntern, TypeIntern};
use core::cell::Cell;
use core::cmp;
use core::fmt;
use core::hash::{Hash, Hasher};
use core::iter::FusedIterator;
use core::str::FromStr;
use core::write;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(not(mr_mime_no_non_exhaustive), non_exhaustive)]
pub enum ParseError {
NoSlash,
MissingType,
MissingSubtype,
#[cfg(mr_mime_no_non_exhaustive)]
#[doc(hidden)]
NonExhaustive,
}
impl fmt::Display for ParseError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ParseError::NoSlash => write!(f, "no slash in MIME type"),
ParseError::MissingType => write!(f, "missing MIME type"),
ParseError::MissingSubtype => write!(f, "missing MIME subtype"),
#[cfg(mr_mime_no_non_exhaustive)]
ParseError::NonExhaustive => unreachable!(),
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for ParseError {}
#[derive(Clone, Copy)]
pub struct Mime<'a>(Repr<'a>);
impl<'a> fmt::Display for Mime<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}/{}", self.r#type(), self.subtype())?;
if let Some(suffix) = self.suffix() {
write!(f, "+{}", suffix)?;
}
for (key, value) in self.parameters() {
write!(f, ";{}={}", key, value)?;
}
Ok(())
}
}
impl<'a> fmt::Debug for Mime<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
struct Parameters<I>(Cell<Option<I>>);
impl<'a, 'b, I: Iterator<Item = (&'a str, &'b str)>> fmt::Debug for Parameters<I> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let iter = self.0.take().unwrap();
f.debug_map().entries(iter).finish()
}
}
f.debug_struct("Mime")
.field("type", &self.r#type())
.field("subtype", &self.subtype())
.field("suffix", &self.suffix())
.field(
"parameters",
&Parameters(Cell::new(Some(self.parameters()))),
)
.finish()
}
}
impl<'a> Mime<'a> {
pub fn new(
ty: Type<'a>,
subtype: Subtype<'a>,
suffix: Option<Suffix<'a>>,
parameters: &'a [(&'a str, &'a str)],
) -> Self {
Self(Repr::Parts {
ty: ty.0,
subtype: subtype.0,
suffix: suffix.map(|s| s.0),
parameters,
})
}
pub fn parse(source: &'a str) -> Result<Self, ParseError> {
let slash = source.find('/').ok_or(ParseError::NoSlash)?;
let plus = source.find('+');
let semicolon = source.find(';');
if slash == 0 {
return Err(ParseError::MissingType);
} else if slash == source.len() - 1 {
return Err(ParseError::MissingSubtype);
}
if let Some(semicolon) = semicolon {
Ok(Self(Repr::Buffer {
buffer: source,
slash,
plus,
semicolon,
}))
} else {
Ok(Self(Repr::Parts {
ty: Name::new(&source[..slash]),
subtype: Name::new(&source[&slash + 1..plus.unwrap_or(source.len())]),
suffix: plus.map(|plus| Name::new(&source[plus + 1..])),
parameters: &[],
}))
}
}
pub fn r#type(&self) -> Type<'_> {
self.type_name().into()
}
pub fn subtype(&self) -> Subtype<'_> {
self.subtype_name().into()
}
pub fn suffix(&self) -> Option<Suffix<'_>> {
self.suffix_name().map(|s| s.into())
}
pub fn parameters(&self) -> impl DoubleEndedIterator<Item = (&str, &str)> + FusedIterator {
match self.0 {
Repr::Parts { parameters, .. } => Either::Left(parameters.iter().map(|&c| c)),
Repr::Buffer {
buffer, semicolon, ..
} => Either::Right({
let semicolons = buffer[semicolon + 1..].split(';');
semicolons.map(|semicolon| {
let mut parts = semicolon.split('=');
let key = parts.next().unwrap().trim();
let value = parts.next().unwrap().trim();
(key, value)
})
}),
}
}
pub fn essence(&self) -> Mime<'a> {
match self.0 {
Repr::Parts { ty, subtype, .. } => Mime(Repr::Parts {
ty,
subtype,
suffix: None,
parameters: &[],
}),
Repr::Buffer {
buffer,
slash,
plus,
semicolon,
} => {
let end = plus.unwrap_or(semicolon);
Self::new(
Type::new(&buffer[..slash]),
Subtype::new(&buffer[slash + 1..end]),
None,
&[],
)
}
}
}
fn type_name(&self) -> Name<'a, TypeIntern> {
match self.0 {
Repr::Parts { ty, .. } => ty,
Repr::Buffer { buffer, slash, .. } => Name::Dynamic(&buffer[..slash]),
}
}
fn subtype_name(&self) -> Name<'a, SubtypeIntern> {
match self.0 {
Repr::Parts { subtype, .. } => subtype,
Repr::Buffer {
buffer,
slash,
plus,
semicolon,
} => {
let end = plus.unwrap_or(semicolon);
Name::Dynamic(&buffer[slash + 1..end])
}
}
}
fn suffix_name(&self) -> Option<Name<'a, SuffixIntern>> {
match self.0 {
Repr::Parts { suffix, .. } => suffix,
Repr::Buffer {
buffer,
plus,
semicolon,
..
} => {
let end = semicolon;
plus.map(|plus| Name::Dynamic(&buffer[plus + 1..end]))
}
}
}
}
impl Mime<'static> {
pub fn guess(extension: &str) -> impl ExactSizeIterator<Item = Mime<'static>> + FusedIterator {
segments::guess_mime_type(extension)
.unwrap_or(&[])
.iter()
.map(|&c| c)
}
}
impl<'a, 'b> PartialEq<&'a str> for Mime<'b> {
fn eq(&self, other: &&'a str) -> bool {
let mut other = *other;
let ty = self.r#type().into_str();
let ty_len = ty.len();
if !other.starts_with(ty) {
return false;
}
if other.as_bytes()[ty_len] != b'/' {
return false;
}
other = &other[ty_len + 1..];
let subtype = self.subtype().into_str();
let subtype_len = subtype.len();
if !other.starts_with(subtype) {
return false;
}
if let Some(suffix) = self.suffix() {
let suffix = suffix.into_str();
if other.as_bytes()[subtype_len] != b'+' {
return false;
}
other = &other[subtype_len + 1..];
let suffix_len = suffix.len();
if !other.starts_with(suffix) {
return false;
}
other = &other[suffix_len..];
} else {
other = &other[subtype_len..];
}
for (key, value) in self.parameters() {
if other.as_bytes()[0] != b';' {
return false;
}
other = &other[1..];
let key_len = key.len();
if !other.eq_ignore_ascii_case(key) {
return false;
}
if other.as_bytes()[key_len] != b'=' {
return false;
}
other = &other[key_len + 1..];
let value_len = value.len();
if other != value {
return false;
}
other = &other[value_len..];
}
true
}
}
impl<'a, 'b> PartialEq<Mime<'a>> for Mime<'b> {
fn eq(&self, other: &Mime<'a>) -> bool {
(self.type_name() == other.type_name())
.and_then(|| self.subtype_name() == other.subtype_name())
.and_then(|| self.suffix_name() == other.suffix_name())
.and_then(|| {
cmp_params_ignore_case(self.parameters(), other.parameters())
== cmp::Ordering::Equal
})
}
}
impl<'a> Eq for Mime<'a> {}
impl<'a, 'b> PartialOrd<Mime<'a>> for Mime<'b> {
fn partial_cmp(&self, other: &Mime<'a>) -> Option<cmp::Ordering> {
Some(self.cmp(other))
}
}
impl<'a> Ord for Mime<'a> {
fn cmp(&self, other: &Self) -> cmp::Ordering {
self.type_name()
.cmp(&other.type_name())
.and_then(|| self.subtype_name().cmp(&other.subtype_name()))
.and_then(|| self.suffix_name().cmp(&other.suffix_name()))
.and_then(|| cmp_params_ignore_case(self.parameters(), other.parameters()))
}
}
impl<'a> Hash for Mime<'a> {
fn hash<H: Hasher>(&self, state: &mut H) {
self.type_name().hash(state);
self.subtype_name().hash(state);
self.suffix_name().hash(state);
for (key, value) in self.parameters() {
hash_ignore_case(key, state);
value.hash(state);
}
}
}
macro_rules! name_wrappers {
(
$(
$(#[$outer:meta])*
$name: ident <'a> => Name<'a, $ty: ty>
),* $(,)?
) => {
$(
$(#[$outer])*
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct $name <'a> ( Name<'a, $ty> );
impl<'a> $name<'a> {
pub fn new(s: &'a str) -> Self {
$name(Name::new(s))
}
pub fn into_str(self) -> &'a str {
self.0.into_str()
}
}
impl fmt::Debug for $name <'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_tuple(stringify!($name))
.field(&self.0.into_str())
.finish()
}
}
impl fmt::Display for $name <'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.0.into_str())
}
}
impl AsRef<str> for $name <'_> {
fn as_ref(&self) -> &str {
self.0.into_str()
}
}
impl<'a> From<Name<'a, $ty>> for $name<'a> {
fn from(name: Name<'a, $ty>) -> Self {
$name(name)
}
}
impl PartialEq<&str> for $name<'_> {
fn eq(&self, other: &&str) -> bool {
self.0.into_str().eq_ignore_ascii_case(other)
}
}
impl<'a> From<&'a str> for $name<'a> {
fn from(s: &'a str) -> Self {
Self::new(s)
}
}
)*
}
}
name_wrappers! {
Type<'a> => Name<'a, TypeIntern>,
Subtype<'a> => Name<'a, SubtypeIntern>,
Suffix<'a> => Name<'a, SuffixIntern>
}
#[derive(Clone, Copy)]
enum Repr<'a> {
Parts {
ty: Name<'a, TypeIntern>,
subtype: Name<'a, SubtypeIntern>,
suffix: Option<Name<'a, SuffixIntern>>,
parameters: &'a [(&'a str, &'a str)],
},
Buffer {
buffer: &'a str,
slash: usize,
plus: Option<usize>,
semicolon: usize,
},
}
#[derive(Debug, Clone, Copy)]
enum Name<'a, Intern> {
Interned(Intern),
Dynamic(&'a str),
}
impl<'a, T: Into<&'static str>> Name<'a, T> {
fn into_str(self) -> &'a str {
match self {
Name::Interned(interned) => interned.into(),
Name::Dynamic(dynamic) => dynamic,
}
}
}
impl<'a, T> From<T> for Name<'a, T> {
fn from(item: T) -> Self {
Name::Interned(item)
}
}
impl<'a, T: AsRef<str>> AsRef<str> for Name<'a, T> {
fn as_ref(&self) -> &str {
match self {
Name::Interned(interned) => interned.as_ref(),
Name::Dynamic(dynamic) => dynamic,
}
}
}
impl<'a, T: FromStr<Err = InvalidName>> Name<'a, T> {
fn new(name: &'a str) -> Self {
match name.parse::<T>() {
Ok(interned) => Name::Interned(interned),
Err(_) => Name::Dynamic(name),
}
}
}
impl<'a, T: AsRef<str> + PartialEq> PartialEq for Name<'a, T> {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Name::Interned(this), Name::Interned(other)) => this == other,
(Name::Dynamic(s), Name::Interned(i)) | (Name::Interned(i), Name::Dynamic(s)) => {
s.eq_ignore_ascii_case(i.as_ref())
}
(Name::Dynamic(this), Name::Dynamic(other)) => this.eq_ignore_ascii_case(other),
}
}
}
impl<'a, T: AsRef<str> + Eq> Eq for Name<'a, T> {}
impl<'a, T: AsRef<str> + PartialOrd> PartialOrd for Name<'a, T> {
fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
match (self, other) {
(Name::Interned(this), Name::Interned(other)) => this.partial_cmp(other),
(Name::Dynamic(s), Name::Interned(i)) | (Name::Interned(i), Name::Dynamic(s)) => {
Some(cmp_str_ignore_case(s, i.as_ref()))
}
(Name::Dynamic(this), Name::Dynamic(other)) => Some(cmp_str_ignore_case(this, other)),
}
}
}
impl<'a, T: AsRef<str> + Ord> Ord for Name<'a, T> {
fn cmp(&self, other: &Self) -> core::cmp::Ordering {
match (self, other) {
(Name::Interned(this), Name::Interned(other)) => this.cmp(other),
(Name::Dynamic(s), Name::Interned(i)) | (Name::Interned(i), Name::Dynamic(s)) => {
cmp_str_ignore_case(s, i.as_ref())
}
(Name::Dynamic(this), Name::Dynamic(other)) => cmp_str_ignore_case(this, other),
}
}
}
impl<'a, T: AsRef<str> + Hash> Hash for Name<'a, T> {
fn hash<H: Hasher>(&self, state: &mut H) {
hash_ignore_case(self.as_ref(), state)
}
}
fn cmp_str_ignore_case(a: &str, b: &str) -> cmp::Ordering {
let common_len = cmp::min(a.len(), b.len());
let a_part = &a[..common_len];
let b_part = &b[..common_len];
for (ac, bc) in a_part.chars().zip(b_part.chars()) {
let ac = ac.to_ascii_lowercase();
let bc = bc.to_ascii_lowercase();
match ac.cmp(&bc) {
cmp::Ordering::Equal => continue,
other => return other,
}
}
a.len().cmp(&b.len())
}
fn cmp_params_ignore_case<'a, 'b, 'c, 'd>(
left: impl Iterator<Item = (&'a str, &'b str)>,
right: impl Iterator<Item = (&'c str, &'d str)>,
) -> cmp::Ordering {
let mut left = left.fuse();
let mut right = right.fuse();
for (left, right) in left.by_ref().zip(right.by_ref()) {
match cmp_str_ignore_case(left.0, right.0) {
cmp::Ordering::Equal => {}
other => return other,
}
match left.1.cmp(right.1) {
cmp::Ordering::Equal => {}
other => return other,
}
}
if left.next().is_some() {
cmp::Ordering::Greater
} else if right.next().is_some() {
cmp::Ordering::Less
} else {
cmp::Ordering::Equal
}
}
fn hash_ignore_case(a: &str, state: &mut impl Hasher) {
#[cfg(feature = "alloc")]
use alloc::string::String;
const MAX_LEN: usize = 128;
let mut stack_space = [0u8; MAX_LEN];
#[cfg(feature = "alloc")]
let mut heap_space;
let copied_str = if a.len() > MAX_LEN {
#[cfg(not(feature = "alloc"))]
panic!("MIME type string cannot be hashed longer than 128 characters");
#[cfg(feature = "alloc")]
{
heap_space = String::from(a);
&mut heap_space
}
} else {
stack_space[..a.len()].copy_from_slice(a.as_bytes());
core::str::from_utf8_mut(&mut stack_space[..a.len()]).unwrap()
};
copied_str.make_ascii_lowercase();
copied_str.hash(state);
}
trait Comparison: Sized {
fn and_then(self, other: impl FnOnce() -> Self) -> Self;
}
impl Comparison for bool {
fn and_then(self, other: impl FnOnce() -> Self) -> Self {
match self {
true => other(),
false => false,
}
}
}
impl Comparison for Option<cmp::Ordering> {
fn and_then(self, other: impl FnOnce() -> Self) -> Self {
match self {
Some(cmp::Ordering::Greater) => other(),
this => this,
}
}
}
impl Comparison for cmp::Ordering {
fn and_then(self, other: impl FnOnce() -> Self) -> Self {
if let cmp::Ordering::Equal = self {
other()
} else {
self
}
}
}
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
struct InvalidName;
#[derive(Debug)]
enum Either<A, B> {
Left(A),
Right(B),
}
impl<A, B> Iterator for Either<A, B>
where
A: Iterator,
B: Iterator<Item = A::Item>,
{
type Item = A::Item;
fn next(&mut self) -> Option<Self::Item> {
match self {
Either::Left(a) => a.next(),
Either::Right(b) => b.next(),
}
}
fn size_hint(&self) -> (usize, Option<usize>) {
match self {
Either::Left(a) => a.size_hint(),
Either::Right(b) => b.size_hint(),
}
}
fn fold<Closure, F>(self, init: Closure, f: F) -> Closure
where
Self: Sized,
F: FnMut(Closure, Self::Item) -> Closure,
{
match self {
Either::Left(a) => a.fold(init, f),
Either::Right(b) => b.fold(init, f),
}
}
fn nth(&mut self, n: usize) -> Option<Self::Item> {
match self {
Either::Left(a) => a.nth(n),
Either::Right(b) => b.nth(n),
}
}
fn last(self) -> Option<Self::Item>
where
Self: Sized,
{
match self {
Either::Left(a) => a.last(),
Either::Right(b) => b.last(),
}
}
}
impl<A, B> FusedIterator for Either<A, B>
where
A: FusedIterator,
B: FusedIterator<Item = A::Item>,
{
}
impl<A, B> ExactSizeIterator for Either<A, B>
where
A: ExactSizeIterator,
B: ExactSizeIterator<Item = A::Item>,
{
}
impl<A, B> DoubleEndedIterator for Either<A, B>
where
A: DoubleEndedIterator,
B: DoubleEndedIterator<Item = A::Item>,
{
fn next_back(&mut self) -> Option<Self::Item> {
match self {
Either::Left(a) => a.next_back(),
Either::Right(b) => b.next_back(),
}
}
fn rfold<Closure, F>(self, init: Closure, f: F) -> Closure
where
Self: Sized,
F: FnMut(Closure, Self::Item) -> Closure,
{
match self {
Either::Left(a) => a.rfold(init, f),
Either::Right(b) => b.rfold(init, f),
}
}
#[cfg(not(mr_mime_no_nth_back))]
fn nth_back(&mut self, n: usize) -> Option<Self::Item> {
match self {
Either::Left(a) => a.nth_back(n),
Either::Right(b) => b.nth_back(n),
}
}
}