{-# LANGUAGE DuplicateRecordFields #-} module Http.Response ( Response (..) , StatusLine (..) , decode ) where import Control.Monad (when) import Data.Bytes (Bytes) import Data.Bytes.Parser (Parser) import Data.Bytes.Types (Bytes (Bytes)) import Data.Primitive (ByteArray (ByteArray)) import Data.Text (Text) import Data.Word (Word16) import Http.Headers (Headers) import Data.Bytes.Parser qualified as Parser import Data.Bytes.Parser.Latin qualified as Latin import Data.Text.Array qualified import Data.Text.Internal qualified as Text import Http.Header qualified as Header import Http.Headers qualified as Headers -- | The response status line and the response headers. data Response = Response { statusLine :: !StatusLine , headers :: !Headers } deriving (Eq, Show) data StatusLine = StatusLine { statusCode :: !Word16 , statusReason :: {-# UNPACK #-} !Text } deriving (Eq, Show) {- | Decode the response status line and the response headers. Fails if any extraneous input is present after the double CRLF sequence that ends the headers. -} decode :: Int -> Bytes -> Maybe Response decode !n !b = Parser.parseBytesMaybe (parserResponse n <* Parser.endOfInput ()) b parserResponse :: -- | Maximum number of headers Int -> Parser () s Response parserResponse !n = do statusLine <- parserStatusLine headers0 <- Header.parserSmallArray n let !headers = Headers.fromArray headers0 pure Response {statusLine, headers} -- Consumes the trailing CRLF parserStatusLine :: Parser () s StatusLine parserStatusLine = do Latin.char5 () 'H' 'T' 'T' 'P' '/' versionMajor <- Latin.decWord8 () when (versionMajor /= 1) (Parser.fail ()) Latin.char () '.' versionMinor <- Latin.decWord8 () when (versionMinor /= 1) (Parser.fail ()) Latin.char () ' ' statusCode <- Latin.decWord16 () when (statusCode >= 1000) (Parser.fail ()) Latin.char () ' ' -- RFC 7230: reason-phrase = *( HTAB / SP / VCHAR / obs-text ) statusReason <- Parser.takeWhile $ \c -> (c >= 0x20 && c <= 0x7e) || (c == 0x09) Latin.char2 () '\r' '\n' pure StatusLine {statusCode, statusReason = unsafeBytesToText statusReason} unsafeBytesToText :: Bytes -> Text {-# INLINE unsafeBytesToText #-} unsafeBytesToText (Bytes (ByteArray arr) off len) = Text.Text (Data.Text.Array.ByteArray arr) off len