-- | -- Module: Network.IHttp.Header -- Copyright: (c) 2010 Ertugrul Soeylemez -- License: BSD3 -- Maintainer: Ertugrul Soeylemez -- Stability: beta -- -- Iteratees for header parsing. {-# LANGUAGE OverloadedStrings, ScopedTypeVariables #-} module Network.IHttp.Header ( -- * Iteratees httpHeader, httpHeaders, -- * Enumerators enumHeaders ) where import qualified Data.ByteString as B import qualified Data.Map as M import Data.ByteString (ByteString) import Data.ByteString.Char8 () import Data.Enumerator as E import Data.Enumerator.List as EL import Network.IHttp.Parsers import Network.IHttp.Tools import Network.IHttp.Types -- TODO: Turn a 'HeaderMap' to a record of common headers. -- commonHeaders :: HeaderMap -> CommonHeaders -- commonHeaders m = -- let (ctype, charset) = do -- typeStr <- M.lookup "CONTENT-TYPE" m -- CommonHeaders { contentCharsetHeader = undefined, -- contentLengthHeader = undefined, -- contentTypeHeader = undefined } -- | Enumerate a 'HeaderMap' as a protocol string stream. You can use -- 'Data.Enumerator.Binary.iterHandle' to send it. Note that this -- enumerator never generates continuation lines. It also does not -- enumerate the final empty line. enumHeaders :: forall b m. Monad m => HeaderMap -> Enumerator ByteString m b enumHeaders headers = enum where format :: HeaderKey -> ByteString -> [ByteString] -> [ByteString] format (HeaderKey headerStr) content rest = headerStr : ": " : content : "\r\n" : rest enum :: Enumerator ByteString m b enum (Continue k) = k (Chunks . M.foldrWithKey format [] $ headers) enum step = returnI step -- | Get the next header from the 'netLinesEmpty'-splitted stream. The -- header's content is length-limited by the given argument. If it's -- longer, it's safely truncated in constant space. This iteratee -- throws an iteratee error, if the next lines are not a valid HTTP -- header or the stream ends prematurely. If the next line is an empty -- line, this iteratee returns 'Nothing'. httpHeader :: forall m. Monad m => Int -> Iteratee ByteString m (Maybe (HeaderKey, ByteString)) httpHeader n = do line <- EL.head >>= maybe (throwError $ InvalidHeaderError "Premature end of stream") return if B.null line then return Nothing else do (hdrName, hdrTxt') <- parseIter httpFirstHeaderP InvalidHeaderError line hdrTxt <- continue (loop (B.take n hdrTxt')) return $ Just (hdrName, hdrTxt) where loop :: ByteString -> Stream ByteString -> Iteratee ByteString m ByteString loop hdrTxt EOF = yield hdrTxt EOF loop hdrTxt (Chunks []) = continue (loop hdrTxt) loop hdrTxt' ch@(Chunks (line:lines)) = let (pfx, sfx) = B.span (\c -> c == 32 || c == 9) line hdrTxt = B.take n $ B.append (B.snoc hdrTxt' 32) sfx in if B.null pfx then yield hdrTxt' ch else hdrTxt `seq` loop hdrTxt (Chunks lines) -- | Get the headers of an HTTP request from a 'netLinesEmpty'-splitted -- byte stream. The first 'Int' specifies the maximum length of -- individual headers. The second 'Int' specifies the maximum number of -- headers. This iteratee throws an iteratee error on invalid input, of -- if the stream ends prematurely. -- -- Excess data is truncated safely in constant space. httpHeaders :: forall m. Monad m => Int -> Int -> Iteratee ByteString m HeaderMap httpHeaders maxLine maxHeaders = loop M.empty where loop :: HeaderMap -> Iteratee ByteString m HeaderMap loop m' = do mHeader <- httpHeader maxLine case mHeader of Just (hdrName, hdrSfx) -> do let accum hdrPfx = B.take maxLine $ B.concat [hdrPfx, ", ", hdrSfx] hdrTxt = maybe hdrSfx accum (M.lookup hdrName m') if M.size m' < maxHeaders then hdrTxt `seq` loop (M.insert hdrName hdrTxt m') else loop m' Nothing -> return m'