aboutsummaryrefslogtreecommitdiff
path: root/ch16/16_a_4.hs
blob: c828c37584583ec476031232dc4da6260adecdc5 (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
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
-- Another popular attack is to open a connection and either leave it idle or
-- send data extremely slowly.
-- Write a wrapper in the IO monad that will invoke the parser. Use the
-- System.Timeout module to close the connection if the parser does not complete
-- within 30 seconds.

-- The chapter doesn't use network connections in the parsing examples. Closing
-- the connection probably means stopping the parse so it doesn't block for a
-- long time.


import Control.Concurrent (threadDelay)
import System.Timeout (timeout)
import Data.List (intercalate)


{-- From examples/examples/ch16/HttpRequestParser.hs --}
import Control.Applicative
import Text.ParserCombinators.Parsec hiding (many, optional, (<|>))
import Control.Monad (liftM4)

p_headers :: CharParser st [(String, String)]
p_headers = header `manyTill` crlf
  where header = liftA2 (,) fieldName (char ':' *> spaces *> contents)
        contents = liftA2 (++) (many1 notEOL <* crlf)
                               (continuation <|> pure [])
        continuation = liftA2 (:) (' ' <$ many1 (oneOf " \t")) contents
        fieldName = (:) <$> letter <*> many fieldChar
        fieldChar = letter <|> digit <|> oneOf "-_"

crlf :: CharParser st ()
crlf = (() <$ string "\r\n") <|> (() <$ newline)

notEOL :: CharParser st Char
notEOL = noneOf "\r\n"

data Method = Get | Post
          deriving (Eq, Ord, Show)

data HttpRequest = HttpRequest {
      reqMethod :: Method
    , reqURL :: String
    , reqHeaders :: [(String, String)]
    , reqBody :: Maybe String
    } deriving (Eq, Show)

p_request :: CharParser () HttpRequest
p_request = q "GET" Get (pure Nothing)
        <|> q "POST" Post (Just <$> many anyChar)
  where q name ctor body = liftM4 HttpRequest req url p_headers body
            where req = ctor <$ string name <* char ' '
        url = optional (char '/') *>
              manyTill notEOL (try $ string " HTTP/1." <* oneOf "01")
              <* crlf
{-- End of code from examples --}


parseWithTimeout parser request = timeout timeoutMicrosec parse
  where
    timeoutMicrosec = 30 * 10^6 -- 30 seconds
    parse = do
      req <- request
      let res = runParser p_request () "" req
      return res




testRequestWithDelay :: Int -> IO String
testRequestWithDelay delayMicrosec = do
  let start = intercalate "\r\n" [
        "POST /index.html HTTP/1.1",
        "Host: book.realworldhaskell.org",
        "User-Agent: Mozilla/5.0",
        "Accept: text/html",
        "",
        "Hello "
        ]
  threadDelay delayMicrosec
  return (start ++ "World!")

testRequestFast = testRequestWithDelay (25 * 10^6)

testRequestSlow = testRequestWithDelay (32 * 10^6)


-- ghci> :l 16_a_4.hs
-- [1 of 2] Compiling Main             ( 16_a_4.hs, interpreted )
-- Ok, one module loaded.

-- ghci> parseWithTimeout p_request testRequestFast
-- Just (Right (HttpRequest {reqMethod = Post, reqURL = "index.html", reqHeaders = [("Host","book.realworldhaskell.org"),("User-Agent","Mozilla/5.0"),("Accept","text/html")], reqBody = Just "Hello World!"}))

-- ghci> parseWithTimeout p_request testRequestSlow
-- Nothing