]> gitweb @ CieloNegro.org - Lucu.git/blobdiff - Network/HTTP/Lucu/Resource.hs
Fixed build failure on recent GHC and other libraries
[Lucu.git] / Network / HTTP / Lucu / Resource.hs
index 44db0dc2a36633787f87707a0cc96ec72063cfd1..15b211fba6d17872dc6201a55d8a69bdfd42c326 100644 (file)
@@ -1,4 +1,4 @@
--- #prune
+{-# OPTIONS_HADDOCK prune #-}
 
 -- |This is the Resource Monad; monadic actions to define the behavior
 -- of each resources. The 'Resource' Monad is a kind of 'Prelude.IO'
@@ -60,8 +60,9 @@
 
 module Network.HTTP.Lucu.Resource
     (
-    -- * Monad
-    Resource
+    -- * Types
+      Resource
+    , FormData(..)
     , runRes -- private
 
     -- * Actions
@@ -73,6 +74,8 @@ module Network.HTTP.Lucu.Resource
     , getConfig
     , getRemoteAddr
     , getRemoteAddr'
+    , getRemoteHost
+    , getRemoteCertificate
     , getRequest
     , getMethod
     , getRequestURI
@@ -85,6 +88,7 @@ module Network.HTTP.Lucu.Resource
     , getAcceptEncoding
     , isEncodingAcceptable
     , getContentType
+    , getAuthorization
 
     -- ** Finding an entity
 
@@ -117,6 +121,7 @@ module Network.HTTP.Lucu.Resource
     , setContentType
     , setLocation
     , setContentEncoding
+    , setWWWAuthenticate
 
     -- ** Writing a response body
 
@@ -133,12 +138,17 @@ module Network.HTTP.Lucu.Resource
 
 import           Control.Concurrent.STM
 import           Control.Monad.Reader
-import           Data.Bits
-import           Data.ByteString.Base (LazyByteString)
-import qualified Data.ByteString.Lazy.Char8 as B
+import qualified Data.ByteString as Strict (ByteString)
+import qualified Data.ByteString.Lazy as Lazy (ByteString)
+import qualified Data.ByteString.Char8 as C8 hiding (ByteString)
+import qualified Data.ByteString.Lazy.Char8 as L8 hiding (ByteString)
+import           Data.Char
 import           Data.List
 import           Data.Maybe
+import           Data.Time
+import qualified Data.Time.HTTP as HTTP
 import           Network.HTTP.Lucu.Abortion
+import           Network.HTTP.Lucu.Authorization
 import           Network.HTTP.Lucu.Config
 import           Network.HTTP.Lucu.ContentCoding
 import           Network.HTTP.Lucu.DefaultPage
@@ -146,16 +156,16 @@ import           Network.HTTP.Lucu.ETag
 import qualified Network.HTTP.Lucu.Headers as H
 import           Network.HTTP.Lucu.HttpVersion
 import           Network.HTTP.Lucu.Interaction
+import           Network.HTTP.Lucu.MultipartForm
 import           Network.HTTP.Lucu.Parser
 import           Network.HTTP.Lucu.Postprocess
-import           Network.HTTP.Lucu.RFC1123DateTime
 import           Network.HTTP.Lucu.Request
 import           Network.HTTP.Lucu.Response
 import           Network.HTTP.Lucu.MIMEType
 import           Network.HTTP.Lucu.Utils
-import           Network.Socket
-import           Network.URI
-import           System.Time
+import           Network.Socket hiding (accept)
+import           Network.URI hiding (path)
+import           OpenSSL.X509
 
 -- |The 'Resource' monad. This monad implements
 -- 'Control.Monad.Trans.MonadIO' so it can do any 'Prelude.IO'
@@ -202,19 +212,29 @@ getRemoteAddr = do itr <- getInteraction
 -- you want a 'Network.Socket.SockAddr' instead of 'Prelude.String',
 -- use 'getRemoteAddr'.
 getRemoteAddr' :: Resource String
-getRemoteAddr' = do addr <- getRemoteAddr
-                    case addr of
-                      -- Network.Socket は IPv6 を考慮してゐないやうだ…
-                      (SockAddrInet _ v4addr)
-                          -> let b1 = (v4addr `shiftR` 24) .&. 0xFF
-                                 b2 = (v4addr `shiftR` 16) .&. 0xFF
-                                 b3 = (v4addr `shiftR`  8) .&. 0xFF
-                                 b4 =  v4addr              .&. 0xFF
-                             in
-                               return $ concat $ intersperse "." $ map show [b1, b2, b3, b4]
-                      (SockAddrUnix path)
-                          -> return path
+getRemoteAddr' = do addr          <- getRemoteAddr
+                    (Just str, _) <- liftIO $! getNameInfo [NI_NUMERICHOST] True False addr
+                    return str
 
+-- |Resolve an address to the remote host.
+getRemoteHost :: Resource String
+getRemoteHost = do addr          <- getRemoteAddr
+                   (Just str, _) <- liftIO $! getNameInfo [] True False addr
+                   return str
+
+-- | Return the X.509 certificate of the client, or 'Nothing' if:
+--
+--   * This request didn't came through an SSL stream.
+--
+--   * The client didn't send us its certificate.
+--
+--   * The 'OpenSSL.Session.VerificationMode' of
+--     'OpenSSL.Session.SSLContext' in
+--     'Network.HTTP.Lucu.Config.SSLConfig' has not been set to
+--     'OpenSSL.Session.VerifyPeer'.
+getRemoteCertificate :: Resource (Maybe X509)
+getRemoteCertificate = do itr <- getInteraction
+                          return $! itrRemoteCert itr
 
 -- |Get the 'Network.HTTP.Lucu.Request.Request' value which represents
 -- the request header. In general you don't have to use this action.
@@ -265,14 +285,15 @@ getResourcePath = do itr <- getInteraction
                      return $! fromJust $! itrResourcePath itr
 
 
--- |This is an analogy of CGI PATH_INFO. Its result is always @[]@ if
--- the 'Network.HTTP.Lucu.Resource.Tree.ResourceDef' is not
--- greedy. See 'getResourcePath'.
+-- |This is an analogy of CGI PATH_INFO. The result is
+-- URI-unescaped. It is always @[]@ if the
+-- 'Network.HTTP.Lucu.Resource.Tree.ResourceDef' is not greedy. See
+-- 'getResourcePath'.
 getPathInfo :: Resource [String]
 getPathInfo = do rsrcPath <- getResourcePath
-                 reqURI   <- getRequestURI
-                 let reqPathStr = uriPath reqURI
-                     reqPath    = [x | x <- splitBy (== '/') reqPathStr, x /= ""]
+                 uri      <- getRequestURI
+                 let reqPathStr = uriPath uri
+                     reqPath    = [unEscapeString x | x <- splitBy (== '/') reqPathStr, x /= ""]
                  -- rsrcPath と reqPath の共通する先頭部分を reqPath か
                  -- ら全部取り除くと、それは PATH_INFO のやうなものにな
                  -- る。rsrcPath は全部一致してゐるに決まってゐる(でな
@@ -280,18 +301,32 @@ getPathInfo = do rsrcPath <- getResourcePath
                  -- rsrcPath の長さの分だけ削除すれば良い。
                  return $! drop (length rsrcPath) reqPath
 
--- | Assume the query part of request URI as
--- application\/x-www-form-urlencoded, and parse it. This action
--- doesn't parse the request body. See 'inputForm'.
-getQueryForm :: Resource [(String, String)]
-getQueryForm = do reqURI <- getRequestURI
-                  return $! parseWWWFormURLEncoded $ uriQuery reqURI
+-- |Assume the query part of request URI as
+-- application\/x-www-form-urlencoded, and parse it to pairs of
+-- @(name, formData)@. This action doesn't parse the request body. See
+-- 'inputForm'.
+getQueryForm :: Resource [(String, FormData)]
+getQueryForm = liftM parse' getRequestURI
+    where
+      parse' = map toPairWithFormData .
+               parseWWWFormURLEncoded .
+               snd .
+               splitAt 1 .
+               uriQuery
+
+toPairWithFormData :: (String, String) -> (String, FormData)
+toPairWithFormData (name, value)
+    = let fd = FormData {
+                 fdFileName = Nothing
+               , fdContent  = L8.pack value
+               }
+      in (name, fd)
 
 -- |Get a value of given request header. Comparison of header name is
 -- case-insensitive. Note that this action is not intended to be used
 -- so frequently: there should be actions like 'getContentType' for
 -- every common headers.
-getHeader :: String -> Resource (Maybe String)
+getHeader :: Strict.ByteString -> Resource (Maybe Strict.ByteString)
 getHeader name = name `seq`
                  do req <- getRequest
                     return $! H.getHeader name req
@@ -299,22 +334,22 @@ getHeader name = name `seq`
 -- |Get a list of 'Network.HTTP.Lucu.MIMEType.MIMEType' enumerated on
 -- header \"Accept\".
 getAccept :: Resource [MIMEType]
-getAccept = do acceptM <- getHeader "Accept"
+getAccept = do acceptM <- getHeader (C8.pack "Accept")
                case acceptM of
                  Nothing 
                      -> return []
                  Just accept
-                     -> case parseStr mimeTypeListP accept of
+                     -> case parse mimeTypeListP (L8.fromChunks [accept]) of
                           (# Success xs, _ #) -> return xs
                           (# _         , _ #) -> abort BadRequest []
-                                                 (Just $ "Unparsable Accept: " ++ accept)
+                                                 (Just $ "Unparsable Accept: " ++ C8.unpack accept)
 
 -- |Get a list of @(contentCoding, qvalue)@ enumerated on header
 -- \"Accept-Encoding\". The list is sorted in descending order by
 -- qvalue.
 getAcceptEncoding :: Resource [(String, Maybe Double)]
 getAcceptEncoding
-    = do accEncM <- getHeader "Accept-Encoding"
+    = do accEncM <- getHeader (C8.pack "Accept-Encoding")
          case accEncM of
            Nothing
                -- HTTP/1.0 には Accept-Encoding が無い場合の規定が無い
@@ -325,36 +360,52 @@ getAcceptEncoding
                      case ver of
                        HttpVersion 1 0 -> return [("identity", Nothing)]
                        HttpVersion 1 1 -> return [("*"       , Nothing)]
-           Just ""
-               -- identity のみが許される。
-               -> return [("identity", Nothing)]
-           Just accEnc
-               -> case parseStr acceptEncodingListP accEnc of
-                    (# Success x, _ #) -> return $ reverse $ sortBy orderAcceptEncodings x
-                    (# _        , _ #) -> abort BadRequest []
-                                          (Just $ "Unparsable Accept-Encoding: " ++ accEnc)
+                       _               -> undefined
+           Just value
+               -> if C8.null value then
+                      -- identity のみが許される。
+                      return [("identity", Nothing)]
+                  else
+                      case parse acceptEncodingListP (L8.fromChunks [value]) of
+                        (# Success x, _ #) -> return $ reverse $ sortBy orderAcceptEncodings x
+                        (# _        , _ #) -> abort BadRequest []
+                                              (Just $ "Unparsable Accept-Encoding: " ++ C8.unpack value)
 
 -- |Check whether a given content-coding is acceptable.
 isEncodingAcceptable :: String -> Resource Bool
 isEncodingAcceptable coding
     = do accList <- getAcceptEncoding
          return (flip any accList $ \ (c, q) ->
-                     (c == "*" || c `noCaseEq` coding) && q /= Just 0)
+                     (c == "*" || C8.pack c `H.noCaseEq` C8.pack coding) && q /= Just 0)
 
 
 -- |Get the header \"Content-Type\" as
 -- 'Network.HTTP.Lucu.MIMEType.MIMEType'.
 getContentType :: Resource (Maybe MIMEType)
 getContentType
-    = do cTypeM <- getHeader "Content-Type"
+    = do cTypeM <- getHeader (C8.pack "Content-Type")
          case cTypeM of
            Nothing
                -> return Nothing
            Just cType
-               -> case parseStr mimeTypeP cType of
+               -> case parse mimeTypeP (L8.fromChunks [cType]) of
                     (# Success t, _ #) -> return $ Just t
                     (# _        , _ #) -> abort BadRequest []
-                                          (Just $ "Unparsable Content-Type: " ++ cType)
+                                          (Just $ "Unparsable Content-Type: " ++ C8.unpack cType)
+
+
+-- |Get the header \"Authorization\" as
+-- 'Network.HTTP.Lucu.Authorization.AuthCredential'.
+getAuthorization :: Resource (Maybe AuthCredential)
+getAuthorization
+    = do authM <- getHeader (C8.pack "Authorization")
+         case authM of
+           Nothing
+               -> return Nothing
+           Just auth
+               -> case parse authCredentialP (L8.fromChunks [auth]) of
+                    (# Success a, _ #) -> return $ Just a
+                    (# _        , _ #) -> return Nothing
 
 
 {- ExaminingRequest 時に使用するアクション群 -}
@@ -362,7 +413,7 @@ getContentType
 -- |Tell the system that the 'Resource' found an entity for the
 -- request URI. If this is a GET or HEAD request, a found entity means
 -- a datum to be replied. If this is a PUT or DELETE request, it means
--- a datum which was stored for the URI up to now. It is an error to
+-- a datum which was stored for the URI until now. It is an error to
 -- compute 'foundEntity' if this is a POST request.
 --
 -- Computation of 'foundEntity' performs \"If-Match\" test or
@@ -373,17 +424,17 @@ getContentType
 --
 -- If this is a GET or HEAD request, 'foundEntity' automatically puts
 -- \"ETag\" and \"Last-Modified\" headers into the response.
-foundEntity :: ETag -> ClockTime -> Resource ()
+foundEntity :: ETag -> UTCTime -> Resource ()
 foundEntity tag timeStamp
     = tag `seq` timeStamp `seq`
       do driftTo ExaminingRequest
 
          method <- getMethod
          when (method == GET || method == HEAD)
-                  $ setHeader' "Last-Modified" $! formatHTTPDateTime timeStamp
+                  $ setHeader' (C8.pack "Last-Modified") (C8.pack $ HTTP.format timeStamp)
          when (method == POST)
                   $ abort InternalServerError []
-                        (Just "Illegal computation of foundEntity for POST request.")
+                        (Just "Illegal computation of foundEntity for POST request.")
          foundETag tag
 
          driftTo GettingBody
@@ -402,25 +453,27 @@ foundETag tag
       
          method <- getMethod
          when (method == GET || method == HEAD)
-                  $ setHeader' "ETag" $! show tag
+                  $ setHeader' (C8.pack "ETag") (C8.pack $ show tag)
          when (method == POST)
                   $ abort InternalServerError []
                         (Just "Illegal computation of foundETag for POST request.")
 
          -- If-Match があればそれを見る。
-         ifMatch <- getHeader "If-Match"
+         ifMatch <- getHeader (C8.pack "If-Match")
          case ifMatch of
-           Nothing   -> return ()
-           Just "*"  -> return ()
-           Just list -> case parseStr eTagListP list of
-                          (# Success tags, _ #)
-                              -- tags の中に一致するものが無ければ
-                              -- PreconditionFailed で終了。
-                              -> when (not $ any (== tag) tags)
-                                 $ abort PreconditionFailed []
-                                       $! Just ("The entity tag doesn't match: " ++ list)
-                          (# _, _ #)
-                              -> abort BadRequest [] $! Just ("Unparsable If-Match: " ++ fromJust ifMatch)
+           Nothing    -> return ()
+           Just value -> if value == C8.pack "*" then
+                             return ()
+                         else
+                             case parse eTagListP (L8.fromChunks [value]) of
+                               (# Success tags, _ #)
+                                 -- tags の中に一致するものが無ければ
+                                 -- PreconditionFailed で終了。
+                                 -> when (not $ any (== tag) tags)
+                                    $ abort PreconditionFailed []
+                                          $! Just ("The entity tag doesn't match: " ++ C8.unpack value)
+                               (# _, _ #)
+                                   -> abort BadRequest [] $! Just ("Unparsable If-Match: " ++ C8.unpack value)
 
          let statusForNoneMatch = if method == GET || method == HEAD then
                                       NotModified
@@ -428,16 +481,18 @@ foundETag tag
                                       PreconditionFailed
 
          -- If-None-Match があればそれを見る。
-         ifNoneMatch <- getHeader "If-None-Match"
+         ifNoneMatch <- getHeader (C8.pack "If-None-Match")
          case ifNoneMatch of
-           Nothing   -> return ()
-           Just "*"  -> abort statusForNoneMatch [] $! Just ("The entity tag matches: *")
-           Just list -> case parseStr eTagListP list of
-                          (# Success tags, _ #)
-                              -> when (any (== tag) tags)
-                                 $ abort statusForNoneMatch [] $! Just ("The entity tag matches: " ++ list)
-                          (# _, _ #)
-                              -> abort BadRequest [] $! Just ("Unparsable If-None-Match: " ++ list)
+           Nothing    -> return ()
+           Just value -> if value == C8.pack "*" then
+                             abort statusForNoneMatch [] $! Just ("The entity tag matches: *")
+                         else
+                             case parse eTagListP (L8.fromChunks [value]) of
+                               (# Success tags, _ #)
+                                   -> when (any (== tag) tags)
+                                      $ abort statusForNoneMatch [] $! Just ("The entity tag matches: " ++ C8.unpack value)
+                               (# _, _ #)
+                                   -> abort BadRequest [] $! Just ("Unparsable If-None-Match: " ++ C8.unpack value)
 
          driftTo GettingBody
 
@@ -451,14 +506,14 @@ foundETag tag
 --
 -- This action is not preferred. You should use 'foundEntity' whenever
 -- possible.
-foundTimeStamp :: ClockTime -> Resource ()
+foundTimeStamp :: UTCTime -> Resource ()
 foundTimeStamp timeStamp
     = timeStamp `seq`
       do driftTo ExaminingRequest
 
          method <- getMethod
          when (method == GET || method == HEAD)
-                  $ setHeader' "Last-Modified" $! formatHTTPDateTime timeStamp
+                  $ setHeader' (C8.pack "Last-Modified") (C8.pack $ HTTP.format timeStamp)
          when (method == POST)
                   $ abort InternalServerError []
                         (Just "Illegal computation of foundTimeStamp for POST request.")
@@ -469,25 +524,25 @@ foundTimeStamp timeStamp
                                        PreconditionFailed
 
          -- If-Modified-Since があればそれを見る。
-         ifModSince <- getHeader "If-Modified-Since"
+         ifModSince <- getHeader (C8.pack "If-Modified-Since")
          case ifModSince of
-           Just str -> case parseHTTPDateTime str of
+           Just str -> case HTTP.parse (C8.unpack str) of
                          Just lastTime
                              -> when (timeStamp <= lastTime)
                                 $ abort statusForIfModSince []
-                                      $! Just ("The entity has not been modified since " ++ str)
+                                      $! Just ("The entity has not been modified since " ++ C8.unpack str)
                          Nothing
                              -> return () -- 不正な時刻は無視
            Nothing  -> return ()
 
          -- If-Unmodified-Since があればそれを見る。
-         ifUnmodSince <- getHeader "If-Unmodified-Since"
+         ifUnmodSince <- getHeader (C8.pack "If-Unmodified-Since")
          case ifUnmodSince of
-           Just str -> case parseHTTPDateTime str of
+           Just str -> case HTTP.parse (C8.unpack str) of
                          Just lastTime
                              -> when (timeStamp > lastTime)
                                 $ abort PreconditionFailed []
-                                      $! Just  ("The entity has not been modified since " ++ str)
+                                      $! Just  ("The entity has not been modified since " ++ C8.unpack str)
                          Nothing
                              -> return () -- 不正な時刻は無視
            Nothing  -> return ()
@@ -513,7 +568,7 @@ foundNoEntity msgM
 
          -- エンティティが存在しないと云ふ事は、"*" も含めたどのやうな
          -- If-Match: 條件も滿たさない。
-         ifMatch <- getHeader "If-Match"
+         ifMatch <- getHeader (C8.pack "If-Match")
          when (ifMatch /= Nothing)
                   $ abort PreconditionFailed [] msgM
 
@@ -538,17 +593,16 @@ foundNoEntity msgM
 -- use it whenever possible.
 input :: Int -> Resource String
 input limit = limit `seq`
-              inputLBS limit >>= return . B.unpack
+              inputLBS limit >>= return . L8.unpack
 
 
 -- | This is mostly the same as 'input' but is more
--- efficient. 'inputLBS' returns a
--- 'Data.ByteString.Base.LazyByteString' but it's not really lazy:
--- reading from the socket just happens at the computation of
--- 'inputLBS', not at the evaluation of the
--- 'Data.ByteString.Base.LazyByteString'. The same goes for
+-- efficient. 'inputLBS' returns a 'Data.ByteString.Lazy.ByteString'
+-- but it's not really lazy: reading from the socket just happens at
+-- the computation of 'inputLBS', not at the evaluation of the
+-- 'Data.ByteString.Lazy.ByteString'. The same goes for
 -- 'inputChunkLBS'.
-inputLBS :: Int -> Resource LazyByteString
+inputLBS :: Int -> Resource Lazy.ByteString
 inputLBS limit
     = limit `seq`
       do driftTo GettingBody
@@ -558,17 +612,17 @@ inputLBS limit
                         askForInput itr
                     else
                         do driftTo DecidingHeader
-                           return B.empty
+                           return L8.empty
          return chunk
     where
-      askForInput :: Interaction -> Resource LazyByteString
+      askForInput :: Interaction -> Resource Lazy.ByteString
       askForInput itr
           = itr `seq`
-            do let defaultLimit = cnfMaxEntityLength $ itrConfig itr
-                   actualLimit  = if limit <= 0 then
-                                      defaultLimit
-                                  else
-                                      limit
+            do let confLimit   = cnfMaxEntityLength $ itrConfig itr
+                   actualLimit = if limit <= 0 then
+                                     confLimit
+                                 else
+                                     limit
                when (actualLimit <= 0)
                         $ fail ("inputLBS: limit must be positive: " ++ show actualLimit)
                -- Reader にリクエスト
@@ -584,7 +638,7 @@ inputLBS limit
                chunk <- liftIO $! atomically
                         $! do chunk       <- readItr itr itrReceivedBody id
                               chunkIsOver <- readItr itr itrReqChunkIsOver id
-                              if B.length chunk < fromIntegral actualLimit then
+                              if L8.length chunk < fromIntegral actualLimit then
                                   -- 要求された量に滿たなくて、まだ殘り
                                   -- があるなら再試行。
                                   unless chunkIsOver
@@ -597,7 +651,7 @@ inputLBS limit
                                              $ tooLarge actualLimit
                               -- 成功。itr 内にチャンクを置いたままにす
                               -- るとメモリの無駄になるので除去。
-                              writeItr itr itrReceivedBody B.empty
+                              writeItr itr itrReceivedBody L8.empty
                               return chunk
                driftTo DecidingHeader
                return chunk
@@ -623,12 +677,12 @@ inputLBS limit
 -- should use it whenever possible.
 inputChunk :: Int -> Resource String
 inputChunk limit = limit `seq`
-                   inputChunkLBS limit >>= return . B.unpack
+                   inputChunkLBS limit >>= return . L8.unpack
 
 
 -- | This is mostly the same as 'inputChunk' but is more
 -- efficient. See 'inputLBS'.
-inputChunkLBS :: Int -> Resource LazyByteString
+inputChunkLBS :: Int -> Resource Lazy.ByteString
 inputChunkLBS limit
     = limit `seq`
       do driftTo GettingBody
@@ -638,15 +692,15 @@ inputChunkLBS limit
                         askForInput itr
                     else
                         do driftTo DecidingHeader
-                           return B.empty
+                           return L8.empty
          return chunk
     where
-      askForInput :: Interaction -> Resource LazyByteString
+      askForInput :: Interaction -> Resource Lazy.ByteString
       askForInput itr
           = itr `seq`
-            do let defaultLimit = cnfMaxEntityLength $! itrConfig itr
-                   actualLimit  = if limit < 0 then
-                                      defaultLimit
+            do let confLimit   = cnfMaxEntityLength $! itrConfig itr
+                   actualLimit = if limit < 0 then
+                                      confLimit
                                   else
                                       limit
                when (actualLimit <= 0)
@@ -660,28 +714,25 @@ inputChunkLBS limit
                         $ do chunk <- readItr itr itrReceivedBody id
                              -- 要求された量に滿たなくて、まだ殘りがあ
                              -- るなら再試行。
-                             when (B.length chunk < fromIntegral actualLimit)
+                             when (L8.length chunk < fromIntegral actualLimit)
                                       $ do chunkIsOver <- readItr itr itrReqChunkIsOver id
                                            unless chunkIsOver
                                                       $ retry
                              -- 成功
-                             writeItr itr itrReceivedBody B.empty
+                             writeItr itr itrReceivedBody L8.empty
                              return chunk
-               when (B.null chunk)
+               when (L8.null chunk)
                         $ driftTo DecidingHeader
                return chunk
 
 -- | Computation of @'inputForm' limit@ attempts to read the request
 -- body with 'input' and parse it as
--- application\/x-www-form-urlencoded. If the request header
--- \"Content-Type\" is not application\/x-www-form-urlencoded,
--- 'inputForm' makes 'Resource' abort with status \"415 Unsupported
--- Media Type\". If the request has no \"Content-Type\", it aborts
--- with \"400 Bad Request\".
---
--- This action should also support multipart\/form-data somehow, but
--- it is not (yet) done.
-inputForm :: Int -> Resource [(String, String)]
+-- application\/x-www-form-urlencoded or multipart\/form-data. If the
+-- request header \"Content-Type\" is neither of them, 'inputForm'
+-- makes 'Resource' abort with status \"415 Unsupported Media
+-- Type\". If the request has no \"Content-Type\", it aborts with
+-- \"400 Bad Request\".
+inputForm :: Int -> Resource [(String, FormData)]
 inputForm limit
     = limit `seq` 
       do cTypeM <- getContentType
@@ -690,19 +741,26 @@ inputForm limit
                -> abort BadRequest [] (Just "Missing Content-Type")
            Just (MIMEType "application" "x-www-form-urlencoded" _)
                -> readWWWFormURLEncoded
-           Just (MIMEType "multipart" "form-data" _)
-               -> readMultipartFormData
+           Just (MIMEType "multipart" "form-data" params)
+               -> readMultipartFormData params
            Just cType
                -> abort UnsupportedMediaType [] (Just $! "Unsupported media type: "
                                                           ++ show cType)
     where
       readWWWFormURLEncoded
-          = do src <- input limit
-               return $ parseWWWFormURLEncoded src
-
-      readMultipartFormData -- FIXME: 未對應
-          = abort UnsupportedMediaType []
-            (Just $! "Sorry, inputForm does not currently support multipart/form-data.")
+          = liftM (map toPairWithFormData . parseWWWFormURLEncoded) (input limit)
+
+      readMultipartFormData params
+          = do case find ((== "boundary") . map toLower . fst) params of
+                 Nothing
+                     -> abort BadRequest [] (Just "Missing boundary of multipart/form-data")
+                 Just (_, boundary)
+                     -> do src <- inputLBS limit
+                           case parse (multipartFormP boundary) src of
+                             (# Success formList, _ #)
+                                 -> return formList
+                             (# _, _ #)
+                                 -> abort BadRequest [] (Just "Unparsable multipart/form-data")
 
 -- | This is just a constant @-1@. It's better to say @'input'
 -- 'defaultLimit'@ than to say @'input' (-1)@ but these are exactly
@@ -740,13 +798,13 @@ setStatus code
 -- 20 bytes long. In this case the client shall only accept the first
 -- 10 bytes of response body and thinks that the residual 10 bytes is
 -- a part of header of the next response.
-setHeader :: String -> String -> Resource ()
+setHeader :: Strict.ByteString -> Strict.ByteString -> Resource ()
 setHeader name value
     = name `seq` value `seq`
       driftTo DecidingHeader >> setHeader' name value
          
 
-setHeader' :: String -> String -> Resource ()
+setHeader' :: Strict.ByteString -> Strict.ByteString -> Resource ()
 setHeader' name value
     = name `seq` value `seq`
       do itr <- getInteraction
@@ -772,13 +830,13 @@ redirect code uri
 -- \"Content-Type\" to @mType@.
 setContentType :: MIMEType -> Resource ()
 setContentType mType
-    = setHeader "Content-Type" $! show mType
+    = setHeader (C8.pack "Content-Type") (C8.pack $ show mType)
 
 -- | Computation of @'setLocation' uri@ sets the response header
 -- \"Location\" to @uri@.
 setLocation :: URI -> Resource ()
 setLocation uri
-    = setHeader "Location" $ uriToString id uri $ ""
+    = setHeader (C8.pack "Location") (C8.pack $ uriToString id uri $ "")
 
 -- |Computation of @'setContentEncoding' codings@ sets the response
 -- header \"Content-Encoding\" to @codings@.
@@ -788,7 +846,14 @@ setContentEncoding codings
          let tr = case ver of
                     HttpVersion 1 0 -> unnormalizeCoding
                     HttpVersion 1 1 -> id
-         setHeader "Content-Encoding" $ joinWith ", " $ map tr codings
+                    _               -> undefined
+         setHeader (C8.pack "Content-Encoding") (C8.pack $ joinWith ", " $ map tr codings)
+
+-- |Computation of @'setWWWAuthenticate' challenge@ sets the response
+-- header \"WWW-Authenticate\" to @challenge@.
+setWWWAuthenticate :: AuthChallenge -> Resource ()
+setWWWAuthenticate challenge
+    = setHeader (C8.pack "WWW-Authenticate") (C8.pack $ show challenge)
 
 
 {- DecidingBody 時に使用するアクション群 -}
@@ -801,11 +866,11 @@ setContentEncoding codings
 -- Note that 'outputLBS' is more efficient than 'output' so you should
 -- use it whenever possible.
 output :: String -> Resource ()
-output str = outputLBS $! B.pack str
+output str = outputLBS $! L8.pack str
 {-# INLINE output #-}
 
 -- | This is mostly the same as 'output' but is more efficient.
-outputLBS :: LazyByteString -> Resource ()
+outputLBS :: Lazy.ByteString -> Resource ()
 outputLBS str = do outputChunkLBS str
                    driftTo Done
 {-# INLINE outputLBS #-}
@@ -818,13 +883,13 @@ outputLBS str = do outputChunkLBS str
 -- Note that 'outputChunkLBS' is more efficient than 'outputChunk' so
 -- you should use it whenever possible.
 outputChunk :: String -> Resource ()
-outputChunk str = outputChunkLBS $! B.pack str
+outputChunk str = outputChunkLBS $! L8.pack str
 {-# INLINE outputChunk #-}
 
 -- | This is mostly the same as 'outputChunk' but is more efficient.
-outputChunkLBS :: LazyByteString -> Resource ()
-outputChunkLBS str
-    = str `seq`
+outputChunkLBS :: Lazy.ByteString -> Resource ()
+outputChunkLBS wholeChunk
+    = wholeChunk `seq`
       do driftTo DecidingBody
          itr <- getInteraction
          
@@ -837,32 +902,32 @@ outputChunkLBS str
                         readItr itr itrWillDiscardBody id
 
          unless (discardBody)
-                    $ sendChunks str limit
+                    $ sendChunks wholeChunk limit
 
-         unless (B.null str)
+         unless (L8.null wholeChunk)
                     $ liftIO $ atomically $
                       writeItr itr itrBodyIsNull False
     where
       -- チャンクの大きさは Config で制限されてゐる。もし例へば
-      -- "/dev/zero" を B.readFile して作った LazyByteString をそのまま
+      -- "/dev/zero" を L8.readFile して作った Lazy.ByteString をそのまま
       -- ResponseWriter に渡したりすると大變な事が起こる。何故なら
       -- ResponseWriter は Transfer-Encoding: chunked の時、ヘッダを書
       -- く爲にチャンクの大きさを測る。
-      sendChunks :: LazyByteString -> Int -> Resource ()
+      sendChunks :: Lazy.ByteString -> Int -> Resource ()
       sendChunks str limit
-          | B.null str = return ()
-          | otherwise  = do let (chunk, remaining) = B.splitAt (fromIntegral limit) str
-                            itr <- getInteraction
-                            liftIO $ atomically $ 
-                                   do buf <- readItr itr itrBodyToSend id
-                                      if B.null buf then
-                                          -- バッファが消化された
-                                          writeItr itr itrBodyToSend chunk
-                                        else
-                                          -- 消化されるのを待つ
-                                          retry
-                            -- 殘りのチャンクについて繰り返す
-                            sendChunks remaining limit
+          | L8.null str = return ()
+          | otherwise   = do let (chunk, remaining) = L8.splitAt (fromIntegral limit) str
+                             itr <- getInteraction
+                             liftIO $ atomically $ 
+                                    do buf <- readItr itr itrBodyToSend id
+                                       if L8.null buf then
+                                           -- バッファが消化された
+                                           writeItr itr itrBodyToSend chunk
+                                         else
+                                           -- 消化されるのを待つ
+                                           retry
+                             -- 殘りのチャンクについて繰り返す
+                             sendChunks remaining limit
 
 {-