/
Parser.hs
221 lines (185 loc) · 6.97 KB
/
Parser.hs
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
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE PackageImports #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ViewPatterns #-}
module Snap.Internal.Http.Parser
( IRequest(..)
, HttpParseException
, parseRequest
, readChunkedTransferEncoding
, iterParser
, parseCookie
, parseUrlEncoded
, strictize
) where
------------------------------------------------------------------------------
import Control.Exception
import Control.Monad (liftM)
import Control.Monad.Trans
import Data.Attoparsec hiding (many, Result(..))
import Data.Attoparsec.Enumerator
import Data.ByteString.Char8 (ByteString)
import qualified Data.ByteString.Char8 as S
import qualified Data.ByteString.Unsafe as S
import Data.ByteString.Internal (w2c)
import qualified Data.ByteString.Lazy.Char8 as L
import qualified Data.ByteString.Nums.Careless.Hex as Cvt
import Data.Char
import Data.Int
import Data.Typeable
import Prelude hiding (head, take, takeWhile)
----------------------------------------------------------------------------
import Snap.Internal.Http.Types
import Snap.Internal.Iteratee.Debug
import Snap.Internal.Parsing hiding (pHeaders)
import Snap.Iteratee hiding (map, take)
------------------------------------------------------------------------------
-- | an internal version of the headers part of an HTTP request
data IRequest = IRequest
{ iMethod :: Method
, iRequestUri :: ByteString
, iHttpVersion :: (Int,Int)
, iRequestHeaders :: [(ByteString, ByteString)]
}
------------------------------------------------------------------------------
instance Show IRequest where
show (IRequest m u v r) =
concat [ show m
, " "
, show u
, " "
, show v
, " "
, show r ]
------------------------------------------------------------------------------
data HttpParseException = HttpParseException String deriving (Typeable, Show)
instance Exception HttpParseException
------------------------------------------------------------------------------
parseRequest :: (Monad m) => Iteratee ByteString m (Maybe IRequest)
parseRequest = do
eof <- isEOF
if eof
then return Nothing
else do
line <- pLine
if S.null line
then parseRequest
else do
let (!mStr,!s) = bSp line
let (!uri,!vStr) = bSp s
!method <- methodFromString mStr
let ver@(!_,!_) = pVer vStr
hdrs <- pHeaders
return $ Just $ IRequest method uri ver hdrs
where
pVer s = if S.isPrefixOf "HTTP/" s
then let (a,b) = bDot $ S.drop 5 s
in (read $ S.unpack a, read $ S.unpack b)
else (1,0)
isSp = (== ' ')
bSp = splitWith isSp
isDot = (== '.')
bDot = splitWith isDot
------------------------------------------------------------------------------
pLine :: (Monad m) => Iteratee ByteString m ByteString
pLine = continue $ k S.empty
where
k _ EOF = throwError $
HttpParseException "parse error: expected line ending in crlf"
k !pre (Chunks xs) =
if S.null b
then continue $ k a
else yield a (Chunks [S.drop 2 b])
where
(!a,!b) = S.breakSubstring "\r\n" s
!s = S.append pre s'
!s' = S.concat xs
------------------------------------------------------------------------------
splitWith :: (Char -> Bool) -> ByteString -> (ByteString,ByteString)
splitWith !f !s = let (!a,!b) = S.break f s
!b' = S.dropWhile f b
in (a, b')
------------------------------------------------------------------------------
pHeaders :: Monad m => Iteratee ByteString m [(ByteString,ByteString)]
pHeaders = do
f <- go id
return $! f []
where
go !dlistSoFar = {-# SCC "pHeaders/go" #-} do
line <- pLine
if S.null line
then return dlistSoFar
else do
let (!k,!v) = pOne line
vf <- pCont id
let vs = vf []
let !v' = S.concat (v:vs)
go (dlistSoFar . ((k,v'):))
where
pOne s = let (k,v) = splitWith (== ':') s
in (trim k, trim v)
isCont c = c == ' ' || c == '\t'
pCont !dlist = do
mbS <- peek
maybe (return dlist)
(\s -> if S.null s
then head >> pCont dlist
else if isCont $ w2c $ S.unsafeHead s
then procCont dlist
else return dlist)
mbS
procCont !dlist = do
line <- pLine
let !t = trim line
pCont (dlist . (" ":) . (t:))
------------------------------------------------------------------------------
methodFromString :: (Monad m) => ByteString -> Iteratee ByteString m Method
methodFromString "GET" = return GET
methodFromString "POST" = return POST
methodFromString "HEAD" = return HEAD
methodFromString "PUT" = return PUT
methodFromString "DELETE" = return DELETE
methodFromString "TRACE" = return TRACE
methodFromString "OPTIONS" = return OPTIONS
methodFromString "CONNECT" = return CONNECT
methodFromString s =
throwError $ HttpParseException $ "Bad method '" ++ S.unpack s ++ "'"
------------------------------------------------------------------------------
readChunkedTransferEncoding :: (MonadIO m) =>
Enumeratee ByteString ByteString m a
readChunkedTransferEncoding =
chunkParserToEnumeratee $
iterateeDebugWrapper "pGetTransferChunk" $
iterParser pGetTransferChunk
------------------------------------------------------------------------------
chunkParserToEnumeratee :: (MonadIO m) =>
Iteratee ByteString m (Maybe ByteString)
-> Enumeratee ByteString ByteString m a
chunkParserToEnumeratee getChunk client = do
mbB <- getChunk
maybe finishIt sendBS mbB
where
sendBS s = do
step <- lift $ runIteratee $ enumBS s client
chunkParserToEnumeratee getChunk step
finishIt = lift $ runIteratee $ enumEOF client
------------------------------------------------------------------------------
-- parse functions
------------------------------------------------------------------------------
------------------------------------------------------------------------------
pGetTransferChunk :: Parser (Maybe ByteString)
pGetTransferChunk = do
!hex <- liftM fromHex $ (takeWhile (isHexDigit . w2c))
takeTill ((== '\r') . w2c)
crlf
if hex <= 0
then return Nothing
else do
x <- take hex
crlf
return $ Just x
where
fromHex :: ByteString -> Int
fromHex s = Cvt.hex (L.fromChunks [s])