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
19 import Data.CaseInsensitive (CI)
20 import qualified Data.CaseInsensitive as CI
22 import Data.Text (Text)
23 import qualified Data.Text as T
24 import qualified Data.Text.Encoding as T
25 import Network.HTTP.Lucu.Headers
26 import Network.HTTP.Lucu.HttpVersion
27 import Network.HTTP.Lucu.Request
28 import Network.HTTP.Lucu.Response
31 import Prelude.Unicode
36 , arInitialStatus ∷ !SomeStatusCode
37 , arWillChunkBody ∷ !Bool
38 , arWillDiscardBody ∷ !Bool
40 , arExpectedContinue ∷ !Bool
41 , arReqBodyLength ∷ !(Maybe RequestBodyLength)
44 data RequestBodyLength
49 preprocess ∷ CI Text → PortNumber → Request → AugmentedRequest
50 preprocess localHost localPort req@(Request {..})
51 = execState go initialAR
53 initialAR ∷ AugmentedRequest
54 initialAR = AugmentedRequest {
56 , arInitialStatus = fromStatusCode OK
57 , arWillChunkBody = False
58 , arWillDiscardBody = False
60 , arExpectedContinue = False
61 , arReqBodyLength = Nothing
63 go ∷ State AugmentedRequest ()
64 go = do examineHttpVersion
66 examineAuthority localHost localPort
70 setRequest ∷ Request → State AugmentedRequest ()
72 = modify $ \ar → ar { arRequest = req }
74 setStatus ∷ StatusCode sc ⇒ sc → State AugmentedRequest ()
76 = modify $ \ar → ar { arInitialStatus = fromStatusCode sc }
78 setWillClose ∷ Bool → State AugmentedRequest ()
80 = modify $ \ar → ar { arWillClose = b }
82 setBodyLength ∷ Maybe RequestBodyLength → State AugmentedRequest ()
84 = modify $ \ar → ar { arReqBodyLength = len }
86 examineHttpVersion ∷ State AugmentedRequest ()
88 = do req ← gets arRequest
89 case reqVersion req of
90 -- HTTP/1.0 requests can't Keep-Alive.
94 → modify $ \ar → ar { arWillChunkBody = True }
95 _ → do setStatus HTTPVersionNotSupported
98 examineMethod ∷ State AugmentedRequest ()
100 = do req ← gets arRequest
101 case reqMethod req of
103 HEAD → modify $ \ar → ar { arWillDiscardBody = True }
107 _ → setStatus NotImplemented
109 examineAuthority ∷ CI Text → PortNumber → State AugmentedRequest ()
110 examineAuthority localHost localPort
111 = do req ← gets arRequest
112 when (isNothing $ uriAuthority $ reqURI req) $
113 case reqVersion req of
114 -- HTTP/1.0 requests have no Host header so complete it
115 -- with the configuration value.
117 → let host = localHost
118 port = case localPort of
120 n → A.unsafeFromString $ ':':show n
121 req' = updateAuthority host port req
124 -- HTTP/1.1 requests MUST have a Host header.
126 → case getHeader "Host" req of
130 req' = updateAuthority host port req
134 → setStatus BadRequest
135 -- Should never reach here...
136 ver → fail ("internal error: unknown version: " ⧺ show ver)
138 parseHost ∷ Ascii → (CI Text, Ascii)
140 = let (h, p) = C8.break (≡ ':') $ A.toByteString hp
141 -- FIXME: should decode punycode here.
142 hText = CI.mk $ T.decodeUtf8 h
143 pAscii = A.unsafeFromByteString p
147 updateAuthority ∷ CI Text → Ascii → Request → Request
148 updateAuthority host port req
149 = let uri = reqURI req
151 uriAuthority = Just URIAuth {
153 , uriRegName = T.unpack $ CI.original host
154 , uriPort = A.toString port
158 req { reqURI = uri' }
160 examineHeaders ∷ State AugmentedRequest ()
162 = do req ← gets arRequest
164 case getCIHeader "Expect" req of
168 → modify $ \ar → ar { arExpectedContinue = True }
170 → setStatus ExpectationFailed
172 case getCIHeader "Transfer-Encoding" req of
178 → setBodyLength $ Just Chunked
180 → setStatus NotImplemented
182 case A.toByteString <$> getHeader "Content-Length" req of
184 Just value → case C8.readInt value of
186 | C8.null garbage ∧ len ≥ 0
187 → setBodyLength $ Just $ Fixed len
188 _ → setStatus BadRequest
190 case getCIHeader "Connection" req of
196 examineBodyLength ∷ State AugmentedRequest ()
198 = do req ← gets arRequest
199 len ← gets arReqBodyLength
200 if reqMustHaveBody req then
201 -- POST and PUT requests must have an entity body.
203 $ setStatus LengthRequired
205 -- Other requests must NOT have an entity body.
207 $ setStatus BadRequest