summaryrefslogtreecommitdiff
path: root/mini-http/Network/HTTP/Server/Mini/Parser.hs
diff options
context:
space:
mode:
Diffstat (limited to 'mini-http/Network/HTTP/Server/Mini/Parser.hs')
-rw-r--r--mini-http/Network/HTTP/Server/Mini/Parser.hs86
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