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 <- readItr itr itrResponse id when (res == Nothing) $ setStatus itr InternalServerError when (itrRequest itr /= Nothing) $ relyOnRequest itr do oldRes <- readItr itr itrResponse id newRes <- unsafeIOToSTM $ completeUnconditionalHeaders $ fromJust oldRes writeItr itr itrResponse $ Just newRes where relyOnRequest itr = do status <- readItr itr itrResponse (resStatus . fromJust) let req = fromJust $ itrRequest itr reqVer = reqVersion req canHaveBody = if reqMethod req == HEAD then False else not (isInformational status || status == NoContent || status == ResetContent || status == NotModified ) updateRes itr $ deleteHeader "Content-Length" if canHaveBody then do teM <- readHeader itr "Transfer-Encoding" if reqVer == HttpVersion 1 1 then do case teM of Nothing -> updateRes itr $ setHeader "Transfer-Encoding" "chunked" Just te -> let teList = [trim isWhiteSpace x | x <- splitBy (== ',') (map toLower te)] in when (teList == [] || last teList /= "chunked") $ setStatus itr InternalServerError writeItr itr itrWillChunkBody True else case fmap (map toLower) teM of Nothing -> return () Just "identity" -> return () _ -> setStatus itr InternalServerError cType <- readHeader itr "Content-Type" when (cType == Nothing) $ updateRes itr $ setHeader "Content-Type" "application/octet-stream" else -- body 関連のヘッダを削除。但し HEAD なら Content-* は殘す do updateRes itr $ deleteHeader "Transfer-Encoding" when (reqMethod req /= HEAD) $ updateRes itr $ deleteHeader "Content-Type" conn <- readHeader itr "Connection" case fmap (map toLower) conn of Just "close" -> writeItr itr itrWillClose True _ -> updateRes itr $ setHeader "Connection" "close" when (reqMethod req == HEAD || not canHaveBody) $ writeTVar (itrWillDiscardBody itr) True setStatus :: Interaction -> StatusCode -> STM () setStatus itr status = writeTVar (itrResponse itr) (Just $ Response { resVersion = HttpVersion 1 1 , resStatus = status , resHeaders = [] }) readHeader :: Interaction -> String -> STM (Maybe String) readHeader itr name = do valueMM <- readItrF itr itrResponse $ getHeader name case valueMM of Just (Just val) -> return $ Just val _ -> return Nothing updateRes :: Interaction -> (Response -> Response) -> STM () updateRes itr updator = updateItrF itr itrResponse updator completeUnconditionalHeaders :: Response -> IO Response completeUnconditionalHeaders res = return res >>= compServer >>= compDate >>= return where compServer res = case getHeader "Server" res of Nothing -> return $ addHeader "Server" "Lucu/1.0" res Just _ -> return res compDate res = case getHeader "Date" res of Nothing -> do time <- getClockTime return $ addHeader "Date" (formatHTTPDateTime time) res Just _ -> return res