{-# LANGUAGE OverloadedStrings #-} module Main where import Crypto.Hash (Digest) import qualified Crypto.Hash as CH import Data.Byteable (toBytes) import Data.ByteString (ByteString) import Data.ByteString.Lazy (fromStrict, toStrict) import Options.Applicative import System.Exit (exitFailure, exitSuccess) import System.IO.Streams (InputStream, stdin, stdout, withFileAsInput, write) import System.IO.Streams.Crypto (hashInputStream) import qualified Data.Multihash.Base as MB import qualified Data.Multihash.Digest as MH data Termination = Null | Newline deriving (Show, Eq) data Config = Config { cfFile :: Maybe FilePath , cfAlgo :: MH.HashAlgorithm , cfBase :: MB.BaseEncoding , cfHash :: Maybe MH.Digest , cfTerm :: Termination } deriving Show main :: IO () main = do -- TODO add file checking config <- execParser opts digest <- maybe (hashStdin config) (hashFile config) $ cfFile config write (multihash config digest) stdout where hashStdin config = hash (cfAlgo config) stdin hashFile config file = withFileAsInput file . hash $ cfAlgo config multihash (Config _file algo base _hash term) = Just . toStrict . line term . MB.encode base . MH.encode algo line Null = (<> "\0") line Newline = (<> "\n") -- TODO add BLAKE support hash :: MH.HashAlgorithm -> InputStream ByteString -> IO MH.Digest hash MH.SHA1 is = toBytes <$> (hashInputStream is :: IO (Digest CH.SHA1)) hash MH.SHA256 is = toBytes <$> (hashInputStream is :: IO (Digest CH.SHA256)) hash MH.SHA512 is = toBytes <$> (hashInputStream is :: IO (Digest CH.SHA512)) hash MH.SHA3 is = toBytes <$> (hashInputStream is :: IO (Digest CH.SHA3_256)) hash MH.BLAKE2B _ = undefined hash MH.BLAKE2S _ = undefined opts :: ParserInfo Config opts = info (helper <*> (Config <$> fileArg <*> algoOpt <*> baseOpt <*> checkOpt <*> nullTermFlag )) (fullDesc <> header "Generate a multihash for the given input." <> progDesc "Hash from FILE or stdin if not given.") algoOpt :: Parser MH.HashAlgorithm algoOpt = option auto $ long "algorithm" <> short 'a' <> metavar "ALGO" <> showDefault <> value MH.SHA256 <> help ("Hash algorithm to apply to input, ignored if checking hash " <> show ([minBound..] :: [MH.HashAlgorithm])) baseOpt :: Parser MB.BaseEncoding baseOpt = option auto $ long "encoding" <> short 'e' <> metavar "ENCODING" <> showDefault <> value MB.Base58 <> help ("Base encoding of output digest, ignored if checking hash " <> show ([minBound..] :: [MB.BaseEncoding])) checkOpt :: Parser (Maybe MH.Digest) checkOpt = optional . option auto $ long "check" <> short 'c' <> metavar "DIGEST" <> help "Check for matching digest" nullTermFlag :: Parser Termination nullTermFlag = flag Newline Null $ long "print0" <> short '0' <> help "End filenames with NUL, for use with xargs" fileArg :: Parser (Maybe FilePath) fileArg = optional . argument str $ metavar "FILE"