#[macro_use] extern crate nom;
#[macro_use] extern crate lazy_static;
extern crate petgraph;
extern crate fnv;
extern crate parking_lot;
use petgraph::prelude::*;
use fnv::FnvHashMap;
use fnv::FnvHashSet;
use std::io::prelude::*;
use std::io::BufReader;
use std::fs::File;
use std::path::Path;
use parking_lot::RwLock;
use std::sync::Arc;
mod fdo_magic;
mod basetype;
#[cfg(feature="staticmime")] type MIME = &'static str;
#[cfg(not(feature="staticmime"))] type MIME = String;
const TYPEORDER: [&'static str; 6] =
[
"image/png",
"image/jpeg",
"image/gif",
"application/zip",
"application/x-msdos-executable",
"application/pdf"
];
struct CheckerStruct {
from_u8: fn(&[u8], &str, CacheItem) -> bool,
from_filepath: fn(&Path, &str, CacheItem) -> bool,
get_supported: fn() -> Vec<MIME>,
get_subclasses: fn() -> Vec<(MIME, MIME)>,
get_aliaslist: fn() -> FnvHashMap<MIME, MIME>
}
#[cfg(not(feature="staticmime"))]
const CHECKERCOUNT: usize = 3;
#[cfg(feature="staticmime")]
const CHECKERCOUNT: usize = 2;
const CHECKERS: [CheckerStruct; CHECKERCOUNT] =
[
#[cfg(not(feature="staticmime"))] CheckerStruct{
from_u8: fdo_magic::sys::check::from_u8,
from_filepath: fdo_magic::sys::check::from_filepath,
get_supported: fdo_magic::sys::init::get_supported,
get_subclasses: fdo_magic::sys::init::get_subclasses,
get_aliaslist: fdo_magic::sys::init::get_aliaslist
},
CheckerStruct{
from_u8: fdo_magic::builtin::check::from_u8,
from_filepath: fdo_magic::builtin::check::from_filepath,
get_supported: fdo_magic::builtin::init::get_supported,
get_subclasses: fdo_magic::builtin::init::get_subclasses,
get_aliaslist: fdo_magic::builtin::init::get_aliaslist
},
CheckerStruct{
from_u8: basetype::check::from_u8,
from_filepath: basetype::check::from_filepath,
get_supported: basetype::init::get_supported,
get_subclasses: basetype::init::get_subclasses,
get_aliaslist: basetype::init::get_aliaslist
}
];
lazy_static! {
static ref CHECKER_SUPPORT: FnvHashMap<MIME, usize> = {
let mut out = FnvHashMap::<MIME, usize>::default();
for i in 0..CHECKERS.len() {
for j in (CHECKERS[i].get_supported)() {
out.insert(j, i);
}
}
out
};
}
lazy_static! {
static ref ALIASES: FnvHashMap<MIME, MIME> = {
let mut out = FnvHashMap::<MIME, MIME>::default();
for i in 0..CHECKERS.len() {
out.extend((CHECKERS[i].get_aliaslist)());
}
out
};
}
#[derive(Clone)]
pub enum Cache {
#[cfg(not(feature="staticmime"))] FdoMagicSys(fdo_magic::sys::Cache),
FdoMagicBuiltin(fdo_magic::builtin::Cache),
Basetype(basetype::Cache)
}
type CacheItem = Arc<RwLock<Option<Cache>>>;
type CacheContainer = Vec<CacheItem>;
pub struct TypeStruct {
pub graph: DiGraph<MIME, u32>,
pub hash: FnvHashMap<MIME, NodeIndex>
}
lazy_static! {
pub static ref TYPE: TypeStruct = {
graph_init().unwrap_or(
TypeStruct{
graph: DiGraph::new(),
hash: FnvHashMap::default()
} )
};
}
#[cfg(not(feature="staticmime"))]
macro_rules! convmime {
($x:expr) => {$x.to_string()}
}
#[cfg(feature="staticmime")]
macro_rules! convmime {
($x:expr) => {$x}
}
#[cfg(not(feature="staticmime"))]
macro_rules! unconvmime {
($x:expr) => {$x.as_str()}
}
#[cfg(feature="staticmime")]
macro_rules! unconvmime {
($x:expr) => {$x}
}
#[cfg(not(feature="staticmime"))]
macro_rules! clonemime {
($x:expr) => {$x.clone()}
}
#[cfg(feature="staticmime")]
macro_rules! clonemime {
($x:expr) => {$x}
}
fn graph_init() -> Result<TypeStruct, std::io::Error> {
let mut graph = DiGraph::<MIME, u32>::new();
let mut added_mimes = FnvHashMap::<MIME, NodeIndex>::default();
let mut mimelist = Vec::<MIME>::new();
let mut edgelist_raw = Vec::<(MIME, MIME)>::new();
for i in 0..CHECKERS.len() {
mimelist.extend((CHECKERS[i].get_supported)());
edgelist_raw.extend((CHECKERS[i].get_subclasses)());
}
mimelist.sort();
mimelist.dedup();
let mimelist = mimelist;
for mimetype in mimelist.iter() {
let node = graph.add_node(clonemime!(mimetype));
added_mimes.insert(clonemime!(mimetype), node);
}
let mut edge_list = FnvHashSet::<(NodeIndex, NodeIndex)>::with_capacity_and_hasher(
edgelist_raw.len(), Default::default()
);
for x in edgelist_raw {
let child_raw = x.0;
let parent_raw = x.1;
let parent = match added_mimes.get(&parent_raw) {
Some(node) => *node,
None => {continue;}
};
let child = match added_mimes.get(&child_raw) {
Some(node) => *node,
None => {continue;}
};
edge_list.insert( (child, parent) );
}
graph.extend_with_edges(&edge_list);
let added_mimes_tmp = added_mimes.clone();
let node_text = match added_mimes_tmp.get("text/plain"){
Some(x) => *x,
None => {
let node = graph.add_node(convmime!("text/plain"));
added_mimes.insert(convmime!("text/plain"), node);
node
}
};
let node_octet = match added_mimes_tmp.get("application/octet-stream"){
Some(x) => *x,
None => {
let node = graph.add_node(convmime!("application/octet-stream"));
added_mimes.insert(convmime!("application/octet-stream"), node);
node
}
};
let node_allall = match added_mimes_tmp.get("all/all"){
Some(x) => *x,
None => {
let node = graph.add_node(convmime!("all/all"));
added_mimes.insert(convmime!("all/all"), node);
node
}
};
let node_allfiles = match added_mimes_tmp.get("all/allfiles"){
Some(x) => *x,
None => {
let node = graph.add_node(convmime!("all/allfiles"));
added_mimes.insert(convmime!("all/allfiles"), node);
node
}
};
let mut edge_list_2 = FnvHashSet::<(NodeIndex, NodeIndex)>::default();
for mimenode in graph.externals(Incoming) {
let ref mimetype = graph[mimenode];
let toplevel = mimetype.split("/").nth(0).unwrap_or("");
if mimenode == node_text || mimenode == node_octet ||
mimenode == node_allfiles || mimenode == node_allall
{
continue;
}
if toplevel == "text" {
edge_list_2.insert( (node_text, mimenode) );
} else if toplevel == "inode" {
edge_list_2.insert( (node_allall, mimenode) );
} else {
edge_list_2.insert( (node_octet, mimenode) );
}
}
graph.extend_with_edges(edge_list_2.difference(&edge_list));
let graph = graph;
let added_mimes = added_mimes;
Ok( TypeStruct{graph: graph, hash: added_mimes} )
}
fn typegraph_walker<T: Clone>(
parentnode: NodeIndex,
input: T,
cache: &CacheContainer,
matchfn: fn(&str, T, &CacheContainer) -> bool
) -> Option<MIME> {
let mut children: Vec<NodeIndex> = TYPE.graph
.neighbors_directed(parentnode, Outgoing)
.collect();
for i in 0..children.len() {
let x = children[i];
if TYPEORDER.contains(&&*TYPE.graph[x]) {
children.remove(i);
children.insert(0, x);
}
}
for childnode in children {
let ref mimetype = TYPE.graph[childnode];
let result = (matchfn)(mimetype, input.clone(), cache);
match result {
true => {
match typegraph_walker(
childnode, input, cache, matchfn
) {
Some(foundtype) => return Some(foundtype),
None => return Some(clonemime!(mimetype)),
}
}
false => continue,
}
}
None
}
#[cfg(feature="staticmime")]
fn get_alias(mimetype: &str) -> &str {
match ALIASES.get(mimetype) {
Some(x) => x,
None => mimetype
}
}
#[cfg(not(feature="staticmime"))]
fn get_alias(mimetype: &String) -> &String {
match ALIASES.get(mimetype) {
Some(x) => x,
None => mimetype
}
}
fn match_u8_noalias(mimetype: &str, bytes: &[u8], cache: &CacheContainer) -> bool
{
match CHECKER_SUPPORT.get(mimetype) {
None => {false},
Some(y) => (CHECKERS[*y].from_u8)(bytes, mimetype, cache[*y].clone())
}
}
pub fn match_u8(mimetype: &str, bytes: &[u8]) -> bool
{
let oldmime = convmime!(mimetype);
let x = unconvmime!(get_alias(&oldmime));
match_u8_noalias(x, bytes, &vec![CacheItem::default(); CHECKERCOUNT])
}
pub fn from_u8_node(parentnode: NodeIndex, bytes: &[u8]) -> Option<MIME>
{
typegraph_walker(parentnode, bytes, &vec![CacheItem::default(); CHECKERCOUNT], match_u8_noalias)
}
pub fn from_u8(bytes: &[u8]) -> MIME
{
let node = match TYPE.graph.externals(Incoming).next() {
Some(foundnode) => foundnode,
None => panic!("No filetype definitions are loaded.")
};
from_u8_node(node, bytes).unwrap()
}
fn match_filepath_noalias(mimetype: &str, filepath: &Path, cache: &CacheContainer) -> bool
{
match CHECKER_SUPPORT.get(mimetype) {
None => {false},
Some(y) => (CHECKERS[*y].from_filepath)(filepath, mimetype, cache[*y].clone())
}
}
pub fn match_filepath(mimetype: &str, filepath: &Path) -> bool
{
let oldmime = convmime!(mimetype);
let x = unconvmime!(get_alias(&oldmime));
match_filepath_noalias(x, filepath, &vec![CacheItem::default(); CHECKERCOUNT])
}
pub fn from_filepath_node(parentnode: NodeIndex, filepath: &Path) -> Option<MIME>
{
if !match_filepath("application/octet-stream", filepath){
return typegraph_walker(parentnode, filepath, &vec![CacheItem::default(); CHECKERCOUNT], match_filepath_noalias);
}
let f = match File::open(filepath) {
Ok(x) => x,
Err(_) => return None };
let r = BufReader::new(f);
let mut b = Vec::<u8>::new();
match r.take(2048).read_to_end(&mut b) {
Ok(_) => {},
Err(_) => return None }
from_u8_node(parentnode, b.as_slice())
}
pub fn from_filepath(filepath: &Path) -> MIME {
let node = match TYPE.graph.externals(Incoming).next() {
Some(foundnode) => foundnode,
None => panic!("No filetype definitions are loaded.")
};
from_filepath_node(node, filepath).unwrap()
}
pub fn is_alias(mime1: MIME, mime2: MIME) -> bool {
let x = get_alias(&mime1);
let y = get_alias(&mime2);
#[cfg(feature="staticmime")]
return x == mime2 || y == mime1;
#[cfg(not(feature="staticmime"))]
return *x == mime2 || *y == mime1;
}