module Happstack.Server.Validation where
import Control.Concurrent (forkIO)
import Control.Exception (evaluate)
import Control.Monad
import Control.Monad.Trans (MonadIO(liftIO))
import qualified Data.ByteString.Char8 as B
import qualified Data.ByteString.Lazy.Char8 as L
import Happstack.Server.Types (Conf(..), Response(..), getHeader, nullConf)
import Happstack.Server.Response (ToMessage, toResponse)
import System.Exit (ExitCode(ExitSuccess, ExitFailure))
import System.IO (hGetContents, hClose)
import System.Process (runInteractiveProcess, waitForProcess)
setValidator :: (Response -> IO Response) -> Response -> Response
setValidator :: (Response -> IO Response) -> Response -> Response
setValidator Response -> IO Response
v Response
r = Response
r { rsValidator :: Maybe (Response -> IO Response)
rsValidator = forall a. a -> Maybe a
Just Response -> IO Response
v }
setValidatorSP :: (Monad m, ToMessage r) => (Response -> IO Response) -> m r -> m Response
setValidatorSP :: forall (m :: * -> *) r.
(Monad m, ToMessage r) =>
(Response -> IO Response) -> m r -> m Response
setValidatorSP Response -> IO Response
v m r
sp = forall (m :: * -> *) a. Monad m => a -> m a
return forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Response -> IO Response) -> Response -> Response
setValidator Response -> IO Response
v forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a. ToMessage a => a -> Response
toResponse forall (m :: * -> *) a b. Monad m => (a -> m b) -> m a -> m b
=<< m r
sp
validateConf :: Conf
validateConf :: Conf
validateConf = Conf
nullConf { validator :: Maybe (Response -> IO Response)
validator = forall a. a -> Maybe a
Just forall (m :: * -> *) r. (MonadIO m, ToMessage r) => r -> m Response
wdgHTMLValidator }
runValidator :: (Response -> IO Response) -> Response -> IO Response
runValidator :: (Response -> IO Response) -> Response -> IO Response
runValidator Response -> IO Response
defaultValidator Response
r =
case Response -> Maybe (Response -> IO Response)
rsValidator Response
r of
Maybe (Response -> IO Response)
Nothing -> Response -> IO Response
defaultValidator Response
r
(Just Response -> IO Response
altValidator) -> Response -> IO Response
altValidator Response
r
wdgHTMLValidator :: (MonadIO m, ToMessage r) => r -> m Response
wdgHTMLValidator :: forall (m :: * -> *) r. (MonadIO m, ToMessage r) => r -> m Response
wdgHTMLValidator = forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO forall b c a. (b -> c) -> (a -> b) -> a -> c
. String
-> [String]
-> Maybe String
-> Maybe [(String, String)]
-> (Maybe ByteString -> Bool)
-> Response
-> IO Response
lazyProcValidator String
"validate" [String
"-w",String
"--verbose",String
"--charset=utf-8"] forall a. Maybe a
Nothing forall a. Maybe a
Nothing Maybe ByteString -> Bool
handledContentTypes forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a. ToMessage a => a -> Response
toResponse
where
handledContentTypes :: Maybe ByteString -> Bool
handledContentTypes (Just ByteString
ct) = forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
elem (forall a. (a -> Bool) -> [a] -> [a]
takeWhile (\Char
c -> Char
c forall a. Eq a => a -> a -> Bool
/= Char
';' Bool -> Bool -> Bool
&& Char
c forall a. Eq a => a -> a -> Bool
/= Char
' ') (ByteString -> String
B.unpack ByteString
ct)) [ String
"text/html", String
"application/xhtml+xml" ]
handledContentTypes Maybe ByteString
Nothing = Bool
False
noopValidator :: Response -> IO Response
noopValidator :: Response -> IO Response
noopValidator = forall (m :: * -> *) a. Monad m => a -> m a
return
lazyProcValidator :: FilePath
-> [String]
-> Maybe FilePath
-> Maybe [(String, String)]
-> (Maybe B.ByteString -> Bool)
-> Response
-> IO Response
lazyProcValidator :: String
-> [String]
-> Maybe String
-> Maybe [(String, String)]
-> (Maybe ByteString -> Bool)
-> Response
-> IO Response
lazyProcValidator String
exec [String]
args Maybe String
wd Maybe [(String, String)]
env Maybe ByteString -> Bool
mimeTypePred Response
response
| Maybe ByteString -> Bool
mimeTypePred (forall r. HasHeaders r => String -> r -> Maybe ByteString
getHeader String
"content-type" Response
response) =
do (Handle
inh, Handle
outh, Handle
errh, ProcessHandle
ph) <- String
-> [String]
-> Maybe String
-> Maybe [(String, String)]
-> IO (Handle, Handle, Handle, ProcessHandle)
runInteractiveProcess String
exec [String]
args Maybe String
wd Maybe [(String, String)]
env
String
out <- Handle -> IO String
hGetContents Handle
outh
String
err <- Handle -> IO String
hGetContents Handle
errh
forall (f :: * -> *) a. Functor f => f a -> f ()
void forall a b. (a -> b) -> a -> b
$ IO () -> IO ThreadId
forkIO forall a b. (a -> b) -> a -> b
$ do Handle -> ByteString -> IO ()
L.hPut Handle
inh (Response -> ByteString
rsBody Response
response)
Handle -> IO ()
hClose Handle
inh
forall (f :: * -> *) a. Functor f => f a -> f ()
void forall a b. (a -> b) -> a -> b
$ IO () -> IO ThreadId
forkIO forall a b. (a -> b) -> a -> b
$ forall a. a -> IO a
evaluate (forall (t :: * -> *) a. Foldable t => t a -> Int
length String
out) forall (m :: * -> *) a b. Monad m => m a -> m b -> m b
>> forall (m :: * -> *) a. Monad m => a -> m a
return ()
forall (f :: * -> *) a. Functor f => f a -> f ()
void forall a b. (a -> b) -> a -> b
$ IO () -> IO ThreadId
forkIO forall a b. (a -> b) -> a -> b
$ forall a. a -> IO a
evaluate (forall (t :: * -> *) a. Foldable t => t a -> Int
length String
err) forall (m :: * -> *) a b. Monad m => m a -> m b -> m b
>> forall (m :: * -> *) a. Monad m => a -> m a
return ()
ExitCode
ec <- ProcessHandle -> IO ExitCode
waitForProcess ProcessHandle
ph
case ExitCode
ec of
ExitCode
ExitSuccess -> forall (m :: * -> *) a. Monad m => a -> m a
return Response
response
(ExitFailure Int
_) ->
forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ forall a. ToMessage a => a -> Response
toResponse ([String] -> String
unlines ([ String
"ExitCode: " forall a. [a] -> [a] -> [a]
++ forall a. Show a => a -> String
show ExitCode
ec
, String
"stdout:"
, String
out
, String
"stderr:"
, String
err
, String
"input:"
] forall a. [a] -> [a] -> [a]
++
ByteString -> [String]
showLines (Response -> ByteString
rsBody Response
response)))
| Bool
otherwise = forall (m :: * -> *) a. Monad m => a -> m a
return Response
response
where
column :: String
column = String
" " forall a. [a] -> [a] -> [a]
++ (forall a. Int -> [a] -> [a]
take Int
120 forall a b. (a -> b) -> a -> b
$ forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
concatMap (\Int
n -> String
" " forall a. [a] -> [a] -> [a]
++ forall a. Show a => a -> String
show Int
n) (forall a. Int -> [a] -> [a]
drop Int
1 forall a b. (a -> b) -> a -> b
$ forall a. [a] -> [a]
cycle [Int
0..Int
9::Int]))
showLines :: L.ByteString -> [String]
showLines :: ByteString -> [String]
showLines ByteString
string = String
column forall a. a -> [a] -> [a]
: forall a b c. (a -> b -> c) -> [a] -> [b] -> [c]
zipWith (\Integer
n -> \ByteString
l -> forall a. Show a => a -> String
show Integer
n forall a. [a] -> [a] -> [a]
++ String
" " forall a. [a] -> [a] -> [a]
++ (ByteString -> String
L.unpack ByteString
l)) [Integer
1::Integer ..] (ByteString -> [ByteString]
L.lines ByteString
string)