#![forbid(unsafe_code)]
#![doc = include_str!("../README.md")]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct MimeType {
pub type_: String,
pub subtype: String,
pub suffix: Option<String>,
}
#[must_use]
pub fn parse_mime(input: &str) -> Option<MimeType> {
let essence = input.trim().split(';').next()?.trim().to_ascii_lowercase();
let (type_, subtype_with_suffix) = essence.split_once('/')?;
if type_.is_empty() || subtype_with_suffix.is_empty() {
return None;
}
if !is_token(type_) || !is_token(subtype_with_suffix) {
return None;
}
let (subtype, suffix) = match subtype_with_suffix.rsplit_once('+') {
Some((subtype, suffix)) if !subtype.is_empty() && !suffix.is_empty() => {
(subtype.to_string(), Some(suffix.to_string()))
}
_ => (subtype_with_suffix.to_string(), None),
};
Some(MimeType {
type_: type_.to_string(),
subtype,
suffix,
})
}
#[must_use]
pub fn looks_like_mime(input: &str) -> bool {
parse_mime(input).is_some()
}
#[must_use]
pub fn mime_from_extension(extension: &str) -> Option<&'static str> {
match extension
.trim()
.trim_start_matches('.')
.to_ascii_lowercase()
.as_str()
{
"html" | "htm" => Some("text/html"),
"css" => Some("text/css"),
"js" | "mjs" => Some("application/javascript"),
"json" => Some("application/json"),
"xml" => Some("application/xml"),
"txt" => Some("text/plain"),
"md" => Some("text/markdown"),
"csv" => Some("text/csv"),
"png" => Some("image/png"),
"jpg" | "jpeg" => Some("image/jpeg"),
"gif" => Some("image/gif"),
"svg" => Some("image/svg+xml"),
"webp" => Some("image/webp"),
"ico" => Some("image/x-icon"),
"pdf" => Some("application/pdf"),
"wasm" => Some("application/wasm"),
"zip" => Some("application/zip"),
_ => None,
}
}
#[must_use]
pub fn extension_from_mime(mime: &str) -> Option<&'static str> {
match mime
.trim()
.split(';')
.next()?
.trim()
.to_ascii_lowercase()
.as_str()
{
"text/html" => Some("html"),
"text/css" => Some("css"),
"application/javascript" | "text/javascript" => Some("js"),
"application/json" => Some("json"),
"application/xml" | "text/xml" => Some("xml"),
"text/plain" => Some("txt"),
"text/markdown" => Some("md"),
"text/csv" => Some("csv"),
"image/png" => Some("png"),
"image/jpeg" => Some("jpg"),
"image/gif" => Some("gif"),
"image/svg+xml" => Some("svg"),
"image/webp" => Some("webp"),
"image/x-icon" => Some("ico"),
"application/pdf" => Some("pdf"),
"application/wasm" => Some("wasm"),
"application/zip" => Some("zip"),
_ => None,
}
}
#[must_use]
pub fn is_text_mime(mime: &str) -> bool {
matches!(parse_mime(mime), Some(MimeType { type_, .. }) if type_ == "text")
}
#[must_use]
pub fn is_image_mime(mime: &str) -> bool {
matches!(parse_mime(mime), Some(MimeType { type_, .. }) if type_ == "image")
}
#[must_use]
pub fn is_json_mime(mime: &str) -> bool {
matches!(parse_mime(mime), Some(MimeType { subtype, suffix, .. }) if subtype == "json" || suffix.as_deref() == Some("json"))
}
#[must_use]
pub fn is_html_mime(mime: &str) -> bool {
matches!(parse_mime(mime), Some(MimeType { type_, subtype, .. }) if type_ == "text" && subtype == "html")
}
#[must_use]
pub fn is_xml_mime(mime: &str) -> bool {
matches!(parse_mime(mime), Some(MimeType { subtype, suffix, .. }) if subtype == "xml" || suffix.as_deref() == Some("xml"))
}
#[must_use]
pub fn is_css_mime(mime: &str) -> bool {
matches!(parse_mime(mime), Some(MimeType { type_, subtype, .. }) if type_ == "text" && subtype == "css")
}
fn is_token(input: &str) -> bool {
input.bytes().all(|byte| {
byte.is_ascii_alphanumeric()
|| matches!(
byte,
b'!' | b'#' | b'$' | b'&' | b'^' | b'_' | b'.' | b'+' | b'-'
)
})
}