module Network.Web.Server.CGI (tryGetCGI) where
import Control.Applicative
import Control.Concurrent
import Data.ByteString.Lazy.Char8 (ByteString)
import qualified Data.ByteString.Lazy.Char8 as LBS
import Data.Char
import Network.URI
import Network.Web.HTTP
import Network.Web.Server.Params
import Network.Web.Utils
import System.IO
import System.Posix.IO
import System.Process
import System.Timeout
gatewayInterface :: String
gatewayInterface = "CGI/1.1"
tryGetCGI :: BasicConfig -> Request -> FilePath -> URLParameter -> ScriptName -> IO (Maybe Response)
tryGetCGI cnf req prog param snm = processCGI `catch` const internalError
where
processCGI = do
(mrhdl0,mhb) <- maybeCreateHandle
(rhdl1,whdl1) <- createHandle
let envVars = makeEnv cnf req param snm
forkIO $ execCGI prog envVars mrhdl0 (Just whdl1) mhb
mrsp <- timeout (10 * 1000000) $ processCGIoutput rhdl1
maybe internalError (return . Just) mrsp
maybeCreateHandle = case reqBody req of
Nothing -> return (Nothing,Nothing)
Just body -> do
(rhdl0,whdl0) <- createHandle
return (Just rhdl0, Just (whdl0,body))
internalError = return $ Just responseInternalServerError
createHandle :: IO (Handle,Handle)
createHandle = do
(rfd,wfd) <- createPipe
rhdl <- fdToHandle rfd
whdl <- fdToHandle wfd
return (rhdl,whdl)
type ENVVARS = [(String,String)]
execCGI :: FilePath -> ENVVARS -> Maybe Handle -> Maybe Handle -> Maybe (Handle,ByteString) -> IO ()
execCGI prog envVars sti sto mhb = do
runProcess prog [] Nothing (Just envVars) sti sto Nothing
case mhb of
Nothing -> return ()
Just (whdl,body) -> do
LBS.hPut whdl body
hClose whdl
makeEnv :: BasicConfig -> Request -> URLParameter -> ScriptName -> ENVVARS
makeEnv cnf req param snm = addLength . addType . addCookie $ pathOrQuery param ++ baseEnv
where
baseEnv = [("GATEWAY_INTERFACE", gatewayInterface)
,("SCRIPT_NAME", snm)
,("REQUEST_METHOD", show (reqMethod req))
,("SERVER_NAME", uriHostName (reqURI req))
,("SERVER_PORT", myPort (tcpInfo cnf))
,("REMOTE_ADDR", peerAddr (tcpInfo cnf))
,("SERVER_PROTOCOL", show (reqVersion req))
,("SERVER_SOFTWARE", serverName cnf)]
pathOrQuery par
| par == "" = [("QUERY_STRING","")
,("PATH_INFO", "")]
| head par == '?' = [("QUERY_STRING", unEscapeString(tail par))
,("PATH_INFO", "")]
| otherwise = [("QUERY_STRING","")
,("PATH_INFO", unEscapeString par)]
addLength = add "CONTENT_LENGTH" (lookupField FkContentLength req)
addType = add "CONTENT_TYPE" (lookupField FkContentType req)
addCookie = add "HTTP_COOKIE" (lookupField FkCookie req)
add _ Nothing envs = envs
add key (Just val) envs = (key,val) : envs
processCGIoutput :: Handle -> IO Response
processCGIoutput rhdl = do
flds <- receiveFields rhdl
case lookupField' FkContentType flds of
Nothing -> return responseInternalServerError
Just _ -> do
let st = maybe OK id (lookupField' FkStatus flds >>= toStatus)
responseAny st flds <$> LBS.hGetContents rhdl
responseAny :: Status -> Fields -> ByteString -> Response
responseAny st flds val = makeResponse3 st (Just val) Nothing flds'
where
flds' = case lookupField' FkSetCookie2 flds of
Nothing -> flds
Just _ -> insertField' FkCacheControl "no-cache=\"set-cookie2\"" flds
responseInternalServerError :: Response
responseInternalServerError = makeResponse InternalServerError []