-- | -- Module: Network.IHttp.Parsers -- Copyright: (c) 2010 Ertugrul Soeylemez -- License: BSD3 -- Maintainer: Ertugrul Soeylemez -- Stability: beta -- -- HTTP parsers. {-# LANGUAGE OverloadedStrings #-} module Network.IHttp.Parsers ( -- * Protocol parsers httpCodeP, httpFirstHeaderP, httpMethodP, httpMethodP', httpVersionP, messageP, requestLineP, responseLineP ) where import qualified Data.ByteString as B import qualified Data.ByteString.Char8 as BC import qualified Data.Map as M import Control.ContStuff import Data.Attoparsec.Char8 as P import Data.ByteString (ByteString) import Network.IHttp.Tools import Network.IHttp.Types -- | Parse an HTTP status code. httpCodeP :: Parser Int httpCodeP = ( "HTTP response code") $ do code <- P.take 3 case BC.readInt code of Just (n, rest) | B.null rest -> return n _ -> empty "Invalid response code" -- | Parse first HTTP header line. httpFirstHeaderP :: Parser (ByteString, ByteString) httpFirstHeaderP = do (,) <$> (BC.map asciiToUpper <$> httpTokenP) <* string ": " <*> messageP "initial header line" -- | Parse a known HTTP method. httpMethodP :: Parser HttpMethod httpMethodP = ( "HTTP request method") $ P.choice . map (P.try . httpMethodP') $ methods where methods :: [HttpMethod] methods = [ ConnectMethod, DeleteMethod, GetMethod, HeadMethod, OptionsMethod, PatchMethod, PostMethod, PutMethod, TraceMethod ] -- | Parse the given HTTP method. httpMethodP' :: HttpMethod -> Parser HttpMethod httpMethodP' method = let parser = case method of ConnectMethod -> string "CONNECT" "CONNECT method" DeleteMethod -> string "DELETE" "DELETE method" GetMethod -> string "GET" "GET method" HeadMethod -> string "HEAD" "HEAD method" OptionsMethod -> string "OPTIONS" "OPTIONS method" PatchMethod -> string "PATCH" "PATCH method" PostMethod -> string "POST" "POST method" PutMethod -> string "PUT" "PUT method" TraceMethod -> string "TRACE" "TRACE method" XMethod str -> return str BC.unpack str ++ " method" in method <$ parser -- | Parse an HTTP token as specified by RFC 1945. httpTokenP :: Parser ByteString httpTokenP = P.takeWhile1 isTokenChar "HTTP token" where tspecials' :: [Char] tspecials' = "()<>@,;:\\\"/[]?={}" isTokenChar :: Char -> Bool isTokenChar c = c > ' ' && c < '\DEL' && notInClass tspecials' c -- | Parse an HTTP version in the format @HTTP/major.minor@. httpVersionP :: Parser HttpVersion httpVersionP = ( "version string") $ do string "HTTP/" "\"HTTP/\" version prefix" major <- decimal "major version" char '.' minor <- decimal "minor version" case (major, minor) of (1, 0) -> return Http1_0 (1, 1) -> return Http1_1 _ -> empty "unsupported version" -- | Parse the rest of input as a status message. messageP :: Parser ByteString messageP = P.takeWhile (const True) <* endOfInput "status message" -- | Parse an HTTP request line. requestLineP :: Parser Request requestLineP = Request M.empty <$> httpMethodP <* char ' ' <*> uriP <* char ' ' <*> httpVersionP <* endOfInput -- | Parse an HTTP response line. responseLineP :: Parser Response responseLineP = (\ver code msg -> Response code M.empty msg ver) <$> httpVersionP <* char ' ' <*> httpCodeP <* char ' ' <*> messageP -- | Parse a URI (which is right now just a nonempty string token -- without whitespace). uriP :: Parser ByteString uriP = P.takeWhile1 (/= ' ') "URI string"