{-# LANGUAGE OverloadedStrings, BangPatterns #-}
{-# LANGUAGE NamedFieldPuns, RecordWildCards #-}

module Network.Wai.Handler.Warp.HTTP2.HPACK (
    hpackEncodeHeader
  , hpackEncodeHeaderLoop
  , hpackDecodeHeader
  , just
  , fixHeaders
  , addHeader -- testing
  , deleteUnnecessaryHeaders
  ) where

import qualified Control.Exception as E
import Network.HPACK hiding (Buffer)
import Network.HPACK.Token
import Network.HTTP2

import Network.Wai.Handler.Warp.HTTP2.Types
import Network.Wai.Handler.Warp.Imports
import Network.Wai.Handler.Warp.PackInt
import qualified Network.Wai.Handler.Warp.Settings as S
import Network.Wai.Handler.Warp.Types

-- $setup
-- >>> :set -XOverloadedStrings

----------------------------------------------------------------

{-# INLINE addHeader #-}
addHeader :: Token -> ByteString -> ValueTable -> TokenHeaderList -> TokenHeaderList
addHeader t "" _ ths
  | t == tokenServer  = filter ((/= tokenServer) . fst) ths
addHeader t v tbl ths = case getHeaderValue t tbl of
    Nothing -> (t,v) : ths
    _       -> ths

fixHeaders :: Context
           -> Rspn
           -> InternalInfo
           -> S.Settings
           -> IO TokenHeaderList
fixHeaders Context{..} rspn ii settings = do
    date <- getDate ii
    let !s = rspnStatus rspn
        !status = packStatus s
        !defServer = S.settingsServerName settings
        (!ths0,tbl) = rspnHeaders rspn
        !ths1 = addHeader tokenServer defServer tbl ths0
        !ths2 = addHeader tokenDate date tbl ths1
        !ths3 = (tokenStatus, status) : ths2
        !ths4 = deleteUnnecessaryHeaders ths3 tbl
    return ths4

deleteUnnecessaryHeaders :: TokenHeaderList -> ValueTable -> TokenHeaderList
deleteUnnecessaryHeaders htl0 tbl0 = loop headersToBeRemoved htl0
  where
    loop [] thl = thl
    loop (t:ts) thl = case getHeaderValue t tbl0 of
      Nothing -> thl
      _       -> let !thl' = filter ((/= t) . fst) thl
                 in loop ts thl'

headersToBeRemoved :: [Token]
headersToBeRemoved = [ tokenConnection
                     , tokenTransferEncoding
                     -- Keep-Alive:
                     -- Proxy-Connection:
                     -- Upgrade:
                     ]

----------------------------------------------------------------

strategy :: EncodeStrategy
strategy = EncodeStrategy { compressionAlgo = Linear, useHuffman = False }

-- Set-Cookie: contains only one cookie value.
-- So, we don't need to split it.
hpackEncodeHeader :: Context -> Buffer -> BufSize
                  -> TokenHeaderList
                  -> IO (TokenHeaderList, Int)
hpackEncodeHeader Context{..} buf siz ths =
    encodeTokenHeader buf siz strategy True encodeDynamicTable ths

hpackEncodeHeaderLoop :: Context -> Buffer -> BufSize
                      -> TokenHeaderList
                      -> IO (TokenHeaderList, Int)
hpackEncodeHeaderLoop Context{..} buf siz hs =
    encodeTokenHeader buf siz strategy False encodeDynamicTable hs

----------------------------------------------------------------

hpackDecodeHeader :: HeaderBlockFragment -> Context -> IO (TokenHeaderList, ValueTable)
hpackDecodeHeader hdrblk Context{..} = do
    tbl@(_,vt) <- decodeTokenHeader decodeDynamicTable hdrblk `E.catch` handl
    unless (checkRequestHeader vt) $
        E.throwIO $ ConnectionError ProtocolError "the header key is illegal"
    return tbl
  where
    handl IllegalHeaderName =
        E.throwIO $ ConnectionError ProtocolError "the header key is illegal"
    handl _ =
        E.throwIO $ ConnectionError CompressionError "cannot decompress the header"

{-# INLINE checkRequestHeader #-}
checkRequestHeader :: ValueTable -> Bool
checkRequestHeader reqvt
  | just mMethod (== "CONNECT") = isNothing mPath && isNothing mScheme
  | isJust mStatus              = False
  | isNothing mMethod           = False
  | isNothing mScheme           = False
  | isNothing mPath             = False
  | mPath       == Just ""      = False
  | isJust mConnection          = False
  | just mTE (/= "trailers")    = False
  | otherwise                   = True
  where
    mStatus     = getHeaderValue tokenStatus reqvt
    mScheme     = getHeaderValue tokenScheme reqvt
    mPath       = getHeaderValue tokenPath reqvt
    mMethod     = getHeaderValue tokenMethod reqvt
    mConnection = getHeaderValue tokenConnection reqvt
    mTE         = getHeaderValue tokenTE reqvt

{-# INLINE just #-}
just :: Maybe a -> (a -> Bool) -> Bool
just Nothing  _    = False
just (Just x) p
  | p x            = True
  | otherwise      = False
