diff options
Diffstat (limited to 'ch16/16_a_4.hs')
| -rw-r--r-- | ch16/16_a_4.hs | 95 |
1 files changed, 95 insertions, 0 deletions
diff --git a/ch16/16_a_4.hs b/ch16/16_a_4.hs new file mode 100644 index 0000000..c828c37 --- /dev/null +++ b/ch16/16_a_4.hs @@ -0,0 +1,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 |
