diff options
Diffstat (limited to 'mini-http/Network/HTTP/Server/Mini/Parser.hs')
| -rw-r--r-- | mini-http/Network/HTTP/Server/Mini/Parser.hs | 86 |
1 files changed, 85 insertions, 1 deletions
diff --git a/mini-http/Network/HTTP/Server/Mini/Parser.hs b/mini-http/Network/HTTP/Server/Mini/Parser.hs index 02da6d3..97e25c1 100644 --- a/mini-http/Network/HTTP/Server/Mini/Parser.hs +++ b/mini-http/Network/HTTP/Server/Mini/Parser.hs @@ -1,10 +1,20 @@ +{-# LANGUAGE TemplateHaskell #-} +{-# LANGUAGE TypeApplications #-} module Network.HTTP.Server.Mini.Parser where +import Control.Monad (guard) import Control.Monad.Trans.Maybe import Data.ByteString (ByteString) import Data.ByteString qualified as BS +import Data.ByteString.Short (ShortByteString) +import Data.ByteString.Short qualified as SBS +import Data.Char +import Data.Word +import FlatParse.Basic qualified as P import Network.HTTP.Server.Mini.Types +import Network.HTTP.Server.Mini.URI +import Network.HTTP.Server.Mini.Util readRequest :: Monad m => Int -> (Int -> m ByteString) -> m (Maybe Request) @@ -13,7 +23,71 @@ readRequest maxsize recv = runMaybeT $ do hoistMaybe (parseRequest bs) parseRequest :: ByteString -> Maybe Request -parseRequest = _ +parseRequest bs = + case P.runParser pRequest bs of + P.OK req rest | BS.null rest -> Just req + _ -> Nothing + +pRequest :: P.Parser e Request +pRequest = do + (method, uri) <- pFirstLine + headers <- P.many pHeader + let (path, query) = parseURI uri + return Request { reqMethod = method + , reqURI = uri + , reqHeaders = headers + , reqPath = path + , reqQuery = query } + +pFirstLine :: P.Parser e (ShortByteString, ByteString) +pFirstLine = do + method <- pTakeUntil (ord8 ' ') + guard (BS.length method <= 32) -- will put it in an SBS later + P.word8 32 + uri <- pTakeUntil (ord8 ' ') + P.word8 32 + $(P.bytes (map (fromIntegral @Int @Word . ord) "HTTP/1.")) + version <- P.anyWord8 + guard (version == ord8 '0' || version == ord8 '1') + pNewline + return (SBS.pack (map toAsciiUpperCase (BS.unpack method)), uri) + +pHeader :: P.Parser e (Header, ByteString) +pHeader = do + key <- pTakeUntil (ord8 ':') + guard (BS.length key <= 128) -- will put it in an SBS later + P.word8 (ord8 ':') + P.skipMany (P.satisfy isLinearSpace) + value <- pValue [] + return (SBS.pack (map toAsciiLowerCase (BS.unpack key)), value) + where + pValue revlines = do + line <- trimCR <$> pTakeUntil 10 + P.word8 10 + P.withOption (P.lookahead (P.skipSatisfyAscii isLinearSpace)) + (\_ -> pValue (line : revlines)) + (return (BS.concat (reverse (line : revlines)))) + + trimCR bs + | len >= 1, bs `BS.index` (len - 1) == 13 = BS.take (len - 1) bs + | otherwise = bs + where len = BS.length bs + +isLinearSpace :: Char -> Bool +isLinearSpace c = c == ' ' || c == '\t' + +pTakeUntil :: Word8 -> P.Parser e ByteString +pTakeUntil term = do + midx <- P.lookahead (BS.elemIndex term <$> P.takeRest) + case midx of + Just idx -> P.take idx + Nothing -> P.failed + +pNewline :: P.Parser e () +pNewline = P.withAnyWord8 $ \case + 10 -> return () + 13 -> P.word8 10 + _ -> P.failed readUntilTerminator :: Monad m => Int -> (Int -> m ByteString) -> [ByteString] -> m (Maybe ByteString) readUntilTerminator 0 _ _ = return Nothing @@ -76,3 +150,13 @@ findTerminator bs = -- [i+2] is read twice if this check fails, but LF CR (!=LF) will be very rare so it's fine 13 | bs `BS.index` (i+2) == 10 -> FoundTerm (i+1) _ -> go is + +toAsciiUpperCase :: Word8 -> Word8 +toAsciiUpperCase c + | ord8 'a' <= c, c <= ord8 'z' = c - 32 + | otherwise = c + +toAsciiLowerCase :: Word8 -> Word8 +toAsciiLowerCase c + | ord8 'A' <= c, c <= ord8 'Z' = c + 32 + | otherwise = c |
