packages feed

magic 1.1.1 → 1.1.2

raw patch · 12 files changed

+456/−70 lines, 12 filesdep +unix

Dependencies added: unix

Files

CHANGELOG.md view
@@ -2,6 +2,18 @@  All notable changes to this project are documented here. +## 1.1.2 (2026-06-09)++Backwards-compatible additions: existing code continues to work unchanged. This release rounds out the binding to cover (almost) all of `libmagic`'s public API.++- Added the remaining `MagicFlag` constructors from `<magic.h>`: `MagicApple`, `MagicExtension`, `MagicCompressTransp`, `MagicNoCompressFork`, `MagicNodesc`, and the whole no-check family (`MagicNoCheckCompress`, `…Tar`, `…Soft`, `…Apptype`, `…Elf`, `…Text`, `…Cdf`, `…Csv`, `…Tokens`, `…Encoding`, `…Json`, `…Simh`) plus `MagicNoCheckBuiltin`.+- Added new operations in `Magic.Operations`: `magicDescriptor` (identify an open file descriptor), `magicByteString` (binary-safe identification of a strict `ByteString`), `magicGetFlags`, `magicGetParam`/`magicSetParam` with the new `MagicParam` type, `magicCheck`, `magicGetPath`, `magicVersion`, and `magicErrno`.+- `magicGetParam`/`magicSetParam` require `libmagic` >= 5.21; `magicGetFlags` requires >= 5.05. All current systems are well past this.+- New dependency: `bytestring` (a GHC boot library) for the `ByteString` entry points.+- Moved the `CMagic` phantom type (the tag behind the `Magic` handle) into `Magic.Types` and removed the internal `Magic.TypesLL` module. `CMagic` is now documented and linkable from the exposed `Magic.Types`. Documentation polish throughout: full Haddock coverage with no warnings, `@since` annotations on the new API, and default values listed for each `MagicParam`.+- `magic_load` is now imported as a `safe` foreign call (it was `unsafe`). It reads and parses the magic database from disk (blocking I/O), so under the threaded runtime loading a database no longer stalls other Haskell threads or blocks garbage collection. No API change.+- Documented the thread-safety contract of the `Magic` handle: a single handle is not safe for concurrent use (serialise access or use one per thread); distinct handles are independent.+ ## 1.1.1 (2026-06-09)  No API changes: this release is a drop-in replacement for 1.1.
README.md view
@@ -2,7 +2,7 @@  It is a binding to the C [libmagic](https://www.darwinsys.com/file/) library. It allows you to determine the type of a file not by looking at its name or extension, but rather by examining the contents itself. -libmagic can provide either a textual description or a MIME content type (and, occasionally, also a character set). The Haskell binding can also provide reports over Haskell strings.+libmagic can provide either a textual description or a MIME content type (and, occasionally, also a character set). The Haskell binding can examine files, open file descriptors, and in-memory data (as a `String` or, for binary content, a strict `ByteString`).  ## Requirements 
magic.cabal view
@@ -1,6 +1,6 @@ cabal-version:      2.4 name:               magic-version:            1.1.1+version:            1.1.2 synopsis:           Interface to C file/magic library description:   This package provides a Haskell interface to the C libmagic library.@@ -85,14 +85,15 @@    other-modules:     Magic.Utils-    Magic.TypesLL    default-extensions:     EmptyDataDecls     ForeignFunctionInterface     TypeSynonymInstances -  build-depends:    base >=4.9 && <5+  build-depends:+    , base        >=4.9  && <5+    , bytestring  >=0.10 && <0.13    if flag(pkgconfig)     pkgconfig-depends: libmagic@@ -116,3 +117,4 @@     , filepath    >=1.4  && <1.6     , HUnit       >=1.6  && <1.7     , magic+    , unix        >=2.8  && <2.9
src/Magic/Data.hsc view
@@ -12,7 +12,7 @@ to Haskell. -} -module Magic.Data (module Magic.Data) where+module Magic.Data (MagicFlag(..)) where  #include "magic.h" @@ -47,6 +47,42 @@       MagicRaw     | -- | Treat errors while examining a file as real errors instead of embedding them in the result.       MagicError+    | -- | Return the Apple creator and type.+      MagicApple+    | -- | Return a slash-separated list of valid file extensions for the detected type.+      MagicExtension+    | -- | Report on the compressed contents only, without mentioning the compression itself (transparent decompression).+      MagicCompressTransp+    | -- | Do not allow decompression that requires forking a helper process.+      MagicNoCompressFork+    | -- | Composite of 'MagicExtension', 'MagicMime' and 'MagicApple': return identifiers rather than a textual description.+      MagicNodesc+    | -- | Do not look inside compressed files.+      MagicNoCheckCompress+    | -- | Do not examine tar archives.+      MagicNoCheckTar+    | -- | Do not consult the magic database entries (soft magic).+      MagicNoCheckSoft+    | -- | Do not check for an application type (e.g. EMX).+      MagicNoCheckApptype+    | -- | Do not examine ELF details.+      MagicNoCheckElf+    | -- | Do not examine text files.+      MagicNoCheckText+    | -- | Do not examine CDF (Microsoft Compound Document) files.+      MagicNoCheckCdf+    | -- | Do not examine CSV files.+      MagicNoCheckCsv+    | -- | Do not look for known text tokens.+      MagicNoCheckTokens+    | -- | Do not check text encodings.+      MagicNoCheckEncoding+    | -- | Do not examine JSON files.+      MagicNoCheckJson+    | -- | Do not examine SIMH tape files.+      MagicNoCheckSimh+    | -- | Disable all built-in tests; consult only the magic database.+      MagicNoCheckBuiltin     | -- | A flag value returned by libmagic that these bindings do not       --   recognise, carrying its raw integer value.       UnknownMagicFlag Int@@ -66,6 +102,24 @@  toEnum (#{const MAGIC_PRESERVE_ATIME}) = MagicPreserveAtime  toEnum (#{const MAGIC_RAW}) = MagicRaw  toEnum (#{const MAGIC_ERROR}) = MagicError+ toEnum (#{const MAGIC_APPLE}) = MagicApple+ toEnum (#{const MAGIC_EXTENSION}) = MagicExtension+ toEnum (#{const MAGIC_COMPRESS_TRANSP}) = MagicCompressTransp+ toEnum (#{const MAGIC_NO_COMPRESS_FORK}) = MagicNoCompressFork+ toEnum (#{const MAGIC_NODESC}) = MagicNodesc+ toEnum (#{const MAGIC_NO_CHECK_COMPRESS}) = MagicNoCheckCompress+ toEnum (#{const MAGIC_NO_CHECK_TAR}) = MagicNoCheckTar+ toEnum (#{const MAGIC_NO_CHECK_SOFT}) = MagicNoCheckSoft+ toEnum (#{const MAGIC_NO_CHECK_APPTYPE}) = MagicNoCheckApptype+ toEnum (#{const MAGIC_NO_CHECK_ELF}) = MagicNoCheckElf+ toEnum (#{const MAGIC_NO_CHECK_TEXT}) = MagicNoCheckText+ toEnum (#{const MAGIC_NO_CHECK_CDF}) = MagicNoCheckCdf+ toEnum (#{const MAGIC_NO_CHECK_CSV}) = MagicNoCheckCsv+ toEnum (#{const MAGIC_NO_CHECK_TOKENS}) = MagicNoCheckTokens+ toEnum (#{const MAGIC_NO_CHECK_ENCODING}) = MagicNoCheckEncoding+ toEnum (#{const MAGIC_NO_CHECK_JSON}) = MagicNoCheckJson+ toEnum (#{const MAGIC_NO_CHECK_SIMH}) = MagicNoCheckSimh+ toEnum (#{const MAGIC_NO_CHECK_BUILTIN}) = MagicNoCheckBuiltin  toEnum x = UnknownMagicFlag x   fromEnum MagicNone = (#{const MAGIC_NONE})@@ -81,6 +135,24 @@  fromEnum MagicPreserveAtime = (#{const MAGIC_PRESERVE_ATIME})  fromEnum MagicRaw = (#{const MAGIC_RAW})  fromEnum MagicError = (#{const MAGIC_ERROR})+ fromEnum MagicApple = (#{const MAGIC_APPLE})+ fromEnum MagicExtension = (#{const MAGIC_EXTENSION})+ fromEnum MagicCompressTransp = (#{const MAGIC_COMPRESS_TRANSP})+ fromEnum MagicNoCompressFork = (#{const MAGIC_NO_COMPRESS_FORK})+ fromEnum MagicNodesc = (#{const MAGIC_NODESC})+ fromEnum MagicNoCheckCompress = (#{const MAGIC_NO_CHECK_COMPRESS})+ fromEnum MagicNoCheckTar = (#{const MAGIC_NO_CHECK_TAR})+ fromEnum MagicNoCheckSoft = (#{const MAGIC_NO_CHECK_SOFT})+ fromEnum MagicNoCheckApptype = (#{const MAGIC_NO_CHECK_APPTYPE})+ fromEnum MagicNoCheckElf = (#{const MAGIC_NO_CHECK_ELF})+ fromEnum MagicNoCheckText = (#{const MAGIC_NO_CHECK_TEXT})+ fromEnum MagicNoCheckCdf = (#{const MAGIC_NO_CHECK_CDF})+ fromEnum MagicNoCheckCsv = (#{const MAGIC_NO_CHECK_CSV})+ fromEnum MagicNoCheckTokens = (#{const MAGIC_NO_CHECK_TOKENS})+ fromEnum MagicNoCheckEncoding = (#{const MAGIC_NO_CHECK_ENCODING})+ fromEnum MagicNoCheckJson = (#{const MAGIC_NO_CHECK_JSON})+ fromEnum MagicNoCheckSimh = (#{const MAGIC_NO_CHECK_SIMH})+ fromEnum MagicNoCheckBuiltin = (#{const MAGIC_NO_CHECK_BUILTIN})  fromEnum (UnknownMagicFlag x) = x  instance Ord MagicFlag where
src/Magic/Init.hsc view
@@ -29,7 +29,6 @@ import Magic.Types import Foreign.C.Types import Magic.Utils-import Magic.TypesLL  {- | Create a new magic handle configured with the given flags (see 'MagicFlag'). Before querying anything you must load a database with@@ -57,8 +56,13 @@     withCString s (\cs ->      checkIntError "magicLoad" m $ magic_load cmagic cs))     +-- Allocates the cookie, no file I/O -> unsafe foreign import ccall unsafe "magic.h magic_open"   magic_open :: CInt -> IO (Ptr CMagic) -foreign import ccall unsafe "magic.h magic_load"+-- Reads and parses the (potentially multi-megabyte) magic database from disk:+-- blocking I/O, so this must be a safe call. A safe call lets other Haskell+-- threads run during the load and does not stall garbage collection, whereas+-- an unsafe call would block the capability for the whole load.+foreign import ccall safe "magic.h magic_load"   magic_load :: Ptr CMagic -> CString -> IO CInt
src/Magic/Operations.hsc view
@@ -22,20 +22,33 @@ -}  module Magic.Operations(-- * Guessing the type-                        magicFile, magicStdin,-                        magicString, magicCString,-                        -- * Other operations-                        magicSetFlags, magicCompile)+                        magicFile, magicStdin, magicDescriptor,+                        magicString, magicCString, magicByteString,+                        -- * Flags+                        magicSetFlags, magicGetFlags,+                        -- * Tunable parameters+                        magicGetParam, magicSetParam,+                        -- * Magic databases+                        magicCompile, magicCheck, magicGetPath,+                        -- * Library information+                        magicVersion, magicErrno) where  import Foreign.Ptr import Foreign.C.String-import Magic.Types import Foreign.C.Types+import Foreign.Marshal.Alloc (alloca)+import Foreign.Marshal.Utils (with)+import Foreign.Storable (peek) import Data.Word+import Data.ByteString (ByteString)+import qualified Data.ByteString.Unsafe as BSU+import System.Posix.Types (Fd)+import Magic.Types import Magic.Utils-import Magic.TypesLL +#include <magic.h>+ {- | Identify the file at the given path. The result is in the form selected by the handle's flags (a textual description, a MIME type, an encoding, and so on; see 'MagicFlag'). Raises an 'IOError' if the file cannot be examined. -}@@ -100,6 +113,124 @@                                      )     where worker cm cs = checkIntError "magicCompile" m $ magic_compile cm cs +{- | Identify the data behind an open file descriptor, as 'magicFile' does for a+named file. Useful for sockets, pipes, or files you have already opened. Raises+an 'IOError' if the descriptor cannot be examined.++@since 1.1.2+-}+magicDescriptor :: Magic -> Fd -> IO String+magicDescriptor magic fd =+    withMagicPtr magic (\cmagic ->+     do res <- throwErrorIfNull "magicDescriptor" magic+                 (magic_descriptor cmagic (fromIntegral fd))+        peekCString res)++{- | Identify the contents of a strict 'ByteString'. Unlike 'magicString' this+does no encoding conversion, so it is the right choice for binary data. Raises+an 'IOError' if the data cannot be examined.++@since 1.1.2+-}+magicByteString :: Magic -> ByteString -> IO String+magicByteString magic bs =+    withMagicPtr magic (\cmagic ->+     BSU.unsafeUseAsCStringLen bs (\(cstr, len) ->+      do res <- throwErrorIfNull "magicByteString" magic+                  (magic_buffer cmagic cstr (fromIntegral len))+         peekCString res))++{- | Read the flags currently set on the handle (see 'magicSetFlags'). Composite+masks are returned decomposed into their individual flags. Raises an 'IOError'+if the running @libmagic@ does not support querying flags.++@since 1.1.2+-}+magicGetFlags :: Magic -> IO [MagicFlag]+magicGetFlags m =+    withMagicPtr m (\cmagic ->+     do fl <- magic_getflags cmagic+        if fl < 0+           then ioError (userError+                  "magicGetFlags: magic_getflags is unsupported by this libmagic")+           else return (int2flaglist fl))++{- | Read a tunable parameter (see 'MagicParam').++@since 1.1.2+-}+magicGetParam :: Magic -> MagicParam -> IO Int+magicGetParam m p =+    withMagicPtr m (\cmagic ->+     alloca (\ptr ->+      do checkIntError "magicGetParam" m+           (magic_getparam cmagic (paramToCInt p) (castPtr ptr))+         v <- peek ptr+         return (fromIntegral (v :: CSize))))++{- | Set a tunable parameter (see 'MagicParam'), for example to cap the number+of bytes scanned in untrusted input. Raises an 'IOError' on failure.++@since 1.1.2+-}+magicSetParam :: Magic -> MagicParam -> Int -> IO ()+magicSetParam m p val =+    withMagicPtr m (\cmagic ->+     with (fromIntegral val :: CSize) (\ptr ->+      checkIntError "magicSetParam" m+        (magic_setparam cmagic (paramToCInt p) (castPtr ptr))))++paramToCInt :: MagicParam -> CInt+paramToCInt p = case p of+    MagicParamIndirMax     -> #{const MAGIC_PARAM_INDIR_MAX}+    MagicParamNameMax      -> #{const MAGIC_PARAM_NAME_MAX}+    MagicParamElfPhnumMax  -> #{const MAGIC_PARAM_ELF_PHNUM_MAX}+    MagicParamElfShnumMax  -> #{const MAGIC_PARAM_ELF_SHNUM_MAX}+    MagicParamElfNotesMax  -> #{const MAGIC_PARAM_ELF_NOTES_MAX}+    MagicParamRegexMax     -> #{const MAGIC_PARAM_REGEX_MAX}+    MagicParamBytesMax     -> #{const MAGIC_PARAM_BYTES_MAX}+    MagicParamEncodingMax  -> #{const MAGIC_PARAM_ENCODING_MAX}+    MagicParamElfShsizeMax -> #{const MAGIC_PARAM_ELF_SHSIZE_MAX}++{- | Check the validity of the given magic database file(s) without compiling+them, as @file -c@ does. Pass 'Nothing' for the default database. Returns+'True' if the database is valid.++@since 1.1.2+-}+magicCheck :: Magic -> Maybe FilePath -> IO Bool+magicCheck m mpath =+    withMagicPtr m (\cmagic ->+     case mpath of+       Nothing -> fmap (== 0) (magic_check cmagic nullPtr)+       Just p  -> withCString p (fmap (== 0) . magic_check cmagic))++{- | The path of the default magic database, honouring the @MAGIC@ environment+variable.++@since 1.1.2+-}+magicGetPath :: IO FilePath+magicGetPath =+    do res <- magic_getpath nullPtr 0+       if res == nullPtr then return "" else peekCString res++{- | The version of the @libmagic@ library in use, encoded as a single integer+(for example @545@ for version 5.45).++@since 1.1.2+-}+magicVersion :: IO Int+magicVersion = fmap fromIntegral magic_version++{- | The @errno@ recorded by the last failing operation on the handle, or @0@ if+the last failure was not caused by a system error.++@since 1.1.2+-}+magicErrno :: Magic -> IO Int+magicErrno m = withMagicPtr m (fmap fromIntegral . magic_errno)+ -- Does file I/O -> safe foreign import ccall safe "magic.h magic_file"   magic_file :: Ptr CMagic -> CString -> IO CString@@ -115,3 +246,35 @@ -- Does file I/O -> safe foreign import ccall safe "magic.h magic_compile"   magic_compile :: Ptr CMagic -> CString -> IO CInt++-- Reads the descriptor -> safe+foreign import ccall safe "magic.h magic_descriptor"+  magic_descriptor :: Ptr CMagic -> CInt -> IO CString++-- Validates a database file -> safe+foreign import ccall safe "magic.h magic_check"+  magic_check :: Ptr CMagic -> CString -> IO CInt++-- Does not do I/O -> unsafe+foreign import ccall unsafe "magic.h magic_getflags"+  magic_getflags :: Ptr CMagic -> IO CInt++-- Does not do I/O -> unsafe+foreign import ccall unsafe "magic.h magic_getparam"+  magic_getparam :: Ptr CMagic -> CInt -> Ptr () -> IO CInt++-- Does not do I/O -> unsafe+foreign import ccall unsafe "magic.h magic_setparam"+  magic_setparam :: Ptr CMagic -> CInt -> Ptr () -> IO CInt++-- Reads an environment variable -> unsafe+foreign import ccall unsafe "magic.h magic_getpath"+  magic_getpath :: CString -> CInt -> IO CString++-- Does not do I/O -> unsafe+foreign import ccall unsafe "magic.h magic_version"+  magic_version :: IO CInt++-- Does not do I/O -> unsafe+foreign import ccall unsafe "magic.h magic_errno"+  magic_errno :: Ptr CMagic -> IO CInt
src/Magic/Types.hsc view
@@ -14,26 +14,69 @@    Stability  : provisional    Portability: portable -The core types of the binding: the opaque 'Magic' handle and the 'MagicFlag'-enumeration (re-exported from "Magic.Data").+The core types of the binding: the opaque 'Magic' handle, the 'MagicFlag'+enumeration (re-exported from "Magic.Data") and the 'MagicParam' tunables.  Written by John Goerzen. -}  module Magic.Types(Magic,-                   MagicFlag(..))+                   CMagic,+                   MagicFlag(..),+                   MagicParam(..)) where import Foreign.ForeignPtr import Magic.Data-import Magic.TypesLL  #include <magic.h> +-- | Phantom type standing in for the C @magic_t@ cookie. It appears only as the+-- argument of a 'Foreign.ForeignPtr.ForeignPtr' (the 'Magic' handle) and has no+-- values of its own.+data CMagic+ {- | The magic handle: an opaque cookie obtained from @magicOpen@ and passed to the loading and querying functions.  Handles are closed (and their memory freed) automatically when they are garbage-collected by Haskell. There is no need to close them explicitly.++__Thread safety.__ A single @Magic@ handle is /not/ safe for concurrent use:+the underlying @libmagic@ cookie keeps mutable state, so calling operations on+the same handle from multiple threads at once is a data race. Use one handle+per thread, or serialise access to a shared handle (e.g. behind an @MVar@).+Distinct handles are independent and may be used concurrently. Programs doing+blocking work (loading databases, examining files) should also be built with+the threaded runtime (@-threaded@). -} type Magic = ForeignPtr CMagic++{- | Tunable limits that bound how hard @libmagic@ works when examining data,+read and written with @magicGetParam@ and @magicSetParam@ (see+"Magic.Operations"). Lowering these (especially 'MagicParamBytesMax') is a way+to cap the effort spent on untrusted input. Defaults below are those documented+for @libmagic@ 5.45.++@since 1.1.2+-}+data MagicParam+    = -- | Maximum number of levels of recursion when following indirect magic. (Default: 15.)+      MagicParamIndirMax+    | -- | Maximum length of a name used by name\/use magic. (Default: 30.)+      MagicParamNameMax+    | -- | Maximum number of ELF program header sections processed. (Default: 128.)+      MagicParamElfPhnumMax+    | -- | Maximum number of ELF section header sections processed. (Default: 32768.)+      MagicParamElfShnumMax+    | -- | Maximum number of ELF notes processed. (Default: 256.)+      MagicParamElfNotesMax+    | -- | Maximum length of a regex search. (Default: 8192.)+      MagicParamRegexMax+    | -- | Maximum number of bytes read from a file before giving up. (Default: 1048576, i.e. 1 MiB.)+      MagicParamBytesMax+    | -- | Maximum number of bytes scanned when guessing a text encoding.+      MagicParamEncodingMax+    | -- | Maximum size of an ELF section processed.+      MagicParamElfShsizeMax+  deriving (Show, Eq, Ord, Enum, Bounded) 
− src/Magic/TypesLL.hs
@@ -1,27 +0,0 @@-{-  -*- Mode: haskell; -*--Haskell magic Interface-Copyright (C) 2005 John Goerzen <jgoerzen@complete.org>--This code is under a 3-clause BSD license; see COPYING for details.--}--{- |-   Module     : Magic.TypesLL-   Copyright  : Copyright (C) 2005 John Goerzen-   License    : BSD-3-Clause--   Maintainer : Philippe <philippedev101\@gmail.com>-   Stability  : provisional-   Portability: portable--Low-level types for the binding.--Written by John Goerzen.--}--module Magic.TypesLL(CMagic)-where---- | Phantom type standing in for the C @magic_t@ cookie. It appears only as the--- argument of a @ForeignPtr@ (the @Magic@ handle) and has no values of its own.-data CMagic
src/Magic/Utils.hsc view
@@ -8,32 +8,42 @@ {- |    Module     : Magic.Utils    Copyright  : Copyright (C) 2005 John Goerzen-   License    : BSD+   License    : BSD-3-Clause -   Maintainer : John Goerzen,-   Maintainer : jgoerzen\@complete.org+   Maintainer : Philippe <philippedev101\@gmail.com>    Stability  : provisional    Portability: portable -Utils+Internal marshalling helpers shared by the other modules: converting between+'MagicFlag' lists and the C bit-mask, wrapping the C handle in a 'ForeignPtr',+and turning C error returns into 'IOError's. -Written by John Goerzen, jgoerzen\@complete.org+Written by John Goerzen. -} -module Magic.Utils (flaglist2int, fromMagicPtr, withMagicPtr, checkIntError,-                    throwErrorIfNull)+module Magic.Utils (flaglist2int, int2flaglist, fromMagicPtr, withMagicPtr,+                    checkIntError, throwErrorIfNull) where  import Foreign import Foreign.C.Error import Foreign.C.String-import Magic.TypesLL import Magic.Types import Foreign.C.Types  flaglist2int :: [MagicFlag] -> CInt flaglist2int mfl =     foldl (\c f -> c .|. (fromIntegral . fromEnum $ f)) 0 mfl++-- | Decode a C bit-mask into the list of set 'MagicFlag's. Each set bit is+-- mapped to its individual flag, so composite values come back decomposed+-- (e.g. the MIME mask yields @['MagicMimeType', 'MagicMimeEncoding']@).+int2flaglist :: CInt -> [MagicFlag]+int2flaglist flags =+    [ toEnum v+    | i <- [0 .. finiteBitSize flags - 1]+    , testBit flags i+    , let v = bit i :: Int ]  fromMagicPtr :: String -> IO (Ptr CMagic) -> IO Magic fromMagicPtr caller action =
test/Inittest.hs view
@@ -6,11 +6,12 @@  module Inittest(tests) where -import Control.Exception (bracket)+import Control.Exception (IOException, bracket, try) import qualified Data.ByteString as BS import Data.List (isInfixOf, isPrefixOf) import System.Directory (getTemporaryDirectory, removeFile) import System.FilePath ((</>))+import System.Posix.IO (OpenMode(ReadOnly), closeFd, defaultFileFlags, openFd) import Test.HUnit  import Magic@@ -22,6 +23,21 @@     magicLoadDefault m     act m +-- 8-byte PNG signature followed by the start of an IHDR chunk.+pngBytes :: BS.ByteString+pngBytes = BS.pack+    [0x89,0x50,0x4E,0x47,0x0D,0x0A,0x1A,0x0A+    ,0x00,0x00,0x00,0x0D,0x49,0x48,0x44,0x52]++-- | Run an action with a temporary file holding the PNG bytes.+withPngFile :: (FilePath -> IO b) -> IO b+withPngFile act = do+    dir <- getTemporaryDirectory+    let pngPath = dir </> "magic-test-sample.png"+    bracket (BS.writeFile pngPath pngBytes >> return pngPath)+            removeFile+            act+ -- | A small in-memory string is detected as plain text. test_textMime :: Test test_textMime = TestCase $ do@@ -48,22 +64,111 @@     withPngFile $ \pngPath -> do         result <- withDefault [MagicMimeType] $ \m -> magicFile m pngPath         assertEqual "PNG MIME type" "image/png" result-  where-    -- 8-byte PNG signature followed by the start of an IHDR chunk.-    pngBytes = BS.pack-        [0x89,0x50,0x4E,0x47,0x0D,0x0A,0x1A,0x0A-        ,0x00,0x00,0x00,0x0D,0x49,0x48,0x44,0x52]-    withPngFile :: (FilePath -> IO b) -> IO b-    withPngFile act = do-        dir <- getTemporaryDirectory-        let pngPath = dir </> "magic-test-sample.png"-        bracket (BS.writeFile pngPath pngBytes >> return pngPath)-                removeFile-                act +-- | 'magicByteString' identifies binary data directly, without an intermediate+-- file and without locale-encoding corruption.+test_byteStringMime :: Test+test_byteStringMime = TestCase $ do+    result <- withDefault [MagicMimeType] $ \m -> magicByteString m pngBytes+    assertEqual "PNG MIME via ByteString" "image/png" result++-- | 'magicDescriptor' identifies the data behind an open file descriptor.+test_descriptorMime :: Test+test_descriptorMime = TestCase $+    withPngFile $ \pngPath -> do+        result <- withDefault [MagicMimeType] $ \m ->+            bracket (openFd pngPath ReadOnly defaultFileFlags) closeFd+                    (magicDescriptor m)+        assertEqual "PNG MIME via descriptor" "image/png" result++-- | 'magicVersion' reports a sane (positive) library version.+test_version :: Test+test_version = TestCase $ do+    v <- magicVersion+    assertBool ("expected a positive libmagic version, got " ++ show v) (v > 0)++-- | Flags set with 'magicSetFlags' are read back by 'magicGetFlags'.+test_getFlags :: Test+test_getFlags = TestCase $+    withDefault [MagicNone] $ \m -> do+        magicSetFlags m [MagicMimeType, MagicError]+        fl <- magicGetFlags m+        assertBool ("expected MimeType and Error among " ++ show fl)+                   (MagicMimeType `elem` fl && MagicError `elem` fl)++-- | A parameter set with 'magicSetParam' is read back by 'magicGetParam'.+test_params :: Test+test_params = TestCase $+    withDefault [MagicNone] $ \m -> do+        magicSetParam m MagicParamBytesMax 4096+        v <- magicGetParam m MagicParamBytesMax+        assertEqual "bytes-max round-trip" 4096 v++-- | The system's default magic database validates with 'magicCheck'.+test_check :: Test+test_check = TestCase $ do+    ok <- withDefault [MagicNone] $ \m -> magicCheck m Nothing+    assertBool "default magic database should be valid" ok++-- | 'magicGetPath' returns a non-empty default database path.+test_getPath :: Test+test_getPath = TestCase $ do+    p <- magicGetPath+    assertBool "expected a non-empty default magic path" (not (null p))++-- | A newly added flag ('MagicExtension') maps to the right value end-to-end:+-- it makes libmagic report candidate file extensions.+test_extension :: Test+test_extension = TestCase $+    withPngFile $ \pngPath -> do+        result <- withDefault [MagicExtension] $ \m -> magicFile m pngPath+        assertBool ("expected 'png' among extensions, got: " ++ show result)+                   ("png" `isInfixOf` result)++-- | The composite 'MagicMime' mask is read back decomposed into its component+-- flags by 'magicGetFlags'.+test_getFlagsComposite :: Test+test_getFlagsComposite = TestCase $+    withDefault [MagicNone] $ \m -> do+        magicSetFlags m [MagicMime]+        fl <- magicGetFlags m+        assertBool ("expected MimeType and MimeEncoding among " ++ show fl)+                   (MagicMimeType `elem` fl && MagicMimeEncoding `elem` fl)++-- | With 'MagicError' set, examining a missing file raises an 'IOError' rather+-- than embedding the message in the result.+test_errorRaised :: Test+test_errorRaised = TestCase $ do+    r <- withDefault [MagicError] $ \m ->+        try (magicFile m "/no/such/magic/file") :: IO (Either IOException String)+    case r of+        Left _  -> return ()+        Right s -> assertFailure ("expected an IOError, got: " ++ show s)++-- | After a failed operation, 'magicErrno' reports the underlying system error.+test_errno :: Test+test_errno = TestCase $+    withDefault [MagicError] $ \m -> do+        _ <- try (magicFile m "/no/such/magic/file")+                 :: IO (Either IOException String)+        e <- magicErrno m+        assertBool ("expected a non-zero errno after a failed open, got " ++ show e)+                   (e /= 0)+ tests :: Test tests = TestList-    [ TestLabel "text MIME type"   test_textMime-    , TestLabel "text description" test_textDescription-    , TestLabel "PNG MIME type"    test_pngMime+    [ TestLabel "text MIME type"        test_textMime+    , TestLabel "text description"      test_textDescription+    , TestLabel "PNG MIME type"         test_pngMime+    , TestLabel "PNG MIME (ByteString)" test_byteStringMime+    , TestLabel "PNG MIME (descriptor)" test_descriptorMime+    , TestLabel "library version"       test_version+    , TestLabel "get/set flags"         test_getFlags+    , TestLabel "composite flags"       test_getFlagsComposite+    , TestLabel "extension flag"        test_extension+    , TestLabel "get/set params"        test_params+    , TestLabel "check database"        test_check+    , TestLabel "default path"          test_getPath+    , TestLabel "error raised"          test_errorRaised+    , TestLabel "errno after failure"   test_errno     ]
test/Tests.hs view
@@ -1,4 +1,4 @@-{- arch-tag: Tests main file+{- Tests main file Copyright (C) 2005 John Goerzen <jgoerzen@complete.org>  This code is under a 3-clause BSD license; see COPYING for details.
test/runtests.hs view
@@ -1,5 +1,7 @@-{- arch-tag: Test runner+{- Test runner Copyright (C) 2005 John Goerzen <jgoerzen@complete.org>++This code is under a 3-clause BSD license; see COPYING for details. -}  module Main (main) where