@@ -0,0 +1,79 @@
1+{-# LANGUAGE BangPatterns, FlexibleContexts #-}
3+module Main (main) where
5+import qualified Data.ByteString as B
6+import Control.Applicative
9+import Control.Exception hiding (try)
10+import System.Environment (getArgs)
11+import Text.Parsec.ByteString
12+import Text.Parsec.Char
13+import Text.Parsec.Combinator
14+import Text.Parsec.Prim hiding (many, token, (<|>))
16+token :: Stream s m Char => ParsecT s u m Char
17+token = satisfy $ \c -> not (elem c (['\0'..'\31'] ++ "()<>@,;:\\\"/[]?={} \t" ++ ['\128'..'\255']))
19+isHorizontalSpace c = c == ' ' || c == '\t'
21+skipHSpaces :: Stream s m Char => ParsecT s u m ()
22+{-# SPECIALISE skipHSpaces :: Parser () #-}
23+skipHSpaces = skipMany1 (satisfy isHorizontalSpace)
25+data Request = Request {
26+ requestMethod :: String
27+ , requestUri :: String
28+ , requestProtocol :: String
29+ } deriving (Eq, Ord, Show)
31+requestLine :: Stream s m Char => ParsecT s u m Request
32+{-# SPECIALISE requestLine :: Parser Request #-}
34+ method <- many1 token <* skipHSpaces
35+ uri <- many1 (satisfy (not . isHorizontalSpace)) <* skipHSpaces <* string "HTTP/"
36+ proto <- many httpVersion <* endOfLine
37+ return $! Request method uri proto
39+ httpVersion = satisfy $ \c -> c == '1' || c == '0' || c == '.'
41+endOfLine :: Stream s m Char => ParsecT s u m ()
42+{-# SPECIALISE endOfLine :: Parser () #-}
43+endOfLine = (string "\r\n" *> pure ()) <|> (char '\n' *> pure ())
45+data Header = Header {
47+ , headerValue :: [String]
48+ } deriving (Eq, Ord, Show)
50+messageHeader :: Stream s m Char => ParsecT s u m Header
51+{-# SPECIALISE messageHeader :: Parser Header #-}
53+ header <- many1 token <* char ':' <* skipHSpaces
54+ body <- manyTill anyChar (try endOfLine)
55+ conts <- many $ skipHSpaces *> manyTill anyChar (try endOfLine)
56+ return $! Header header (body:conts)
58+request :: Stream s m Char => ParsecT s u m (Request, [Header])
59+{-# SPECIALISE request :: Parser (Request, [Header]) #-}
60+request = (,) <$> requestLine <*> many messageHeader <* endOfLine
62+main = mapM_ chunky =<< getArgs
65+ r <- parseFromFile (many request) arg
67+ Left err -> putStrLn $ arg ++ ": " ++ show err
68+ Right rs -> print (length rs)
70+chunky arg = bracket (openFile arg ReadMode) hClose $ \h ->
71+ loop 0 =<< B.hGetContents h
74+ | B.null bs = print n
75+ | otherwise = case parse myReq arg bs of
76+ Left err -> putStrLn $ arg ++ ": " ++ show err
77+ Right (r,bs') -> loop (n+1) bs'
78+ myReq :: Parser ((Request, [Header]), B.ByteString)
79+ myReq = liftA2 (,) request getInput