7 module Network.HTTP.Lucu.Preprocess
9 , RequestBodyLength(..)
13 import Control.Applicative
15 import Control.Monad.State.Strict
16 import Data.Ascii (Ascii)
17 import qualified Data.Ascii as A
18 import qualified Data.ByteString.Char8 as C8
20 import qualified Data.Strict.Maybe as S
21 import Data.Text (Text)
22 import qualified Data.Text as T
23 import qualified Data.Text.Encoding as T
24 import Network.HTTP.Lucu.Headers
25 import Network.HTTP.Lucu.HttpVersion
26 import Network.HTTP.Lucu.Request
27 import Network.HTTP.Lucu.Response
30 import Prelude.Unicode
35 , arInitialStatus ∷ !StatusCode
36 , arWillChunkBody ∷ !Bool
37 , arWillDiscardBody ∷ !Bool
39 , arExpectedContinue ∷ !Bool
40 , arReqBodyLength ∷ !(S.Maybe RequestBodyLength)
43 data RequestBodyLength
48 preprocess ∷ Text → PortNumber → Request → AugmentedRequest
49 preprocess localHost localPort req@(Request {..})
50 = execState go initialAR
52 initialAR ∷ AugmentedRequest
53 initialAR = AugmentedRequest {
55 , arInitialStatus = Ok
56 , arWillChunkBody = False
57 , arWillDiscardBody = False
59 , arExpectedContinue = False
60 , arReqBodyLength = S.Nothing
62 go ∷ State AugmentedRequest ()
63 go = do examineHttpVersion
65 examineAuthority localHost localPort
69 setRequest ∷ Request → State AugmentedRequest ()
71 = modify $ \ar → ar { arRequest = req }
73 setStatus ∷ StatusCode → State AugmentedRequest ()
75 = modify $ \ar → ar { arInitialStatus = sc }
77 setWillClose ∷ Bool → State AugmentedRequest ()
79 = modify $ \ar → ar { arWillClose = b }
81 setBodyLength ∷ S.Maybe RequestBodyLength → State AugmentedRequest ()
83 = modify $ \ar → ar { arReqBodyLength = len }
85 examineHttpVersion ∷ State AugmentedRequest ()
87 = do req ← gets arRequest
88 case reqVersion req of
89 -- HTTP/1.0 requests can't Keep-Alive.
93 → modify $ \ar → ar { arWillChunkBody = True }
94 _ → do setStatus HttpVersionNotSupported
97 examineMethod ∷ State AugmentedRequest ()
99 = do req ← gets arRequest
100 case reqMethod req of
102 HEAD → modify $ \ar → ar { arWillDiscardBody = True }
106 _ → setStatus NotImplemented
108 examineAuthority ∷ Text → PortNumber → State AugmentedRequest ()
109 examineAuthority localHost localPort
110 = do req ← gets arRequest
111 when (isNothing $ uriAuthority $ reqURI req) $
112 case reqVersion req of
113 -- HTTP/1.0 requests have no Host header so complete it
114 -- with the configuration value.
116 → let host = localHost
117 port = case localPort of
119 n → A.unsafeFromString $ ':':show n
120 req' = updateAuthority host port req
123 -- HTTP/1.1 requests MUST have a Host header.
125 → case getHeader "Host" req of
129 req' = updateAuthority host port req
133 → setStatus BadRequest
134 -- Should never reach here...
135 ver → fail ("internal error: unknown version: " ⧺ show ver)
137 parseHost ∷ Ascii → (Text, Ascii)
139 = let (h, p) = C8.break (≡ ':') $ A.toByteString hp
140 -- FIXME: should decode punycode here.
141 hText = T.decodeUtf8 h
142 pAscii = A.unsafeFromByteString p
146 updateAuthority ∷ Text → Ascii → Request → Request
147 updateAuthority host port req
148 = let uri = reqURI req
150 uriAuthority = Just URIAuth {
152 , uriRegName = T.unpack host
153 , uriPort = A.toString port
157 req { reqURI = uri' }
159 examineHeaders ∷ State AugmentedRequest ()
161 = do req ← gets arRequest
163 case getCIHeader "Expect" req of
167 → modify $ \ar → ar { arExpectedContinue = True }
169 → setStatus ExpectationFailed
171 case getCIHeader "Transfer-Encoding" req of
177 → setBodyLength $ S.Just Chunked
179 → setStatus NotImplemented
181 case A.toByteString <$> getHeader "Content-Length" req of
183 Just value → case C8.readInt value of
185 | C8.null garbage ∧ len ≥ 0
186 → setBodyLength $ S.Just $ Fixed len
187 _ → setStatus BadRequest
189 case getCIHeader "Connection" req of
195 examineBodyLength ∷ State AugmentedRequest ()
197 = do req ← gets arRequest
198 len ← gets arReqBodyLength
199 if reqMustHaveBody req then
200 -- POST and PUT requests must have an entity body.
201 when (S.isNothing len)
202 $ setStatus LengthRequired
204 -- Other requests must NOT have an entity body.
206 $ setStatus BadRequest