]> gitweb @ CieloNegro.org - Lucu.git/blob - Network/HTTP/Lucu/Resource.hs
298b9b2541edd0f4d3d8b170bccb6cc9965bfe4e
[Lucu.git] / Network / HTTP / Lucu / Resource.hs
1 {-# LANGUAGE
2     GeneralizedNewtypeDeriving
3   , DoAndIfThenElse
4   , OverloadedStrings
5   , RecordWildCards
6   , UnicodeSyntax
7   #-}
8 {-# OPTIONS_HADDOCK prune #-}
9
10 -- |This is the Resource Monad; monadic actions to define the behavior
11 -- of each resources. The 'Resource' Monad is a kind of 'Prelude.IO'
12 -- Monad thus it implements 'Control.Monad.Trans.MonadIO' class. It is
13 -- also a state machine.
14 -- 
15 -- Request Processing Flow:
16 --
17 --   1. A client issues an HTTP request.
18 --
19 --   2. If the URI of it matches to any resource, the corresponding
20 --      'Resource' Monad starts running on a newly spawned thread.
21 --
22 --   3. The 'Resource' Monad looks at the request header, find (or not
23 --      find) an entity, receive the request body (if any), decide the
24 --      response header, and decide the response body. This process
25 --      will be discussed later.
26 --
27 --   4. The 'Resource' Monad and its thread stops running. The client
28 --      may or may not be sending us the next request at this point.
29 --
30 -- 'Resource' Monad takes the following states. The initial state is
31 -- /Examining Request/ and the final state is /Done/.
32 --
33 --   [/Examining Request/] In this state, a 'Resource' looks at the
34 --   request header and thinks about an entity for it. If there is a
35 --   suitable entity, the 'Resource' tells the system an entity tag
36 --   and its last modification time ('foundEntity'). If it found no
37 --   entity, it tells the system so ('foundNoEntity'). In case it is
38 --   impossible to decide the existence of entity, which is a typical
39 --   case for POST requests, 'Resource' does nothing in this state.
40 --
41 --   [/Getting Body/] A 'Resource' asks the system to receive a
42 --   request body from client. Before actually reading from the
43 --   socket, the system sends \"100 Continue\" to the client if need
44 --   be. When a 'Resource' transits to the next state without
45 --   receiving all or part of request body, the system still reads it
46 --   and just throws it away.
47 --
48 --   [/Deciding Header/] A 'Resource' makes a decision of status code
49 --   and response header. When it transits to the next state, the
50 --   system checks the validness of response header and then write
51 --   them to the socket.
52 --
53 --   [/Deciding Body/] In this state, a 'Resource' asks the system to
54 --   write some response body to the socket. When it transits to the
55 --   next state without writing any response body, the system
56 --   completes it depending on the status code.
57 --
58 --   [/Done/] Everything is over. A 'Resource' can do nothing for the
59 --   HTTP interaction anymore.
60 --
61 -- Note that the state transition is one-way: for instance, it is an
62 -- error to try to read a request body after writing some
63 -- response. This limitation is for efficiency. We don't want to read
64 -- the entire request before starting 'Resource', nor we don't want to
65 -- postpone writing the entire response till the end of 'Resource'
66 -- computation.
67
68 module Network.HTTP.Lucu.Resource
69     (
70     -- * Types
71       Resource
72     , FormData(..)
73     , runRes -- private
74
75     -- * Actions
76
77     -- ** Getting request header
78
79     -- |These actions can be computed regardless of the current state,
80     -- and they don't change the state.
81     , getConfig
82     , getRemoteAddr
83     , getRemoteAddr'
84     , getRemoteHost
85     , getRemoteCertificate
86     , getRequest
87     , getMethod
88     , getRequestURI
89     , getRequestVersion
90     , getResourcePath
91     , getPathInfo
92     , getQueryForm
93     , getHeader
94     , getAccept
95     , getAcceptEncoding
96     , isEncodingAcceptable
97     , getContentType
98     , getAuthorization
99
100     -- ** Finding an entity
101
102     -- |These actions can be computed only in the /Examining Request/
103     -- state. After the computation, the 'Resource' transits to
104     -- /Getting Body/ state.
105     , foundEntity
106     , foundETag
107     , foundTimeStamp
108     , foundNoEntity
109
110     -- ** Getting a request body
111
112     -- |Computation of these actions changes the state to /Getting
113     -- Body/.
114     , input
115     , inputChunk
116     , inputForm
117     , defaultLimit
118
119     -- ** Setting response headers
120     
121     -- |Computation of these actions changes the state to /Deciding
122     -- Header/.
123     , setStatus
124     , setHeader
125     , redirect
126     , setContentType
127     , setLocation
128     , setContentEncoding
129     , setWWWAuthenticate
130
131     -- ** Writing a response body
132
133     -- |Computation of these actions changes the state to /Deciding
134     -- Body/.
135     , output
136     , outputChunk
137
138     , driftTo -- private
139     )
140     where
141 import Blaze.ByteString.Builder (Builder)
142 import qualified Blaze.ByteString.Builder.ByteString as BB
143 import Control.Applicative
144 import Control.Concurrent.STM
145 import Control.Monad.Reader
146 import Control.Monad.Unicode
147 import Data.Ascii (Ascii, CIAscii)
148 import qualified Data.Ascii as A
149 import qualified Data.Attoparsec.Char8 as P
150 import qualified Data.Attoparsec.Lazy  as LP
151 import Data.ByteString (ByteString)
152 import qualified Data.ByteString as Strict
153 import qualified Data.ByteString.Lazy as Lazy
154 import Data.Foldable (toList)
155 import Data.List
156 import qualified Data.Map as M
157 import Data.Maybe
158 import Data.Monoid
159 import Data.Monoid.Unicode
160 import Data.Sequence (Seq)
161 import Data.Text (Text)
162 import qualified Data.Text as T
163 import qualified Data.Text.Encoding as T
164 import Data.Time
165 import qualified Data.Time.HTTP as HTTP
166 import Network.HTTP.Lucu.Abortion
167 import Network.HTTP.Lucu.Authorization
168 import Network.HTTP.Lucu.Config
169 import Network.HTTP.Lucu.ContentCoding
170 import Network.HTTP.Lucu.DefaultPage
171 import Network.HTTP.Lucu.ETag
172 import qualified Network.HTTP.Lucu.Headers as H
173 import Network.HTTP.Lucu.HttpVersion
174 import Network.HTTP.Lucu.Interaction
175 import Network.HTTP.Lucu.MultipartForm
176 import Network.HTTP.Lucu.Postprocess
177 import Network.HTTP.Lucu.Request
178 import Network.HTTP.Lucu.Response
179 import Network.HTTP.Lucu.MIMEType
180 import Network.HTTP.Lucu.Utils
181 import Network.Socket hiding (accept)
182 import Network.URI hiding (path)
183 import OpenSSL.X509
184 import Prelude.Unicode
185
186 -- |The 'Resource' monad. This monad implements 'MonadIO' so it can do
187 -- any 'IO' actions.
188 newtype Resource a
189     = Resource {
190         unRes ∷ ReaderT Interaction IO a
191       }
192     deriving (Applicative, Functor, Monad, MonadIO)
193
194 runRes ∷ Resource a → Interaction → IO a
195 runRes r itr
196     = runReaderT (unRes r) itr
197
198 getInteraction ∷ Resource Interaction
199 getInteraction = Resource ask
200
201 -- |Get the 'Config' value which is used for the httpd.
202 getConfig ∷ Resource Config
203 getConfig = itrConfig <$> getInteraction
204
205 -- |Get the 'SockAddr' of the remote host. If you want a string
206 -- representation instead of 'SockAddr', use 'getRemoteAddr''.
207 getRemoteAddr ∷ Resource SockAddr
208 getRemoteAddr = itrRemoteAddr <$> getInteraction
209
210 -- |Get the string representation of the address of remote host. If
211 -- you want a 'SockAddr' instead of 'String', use 'getRemoteAddr'.
212 getRemoteAddr' ∷ Resource HostName
213 getRemoteAddr'
214     = do sa          ← getRemoteAddr
215          (Just a, _) ← liftIO $ getNameInfo [NI_NUMERICHOST] False False sa
216          return a
217
218 -- |Resolve an address to the remote host.
219 getRemoteHost ∷ Resource (Maybe HostName)
220 getRemoteHost
221     = do sa ← getRemoteAddr
222          fst <$> (liftIO $ getNameInfo [] True False sa)
223
224 -- | Return the X.509 certificate of the client, or 'Nothing' if:
225 --
226 --   * This request didn't came through an SSL stream.
227 --
228 --   * The client didn't send us its certificate.
229 --
230 --   * The 'OpenSSL.Session.VerificationMode' of
231 --   'OpenSSL.Session.SSLContext' in 'SSLConfig' has not been set to
232 --   'OpenSSL.Session.VerifyPeer'.
233 getRemoteCertificate ∷ Resource (Maybe X509)
234 getRemoteCertificate = itrRemoteCert <$> getInteraction
235
236 -- |Get the 'Request' value which represents the request header. In
237 -- general you don't have to use this action.
238 getRequest ∷ Resource Request
239 getRequest = (fromJust ∘ itrRequest) <$> getInteraction
240
241 -- |Get the 'Method' value of the request.
242 getMethod ∷ Resource Method
243 getMethod = reqMethod <$> getRequest
244
245 -- |Get the URI of the request.
246 getRequestURI ∷ Resource URI
247 getRequestURI = reqURI <$> getRequest
248
249 -- |Get the HTTP version of the request.
250 getRequestVersion ∷ Resource HttpVersion
251 getRequestVersion = reqVersion <$> getRequest
252
253 -- |Get the path of this 'Resource' (to be exact,
254 -- 'Network.HTTP.Lucu.Resource.Tree.ResourceDef') in the
255 -- 'Network.HTTP.Lucu.Resource.Tree.ResTree'. The result of this
256 -- action is the exact path in the tree even if the
257 -- 'Network.HTTP.Lucu.Resource.Tree.ResourceDef' is greedy.
258 --
259 -- Example:
260 --
261 -- > main = let tree = mkResTree [ (["foo"], resFoo) ]
262 -- >        in runHttpd defaultConfig tree
263 -- >
264 -- > resFoo = ResourceDef {
265 -- >     resIsGreedy = True
266 -- >   , resGet = Just $ do requestURI   ← getRequestURI
267 -- >                        resourcePath ← getResourcePath
268 -- >                        pathInfo     ← getPathInfo
269 -- >                        -- uriPath requestURI == "/foo/bar/baz"
270 -- >                        -- resourcePath       == ["foo"]
271 -- >                        -- pathInfo           == ["bar", "baz"]
272 -- >                        ...
273 -- >   , ...
274 -- >   }
275 getResourcePath ∷ Resource [Text]
276 getResourcePath = (fromJust ∘ itrResourcePath) <$> getInteraction
277
278 -- |This is an analogy of CGI PATH_INFO. The result is
279 -- URI-unescaped. It is always @[]@ if the
280 -- 'Network.HTTP.Lucu.Resource.Tree.ResourceDef' is not greedy. See
281 -- 'getResourcePath'.
282 --
283 -- Note that the returned path is URI-decoded and then UTF-8 decoded.
284 getPathInfo ∷ Resource [Text]
285 getPathInfo = do rsrcPath ← getResourcePath
286                  reqPath  ← splitPathInfo <$> getRequestURI
287                  -- rsrcPath と reqPath の共通する先頭部分を reqPath か
288                  -- ら全部取り除くと、それは PATH_INFO のやうなものにな
289                  -- る。rsrcPath は全部一致してゐるに決まってゐる(でな
290                  -- ければこの Resource が撰ばれた筈が無い)ので、
291                  -- rsrcPath の長さの分だけ削除すれば良い。
292                  return $ drop (length rsrcPath) reqPath
293
294 -- |Assume the query part of request URI as
295 -- application\/x-www-form-urlencoded, and parse it to pairs of
296 -- @(name, formData)@. This action doesn't parse the request body. See
297 -- 'inputForm'. Field names are decoded in UTF-8.
298 getQueryForm ∷ Resource [(Text, FormData)]
299 getQueryForm = parse' <$> getRequestURI
300     where
301       parse' = map toPairWithFormData ∘
302                parseWWWFormURLEncoded ∘
303                fromJust ∘
304                A.fromChars ∘
305                drop 1 ∘
306                uriQuery
307
308 toPairWithFormData ∷ (ByteString, ByteString) → (Text, FormData)
309 toPairWithFormData (name, value)
310     = let fd = FormData {
311                  fdFileName = Nothing
312                , fdContent  = Lazy.fromChunks [value]
313                }
314       in (T.decodeUtf8 name, fd)
315
316 -- |Get a value of given request header. Comparison of header name is
317 -- case-insensitive. Note that this action is not intended to be used
318 -- so frequently: there should be actions like 'getContentType' for
319 -- every common headers.
320 getHeader ∷ CIAscii → Resource (Maybe Ascii)
321 getHeader name
322     = H.getHeader name <$> getRequest
323
324 -- |Get a list of 'MIMEType' enumerated on header \"Accept\".
325 getAccept ∷ Resource [MIMEType]
326 getAccept
327     = do acceptM ← getHeader "Accept"
328          case acceptM of
329            Nothing
330                → return []
331            Just accept
332                → case P.parseOnly p (A.toByteString accept) of
333                     Right xs → return xs
334                     Left  _  → abort BadRequest []
335                                (Just $ "Unparsable Accept: " ⊕ A.toText accept)
336     where
337       p = do xs ← mimeTypeListP
338              P.endOfInput
339              return xs
340
341 -- |Get a list of @(contentCoding, qvalue)@ enumerated on header
342 -- \"Accept-Encoding\". The list is sorted in descending order by
343 -- qvalue.
344 getAcceptEncoding ∷ Resource [(CIAscii, Maybe Double)]
345 getAcceptEncoding
346     = do accEncM ← getHeader "Accept-Encoding"
347          case accEncM of
348            Nothing
349                -- HTTP/1.0 には Accept-Encoding が無い場合の規定が無い
350                -- ので安全の爲 identity が指定された事にする。HTTP/1.1
351                -- の場合は何でも受け入れて良い事になってゐるので "*" が
352                -- 指定された事にする。
353                → do ver ← getRequestVersion
354                     case ver of
355                       HttpVersion 1 0 → return [("identity", Nothing)]
356                       HttpVersion 1 1 → return [("*"       , Nothing)]
357                       _               → abort InternalServerError []
358                                         (Just "getAcceptEncoding: unknown HTTP version")
359            Just ae
360                → if ae ≡ "" then
361                       -- identity のみが許される。
362                       return [("identity", Nothing)]
363                   else
364                       case P.parseOnly p (A.toByteString ae) of
365                         Right xs → return $ map toTuple $ reverse $ sort xs
366                         Left  _  → abort BadRequest []
367                                    (Just $ "Unparsable Accept-Encoding: " ⊕ A.toText ae)
368     where
369       p = do xs ← acceptEncodingListP
370              P.endOfInput
371              return xs
372
373       toTuple (AcceptEncoding {..})
374           = (aeEncoding, aeQValue)
375
376 -- |Check whether a given content-coding is acceptable.
377 isEncodingAcceptable ∷ CIAscii → Resource Bool
378 isEncodingAcceptable encoding = any f <$> getAcceptEncoding
379     where
380       f (e, q)
381           = (e ≡ "*" ∨ e ≡ encoding) ∧ q ≢ Just 0
382
383 -- |Get the header \"Content-Type\" as 'MIMEType'.
384 getContentType ∷ Resource (Maybe MIMEType)
385 getContentType
386     = do cTypeM ← getHeader "Content-Type"
387          case cTypeM of
388            Nothing
389                → return Nothing
390            Just cType
391                → case P.parseOnly p (A.toByteString cType) of
392                     Right t → return $ Just t
393                     Left  _ → abort BadRequest []
394                               (Just $ "Unparsable Content-Type: " ⊕ A.toText cType)
395     where
396       p = do t ← mimeTypeP
397              P.endOfInput
398              return t
399
400 -- |Get the header \"Authorization\" as 'AuthCredential'.
401 getAuthorization ∷ Resource (Maybe AuthCredential)
402 getAuthorization
403     = do authM ← getHeader "Authorization"
404          case authM of
405            Nothing
406                → return Nothing
407            Just auth
408                → case P.parseOnly p (A.toByteString auth) of
409                     Right ac → return $ Just ac
410                     Left  _  → return Nothing
411     where
412       p = do ac ← authCredentialP
413              P.endOfInput
414              return ac
415
416
417 {- ExaminingRequest 時に使用するアクション群 -}
418
419 -- |Tell the system that the 'Resource' found an entity for the
420 -- request URI. If this is a GET or HEAD request, a found entity means
421 -- a datum to be replied. If this is a PUT or DELETE request, it means
422 -- a datum which was stored for the URI until now. It is an error to
423 -- compute 'foundEntity' if this is a POST request.
424 --
425 -- Computation of 'foundEntity' performs \"If-Match\" test or
426 -- \"If-None-Match\" test if possible. When those tests fail, the
427 -- computation of 'Resource' immediately aborts with status \"412
428 -- Precondition Failed\" or \"304 Not Modified\" depending on the
429 -- situation.
430 --
431 -- If this is a GET or HEAD request, 'foundEntity' automatically puts
432 -- \"ETag\" and \"Last-Modified\" headers into the response.
433 foundEntity ∷ ETag → UTCTime → Resource ()
434 foundEntity tag timeStamp
435     = do driftTo ExaminingRequest
436
437          method ← getMethod
438          when (method ≡ GET ∨ method ≡ HEAD)
439              $ setHeader' "Last-Modified" (HTTP.toAscii timeStamp)
440          when (method ≡ POST)
441              $ abort InternalServerError []
442                (Just "Illegal computation of foundEntity for a POST request.")
443          foundETag tag
444
445          driftTo GettingBody
446
447 -- |Tell the system that the 'Resource' found an entity for the
448 -- request URI. The only difference from 'foundEntity' is that
449 -- 'foundETag' doesn't (and can't) put \"Last-Modified\" header into
450 -- the response.
451 --
452 -- This action is not preferred. You should use 'foundEntity' whenever
453 -- possible.
454 foundETag ∷ ETag → Resource ()
455 foundETag tag
456     = do driftTo ExaminingRequest
457       
458          method ← getMethod
459          when (method ≡ GET ∨ method ≡ HEAD)
460               $ setHeader' "ETag" (printETag tag)
461          when (method ≡ POST)
462               $ abort InternalServerError []
463                 (Just "Illegal computation of foundETag for POST request.")
464
465          -- If-Match があればそれを見る。
466          ifMatch ← getHeader "If-Match"
467          case ifMatch of
468            Nothing    → return ()
469            Just value → if value ≡ "*" then
470                             return ()
471                         else
472                             case P.parseOnly p (A.toByteString value) of
473                               Right tags
474                                  -- tags の中に一致するものが無ければ
475                                  -- PreconditionFailed で終了。
476                                  → when ((¬) (any (≡ tag) tags))
477                                        $ abort PreconditionFailed []
478                                          (Just $ "The entity tag doesn't match: " ⊕ A.toText value)
479                               Left _
480                                    → abort BadRequest [] (Just $ "Unparsable If-Match: " ⊕ A.toText value)
481
482          let statusForNoneMatch
483                  = if method ≡ GET ∨ method ≡ HEAD then
484                        NotModified
485                    else
486                        PreconditionFailed
487
488          -- If-None-Match があればそれを見る。
489          ifNoneMatch ← getHeader "If-None-Match"
490          case ifNoneMatch of
491            Nothing    → return ()
492            Just value → if value ≡ "*" then
493                              abort statusForNoneMatch [] (Just "The entity tag matches: *")
494                          else
495                              case P.parseOnly p (A.toByteString value) of
496                                Right tags
497                                    → when (any (≡ tag) tags)
498                                          $ abort statusForNoneMatch []
499                                            (Just $ "The entity tag matches: " ⊕ A.toText value)
500                                Left _
501                                    → abort BadRequest []
502                                      (Just $ "Unparsable If-None-Match: " ⊕ A.toText value)
503
504          driftTo GettingBody
505     where
506       p = do xs ← eTagListP
507              P.endOfInput
508              return xs
509
510 -- |Tell the system that the 'Resource' found an entity for the
511 -- request URI. The only difference from 'foundEntity' is that
512 -- 'foundTimeStamp' performs \"If-Modified-Since\" test or
513 -- \"If-Unmodified-Since\" test instead of \"If-Match\" test or
514 -- \"If-None-Match\" test. Be aware that any tests based on last
515 -- modification time are unsafe because it is possible to mess up such
516 -- tests by modifying the entity twice in a second.
517 --
518 -- This action is not preferred. You should use 'foundEntity' whenever
519 -- possible.
520 foundTimeStamp ∷ UTCTime → Resource ()
521 foundTimeStamp timeStamp
522     = do driftTo ExaminingRequest
523
524          method ← getMethod
525          when (method ≡ GET ∨ method ≡ HEAD)
526              $ setHeader' "Last-Modified" (HTTP.toAscii timeStamp)
527          when (method ≡ POST)
528              $ abort InternalServerError []
529                (Just "Illegal computation of foundTimeStamp for POST request.")
530
531          let statusForIfModSince
532                  = if method ≡ GET ∨ method ≡ HEAD then
533                        NotModified
534                    else
535                        PreconditionFailed
536
537          -- If-Modified-Since があればそれを見る。
538          ifModSince ← getHeader "If-Modified-Since"
539          case ifModSince of
540            Just str → case HTTP.fromAscii str of
541                          Right lastTime
542                              → when (timeStamp ≤ lastTime)
543                                $ abort statusForIfModSince []
544                                  (Just $ "The entity has not been modified since " ⊕ A.toText str)
545                          Left _
546                              → return () -- 不正な時刻は無視
547            Nothing  → return ()
548
549          -- If-Unmodified-Since があればそれを見る。
550          ifUnmodSince ← getHeader "If-Unmodified-Since"
551          case ifUnmodSince of
552            Just str → case HTTP.fromAscii str of
553                          Right lastTime
554                              → when (timeStamp > lastTime)
555                                $ abort PreconditionFailed []
556                                  (Just $ "The entity has not been modified since " ⊕ A.toText str)
557                          Left _
558                              → return () -- 不正な時刻は無視
559            Nothing  → return ()
560
561          driftTo GettingBody
562
563 -- | Computation of @'foundNoEntity' mStr@ tells the system that the
564 -- 'Resource' found no entity for the request URI. @mStr@ is an
565 -- optional error message to be replied to the client.
566 --
567 -- If this is a PUT request, 'foundNoEntity' performs \"If-Match\"
568 -- test and aborts with status \"412 Precondition Failed\" when it
569 -- failed. If this is a GET, HEAD, POST or DELETE request,
570 -- 'foundNoEntity' always aborts with status \"404 Not Found\".
571 foundNoEntity ∷ Maybe Text → Resource ()
572 foundNoEntity msgM
573     = do driftTo ExaminingRequest
574
575          method ← getMethod
576          when (method ≢ PUT)
577              $ abort NotFound [] msgM
578
579          -- エンティティが存在しないと云ふ事は、"*" も含めたどのやうな
580          -- If-Match: 條件も滿たさない。
581          ifMatch ← getHeader "If-Match"
582          when (ifMatch ≢ Nothing)
583              $ abort PreconditionFailed [] msgM
584
585          driftTo GettingBody
586
587
588 {- GettingBody 時に使用するアクション群 -}
589
590 -- | Computation of @'input' limit@ attempts to read the request body
591 -- up to @limit@ bytes, and then make the 'Resource' transit to
592 -- /Deciding Header/ state. When the actual size of body is larger
593 -- than @limit@ bytes, computation of 'Resource' immediately aborts
594 -- with status \"413 Request Entity Too Large\". When the request has
595 -- no body, 'input' returns an empty string.
596 --
597 -- @limit@ may be less than or equal to zero. In this case, the
598 -- default limitation value ('cnfMaxEntityLength') is used. See
599 -- 'defaultLimit'.
600 --
601 -- 'input' returns a 'Lazy.ByteString' but it's not really lazy:
602 -- reading from the socket just happens at the computation of 'input',
603 -- not at the evaluation of the 'Lazy.ByteString'. The same goes for
604 -- 'inputChunk'.
605 input ∷ Int → Resource Lazy.ByteString
606 input limit
607     = do driftTo GettingBody
608          itr     ← getInteraction
609          chunk   ← if reqHasBody $ fromJust $ itrRequest itr then
610                        askForInput itr
611                    else
612                        do driftTo DecidingHeader
613                           return (∅)
614          return chunk
615     where
616       askForInput ∷ Interaction → Resource Lazy.ByteString
617       askForInput (Interaction {..})
618           = do let confLimit   = cnfMaxEntityLength itrConfig
619                    actualLimit = if limit ≤ 0 then
620                                      confLimit
621                                  else
622                                      limit
623                when (actualLimit ≤ 0)
624                         $ fail ("inputLBS: limit must be positive: " ⧺ show actualLimit)
625                -- Reader にリクエスト
626                liftIO $ atomically
627                       $ writeTVar itrReqBodyWanted (Just actualLimit)
628                -- 應答を待つ。トランザクションを分けなければ當然デッドロックする。
629                chunk ← liftIO $ atomically
630                        $ do chunkLen    ← readTVar itrReceivedBodyLen
631                             chunkIsOver ← readTVar itrReqChunkIsOver
632                             if chunkLen < actualLimit then
633                                 -- 要求された量に滿たなくて、まだ殘りが
634                                 -- あるなら再試行。
635                                 unless chunkIsOver
636                                     $ retry
637                             else
638                                 -- 制限値一杯まで讀むやうに指示したのに
639                                 -- まだ殘ってゐるなら、それは多過ぎる。
640                                 unless chunkIsOver
641                                     $ tooLarge actualLimit
642                             -- 成功。itr 内にチャンクを置いたままにする
643                             -- とメモリの無駄になるので除去。
644                             chunk ← seqToLBS <$> readTVar itrReceivedBody
645                             writeTVar itrReceivedBody    (∅)
646                             writeTVar itrReceivedBodyLen 0
647                             return chunk
648
649                driftTo DecidingHeader
650                return chunk
651
652       tooLarge ∷ Int → STM ()
653       tooLarge lim = abortSTM RequestEntityTooLarge []
654                      (Just $ "Request body must be smaller than "
655                              ⊕ T.pack (show lim) ⊕ " bytes.")
656
657 seqToLBS ∷ Seq ByteString → Lazy.ByteString
658 {-# INLINE seqToLBS #-}
659 seqToLBS = Lazy.fromChunks ∘ toList
660          
661 -- | Computation of @'inputChunk' limit@ attempts to read a part of
662 -- request body up to @limit@ bytes. You can read any large request by
663 -- repeating computation of this action. When you've read all the
664 -- request body, 'inputChunk' returns an empty string and then make
665 -- the 'Resource' transit to /Deciding Header/ state.
666 --
667 -- @limit@ may be less than or equal to zero. In this case, the
668 -- default limitation value ('cnfMaxEntityLength') is used. See
669 -- 'defaultLimit'.
670 --
671 -- Note that 'inputChunkLBS' is more efficient than 'inputChunk' so you
672 -- should use it whenever possible.
673 inputChunk ∷ Int → Resource Lazy.ByteString
674 inputChunk limit
675     = do driftTo GettingBody
676          itr     ← getInteraction
677          chunk   ← if reqHasBody $ fromJust $ itrRequest itr then
678                        askForInput itr
679                    else
680                        do driftTo DecidingHeader
681                           return (∅)
682          return chunk
683     where
684       askForInput ∷ Interaction → Resource Lazy.ByteString
685       askForInput (Interaction {..})
686           = do let confLimit   = cnfMaxEntityLength itrConfig
687                    actualLimit = if limit < 0 then
688                                      confLimit
689                                  else
690                                      limit
691                when (actualLimit ≤ 0)
692                         $ fail ("inputChunkLBS: limit must be positive: " ++ show actualLimit)
693                -- Reader にリクエスト
694                liftIO $ atomically
695                       $ writeTVar itrReqBodyWanted (Just actualLimit)
696                -- 應答を待つ。トランザクションを分けなければ當然デッドロック。
697                chunk ← liftIO $ atomically
698                        $ do chunkLen ← readTVar itrReceivedBodyLen
699                             -- 要求された量に滿たなくて、まだ殘りがある
700                             -- なら再試行。
701                             when (chunkLen < actualLimit)
702                                 $ do chunkIsOver ← readTVar itrReqChunkIsOver
703                                      unless chunkIsOver
704                                          $ retry
705                             -- 成功
706                             chunk ← seqToLBS <$> readTVar itrReceivedBody
707                             writeTVar itrReceivedBody    (∅)
708                             writeTVar itrReceivedBodyLen 0
709                             return chunk
710                when (Lazy.null chunk)
711                    $ driftTo DecidingHeader
712                return chunk
713
714 -- | Computation of @'inputForm' limit@ attempts to read the request
715 -- body with 'input' and parse it as
716 -- @application\/x-www-form-urlencoded@ or @multipart\/form-data@. If
717 -- the request header \"Content-Type\" is neither of them, 'inputForm'
718 -- makes 'Resource' abort with status \"415 Unsupported Media
719 -- Type\". If the request has no \"Content-Type\", it aborts with
720 -- \"400 Bad Request\".
721 --
722 -- Field names in @multipart\/form-data@ will be precisely decoded in
723 -- accordance with RFC 2231. On the other hand,
724 -- @application\/x-www-form-urlencoded@ says nothing about the
725 -- encoding of field names, so they'll always be decoded in UTF-8.
726 inputForm ∷ Int → Resource [(Text, FormData)]
727 inputForm limit
728     = do cTypeM ← getContentType
729          case cTypeM of
730            Nothing
731                → abort BadRequest [] (Just "Missing Content-Type")
732            Just (MIMEType "application" "x-www-form-urlencoded" _)
733                → readWWWFormURLEncoded
734            Just (MIMEType "multipart" "form-data" params)
735                → readMultipartFormData params
736            Just cType
737                → abort UnsupportedMediaType []
738                  $ Just
739                  $ A.toText
740                  $ A.fromAsciiBuilder
741                  $ A.toAsciiBuilder "Unsupported media type: "
742                  ⊕ printMIMEType cType
743     where
744       readWWWFormURLEncoded
745           = (map toPairWithFormData ∘ parseWWWFormURLEncoded)
746             <$>
747             (bsToAscii =≪ input limit)
748
749       bsToAscii bs
750           = case A.fromByteString (Strict.concat (Lazy.toChunks bs)) of
751               Just a  → return a
752               Nothing → abort BadRequest [] (Just "Malformed x-www-form-urlencoded")
753
754       readMultipartFormData params
755           = do case M.lookup "boundary" params of
756                  Nothing
757                      → abort BadRequest [] (Just "Missing boundary of multipart/form-data")
758                  Just boundary
759                      → do src ← input limit
760                           b   ← case A.fromText boundary of
761                                    Just b  → return b
762                                    Nothing → abort BadRequest []
763                                              (Just $ "Malformed boundary: " ⊕ boundary)
764                           case LP.parse (p b) src of
765                             LP.Done _ formList
766                                 → return formList
767                             _   → abort BadRequest [] (Just "Unparsable multipart/form-data")
768           where
769             p b = do xs ← multipartFormP b
770                      P.endOfInput
771                      return xs
772
773 -- | This is just a constant @-1@. It's better to say @'input'
774 -- 'defaultLimit'@ than to say @'input' (-1)@ but these are exactly
775 -- the same.
776 defaultLimit ∷ Int
777 defaultLimit = (-1)
778
779
780 {- DecidingHeader 時に使用するアクション群 -}
781
782 -- | Set the response status code. If you omit to compute this action,
783 -- the status code will be defaulted to \"200 OK\".
784 setStatus ∷ StatusCode → Resource ()
785 setStatus sc
786     = do driftTo DecidingHeader
787          itr ← getInteraction
788          liftIO
789              $ atomically
790              $ setResponseStatus itr sc
791
792 -- | Set a value of given resource header. Comparison of header name
793 -- is case-insensitive. Note that this action is not intended to be
794 -- used so frequently: there should be actions like 'setContentType'
795 -- for every common headers.
796 --
797 -- Some important headers (especially \"Content-Length\" and
798 -- \"Transfer-Encoding\") may be silently dropped or overwritten by
799 -- the system not to corrupt the interaction with client at the
800 -- viewpoint of HTTP protocol layer. For instance, if we are keeping
801 -- the connection alive, without this process it causes a catastrophe
802 -- to send a header \"Content-Length: 10\" and actually send a body of
803 -- 20 bytes long. In this case the client shall only accept the first
804 -- 10 bytes of response body and thinks that the residual 10 bytes is
805 -- a part of header of the next response.
806 setHeader ∷ CIAscii → Ascii → Resource ()
807 setHeader name value
808     = driftTo DecidingHeader ≫ setHeader' name value
809
810 setHeader' ∷ CIAscii → Ascii → Resource ()
811 setHeader' name value
812     = do itr ← getInteraction
813          liftIO $ atomically
814                 $ do res ← readTVar $ itrResponse itr
815                      let res' = H.setHeader name value res
816                      writeTVar (itrResponse itr) res'
817
818 -- | Computation of @'redirect' code uri@ sets the response status to
819 -- @code@ and \"Location\" header to @uri@. The @code@ must satisfy
820 -- 'isRedirection' or it causes an error.
821 redirect ∷ StatusCode → URI → Resource ()
822 redirect code uri
823     = do when (code ≡ NotModified ∨ not (isRedirection code))
824              $ abort InternalServerError []
825              $ Just
826              $ A.toText
827              $ A.fromAsciiBuilder
828              $ A.toAsciiBuilder "Attempted to redirect with status "
829              ⊕ printStatusCode code
830          setStatus code
831          setLocation uri
832
833 -- | Computation of @'setContentType' mType@ sets the response header
834 -- \"Content-Type\" to @mType@.
835 setContentType ∷ MIMEType → Resource ()
836 setContentType
837     = setHeader "Content-Type" ∘ A.fromAsciiBuilder ∘ printMIMEType
838
839 -- | Computation of @'setLocation' uri@ sets the response header
840 -- \"Location\" to @uri@.
841 setLocation ∷ URI → Resource ()
842 setLocation uri
843     = case A.fromChars uriStr of
844         Just a  → setHeader "Location" a
845         Nothing → abort InternalServerError []
846                   (Just $ "Malformed URI: " ⊕ T.pack uriStr)
847     where
848       uriStr = uriToString id uri ""
849
850 -- |Computation of @'setContentEncoding' codings@ sets the response
851 -- header \"Content-Encoding\" to @codings@.
852 setContentEncoding ∷ [CIAscii] → Resource ()
853 setContentEncoding codings
854     = do ver ← getRequestVersion
855          tr  ← case ver of
856                   HttpVersion 1 0 → return (toAB ∘ unnormalizeCoding)
857                   HttpVersion 1 1 → return toAB
858                   _               → abort InternalServerError []
859                                     (Just "setContentEncoding: Unknown HTTP version")
860          setHeader "Content-Encoding"
861                    (A.fromAsciiBuilder $ joinWith ", " $ map tr codings)
862     where
863       toAB = A.toAsciiBuilder ∘ A.fromCIAscii
864
865 -- |Computation of @'setWWWAuthenticate' challenge@ sets the response
866 -- header \"WWW-Authenticate\" to @challenge@.
867 setWWWAuthenticate ∷ AuthChallenge → Resource ()
868 setWWWAuthenticate challenge
869     = setHeader "WWW-Authenticate" (printAuthChallenge challenge)
870
871
872 {- DecidingBody 時に使用するアクション群 -}
873
874 -- | Write a 'Lazy.ByteString' to the response body, and then transit
875 -- to the /Done/ state. It is safe to apply 'output' to an infinite
876 -- string, such as the lazy stream of \/dev\/random.
877 output ∷ Lazy.ByteString → Resource ()
878 {-# INLINE output #-}
879 output str = outputChunk str *> driftTo Done
880
881 -- | Write a 'Lazy.ByteString' to the response body. This action can
882 -- be repeated as many times as you want. It is safe to apply
883 -- 'outputChunk' to an infinite string.
884 outputChunk ∷ Lazy.ByteString → Resource ()
885 outputChunk wholeChunk
886     = do driftTo DecidingBody
887          itr ← getInteraction
888          
889          let limit = cnfMaxOutputChunkLength $ itrConfig itr
890          when (limit ≤ 0)
891              $ abort InternalServerError []
892                (Just $ "cnfMaxOutputChunkLength must be positive: " ⊕ T.pack (show limit))
893
894          discardBody ← liftIO $ atomically $ readTVar $ itrWillDiscardBody itr
895          unless (discardBody)
896              $ sendChunks itr wholeChunk limit
897
898          unless (Lazy.null wholeChunk)
899              $ liftIO $ atomically $
900                writeTVar (itrSentNoBodySoFar itr) False
901     where
902       sendChunks ∷ Interaction → Lazy.ByteString → Int → Resource ()
903       sendChunks itr@(Interaction {..}) str limit
904           | Lazy.null str = return ()
905           | otherwise     = do let (chunk, remaining) = Lazy.splitAt (fromIntegral limit) str
906                                liftIO $ atomically
907                                       $ putTMVar itrBodyToSend (chunkToBuilder chunk)
908                                sendChunks itr remaining limit
909
910       chunkToBuilder ∷ Lazy.ByteString → Builder
911       chunkToBuilder = mconcat ∘ map BB.fromByteString ∘ Lazy.toChunks
912
913 {-
914
915   [GettingBody からそれ以降の状態に遷移する時]
916   
917   body を讀み終へてゐなければ、殘りの body を讀み捨てる。
918
919
920   [DecidingHeader からそれ以降の状態に遷移する時]
921
922   postprocess する。
923
924
925   [Done に遷移する時]
926
927   bodyIsNull が False ならば何もしない。True だった場合は出力補完す
928   る。
929
930 -}
931
932 driftTo ∷ InteractionState → Resource ()
933 driftTo newState
934     = do itr ← getInteraction
935          liftIO $ atomically
936                 $ do oldState ← readTVar $ itrState itr
937                      if newState < oldState then
938                          throwStateError oldState newState
939                      else
940                          do let a = [oldState .. newState]
941                                 b = tail a
942                                 c = zip a b
943                             mapM_ (uncurry $ drift itr) c
944                             writeTVar (itrState itr) newState
945     where
946       throwStateError ∷ Monad m ⇒ InteractionState → InteractionState → m a
947       throwStateError Done DecidingBody
948           = fail "It makes no sense to output something after finishing to output."
949       throwStateError old new
950           = fail ("state error: " ⧺ show old ⧺ " ==> " ⧺ show new)
951
952       drift ∷ Interaction → InteractionState → InteractionState → STM ()
953       drift (Interaction {..}) GettingBody _
954           = writeTVar itrReqBodyWasteAll True
955       drift itr DecidingHeader _
956           = postprocess itr
957       drift itr@(Interaction {..}) _ Done
958           = do bodyIsNull ← readTVar itrSentNoBodySoFar
959                when bodyIsNull
960                    $ writeDefaultPage itr
961       drift _ _ _
962           = return ()