magic/lib.rs
1// SPDX-FileCopyrightText: © The `magic` Rust crate authors
2// SPDX-License-Identifier: MIT OR Apache-2.0
3
4//! # About
5//!
6//! This crate provides bindings for the `libmagic` C library, which recognizes the
7//! type of data contained in a file (or buffer).
8//!
9//! You might be familiar with `libmagic`'s command-line-interface; [`file`](https://www.darwinsys.com/file/):
10//!
11//! ```shell
12//! $ file data/tests/rust-logo-128x128-blk.png
13//! data/tests/rust-logo-128x128-blk.png: PNG image data, 128 x 128, 8-bit/color RGBA, non-interlaced
14//! ```
15//!
16//! ## `libmagic`
17//!
18//! Understanding how the `libmagic` C library and thus this Rust crate works requires a bit of glossary.
19//!
20//! `libmagic` at its core can analyze a file or buffer and return a mostly unstructured text that describes the analysis result.
21//! There are built-in tests for special cases such as symlinks and compressed files
22//! and there are magic databases with signatures which can be supplied by the user for the generic cases
23//! ("if those bytes look like this, it's a PNG image file").
24//!
25//! The analysis behaviour can be influenced by so-called flags and parameters.
26//! Flags are either set or unset and do not have a value, parameters have a value.
27//!
28//! Databases can be in text form or compiled binary form for faster access. They can be loaded from files on disk or from in-memory buffers.
29//! A regular `libmagic` / `file` installation contains a default database file that includes a plethora of file formats.
30//!
31//! Most `libmagic` functionality requires a configured instance which is called a "magic cookie".
32//! Creating a cookie instance requires initial flags and usually loaded databases.
33//!
34//! # Usage example
35//!
36//! ```rust
37//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
38//! # use std::convert::TryInto;
39//! // open a new configuration with flags
40//! let cookie = magic::Cookie::open(magic::cookie::Flags::ERROR)?;
41//!
42//! // load a specific database
43//! // (so exact test text assertion below works regardless of the system's default database version)
44//! let database = ["data/tests/db-images-png"].try_into()?;
45//! // you can instead load the default database
46//! //let database = Default::default();
47//!
48//! let cookie = cookie.load(&database)?;
49//!
50//! // analyze a test file
51//! let file_to_analyze = "data/tests/rust-logo-128x128-blk.png";
52//! let expected_analysis_result = "PNG image data, 128 x 128, 8-bit/color RGBA, non-interlaced";
53//! assert_eq!(cookie.file(file_to_analyze)?, expected_analysis_result);
54//! # Ok(())
55//! # }
56//! ```
57//!
58//! See further examples in [`examples/`](https://github.com/robo9k/rust-magic/tree/main/examples).
59//!
60//! # MIME example
61//!
62//! Return a MIME type with "charset" encoding parameter:
63//!
64//! ```shell
65//! $ file --mime data/tests/rust-logo-128x128-blk.png
66//! data/tests/rust-logo-128x128-blk.png: image/png; charset=binary
67//! ```
68//! ```rust
69//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
70//! # use std::convert::TryInto;
71//! // open a new configuration with flags for mime type and encoding
72//! let flags = magic::cookie::Flags::MIME_TYPE | magic::cookie::Flags::MIME_ENCODING;
73//! let cookie = magic::Cookie::open(flags)?;
74//!
75//! // load a specific database
76//! let database = ["data/tests/db-images-png"].try_into()?;
77//! let cookie = cookie.load(&database)?;
78//!
79//! // analyze a test file
80//! let file_to_analyze = "data/tests/rust-logo-128x128-blk.png";
81//! let expected_analysis_result = "image/png; charset=binary";
82//! assert_eq!(cookie.file(file_to_analyze)?, expected_analysis_result);
83//! # Ok(())
84//! # }
85//! ```
86//!
87//! See [`magic::cookie::Flags::MIME`](crate::cookie::Flags::MIME).
88//!
89//! # Filename extensions example
90//!
91//! Return slash-separated filename extensions (the ".png" in "example.png")
92//! from file contents (the input filename is not used for detection):
93//!
94//! ```shell
95//! $ file --extension data/tests/rust-logo-128x128-blk.png
96//! data/tests/rust-logo-128x128-blk.png: png
97//! ```
98//! ```rust
99//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
100//! # use std::convert::TryInto;
101//! // open a new configuration with flags for filename extension
102//! let flags = magic::cookie::Flags::EXTENSION;
103//! let cookie = magic::Cookie::open(flags)?;
104//!
105//! // load a specific database
106//! let database = ["data/tests/db-images-png"].try_into()?;
107//! let cookie = cookie.load(&database)?;
108//!
109//! // analyze a test file
110//! let file_to_analyze = "data/tests/rust-logo-128x128-blk.png";
111//! let expected_analysis_result = "png";
112//! assert_eq!(cookie.file(file_to_analyze)?, expected_analysis_result);
113//! # Ok(())
114//! # }
115//! ```
116//!
117//! See [`magic::cookie::Flags::EXTENSION`](crate::cookie::Flags::EXTENSION).
118//!
119//! # Further reading
120//!
121//! * [`Cookie::open()`][Cookie::open]
122//! * cookie [`Flags`](crate::cookie::Flags), in particular:
123//! * [`Flags::ERROR`](crate::cookie::Flags::ERROR)
124//! * [`Flags::MIME`](crate::cookie::Flags::MIME)
125//! * [`Flags::EXTENSION`](crate::cookie::Flags::EXTENSION)
126//! * [`Flags::CONTINUE`](crate::cookie::Flags::CONTINUE)
127//! * [`Flags::NO_CHECK_BUILTIN`](crate::cookie::Flags::NO_CHECK_BUILTIN)
128//! * [`Cookie::load()`](Cookie::load), [`Cookie::load_buffers()`](Cookie::load_buffers)
129//! * [`Cookie::file()`](Cookie::file), [`Cookie::buffer()`](Cookie::buffer)
130//!
131//! Note that while some `libmagic` functions return somewhat structured text, e.g. MIME types and file extensions,
132//! the `magic` crate does not attempt to parse them into Rust data types since the format is not guaranteed by the C FFI API.
133//!
134//! Check the [crate README](https://crates.io/crates/magic) for required dependencies and MSRV.
135//!
136//! # Safety
137//!
138//! This crate is a binding to the `libmagic` C library and as such subject to its security problems.
139//! Please note that `libmagic` has several CVEs, listed on e.g. [Repology](https://repology.org/project/file/cves).
140//! Make sure that you are using an up-to-date version of `libmagic` and ideally
141//! add additional security layers such as sandboxing (which this crate does _not_ provide)
142//! and __do not use it on untrusted input__ e.g. from users on the internet!
143//!
144//! The Rust code of this crate needs to use some `unsafe` for interacting with the `libmagic` C FFI.
145//!
146//! This crate has not been audited nor is it ready for production use.
147//!
148//! This Rust project / crate is not affiliated with the original `file` / `libmagic` C project.
149//!
150//! # Use cases
151//!
152//! `libmagic` can help to identify unknown content. It does this by looking at byte patterns, among other things.
153//! This does not guarantee that e.g. a file which is detected as a PNG image is indeed a valid PNG image.
154//!
155//! Maybe you just want a mapping from file name extensions to MIME types instead, e.g. ".png" ↔ "image/png"?
156//! In this case you do not even need to look at file contents and could use e.g. the [`mime_guess` crate](https://crates.io/crates/mime_guess).
157//!
158//! Maybe you want to be certain that a file is valid for a kown format, e.g. a PNG image?
159//! In this case you should use a parser for that format specifically, e.g. the [`image` crate](https://crates.io/crates/image).
160//!
161//! Maybe you want to know if a file contains other, malicious content?
162//! In this case you should use an anti-virus software, e.g. [ClamAV](https://www.clamav.net/), [Virus Total](https://www.virustotal.com/).
163
164#![deny(unsafe_code)]
165
166mod ffi;
167
168/// Returns the version of the `libmagic` C library as reported by itself.
169///
170/// # Examples
171/// A version of "5.41" is returned as `541`.
172#[doc(alias = "magic_version")]
173pub fn libmagic_version() -> libc::c_int {
174 crate::ffi::version()
175}
176
177/// Functionality for [`Cookie`]
178pub mod cookie {
179 use std::convert::TryFrom;
180 use std::ffi::CString;
181 use std::path::Path;
182
183 use magic_sys as libmagic;
184
185 bitflags::bitflags! {
186 /// Configuration bits for [`Cookie`]
187 ///
188 /// A bitflags instance is a combined set of individual flags.
189 /// `cookie::Flags` are configuration bits for `Cookie` instances that specify how the cookie should behave.
190 ///
191 /// `cookie::Flags` influence several functions, e.g. [`Cookie::file()`](Cookie::file)
192 /// but also [`Cookie::load()`](Cookie::load).
193 ///
194 /// Flags are initially set when a new cookie is created with [`Cookie::open()`](Cookie::open)
195 /// and can be overwritten lateron with [`Cookie::set_flags()`](Cookie::set_flags).
196 ///
197 /// Flags of particular interest:
198 /// - [`ERROR`](Flags::ERROR)
199 /// - [`MIME`](Flags::MIME)
200 /// - [`EXTENSION`](Flags::EXTENSION)
201 /// - [`CONTINUE`](Flags::CONTINUE)
202 /// - [`NO_CHECK_BUILTIN`](Flags::NO_CHECK_BUILTIN)
203 ///
204 /// # Examples
205 ///
206 /// ```
207 /// // default flags
208 /// // `: Flags` type annotation is only needed for this example
209 /// // if you pass it to Cookie::open() etc., Rust will figure it out
210 /// let flags: magic::cookie::Flags = Default::default();
211 ///
212 /// // custom flags combination
213 /// let flags = magic::cookie::Flags::COMPRESS | magic::cookie::Flags::DEVICES;
214 /// ```
215 ///
216 /// # Errors
217 ///
218 /// Some flags might not be supported on all platforms, i.e.
219 /// - [`Cookie::open()`](Cookie::open) might return a [`cookie::OpenError`](OpenError)
220 /// - [`Cookie::set_flags()`](Cookie::set_flags) might return a [`cookie::SetFlagsError`](SetFlagsError)
221 ///
222 /// # Misc
223 ///
224 /// NOTE: The flag descriptions are mostly copied from `man libmagic 3`.
225 ///
226 /// `MAGIC_NONE` is the default, meaning "No special handling" and has no constant.
227 /// ```
228 /// let default_flags: magic::cookie::Flags = Default::default();
229 /// assert_eq!(default_flags, magic::cookie::Flags::empty());
230 /// ```
231 #[derive(std::default::Default, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Clone, Copy)]
232 pub struct Flags: libc::c_int {
233 // MAGIC_NONE is 0/default, see https://docs.rs/bitflags/latest/bitflags/#zero-bit-flags
234
235 // Define unnamed flag for all other bits https://docs.rs/bitflags/latest/bitflags/#externally-defined-flags
236 const _ = !0;
237
238 /// Print debugging messages to `stderr`
239 ///
240 /// This is equivalent to the `file` CLI option `--debug`.
241 ///
242 /// NOTE: Those messages are printed by `libmagic` itself, no this Rust crate.
243 #[doc(alias = "MAGIC_DEBUG")]
244 #[doc(alias = "--debug")]
245 const DEBUG = libmagic::MAGIC_DEBUG;
246
247 /// If the file queried is a symlink, follow it
248 ///
249 /// This is equivalent to the `file` CLI option `--dereference`.
250 #[doc(alias = "MAGIC_SYMLINK")]
251 #[doc(alias = "--dereference")]
252 const SYMLINK = libmagic::MAGIC_SYMLINK;
253
254 /// If the file is compressed, unpack it and look at the contents
255 ///
256 /// This is equivalent to the `file` CLI option `--uncompress`.
257 #[doc(alias = "MAGIC_COMPRESS")]
258 #[doc(alias = "--uncompress")]
259 const COMPRESS = libmagic::MAGIC_COMPRESS;
260
261 /// If the file is a block or character special device, then open the device and try to look in its contents
262 ///
263 /// This is equivalent to the `file` CLI option `--special-files`.
264 #[doc(alias = "MAGIC_DEVICES")]
265 #[doc(alias = "--special-files")]
266 const DEVICES = libmagic::MAGIC_DEVICES;
267
268 /// Return a MIME type string, instead of a textual description
269 ///
270 /// See also: [`Flags::MIME`]
271 ///
272 /// This is equivalent to the `file` CLI option `--mime-type`.
273 ///
274 /// NOTE: `libmagic` uses non-standard MIME types for at least some built-in checks,
275 /// e.g. `inode/*` (also see [`Flags::SYMLINK`], [`Flags::DEVICES`]):
276 /// ```shell
277 /// $ file --mime-type /proc/self/exe
278 /// /proc/self/exe: inode/symlink
279 ///
280 /// $ file --mime-type /dev/sda
281 /// /dev/sda: inode/blockdevice
282 /// ```
283 #[doc(alias = "MAGIC_MIME_TYPE")]
284 #[doc(alias = "--mime-type")]
285 const MIME_TYPE = libmagic::MAGIC_MIME_TYPE;
286
287 /// Return all matches, not just the first
288 ///
289 /// This is equivalent to the `file` CLI option `--keep-going`.
290 #[doc(alias = "MAGIC_CONTINUE")]
291 #[doc(alias = "--keep-going")]
292 const CONTINUE = libmagic::MAGIC_CONTINUE;
293
294 /// Check the magic database for consistency and print warnings to `stderr`
295 ///
296 /// NOTE: Those warnings are printed by `libmagic` itself, no this Rust crate.
297 #[doc(alias = "MAGIC_CHECK")]
298 const CHECK = libmagic::MAGIC_CHECK;
299
300 /// On systems that support `utime(2)` or `utimes(2)`, attempt to preserve the access time of files analyzed
301 ///
302 /// This is equivalent to the `file` CLI option `--preserve-date`.
303 #[doc(alias = "MAGIC_PRESERVE_ATIME")]
304 #[doc(alias = "--preserve-date")]
305 const PRESERVE_ATIME = libmagic::MAGIC_PRESERVE_ATIME;
306
307 /// Don't translate unprintable characters to a `\\ooo` octal representation
308 ///
309 /// This is equivalent to the `file` CLI option `--raw`.
310 #[doc(alias = "MAGIC_RAW")]
311 #[doc(alias = "--raw")]
312 const RAW = libmagic::MAGIC_RAW;
313
314 /// Treat operating system errors while trying to open files and follow symlinks as real errors, instead of printing them in the magic buffer
315 #[doc(alias = "MAGIC_ERROR")]
316 const ERROR = libmagic::MAGIC_ERROR;
317
318 /// Return a MIME encoding, instead of a textual description
319 ///
320 /// See also: [`Flags::MIME`]
321 ///
322 /// This is equivalent to the `file` CLI option `--mime-encoding`.
323 ///
324 /// NOTE: `libmagic` uses non-standard MIME `charset` values, e.g. for binary files:
325 /// ```shell
326 /// $ file --mime-encoding /proc/self/exe
327 /// binary
328 /// ```
329 #[doc(alias = "MAGIC_MIME_ENCODING")]
330 #[doc(alias = "--mime-encoding")]
331 const MIME_ENCODING = libmagic::MAGIC_MIME_ENCODING;
332
333 /// A shorthand for `MIME_TYPE | MIME_ENCODING`
334 ///
335 /// See also: [`Flags::MIME_TYPE`], [`Flags::MIME_ENCODING`]
336 ///
337 /// This is equivalent to the `file` CLI option `--mime`.
338 ///
339 /// NOTE: `libmagic` returns a parseable MIME type with a `charset` field:
340 /// ```shell
341 /// $ file --mime /proc/self/exe
342 /// /proc/self/exe: inode/symlink; charset=binary
343 /// ```
344 #[doc(alias = "MAGIC_MIME")]
345 #[doc(alias = "--mime")]
346 const MIME = Self::MIME_TYPE.bits()
347 | Self::MIME_ENCODING.bits();
348
349 /// Return the Apple creator and type
350 ///
351 /// This is equivalent to the `file` CLI option `--apple`.
352 #[doc(alias = "MAGIC_APPLE")]
353 #[doc(alias = "--apple")]
354 const APPLE = libmagic::MAGIC_APPLE;
355
356 /// Return a slash-separated list of extensions for this file type
357 ///
358 /// This is equivalent to the `file` CLI option `--extension`.
359 ///
360 /// NOTE: `libmagic` returns a list with one or more extensions without a leading "." (dot):
361 /// ```shell
362 /// $ file --extension example.jpg
363 /// example.jpg: jpeg/jpg/jpe/jfif
364 ///
365 /// $ file --extension /proc/self/exe
366 /// /proc/self/exe: ???
367 /// ```
368 #[doc(alias = "MAGIC_EXTENSION")]
369 #[doc(alias = "--extension")]
370 const EXTENSION = libmagic::MAGIC_EXTENSION;
371
372 /// Don't report on compression, only report about the uncompressed data
373 ///
374 /// This is equivalent to the `file` CLI option `--uncompress-noreport`.
375 #[doc(alias = "MAGIC_COMPRESS_TRANSP")]
376 #[doc(alias = "--uncompress-noreport")]
377 const COMPRESS_TRANSP = libmagic::MAGIC_COMPRESS_TRANSP;
378
379 /// A shorthand for `EXTENSION | MIME | APPLE`
380 #[doc(alias = "MAGIC_NODESC")]
381 const NODESC = Self::EXTENSION.bits()
382 | Self::MIME.bits()
383 | Self::APPLE.bits();
384
385 /// Don't look inside compressed files
386 ///
387 /// This is equivalent to the `file` CLI option `--exclude compress`.
388 #[doc(alias = "MAGIC_NO_CHECK_COMPRESS")]
389 #[doc(alias = "--exclude compress")]
390 const NO_CHECK_COMPRESS = libmagic::MAGIC_NO_CHECK_COMPRESS;
391
392 /// Don't examine tar files
393 ///
394 /// This is equivalent to the `file` CLI option `--exclude tar`.
395 #[doc(alias = "MAGIC_NO_CHECK_TAR")]
396 #[doc(alias = "--exclude tar")]
397 const NO_CHECK_TAR = libmagic::MAGIC_NO_CHECK_TAR;
398
399 /// Don't consult magic files
400 ///
401 /// This is equivalent to the `file` CLI option `--exclude soft`.
402 #[doc(alias = "MAGIC_NO_CHECK_SOFT")]
403 #[doc(alias = "--exclude soft")]
404 const NO_CHECK_SOFT = libmagic::MAGIC_NO_CHECK_SOFT;
405
406 /// Check for EMX application type (only on EMX)
407 ///
408 /// This is equivalent to the `file` CLI option `--exclude apptype`.
409 #[doc(alias = "MAGIC_NO_CHECK_APPTYPE")]
410 #[doc(alias = "--exclude apptype")]
411 const NO_CHECK_APPTYPE = libmagic::MAGIC_NO_CHECK_APPTYPE;
412
413 /// Don't print ELF details
414 ///
415 /// This is equivalent to the `file` CLI option `--exclude elf`.
416 #[doc(alias = "MAGIC_NO_CHECK_ELF")]
417 #[doc(alias = "--exclude elf")]
418 const NO_CHECK_ELF = libmagic::MAGIC_NO_CHECK_ELF;
419
420 /// Don't check for various types of text files
421 ///
422 /// This is equivalent to the `file` CLI option `--exclude text`.
423 #[doc(alias = "MAGIC_NO_CHECK_TEXT")]
424 #[doc(alias = "--exclude text")]
425 const NO_CHECK_TEXT = libmagic::MAGIC_NO_CHECK_TEXT;
426
427 /// Don't get extra information on MS Composite Document Files
428 ///
429 /// This is equivalent to the `file` CLI option `--exclude cdf`.
430 #[doc(alias = "MAGIC_NO_CHECK_CDF")]
431 #[doc(alias = "--exclude cdf")]
432 const NO_CHECK_CDF = libmagic::MAGIC_NO_CHECK_CDF;
433
434 /// Don't examine CSV files
435 ///
436 /// This is equivalent to the `file` CLI option `--exclude csv`.
437 #[doc(alias = "MAGIC_NO_CHECK_CSV")]
438 #[doc(alias = "--exclude csv")]
439 const NO_CHECK_CSV = libmagic::MAGIC_NO_CHECK_CSV;
440
441 /// Don't look for known tokens inside ascii files
442 ///
443 /// This is equivalent to the `file` CLI option `--exclude tokens`.
444 #[doc(alias = "MAGIC_NO_CHECK_TOKENS")]
445 #[doc(alias = "--exclude tokens")]
446 const NO_CHECK_TOKENS = libmagic::MAGIC_NO_CHECK_TOKENS;
447
448 /// Don't check text encodings
449 ///
450 /// This is equivalent to the `file` CLI option `--exclude encoding`.
451 #[doc(alias = "MAGIC_NO_CHECK_ENCODING")]
452 #[doc(alias = "--exclude encoding")]
453 const NO_CHECK_ENCODING = libmagic::MAGIC_NO_CHECK_ENCODING;
454
455 /// Don't examine JSON files
456 ///
457 /// This is equivalent to the `file` CLI option `--exclude json`.
458 #[doc(alias = "MAGIC_NO_CHECK_JSON")]
459 #[doc(alias = "--exclude json")]
460 const NO_CHECK_JSON = libmagic::MAGIC_NO_CHECK_JSON;
461
462 /// No built-in tests; only consult the magic file
463 #[doc(alias = "MAGIC_NO_CHECK_BUILTIN")]
464 const NO_CHECK_BUILTIN = Self::NO_CHECK_COMPRESS.bits()
465 | Self::NO_CHECK_TAR.bits()
466 | Self::NO_CHECK_APPTYPE.bits()
467 | Self::NO_CHECK_ELF.bits()
468 | Self::NO_CHECK_TEXT.bits()
469 | Self::NO_CHECK_CSV.bits()
470 | Self::NO_CHECK_CDF.bits()
471 | Self::NO_CHECK_TOKENS.bits()
472 | Self::NO_CHECK_ENCODING.bits()
473 | Self::NO_CHECK_JSON.bits();
474 }
475 }
476
477 impl std::fmt::Display for Flags {
478 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
479 bitflags::parser::to_writer(self, f)
480 }
481 }
482
483 /// Invalid [`DatabasePaths`]
484 ///
485 /// This is returned from [`DatabasePaths::new()`](DatabasePaths::new)
486 #[derive(thiserror::Error, Debug)]
487 #[error("invalid database files path")]
488 pub struct InvalidDatabasePathError {}
489
490 /// Magic database file paths
491 ///
492 /// `libmagic` requires database file paths for certain operations on a [`Cookie`] that must:
493 /// - be a valid C string
494 /// - not contain ":" (colon), since that is used to separate multiple file paths (on all platforms)
495 ///
496 /// Those operations are [`Cookie::load()`](Cookie::load), [`Cookie::compile()`](Cookie::compile), [`Cookie::check()`](Cookie::check), [`Cookie::list()`](Cookie::list).\
497 /// [`Cookie::file()`](Cookie::file) does not take database file paths but the single file to inspect instead.
498 ///
499 /// The default unnamed database can be constructed with [`Default::default()`](DatabasePaths::default).
500 /// Explicit paths can be constructed manually with [`new()`](DatabasePaths::new) or by fallible conversion from an array, slice or Vec
501 /// containing something convertible as [`std::path::Path`], or a single something.
502 ///
503 /// Note that this only ensures the paths themselves are valid.
504 /// Operating on those database file paths can still fail,
505 /// for example if they refer to files that do not exist, can not be opened or do not have the required format.
506 ///
507 /// # Examples
508 ///
509 /// ```
510 /// # use std::convert::TryInto;
511 /// # use magic::cookie::DatabasePaths;
512 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
513 /// // `: DatabasePaths` type annotation is only needed for these examples
514 /// // if you pass it to Cookie::load() etc., Rust will figure it out
515 ///
516 /// // construct default unnamed database paths
517 /// let database: DatabasePaths = Default::default();
518 ///
519 /// // construct from single path
520 /// let database: DatabasePaths = "first-directory/first-database".try_into()?;
521 /// let database: DatabasePaths =
522 /// std::path::Path::new("second-directory/second-database").try_into()?;
523 ///
524 /// // construct from multiple paths in array
525 /// let array: [&'static str; 2] = [
526 /// "first-directory/first-database",
527 /// "second-directory/second-database",
528 /// ];
529 /// let database: DatabasePaths = array.try_into()?;
530 ///
531 /// // construct from multiple paths in slice
532 /// let database: DatabasePaths = [
533 /// "first-directory/first-database".as_ref(),
534 /// std::path::Path::new("second-directory/second-database"),
535 /// ]
536 /// .try_into()?;
537 ///
538 /// // construct from multiple paths in Vec
539 /// let database: DatabasePaths = vec![
540 /// std::ffi::OsStr::new("first-directory/first-database"),
541 /// "second-directory/second-database".as_ref(),
542 /// ]
543 /// .try_into()?;
544 /// # Ok(())
545 /// # }
546 /// ```
547 pub struct DatabasePaths {
548 filenames: Option<CString>,
549 }
550
551 const DATABASE_FILENAME_SEPARATOR: &str = ":";
552
553 impl DatabasePaths {
554 /// Create a new database paths instance
555 ///
556 /// Using one of the `TryFrom` implementations is recommended instead, see [`DatabasePaths`] examples.
557 ///
558 /// Empty `paths` returns [`Default::default()`](DatabasePaths::default).
559 ///
560 /// # Errors
561 ///
562 /// If the `paths` contain a ":" (colon), a [`cookie::InvalidDatabasePathError`](InvalidDatabasePathError) will be returned.
563 ///
564 pub fn new<I, P>(paths: I) -> Result<Self, InvalidDatabasePathError>
565 where
566 I: IntoIterator<Item = P>,
567 P: AsRef<Path>,
568 {
569 // this is not the most efficient nor correct for Windows, but consistent with previous behaviour
570
571 let filename = paths
572 .into_iter()
573 .map(|f| f.as_ref().to_string_lossy().into_owned())
574 .collect::<Vec<String>>()
575 .join(DATABASE_FILENAME_SEPARATOR);
576
577 Ok(Self {
578 filenames: match filename.is_empty() {
579 true => None,
580 _ => Some(CString::new(filename).map_err(|_| InvalidDatabasePathError {})?),
581 },
582 })
583 }
584 }
585
586 impl Default for DatabasePaths {
587 /// Returns the path for the default unnamed database/s
588 ///
589 /// Note that the default database/s can be overwritten by setting the "MAGIC" environment variable
590 /// to a colon-separated text of database file paths:
591 /// ```shell
592 /// $ export MAGIC='data/tests/db-python:data/tests/db-images-png-precompiled.mgc'
593 /// $ # file-ish uses `DatabasePaths::default()`
594 /// $ cargo run --example file-ish -- data/tests/rust-logo-128x128-blk.png
595 /// ```
596 /// This is a feature of `libmagic` itself, not of this Rust crate.
597 ///
598 /// Note that the `file` CLI (which uses `libmagic`) prints the location of its default database with:
599 /// ```shell
600 /// $ file --version
601 /// file-5.38
602 /// magic file from /etc/magic:/usr/share/misc/magic
603 ///
604 /// $ export MAGIC='data/tests/db-python:data/tests/db-images-png-precompiled.mgc'
605 /// $ file --version
606 /// file-5.39
607 /// magic file from data/tests/db-python:data/tests/db-images-png-precompiled.mgc
608 /// ```
609 fn default() -> Self {
610 Self { filenames: None }
611 }
612 }
613
614 impl<P: AsRef<std::path::Path>, const N: usize> TryFrom<[P; N]> for DatabasePaths {
615 type Error = InvalidDatabasePathError;
616
617 /// Invokes [`DatabasePaths::new()`](DatabasePaths::new)
618 fn try_from(value: [P; N]) -> Result<Self, <Self as TryFrom<[P; N]>>::Error> {
619 Self::new(value)
620 }
621 }
622
623 impl<P: AsRef<std::path::Path>> TryFrom<Vec<P>> for DatabasePaths {
624 type Error = InvalidDatabasePathError;
625
626 /// Invokes [`DatabasePaths::new()`](DatabasePaths::new)
627 fn try_from(value: Vec<P>) -> Result<Self, <Self as TryFrom<Vec<P>>>::Error> {
628 Self::new(value)
629 }
630 }
631
632 impl<P: AsRef<std::path::Path>> TryFrom<&'_ [P]> for DatabasePaths {
633 type Error = InvalidDatabasePathError;
634
635 /// Invokes [`DatabasePaths::new()`](DatabasePaths::new)
636 fn try_from(value: &[P]) -> Result<Self, <Self as TryFrom<&[P]>>::Error> {
637 Self::new(value)
638 }
639 }
640
641 macro_rules! databasepaths_try_from_impl {
642 ($t:ty) => {
643 impl TryFrom<$t> for DatabasePaths {
644 type Error = InvalidDatabasePathError;
645
646 /// Invokes [`DatabasePaths::new()`](DatabasePaths::new)
647 fn try_from(value: $t) -> Result<Self, <Self as TryFrom<$t>>::Error> {
648 DatabasePaths::new(std::iter::once(value))
649 }
650 }
651 };
652 }
653
654 // missing for now are:
655 // - Cow<'_, OsStr>
656 // - std::path::Component<'_>
657 // - std::path::Components<'_>
658 // - std::path::Iter<'_>
659 databasepaths_try_from_impl!(&str);
660 databasepaths_try_from_impl!(&std::ffi::OsStr);
661 databasepaths_try_from_impl!(std::ffi::OsString);
662 databasepaths_try_from_impl!(&std::path::Path);
663 databasepaths_try_from_impl!(std::path::PathBuf);
664 databasepaths_try_from_impl!(String);
665
666 /// Error within several [`Cookie`] functions
667 ///
668 /// Most functions on a [`Cookie`] can return an error from `libmagic`,
669 /// which unfortunately is not very structured.
670 #[derive(thiserror::Error, Debug)]
671 #[error("magic cookie error in `libmagic` function {}", .function)]
672 pub struct Error {
673 function: &'static str,
674 //#[backtrace]
675 source: crate::ffi::CookieError,
676 }
677
678 #[doc(hidden)]
679 #[derive(Debug)]
680 pub enum Open {}
681
682 #[doc(hidden)]
683 #[derive(Debug)]
684 pub enum Load {}
685
686 mod private {
687 pub trait Sealed {}
688
689 impl Sealed for super::Open {}
690 impl Sealed for super::Load {}
691 }
692
693 #[doc(hidden)]
694 pub trait State: private::Sealed {}
695
696 impl State for Open {}
697 impl State for Load {}
698
699 /// Combined configuration of [`Flags`], parameters and databases
700 ///
701 /// A "cookie" is `libmagic` lingo for a combined configuration of
702 /// - [`cookie::Flags`](crate::cookie::Flags)
703 /// - parameters (not implemented yet)
704 /// - loaded datbases, e.g. [`cookie::DatabasePaths`](crate::cookie::DatabasePaths)
705 ///
706 /// A cookie advances through 2 states: opened, then loaded.
707 /// ```
708 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
709 /// // create new cookie in initial opened state with given flags
710 /// let cookie = magic::Cookie::open(magic::cookie::Flags::default())?;
711 ///
712 /// // advance cookie into loaded state
713 /// let cookie = cookie.load(&magic::cookie::DatabasePaths::default())?;
714 /// # Ok(())
715 /// # }
716 /// ```
717 ///
718 /// In either state, you can use operations that do not require
719 /// already loaded magic databases:
720 /// - [`Cookie::load()`](Cookie::load), [`Cookie::load_buffers()`](Cookie::load_buffers) to load databases and transition into the loaded state
721 /// - [`Cookie::set_flags()`](Cookie::set_flags) to overwrite the initial flags given in [`Cookie::open()`](Cookie::open)
722 /// - [`Cookie::compile()`](Cookie::compile), [`Cookie::check()`](Cookie::check), [`Cookie::list()`](Cookie::list) to operate on magic database files
723 ///
724 /// Once in the loaded state, you can perform magic "queries":
725 /// - [`Cookie::file()`](Cookie::file), [`Cookie::buffer()`](Cookie::buffer)
726 #[derive(Debug)]
727 #[doc(alias = "magic_t")]
728 #[doc(alias = "magic_set")]
729 pub struct Cookie<S: State> {
730 cookie: crate::ffi::Cookie,
731 marker: std::marker::PhantomData<S>,
732 }
733
734 /// Error within [`Cookie::load()`](Cookie::load) or [`Cookie::load_buffers()`](Cookie::load_buffers)
735 ///
736 /// This is like [`cookie:Error`](Error) but also has the cookie in its original state.
737 ///
738 /// # Examples
739 ///
740 /// ```
741 /// # use std::convert::TryInto;
742 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
743 /// let cookie = magic::Cookie::open(Default::default())?;
744 /// let database = "data/tests/db-images-png".try_into()?;
745 /// // try to load an existing database, consuming and returning early
746 /// let cookie = cookie.load(&database)?;
747 ///
748 /// let database = "doesntexist.mgc".try_into()?;
749 /// // load a database that does not exist
750 /// let cookie = match cookie.load(&database) {
751 /// Err(err) => {
752 /// println!("whoopsie: {:?}", err);
753 /// // recover the loaded cookie without dropping it
754 /// err.cookie()
755 /// },
756 /// Ok(cookie) => cookie,
757 /// };
758 ///
759 /// let database = "data/tests/db-python".try_into()?;
760 /// // try to load another existing database
761 /// let cookie = cookie.load(&database)?;
762 /// # Ok(())
763 /// # }
764 /// ```
765 #[derive(thiserror::Error, Debug)]
766 #[error("magic cookie error in `libmagic` function {}", .function)]
767 pub struct LoadError<S: State> {
768 function: &'static str,
769 //#[backtrace]
770 source: crate::ffi::CookieError,
771 cookie: Cookie<S>,
772 }
773
774 impl<S: State> LoadError<S> {
775 /// Returns the cookie in its original state
776 pub fn cookie(self) -> Cookie<S> {
777 self.cookie
778 }
779 }
780
781 impl<S: State> Drop for Cookie<S> {
782 /// Closes the loaded magic database files and deallocates any resources used
783 #[doc(alias = "magic_close")]
784 fn drop(&mut self) {
785 crate::ffi::close(&mut self.cookie);
786 }
787 }
788
789 /// Operations that are valid in the `Open` state
790 ///
791 /// A new cookie created with [`Cookie::open`](Cookie::open) does not have any databases [loaded](Cookie::load).
792 impl Cookie<Open> {
793 /// Creates a new configuration cookie, `flags` specify how other operations on this cookie should behave
794 ///
795 /// This does not [`load()`](Cookie::load) any databases yet.
796 ///
797 /// The `flags` can be changed lateron with [`set_flags()`](Cookie::set_flags).
798 ///
799 /// # Examples
800 ///
801 /// ```
802 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
803 /// // open a new cookie with default flags
804 /// let cookie = magic::Cookie::open(Default::default())?;
805 ///
806 /// // open a new cookie with custom flags
807 /// let flags = magic::cookie::Flags::COMPRESS | magic::cookie::Flags::DEVICES;
808 /// let cookie = magic::Cookie::open(flags)?;
809 /// # Ok(())
810 /// # }
811 /// ```
812 ///
813 /// # Errors
814 ///
815 /// If there was an `libmagic` internal error allocating a new cookie, a [`cookie::OpenError`](OpenError) will be returned.
816 ///
817 /// If the given `flags` are unsupported on the current platform, a [`cookie::OpenError`](OpenError) will be returned.
818 #[doc(alias = "magic_open")]
819 pub fn open(flags: Flags) -> Result<Cookie<Open>, OpenError> {
820 match crate::ffi::open(flags.bits()) {
821 Err(err) => Err(OpenError {
822 flags,
823 kind: match err.errno().kind() {
824 std::io::ErrorKind::InvalidInput => OpenErrorKind::UnsupportedFlags,
825 _ => OpenErrorKind::Errno,
826 },
827 source: err,
828 }),
829 Ok(cookie) => {
830 let cookie = Cookie {
831 cookie,
832 marker: std::marker::PhantomData,
833 };
834 Ok(cookie)
835 }
836 }
837 }
838 }
839
840 /// Operations that are valid in the `Load` state
841 ///
842 /// An opened cookie with [loaded](Cookie::load) databases can inspect [files](Cookie::file) and [buffers](Cookie::buffer).
843 impl Cookie<Load> {
844 /// Returns a textual description of the contents of the file `filename`
845 ///
846 /// Requires to [`load()`](Cookie::load) databases before calling.
847 ///
848 /// # Examples
849 ///
850 /// ```
851 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
852 /// // open a new cookie with default flags and database
853 /// let cookie = magic::Cookie::open(Default::default())?.load(&Default::default())?;
854 ///
855 /// let file_description = cookie.file("data/tests/rust-logo-128x128-blk.png");
856 /// # Ok(())
857 /// # }
858 /// ```
859 ///
860 /// # Errors
861 ///
862 /// If there was an `libmagic` internal error, a [`cookie::Error`](Error) will be returned.
863 ///
864 /// # Panics
865 ///
866 /// Panics if `libmagic` violates its API contract, e.g. by not setting the last error.
867 #[doc(alias = "magic_file")]
868 pub fn file<P: AsRef<Path>>(&self, filename: P) -> Result<String, Error> {
869 let c_string = CString::new(filename.as_ref().to_string_lossy().into_owned()).unwrap();
870 match crate::ffi::file(&self.cookie, c_string.as_c_str()) {
871 Ok(res) => Ok(res.to_string_lossy().to_string()),
872 Err(err) => Err(Error {
873 function: "magic_file",
874 source: err,
875 }),
876 }
877 }
878
879 /// Returns a textual description of the contents of the `buffer`
880 ///
881 /// Requires to [`load()`](Cookie::load) databases before calling.
882 ///
883 /// # Examples
884 ///
885 /// ```
886 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
887 /// // open a new cookie with default flags and database
888 /// let cookie = magic::Cookie::open(Default::default())?.load(&Default::default())?;
889 ///
890 /// let buffer = b"%PDF-\xE2\x80\xA6";
891 /// let buffer_description = cookie.buffer(buffer);
892 /// # Ok(())
893 /// # }
894 /// ```
895 ///
896 /// # Errors
897 ///
898 /// If there was an `libmagic` internal error, a [`cookie::Error`](Error) will be returned.
899 ///
900 /// # Panics
901 ///
902 /// Panics if `libmagic` violates its API contract, e.g. by not setting the last error.
903 #[doc(alias = "magic_buffer")]
904 pub fn buffer(&self, buffer: &[u8]) -> Result<String, Error> {
905 match crate::ffi::buffer(&self.cookie, buffer) {
906 Ok(res) => Ok(res.to_string_lossy().to_string()),
907 Err(err) => Err(Error {
908 function: "magic_buffer",
909 source: err,
910 }),
911 }
912 }
913 }
914
915 /// Operations that are valid in any state
916 impl<S: State> Cookie<S> {
917 /// Loads the given database `filenames` for further queries
918 ///
919 /// Adds ".mgc" to the database filenames as appropriate.
920 ///
921 /// Calling `load()` or [`load_buffers()`](Cookie::load_buffers) replaces the previously loaded database/s.
922 ///
923 /// This is equivalent to the using the `file` CLI:
924 /// ```shell
925 /// $ file --magic-file 'data/tests/db-images-png:data/tests/db-python' --version
926 /// file-5.39
927 /// magic file from data/tests/db-images-png:data/tests/db-python
928 /// ```
929 ///
930 /// # Examples
931 /// ```rust
932 /// # use std::convert::TryInto;
933 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
934 /// // open a new cookie with default flags
935 /// let cookie = magic::Cookie::open(Default::default())?;
936 ///
937 /// // load the default unnamed database
938 /// let database = Default::default();
939 /// let cookie = cookie.load(&database)?;
940 ///
941 /// // load databases from files
942 /// let databases = ["data/tests/db-images-png", "data/tests/db-python"].try_into()?;
943 /// let cookie = cookie.load(&databases)?;
944 ///
945 /// // load precompiled database from file
946 /// let database = "data/tests/db-images-png-precompiled.mgc".try_into()?;
947 /// let cookie = cookie.load(&database)?;
948 /// # Ok(())
949 /// # }
950 /// ```
951 ///
952 /// # Errors
953 ///
954 /// If there was an `libmagic` internal error, a [`cookie::LoadError`](LoadError) will be returned,
955 /// which contains the cookie in its original state.
956 ///
957 /// # Panics
958 ///
959 /// Panics if `libmagic` violates its API contract, e.g. by not setting the last error or returning undefined data.
960 #[doc(alias = "magic_load")]
961 #[doc(alias = "--magic-file")]
962 pub fn load(self, filenames: &DatabasePaths) -> Result<Cookie<Load>, LoadError<S>> {
963 match crate::ffi::load(&self.cookie, filenames.filenames.as_deref()) {
964 Err(err) => Err(LoadError {
965 function: "magic_load",
966 source: err,
967 cookie: self,
968 }),
969 Ok(_) => {
970 let mut cookie = std::mem::ManuallyDrop::new(self);
971
972 let cookie = Cookie {
973 cookie: crate::ffi::Cookie::new(&mut cookie.cookie),
974 marker: std::marker::PhantomData,
975 };
976 Ok(cookie)
977 }
978 }
979 }
980
981 /// Loads the given compiled databases `buffers` for further queries
982 ///
983 /// Databases need to be compiled with a compatible `libmagic` version.
984 ///
985 /// This function can be used in environments where `libmagic` does
986 /// not have direct access to the filesystem, but can access the magic
987 /// database via shared memory or other IPC means.
988 ///
989 /// Calling `load_buffers()` or [`load()`](Cookie::load) replaces the previously loaded database/s.
990 ///
991 /// # Errors
992 ///
993 /// If there was an `libmagic` internal error, a [`cookie::LoadError`](LoadError) will be returned,
994 /// which contains the cookie in its original state.
995 ///
996 /// # Panics
997 ///
998 /// Panics if `libmagic` violates its API contract, e.g. by not setting the last error or returning undefined data.
999 #[doc(alias = "magic_load_buffers")]
1000 pub fn load_buffers(self, buffers: &[&[u8]]) -> Result<Cookie<Load>, LoadError<S>> {
1001 match crate::ffi::load_buffers(&self.cookie, buffers) {
1002 Err(err) => Err(LoadError {
1003 function: "magic_load_buffers",
1004 source: err,
1005 cookie: self,
1006 }),
1007 Ok(_) => {
1008 let mut cookie = std::mem::ManuallyDrop::new(self);
1009
1010 let cookie = Cookie {
1011 cookie: crate::ffi::Cookie::new(&mut cookie.cookie),
1012 marker: std::marker::PhantomData,
1013 };
1014 Ok(cookie)
1015 }
1016 }
1017 }
1018
1019 /// Sets the `flags` to use for this configuration
1020 ///
1021 /// Overwrites any previously set flags, e.g. those from [`load()`](Cookie::load).
1022 ///
1023 /// # Examples
1024 /// ```rust
1025 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
1026 /// // open a new cookie with initial default flags
1027 /// let cookie = magic::Cookie::open(Default::default())?;
1028 ///
1029 /// // overwrite the initial flags
1030 /// let flags = magic::cookie::Flags::COMPRESS | magic::cookie::Flags::DEVICES;
1031 /// cookie.set_flags(flags)?;
1032 /// # Ok(())
1033 /// # }
1034 /// ```
1035 ///
1036 /// # Errors
1037 ///
1038 /// If the given `flags` are unsupported on the current platform, an [`cookie::SetFlagsError`](SetFlagsError) will be returned.
1039 #[doc(alias = "magic_setflags")]
1040 pub fn set_flags(&self, flags: Flags) -> Result<(), SetFlagsError> {
1041 let ret = crate::ffi::setflags(&self.cookie, flags.bits());
1042 match ret {
1043 // according to `libmagic` man page this is the only flag that could be unsupported
1044 Err(err) => Err(SetFlagsError {
1045 flags: Flags::PRESERVE_ATIME,
1046 source: err,
1047 }),
1048 Ok(_) => Ok(()),
1049 }
1050 }
1051
1052 // TODO: check, compile, list and load mostly do the same, refactor!
1053
1054 /// Compiles the given database files `filenames` for faster access
1055 ///
1056 /// The compiled files created are named from the `basename` of each file argument with ".mgc" appended to it.
1057 ///
1058 /// This is equivalent to the following `file` CLI command:
1059 /// ```shell
1060 /// $ file --compile --magic-file data/tests/db-images-png:data/tests/db-python
1061 /// ```
1062 ///
1063 /// # Errors
1064 ///
1065 /// If there was an `libmagic` internal error, a [`cookie::Error`](Error) will be returned.
1066 ///
1067 /// # Panics
1068 ///
1069 /// Panics if `libmagic` violates its API contract, e.g. by not setting the last error or returning undefined data.
1070 #[doc(alias = "magic_compile")]
1071 #[doc(alias = "--compile")]
1072 pub fn compile(&self, filenames: &DatabasePaths) -> Result<(), Error> {
1073 match crate::ffi::compile(&self.cookie, filenames.filenames.as_deref()) {
1074 Err(err) => Err(Error {
1075 function: "magic_compile",
1076 source: err,
1077 }),
1078 Ok(_) => Ok(()),
1079 }
1080 }
1081
1082 /// Checks the validity of entries in the database files `filenames`
1083 ///
1084 /// # Errors
1085 ///
1086 /// If there was an `libmagic` internal error, a [`cookie::Error`](Error) will be returned.
1087 ///
1088 /// # Panics
1089 ///
1090 /// Panics if `libmagic` violates its API contract, e.g. by not setting the last error or returning undefined data.
1091 #[doc(alias = "magic_check")]
1092 pub fn check(&self, filenames: &DatabasePaths) -> Result<(), Error> {
1093 match crate::ffi::check(&self.cookie, filenames.filenames.as_deref()) {
1094 Err(err) => Err(Error {
1095 function: "magic_check",
1096 source: err,
1097 }),
1098 Ok(_) => Ok(()),
1099 }
1100 }
1101
1102 /// Dumps all magic entries in the given database files `filenames` in a human readable format
1103 ///
1104 /// This is equivalent to the following `file` CLI command:
1105 /// ```shell
1106 /// $ file --checking-printout --magic-file data/tests/db-images-png:data/tests/db-python
1107 /// ```
1108 ///
1109 /// # Errors
1110 ///
1111 /// If there was an `libmagic` internal error, a [`cookie::Error`](Error) will be returned.
1112 ///
1113 /// # Panics
1114 ///
1115 /// Panics if `libmagic` violates its API contract, e.g. by not setting the last error or returning undefined data.
1116 #[doc(alias = "magic_list")]
1117 #[doc(alias = "--checking-printout")]
1118 pub fn list(&self, filenames: &DatabasePaths) -> Result<(), Error> {
1119 match crate::ffi::list(&self.cookie, filenames.filenames.as_deref()) {
1120 Err(err) => Err(Error {
1121 function: "magic_list",
1122 source: err,
1123 }),
1124 Ok(_) => Ok(()),
1125 }
1126 }
1127 }
1128
1129 /// Error within [`Cookie::open()`](Cookie::open)
1130 ///
1131 /// Note that a similar [`cookie::SetFlagsError`](SetFlagsError) can also occur
1132 #[derive(thiserror::Error, Debug)]
1133 #[error("could not open magic cookie: {}",
1134 match .kind {
1135 OpenErrorKind::UnsupportedFlags => format!("unsupported flags {}", .flags),
1136 OpenErrorKind::Errno => "other error".to_string(),
1137 }
1138 )]
1139 pub struct OpenError {
1140 flags: Flags,
1141 kind: OpenErrorKind,
1142 //#[backtrace]
1143 source: crate::ffi::OpenError,
1144 }
1145
1146 /// Kind of [`OpenError`]
1147 #[derive(Debug)]
1148 enum OpenErrorKind {
1149 /// Unsupported flags given
1150 UnsupportedFlags,
1151 /// Other kind
1152 Errno,
1153 }
1154
1155 /// Error within [`Cookie::set_flags()`](Cookie::set_flags)
1156 ///
1157 /// Note that a similar [`cookie::OpenError`](OpenError) can also occur
1158 #[derive(thiserror::Error, Debug)]
1159 #[error("could not set magic cookie flags {}", .flags)]
1160 pub struct SetFlagsError {
1161 flags: Flags,
1162 //#[backtrace]
1163 source: crate::ffi::SetFlagsError,
1164 }
1165} // mod cookie
1166
1167pub use crate::cookie::Cookie;
1168
1169#[cfg(test)]
1170mod tests {
1171 use super::cookie::Flags;
1172 use super::Cookie;
1173 use std::convert::TryInto;
1174
1175 // Using relative paths to test files should be fine, since cargo doc
1176 // https://doc.rust-lang.org/cargo/reference/build-scripts.html#inputs-to-the-build-script
1177 // states that cwd == CARGO_MANIFEST_DIR
1178
1179 #[test]
1180 fn file() {
1181 let cookie = Cookie::open(Flags::ERROR).unwrap();
1182 let databases = &["data/tests/db-images-png"].try_into().unwrap();
1183 let cookie = cookie.load(databases).unwrap();
1184
1185 let path = "data/tests/rust-logo-128x128-blk.png";
1186
1187 assert_eq!(
1188 cookie.file(path).ok().unwrap(),
1189 "PNG image data, 128 x 128, 8-bit/color RGBA, non-interlaced"
1190 );
1191
1192 cookie.set_flags(Flags::MIME_TYPE).unwrap();
1193 assert_eq!(cookie.file(path).ok().unwrap(), "image/png");
1194
1195 cookie
1196 .set_flags(Flags::MIME_TYPE | Flags::MIME_ENCODING)
1197 .unwrap();
1198 assert_eq!(cookie.file(path).ok().unwrap(), "image/png; charset=binary");
1199 }
1200
1201 #[test]
1202 fn buffer() {
1203 let cookie = Cookie::open(Flags::ERROR).unwrap();
1204 let databases = &["data/tests/db-python"].try_into().unwrap();
1205 let cookie = cookie.load(databases).unwrap();
1206
1207 let s = b"#!/usr/bin/env python\nprint('Hello, world!')";
1208 assert_eq!(
1209 cookie.buffer(s).ok().unwrap(),
1210 "Python script, ASCII text executable"
1211 );
1212
1213 cookie.set_flags(Flags::MIME_TYPE).unwrap();
1214 assert_eq!(cookie.buffer(s).ok().unwrap(), "text/x-python");
1215 }
1216
1217 #[test]
1218 fn file_error() {
1219 let cookie = Cookie::open(Flags::ERROR).unwrap();
1220 let cookie = cookie.load(&Default::default()).unwrap();
1221
1222 let ret = cookie.file("non-existent_file.txt");
1223 assert!(ret.is_err());
1224 }
1225
1226 #[test]
1227 fn load_default() {
1228 let cookie = Cookie::open(Flags::ERROR).unwrap();
1229 assert!(cookie.load(&Default::default()).is_ok());
1230 }
1231
1232 #[test]
1233 fn load_one() {
1234 let cookie = Cookie::open(Flags::ERROR).unwrap();
1235 let databases = &["data/tests/db-images-png"].try_into().unwrap();
1236 assert!(cookie.load(databases).is_ok());
1237 }
1238
1239 #[test]
1240 fn load_multiple() {
1241 let cookie = Cookie::open(Flags::ERROR).unwrap();
1242 let databases = &["data/tests/db-images-png", "data/tests/db-python"]
1243 .try_into()
1244 .unwrap();
1245 assert!(cookie.load(databases).is_ok());
1246 }
1247
1248 // TODO:
1249 //static_assertions::assert_impl_all!(Cookie<S>: std::fmt::Debug);
1250
1251 #[test]
1252 fn load_buffers_file() {
1253 let cookie = Cookie::open(Flags::ERROR).unwrap();
1254 // file --compile --magic-file data/tests/db-images-png
1255 let magic_database = std::fs::read("data/tests/db-images-png-precompiled.mgc").unwrap();
1256 let buffers = vec![magic_database.as_slice()];
1257 let cookie = cookie.load_buffers(&buffers).unwrap();
1258
1259 let path = "data/tests/rust-logo-128x128-blk.png";
1260 assert_eq!(
1261 cookie.file(path).ok().unwrap(),
1262 "PNG image data, 128 x 128, 8-bit/color RGBA, non-interlaced"
1263 );
1264 }
1265
1266 #[test]
1267 fn libmagic_version() {
1268 let version = super::libmagic_version();
1269
1270 assert!(version > 500);
1271 }
1272}
1273
1274#[cfg(doctest)]
1275#[doc=include_str!("../README-crate.md")]
1276mod readme {}