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 {}