{-# LANGUAGE BangPatterns, RecordWildCards, OverloadedStrings #-}
module Network.HPACK.HeaderBlock.Decode (
decodeHeader
, decodeTokenHeader
, ValueTable
, HeaderTable
, toHeaderTable
, getHeaderValue
) where
import Control.Exception (throwIO)
import Data.Array (Array)
import Data.Array.Base (unsafeAt, unsafeRead, unsafeWrite)
import qualified Data.Array.IO as IOA
import qualified Data.Array.Unsafe as Unsafe
import qualified Data.ByteString as BS
import qualified Data.ByteString.Char8 as B8
import Data.Char (isUpper)
import Data.CaseInsensitive (CI(..))
import Network.ByteOrder
import Imports hiding (empty)
import Network.HPACK.Builder
import qualified Network.HPACK.HeaderBlock.Integer as I
import Network.HPACK.Huffman
import Network.HPACK.Table
import Network.HPACK.Token
import Network.HPACK.Types
type ValueTable = Array Int (Maybe HeaderValue)
{-# INLINE getHeaderValue #-}
getHeaderValue :: Token -> ValueTable -> Maybe HeaderValue
getHeaderValue t tbl = tbl `unsafeAt` tokenIx t
decodeHeader :: DynamicTable
-> ByteString
-> IO HeaderList
decodeHeader dyntbl inp = decodeHPACK dyntbl inp decodeSimple
decodeTokenHeader :: DynamicTable
-> ByteString
-> IO HeaderTable
decodeTokenHeader dyntbl inp = decodeHPACK dyntbl inp decodeSophisticated
decodeHPACK :: DynamicTable
-> ByteString
-> (DynamicTable -> ReadBuffer -> IO a)
-> IO a
decodeHPACK dyntbl inp dec = withReadBuffer inp chkChange
where
chkChange rbuf = do
leftover <- remainingSize rbuf
if leftover >= 1 then do
w <- read8 rbuf
if isTableSizeUpdate w then do
tableSizeUpdate dyntbl w rbuf
chkChange rbuf
else do
ff rbuf (-1)
dec dyntbl rbuf
else
throwIO HeaderBlockTruncated
decodeSimple :: DynamicTable -> ReadBuffer -> IO HeaderList
decodeSimple dyntbl rbuf = go empty
where
go builder = do
leftover <- remainingSize rbuf
if leftover >= 1 then do
w <- read8 rbuf
!tv <- toTokenHeader dyntbl w rbuf
let builder' = builder << tv
go builder'
else do
let !tvs = run builder
!kvs = map (\(t,v) -> let !k = tokenFoldedKey t in (k,v)) tvs
return kvs
decodeSophisticated :: DynamicTable -> ReadBuffer
-> IO HeaderTable
decodeSophisticated dyntbl rbuf = do
arr <- IOA.newArray (minTokenIx,maxTokenIx) Nothing
!tvs <- pseudoNormal arr
tbl <- Unsafe.unsafeFreeze arr
return (tvs, tbl)
where
pseudoNormal :: IOA.IOArray Int (Maybe HeaderValue) -> IO TokenHeaderList
pseudoNormal arr = pseudo
where
pseudo = do
leftover <- remainingSize rbuf
if leftover >= 1 then do
w <- read8 rbuf
tv@(!Token{..},!v) <- toTokenHeader dyntbl w rbuf
if isPseudo then do
mx <- unsafeRead arr ix
when (isJust mx) $ throwIO IllegalHeaderName
when (isMaxTokenIx ix) $ throwIO IllegalHeaderName
unsafeWrite arr ix (Just v)
pseudo
else do
when (isMaxTokenIx ix && B8.any isUpper (original tokenKey)) $
throwIO IllegalHeaderName
unsafeWrite arr ix (Just v)
if isCookieTokenIx ix then
normal empty (empty << v)
else
normal (empty << tv) empty
else
return []
normal !builder !cookie = do
leftover <- remainingSize rbuf
if leftover >= 1 then do
w <- read8 rbuf
tv@(Token{..},!v) <- toTokenHeader dyntbl w rbuf
when isPseudo $ throwIO IllegalHeaderName
when (isMaxTokenIx ix && B8.any isUpper (original tokenKey)) $
throwIO IllegalHeaderName
unsafeWrite arr ix (Just v)
if isCookieTokenIx ix then
normal builder (cookie << v)
else
normal (builder << tv) cookie
else do
let !tvs0 = run builder
!cook = run cookie
if null cook then
return tvs0
else do
let !v = BS.intercalate "; " cook
!tvs = (tokenCookie, v) : tvs0
unsafeWrite arr cookieTokenIx (Just v)
return tvs
toTokenHeader :: DynamicTable -> Word8 -> ReadBuffer -> IO TokenHeader
toTokenHeader dyntbl w rbuf
| w `testBit` 7 = indexed dyntbl w rbuf
| w `testBit` 6 = incrementalIndexing dyntbl w rbuf
| w `testBit` 5 = throwIO IllegalTableSizeUpdate
| w `testBit` 4 = neverIndexing dyntbl w rbuf
| otherwise = withoutIndexing dyntbl w rbuf
tableSizeUpdate :: DynamicTable -> Word8 -> ReadBuffer -> IO ()
tableSizeUpdate dyntbl w rbuf = do
let !w' = mask5 w
!siz <- I.decode 5 w' rbuf
suitable <- isSuitableSize siz dyntbl
unless suitable $ throwIO TooLargeTableSize
renewDynamicTable siz dyntbl
indexed :: DynamicTable -> Word8 -> ReadBuffer -> IO TokenHeader
indexed dyntbl w rbuf = do
let !w' = clearBit w 7
!idx <- I.decode 7 w' rbuf
entryTokenHeader <$> toIndexedEntry dyntbl idx
incrementalIndexing :: DynamicTable -> Word8 -> ReadBuffer -> IO TokenHeader
incrementalIndexing dyntbl w rbuf = do
tv@(t,v) <- if isIndexedName1 w then
indexedName dyntbl w rbuf 6 mask6
else
newName dyntbl rbuf
let !e = toEntryToken t v
insertEntry e dyntbl
return tv
withoutIndexing :: DynamicTable -> Word8 -> ReadBuffer -> IO TokenHeader
withoutIndexing dyntbl w rbuf
| isIndexedName2 w = indexedName dyntbl w rbuf 4 mask4
| otherwise = newName dyntbl rbuf
neverIndexing :: DynamicTable -> Word8 -> ReadBuffer -> IO TokenHeader
neverIndexing dyntbl w rbuf
| isIndexedName2 w = indexedName dyntbl w rbuf 4 mask4
| otherwise = newName dyntbl rbuf
indexedName :: DynamicTable -> Word8 -> ReadBuffer
-> Int -> (Word8 -> Word8)
-> IO TokenHeader
indexedName dyntbl w rbuf n mask = do
let !p = mask w
!idx <- I.decode n p rbuf
!t <- entryToken <$> toIndexedEntry dyntbl idx
!val <- headerStuff dyntbl rbuf
let !tv = (t,val)
return tv
newName :: DynamicTable -> ReadBuffer -> IO TokenHeader
newName dyntbl rbuf = do
!t <- toToken <$> headerStuff dyntbl rbuf
!val <- headerStuff dyntbl rbuf
let !tv = (t,val)
return tv
headerStuff :: DynamicTable -> ReadBuffer -> IO HeaderStuff
headerStuff dyntbl rbuf = do
leftover <- remainingSize rbuf
if leftover >= 1 then do
w <- read8 rbuf
let !p = dropHuffman w
!huff = isHuffman w
!len <- I.decode 7 p rbuf
decodeString huff (huffmanDecoder dyntbl) rbuf len
else
throwIO EmptyEncodedString
mask6 :: Word8 -> Word8
mask6 w = w .&. 63
mask5 :: Word8 -> Word8
mask5 w = w .&. 31
mask4 :: Word8 -> Word8
mask4 w = w .&. 15
isIndexedName1 :: Word8 -> Bool
isIndexedName1 w = mask6 w /= 0
isIndexedName2 :: Word8 -> Bool
isIndexedName2 w = mask4 w /= 0
isTableSizeUpdate :: Word8 -> Bool
isTableSizeUpdate w = w .&. 0xe0 == 0x20
isHuffman :: Word8 -> Bool
isHuffman w = w `testBit` 7
dropHuffman :: Word8 -> Word8
dropHuffman w = w `clearBit` 7
decodeString :: Bool -> HuffmanDecoding -> ReadBuffer -> Int -> IO HeaderStuff
decodeString huff hufdec rbuf len = do
leftover <- remainingSize rbuf
if leftover >= len then
if huff then
hufdec rbuf len
else
extractByteString rbuf len
else
throwIO HeaderBlockTruncated
type HeaderTable = (TokenHeaderList, ValueTable)
toHeaderTable :: [(CI HeaderName,HeaderValue)] -> IO HeaderTable
toHeaderTable kvs = do
arr <- IOA.newArray (minTokenIx,maxTokenIx) Nothing
!tvs <- conv arr
tbl <- Unsafe.unsafeFreeze arr
return (tvs, tbl)
where
conv :: IOA.IOArray Int (Maybe HeaderValue) -> IO TokenHeaderList
conv arr = go kvs empty
where
go :: [(CI HeaderName,HeaderValue)] -> Builder TokenHeader -> IO TokenHeaderList
go [] builder = return $ run builder
go ((k,v):xs) builder = do
let !t = toToken (foldedCase k)
unsafeWrite arr (tokenIx t) (Just v)
let !tv = (t,v)
!builder' = builder << tv
go xs builder'