summaryrefslogtreecommitdiff
path: root/mini-http/Network/HTTP/Server/Mini/Parser.hs
blob: 02da6d3cbf20e02721b646e67ea347bbb96b8e33 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
module Network.HTTP.Server.Mini.Parser where

import Control.Monad.Trans.Maybe
import Data.ByteString (ByteString)
import Data.ByteString qualified as BS

import Network.HTTP.Server.Mini.Types


readRequest :: Monad m => Int -> (Int -> m ByteString) -> m (Maybe Request)
readRequest maxsize recv = runMaybeT $ do
  bs <- MaybeT $ readUntilTerminator maxsize recv []
  hoistMaybe (parseRequest bs)

parseRequest :: ByteString -> Maybe Request
parseRequest = _

readUntilTerminator :: Monad m => Int -> (Int -> m ByteString) -> [ByteString] -> m (Maybe ByteString)
readUntilTerminator 0 _ _ = return Nothing
readUntilTerminator maxsize recv toprevprefix = do
  text <- recv maxsize
  if BS.length text == 0
    then return Nothing
    else goFind toprevprefix text
  where
    goFind revprefix text =
      case findTerminator text of
        NoTerm -> readUntilTerminator (maxsize - BS.length text) recv (text : revprefix)
        IncompleteTerm haveCR
          | maxsize == BS.length text -> return Nothing
          | otherwise -> do
              text2 <- recv (maxsize - BS.length text)
              text2' <- case BS.length text2 of
                1 -> (text2 <>) <$> recv (maxsize - BS.length text - 1)  -- premature concat but rare
                _ -> return text2  -- also for length 0 (i.e. EOF)!
              -- now text2' has length at least 2 if available before EOF
              case (haveCR, BS.length text2') of
                (_, 0) -> return Nothing
                (False, _) | text2' `BS.index` 0 == 10 -> return (Just (BS.concat (reverse revprefix)))
                (False, n) | n >= 2, text2' `BS.index` 0 == 13, text2' `BS.index` 0 == 10 ->
                  return (Just (BS.concat (reverse revprefix)))
                (True, _) | text2' `BS.index` 0 == 10 -> return (Just (BS.init (BS.concat (reverse revprefix))))
                (_, 1) -> return Nothing  -- second recv returned EOF and no terminator found before that
                _ -> goFind (text : revprefix) text2'
        FoundTerm i -> return (Just (BS.concat (reverse (BS.take i text : revprefix))))

data FoundTerminator
  = NoTerm
  | IncompleteTerm {-# UNPACK #-} !Bool
      -- ^ Bool indicates "have CR".
      -- If False, then LF at end of segment, requires LF or CRLF at start of next.
      -- If True, then LF CR at end of segment, requires LF at start of next.
  | FoundTerm Int  -- ^ Index right after the first [CR]LF sequence in the terminator
  deriving (Show)

findTerminator :: ByteString -> FoundTerminator
findTerminator bs =
  -- We're looking for [CR] LF [CR] LF. Look for the first LF, and see if it's
  -- followed by CRLF or LF.
  go (BS.elemIndices 10 bs)
  where
    len = BS.length bs

    go [] = NoTerm
    go (i:is)
      | i == len - 1 = IncompleteTerm False
      | i == len - 2 =
          case bs `BS.index` (i+1) of
            10 -> FoundTerm (i+1)
            13 -> IncompleteTerm True
            _ -> NoTerm
      -- now certainly two bytes after this LF
      | otherwise =
          case bs `BS.index` (i+1) of
            10 -> FoundTerm (i+1)
            -- [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