#![deny(unsafe_code)]
use core::ffi::c_int;
mod ffi;
#[doc(alias = "magic_version")]
pub fn libmagic_version() -> c_int {
crate::ffi::version()
}
pub mod cookie {
use std::convert::TryFrom;
use std::ffi::{c_int, CString};
use std::path::Path;
use magic_sys as libmagic;
bitflags::bitflags! {
#[derive(std::default::Default, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Clone, Copy)]
pub struct Flags: c_int {
const _ = !0;
#[doc(alias = "MAGIC_DEBUG")]
#[doc(alias = "--debug")]
const DEBUG = libmagic::MAGIC_DEBUG;
#[doc(alias = "MAGIC_SYMLINK")]
#[doc(alias = "--dereference")]
const SYMLINK = libmagic::MAGIC_SYMLINK;
#[doc(alias = "MAGIC_COMPRESS")]
#[doc(alias = "--uncompress")]
const COMPRESS = libmagic::MAGIC_COMPRESS;
#[doc(alias = "MAGIC_DEVICES")]
#[doc(alias = "--special-files")]
const DEVICES = libmagic::MAGIC_DEVICES;
#[doc(alias = "MAGIC_MIME_TYPE")]
#[doc(alias = "--mime-type")]
const MIME_TYPE = libmagic::MAGIC_MIME_TYPE;
#[doc(alias = "MAGIC_CONTINUE")]
#[doc(alias = "--keep-going")]
const CONTINUE = libmagic::MAGIC_CONTINUE;
#[doc(alias = "MAGIC_CHECK")]
const CHECK = libmagic::MAGIC_CHECK;
#[doc(alias = "MAGIC_PRESERVE_ATIME")]
#[doc(alias = "--preserve-date")]
const PRESERVE_ATIME = libmagic::MAGIC_PRESERVE_ATIME;
#[doc(alias = "MAGIC_RAW")]
#[doc(alias = "--raw")]
const RAW = libmagic::MAGIC_RAW;
#[doc(alias = "MAGIC_ERROR")]
const ERROR = libmagic::MAGIC_ERROR;
#[doc(alias = "MAGIC_MIME_ENCODING")]
#[doc(alias = "--mime-encoding")]
const MIME_ENCODING = libmagic::MAGIC_MIME_ENCODING;
#[doc(alias = "MAGIC_MIME")]
#[doc(alias = "--mime")]
const MIME = Self::MIME_TYPE.bits()
| Self::MIME_ENCODING.bits();
#[doc(alias = "MAGIC_APPLE")]
#[doc(alias = "--apple")]
const APPLE = libmagic::MAGIC_APPLE;
#[doc(alias = "MAGIC_EXTENSION")]
#[doc(alias = "--extension")]
const EXTENSION = libmagic::MAGIC_EXTENSION;
#[doc(alias = "MAGIC_COMPRESS_TRANSP")]
#[doc(alias = "--uncompress-noreport")]
const COMPRESS_TRANSP = libmagic::MAGIC_COMPRESS_TRANSP;
#[doc(alias = "MAGIC_NODESC")]
const NODESC = Self::EXTENSION.bits()
| Self::MIME.bits()
| Self::APPLE.bits();
#[doc(alias = "MAGIC_NO_CHECK_COMPRESS")]
#[doc(alias = "--exclude compress")]
const NO_CHECK_COMPRESS = libmagic::MAGIC_NO_CHECK_COMPRESS;
#[doc(alias = "MAGIC_NO_CHECK_TAR")]
#[doc(alias = "--exclude tar")]
const NO_CHECK_TAR = libmagic::MAGIC_NO_CHECK_TAR;
#[doc(alias = "MAGIC_NO_CHECK_SOFT")]
#[doc(alias = "--exclude soft")]
const NO_CHECK_SOFT = libmagic::MAGIC_NO_CHECK_SOFT;
#[doc(alias = "MAGIC_NO_CHECK_APPTYPE")]
#[doc(alias = "--exclude apptype")]
const NO_CHECK_APPTYPE = libmagic::MAGIC_NO_CHECK_APPTYPE;
#[doc(alias = "MAGIC_NO_CHECK_ELF")]
#[doc(alias = "--exclude elf")]
const NO_CHECK_ELF = libmagic::MAGIC_NO_CHECK_ELF;
#[doc(alias = "MAGIC_NO_CHECK_TEXT")]
#[doc(alias = "--exclude text")]
const NO_CHECK_TEXT = libmagic::MAGIC_NO_CHECK_TEXT;
#[doc(alias = "MAGIC_NO_CHECK_CDF")]
#[doc(alias = "--exclude cdf")]
const NO_CHECK_CDF = libmagic::MAGIC_NO_CHECK_CDF;
#[doc(alias = "MAGIC_NO_CHECK_CSV")]
#[doc(alias = "--exclude csv")]
const NO_CHECK_CSV = libmagic::MAGIC_NO_CHECK_CSV;
#[doc(alias = "MAGIC_NO_CHECK_TOKENS")]
#[doc(alias = "--exclude tokens")]
const NO_CHECK_TOKENS = libmagic::MAGIC_NO_CHECK_TOKENS;
#[doc(alias = "MAGIC_NO_CHECK_ENCODING")]
#[doc(alias = "--exclude encoding")]
const NO_CHECK_ENCODING = libmagic::MAGIC_NO_CHECK_ENCODING;
#[doc(alias = "MAGIC_NO_CHECK_JSON")]
#[doc(alias = "--exclude json")]
const NO_CHECK_JSON = libmagic::MAGIC_NO_CHECK_JSON;
#[doc(alias = "MAGIC_NO_CHECK_BUILTIN")]
const NO_CHECK_BUILTIN = Self::NO_CHECK_COMPRESS.bits()
| Self::NO_CHECK_TAR.bits()
| Self::NO_CHECK_APPTYPE.bits()
| Self::NO_CHECK_ELF.bits()
| Self::NO_CHECK_TEXT.bits()
| Self::NO_CHECK_CSV.bits()
| Self::NO_CHECK_CDF.bits()
| Self::NO_CHECK_TOKENS.bits()
| Self::NO_CHECK_ENCODING.bits()
| Self::NO_CHECK_JSON.bits();
}
}
impl std::fmt::Display for Flags {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
bitflags::parser::to_writer(self, f)
}
}
#[derive(Debug)]
pub struct InvalidDatabasePathError {}
impl std::error::Error for InvalidDatabasePathError {}
impl std::fmt::Display for InvalidDatabasePathError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "invalid database files path")
}
}
pub struct DatabasePaths {
filenames: Option<CString>,
}
const DATABASE_FILENAME_SEPARATOR: &str = ":";
impl DatabasePaths {
pub fn new<I, P>(paths: I) -> Result<Self, InvalidDatabasePathError>
where
I: IntoIterator<Item = P>,
P: AsRef<Path>,
{
let filename = paths
.into_iter()
.map(|f| f.as_ref().to_string_lossy().into_owned())
.collect::<Vec<String>>()
.join(DATABASE_FILENAME_SEPARATOR);
Ok(Self {
filenames: match filename.is_empty() {
true => None,
_ => Some(CString::new(filename).map_err(|_| InvalidDatabasePathError {})?),
},
})
}
}
impl Default for DatabasePaths {
fn default() -> Self {
Self { filenames: None }
}
}
impl<P: AsRef<std::path::Path>, const N: usize> TryFrom<[P; N]> for DatabasePaths {
type Error = InvalidDatabasePathError;
fn try_from(value: [P; N]) -> Result<Self, <Self as TryFrom<[P; N]>>::Error> {
Self::new(value)
}
}
impl<P: AsRef<std::path::Path>> TryFrom<Vec<P>> for DatabasePaths {
type Error = InvalidDatabasePathError;
fn try_from(value: Vec<P>) -> Result<Self, <Self as TryFrom<Vec<P>>>::Error> {
Self::new(value)
}
}
impl<P: AsRef<std::path::Path>> TryFrom<&'_ [P]> for DatabasePaths {
type Error = InvalidDatabasePathError;
fn try_from(value: &[P]) -> Result<Self, <Self as TryFrom<&[P]>>::Error> {
Self::new(value)
}
}
macro_rules! databasepaths_try_from_impl {
($t:ty) => {
impl TryFrom<$t> for DatabasePaths {
type Error = InvalidDatabasePathError;
fn try_from(value: $t) -> Result<Self, <Self as TryFrom<$t>>::Error> {
DatabasePaths::new(std::iter::once(value))
}
}
};
}
databasepaths_try_from_impl!(&str);
databasepaths_try_from_impl!(&std::ffi::OsStr);
databasepaths_try_from_impl!(std::ffi::OsString);
databasepaths_try_from_impl!(&std::path::Path);
databasepaths_try_from_impl!(std::path::PathBuf);
databasepaths_try_from_impl!(String);
#[derive(Debug)]
pub struct Error {
function: &'static str,
source: crate::ffi::CookieError,
}
impl std::error::Error for Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
Some(&self.source)
}
}
impl std::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
let Self { function, .. } = self;
write!(f, "magic cookie error in `libmagic` function {0}", function,)
}
}
#[derive(Debug)]
pub enum Open {}
#[derive(Debug)]
pub enum Load {}
mod private {
pub trait Sealed {}
impl Sealed for super::Open {}
impl Sealed for super::Load {}
}
pub trait State: private::Sealed {}
impl State for Open {}
impl State for Load {}
#[derive(Debug)]
#[doc(alias = "magic_t")]
#[doc(alias = "magic_set")]
pub struct Cookie<S: State> {
cookie: crate::ffi::Cookie,
marker: std::marker::PhantomData<S>,
}
#[derive(Debug)]
pub struct LoadError<S: State> {
function: &'static str,
source: crate::ffi::CookieError,
cookie: Cookie<S>,
}
impl<S: State> std::error::Error for LoadError<S>
where
Self: std::fmt::Debug,
{
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
Some(&self.source)
}
}
impl<S: State> std::fmt::Display for LoadError<S> {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
let Self { function, .. } = self;
write!(f, "magic cookie error in `libmagic` function {0}", function,)
}
}
impl<S: State> LoadError<S> {
pub fn cookie(self) -> Cookie<S> {
self.cookie
}
}
impl<S: State> Drop for Cookie<S> {
#[doc(alias = "magic_close")]
fn drop(&mut self) {
crate::ffi::close(&mut self.cookie);
}
}
impl Cookie<Open> {
#[doc(alias = "magic_open")]
pub fn open(flags: Flags) -> Result<Cookie<Open>, OpenError> {
match crate::ffi::open(flags.bits()) {
Err(err) => Err(OpenError {
flags,
kind: match err.errno().kind() {
std::io::ErrorKind::InvalidInput => OpenErrorKind::UnsupportedFlags,
_ => OpenErrorKind::Errno,
},
source: err,
}),
Ok(cookie) => {
let cookie = Cookie {
cookie,
marker: std::marker::PhantomData,
};
Ok(cookie)
}
}
}
}
impl Cookie<Load> {
#[doc(alias = "magic_file")]
pub fn file<P: AsRef<Path>>(&self, filename: P) -> Result<String, Error> {
let c_string = CString::new(filename.as_ref().to_string_lossy().into_owned()).unwrap();
match crate::ffi::file(&self.cookie, c_string.as_c_str()) {
Ok(res) => Ok(res.to_string_lossy().to_string()),
Err(err) => Err(Error {
function: "magic_file",
source: err,
}),
}
}
#[doc(alias = "magic_buffer")]
pub fn buffer(&self, buffer: &[u8]) -> Result<String, Error> {
match crate::ffi::buffer(&self.cookie, buffer) {
Ok(res) => Ok(res.to_string_lossy().to_string()),
Err(err) => Err(Error {
function: "magic_buffer",
source: err,
}),
}
}
}
impl<S: State> Cookie<S> {
#[doc(alias = "magic_load")]
#[doc(alias = "--magic-file")]
pub fn load(self, filenames: &DatabasePaths) -> Result<Cookie<Load>, LoadError<S>> {
match crate::ffi::load(&self.cookie, filenames.filenames.as_deref()) {
Err(err) => Err(LoadError {
function: "magic_load",
source: err,
cookie: self,
}),
Ok(_) => {
let mut cookie = std::mem::ManuallyDrop::new(self);
let cookie = Cookie {
cookie: crate::ffi::Cookie::new(&mut cookie.cookie),
marker: std::marker::PhantomData,
};
Ok(cookie)
}
}
}
#[doc(alias = "magic_load_buffers")]
pub fn load_buffers(self, buffers: &[&[u8]]) -> Result<Cookie<Load>, LoadError<S>> {
match crate::ffi::load_buffers(&self.cookie, buffers) {
Err(err) => Err(LoadError {
function: "magic_load_buffers",
source: err,
cookie: self,
}),
Ok(_) => {
let mut cookie = std::mem::ManuallyDrop::new(self);
let cookie = Cookie {
cookie: crate::ffi::Cookie::new(&mut cookie.cookie),
marker: std::marker::PhantomData,
};
Ok(cookie)
}
}
}
#[doc(alias = "magic_setflags")]
pub fn set_flags(&self, flags: Flags) -> Result<(), SetFlagsError> {
let ret = crate::ffi::setflags(&self.cookie, flags.bits());
match ret {
Err(err) => Err(SetFlagsError {
flags: Flags::PRESERVE_ATIME,
source: err,
}),
Ok(_) => Ok(()),
}
}
#[doc(alias = "magic_compile")]
#[doc(alias = "--compile")]
pub fn compile(&self, filenames: &DatabasePaths) -> Result<(), Error> {
match crate::ffi::compile(&self.cookie, filenames.filenames.as_deref()) {
Err(err) => Err(Error {
function: "magic_compile",
source: err,
}),
Ok(_) => Ok(()),
}
}
#[doc(alias = "magic_check")]
pub fn check(&self, filenames: &DatabasePaths) -> Result<(), Error> {
match crate::ffi::check(&self.cookie, filenames.filenames.as_deref()) {
Err(err) => Err(Error {
function: "magic_check",
source: err,
}),
Ok(_) => Ok(()),
}
}
#[doc(alias = "magic_list")]
#[doc(alias = "--checking-printout")]
pub fn list(&self, filenames: &DatabasePaths) -> Result<(), Error> {
match crate::ffi::list(&self.cookie, filenames.filenames.as_deref()) {
Err(err) => Err(Error {
function: "magic_list",
source: err,
}),
Ok(_) => Ok(()),
}
}
}
#[derive(Debug)]
pub struct OpenError {
flags: Flags,
kind: OpenErrorKind,
source: crate::ffi::OpenError,
}
impl std::error::Error for OpenError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
Some(&self.source)
}
}
impl std::fmt::Display for OpenError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
let Self { flags, kind, .. } = self;
write!(
f,
"could not open magic cookie: {0}",
match kind {
OpenErrorKind::UnsupportedFlags => format!("unsupported flags {0}", flags),
OpenErrorKind::Errno => "other error".to_string(),
}
)
}
}
#[derive(Debug)]
enum OpenErrorKind {
UnsupportedFlags,
Errno,
}
#[derive(Debug)]
pub struct SetFlagsError {
flags: Flags,
source: crate::ffi::SetFlagsError,
}
impl std::error::Error for SetFlagsError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
Some(&self.source)
}
}
impl std::fmt::Display for SetFlagsError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
let Self { flags, .. } = self;
write!(f, "could not set magic cookie flags {0}", flags)
}
}
}
pub use crate::cookie::Cookie;
#[cfg(test)]
mod tests {
use super::cookie::Flags;
use super::cookie::{Error, InvalidDatabasePathError, LoadError, OpenError, SetFlagsError};
use super::cookie::{Load, Open};
use super::Cookie;
use std::convert::TryInto;
fn assert_impl_debug<T: std::fmt::Debug>() {}
fn assert_impl_display<T: std::fmt::Display>() {}
fn assert_impl_error<T: std::error::Error>() {}
#[test]
fn file() {
let cookie = Cookie::open(Flags::ERROR).unwrap();
let databases = &["data/tests/db-images-png"].try_into().unwrap();
let cookie = cookie.load(databases).unwrap();
let path = "data/tests/rust-logo-128x128-blk.png";
assert_eq!(
cookie.file(path).ok().unwrap(),
"PNG image data, 128 x 128, 8-bit/color RGBA, non-interlaced"
);
cookie.set_flags(Flags::MIME_TYPE).unwrap();
assert_eq!(cookie.file(path).ok().unwrap(), "image/png");
cookie
.set_flags(Flags::MIME_TYPE | Flags::MIME_ENCODING)
.unwrap();
assert_eq!(cookie.file(path).ok().unwrap(), "image/png; charset=binary");
}
#[test]
fn buffer() {
let cookie = Cookie::open(Flags::ERROR).unwrap();
let databases = &["data/tests/db-python"].try_into().unwrap();
let cookie = cookie.load(databases).unwrap();
let s = b"#!/usr/bin/env python\nprint('Hello, world!')";
assert_eq!(
cookie.buffer(s).ok().unwrap(),
"Python script, ASCII text executable"
);
cookie.set_flags(Flags::MIME_TYPE).unwrap();
assert_eq!(cookie.buffer(s).ok().unwrap(), "text/x-python");
}
#[test]
fn file_error() {
let cookie = Cookie::open(Flags::ERROR).unwrap();
let cookie = cookie.load(&Default::default()).unwrap();
let ret = cookie.file("non-existent_file.txt");
assert!(ret.is_err());
}
#[test]
fn load_default() {
let cookie = Cookie::open(Flags::ERROR).unwrap();
assert!(cookie.load(&Default::default()).is_ok());
}
#[test]
fn load_one() {
let cookie = Cookie::open(Flags::ERROR).unwrap();
let databases = &["data/tests/db-images-png"].try_into().unwrap();
assert!(cookie.load(databases).is_ok());
}
#[test]
fn load_multiple() {
let cookie = Cookie::open(Flags::ERROR).unwrap();
let databases = &["data/tests/db-images-png", "data/tests/db-python"]
.try_into()
.unwrap();
assert!(cookie.load(databases).is_ok());
}
#[test]
fn cookie_impl_debug() {
assert_impl_debug::<Cookie<Open>>();
assert_impl_debug::<Cookie<Load>>();
}
#[test]
#[ignore] fn load_buffers_file() {
let cookie = Cookie::open(Flags::ERROR).unwrap();
let magic_database = std::fs::read("data/tests/db-images-png-precompiled.mgc").unwrap();
let buffers = vec![magic_database.as_slice()];
let cookie = cookie.load_buffers(&buffers).unwrap();
let path = "data/tests/rust-logo-128x128-blk.png";
assert_eq!(
cookie.file(path).ok().unwrap(),
"PNG image data, 128 x 128, 8-bit/color RGBA, non-interlaced"
);
}
#[test]
fn libmagic_version() {
let version = super::libmagic_version();
assert!(version > 500);
}
#[test]
fn error_impls() {
assert_impl_debug::<Error>();
assert_impl_display::<Error>();
assert_impl_error::<Error>();
}
#[test]
fn invaliddatabasepatherror_impls() {
assert_impl_debug::<InvalidDatabasePathError>();
assert_impl_display::<InvalidDatabasePathError>();
assert_impl_error::<InvalidDatabasePathError>();
}
#[test]
fn loaderror_impls() {
assert_impl_debug::<LoadError<Open>>();
assert_impl_debug::<LoadError<Load>>();
assert_impl_display::<LoadError<Open>>();
assert_impl_display::<LoadError<Load>>();
assert_impl_error::<LoadError<Open>>();
assert_impl_error::<LoadError<Load>>();
}
#[test]
fn openerror_impls() {
assert_impl_debug::<OpenError>();
assert_impl_display::<OpenError>();
assert_impl_error::<OpenError>();
}
#[test]
fn setflagserror_impls() {
assert_impl_debug::<SetFlagsError>();
assert_impl_display::<SetFlagsError>();
assert_impl_error::<SetFlagsError>();
}
}
#[cfg(doctest)]
#[doc=include_str!("../README-crate.md")]
mod readme {}