magic 1.1.1 → 1.1.2
raw patch · 12 files changed
+456/−70 lines, 12 filesdep +unix
Dependencies added: unix
Files
- CHANGELOG.md +12/−0
- README.md +1/−1
- magic.cabal +5/−3
- src/Magic/Data.hsc +73/−1
- src/Magic/Init.hsc +6/−2
- src/Magic/Operations.hsc +169/−6
- src/Magic/Types.hsc +47/−4
- src/Magic/TypesLL.hs +0/−27
- src/Magic/Utils.hsc +18/−8
- test/Inittest.hs +121/−16
- test/Tests.hs +1/−1
- test/runtests.hs +3/−1
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