diff --git a/Cargo.lock b/Cargo.lock index df97042..96527c4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -155,6 +155,12 @@ version = "3.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + [[package]] name = "bytes" version = "1.5.0" @@ -170,6 +176,17 @@ dependencies = [ "libc", ] +[[package]] +name = "cfb" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d38f2da7a0a2c4ccf0065be06397cc26a81f4e528be095826eee9d4adbb8c60f" +dependencies = [ + "byteorder", + "fnv", + "uuid", +] + [[package]] name = "cfg-if" version = "1.0.0" @@ -286,6 +303,12 @@ dependencies = [ "once_cell", ] +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + [[package]] name = "form_urlencoded" version = "1.2.1" @@ -441,6 +464,15 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" +[[package]] +name = "infer" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb33622da908807a06f9513c19b3c1ad50fab3e4137d82a78107d502075aa199" +dependencies = [ + "cfb", +] + [[package]] name = "inventory" version = "0.3.14" @@ -633,6 +665,7 @@ dependencies = [ "clap", "color-eyre", "futures", + "infer", "libc", "regex", "serde", @@ -974,6 +1007,12 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" +[[package]] +name = "uuid" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e395fcf16a7a3d8127ec99782007af141946b4795001f876d54fb0d55978560" + [[package]] name = "valuable" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index 622db2b..6b9b9c4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,7 @@ async-trait = "0.1.77" clap = { version = "4.4.16", features = ["derive"] } color-eyre = "0.6.2" futures = "0.3.30" +infer = { version = "0.15.0", default-features = false, features = ["alloc", "cfb"] } libc = "0.2.152" regex = "1.10.2" serde = { version = "1.0.195", features = ["derive"] } diff --git a/src/filters.rs b/src/filters.rs index c565fcb..bdf8ef2 100644 --- a/src/filters.rs +++ b/src/filters.rs @@ -3,8 +3,8 @@ use std::path::Path; use async_trait::async_trait; use serde::Deserialize; -use tokio::io::{self, AsyncReadExt}; use tokio::fs; +use tokio::io::{self, AsyncReadExt, AsyncSeekExt}; use futures::prelude::*; @@ -156,28 +156,49 @@ filter! { #[serde(rename_all = "snake_case")] pub enum Magic { Mime(String), - Bytes(Box<[u8]>), + Magic { + bytes: Box<[u8]>, + #[serde(default)] + offset: u64, + }, } -async fn read_first_bytes(n: usize, path: &Path) -> io::Result> { +async fn read_first_bytes(n: usize, path: &Path, offset: u64) -> io::Result> { + use std::io::SeekFrom; + let mut file = fs::File::open(path).await?; let mut buf = vec![0; n]; + file.seek(SeekFrom::Start(offset)).await?; file.read_exact(&mut buf).await?; Ok(buf.into()) } +async fn guess_mime(path: &Path) -> Option { + let mut file = fs::File::open(path).await.ok()?; + + let mut buf = vec![0; 8192]; + + let len = file.read(&mut buf).await.ok()?; + + infer::get(&buf[0..len]) +} + #[typetag::deserialize(name = "content_type")] #[async_trait] impl Filter for Magic { async fn matches(&self, file: &Path) -> bool { match *self { - Magic::Bytes(ref bytes) => { - read_first_bytes(bytes.len(), file).await.map(|read| read == *bytes).unwrap_or(false) - }, - Magic::Mime(_) => unimplemented!() + Magic::Magic { ref bytes, offset } => read_first_bytes(bytes.len(), file, offset) + .await + .map(|read| read == *bytes) + .unwrap_or(false), + Magic::Mime(ref mime_type) => guess_mime(file) + .await + .map(|typ| typ.mime_type() == mime_type) + .unwrap_or(false), } } }