haskdeep (empty) → 0.1.0.0
raw patch · 13 files changed
+854/−0 lines, 13 filesdep +attoparsecdep +attoparsec-conduitdep +basesetup-changed
Dependencies added: attoparsec, attoparsec-conduit, base, bytestring, cereal, conduit, containers, crypto-api, crypto-conduit, cryptohash, filesystem-conduit, optparse-applicative, regex-tdfa, regex-tdfa-text, system-fileio, system-filepath, text
Files
- LICENSE +30/−0
- README.md +53/−0
- Setup.hs +2/−0
- haskdeep.cabal +88/−0
- src/HaskDeep.hs +44/−0
- src/HaskDeep/Computation.hs +73/−0
- src/HaskDeep/ComputationMode.hs +83/−0
- src/HaskDeep/Configuration.hs +42/−0
- src/HaskDeep/HashSet.hs +133/−0
- src/HaskDeep/KnownHash/Reader.hs +88/−0
- src/HaskDeep/KnownHash/Writer.hs +67/−0
- src/Main.hs +50/−0
- src/Options.hs +101/−0
+ LICENSE view
@@ -0,0 +1,30 @@+Copyright (c) 2012 Mauro Taraborelli++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions+are met:++1. Redistributions of source code must retain the above copyright+ notice, this list of conditions and the following disclaimer.++2. Redistributions in binary form must reproduce the above copyright+ notice, this list of conditions and the following disclaimer in the+ documentation and/or other materials provided with the distribution.++3. Neither the name of the author nor the names of his contributors+ may be used to endorse or promote products derived from this software+ without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE CONTRIBUTORS ``AS IS'' AND ANY EXPRESS+OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE+DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS+OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)+HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,+STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN+ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE+POSSIBILITY OF SUCH DAMAGE.
+ README.md view
@@ -0,0 +1,53 @@+HaskDeep+========++What is it?+-----------+Command line tool that computes file hashes traversing recursively through+a directory structure.+Known hashes are saved to file and they can be used to verify the original+files or a copy of them.++Quick start+-----------+Execute haskdeep without arguments and it will show you the help text:++ user@host:~$ haskdeep++ Usage: haskdeep COMMAND [-c|--computation MODE] [-r|--root DIRNAME] [-k|--known FILENAME] [-i|--ignore RULE]+ Computes hashes and audit a set of files++ Available options:+ -c,--computation MODE md5 | sha1 | sha256 | skein512 - default md5+ -r,--root DIRNAME Root directory - default current directory+ -k,--known FILENAME Known hashes file - default known.haskdeep+ -i,--ignore RULE Regex to ignore files or directories+ -h,--help Show this help text++ Available commands:+ compute Computes file hashes and saves them to known hashes file+ audit Audits files comparing them to known hashes++Default usage:++1. create known hashes of files contained in a root directory (traversed recursively)++ user@host:~$ haskdeep compute -c md5 -r myimportantfiles/ -k knownhashes.txt -i "tmp|\.log"++2. verify a copy of the same files comparing them with known hashes++ user@host:~$ haskdeep audit -c md5 -r copyofmyimportantfiles/ -k knownhashes.txt -i "tmp|\.log"++Licensing+---------+Please see the file called LICENSE.++Reference+---------+Heavily inspired by `hashdeep`: <http://md5deep.sourceforge.net/>++Contacts+--------+For question and comments:++- [MauroTaraborelli@gmail.com](mailto:MauroTaraborelli@gmail.com)
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ haskdeep.cabal view
@@ -0,0 +1,88 @@+name: haskdeep+version: 0.1.0.0+cabal-version: >=1.8+build-type: Simple+license: BSD3+license-file: LICENSE+copyright: © 2012 Mauro Taraborelli+author: Mauro Taraborelli+maintainer: maurotaraborelli@gmail.com+stability: experimental+homepage: https://github.com/maurotrb/haskdeep+bug-reports: https://github.com/maurotrb/haskdeep/issues+synopsis: Computes and audits file hashes.+description:+ A command line application that computes file hashes traversing recursively+ through a directory structure.+ .+ The known hashes can be used to audit the same directory structure or a copy+ of it.+ .+ Execute haskdeep without arguments and it will show you the help text:+ .+ > user@host:~$ haskdeep+ >+ > Usage: haskdeep COMMAND [-c|--computation MODE] [-r|--root DIRNAME] [-k|--known FILENAME] [-i|--ignore RULE]+ > Computes hashes and audit a set of files+ >+ > Available options:+ > -c,--computation MODE md5 | sha1 | sha256 | skein512 - default md5+ > -r,--root DIRNAME Root directory - default current directory+ > -k,--known FILENAME Known hashes file - default known.haskdeep+ > -i,--ignore RULE Regex to ignore files or directories+ > -h,--help Show this help text+ >+ > Available commands:+ > compute Computes file hashes and saves them to known hashes file+ > audit Audits files comparing them to known hashes+ .+ Default usage:+ .+ 1. create known hashes of files contained in a root directory (traversed recursively)+ .+ > user@host:~$ haskdeep compute -c md5 -r myimportantfiles/ -k knownhashes.txt -i "tmp|\.log"+ .+ 2. verify a copy of the same files comparing them with known hashes+ .+ > user@host:~$ haskdeep audit -c md5 -r copyofmyimportantfiles/ -k knownhashes.txt -i "tmp|\.log"+ .+ Heavily inspired by @hashdeep@: <http://md5deep.sourceforge.net/>+category: Console, Cryptography, System+extra-source-files:+ README.md++executable haskdeep+ hs-source-dirs: src+ main-is: Main.hs+ other-modules:+ Options+ HaskDeep+ HaskDeep.Computation+ HaskDeep.ComputationMode+ HaskDeep.Configuration+ HaskDeep.HashSet+ HaskDeep.KnownHash.Reader+ HaskDeep.KnownHash.Writer+ build-depends:+ attoparsec ==0.10.*+ , attoparsec-conduit ==0.5.*+ , base ==4.5.*+ , bytestring ==0.9.*+ , cereal ==0.3.*+ , conduit ==0.5.*+ , containers ==0.4.*+ , cryptohash ==0.7.*+ , crypto-api ==0.10.*+ , crypto-conduit ==0.4.*+ , filesystem-conduit ==0.5.*+ , optparse-applicative ==0.4.*+ , system-fileio ==0.3.*+ , system-filepath ==0.4.*+ , text ==0.11.*+ , regex-tdfa ==1.1.*+ , regex-tdfa-text ==1.0.*+ ghc-options: -Wall++source-repository head+ type: git+ location: git://github.com/maurotrb/haskdeep.git
+ src/HaskDeep.hs view
@@ -0,0 +1,44 @@+-- |+-- Module : HaskDeep+-- Copyright : Mauro Taraborelli 2012+-- License : BSD3+--+-- Maintainer : maurotaraborelli@gmail.com+-- Stability : experimental+-- Portability : unknown+--+-- Computes hashes traversing recursively through a directory structure.+-- Uses a list of known hashes to audit a set of files.++module HaskDeep+ (+ -- * The @HashSet@ type+ HashSet()+ ,audit++ -- * Configuration+ ,HaskDeepConfiguration(..)+ ,defaultHaskDeepConfiguration++ -- * Computation modes+ ,ComputationMode (..)+ ,md5hash+ ,sha1hash+ ,sha256hash+ ,skein512hash++ -- * Computation+ ,compute++ -- * Known hashes file+ ,readHashes+ ,writeHashes+ )+where++import HaskDeep.Computation+import HaskDeep.ComputationMode+import HaskDeep.Configuration+import HaskDeep.HashSet+import HaskDeep.KnownHash.Reader+import HaskDeep.KnownHash.Writer
+ src/HaskDeep/Computation.hs view
@@ -0,0 +1,73 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE DoAndIfThenElse #-}+-- |+-- Module : HaskDeep.Computation+-- Copyright : Mauro Taraborelli 2012+-- License : BSD3+--+-- Maintainer : maurotaraborelli@gmail.com+-- Stability : experimental+-- Portability : unknown+--+-- Computes hashes traversing recursively through a directory structure.+-- Uses a list of known hashes to audit a set of files.+--+-- Internal module.++module HaskDeep.Computation+ (+ -- * Computation+ compute -- Hash ctx a => HaskDeepConfiguration -> ComputationMode a -> IO HashSet+ )+where++import Control.Monad (liftM)+import Data.Maybe (fromJust)+import Prelude hiding (FilePath)++import Crypto.Classes (Hash)+import qualified Crypto.Conduit as CC+import Data.Conduit (($$))+import qualified Data.Conduit as C+import qualified Data.Conduit.Filesystem as CF+import qualified Data.Conduit.List as CL+import qualified Data.Text as T+import qualified Filesystem as FS+import Filesystem.Path.CurrentOS (FilePath)+import qualified Filesystem.Path.CurrentOS as FSC+import Text.Regex.TDFA ((=~))+import Text.Regex.TDFA.Text ()++import HaskDeep.Configuration+import HaskDeep.ComputationMode+import HaskDeep.HashSet (HashInfo(..), HashSet)+import qualified HaskDeep.HashSet as HS++-- | Compute @HashSet@ traversing recursively through the directory structure.+compute :: Hash ctx a => HaskDeepConfiguration+ -> ComputationMode a+ -> IO HashSet+compute conf cm = CF.traverse False root $$ CL.foldM insert_hash empty_with_symbol+ where+ root = rootDirectory conf+ rule = ignoreRule conf+ empty_with_symbol = HS.setSymbol (symbol cm) HS.empty++ insert_hash :: HashSet -> FilePath -> IO HashSet+ insert_hash hs fp = do let fpt = fpToText $ relativize root fp+ if ignore fpt rule+ then return hs+ else do s <- FS.getSize fp+ h <- liftM (runComputation cm) $ C.runResourceT+ $ CF.sourceFile fp $$ CC.sinkHash+ return $ HS.insert (HashInfo fpt s h) hs++relativize :: FilePath -> FilePath -> FilePath+relativize root fp = fromJust $ FSC.stripPrefix root fp++fpToText :: FilePath -> T.Text+fpToText = either id id . FSC.toText++ignore :: T.Text -> Maybe T.Text -> Bool+ignore fpt (Just rule) = fpt =~ rule+ignore _ Nothing = False
+ src/HaskDeep/ComputationMode.hs view
@@ -0,0 +1,83 @@+{-# LANGUAGE OverloadedStrings #-}+-- |+-- Module : HaskDeep.Computation+-- Copyright : Mauro Taraborelli 2012+-- License : BSD3+--+-- Maintainer : maurotaraborelli@gmail.com+-- Stability : experimental+-- Portability : unknown+--+-- Computes hashes traversing recursively through a directory structure.+-- Uses a list of known hashes to audit a set of files.+--+-- Internal module.++module HaskDeep.ComputationMode+ (+ -- * Computation modes+ ComputationMode (..)+ ,md5hash+ ,sha1hash+ ,sha256hash+ ,skein512hash+ )+where++import Data.Bits ((.&.), shiftR)++import Crypto.Hash.MD5 (MD5)+import Crypto.Hash.SHA1 (SHA1)+import Crypto.Hash.SHA256 (SHA256)+import Crypto.Hash.Skein512 (Skein512)+import Data.ByteString (ByteString)+import qualified Data.ByteString as BS+import qualified Data.Serialize as S+import qualified Data.Text as T++-- | Algorithm to compute hash.+data ComputationMode a = ComputationMode+ { symbol :: T.Text -- ^ Computation mode symbol+ , runComputation :: a -> ByteString -- ^ Computation function+ }++-- | MD5 computation.+md5hash :: ComputationMode MD5+md5hash = ComputationMode "md5" (toHex . S.encode)++-- | SHA1 computation.+sha1hash :: ComputationMode SHA1+sha1hash = ComputationMode "sha1" (toHex . S.encode)++-- | SHA256 computation.+sha256hash :: ComputationMode SHA256+sha256hash = ComputationMode "sha256" (toHex . S.encode)++-- | Skein512 computation.+skein512hash :: ComputationMode Skein512+skein512hash = ComputationMode "skein512" (toHex . S.encode)++-- Only with ByteString 0.10+--toHex :: ByteString -> ByteString+--toHex = BS.concat . BL.toChunks . BB.toLazyByteString . BA.byteStringHexFixed++-- Taken from+-- http://stackoverflow.com/questions/10099921/efficiently-turn-a-bytestring-into-a-hex-representation+toHex :: ByteString -> ByteString+toHex bs0 =+ fst $ BS.unfoldrN (BS.length bs0 * 2) go (Left bs0)+ where+ go (Left bs) =+ case BS.uncons bs of+ Nothing -> Nothing+ Just (w, bs') ->+ let w1 = w `shiftR` 4+ w2 = w .&. 15+ c1 = toC w1+ c2 = toC w2+ in Just (c1, Right (c2, bs'))+ go (Right (c, bs)) = Just (c, Left bs)++ toC w+ | w < 10 = w + 48+ | otherwise = w + 87
+ src/HaskDeep/Configuration.hs view
@@ -0,0 +1,42 @@+-- |+-- Module : HaskDeep.Configuration+-- Copyright : Mauro Taraborelli 2012+-- License : BSD3+--+-- Maintainer : maurotaraborelli@gmail.com+-- Stability : experimental+-- Portability : unknown+--+-- Computes hashes traversing recursively through a directory structure.+-- Uses a list of known hashes to audit a set of files.+--+-- Internal module.++module HaskDeep.Configuration+ (+ -- * Configuration+ HaskDeepConfiguration (..)+ ,defaultHaskDeepConfiguration+ )+where++import Prelude hiding (FilePath)++import Data.Text (Text)+import Filesystem.Path (FilePath)+import qualified Filesystem.Path.CurrentOS as FSC++-- | HaskDeep configuration.+data HaskDeepConfiguration = HaskDeepConfiguration+ { rootDirectory :: FilePath -- ^ Root directory+ , knownHashes :: FilePath -- ^ Known hashes file+ , ignoreRule :: Maybe Text -- ^ Ignore rule (Regex)+ }++-- | HaskDeep default configuration.+defaultHaskDeepConfiguration :: HaskDeepConfiguration+defaultHaskDeepConfiguration = HaskDeepConfiguration+ { rootDirectory = FSC.decodeString "."+ , knownHashes = FSC.decodeString "known.haskdeep"+ , ignoreRule = Nothing+ }
+ src/HaskDeep/HashSet.hs view
@@ -0,0 +1,133 @@+{-# LANGUAGE OverloadedStrings #-}+-- |+-- Module : HaskDeep.HashSet+-- Copyright : Mauro Taraborelli 2012+-- License : BSD3+--+-- Maintainer : maurotaraborelli@gmail.com+-- Stability : experimental+-- Portability : unknown+--+-- Computes hashes traversing recursively through a directory structure.+-- Uses a list of known hashes to audit a set of files.+--+-- Internal module.++module HaskDeep.HashSet+ (+ -- * The @HashSet@ type+ HashSet(..)+ ,setSymbol -- Text -> HashSet -> HashSet+ ,empty -- HashSet+ ,insert -- HashInfo -> HashSet -> HashSet+ ,fromList -- [HashInfo] -> HashSet+ ,toAscList -- HashSet -> [HashInfo]+ ,audit -- HashSet -> HashSet -> (HashSet, HashSet)++ -- * The @HashInfo@ type+ ,HashInfo(..)+ ,toByteString -- HashInfo -> ByteString+ )+where++import Data.List (intercalate)++import Data.ByteString (ByteString)+import qualified Data.ByteString as BS+import qualified Data.ByteString.Char8 as B8+import Data.Set (Set)+import qualified Data.Set as Set+import Data.Text (Text)+import qualified Data.Text as T+import qualified Data.Text.Encoding as TE++-- | Information about the hashed file.+data HashInfo = HashInfo+ { file :: Text -- ^ Relative file path+ , size :: Integer -- ^ File size in byte+ , hash :: ByteString -- ^ File hash+ } deriving (Eq, Ord)++instance Show HashInfo where+ show (HashInfo f s h) = intercalate "," [ show s+ , B8.unpack h+ , T.unpack f ]++toByteString :: HashInfo -> ByteString+toByteString (HashInfo f s h) = BS.intercalate commaBS [ B8.pack (show s)+ , h+ , TE.encodeUtf8 f ]++commaBS :: ByteString+commaBS = B8.singleton ','++-- | Hashed files.+data HashSet = HashSet+ { filesCount :: Integer -- ^ Number of hashed files+ , sizeSum :: Integer -- ^ Total size in bytes of hashed files+ , compSymbol :: Text -- ^ Computation mode symbol+ , hashSet :: Set HashInfo -- ^ Hashes+ } deriving (Eq, Ord)++instance Show HashSet where+ show (HashSet 0 _ _ _ ) = "No files."+ show (HashSet fc ss cs hs) = unlines [ "Files count : " ++ show fc+ , "Files size : " ++ show ss+ , "Computed with: " ++ T.unpack cs+ , "--"+ , (unlines . map show . Set.toAscList) hs ]++-- | Set computation mode symbol.+setSymbol :: Text -> HashSet -> HashSet+setSymbol cs hs = hs { compSymbol = cs }++-- | Create an empty @HashSet@.+empty :: HashSet+empty = HashSet 0 0 T.empty Set.empty++-- | Insert an @HashInfo@ into an @HashSet@+insert :: HashInfo -> HashSet -> HashSet+insert hi hs = hs+ { filesCount = filesCount hs + 1+ , sizeSum = sizeSum hs + size hi+ , hashSet = Set.insert hi $ hashSet hs+ }++-- | Create an @HashSet@ from a list of @HashInfo@+fromList :: [HashInfo] -> HashSet+fromList his = HashSet fc ss T.empty hs+ where+ hs = Set.fromList his+ fc = countFiles hs+ ss = sumSize hs++-- | Create an ordered list of @HashInfo@ from an @HashSet@.+toAscList :: HashSet -> [HashInfo]+toAscList (HashSet _ _ _ hs) = Set.toAscList hs++-- | Get the number of hashed files+countFiles :: Set HashInfo -> Integer+countFiles = fromIntegral . Set.size++-- | Get the total size in bytes of hashed files+sumSize :: Set HashInfo -> Integer+sumSize = Set.foldr sumSize' 0+ where+ sumSize' hi ss = ss + size hi++-- | Compare two @HashSet@ and return the not matching @HashInfo@.+--+-- Not matching means all the HashInfo of the first HashSet not present in the second HashSet+-- and all the HashInfo of the second HashSet not present in the first HashSet.+audit :: HashSet -> HashSet -> (HashSet, HashSet)+audit (HashSet _ _ cs1 hs1) (HashSet _ _ cs2 hs2) =+ ( HashSet hs1_no_match_filescount hs1_no_match_sizesum cs1 hs1_no_match+ , HashSet hs2_no_match_filescount hs2_no_match_sizesum cs2 hs2_no_match )+ where+ matching = hs1 `Set.intersection` hs2+ hs1_no_match = hs1 `Set.difference` matching+ hs2_no_match = hs2 `Set.difference` matching+ hs1_no_match_filescount = countFiles hs1_no_match+ hs1_no_match_sizesum = sumSize hs1_no_match+ hs2_no_match_filescount = countFiles hs2_no_match+ hs2_no_match_sizesum = sumSize hs2_no_match
+ src/HaskDeep/KnownHash/Reader.hs view
@@ -0,0 +1,88 @@+{-# LANGUAGE OverloadedStrings #-}+-- |+-- Module : HaskDeep.Computation.Reader+-- Copyright : Mauro Taraborelli 2012+-- License : BSD3+--+-- Maintainer : maurotaraborelli@gmail.com+-- Stability : experimental+-- Portability : unknown+--+-- Computes hashes traversing recursively through a directory structure.+-- Uses a list of known hashes to audit a set of files.+--+-- Internal module.++module HaskDeep.KnownHash.Reader+ (+ -- * Read the known hashes file+ readHashes -- HaskDeepConfiguration -> IO HashSet+ )+where++import Control.Applicative ((<*), many)+import Control.Monad (liftM)+import Data.Maybe(fromJust)+import Data.Word (Word8)+import Prelude hiding (FilePath)++import Data.Attoparsec (Parser)+import qualified Data.Attoparsec as A+import qualified Data.Attoparsec.Char8 as A8+import qualified Data.Attoparsec.Combinator as AC+import qualified Data.ByteString.Char8 as B8+import Data.Conduit (($$))+import qualified Data.Conduit as C+import qualified Data.Conduit.Attoparsec as CA+import qualified Data.Conduit.Filesystem as CF+import Data.Text ()+import qualified Data.Text.Encoding as TE+import Filesystem.Path ()++import HaskDeep.Configuration+import HaskDeep.HashSet (HashSet, HashInfo(..))+import qualified HaskDeep.HashSet as HS++-- | Read known hashes file into an @HashSet@.+readHashes :: HaskDeepConfiguration -- ^ Configuration+ -> IO HashSet -- ^ @HashSet@ red from file+readHashes conf = liftM HS.fromList $ C.runResourceT+ $ CF.sourceFile (knownHashes conf) $$ CA.sinkParser knownHashesP+++-- Parsers++knownHashesP :: Parser [HashInfo]+knownHashesP = do _ <- AC.count 2 headerP+ _ <- many commentP+ AC.many1 fileinfoP++headerP :: Parser ()+headerP = do _ <- A8.string "%%%%"+ _ <- A.skipWhile (not . isNewline)+ skipNewline++commentP :: Parser ()+commentP = do _ <- A8.char8 '#'+ A.skipWhile (not . isNewline)+ skipNewline++fileinfoP :: Parser HashInfo+fileinfoP = do sizep <- A.takeTill isComma <* skipComma+ hashp <- A.takeTill isComma <* skipComma+ filep <- A.takeTill isNewline <* skipNewline+ let filep' = TE.decodeUtf8 filep+ (sizep', _) = fromJust $ B8.readInteger sizep+ return $ HashInfo filep' sizep' hashp++isComma :: Word8 -> Bool+isComma w = w == 44++skipComma :: Parser ()+skipComma = A.skip isComma++isNewline :: Word8 -> Bool+isNewline w = w == 10++skipNewline :: Parser ()+skipNewline = A.skip isNewline
+ src/HaskDeep/KnownHash/Writer.hs view
@@ -0,0 +1,67 @@+{-# LANGUAGE OverloadedStrings #-}+-- |+-- Module : HaskDeep.Computation.Writer+-- Copyright : Mauro Taraborelli 2012+-- License : BSD3+--+-- Maintainer : maurotaraborelli@gmail.com+-- Stability : experimental+-- Portability : unknown+--+-- Computes hashes traversing recursively through a directory structure.+-- Uses a list of known hashes to audit a set of files.+--+-- Internal module.++module HaskDeep.KnownHash.Writer+ (+ -- * Write the known hashes file+ writeHashes -- HaskDeepConfiguration -> HashSet -> IO ()+ )+where++import Prelude hiding (FilePath)++import Data.ByteString (ByteString)+import qualified Data.ByteString as BS+import qualified Data.ByteString.Char8 as B8+import Data.Conduit (($$))+import qualified Data.Conduit as C+import qualified Data.Conduit.Filesystem as CF+import qualified Data.Conduit.List as CL+import Data.Text ()+import qualified Data.Text.Encoding as TE+import Filesystem.Path ()+import qualified Filesystem.Path.CurrentOS as FSC++import HaskDeep.Configuration+import HaskDeep.HashSet (HashSet)+import qualified HaskDeep.HashSet as HS++-- | Write an @HashSet@ to a known hashes file.+writeHashes :: HaskDeepConfiguration -- ^ Configuration+ -> HashSet -- ^ @HashSet@ to write+ -> IO ()+writeHashes conf hs = C.runResourceT $ CL.sourceList bs_known $$ CF.sinkFile (knownHashes conf)+ where+ newline = "\n" :: ByteString+ root = TE.encodeUtf8 $ either id id $ FSC.toText $ rootDirectory conf+ ignore = case ignoreRule conf of+ (Just rule) -> TE.encodeUtf8 rule+ Nothing -> BS.empty+ files_count = B8.pack $ show $ HS.filesCount hs+ size_sum = B8.pack $ show $ HS.sizeSum hs+ known_header1 = "%%%% HASHDEEP-1.0"+ `BS.append` newline+ known_header2 = "%%%% size,"+ `BS.append` TE.encodeUtf8 (HS.compSymbol hs)+ `BS.append` ",filename"+ `BS.append` newline+ bs_comments = map (flip BS.append newline . BS.append "## ")+ [ "Root directory: " `BS.append` root+ , "Ignore rule : " `BS.append` ignore+ , "Files count : " `BS.append` files_count+ , "Files size : " `BS.append` size_sum+ , "" ]+ bs_hash_set = map (flip BS.append newline . HS.toByteString) $ HS.toAscList hs+ bs_known = [known_header1, known_header2] ++ bs_comments ++ bs_hash_set
+ src/Main.hs view
@@ -0,0 +1,50 @@+-- |+-- Module : HaskDeep.Command.Main+-- Copyright : Mauro Taraborelli 2012+-- License : BSD3+--+-- Maintainer : maurotaraborelli@gmail.com+-- Stability : experimental+-- Portability : unknown+--+-- Computes hashes traversing recursively through a directory structure.+-- Uses a list of known hashes to audit a set of files.++module Main+ (+ main+ )+where++import Prelude hiding (FilePath)++import Crypto.Classes (Hash)+import qualified Options.Applicative as OA++import HaskDeep+import Options++main :: IO ()+main = OA.execParser optionsPI >>= haskdeep++haskdeep :: Options -> IO ()+haskdeep (Options OptComputation OptMD5 conf) = execComputation conf md5hash+haskdeep (Options OptComputation OptSHA1 conf) = execComputation conf sha1hash+haskdeep (Options OptComputation OptSHA256 conf) = execComputation conf sha256hash+haskdeep (Options OptComputation OptSkein512 conf) = execComputation conf skein512hash+haskdeep (Options OptAudit OptMD5 conf) = execAudit conf md5hash+haskdeep (Options OptAudit OptSHA1 conf) = execAudit conf sha1hash+haskdeep (Options OptAudit OptSHA256 conf) = execAudit conf sha256hash+haskdeep (Options OptAudit OptSkein512 conf) = execAudit conf skein512hash++execComputation :: Hash ctx a => HaskDeepConfiguration -> ComputationMode a -> IO ()+execComputation conf cm = writeHashes conf =<< compute conf cm++execAudit :: Hash ctx a => HaskDeepConfiguration -> ComputationMode a -> IO ()+execAudit conf cm = do hashset1 <- readHashes conf+ hashset2 <- compute conf cm+ let (hashdiff1, hashdiff2) = audit hashset1 hashset2+ putStrLn "**** Not present in known hashes"+ print hashdiff1+ putStrLn "**** Present only in root directory"+ print hashdiff2
+ src/Options.hs view
@@ -0,0 +1,101 @@+-- |+-- Module : HaskDeep.Command.Options+-- Copyright : Mauro Taraborelli 2012+-- License : BSD3+--+-- Maintainer : maurotaraborelli@gmail.com+-- Stability : experimental+-- Portability : unknown+--+-- Computes hashes traversing recursively through the directory structure.+-- Use a list of known hashes to audit a set of files.++module Options+ (+ Options(..)+ ,OptExecution(..)+ ,OptCompMode(..)+ ,optionsPI+ )+where++import Prelude hiding (FilePath)++import Data.Text (Text)+import qualified Data.Text as T+import Filesystem.Path.CurrentOS (FilePath)+import qualified Filesystem.Path.CurrentOS as FSC+import Options.Applicative++import HaskDeep++data Options = Options OptExecution OptCompMode HaskDeepConfiguration++data OptExecution = OptComputation+ | OptAudit++data OptCompMode = OptMD5+ | OptSHA1+ | OptSHA256+ | OptSkein512++optionsPI :: ParserInfo Options+optionsPI = info (optionsP <**> helper)+ ( fullDesc+ & progDesc "Computes hashes and audit a set of files"+ & header "haskdeep - file hashing and audit" )++configurationP :: Parser HaskDeepConfiguration+configurationP = HaskDeepConfiguration+ <$> nullOption+ ( long "root"+ & short 'r'+ & metavar "DIRNAME"+ & help "Root directory - default current directory"+ & reader fpReader+ & value (rootDirectory defaultHaskDeepConfiguration))+ <*> nullOption+ ( long "known"+ & short 'k'+ & metavar "FILENAME"+ & help "Known hashes file - default known.haskdeep"+ & reader fpReader+ & value (knownHashes defaultHaskDeepConfiguration))+ <*> nullOption+ ( long "ignore"+ & short 'i'+ & metavar "RULE"+ & help "Regex to ignore files or directories"+ & reader ignReader+ & value (ignoreRule defaultHaskDeepConfiguration))++optionsP :: Parser Options+optionsP = Options+ <$> subparser+ ( command "compute"+ (info (pure OptComputation)+ (progDesc "Computes file hashes and saves them to known hashes file"))+ & command "audit"+ (info (pure OptAudit)+ (progDesc "Audit files comparing them to known hashes")))+ <*> nullOption+ ( long "computation"+ & short 'c'+ & metavar "MODE"+ & help "md5 | sha1 | sha256 | skein512 - default md5"+ & reader compReader+ & value OptMD5)+ <*> configurationP++fpReader :: String -> Maybe FilePath+fpReader fp = Just $ FSC.decodeString fp++ignReader :: String -> Maybe (Maybe Text)+ignReader = Just . Just . T.pack++compReader :: String -> Maybe OptCompMode+compReader "md5" = Just OptMD5+compReader "sha1" = Just OptSHA1+compReader "sha256" = Just OptSHA256+compReader "skein512" = Just OptSkein512+compReader _ = Nothing