module Network.HTTP.Lucu.Postprocess ( postprocess -- Interaction -> STM () , completeUnconditionalHeaders -- Response -> IO Response ) where import Control.Concurrent.STM import Control.Monad import Data.Char import Data.Maybe import GHC.Conc (unsafeIOToSTM) import Network.HTTP.Lucu.Headers import Network.HTTP.Lucu.HttpVersion import Network.HTTP.Lucu.Interaction import Network.HTTP.Lucu.RFC1123DateTime import Network.HTTP.Lucu.Request import Network.HTTP.Lucu.Response import Network.HTTP.Lucu.Utils import System.Time {- * Response が未設定なら、HTTP/1.1 500 Internal Server Error にする。 * Content-Length があれば、それを削除する。 * HTTP/1.1 であり、且つ body を持つ事が出來る時、Transfer-Encoding の 最後の要素が chunked でなければ 500 Internal Error にする。 Transfer-Encoding が未設定であれば、chunked に設定する。 * HTTP/1.0 なのに Transfer-Encoding が設定濟なら 500 Internal Server Error にする。但し identity だけは許す。 * body を持つ事が出來る時、Content-Type が無ければ application/octet-stream にする。出來ない時、HEAD でなければ Content-Type を削除する。 * body を持つ事が出來ない時、body 破棄フラグを立てる。 * Connection: close が設定されてゐる時、切斷フラグを立てる。 * 切斷フラグが立ってゐる時、Connection: close を設定する。 * Server が無ければ設定。 * Date が無ければ設定。 -} {- Postprocess は body を補完した後で實行する事 -} postprocess :: Interaction -> STM () postprocess itr = do res <- readTVar (itrResponse itr) when (res == Nothing) $ setStatus itr InternalServerError when (itrRequest itr /= Nothing) $ relyOnRequest itr do oldRes <- readTVar (itrResponse itr) newRes <- unsafeIOToSTM $ completeUnconditionalHeaders $ fromJust oldRes setRes itr newRes where relyOnRequest itr = do resM <- readTVar (itrResponse itr) let req = fromJust $ itrRequest itr reqVer = reqVersion req res = fromJust resM status = resStatus res canHaveBody = if reqMethod req == HEAD then False else isInformational status || status == NoContent || status == ResetContent || status == NotModified setRes itr (deleteHeader res "Content-Length") if canHaveBody then do if reqVer == HttpVersion 1 1 then case getHeader res "Transfer-Encoding" of Nothing -> setRes itr (setHeader res "Transfer-Encoding" "chunked") Just te -> let teList = [trim isWhiteSpace x | x <- splitBy (== ',') (map toLower te)] in when (teList == [] || last teList /= "chunked") $ setStatus itr InternalServerError else case getHeader res "Transfer-Encoding" of Nothing -> return () Just "identity" -> return () _ -> setStatus itr InternalServerError when (getHeader res "Content-Type" == Nothing) $ setRes itr (setHeader res "Content-Type" "application/octet-stream") else -- body 関連のヘッダを削除。但し HEAD なら Content-* は殘す do setRes itr (deleteHeader res "Transfer-Encoding") when (reqMethod req /= HEAD) $ setRes itr (deleteHeader res "Content-Type") if fmap (map toLower) (getHeader res "Connection") == Just "close" then writeTVar (itrWillClose itr) True else setRes itr (setHeader res "Connection" "close") when (reqMethod req == HEAD || not canHaveBody) $ writeTVar (itrWillDiscardBody itr) True setStatus itr status = writeTVar (itrResponse itr) (Just $ Response { resVersion = HttpVersion 1 1 , resStatus = status , resHeaders = [] }) setRes itr res = writeTVar (itrResponse itr) (Just res) completeUnconditionalHeaders :: Response -> IO Response completeUnconditionalHeaders res = return res >>= compServer >>= compDate >>= return where compServer res = case getHeader res "Server" of Nothing -> return $ addHeader res "Server" "Lucu/1.0" Just _ -> return res compDate res = case getHeader res "Date" of Nothing -> do time <- getClockTime return $ addHeader res "Date" $ formatHTTPDateTime time Just _ -> return res