{-# LANGUAGE
    BangPatterns
  #-}
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.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
    = 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
          = updateItr itr itrResponse
            $! \ res -> res {
                          resStatus = status
                        }

      completeAuthority :: Request -> STM ()
      completeAuthority !req
          = 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 = itrLocalPort itr
                         portStr
                              = case port of
                                  80 -> ""
                                  n  -> ':' : show n
                     updateAuthority host (C8.pack portStr)
              else
                  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
          = 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
          = 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 -> unless (value `noCaseEq` C8.pack "identity")
                                   $ 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 -> when (value `noCaseEq` C8.pack "close")
                                   $ writeItr itr itrWillClose True