#![deny(unsafe_code)]
use std::ffi::CString;
use std::path::Path;
use magic_sys as libmagic;
mod ffi;
#[doc(alias = "magic_version")]
pub fn libmagic_version() -> libc::c_int {
crate::ffi::version()
}
bitflags::bitflags! {
#[derive(std::default::Default, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Clone, Copy)]
pub struct CookieFlags: libc::c_int {
const _ = !0;
#[doc(alias = "MAGIC_DEBUG")]
const DEBUG = libmagic::MAGIC_DEBUG;
#[doc(alias = "MAGIC_SYMLINK")]
const SYMLINK = libmagic::MAGIC_SYMLINK;
#[doc(alias = "MAGIC_COMPRESS")]
const COMPRESS = libmagic::MAGIC_COMPRESS;
#[doc(alias = "MAGIC_DEVICES")]
const DEVICES = libmagic::MAGIC_DEVICES;
#[doc(alias = "MAGIC_MIME_TYPE")]
const MIME_TYPE = libmagic::MAGIC_MIME_TYPE;
#[doc(alias = "MAGIC_CONTINUE")]
const CONTINUE = libmagic::MAGIC_CONTINUE;
#[doc(alias = "MAGIC_CHECK")]
const CHECK = libmagic::MAGIC_CHECK;
#[doc(alias = "MAGIC_PRESERVE_ATIME")]
const PRESERVE_ATIME = libmagic::MAGIC_PRESERVE_ATIME;
#[doc(alias = "MAGIC_RAW")]
const RAW = libmagic::MAGIC_RAW;
#[doc(alias = "MAGIC_ERROR")]
const ERROR = libmagic::MAGIC_ERROR;
#[doc(alias = "MAGIC_MIME_ENCODING")]
const MIME_ENCODING = libmagic::MAGIC_MIME_ENCODING;
#[doc(alias = "MAGIC_MIME")]
const MIME = Self::MIME_TYPE.bits()
| Self::MIME_ENCODING.bits();
#[doc(alias = "MAGIC_APPLE")]
const APPLE = libmagic::MAGIC_APPLE;
#[doc(alias = "MAGIC_EXTENSION")]
const EXTENSION = libmagic::MAGIC_EXTENSION;
#[doc(alias = "MAGIC_COMPRESS_TRANSP")]
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")]
const NO_CHECK_COMPRESS = libmagic::MAGIC_NO_CHECK_COMPRESS;
#[doc(alias = "MAGIC_NO_CHECK_TAR")]
const NO_CHECK_TAR = libmagic::MAGIC_NO_CHECK_TAR;
#[doc(alias = "MAGIC_NO_CHECK_SOFT")]
const NO_CHECK_SOFT = libmagic::MAGIC_NO_CHECK_SOFT;
#[doc(alias = "MAGIC_NO_CHECK_APPTYPE")]
const NO_CHECK_APPTYPE = libmagic::MAGIC_NO_CHECK_APPTYPE;
#[doc(alias = "MAGIC_NO_CHECK_ELF")]
const NO_CHECK_ELF = libmagic::MAGIC_NO_CHECK_ELF;
#[doc(alias = "MAGIC_NO_CHECK_TEXT")]
const NO_CHECK_TEXT = libmagic::MAGIC_NO_CHECK_TEXT;
#[doc(alias = "MAGIC_NO_CHECK_CDF")]
const NO_CHECK_CDF = libmagic::MAGIC_NO_CHECK_CDF;
#[doc(alias = "MAGIC_NO_CHECK_CSV")]
const NO_CHECK_CSV = libmagic::MAGIC_NO_CHECK_CSV;
#[doc(alias = "MAGIC_NO_CHECK_TOKENS")]
const NO_CHECK_TOKENS = libmagic::MAGIC_NO_CHECK_TOKENS;
#[doc(alias = "MAGIC_NO_CHECK_ENCODING")]
const NO_CHECK_ENCODING = libmagic::MAGIC_NO_CHECK_ENCODING;
#[doc(alias = "MAGIC_NO_CHECK_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();
}
}
fn db_filenames<P: AsRef<Path>>(filenames: &[P]) -> Result<Option<CString>, MagicError> {
match filenames.len() {
0 => Ok(None),
_ => Ok(Some(
CString::new(
filenames
.iter()
.map(|f| f.as_ref().to_string_lossy().into_owned())
.collect::<Vec<String>>()
.join(":"),
)
.map_err(|_| MagicError::InvalidDatabaseFilePath)?,
)),
}
}
#[derive(thiserror::Error, Debug)]
#[error("`libmagic` error: {0:?}")]
pub struct FfiError(#[from] crate::ffi::LibmagicError);
#[non_exhaustive]
#[derive(thiserror::Error, Debug)]
pub enum MagicError {
#[error(transparent)]
Libmagic(#[from] FfiError),
#[error("`libmagic` flag {0:?} is not supported on this system")]
LibmagicFlagUnsupported(CookieFlags),
#[error("invalid database file path")]
InvalidDatabaseFilePath,
}
impl From<crate::ffi::LibmagicError> for MagicError {
fn from(libmagic_error: crate::ffi::LibmagicError) -> Self {
FfiError::from(libmagic_error).into()
}
}
#[derive(Debug)]
#[doc(alias = "magic_t")]
#[doc(alias = "magic_set")]
pub struct Cookie {
cookie: libmagic::magic_t,
}
impl Drop for Cookie {
#[doc(alias = "magic_close")]
fn drop(&mut self) {
crate::ffi::close(self.cookie);
}
}
impl Cookie {
#[doc(alias = "magic_file")]
pub fn file<P: AsRef<Path>>(&self, filename: P) -> Result<String, MagicError> {
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(err.into()),
}
}
#[doc(alias = "magic_buffer")]
pub fn buffer(&self, buffer: &[u8]) -> Result<String, MagicError> {
match crate::ffi::buffer(self.cookie, buffer) {
Ok(res) => Ok(res.to_string_lossy().to_string()),
Err(err) => Err(err.into()),
}
}
#[doc(alias = "magic_setflags")]
pub fn set_flags(&self, flags: CookieFlags) -> Result<(), MagicError> {
let ret = crate::ffi::setflags(self.cookie, flags.bits());
match ret {
Err(_) => Err(MagicError::LibmagicFlagUnsupported(
CookieFlags::PRESERVE_ATIME,
)),
Ok(_) => Ok(()),
}
}
#[doc(alias = "magic_check")]
pub fn check<P: AsRef<Path>>(&self, filenames: &[P]) -> Result<(), MagicError> {
let db_filenames = db_filenames(filenames)?;
match crate::ffi::check(self.cookie, db_filenames.as_deref()) {
Err(err) => Err(err.into()),
Ok(_) => Ok(()),
}
}
#[doc(alias = "magic_compile")]
pub fn compile<P: AsRef<Path>>(&self, filenames: &[P]) -> Result<(), MagicError> {
let db_filenames = db_filenames(filenames)?;
match crate::ffi::compile(self.cookie, db_filenames.as_deref()) {
Err(err) => Err(err.into()),
Ok(_) => Ok(()),
}
}
#[doc(alias = "magic_list")]
pub fn list<P: AsRef<Path>>(&self, filenames: &[P]) -> Result<(), MagicError> {
let db_filenames = db_filenames(filenames)?;
match crate::ffi::list(self.cookie, db_filenames.as_deref()) {
Err(err) => Err(err.into()),
Ok(_) => Ok(()),
}
}
#[doc(alias = "magic_load")]
pub fn load<P: AsRef<Path>>(&self, filenames: &[P]) -> Result<(), MagicError> {
let db_filenames = db_filenames(filenames)?;
match crate::ffi::load(self.cookie, db_filenames.as_deref()) {
Err(err) => Err(err.into()),
Ok(_) => Ok(()),
}
}
#[doc(alias = "magic_load_buffers")]
pub fn load_buffers(&self, buffers: &[&[u8]]) -> Result<(), MagicError> {
match crate::ffi::load_buffers(self.cookie, buffers) {
Err(err) => Err(err.into()),
Ok(_) => Ok(()),
}
}
#[doc(alias = "magic_open")]
pub fn open(flags: CookieFlags) -> Result<Cookie, MagicError> {
match crate::ffi::open(flags.bits()) {
Err(err) => Err(err.into()),
Ok(cookie) => Ok(Cookie { cookie }),
}
}
}
#[cfg(test)]
mod tests {
use super::Cookie;
use super::CookieFlags;
use super::MagicError;
#[test]
fn file() {
let cookie = Cookie::open(Default::default()).ok().unwrap();
assert!(cookie.load(&vec!["data/tests/db-images-png"]).is_ok());
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(CookieFlags::MIME_TYPE).unwrap();
assert_eq!(cookie.file(&path).ok().unwrap(), "image/png");
cookie
.set_flags(CookieFlags::MIME_TYPE | CookieFlags::MIME_ENCODING)
.unwrap();
assert_eq!(
cookie.file(&path).ok().unwrap(),
"image/png; charset=binary"
);
}
#[test]
fn buffer() {
let cookie = Cookie::open(Default::default()).ok().unwrap();
assert!(cookie
.load(&vec!["data/tests/db-python"].as_slice())
.is_ok());
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(CookieFlags::MIME_TYPE).unwrap();
assert_eq!(cookie.buffer(s).ok().unwrap(), "text/x-python");
}
#[test]
fn file_error() {
let cookie = Cookie::open(CookieFlags::ERROR).ok().unwrap();
assert!(cookie.load::<&str>(&[]).is_ok());
let ret = cookie.file("non-existent_file.txt");
match ret {
Err(e @ MagicError::Libmagic { .. }) => println!("{}", e),
ref e => panic!("result is not a `Libmagic` error: {:?}", e),
}
}
#[test]
fn load_default() {
let cookie = Cookie::open(CookieFlags::ERROR).ok().unwrap();
assert!(cookie.load::<&str>(&[]).is_ok());
}
#[test]
fn load_one() {
let cookie = Cookie::open(CookieFlags::ERROR).ok().unwrap();
assert!(cookie.load(&vec!["data/tests/db-images-png"]).is_ok());
}
#[test]
fn load_multiple() {
let cookie = Cookie::open(CookieFlags::ERROR).ok().unwrap();
assert!(cookie
.load(&vec!["data/tests/db-images-png", "data/tests/db-python",])
.is_ok());
}
static_assertions::assert_impl_all!(Cookie: std::fmt::Debug);
#[test]
fn load_buffers_file() {
let cookie = Cookie::open(Default::default()).ok().unwrap();
let magic_database = std::fs::read("data/tests/db-images-png-precompiled.mgc").unwrap();
let buffers = vec![magic_database.as_slice()];
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);
}
}
#[cfg(doctest)]
#[doc=include_str!("../README.md")]
mod readme {}