module Network.HTTP.Lucu.Preprocess ( preprocess ) where import Control.Concurrent.STM import Control.Monad import qualified Data.ByteString as Strict (ByteString) import qualified Data.ByteString.Char8 as C8 hiding (ByteString) import Data.Char import Data.Maybe import Network.HTTP.Lucu.Config import Network.HTTP.Lucu.Headers import Network.HTTP.Lucu.HttpVersion import Network.HTTP.Lucu.Interaction import Network.HTTP.Lucu.Request import Network.HTTP.Lucu.Response import Network import Network.URI {- * URI にホスト名が存在しない時、 [1] HTTP/1.0 ならば Config を使って補完 [2] HTTP/1.1 ならば Host ヘッダで補完。Host が無ければ 400。 * Expect: に問題があった場合は 417 Expectation Failed に設定。 100-continue 以外のものは全部 417 に。 * Transfer-Encoding: に問題があったら 501 Not Implemented にする。具 体的には、identity でも chunked でもなければ 501 Not Implemented に する。 * メソッドが GET, HEAD, POST, PUT, DELETE の何れでもない場合は 501 Not Implemented にする。 * HTTP/1.0 でも HTTP/1.1 でもないリクエストに對しては 505 HTTP Version Not Supported を返す。 * POST または PUT に Content-Length も Transfer-Encoding も無い時は、 411 Length Required にする。 * Content-Length の値が數値でなかったり負だったりしたら 400 Bad Request にする。 * willDiscardBody その他の變數を設定する。 -} preprocess :: Interaction -> STM () preprocess itr = itr `seq` do req <- readItr itr itrRequest fromJust let reqVer = reqVersion req if reqVer /= HttpVersion 1 0 && reqVer /= HttpVersion 1 1 then do setStatus HttpVersionNotSupported writeItr itr itrWillClose True else -- HTTP/1.0 では Keep-Alive できない do when (reqVer == HttpVersion 1 0) $ writeItr itr itrWillClose True -- ホスト部の補完 completeAuthority req case reqMethod req of GET -> return () HEAD -> writeItr itr itrWillDiscardBody True POST -> writeItr itr itrRequestHasBody True PUT -> writeItr itr itrRequestHasBody True DELETE -> return () _ -> setStatus NotImplemented preprocessHeader req where setStatus :: StatusCode -> STM () setStatus status = status `seq` updateItr itr itrResponse $! \ res -> res { resStatus = status } completeAuthority :: Request -> STM () completeAuthority req = req `seq` when (uriAuthority (reqURI req) == Nothing) $ if reqVersion req == HttpVersion 1 0 then -- HTTP/1.0 なので Config から補完 do let conf = itrConfig itr host = cnfServerHost conf port = case cnfServerPort conf of PortNumber n -> Just (fromIntegral n :: Int) _ -> Nothing portStr = case port of Just 80 -> Just "" Just n -> Just $ ":" ++ show n Nothing -> Nothing case portStr of Just str -> updateAuthority host (C8.pack str) -- FIXME: このエラーの原因は、listen してゐるソ -- ケットが INET でない故にポート番號が分からな -- い事だが、その事をどうにかして通知した方が良 -- いと思ふ。stderr? Nothing -> setStatus InternalServerError else do case getHeader (C8.pack "Host") req of Just str -> let (host, portStr) = parseHost str in updateAuthority host portStr Nothing -> setStatus BadRequest parseHost :: Strict.ByteString -> (Strict.ByteString, Strict.ByteString) parseHost = C8.break (== ':') updateAuthority :: Strict.ByteString -> Strict.ByteString -> STM () updateAuthority host portStr = host `seq` portStr `seq` updateItr itr itrRequest $! \ (Just req) -> Just req { reqURI = let uri = reqURI req in uri { uriAuthority = Just URIAuth { uriUserInfo = "" , uriRegName = C8.unpack host , uriPort = C8.unpack portStr } } } preprocessHeader :: Request -> STM () preprocessHeader req = req `seq` do case getHeader (C8.pack "Expect") req of Nothing -> return () Just value -> if value `noCaseEq` C8.pack "100-continue" then writeItr itr itrExpectedContinue True else setStatus ExpectationFailed case getHeader (C8.pack "Transfer-Encoding") req of Nothing -> return () Just value -> if value `noCaseEq` C8.pack "identity" then return () else if value `noCaseEq` C8.pack "chunked" then writeItr itr itrRequestIsChunked True else setStatus NotImplemented case getHeader (C8.pack "Content-Length") req of Nothing -> return () Just value -> if C8.all isDigit value then do let Just (len, _) = C8.readInt value writeItr itr itrReqChunkLength $ Just len writeItr itr itrReqChunkRemaining $ Just len else setStatus BadRequest case getHeader (C8.pack "Connection") req of Nothing -> return () Just value -> if value `noCaseEq` C8.pack "close" then writeItr itr itrWillClose True else return ()