#if __GLASGOW_HASKELL__ >= 702
#endif
module Hails.IterIO.HttpClient (
HttpRespDC(..)
, simpleHttp, simpleHttpP
, simpleGetHttp, simpleGetHttpP
, simpleHeadHttp, simpleHeadHttpP
, extractBody
, multiHttp, DCHttpResponseHandler
, headRequest, getRequest, postRequest
) where
import qualified Data.ByteString as S
import qualified Data.ByteString.Char8 as S8
import qualified Data.ByteString.Lazy as L
import Data.IterIO
import Data.IterIO.Http
import Data.IterIO.HttpClient ( headRequest
, getRequest
, postRequest
, mkHttpClient
, httpConnect )
import qualified Data.IterIO.HttpClient as I
import Hails.IterIO.Conversions
import LIO
import LIO.TCB (rtioTCB, getTCB)
import LIO.DCLabel
import LIO.LIORef
import LIO.MonadCatch
import Control.Monad
import Control.Exception (SomeException(..))
import qualified OpenSSL.Session as SSL
import System.Environment
type L = L.ByteString
type S = S.ByteString
httpRespToDC :: HttpResp IO -> HttpRespDC
httpRespToDC resp =
HttpRespDC { respStatusDC = respStatus resp
, respHeadersDC = respHeaders resp
, respBodyDC = getTCB >>= \s ->
return $ inumIOtoInumLIO enumHttpBodyResp s }
where enumHttpBodyResp = respBody resp |. maybeChunk
maybeChunk = if respChunk resp then inumToChunks else inumNop
data HttpRespDC = HttpRespDC { respStatusDC :: !HttpStatus
, respHeadersDC :: ![(S, S)]
, respBodyDC :: DC (Onum L DC ())
}
extractBody :: HttpRespDC -> DC L
extractBody resp = do
bodyOnum <- respBodyDC resp
l <- getLabel
ref <- newLIORef l L.empty
bodyOnum |$ do bdy <- pureI
liftLIO $ writeLIORef ref bdy
readLIORef ref
simpleHttp :: HttpReq ()
-> L
-> DC HttpRespDC
simpleHttp = simpleHttpP noPrivs
simpleHttpP :: DCPrivTCB
-> HttpReq ()
-> L
-> DC HttpRespDC
simpleHttpP p req body = do
wguardURLP p req
ctx <- mkSSLContext
liftM httpRespToDC . rtioTCB $ I.simpleHttp req body ctx
simpleGetHttpP :: DCPrivTCB
-> String
-> DC HttpRespDC
simpleGetHttpP p url = simpleHttpP p (getRequest url) L.empty
simpleGetHttp :: String -> DC HttpRespDC
simpleGetHttp = simpleGetHttpP noPrivs
simpleHeadHttpP :: DCPrivTCB
-> String
-> DC HttpRespDC
simpleHeadHttpP p url = simpleHttpP p (getRequest url) L.empty
simpleHeadHttp :: String -> DC HttpRespDC
simpleHeadHttp = simpleHeadHttpP noPrivs
labelOfReq :: HttpReq () -> Maybe (DCLabel, DCLabel)
labelOfReq req = do
scheme <- notNull $ reqScheme req
host <- notNull $ reqHost req
port <- maybe (defaultPort scheme) Just $ reqPort req
let prin = S8.concat [ scheme
, S8.pack "://"
, host
, S8.pack $ ':' : show port ]
return (newDC (principal prin) (<>), newDC (<>) (principal prin))
where defaultPort s | s == S8.pack "http" = return 80
| s == S8.pack "https" = return 443
| otherwise = Nothing
notNull s = if S.null s then Nothing else Just s
wguardURLP :: DCPrivTCB -> HttpReq () -> DC ()
wguardURLP p' req = withCombinedPrivs p' $ \p -> do
l <- getLabel
case labelOfReq req of
Nothing -> throwIO . userError $ "Parse error: cannot create request label"
Just (lr, lw) -> do
unless (leqp p l lr) $ throwIO . userError $
"Current label must flow to origin read label"
taintP p lw
mkSSLContext :: DC (Maybe SSL.SSLContext)
mkSSLContext = rtioTCB $ do
env <- getEnvironment
case lookup "HAILS_SSL_CA_FILE" env of
Nothing -> return Nothing
Just caDir -> do ctx <- SSL.context
SSL.contextSetCADirectory ctx caDir
return $! Just ctx
multiHttp :: (HttpReq (), L)
-> DCHttpResponseHandler
-> DC ()
multiHttp = multiHttpP noPrivs
multiHttpP :: DCPrivTCB
-> (HttpReq (), L)
-> DCHttpResponseHandler
-> DC ()
multiHttpP p' (req, body) handler = withCombinedPrivs p' $ \p -> do
let scheme = reqScheme req
isHttps = scheme == (S8.pack "https")
port <- maybe (defaultPort scheme) return $ reqPort req
wguardURLP p req
ctx <- mkSSLContext
s <- getTCB
(sIter,sOnum) <- rtioTCB $ do
client <- mkHttpClient (reqHost req) port ctx isHttps
(i,o) <- httpConnect client
return (iterIOtoIterLIO i, inumIOtoInumLIO o s)
sOnum |$ dcInumHttpClient (req, body) handler .| sIter
where defaultPort s | s == S8.pack "http" = return 80
| s == S8.pack "https" = return 443
| otherwise = throwIO . userError $
"Unrecognized scheme" ++ S8.unpack s
type DCHttpResponseHandler = HttpRespDC -> Iter L DC (Maybe (HttpReq (), L))
dcInumHttpClient :: (HttpReq s, L)
-> DCHttpResponseHandler
-> Inum L L DC a
dcInumHttpClient (req, body) respHandler = mkInumM $
tryI (irun $ enumHttpReq req body) >>=
either (fatal . fst) (const loop)
where loop = do eof <- atEOFI
unless eof doresp
doresp = do
resp <- liftI $ iterIOtoIterLIO httpRespI
mreq <- catchI (liftI $ respHandler (httpRespToDC resp)) errH
maybe (return ())
(\(req', body') -> do
er <- tryI (irun $ enumHttpReq req' body')
either (fatal . fst) (const loop) er
) mreq
fatal (SomeException _) = return ()
errH (SomeException _) = return . return $ Nothing