#![cfg_attr(feature = "dev", deny(missing_docs, missing_debug_implementations,
missing_copy_implementations, trivial_casts,
trivial_numeric_casts, unsafe_code,
unused_import_braces, unused_qualifications,
warnings))]
#![cfg_attr(feature = "dev", allow(unstable_features))]
#![cfg_attr(feature = "dev", feature(plugin))]
#![cfg_attr(feature = "dev", plugin(clippy))]
extern crate charsets;
use std::borrow::Cow;
use std::collections::HashMap;
use std::fmt::{self, Display, Formatter};
use std::str::FromStr;
pub use charsets::Charset;
pub use self::Type::{Application, Audio, Image, Message, Model, Multipart, Text, Video};
pub use self::Tree::{Personal, Private, Standards, Vendor};
pub use error::{Error, Result};
mod error;
mod utils;
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct MediaType {
pub type_: Option<Type>,
pub subtype: Option<(Tree, Cow<'static, str>, Option<Cow<'static, str>>)>,
pub parameters: HashMap<Cow<'static, str>, Cow<'static, str>>,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum Type {
Text,
Image,
Audio,
Video,
Application,
Multipart,
Message,
Model,
Unregistered(Cow<'static, str>),
}
impl Display for Type {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.write_str(match *self {
Text => "text",
Image => "image",
Audio => "audio",
Video => "video",
Application => "application",
Multipart => "multipart",
Message => "message",
Model => "model",
Type::Unregistered(ref string) => &string[..],
})
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum Tree {
Standards,
Vendor,
Personal,
Private,
Unregistered(Cow<'static, str>),
}
impl Display for Tree {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match *self {
Standards => Ok(()),
Vendor => f.write_str("vnd."),
Personal => f.write_str("prs."),
Private => f.write_str("x."),
Tree::Unregistered(ref string) => write!(f, "{}.", &string[..]),
}
}
}
impl MediaType {
pub fn wildcard() -> MediaType {
MediaType {
type_: None,
subtype: None,
parameters: HashMap::new(),
}
}
pub fn wildcard_subtype(type_: Type) -> MediaType {
MediaType {
type_: Some(type_),
subtype: None,
parameters: HashMap::new(),
}
}
pub fn new<A>(type_: Type, tree: Tree, subtype: A) -> MediaType
where A: Into<Cow<'static, str>>
{
MediaType {
type_: Some(type_),
subtype: Some((tree, subtype.into(), None)),
parameters: HashMap::new(),
}
}
pub fn new_with_suffix<A, B>(type_: Type, tree: Tree, subtype: A, suffix: B) -> MediaType
where A: Into<Cow<'static, str>>,
B: Into<Cow<'static, str>>
{
MediaType {
type_: Some(type_),
subtype: Some((tree, subtype.into(), Some(suffix.into()))),
parameters: HashMap::new(),
}
}
pub fn tree(&self) -> Option<&Tree> {
if let Some(ref subtype) = self.subtype {
Some(&subtype.0)
} else {
None
}
}
pub fn sub(&self) -> Option<&str> {
if let Some(ref subtype) = self.subtype {
Some(&subtype.1[..])
} else {
None
}
}
pub fn suffix(&self) -> Option<&str> {
if let Some(ref subtype) = self.subtype {
if let Some(ref string) = subtype.2 {
return Some(&string[..]);
}
}
None
}
pub fn boundary(&self) -> Result<&str> {
let boundary = try!(self.parameters.get("boundary").ok_or(Error::NotFound));
if !utils::boundary(boundary) {
return Err(Error::Invalid);
}
Ok(&boundary[..])
}
pub fn charset(&self) -> Result<Charset> {
let charset = try!(self.parameters.get("charset").ok_or(Error::NotFound));
Ok(try!(charset.parse()))
}
pub fn set_charset(&mut self, charset: Charset) -> Option<Cow<'static, str>> {
self.parameters.insert("charset".into(), Cow::Owned(charset.to_string()))
}
pub fn set_charset_utf8(&mut self) -> Option<Cow<'static, str>> {
self.set_charset(Charset::Utf8)
}
pub fn eq_mime_portion(&self, other: &MediaType) -> bool {
self.type_ == other.type_ && self.subtype == other.subtype
}
pub fn ne_mime_portion(&self, other: &MediaType) -> bool {
!self.eq_mime_portion(other)
}
pub fn is_image_type(&self) -> bool {
self.type_ == Some(Image)
}
pub fn is_audio_or_video_type(&self) -> bool {
self.type_ == Some(Audio) || self.type_ == Some(Video) ||
MediaType::new(Application, Standards, "ogg").eq_mime_portion(self)
}
pub fn is_font_type(&self) -> bool {
[MediaType::new(Application, Standards, "font-ttf"),
MediaType::new(Application, Standards, "font-cff"),
MediaType::new(Application, Standards, "font-off"),
MediaType::new(Application, Standards, "font-sfnt"),
MediaType::new(Application, Vendor, "ms-opentype"),
MediaType::new(Application, Standards, "font-woff"),
MediaType::new(Application, Vendor, "ms-fontobject")]
.iter()
.any(|x| x.eq_mime_portion(self))
}
pub fn is_zip_based_type(&self) -> bool {
self.suffix() == Some("zip") ||
MediaType::new(Application, Standards, "zip").eq_mime_portion(self)
}
pub fn is_archive_type(&self) -> bool {
[MediaType::new(Application, Standards, "x-rar-compressed"),
MediaType::new(Application, Standards, "zip"),
MediaType::new(Application, Standards, "x-gzip")]
.iter()
.any(|x| x.eq_mime_portion(self))
}
pub fn is_xml_type(&self) -> bool {
self.suffix() == Some("xml") ||
[MediaType::new(Text, Standards, "xml"), MediaType::new(Application, Standards, "xml")]
.iter()
.any(|x| x.eq_mime_portion(self))
}
pub fn is_scriptable_mime_type(&self) -> bool {
[MediaType::new(Text, Standards, "html"), MediaType::new(Application, Standards, "pdf")]
.iter()
.any(|x| x.eq_mime_portion(self))
}
}
impl FromStr for MediaType {
type Err = Error;
fn from_str(s: &str) -> Result<MediaType> {
let (raw_type, raw_subtype, raw_parameters) = try!(utils::parse_media_type(s.as_bytes()));
let type_ = match &raw_type[..] {
b"*" => None,
b"text" => Some(Text),
b"image" => Some(Image),
b"audio" => Some(Audio),
b"video" => Some(Video),
b"application" => Some(Application),
b"multipart" => Some(Multipart),
b"message" => Some(Message),
b"model" => Some(Model),
_ => Some(Type::Unregistered(Cow::Owned(try!(String::from_utf8(raw_type))))),
};
let mut parameters = HashMap::new();
for (key, value) in raw_parameters {
parameters.insert(try!(String::from_utf8(key)).into(),
try!(String::from_utf8(value)).into());
}
if raw_subtype == b"*" {
Ok(MediaType {
type_: type_,
subtype: None,
parameters: parameters,
})
} else {
let subtype = try!(String::from_utf8(raw_subtype));
let (prefix, suffix) = if subtype.contains('+') {
let mut parts = subtype.rsplitn(2, '+');
let suffix = parts.next().unwrap();
let prefix = parts.next().unwrap();
(prefix, Some(suffix))
} else {
(&subtype[..], None)
};
let (tree, sub) = if prefix.contains('.') {
let mut parts = prefix.splitn(2, '.');
let tree = match parts.next().unwrap() {
"vnd" => Vendor,
"prs" => Personal,
"x" => Private,
s => Tree::Unregistered(Cow::Owned(s.to_owned())),
};
(tree, parts.next().unwrap())
} else {
(Standards, prefix)
};
Ok(MediaType {
type_: type_,
subtype: Some((tree,
Cow::Owned(sub.to_owned()),
suffix.map(|x| Cow::Owned(x.to_owned())))),
parameters: parameters,
})
}
}
}
impl Display for MediaType {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
if let Some(ref type_) = self.type_ {
try!(write!(f, "{}/", type_));
if let Some((ref tree, ref subtype, ref suffix_opt)) = self.subtype {
try!(tree.fmt(f));
try!(subtype.fmt(f));
if let Some(ref suffix) = *suffix_opt {
try!(write!(f, "+{}", suffix));
}
} else {
try!(f.write_str("*"));
}
} else {
try!(f.write_str("*/*"))
}
let mut items: Vec<(&Cow<'static, str>, &Cow<'static, str>)> = self.parameters
.iter()
.collect();
items.sort_by(|&(ref first, _), &(ref second, _)| first.cmp(second));
for (ref key, ref value) in items {
if utils::token(&value) {
try!(write!(f, "; {}={}", key, value));
} else {
try!(write!(f, "; {}=\"{}\"", key, value));
}
}
Ok(())
}
}