Beginning of Yesod middlewares, massive refactor, more to come
This commit is contained in:
parent
1013e20067
commit
2f7ac58189
@ -4,6 +4,7 @@
|
|||||||
{-# LANGUAGE MultiParamTypeClasses #-}
|
{-# LANGUAGE MultiParamTypeClasses #-}
|
||||||
{-# LANGUAGE DeriveDataTypeable #-}
|
{-# LANGUAGE DeriveDataTypeable #-}
|
||||||
{-# LANGUAGE Rank2Types #-}
|
{-# LANGUAGE Rank2Types #-}
|
||||||
|
{-# LANGUAGE OverloadedStrings #-}
|
||||||
|
|
||||||
module Yesod.Content
|
module Yesod.Content
|
||||||
( -- * Content
|
( -- * Content
|
||||||
@ -47,6 +48,7 @@ module Yesod.Content
|
|||||||
|
|
||||||
import Data.Maybe (mapMaybe)
|
import Data.Maybe (mapMaybe)
|
||||||
import qualified Data.ByteString as B
|
import qualified Data.ByteString as B
|
||||||
|
import qualified Data.ByteString.Char8 as S8
|
||||||
import qualified Data.ByteString.Lazy as L
|
import qualified Data.ByteString.Lazy as L
|
||||||
import Data.Text.Lazy (Text, pack)
|
import Data.Text.Lazy (Text, pack)
|
||||||
import qualified Data.Text as T
|
import qualified Data.Text as T
|
||||||
@ -63,6 +65,7 @@ import Data.Monoid (mempty)
|
|||||||
|
|
||||||
import Text.Hamlet (Html)
|
import Text.Hamlet (Html)
|
||||||
import Text.Blaze.Renderer.Utf8 (renderHtmlBuilder)
|
import Text.Blaze.Renderer.Utf8 (renderHtmlBuilder)
|
||||||
|
import Data.String (IsString (fromString))
|
||||||
|
|
||||||
data Content = ContentBuilder Builder (Maybe Int) -- ^ The content and optional content length.
|
data Content = ContentBuilder Builder (Maybe Int) -- ^ The content and optional content length.
|
||||||
| ContentEnum (forall a. Enumerator Builder IO a)
|
| ContentEnum (forall a. Enumerator Builder IO a)
|
||||||
@ -72,6 +75,9 @@ data Content = ContentBuilder Builder (Maybe Int) -- ^ The content and optional
|
|||||||
emptyContent :: Content
|
emptyContent :: Content
|
||||||
emptyContent = ContentBuilder mempty $ Just 0
|
emptyContent = ContentBuilder mempty $ Just 0
|
||||||
|
|
||||||
|
instance IsString Content where
|
||||||
|
fromString = toContent
|
||||||
|
|
||||||
-- | Anything which can be converted into 'Content'. Most of the time, you will
|
-- | Anything which can be converted into 'Content'. Most of the time, you will
|
||||||
-- want to use the 'ContentBuilder' constructor. An easier approach will be to use
|
-- want to use the 'ContentBuilder' constructor. An easier approach will be to use
|
||||||
-- a pre-defined 'toContent' function, such as converting your data into a lazy
|
-- a pre-defined 'toContent' function, such as converting your data into a lazy
|
||||||
@ -131,7 +137,7 @@ instance HasReps ChooseRep where
|
|||||||
chooseRep = id
|
chooseRep = id
|
||||||
|
|
||||||
instance HasReps () where
|
instance HasReps () where
|
||||||
chooseRep = defChooseRep [(typePlain, const $ return $ toContent "")]
|
chooseRep = defChooseRep [(typePlain, const $ return $ toContent B.empty)]
|
||||||
|
|
||||||
instance HasReps (ContentType, Content) where
|
instance HasReps (ContentType, Content) where
|
||||||
chooseRep = const . return
|
chooseRep = const . return
|
||||||
@ -165,7 +171,7 @@ newtype RepXml = RepXml Content
|
|||||||
instance HasReps RepXml where
|
instance HasReps RepXml where
|
||||||
chooseRep (RepXml c) _ = return (typeXml, c)
|
chooseRep (RepXml c) _ = return (typeXml, c)
|
||||||
|
|
||||||
type ContentType = String
|
type ContentType = B.ByteString
|
||||||
|
|
||||||
typeHtml :: ContentType
|
typeHtml :: ContentType
|
||||||
typeHtml = "text/html; charset=utf-8"
|
typeHtml = "text/html; charset=utf-8"
|
||||||
@ -214,8 +220,8 @@ typeOctet = "application/octet-stream"
|
|||||||
--
|
--
|
||||||
-- For example, \"text/html; charset=utf-8\" is commonly used to specify the
|
-- For example, \"text/html; charset=utf-8\" is commonly used to specify the
|
||||||
-- character encoding for HTML data. This function would return \"text/html\".
|
-- character encoding for HTML data. This function would return \"text/html\".
|
||||||
simpleContentType :: String -> String
|
simpleContentType :: B.ByteString -> B.ByteString
|
||||||
simpleContentType = fst . span (/= ';')
|
simpleContentType = S8.takeWhile (/= ';')
|
||||||
|
|
||||||
-- | Format a 'UTCTime' in W3 format.
|
-- | Format a 'UTCTime' in W3 format.
|
||||||
formatW3 :: UTCTime -> String
|
formatW3 :: UTCTime -> String
|
||||||
|
|||||||
236
Yesod/Core.hs
236
Yesod/Core.hs
@ -24,6 +24,7 @@ module Yesod.Core
|
|||||||
, AuthResult (..)
|
, AuthResult (..)
|
||||||
-- * Misc
|
-- * Misc
|
||||||
, yesodVersion
|
, yesodVersion
|
||||||
|
, yesodRender
|
||||||
#if TEST
|
#if TEST
|
||||||
, coreTestSuite
|
, coreTestSuite
|
||||||
#endif
|
#endif
|
||||||
@ -45,7 +46,7 @@ import qualified Data.ByteString.Char8 as S8
|
|||||||
import qualified Data.ByteString.Lazy as L
|
import qualified Data.ByteString.Lazy as L
|
||||||
import Data.Monoid
|
import Data.Monoid
|
||||||
import Control.Monad.Trans.Writer
|
import Control.Monad.Trans.Writer
|
||||||
import Control.Monad.Trans.State hiding (get)
|
import Control.Monad.Trans.State hiding (get, put)
|
||||||
import Text.Hamlet
|
import Text.Hamlet
|
||||||
import Text.Cassius
|
import Text.Cassius
|
||||||
import Text.Julius
|
import Text.Julius
|
||||||
@ -53,6 +54,20 @@ import Web.Routes
|
|||||||
import Text.Blaze (preEscapedLazyText)
|
import Text.Blaze (preEscapedLazyText)
|
||||||
import Data.Text.Lazy.Builder (toLazyText)
|
import Data.Text.Lazy.Builder (toLazyText)
|
||||||
import Data.Text.Lazy.Encoding (encodeUtf8)
|
import Data.Text.Lazy.Encoding (encodeUtf8)
|
||||||
|
import Data.Maybe (fromMaybe)
|
||||||
|
import System.Random (randomR, newStdGen)
|
||||||
|
import Control.Arrow (first, (***))
|
||||||
|
import qualified Network.Wai.Parse as NWP
|
||||||
|
import Data.ByteString (ByteString)
|
||||||
|
import Data.Enumerator (Iteratee, ($$), run_)
|
||||||
|
import Control.Concurrent.MVar (MVar, takeMVar, putMVar, newMVar)
|
||||||
|
import Control.Monad.IO.Class (liftIO)
|
||||||
|
import Control.Applicative ((<$>))
|
||||||
|
import Web.Cookie (parseCookies, SetCookie (..), renderSetCookie)
|
||||||
|
import qualified Data.Map as Map
|
||||||
|
import Control.Monad (guard)
|
||||||
|
import Data.Serialize
|
||||||
|
import Data.Time
|
||||||
|
|
||||||
#if TEST
|
#if TEST
|
||||||
import Test.Framework (testGroup, Test)
|
import Test.Framework (testGroup, Test)
|
||||||
@ -69,18 +84,24 @@ import qualified Data.Text.Encoding
|
|||||||
#define HAMLET $hamlet
|
#define HAMLET $hamlet
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
{- FIXME
|
||||||
|
class YesodDispatcher y where
|
||||||
|
dispatchSubsite :: y -> Key -> [String] -> Maybe Application
|
||||||
|
-}
|
||||||
|
|
||||||
-- | This class is automatically instantiated when you use the template haskell
|
-- | This class is automatically instantiated when you use the template haskell
|
||||||
-- mkYesod function. You should never need to deal with it directly.
|
-- mkYesod function. You should never need to deal with it directly.
|
||||||
class Eq (Route y) => YesodSite y where
|
class Eq (Route y) => YesodSite y where
|
||||||
getSite :: Site (Route y) (Method -> Maybe (GHandler y y ChooseRep))
|
getSite :: Site (Route y) (Method -> Maybe (GHandler y y ChooseRep))
|
||||||
|
getSite' :: y -> Site (Route y) (Method -> Maybe (GHandler y y ChooseRep))
|
||||||
|
getSite' _ = getSite
|
||||||
|
|
||||||
type Method = String
|
type Method = String
|
||||||
|
|
||||||
-- | Same as 'YesodSite', but for subsites. Once again, users should not need
|
-- | Same as 'YesodSite', but for subsites. Once again, users should not need
|
||||||
-- to deal with it directly, as the mkYesodSub creates instances appropriately.
|
-- to deal with it directly, as mkYesodSub creates instances appropriately.
|
||||||
class Eq (Route s) => YesodSubSite s y where
|
class Eq (Route s) => YesodSubSite s y where
|
||||||
getSubSite :: Site (Route s) (Method -> Maybe (GHandler s y ChooseRep))
|
getSubSite :: Site (Route s) (Method -> Maybe (GHandler s y ChooseRep))
|
||||||
getSiteFromSubSite :: s -> Site (Route s) (Method -> Maybe (GHandler s y ChooseRep))
|
|
||||||
getSiteFromSubSite _ = getSubSite
|
|
||||||
|
|
||||||
-- | Define settings for a Yesod applications. The only required setting is
|
-- | Define settings for a Yesod applications. The only required setting is
|
||||||
-- 'approot'; other than that, there are intelligent defaults.
|
-- 'approot'; other than that, there are intelligent defaults.
|
||||||
@ -95,6 +116,8 @@ class Eq (Route a) => Yesod a where
|
|||||||
--
|
--
|
||||||
-- * You do not use any features that require absolute URLs, such as Atom
|
-- * You do not use any features that require absolute URLs, such as Atom
|
||||||
-- feeds and XML sitemaps.
|
-- feeds and XML sitemaps.
|
||||||
|
--
|
||||||
|
-- FIXME: is this the right typesig?
|
||||||
approot :: a -> S.ByteString
|
approot :: a -> S.ByteString
|
||||||
|
|
||||||
-- | The encryption key to be used for encrypting client sessions.
|
-- | The encryption key to be used for encrypting client sessions.
|
||||||
@ -129,12 +152,6 @@ class Eq (Route a) => Yesod a where
|
|||||||
^{pageBody p}
|
^{pageBody p}
|
||||||
|]
|
|]
|
||||||
|
|
||||||
-- | Gets called at the beginning of each request. Useful for logging.
|
|
||||||
--
|
|
||||||
-- FIXME make this a part of the Yesod middlewares
|
|
||||||
onRequest :: GHandler sub a ()
|
|
||||||
onRequest = return ()
|
|
||||||
|
|
||||||
-- | Override the rendering function for a particular URL. One use case for
|
-- | Override the rendering function for a particular URL. One use case for
|
||||||
-- this is to offload static hosting to a different domain name to avoid
|
-- this is to offload static hosting to a different domain name to avoid
|
||||||
-- sending cookies.
|
-- sending cookies.
|
||||||
@ -193,6 +210,8 @@ class Eq (Route a) => Yesod a where
|
|||||||
|
|
||||||
-- | Join the pieces of a path together into an absolute URL. This should
|
-- | Join the pieces of a path together into an absolute URL. This should
|
||||||
-- be the inverse of 'splitPath'.
|
-- be the inverse of 'splitPath'.
|
||||||
|
--
|
||||||
|
-- FIXME is this the right type sig?
|
||||||
joinPath :: a
|
joinPath :: a
|
||||||
-> S.ByteString -- ^ application root
|
-> S.ByteString -- ^ application root
|
||||||
-> [String] -- ^ path pieces
|
-> [String] -- ^ path pieces
|
||||||
@ -226,6 +245,68 @@ class Eq (Route a) => Yesod a where
|
|||||||
sessionIpAddress :: a -> Bool
|
sessionIpAddress :: a -> Bool
|
||||||
sessionIpAddress _ = True
|
sessionIpAddress _ = True
|
||||||
|
|
||||||
|
yesodRunner :: YesodSite a => a -> Maybe CS.Key -> Maybe (Route a) -> GHandler a a ChooseRep -> W.Application
|
||||||
|
yesodRunner = defaultYesodRunner
|
||||||
|
|
||||||
|
defaultYesodRunner y mkey murl handler req = do
|
||||||
|
now <- liftIO getCurrentTime
|
||||||
|
let getExpires m = fromIntegral (m * 60) `addUTCTime` now
|
||||||
|
let exp' = getExpires $ clientSessionDuration y
|
||||||
|
-- FIXME will show remoteHost give the answer I need? will it include port
|
||||||
|
-- information that changes on each request?
|
||||||
|
let host = if sessionIpAddress y then S8.pack (show (W.remoteHost req)) else ""
|
||||||
|
let session' =
|
||||||
|
case mkey of
|
||||||
|
Nothing -> []
|
||||||
|
Just key -> fromMaybe [] $ do
|
||||||
|
raw <- lookup "Cookie" $ W.requestHeaders req
|
||||||
|
val <- lookup sessionName $ parseCookies raw
|
||||||
|
decodeSession key now host val
|
||||||
|
rr <- liftIO $ parseWaiRequest req session' mkey
|
||||||
|
let h = do
|
||||||
|
case murl of
|
||||||
|
Nothing -> handler
|
||||||
|
Just url -> do
|
||||||
|
isWrite <- isWriteRequest url
|
||||||
|
ar <- isAuthorized url isWrite
|
||||||
|
case ar of
|
||||||
|
Authorized -> return ()
|
||||||
|
AuthenticationRequired ->
|
||||||
|
case authRoute y of
|
||||||
|
Nothing ->
|
||||||
|
permissionDenied "Authentication required"
|
||||||
|
Just url' -> do
|
||||||
|
setUltDest'
|
||||||
|
redirect RedirectTemporary url'
|
||||||
|
Unauthorized s -> permissionDenied s
|
||||||
|
handler
|
||||||
|
let sessionMap = Map.fromList
|
||||||
|
$ filter (\(x, _) -> x /= nonceKey) session'
|
||||||
|
yar <- handlerToYAR y (yesodRender y) errorHandler rr murl sessionMap h
|
||||||
|
let mnonce = Just $ reqNonce rr -- FIXME
|
||||||
|
return $ yarToResponse (hr mnonce getExpires host exp') yar
|
||||||
|
where
|
||||||
|
hr mnonce getExpires host exp' hs ct sm =
|
||||||
|
hs'''
|
||||||
|
where
|
||||||
|
sessionVal =
|
||||||
|
case (mkey, mnonce) of
|
||||||
|
(Just key, Just nonce)
|
||||||
|
-> encodeSession key exp' host
|
||||||
|
$ Map.toList
|
||||||
|
$ Map.insert nonceKey nonce sm
|
||||||
|
_ -> S.empty
|
||||||
|
hs' =
|
||||||
|
case mkey of
|
||||||
|
Nothing -> hs
|
||||||
|
Just _ -> AddCookie
|
||||||
|
(clientSessionDuration y)
|
||||||
|
sessionName
|
||||||
|
sessionVal
|
||||||
|
: hs
|
||||||
|
hs'' = map (headerToPair getExpires) hs'
|
||||||
|
hs''' = ("Content-Type", ct) : hs''
|
||||||
|
|
||||||
data AuthResult = Authorized | AuthenticationRequired | Unauthorized String
|
data AuthResult = Authorized | AuthenticationRequired | Unauthorized String
|
||||||
deriving (Eq, Show, Read)
|
deriving (Eq, Show, Read)
|
||||||
|
|
||||||
@ -483,3 +564,138 @@ redirectToPost dest = hamletToRepHtml
|
|||||||
|
|
||||||
yesodVersion :: String
|
yesodVersion :: String
|
||||||
yesodVersion = showVersion Paths_yesod_core.version
|
yesodVersion = showVersion Paths_yesod_core.version
|
||||||
|
|
||||||
|
yesodRender :: (Yesod y, YesodSite y)
|
||||||
|
=> y
|
||||||
|
-> Route y
|
||||||
|
-> [(String, String)]
|
||||||
|
-> String
|
||||||
|
yesodRender y u qs =
|
||||||
|
S8.unpack $ fromMaybe
|
||||||
|
(joinPath y (approot y) ps $ qs ++ qs')
|
||||||
|
(urlRenderOverride y u)
|
||||||
|
where
|
||||||
|
(ps, qs') = formatPathSegments (getSite' y) u
|
||||||
|
|
||||||
|
parseWaiRequest :: W.Request
|
||||||
|
-> [(String, String)] -- ^ session
|
||||||
|
-> Maybe a
|
||||||
|
-> IO Request
|
||||||
|
parseWaiRequest env session' key' = do
|
||||||
|
let gets' = map (bsToChars *** bsToChars)
|
||||||
|
$ NWP.parseQueryString $ W.queryString env
|
||||||
|
let reqCookie = fromMaybe S.empty $ lookup "Cookie"
|
||||||
|
$ W.requestHeaders env
|
||||||
|
cookies' = map (bsToChars *** bsToChars) $ parseCookies reqCookie
|
||||||
|
acceptLang = lookup "Accept-Language" $ W.requestHeaders env
|
||||||
|
langs = map bsToChars $ maybe [] NWP.parseHttpAccept acceptLang
|
||||||
|
langs' = case lookup langKey session' of
|
||||||
|
Nothing -> langs
|
||||||
|
Just x -> x : langs
|
||||||
|
langs'' = case lookup langKey cookies' of
|
||||||
|
Nothing -> langs'
|
||||||
|
Just x -> x : langs'
|
||||||
|
langs''' = case lookup langKey gets' of
|
||||||
|
Nothing -> langs''
|
||||||
|
Just x -> x : langs''
|
||||||
|
rbthunk <- iothunk $ rbHelper env
|
||||||
|
nonce <- case (key', lookup nonceKey session') of
|
||||||
|
(Nothing, _) -> return $ error "You have attempted to use the nonce, but sessions are disabled." -- FIXME maybe this should be handled without an error?
|
||||||
|
(_, Just x) -> return x
|
||||||
|
(_, Nothing) -> do
|
||||||
|
g <- newStdGen
|
||||||
|
return $ fst $ randomString 10 g
|
||||||
|
return $ Request gets' cookies' rbthunk env langs''' nonce
|
||||||
|
where
|
||||||
|
randomString len =
|
||||||
|
first (map toChar) . sequence' (replicate len (randomR (0, 61)))
|
||||||
|
sequence' [] g = ([], g)
|
||||||
|
sequence' (f:fs) g =
|
||||||
|
let (f', g') = f g
|
||||||
|
(fs', g'') = sequence' fs g'
|
||||||
|
in (f' : fs', g'')
|
||||||
|
toChar i
|
||||||
|
| i < 26 = toEnum $ i + fromEnum 'A'
|
||||||
|
| i < 52 = toEnum $ i + fromEnum 'a' - 26
|
||||||
|
| otherwise = toEnum $ i + fromEnum '0' - 52
|
||||||
|
|
||||||
|
nonceKey :: String
|
||||||
|
nonceKey = "_NONCE"
|
||||||
|
|
||||||
|
rbHelper :: W.Request -> Iteratee ByteString IO RequestBodyContents
|
||||||
|
rbHelper req =
|
||||||
|
(map fix1 *** map fix2) <$> iter
|
||||||
|
where
|
||||||
|
iter = NWP.parseRequestBody NWP.lbsSink req
|
||||||
|
fix1 = bsToChars *** bsToChars
|
||||||
|
fix2 (x, NWP.FileInfo a b c) =
|
||||||
|
(bsToChars x, FileInfo (bsToChars a) (bsToChars b) c)
|
||||||
|
|
||||||
|
-- | Produces a \"compute on demand\" value. The computation will be run once
|
||||||
|
-- it is requested, and then the result will be stored. This will happen only
|
||||||
|
-- once.
|
||||||
|
--
|
||||||
|
-- FIXME: remove this function and use a StateT in Handler
|
||||||
|
iothunk :: Iteratee ByteString IO a -> IO (Iteratee ByteString IO a)
|
||||||
|
iothunk =
|
||||||
|
fmap go . liftIO . newMVar . Left
|
||||||
|
where
|
||||||
|
go :: MVar (Either (Iteratee ByteString IO a) a) -> Iteratee ByteString IO a
|
||||||
|
go mvar = do
|
||||||
|
x <- liftIO $ takeMVar mvar
|
||||||
|
(x', a) <- go' x
|
||||||
|
liftIO $ putMVar mvar x'
|
||||||
|
return a
|
||||||
|
go' :: Either (Iteratee ByteString IO a) a
|
||||||
|
-> Iteratee ByteString IO (Either (Iteratee ByteString IO a) a, a)
|
||||||
|
go' (Right val) = return (Right val, val)
|
||||||
|
go' (Left comp) = do
|
||||||
|
val <- comp
|
||||||
|
return (Right val, val)
|
||||||
|
|
||||||
|
-- FIXME don't duplicate
|
||||||
|
sessionName :: ByteString
|
||||||
|
sessionName = "_SESSION"
|
||||||
|
|
||||||
|
encodeSession :: CS.Key
|
||||||
|
-> UTCTime -- ^ expire time
|
||||||
|
-> ByteString -- ^ remote host
|
||||||
|
-> [(String, String)] -- ^ session
|
||||||
|
-> ByteString -- ^ cookie value
|
||||||
|
encodeSession key expire rhost session' =
|
||||||
|
CS.encrypt key $ encode $ SessionCookie expire rhost session'
|
||||||
|
|
||||||
|
decodeSession :: CS.Key
|
||||||
|
-> UTCTime -- ^ current time
|
||||||
|
-> ByteString -- ^ remote host field
|
||||||
|
-> ByteString -- ^ cookie value
|
||||||
|
-> Maybe [(String, String)]
|
||||||
|
decodeSession key now rhost encrypted = do
|
||||||
|
decrypted <- CS.decrypt key encrypted
|
||||||
|
SessionCookie expire rhost' session' <-
|
||||||
|
either (const Nothing) Just $ decode decrypted
|
||||||
|
guard $ expire > now
|
||||||
|
guard $ rhost' == rhost
|
||||||
|
return session'
|
||||||
|
|
||||||
|
data SessionCookie = SessionCookie UTCTime ByteString [(String, String)]
|
||||||
|
deriving (Show, Read)
|
||||||
|
instance Serialize SessionCookie where
|
||||||
|
put (SessionCookie a b c) = putTime a >> put b >> put c
|
||||||
|
get = do
|
||||||
|
a <- getTime
|
||||||
|
b <- get
|
||||||
|
c <- get
|
||||||
|
return $ SessionCookie a b c
|
||||||
|
|
||||||
|
putTime :: Putter UTCTime
|
||||||
|
putTime t@(UTCTime d _) = do
|
||||||
|
put $ toModifiedJulianDay d
|
||||||
|
let ndt = diffUTCTime t $ UTCTime d 0
|
||||||
|
put $ toRational ndt
|
||||||
|
|
||||||
|
getTime :: Get UTCTime
|
||||||
|
getTime = do
|
||||||
|
d <- get
|
||||||
|
ndt <- get
|
||||||
|
return $ fromRational ndt `addUTCTime` UTCTime (ModifiedJulianDay d) 0
|
||||||
|
|||||||
@ -44,6 +44,7 @@ import qualified Data.ByteString.Char8 as B
|
|||||||
import qualified Data.ByteString as S
|
import qualified Data.ByteString as S
|
||||||
import Data.ByteString (ByteString)
|
import Data.ByteString (ByteString)
|
||||||
import qualified Data.ByteString.Lazy as L
|
import qualified Data.ByteString.Lazy as L
|
||||||
|
import Data.ByteString.Lazy.Char8 ()
|
||||||
import Blaze.ByteString.Builder (toLazyByteString)
|
import Blaze.ByteString.Builder (toLazyByteString)
|
||||||
|
|
||||||
import Control.Concurrent.MVar
|
import Control.Concurrent.MVar
|
||||||
@ -225,7 +226,7 @@ mkToMasterArg ps fname = do
|
|||||||
e = rsg `AppE` e'
|
e = rsg `AppE` e'
|
||||||
return $ LamE xps e
|
return $ LamE xps e
|
||||||
|
|
||||||
sessionName :: String
|
sessionName :: B.ByteString
|
||||||
sessionName = "_SESSION"
|
sessionName = "_SESSION"
|
||||||
|
|
||||||
-- | Convert the given argument into a WAI application, executable with any WAI
|
-- | Convert the given argument into a WAI application, executable with any WAI
|
||||||
@ -246,241 +247,53 @@ toWaiAppPlain a = do
|
|||||||
key' <- encryptKey a
|
key' <- encryptKey a
|
||||||
return $ toWaiApp' a key'
|
return $ toWaiApp' a key'
|
||||||
|
|
||||||
|
dispatchPieces _ _ _ = Nothing -- FIXME
|
||||||
|
|
||||||
toWaiApp' :: (Yesod y, YesodSite y)
|
toWaiApp' :: (Yesod y, YesodSite y)
|
||||||
=> y
|
=> y
|
||||||
-> Maybe Key
|
-> Maybe Key
|
||||||
-> W.Application
|
-> W.Application
|
||||||
toWaiApp' y key' env = do
|
toWaiApp' y key' env = do
|
||||||
let segments = decodePathInfo $ B.unpack $ W.pathInfo env
|
let segments =
|
||||||
-- FIXME call cleanPath
|
case decodePathInfo $ B.unpack $ W.pathInfo env of
|
||||||
now <- liftIO getCurrentTime
|
"":x -> x
|
||||||
let getExpires m = fromIntegral (m * 60) `addUTCTime` now
|
x -> x
|
||||||
let exp' = getExpires $ clientSessionDuration y
|
liftIO $ print (W.pathInfo env, segments)
|
||||||
-- FIXME will show remoteHost give the answer I need? will it include port
|
case dispatchPieces y key' segments of
|
||||||
-- information that changes on each request?
|
Nothing ->
|
||||||
let host = if sessionIpAddress y then B.pack (show (W.remoteHost env)) else ""
|
case cleanPath y segments of
|
||||||
let session' =
|
Nothing -> normalDispatch y key' segments env
|
||||||
case key' of
|
Just segments' ->
|
||||||
Nothing -> []
|
let dest = joinPath y (approot y) segments' []
|
||||||
Just key'' -> fromMaybe [] $ do
|
dest' =
|
||||||
raw <- lookup "Cookie" $ W.requestHeaders env
|
if S.null (W.queryString env)
|
||||||
val <- lookup (B.pack sessionName) $ parseCookies raw
|
then dest
|
||||||
decodeSession key'' now host val
|
else S.concat
|
||||||
let site = getSite
|
[ dest
|
||||||
method = B.unpack $ W.requestMethod env
|
, B.singleton '?'
|
||||||
types = httpAccept env
|
, W.queryString env
|
||||||
pathSegments = filter (not . null) segments
|
]
|
||||||
eurl = parsePathSegments site pathSegments
|
in return $ W.responseLBS W.status301
|
||||||
render u qs =
|
[ ("Content-Type", "text/plain")
|
||||||
let (ps, qs') = formatPathSegments site u
|
, ("Location", dest')
|
||||||
in B.unpack $ fromMaybe
|
] "Redirecting"
|
||||||
(joinPath y (approot y) ps $ qs ++ qs')
|
Just app -> app env
|
||||||
(urlRenderOverride y u)
|
|
||||||
let errorHandler' = localNoCurrent . errorHandler
|
|
||||||
rr <- liftIO $ parseWaiRequest env session' key'
|
|
||||||
let h = do
|
|
||||||
onRequest
|
|
||||||
case eurl of
|
|
||||||
Left _ -> errorHandler' NotFound
|
|
||||||
Right url -> do
|
|
||||||
isWrite <- isWriteRequest url
|
|
||||||
ar <- isAuthorized url isWrite
|
|
||||||
case ar of
|
|
||||||
Authorized -> return ()
|
|
||||||
AuthenticationRequired ->
|
|
||||||
case authRoute y of
|
|
||||||
Nothing ->
|
|
||||||
permissionDenied "Authentication required"
|
|
||||||
Just url' -> do
|
|
||||||
setUltDest'
|
|
||||||
redirect RedirectTemporary url'
|
|
||||||
Unauthorized s -> permissionDenied s
|
|
||||||
case handleSite site render url method of
|
|
||||||
Nothing -> errorHandler' $ BadMethod method
|
|
||||||
Just h' -> h'
|
|
||||||
let eurl' = either (const Nothing) Just eurl
|
|
||||||
let eh er = runHandler (errorHandler' er) render eurl' id y id
|
|
||||||
let ya = runHandler h render eurl' id y id
|
|
||||||
let sessionMap = Map.fromList
|
|
||||||
$ filter (\(x, _) -> x /= nonceKey) session'
|
|
||||||
yar <- unYesodApp ya eh rr types sessionMap
|
|
||||||
case yar of
|
|
||||||
YARPlain s hs ct c sessionFinal -> do
|
|
||||||
let sessionVal =
|
|
||||||
case key' of
|
|
||||||
Nothing -> B.empty
|
|
||||||
Just key'' ->
|
|
||||||
encodeSession key'' exp' host
|
|
||||||
$ Map.toList
|
|
||||||
$ Map.insert nonceKey (reqNonce rr) sessionFinal
|
|
||||||
let hs' =
|
|
||||||
case key' of
|
|
||||||
Nothing -> hs
|
|
||||||
Just _ -> AddCookie
|
|
||||||
(clientSessionDuration y)
|
|
||||||
sessionName
|
|
||||||
(bsToChars sessionVal)
|
|
||||||
: hs
|
|
||||||
hs'' = map (headerToPair getExpires) hs'
|
|
||||||
hs''' = ("Content-Type", charsToBs ct) : hs''
|
|
||||||
return $
|
|
||||||
case c of
|
|
||||||
ContentBuilder b mlen ->
|
|
||||||
let hs'''' =
|
|
||||||
case mlen of
|
|
||||||
Nothing -> hs'''
|
|
||||||
Just len ->
|
|
||||||
("Content-Length", B.pack $ show len)
|
|
||||||
: hs'''
|
|
||||||
in W.ResponseBuilder s hs'''' b
|
|
||||||
ContentFile fp -> W.ResponseFile s hs''' fp
|
|
||||||
ContentEnum e -> W.ResponseEnumerator $ \iter ->
|
|
||||||
run_ $ e $$ iter s hs'''
|
|
||||||
YARWai r -> return r
|
|
||||||
|
|
||||||
httpAccept :: W.Request -> [ContentType]
|
normalDispatch :: (Yesod m, YesodSite m)
|
||||||
httpAccept = map B.unpack
|
=> m -> Maybe Key -> [String]
|
||||||
. parseHttpAccept
|
-> W.Application
|
||||||
. fromMaybe B.empty
|
normalDispatch y key' segments env =
|
||||||
. lookup "Accept"
|
yesodRunner y key' murl handler env
|
||||||
. W.requestHeaders
|
|
||||||
|
|
||||||
parseWaiRequest :: W.Request
|
|
||||||
-> [(String, String)] -- ^ session
|
|
||||||
-> Maybe a
|
|
||||||
-> IO Request
|
|
||||||
parseWaiRequest env session' key' = do
|
|
||||||
let gets' = map (bsToChars *** bsToChars)
|
|
||||||
$ parseQueryString $ W.queryString env
|
|
||||||
let reqCookie = fromMaybe B.empty $ lookup "Cookie"
|
|
||||||
$ W.requestHeaders env
|
|
||||||
cookies' = map (bsToChars *** bsToChars) $ parseCookies reqCookie
|
|
||||||
acceptLang = lookup "Accept-Language" $ W.requestHeaders env
|
|
||||||
langs = map bsToChars $ maybe [] parseHttpAccept acceptLang
|
|
||||||
langs' = case lookup langKey session' of
|
|
||||||
Nothing -> langs
|
|
||||||
Just x -> x : langs
|
|
||||||
langs'' = case lookup langKey cookies' of
|
|
||||||
Nothing -> langs'
|
|
||||||
Just x -> x : langs'
|
|
||||||
langs''' = case lookup langKey gets' of
|
|
||||||
Nothing -> langs''
|
|
||||||
Just x -> x : langs''
|
|
||||||
rbthunk <- iothunk $ rbHelper env
|
|
||||||
nonce <- case (key', lookup nonceKey session') of
|
|
||||||
(Nothing, _) -> return $ error "You have attempted to use the nonce, but sessions are disabled." -- FIXME maybe this should be handled without an error?
|
|
||||||
(_, Just x) -> return x
|
|
||||||
(_, Nothing) -> do
|
|
||||||
g <- newStdGen
|
|
||||||
return $ fst $ randomString 10 g
|
|
||||||
return $ Request gets' cookies' rbthunk env langs''' nonce
|
|
||||||
where
|
where
|
||||||
randomString len =
|
method = B.unpack $ W.requestMethod env
|
||||||
first (map toChar) . sequence' (replicate len (randomR (0, 61)))
|
murl = either (const Nothing) Just $ parsePathSegments (getSite' y) segments
|
||||||
sequence' [] g = ([], g)
|
handler =
|
||||||
sequence' (f:fs) g =
|
case murl of
|
||||||
let (f', g') = f g
|
Nothing -> notFound
|
||||||
(fs', g'') = sequence' fs g'
|
Just url ->
|
||||||
in (f' : fs', g'')
|
case handleSite (getSite' y) (yesodRender y) url method of
|
||||||
toChar i
|
Nothing -> badMethod
|
||||||
| i < 26 = toEnum $ i + fromEnum 'A'
|
Just h -> h
|
||||||
| i < 52 = toEnum $ i + fromEnum 'a' - 26
|
|
||||||
| otherwise = toEnum $ i + fromEnum '0' - 52
|
|
||||||
|
|
||||||
nonceKey :: String
|
|
||||||
nonceKey = "_NONCE"
|
|
||||||
|
|
||||||
rbHelper :: W.Request -> Iteratee ByteString IO RequestBodyContents
|
|
||||||
rbHelper req =
|
|
||||||
(map fix1 *** map fix2) <$> iter
|
|
||||||
where
|
|
||||||
iter = parseRequestBody lbsSink req
|
|
||||||
fix1 = bsToChars *** bsToChars
|
|
||||||
fix2 (x, NWP.FileInfo a b c) =
|
|
||||||
(bsToChars x, FileInfo (bsToChars a) (bsToChars b) c)
|
|
||||||
|
|
||||||
-- | Produces a \"compute on demand\" value. The computation will be run once
|
|
||||||
-- it is requested, and then the result will be stored. This will happen only
|
|
||||||
-- once.
|
|
||||||
iothunk :: Iteratee ByteString IO a -> IO (Iteratee ByteString IO a)
|
|
||||||
iothunk =
|
|
||||||
fmap go . liftIO . newMVar . Left
|
|
||||||
where
|
|
||||||
go :: MVar (Either (Iteratee ByteString IO a) a) -> Iteratee ByteString IO a
|
|
||||||
go mvar = do
|
|
||||||
x <- liftIO $ takeMVar mvar
|
|
||||||
(x', a) <- go' x
|
|
||||||
liftIO $ putMVar mvar x'
|
|
||||||
return a
|
|
||||||
go' :: Either (Iteratee ByteString IO a) a
|
|
||||||
-> Iteratee ByteString IO (Either (Iteratee ByteString IO a) a, a)
|
|
||||||
go' (Right val) = return (Right val, val)
|
|
||||||
go' (Left comp) = do
|
|
||||||
val <- comp
|
|
||||||
return (Right val, val)
|
|
||||||
|
|
||||||
-- | Convert Header to a key/value pair.
|
|
||||||
headerToPair :: (Int -> UTCTime) -- ^ minutes -> expiration time
|
|
||||||
-> Header
|
|
||||||
-> (W.ResponseHeader, B.ByteString)
|
|
||||||
headerToPair getExpires (AddCookie minutes key value) =
|
|
||||||
("Set-Cookie", builderToBS $ renderSetCookie $ SetCookie
|
|
||||||
{ setCookieName = B.pack key -- FIXME check for non-ASCII
|
|
||||||
, setCookieValue = B.pack value -- FIXME check for non-ASCII
|
|
||||||
, setCookiePath = Just "/" -- FIXME make a config option, or use approot?
|
|
||||||
, setCookieExpires = Just $ getExpires minutes
|
|
||||||
, setCookieDomain = Nothing
|
|
||||||
})
|
|
||||||
where
|
|
||||||
builderToBS = S.concat . L.toChunks . toLazyByteString
|
|
||||||
headerToPair _ (DeleteCookie key) =
|
|
||||||
("Set-Cookie", charsToBs $
|
|
||||||
key ++ "=; path=/; expires=Thu, 01-Jan-1970 00:00:00 GMT")
|
|
||||||
headerToPair _ (Header key value) =
|
|
||||||
(fromString key, charsToBs value)
|
|
||||||
|
|
||||||
encodeSession :: CS.Key
|
|
||||||
-> UTCTime -- ^ expire time
|
|
||||||
-> B.ByteString -- ^ remote host
|
|
||||||
-> [(String, String)] -- ^ session
|
|
||||||
-> B.ByteString -- ^ cookie value
|
|
||||||
encodeSession key expire rhost session' =
|
|
||||||
encrypt key $ encode $ SessionCookie expire rhost session'
|
|
||||||
|
|
||||||
decodeSession :: CS.Key
|
|
||||||
-> UTCTime -- ^ current time
|
|
||||||
-> B.ByteString -- ^ remote host field
|
|
||||||
-> B.ByteString -- ^ cookie value
|
|
||||||
-> Maybe [(String, String)]
|
|
||||||
decodeSession key now rhost encrypted = do
|
|
||||||
decrypted <- decrypt key encrypted
|
|
||||||
SessionCookie expire rhost' session' <-
|
|
||||||
either (const Nothing) Just $ decode decrypted
|
|
||||||
guard $ expire > now
|
|
||||||
guard $ rhost' == rhost
|
|
||||||
return session'
|
|
||||||
|
|
||||||
data SessionCookie = SessionCookie UTCTime B.ByteString [(String, String)]
|
|
||||||
deriving (Show, Read)
|
|
||||||
instance Serialize SessionCookie where
|
|
||||||
put (SessionCookie a b c) = putTime a >> put b >> put c
|
|
||||||
get = do
|
|
||||||
a <- getTime
|
|
||||||
b <- Ser.get
|
|
||||||
c <- Ser.get
|
|
||||||
return $ SessionCookie a b c
|
|
||||||
|
|
||||||
putTime :: Putter UTCTime
|
|
||||||
putTime t@(UTCTime d _) = do
|
|
||||||
put $ toModifiedJulianDay d
|
|
||||||
let ndt = diffUTCTime t $ UTCTime d 0
|
|
||||||
put $ toRational ndt
|
|
||||||
|
|
||||||
getTime :: Get UTCTime
|
|
||||||
getTime = do
|
|
||||||
d <- Ser.get
|
|
||||||
ndt <- Ser.get
|
|
||||||
return $ fromRational ndt `addUTCTime` UTCTime (ModifiedJulianDay d) 0
|
|
||||||
|
|
||||||
#if TEST
|
#if TEST
|
||||||
|
|
||||||
|
|||||||
118
Yesod/Handler.hs
118
Yesod/Handler.hs
@ -9,6 +9,7 @@
|
|||||||
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
|
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
|
||||||
{-# LANGUAGE CPP #-}
|
{-# LANGUAGE CPP #-}
|
||||||
{-# LANGUAGE FunctionalDependencies #-}
|
{-# LANGUAGE FunctionalDependencies #-}
|
||||||
|
{-# LANGUAGE OverloadedStrings #-}
|
||||||
---------------------------------------------------------
|
---------------------------------------------------------
|
||||||
--
|
--
|
||||||
-- Module : Yesod.Handler
|
-- Module : Yesod.Handler
|
||||||
@ -88,6 +89,9 @@ module Yesod.Handler
|
|||||||
, HandlerData
|
, HandlerData
|
||||||
, ErrorResponse (..)
|
, ErrorResponse (..)
|
||||||
, YesodAppResult (..)
|
, YesodAppResult (..)
|
||||||
|
, handlerToYAR
|
||||||
|
, yarToResponse
|
||||||
|
, headerToPair
|
||||||
#if TEST
|
#if TEST
|
||||||
, handlerTestSuite
|
, handlerTestSuite
|
||||||
#endif
|
#endif
|
||||||
@ -119,15 +123,21 @@ import Text.Hamlet
|
|||||||
|
|
||||||
import Control.Monad.IO.Peel (MonadPeelIO)
|
import Control.Monad.IO.Peel (MonadPeelIO)
|
||||||
import qualified Data.Map as Map
|
import qualified Data.Map as Map
|
||||||
|
import qualified Data.ByteString as S
|
||||||
import qualified Data.ByteString.Char8 as S8
|
import qualified Data.ByteString.Char8 as S8
|
||||||
import Data.ByteString (ByteString)
|
import Data.ByteString (ByteString)
|
||||||
import Data.Enumerator (Iteratee (..))
|
import Data.Enumerator (Iteratee (..))
|
||||||
|
import Network.Wai.Parse (parseHttpAccept)
|
||||||
|
|
||||||
#if TEST
|
#if TEST
|
||||||
import Test.Framework (testGroup, Test)
|
import Test.Framework (testGroup, Test)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
import Yesod.Content
|
import Yesod.Content
|
||||||
|
import Data.Maybe (fromMaybe)
|
||||||
|
import Web.Cookie (SetCookie (..), renderSetCookie)
|
||||||
|
import Blaze.ByteString.Builder (toByteString)
|
||||||
|
import Data.Enumerator (run_, ($$))
|
||||||
|
|
||||||
-- | The type-safe URLs associated with a site argument.
|
-- | The type-safe URLs associated with a site argument.
|
||||||
type family Route a
|
type family Route a
|
||||||
@ -251,8 +261,8 @@ data HandlerContents =
|
|||||||
HCContent W.Status ChooseRep
|
HCContent W.Status ChooseRep
|
||||||
| HCError ErrorResponse
|
| HCError ErrorResponse
|
||||||
| HCSendFile ContentType FilePath
|
| HCSendFile ContentType FilePath
|
||||||
| HCRedirect RedirectType String
|
| HCRedirect RedirectType ByteString
|
||||||
| HCCreated String
|
| HCCreated ByteString
|
||||||
| HCWai W.Response
|
| HCWai W.Response
|
||||||
|
|
||||||
instance Error HandlerContents where
|
instance Error HandlerContents where
|
||||||
@ -349,7 +359,7 @@ runHandler handler mrender sroute tomr ma tosa =
|
|||||||
HCSendFile ct fp -> catchIter
|
HCSendFile ct fp -> catchIter
|
||||||
(sendFile' ct fp)
|
(sendFile' ct fp)
|
||||||
(handleError . toErrorHandler)
|
(handleError . toErrorHandler)
|
||||||
HCCreated loc -> do -- FIXME add status201 to WAI
|
HCCreated loc -> do
|
||||||
let hs = Header "Location" loc : headers []
|
let hs = Header "Location" loc : headers []
|
||||||
return $ YARPlain
|
return $ YARPlain
|
||||||
(W.Status 201 (S8.pack "Created"))
|
(W.Status 201 (S8.pack "Created"))
|
||||||
@ -372,7 +382,7 @@ safeEh er = YesodApp $ \_ _ _ session -> do
|
|||||||
W.status500
|
W.status500
|
||||||
[]
|
[]
|
||||||
typePlain
|
typePlain
|
||||||
(toContent "Internal Server Error")
|
(toContent ("Internal Server Error" :: S.ByteString))
|
||||||
session
|
session
|
||||||
|
|
||||||
-- | Redirect to the given route.
|
-- | Redirect to the given route.
|
||||||
@ -384,10 +394,10 @@ redirectParams :: RedirectType -> Route master -> [(String, String)]
|
|||||||
-> GHandler sub master a
|
-> GHandler sub master a
|
||||||
redirectParams rt url params = do
|
redirectParams rt url params = do
|
||||||
r <- getUrlRenderParams
|
r <- getUrlRenderParams
|
||||||
redirectString rt $ r url params
|
redirectString rt $ S8.pack $ r url params
|
||||||
|
|
||||||
-- | Redirect to the given URL.
|
-- | Redirect to the given URL.
|
||||||
redirectString :: RedirectType -> String -> GHandler sub master a
|
redirectString :: RedirectType -> ByteString -> GHandler sub master a
|
||||||
redirectString rt = GHandler . lift . throwError . HCRedirect rt
|
redirectString rt = GHandler . lift . throwError . HCRedirect rt
|
||||||
|
|
||||||
ultDestKey :: String
|
ultDestKey :: String
|
||||||
@ -431,7 +441,7 @@ redirectUltDest :: RedirectType
|
|||||||
redirectUltDest rt def = do
|
redirectUltDest rt def = do
|
||||||
mdest <- lookupSession ultDestKey
|
mdest <- lookupSession ultDestKey
|
||||||
deleteSession ultDestKey
|
deleteSession ultDestKey
|
||||||
maybe (redirect rt def) (redirectString rt) mdest
|
maybe (redirect rt def) (redirectString rt . S8.pack) mdest
|
||||||
|
|
||||||
msgKey :: String
|
msgKey :: String
|
||||||
msgKey = "_MSG"
|
msgKey = "_MSG"
|
||||||
@ -476,7 +486,7 @@ sendResponseStatus s = GHandler . lift . throwError . HCContent s
|
|||||||
sendResponseCreated :: Route m -> GHandler s m a
|
sendResponseCreated :: Route m -> GHandler s m a
|
||||||
sendResponseCreated url = do
|
sendResponseCreated url = do
|
||||||
r <- getUrlRender
|
r <- getUrlRender
|
||||||
GHandler $ lift $ throwError $ HCCreated $ r url
|
GHandler $ lift $ throwError $ HCCreated $ S8.pack $ r url
|
||||||
|
|
||||||
-- | Send a 'W.Response'. Please note: this function is rarely
|
-- | Send a 'W.Response'. Please note: this function is rarely
|
||||||
-- necessary, and will /disregard/ any changes to response headers and session
|
-- necessary, and will /disregard/ any changes to response headers and session
|
||||||
@ -507,13 +517,13 @@ invalidArgs = failure . InvalidArgs
|
|||||||
------- Headers
|
------- Headers
|
||||||
-- | Set the cookie on the client.
|
-- | Set the cookie on the client.
|
||||||
setCookie :: Int -- ^ minutes to timeout
|
setCookie :: Int -- ^ minutes to timeout
|
||||||
-> String -- ^ key
|
-> ByteString -- ^ key
|
||||||
-> String -- ^ value
|
-> ByteString -- ^ value
|
||||||
-> GHandler sub master ()
|
-> GHandler sub master ()
|
||||||
setCookie a b = addHeader . AddCookie a b
|
setCookie a b = addHeader . AddCookie a b
|
||||||
|
|
||||||
-- | Unset the cookie on the client.
|
-- | Unset the cookie on the client.
|
||||||
deleteCookie :: String -> GHandler sub master ()
|
deleteCookie :: ByteString -> GHandler sub master ()
|
||||||
deleteCookie = addHeader . DeleteCookie
|
deleteCookie = addHeader . DeleteCookie
|
||||||
|
|
||||||
-- | Set the language in the user session. Will show up in 'languages' on the
|
-- | Set the language in the user session. Will show up in 'languages' on the
|
||||||
@ -522,13 +532,13 @@ setLanguage :: String -> GHandler sub master ()
|
|||||||
setLanguage = setSession langKey
|
setLanguage = setSession langKey
|
||||||
|
|
||||||
-- | Set an arbitrary response header.
|
-- | Set an arbitrary response header.
|
||||||
setHeader :: String -> String -> GHandler sub master ()
|
setHeader :: W.ResponseHeader -> ByteString -> GHandler sub master ()
|
||||||
setHeader a = addHeader . Header a
|
setHeader a = addHeader . Header a
|
||||||
|
|
||||||
-- | Set the Cache-Control header to indicate this response should be cached
|
-- | Set the Cache-Control header to indicate this response should be cached
|
||||||
-- for the given number of seconds.
|
-- for the given number of seconds.
|
||||||
cacheSeconds :: Int -> GHandler s m ()
|
cacheSeconds :: Int -> GHandler s m ()
|
||||||
cacheSeconds i = setHeader "Cache-Control" $ concat
|
cacheSeconds i = setHeader "Cache-Control" $ S8.pack $ concat
|
||||||
[ "max-age="
|
[ "max-age="
|
||||||
, show i
|
, show i
|
||||||
, ", public"
|
, ", public"
|
||||||
@ -546,7 +556,7 @@ alreadyExpired = setHeader "Expires" "Thu, 01 Jan 1970 05:05:05 GMT"
|
|||||||
|
|
||||||
-- | Set an Expires header to the given date.
|
-- | Set an Expires header to the given date.
|
||||||
expiresAt :: UTCTime -> GHandler s m ()
|
expiresAt :: UTCTime -> GHandler s m ()
|
||||||
expiresAt = setHeader "Expires" . formatRFC1123
|
expiresAt = setHeader "Expires" . S8.pack . formatRFC1123
|
||||||
|
|
||||||
-- | Set a variable in the user's session.
|
-- | Set a variable in the user's session.
|
||||||
--
|
--
|
||||||
@ -606,3 +616,83 @@ handlerTestSuite = testGroup "Yesod.Handler"
|
|||||||
]
|
]
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
handlerToYAR :: (HasReps a, HasReps b)
|
||||||
|
=> m -- ^ master site foundation
|
||||||
|
-> (Route m -> [(String, String)] -> String) -- ^ url render
|
||||||
|
-> (ErrorResponse -> GHandler m m a)
|
||||||
|
-> Request
|
||||||
|
-> Maybe (Route m)
|
||||||
|
-> SessionMap
|
||||||
|
-> GHandler m m b
|
||||||
|
-> Iteratee ByteString IO YesodAppResult
|
||||||
|
handlerToYAR y render errorHandler rr murl sessionMap h =
|
||||||
|
unYesodApp ya eh' rr types sessionMap
|
||||||
|
where
|
||||||
|
ya = runHandler h render murl id y id
|
||||||
|
eh' er = runHandler (errorHandler' er) render murl id y id
|
||||||
|
types = httpAccept $ reqWaiRequest rr
|
||||||
|
errorHandler' = localNoCurrent . errorHandler
|
||||||
|
|
||||||
|
type HeaderRenderer = [Header]
|
||||||
|
-> ContentType
|
||||||
|
-> SessionMap
|
||||||
|
-> [(W.ResponseHeader, ByteString)]
|
||||||
|
|
||||||
|
yarToResponse :: HeaderRenderer -> YesodAppResult -> W.Response
|
||||||
|
yarToResponse _ (YARWai a) = a
|
||||||
|
yarToResponse renderHeaders (YARPlain s hs ct c sessionFinal) =
|
||||||
|
case c of
|
||||||
|
ContentBuilder b mlen ->
|
||||||
|
let hs' = maybe finalHeaders finalHeaders' mlen
|
||||||
|
in W.ResponseBuilder s hs' b
|
||||||
|
ContentFile fp -> W.ResponseFile s finalHeaders fp
|
||||||
|
ContentEnum e ->
|
||||||
|
W.ResponseEnumerator $ \iter -> run_ $ e $$ iter s finalHeaders
|
||||||
|
where
|
||||||
|
finalHeaders = renderHeaders hs ct sessionFinal
|
||||||
|
finalHeaders' len = ("Content-Length", S8.pack $ show len)
|
||||||
|
: finalHeaders
|
||||||
|
{-
|
||||||
|
getExpires m = fromIntegral (m * 60) `addUTCTime` now
|
||||||
|
sessionVal =
|
||||||
|
case key' of
|
||||||
|
Nothing -> B.empty
|
||||||
|
Just key'' -> encodeSession key'' exp' host
|
||||||
|
$ Map.toList
|
||||||
|
$ Map.insert nonceKey (reqNonce rr) sessionFinal
|
||||||
|
hs' =
|
||||||
|
case key' of
|
||||||
|
Nothing -> hs
|
||||||
|
Just _ -> AddCookie
|
||||||
|
(clientSessionDuration y)
|
||||||
|
sessionName
|
||||||
|
(bsToChars sessionVal)
|
||||||
|
: hs
|
||||||
|
hs'' = map (headerToPair getExpires) hs'
|
||||||
|
hs''' = ("Content-Type", charsToBs ct) : hs''
|
||||||
|
-}
|
||||||
|
|
||||||
|
httpAccept :: W.Request -> [ContentType]
|
||||||
|
httpAccept = parseHttpAccept
|
||||||
|
. fromMaybe S.empty
|
||||||
|
. lookup "Accept"
|
||||||
|
. W.requestHeaders
|
||||||
|
|
||||||
|
-- | Convert Header to a key/value pair.
|
||||||
|
headerToPair :: (Int -> UTCTime) -- ^ minutes -> expiration time
|
||||||
|
-> Header
|
||||||
|
-> (W.ResponseHeader, ByteString)
|
||||||
|
headerToPair getExpires (AddCookie minutes key value) =
|
||||||
|
("Set-Cookie", toByteString $ renderSetCookie $ SetCookie
|
||||||
|
{ setCookieName = key
|
||||||
|
, setCookieValue = value
|
||||||
|
, setCookiePath = Just "/" -- FIXME make a config option, or use approot?
|
||||||
|
, setCookieExpires = Just $ getExpires minutes
|
||||||
|
, setCookieDomain = Nothing
|
||||||
|
})
|
||||||
|
headerToPair _ (DeleteCookie key) =
|
||||||
|
( "Set-Cookie"
|
||||||
|
, key `S.append` "=; path=/; expires=Thu, 01-Jan-1970 00:00:00 GMT"
|
||||||
|
)
|
||||||
|
headerToPair _ (Header key value) = (key, value)
|
||||||
|
|||||||
@ -30,6 +30,7 @@ import Text.Hamlet (Hamlet, hamlet, Html)
|
|||||||
import Data.Monoid (Monoid (..))
|
import Data.Monoid (Monoid (..))
|
||||||
import Data.List (nub)
|
import Data.List (nub)
|
||||||
|
|
||||||
|
import Data.ByteString (ByteString)
|
||||||
import qualified Data.ByteString as S
|
import qualified Data.ByteString as S
|
||||||
import qualified Data.ByteString.Lazy as L
|
import qualified Data.ByteString.Lazy as L
|
||||||
|
|
||||||
@ -40,6 +41,8 @@ import qualified Data.Text.Encoding.Error as T
|
|||||||
import qualified Data.Text.Lazy as LT
|
import qualified Data.Text.Lazy as LT
|
||||||
import qualified Data.Text.Lazy.Encoding as LT
|
import qualified Data.Text.Lazy.Encoding as LT
|
||||||
|
|
||||||
|
import qualified Network.Wai as W
|
||||||
|
|
||||||
#if GHC7
|
#if GHC7
|
||||||
#define HAMLET hamlet
|
#define HAMLET hamlet
|
||||||
#else
|
#else
|
||||||
@ -59,9 +62,9 @@ data ErrorResponse =
|
|||||||
----- header stuff
|
----- header stuff
|
||||||
-- | Headers to be added to a 'Result'.
|
-- | Headers to be added to a 'Result'.
|
||||||
data Header =
|
data Header =
|
||||||
AddCookie Int String String
|
AddCookie Int ByteString ByteString
|
||||||
| DeleteCookie String
|
| DeleteCookie ByteString
|
||||||
| Header String String
|
| Header W.ResponseHeader ByteString
|
||||||
deriving (Eq, Show)
|
deriving (Eq, Show)
|
||||||
|
|
||||||
langKey :: String
|
langKey :: String
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user