module Mongrel2.Parser
( ClientID
, UUID
, messageParser
) where
import Data.Attoparsec
import Data.Attoparsec.Char8 (decimal)
import Data.ByteString (ByteString, uncons)
import Data.CaseInsensitive (CI)
import Data.Map (Map)
import Data.Maybe (catMaybes)
import Data.Text (Text)
import Data.Word (Word8)
import Mongrel2.Tnetstring (parseTnetstring1, TValue(..))
import Mongrel2.Types (Request(..), UUID, ClientID)
import Network.HTTP.Types (HttpVersion, Method, Query, RequestHeaders, decodePath, http09, http10, http11, parseMethod)
import Prelude hiding (take)
import qualified Data.CaseInsensitive as CI
import qualified Data.Map as M
-- Predicates for parsing.
isSpace :: Word8 -> Bool
isSpace 0x20 = True
isSpace _ = False
-- Skip spaces.
skipSpace :: Parser ()
skipSpace = skipWhile isSpace
-- Parse a UUID.
uuidParser :: Parser ByteString
uuidParser = takeWhile1 $ \w -> ((w >= 65) && (w <= 90 )) || -- A-Z
((w >= 97) && (w <= 122)) || -- a-z
((w >= 48) && (w <= 57 )) || -- 0-9
(w == 45) -- Dash
messageParser :: Parser Request
messageParser = do
uuid <- uuidParser
skipSpace
clientId <- decimal
skipSpace
rawPath <- takeTill isSpace
skipSpace
rawReqHdr <- parseTnetstring1
rawBody <- parseTnetstring1
-- Touchups to conform better with Http-types.
let reqHdr = unHeaders rawReqHdr
let method = parseMethod $ extractMethod reqHdr
let version = extractVersion reqHdr
let (path,query) = extractQuery reqHdr
return $ Request path query rawPath method version (extractHeaders reqHdr) (extractBody rawBody) uuid clientId
unHeaders :: TValue -> Map ByteString TValue
unHeaders (TDictionary m) = M.fromList m
unHeaders _ = error "Invalid headers received from Mongrel2"
extractBody :: TValue -> ByteString
extractBody (TString body) = body
extractBody _ = error "Invalid body received from Mongrel2"
extractQuery :: Map ByteString TValue -> ([Text], Query)
extractQuery hdrs =
case M.lookup "URI" hdrs of
Just (TString uriText) -> decodePath uriText
_ -> error "Missing/invalid 'URI' in headers received from Mongrel2"
extractMethod :: Map ByteString TValue -> Method
extractMethod hdrs =
case M.lookup "METHOD" hdrs of
Just (TString methodText) -> methodText
_ -> error "Missing/invalid 'METHOD' in headers received from Mongrel2"
extractVersion :: Map ByteString TValue -> HttpVersion
extractVersion hdrs =
-- TODO: This really should be done in a more general way.
case M.lookup "VERSION" hdrs of
Just (TString "HTTP/0.9") -> http09
Just (TString "HTTP/1.0") -> http10
Just (TString "HTTP/1.1") -> http11
Just _ -> error "Unrecognized HTTP version"
Nothing -> error "Missing HTTP version in headers received from Mongrel2"
extractHeaders :: Map ByteString TValue -> RequestHeaders
extractHeaders hdrs = catMaybes $ map extractHdr $ M.toList hdrs
extractHdr :: (ByteString, TValue) -> Maybe (CI ByteString, ByteString)
extractHdr (t, TString v) = handleHdr t v
extractHdr _ = Nothing
handleHdr :: ByteString -> ByteString -> Maybe (CI ByteString, ByteString)
handleHdr k v =
case uncons k of
Just (c, _) | (c >= 65 && c <= 90) -> Nothing
_ -> Just (CI.mk k, v)