{-# LANGUAGE RecordWildCards #-} module Main ( main ) where import Control.Exception (displayException) import Control.Monad (void, when) import Data.Semigroup ((<>)) import qualified Options.Applicative as O import System.Directory (createDirectoryIfMissing, doesDirectoryExist, doesFileExist, listDirectory, renameFile) import System.FilePath (takeDirectory, takeFileName, ()) import System.IO (hClose, hPutStrLn, openBinaryTempFile, stderr) import Crypto.QNAP (decrypt) data Command = Command { commandSingleFile :: Bool , commandInline :: Bool , commandPassword :: String , commandSource :: String , commandDestination :: String , commandVerbose :: Bool } deriving (Show) command :: O.Parser Command command = O.hsubparser $ O.command "dir" ( O.info parseDirectory (O.progDesc "decrypt a directory") ) <> O.command "dir-in-place" ( O.info parseDirectoryInPlace (O.progDesc "decrypt a directory in place") ) <> O.command "file" ( O.info parseFile (O.progDesc "decrypt a file") ) <> O.command "file-in-place" ( O.info parseFileInPlace (O.progDesc "decrypt a file in place") ) where parseDirectory = Command False False <$> O.strOption ( O.long "password" <> O.short 'p' <> O.metavar "PASS" <> O.help "Password" ) <*> O.strOption ( O.long "source" <> O.short 's' <> O.metavar "SRC" <> O.help "Directory to be decrypted" ) <*> O.strOption ( O.long "destination" <> O.short 'd' <> O.metavar "DST" <> O.help "Directory to store the decrypted files" ) <*> O.switch ( O.long "verbose" <> O.short 'v' <> O.help "Print all decrypted files" ) parseDirectoryInPlace = Command False True <$> O.strOption ( O.long "password" <> O.short 'p' <> O.metavar "PASS" <> O.help "Password" ) <*> O.strOption ( O.long "directory" <> O.short 'd' <> O.metavar "DIR" <> O.help "Directory to be decrypted in place" ) <*> pure "" <*> O.switch ( O.long "verbose" <> O.short 'v' <> O.help "Print all decrypted files" ) parseFile = Command True False <$> O.strOption ( O.long "password" <> O.short 'p' <> O.metavar "PASS" <> O.help "Password" ) <*> O.strOption ( O.long "source" <> O.short 's' <> O.metavar "SRC" <> O.help "File to be decrypted" ) <*> O.strOption ( O.long "destination" <> O.short 'd' <> O.metavar "DST" <> O.help "File/directory to store the decrypted file" ) <*> O.switch ( O.long "verbose" <> O.short 'v' <> O.help "Print all decrypted files" ) parseFileInPlace = Command True True <$> O.strOption ( O.long "password" <> O.short 'p' <> O.metavar "PASS" <> O.help "Password" ) <*> O.strOption ( O.long "file" <> O.short 'f' <> O.metavar "FILE" <> O.help "File to be decrypted in place" ) <*> pure "" <*> O.switch ( O.long "verbose" <> O.short 'v' <> O.help "Print all decrypted files" ) main :: IO () main = do Command{..} <- O.execParser $ O.info (command O.<**> O.helper) ( O.fullDesc <> O.progDesc "Decrypt QNAP files/folders" <> O.footer "WARNING: The target file/directory will be overwritten" ) case (commandSingleFile, commandInline) of (False, False) -> cmdDir commandVerbose commandPassword [(commandSource, commandDestination)] (False, True) -> cmdDirInPlace commandVerbose commandPassword [commandSource] (True, False) -> cmdFile commandVerbose commandPassword commandSource commandDestination (True, True) -> cmdFileInPlace commandVerbose commandPassword commandSource cmdDir :: Bool -> String -> [(FilePath, FilePath)] -> IO () cmdDir verbose pass = go where go [] = return () go ((src, dst):stack) = do isFile <- doesFileExist src isDir <- doesDirectoryExist src case (isFile, isDir) of (True, False) -> do cmdFile verbose pass src dst go stack (False, True) -> do files <- listDirectory src createDirectoryIfMissing True dst go (map (\file -> (src file, dst file)) files ++ stack) _ -> return () cmdDirInPlace :: Bool -> String -> [FilePath] -> IO () cmdDirInPlace verbose pass = go where go [] = return () go (entry:stack) = do isFile <- doesFileExist entry isDir <- doesDirectoryExist entry case (isFile, isDir) of (True, False) -> do cmdFileInPlace verbose pass entry go stack (False, True) -> do files <- listDirectory entry go (map (entry ) files ++ stack) _ -> return () cmdFile :: Bool -> String -> FilePath -> FilePath -> IO () cmdFile verbose pass src dst = do dstIsDir <- doesDirectoryExist dst let dst' = if dstIsDir then dst takeFileName src else dst void $ decrypt' verbose pass src dst' cmdFileInPlace :: Bool -> String -> FilePath -> IO () cmdFileInPlace verbose pass file = do (tempFileName, tmpHandle) <- openBinaryTempFile (takeDirectory file) (takeFileName file ++ ".temp.qnap") hClose tmpHandle success <- decrypt' verbose pass file tempFileName -- the decrypt removes the output file in case of an error -> no need to do it here also when success $ renameFile tempFileName file decrypt' :: Bool -> String -> FilePath -> FilePath -> IO Bool decrypt' verbose pass src dst = do result <- decrypt pass src dst case result of Just err -> do hPutStrLn stderr (src ++ ": " ++ displayException err) return False Nothing -> do when verbose $ hPutStrLn stderr (src ++ ": OK") return True