packages feed

pms-infra-cmdrun-0.0.1.0: src/PMS/Infra/CmdRun/DS/Utility.hs

{-# LANGUAGE OverloadedStrings #-}

module PMS.Infra.CmdRun.DS.Utility where

import System.Log.FastLogger
import qualified Control.Exception.Safe as E
import Control.Monad.IO.Class
import Control.Monad.Except
import Control.Monad.Reader
import qualified Data.Text as T
import Data.Char
import Control.Monad

import qualified PMS.Domain.Model.DM.Type as DM
import qualified PMS.Domain.Model.DS.Utility as DM
import PMS.Infra.CmdRun.DM.Type

-- |
--
runApp :: DM.DomainData -> AppData -> TimedFastLogger -> AppContext a -> IO (Either DM.ErrorData a)
runApp domDat appDat logger ctx =
  DM.runFastLoggerT domDat logger
    $ runExceptT
    $ flip runReaderT domDat
    $ runReaderT ctx appDat


-- |
--
liftIOE :: IO a -> AppContext a
liftIOE f = liftIO (go f) >>= liftEither
  where
    go :: IO b -> IO (Either String b)
    go x = E.catchAny (Right <$> x) errHdl

    errHdl :: E.SomeException -> IO (Either String a)
    errHdl = return . Left . show


-- |
--
validateCommand :: String -> AppContext String
validateCommand cmd = do
  when (null cmd) $
    throwError "Command is empty."

  when (".." `T.isInfixOf` tcmd) $
    throwError "Command contains directory traversal '..'."

  when ("/" `T.isInfixOf` tcmd) $
    throwError "Command must not contain '/'."

  when ("\\" `T.isInfixOf` tcmd) $
    throwError "Command must not contain '\\'."

  when (any (not . isAllowedChar) cmd) $
    throwError $ "Command contains disallowed characters: " ++ cmd

  return cmd

  where
    tcmd = T.pack cmd
    isAllowedChar c = isAlphaNum c || c `elem` ("-._" :: String)
  

-- |
--
validateCommandArg :: String -> AppContext String
validateCommandArg arg = do
  let tArg = T.pack arg
  when (hasDangerousChars tArg) $
    throwError $ "Argument contains potentially dangerous characters: " <> arg
  return arg
  where
    hasDangerousChars :: T.Text -> Bool
    hasDangerousChars txt =
      any (`T.isInfixOf` txt) [";", "&&", "|", "$", "`", "<", ">", "\\", "\""]

-- |
--
validateCommandArgs :: [String] -> AppContext [String]
validateCommandArgs = mapM validateCommandArg


-- |
--
validateMessage :: String -> IO String
validateMessage cmd = do
  when (any (`elem` forbiddenChars) cmd) $
    E.throwString "Command contains forbidden characters."

  case words cmd of
    (firstWord : _) -> when (firstWord `elem` forbiddenCommands) $
                        E.throwString "Command is forbidden."
    _ -> return ()

  return cmd
  where
    forbiddenChars :: [Char]
    forbiddenChars = [';', '&', '|', '`']

    forbiddenCommands :: [String]
    forbiddenCommands = ["rm", "mv", "dd", "chmod", "chown", "shutdown", "reboot", "kill", "nc", "telnet", "ssh"]