#![crate_name = "infer"]
mod map;
mod matchers;
use std::fs::File;
use std::io::Read;
use std::path::Path;
use map::{MatcherType, MATCHER_MAP};
pub use matchers::*;
pub type Matcher = fn(buf: &[u8]) -> bool;
#[derive(Debug, Eq, PartialEq)]
pub struct Type {
pub mime: String,
pub ext: String,
}
pub struct Infer {
mmap: Vec<(map::MatcherType, String, String, Matcher)>,
}
impl Infer {
pub fn new() -> Infer {
Infer { mmap: Vec::new() }
}
fn iter_matchers(&self) -> impl Iterator<Item = (&MatcherType, &str, &str, &Matcher)> {
let custom = self
.mmap
.iter()
.map(|(mt, mime, ext, matcher)| (mt, mime.as_str(), ext.as_str(), matcher));
MATCHER_MAP
.iter()
.map(|(mt, mime, ext, matcher)| (mt, *mime, *ext, matcher))
.chain(custom)
}
pub fn get(&self, buf: &[u8]) -> Option<Type> {
for (_, mime, ext, matcher) in self.iter_matchers() {
if matcher(buf) {
return Some(Type {
mime: mime.to_string(),
ext: ext.to_string(),
});
}
}
None
}
pub fn get_from_path<P: AsRef<Path>>(&self, path: P) -> Result<Option<Type>, std::io::Error> {
let file = File::open(path)?;
let limit = file
.metadata()
.map(|m| std::cmp::min(m.len(), 8192) as usize + 1)
.unwrap_or(0);
let mut bytes = Vec::with_capacity(limit);
file.take(8192).read_to_end(&mut bytes)?;
Ok(self.get(&bytes))
}
pub fn is(&self, buf: &[u8], ext: &str) -> bool {
if let Some((_mt, _mi, _e, matcher)) = self
.iter_matchers()
.find(|(_mt, _mime, ex, _matcher)| *ex == ext)
{
if matcher(buf) {
return true;
}
}
false
}
pub fn is_mime(&self, buf: &[u8], mime: &str) -> bool {
if let Some((_mt, _mi, _e, matcher)) = self
.iter_matchers()
.find(|(_mt, mi, _ext, _matcher)| *mi == mime)
{
if matcher(buf) {
return true;
}
}
false
}
pub fn is_supported(&self, ext: &str) -> bool {
for (_mt, _mime, type_ext, _matcher) in self.iter_matchers() {
if ext == type_ext {
return true;
}
}
false
}
pub fn is_mime_supported(&self, mime: &str) -> bool {
for (_mt, type_mime, _ext, _matcher) in self.iter_matchers() {
if mime == type_mime {
return true;
}
}
false
}
pub fn is_app(&self, buf: &[u8]) -> bool {
self.is_type(buf, map::MatcherType::APP)
}
pub fn is_archive(&self, buf: &[u8]) -> bool {
self.is_type(buf, map::MatcherType::ARCHIVE)
}
pub fn is_audio(&self, buf: &[u8]) -> bool {
self.is_type(buf, map::MatcherType::AUDIO)
}
pub fn is_document(&self, buf: &[u8]) -> bool {
self.is_type(buf, map::MatcherType::DOC)
}
pub fn is_font(&self, buf: &[u8]) -> bool {
self.is_type(buf, map::MatcherType::FONT)
}
pub fn is_image(&self, buf: &[u8]) -> bool {
self.is_type(buf, map::MatcherType::IMAGE)
}
pub fn is_video(&self, buf: &[u8]) -> bool {
self.is_type(buf, map::MatcherType::VIDEO)
}
pub fn is_custom(&self, buf: &[u8]) -> bool {
self.is_type(buf, map::MatcherType::CUSTOM)
}
pub fn add(&mut self, mime: &str, ext: &str, m: Matcher) {
self.mmap.push((
map::MatcherType::CUSTOM,
mime.to_string(),
ext.to_string(),
m,
));
}
fn is_type(&self, buf: &[u8], typ: map::MatcherType) -> bool {
for (_mt, _mi, _ex, matcher) in self
.iter_matchers()
.filter(|(mt, _mime, _e, _matcher)| **mt == typ)
{
if matcher(buf) {
return true;
}
}
false
}
}
impl Default for Infer {
fn default() -> Self {
Infer::new()
}
}
#[cfg(test)]
mod tests {
use super::Infer;
#[test]
fn test_get_unknown() {
let v = Vec::new();
let info = Infer::new();
assert!(info.get(&v).is_none());
}
#[test]
fn test_get_jpeg() {
let v = vec![0xFF, 0xD8, 0xFF, 0xAA];
let info = Infer::new();
match info.get(&v) {
Some(info) => {
assert_eq!(info.ext, "jpg");
assert_eq!(info.mime, "image/jpeg");
}
None => panic!("type info expected"),
}
}
}