X-Git-Url: http://git.cielonegro.org/gitweb.cgi?p=Rakka.git;a=blobdiff_plain;f=Rakka%2FResource.hs;h=a6fc01f492f1b4fcf27089f80c6e335cadeaee90;hp=a69a2242215c2603ccd6f7c79826df8d21ed21b7;hb=HEAD;hpb=7a4f13a3d483c950743e1ced001ade4406d239d3 diff --git a/Rakka/Resource.hs b/Rakka/Resource.hs index a69a224..a6fc01f 100644 --- a/Rakka/Resource.hs +++ b/Rakka/Resource.hs @@ -1,20 +1,44 @@ +{-# LANGUAGE + Arrows + , DoAndIfThenElse + , UnicodeSyntax + #-} module Rakka.Resource ( runIdempotentA + , runIdempotentA' + , runXmlA + , getEntityType , outputXmlPage + , outputXmlPage' + , outputXml + , getUserID ) where - -import Control.Arrow -import Control.Arrow.ArrowList +import qualified Codec.Binary.UTF8.String as UTF8 +import Control.Arrow +import Control.Arrow.ArrowList +import Control.Arrow.ListArrow +import Control.Arrow.Unicode import Control.Monad import Control.Monad.Trans +import Data.Monoid.Unicode +import qualified Data.Text as T import Network.HTTP.Lucu import Network.HTTP.Lucu.Utils import Network.URI hiding (path) -import Text.XML.HXT.Arrow.WriteDocument -import Text.XML.HXT.Arrow.XmlIOStateArrow -import Text.XML.HXT.DOM.TypeDefs -import Text.XML.HXT.DOM.XmlKeywords +import Prelude.Unicode +import Rakka.Authorization +import Rakka.Environment +import Rakka.Validation +import System.FilePath.Posix +import System.Log.Logger +import Text.XML.HXT.Arrow.ReadDocument +import Text.XML.HXT.Arrow.WriteDocument +import Text.XML.HXT.DOM.TypeDefs +import Text.XML.HXT.Arrow.XmlState + +logger :: String +logger = "Rakka.Resource" -- "/" ==> "/" @@ -22,27 +46,28 @@ import Text.XML.HXT.DOM.XmlKeywords -- "/foo/" ==> "/foo.html" -- "/foo.bar/" ==> "/foo.bar" -- "/foo.bar" ==> "/foo.bar" -canonicalizeURI :: Resource () -canonicalizeURI - = do uri <- getRequestURI - let newURI = uri { uriPath = "/" ++ joinWith "/" newPath } - newPath = case [x | x <- splitBy (== '/') (uriPath uri), x /= ""] of - [] -> [] - path -> case break (== '.') $ last path of - (_, "") -> let basePieces = reverse $ tail $ reverse path - lastPiece = last path - in - basePieces ++ [lastPiece ++ ".html"] - (_, _) -> path - when (uri /= newURI) +canonicalizeURI :: URI -> Resource () +canonicalizeURI baseURI + = do rPath <- return . uriPath =<< getRequestURI + let newURI = baseURI { uriPath = uriPath baseURI newPath } + newPath = foldl () "/" newPath' + newPath' = case [x | x <- splitBy (== '/') rPath, x /= ""] of + [] -> [] + path -> case break (== '.') $ last path of + (_, "") -> let basePieces = reverse $ tail $ reverse path + lastPiece = last path + in + basePieces ++ [lastPiece ++ ".html"] + (_, _) -> path + when (rPath /= newPath) $ abort MovedPermanently [("Location", uriToString id newURI $ "")] Nothing -runIdempotentA :: IOSArrow () (Resource c) -> Resource c -runIdempotentA a - = do canonicalizeURI +runIdempotentA :: URI -> IOSArrow () (Resource c) -> Resource c +runIdempotentA baseURI a + = do canonicalizeURI baseURI [rsrc] <- liftIO $ runX ( setErrorMsgHandler False fail >>> constA () @@ -52,6 +77,68 @@ runIdempotentA a rsrc +runIdempotentA' :: IOSArrow () (Resource c) -> Resource c +runIdempotentA' a + = do [rsrc] <- liftIO $ runX ( setErrorMsgHandler False fail + >>> + constA () + >>> + a + ) + rsrc + +runXmlA ∷ FilePath → IOSArrow XmlTree (Resource c) → Resource c +runXmlA schemaPath a + = do inputA ← getInputXmlA schemaPath + [rsrc] ← liftIO $ runX ( inputA + ⋙ + setErrorMsgHandler False fail + ⋙ + a + ) + rsrc + +-- well-formed でない時は 400 Bad Request になり、valid でない時は 422 +-- Unprocessable Entity になる。入力の型が XML でない時は 415 +-- Unsupported Media Type を返す。 +getInputXmlA ∷ FilePath → Resource (IOSArrow b XmlTree) +getInputXmlA schemaPath + = do reader ← getInputReader + validator ← getValidator schemaPath + return ( setErrorMsgHandler False (abort BadRequest [] ∘ Just) + ⋙ + reader + ⋙ + setErrorMsgHandler False (abort UnprocessableEntitiy [] ∘ Just) + ⋙ + validator + ) + +getInputReader ∷ Resource (IOSArrow b XmlTree) +getInputReader + = do mimeType ← getContentType + case mimeType of + Nothing + → getFailingReader BadRequest [] (Just "Missing Content-Type") + Just (MIMEType "text" "xml" _) + → getXmlReader + Just (MIMEType "application" "xml" _) + → getXmlReader + Just t + → getFailingReader UnsupportedMediaType [] + (Just $ "Unsupported media type: " ⊕ show t) + where + getXmlReader + = do req ← input defaultLimit + liftIO $ debugM logger req + return $ readString [ withValidate no + , withCheckNamespaces yes + , withRemoveWS yes + ] (UTF8.decodeString req) + getFailingReader code headers msg + = return $ proc _ -> abortA -< (code, (headers, msg)) + + getEntityType :: Resource MIMEType getEntityType = do uri <- getRequestURI @@ -62,24 +149,56 @@ getEntityType where extMap :: [(String, MIMEType)] extMap = [ ("html", read "application/xhtml+xml") + , ( "rdf", read "application/rss+xml" ) , ( "xml", read "text/xml" ) ] -outputXmlPage :: XmlTree -> IOSArrow XmlTree XmlTree -> Resource () -outputXmlPage tree toXHTML - = do mType <- getEntityType +outputXmlPage ∷ XmlTree → [(MIMEType, IOSArrow XmlTree XmlTree)] → Resource () +outputXmlPage tree formatters + = do mType ← getEntityType setContentType mType - let formatter = case mType of - MIMEType "application" "xhtml+xml" _ -> toXHTML - MIMEType "text" "xml" _ -> this - _ -> undefined - [resultStr] <- liftIO $ runX ( setErrorMsgHandler False fail - >>> - constA tree - >>> - formatter - >>> - writeDocumentToString [ (a_indent, v_1) ] - ) - output resultStr \ No newline at end of file + let formatter = case lookup mType formatters of + Just f → f + Nothing → this + [resultStr] ← liftIO $ + runX ( setErrorMsgHandler False fail + >>> + constA tree + >>> + formatter + >>> + writeDocumentToString + [ withIndent yes + , withXmlPi yes + ] + ) + output $ UTF8.encodeString resultStr + +outputXmlPage' :: XmlTree -> IOSArrow XmlTree XmlTree -> Resource () +outputXmlPage' tree toXHTML + = outputXmlPage tree [(MIMEType "application" "xhtml+xml" [], toXHTML)] + +outputXml ∷ XmlTree → Resource () +outputXml tree + = do setContentType (MIMEType "text" "xml" []) + let [xmlStr] = runLA ( writeDocumentToString + [ withIndent yes + , withXmlPi yes + ] + ) tree + output $ UTF8.encodeString xmlStr + +getUserID ∷ Environment → Resource (Maybe String) +getUserID env + = do auth ← getAuthorization + case auth of + Just (BasicAuthCredential userID password) + → do valid ← isValidPair (envAuthDB env) + (T.pack userID) + (T.pack password) + if valid then + return (Just userID) + else + return Nothing + _ → return Nothing