module Yesod.Core.Handler
(
HandlerT
, getYesod
, getsYesod
, getUrlRender
, getUrlRenderParams
, getCurrentRoute
, getRequest
, waiRequest
, runRequestBody
, rawRequestBody
, RequestBodyContents
, YesodRequest (..)
, FileInfo
, fileName
, fileContentType
, fileSource
, fileMove
, languages
, lookupGetParam
, lookupPostParam
, lookupCookie
, lookupFile
, lookupHeader
, lookupBasicAuth
, lookupBearerAuth
, lookupGetParams
, lookupPostParams
, lookupCookies
, lookupFiles
, lookupHeaders
, respond
, respondSource
, sendChunk
, sendFlush
, sendChunkBS
, sendChunkLBS
, sendChunkText
, sendChunkLazyText
, sendChunkHtml
, RedirectUrl (..)
, redirect
, redirectWith
, redirectToPost
, Fragment(..)
, notFound
, badMethod
, notAuthenticated
, permissionDenied
, permissionDeniedI
, invalidArgs
, invalidArgsI
, sendFile
, sendFilePart
, sendResponse
, sendResponseStatus
, sendResponseCreated
, sendWaiResponse
, sendWaiApplication
, sendRawResponse
, sendRawResponseNoConduit
, notModified
, selectRep
, provideRep
, provideRepType
, ProvidedRep
, setCookie
, getExpires
, deleteCookie
, addHeader
, setHeader
, setLanguage
, cacheSeconds
, neverExpires
, alreadyExpired
, expiresAt
, setEtag
, SessionMap
, lookupSession
, lookupSessionBS
, getSession
, setSession
, setSessionBS
, deleteSession
, clearSession
, setUltDest
, setUltDestCurrent
, setUltDestReferer
, redirectUltDest
, clearUltDest
, setMessage
, setMessageI
, getMessage
, hamletToRepHtml
, giveUrlRenderer
, withUrlRenderer
, newIdent
, handlerToIO
, forkHandler
, getMessageRender
, cached
, cachedBy
, stripHandlerT
, setCsrfCookie
, setCsrfCookieWithCookie
, defaultCsrfCookieName
, checkCsrfHeaderNamed
, hasValidCsrfHeaderNamed
, defaultCsrfHeaderName
, hasValidCsrfParamNamed
, checkCsrfParamNamed
, defaultCsrfParamName
, checkCsrfHeaderOrParam
) where
import Data.Time (UTCTime, addUTCTime,
getCurrentTime)
import Yesod.Core.Internal.Request (langKey, mkFileInfoFile,
mkFileInfoLBS, mkFileInfoSource)
import Control.Applicative ((<$>), (<|>))
import Control.Exception (evaluate, SomeException)
import Control.Exception.Lifted (handle)
import Control.Monad (liftM, void)
import qualified Control.Monad.Trans.Writer as Writer
import Control.Monad.IO.Class (MonadIO, liftIO)
import qualified Network.HTTP.Types as H
import qualified Network.Wai as W
import Network.Wai.Middleware.HttpAuth
( extractBasicAuth, extractBearerAuth )
import Control.Monad.Trans.Class (lift)
import qualified Data.Text as T
import Data.Text.Encoding (decodeUtf8With, encodeUtf8)
import Data.Text.Encoding.Error (lenientDecode)
import qualified Data.Text.Lazy as TL
import qualified Text.Blaze.Html.Renderer.Text as RenderText
import Text.Hamlet (Html, HtmlUrl, hamlet)
import qualified Data.ByteString as S
import qualified Data.ByteString.Lazy as L
import qualified Data.Map as Map
import Data.Byteable (constEqBytes)
import Control.Arrow ((***))
import qualified Data.ByteString.Char8 as S8
import Data.Monoid (Endo (..), mappend, mempty)
import Data.Text (Text)
import qualified Network.Wai.Parse as NWP
import Text.Shakespeare.I18N (RenderMessage (..))
import Web.Cookie (SetCookie (..))
import Yesod.Core.Content (ToTypedContent (..), simpleContentType, contentTypeTypes, HasContentType (..), ToContent (..), ToFlushBuilder (..))
import Yesod.Core.Internal.Util (formatRFC1123)
import Text.Blaze.Html (preEscapedToMarkup, toHtml)
import qualified Data.IORef.Lifted as I
import Data.Maybe (listToMaybe, mapMaybe)
import Data.Typeable (Typeable)
import Web.PathPieces (PathPiece(..))
import Yesod.Core.Class.Handler
import Yesod.Core.Types
import Yesod.Routes.Class (Route)
import Control.Exception (throwIO)
import Blaze.ByteString.Builder (Builder)
import Safe (headMay)
import Data.CaseInsensitive (CI)
import qualified Data.Conduit.List as CL
import Control.Monad (unless)
import Control.Monad.Trans.Resource (MonadResource, InternalState, runResourceT, withInternalState, getInternalState, liftResourceT, resourceForkIO
)
import qualified System.PosixCompat.Files as PC
import Control.Monad.Trans.Control (control, MonadBaseControl)
import Data.Conduit (Source, transPipe, Flush (Flush), yield, Producer
, Sink
)
import qualified Yesod.Core.TypeCache as Cache
import qualified Data.Word8 as W8
import qualified Data.Foldable as Fold
import Data.Default
get :: MonadHandler m => m GHState
get = liftHandlerT $ HandlerT $ I.readIORef . handlerState
put :: MonadHandler m => GHState -> m ()
put x = liftHandlerT $ HandlerT $ flip I.writeIORef x . handlerState
modify :: MonadHandler m => (GHState -> GHState) -> m ()
modify f = liftHandlerT $ HandlerT $ flip I.modifyIORef f . handlerState
tell :: MonadHandler m => Endo [Header] -> m ()
tell hs = modify $ \g -> g { ghsHeaders = ghsHeaders g `mappend` hs }
handlerError :: MonadHandler m => HandlerContents -> m a
handlerError = liftIO . throwIO
hcError :: MonadHandler m => ErrorResponse -> m a
hcError = handlerError . HCError
getRequest :: MonadHandler m => m YesodRequest
getRequest = liftHandlerT $ HandlerT $ return . handlerRequest
runRequestBody :: MonadHandler m => m RequestBodyContents
runRequestBody = do
HandlerData
{ handlerEnv = RunHandlerEnv {..}
, handlerRequest = req
} <- liftHandlerT $ HandlerT return
let len = W.requestBodyLength $ reqWaiRequest req
upload = rheUpload len
x <- get
case ghsRBC x of
Just rbc -> return rbc
Nothing -> do
rr <- waiRequest
internalState <- liftResourceT getInternalState
rbc <- liftIO $ rbHelper upload rr internalState
put x { ghsRBC = Just rbc }
return rbc
rbHelper :: FileUpload -> W.Request -> InternalState -> IO RequestBodyContents
rbHelper upload req internalState =
case upload of
FileUploadMemory s -> rbHelper' s mkFileInfoLBS req
FileUploadDisk s -> rbHelper' (s internalState) mkFileInfoFile req
FileUploadSource s -> rbHelper' s mkFileInfoSource req
rbHelper' :: NWP.BackEnd x
-> (Text -> Text -> x -> FileInfo)
-> W.Request
-> IO ([(Text, Text)], [(Text, FileInfo)])
rbHelper' backend mkFI req =
(map fix1 *** mapMaybe fix2) <$> (NWP.parseRequestBody backend req)
where
fix1 = go *** go
fix2 (x, NWP.FileInfo a' b c)
| S.null a = Nothing
| otherwise = Just (go x, mkFI (go a) (go b) c)
where
a
| S.length a' < 2 = a'
| S8.head a' == '"' && S8.last a' == '"' = S.tail $ S.init a'
| S8.head a' == '\'' && S8.last a' == '\'' = S.tail $ S.init a'
| otherwise = a'
go = decodeUtf8With lenientDecode
askHandlerEnv :: MonadHandler m => m (RunHandlerEnv (HandlerSite m))
askHandlerEnv = liftHandlerT $ HandlerT $ return . handlerEnv
getYesod :: MonadHandler m => m (HandlerSite m)
getYesod = rheSite `liftM` askHandlerEnv
getsYesod :: MonadHandler m => (HandlerSite m -> a) -> m a
getsYesod f = (f . rheSite) `liftM` askHandlerEnv
getUrlRender :: MonadHandler m => m (Route (HandlerSite m) -> Text)
getUrlRender = do
x <- rheRender `liftM` askHandlerEnv
return $ flip x []
getUrlRenderParams
:: MonadHandler m
=> m (Route (HandlerSite m) -> [(Text, Text)] -> Text)
getUrlRenderParams = rheRender `liftM` askHandlerEnv
getCurrentRoute :: MonadHandler m => m (Maybe (Route (HandlerSite m)))
getCurrentRoute = rheRoute `liftM` askHandlerEnv
handlerToIO :: (MonadIO m1, MonadIO m2) => HandlerT site m1 (HandlerT site IO a -> m2 a)
handlerToIO =
HandlerT $ \oldHandlerData -> do
let newReq = oldReq { reqWaiRequest = newWaiReq }
where
oldReq = handlerRequest oldHandlerData
oldWaiReq = reqWaiRequest oldReq
newWaiReq = oldWaiReq { W.requestBody = return mempty
, W.requestBodyLength = W.KnownLength 0
}
oldEnv = handlerEnv oldHandlerData
newState <- liftIO $ do
oldState <- I.readIORef (handlerState oldHandlerData)
return $ oldState { ghsRBC = Nothing
, ghsIdent = 1
, ghsCache = mempty
, ghsCacheBy = mempty
, ghsHeaders = mempty }
liftIO $ evaluate (newReq `seq` oldEnv `seq` newState `seq` ())
return $ \(HandlerT f) ->
liftIO $
runResourceT $ withInternalState $ \resState -> do
newStateIORef <- liftIO (I.newIORef newState)
let newHandlerData =
HandlerData
{ handlerRequest = newReq
, handlerEnv = oldEnv
, handlerState = newStateIORef
, handlerToParent = const ()
, handlerResource = resState
}
liftIO (f newHandlerData)
forkHandler :: (SomeException -> HandlerT site IO ())
-> HandlerT site IO ()
-> HandlerT site IO ()
forkHandler onErr handler = do
yesRunner <- handlerToIO
void $ liftResourceT $ resourceForkIO $ yesRunner $ handle onErr handler
redirect :: (MonadHandler m, RedirectUrl (HandlerSite m) url)
=> url -> m a
redirect url = do
req <- waiRequest
let status =
if W.httpVersion req == H.http11
then H.status303
else H.status302
redirectWith status url
redirectWith :: (MonadHandler m, RedirectUrl (HandlerSite m) url)
=> H.Status
-> url
-> m a
redirectWith status url = do
urlText <- toTextUrl url
handlerError $ HCRedirect status urlText
ultDestKey :: Text
ultDestKey = "_ULT"
setUltDest :: (MonadHandler m, RedirectUrl (HandlerSite m) url)
=> url
-> m ()
setUltDest url = do
urlText <- toTextUrl url
setSession ultDestKey urlText
setUltDestCurrent :: MonadHandler m => m ()
setUltDestCurrent = do
route <- getCurrentRoute
case route of
Nothing -> return ()
Just r -> do
gets' <- reqGetParams `liftM` getRequest
setUltDest (r, gets')
setUltDestReferer :: MonadHandler m => m ()
setUltDestReferer = do
mdest <- lookupSession ultDestKey
maybe
(waiRequest >>= maybe (return ()) setUltDestBS . lookup "referer" . W.requestHeaders)
(const $ return ())
mdest
where
setUltDestBS = setUltDest . T.pack . S8.unpack
redirectUltDest :: (RedirectUrl (HandlerSite m) url, MonadHandler m)
=> url
-> m a
redirectUltDest defaultDestination = do
mdest <- lookupSession ultDestKey
deleteSession ultDestKey
maybe (redirect defaultDestination) redirect mdest
clearUltDest :: MonadHandler m => m ()
clearUltDest = deleteSession ultDestKey
msgKey :: Text
msgKey = "_MSG"
setMessage :: MonadHandler m => Html -> m ()
setMessage = setSession msgKey . T.concat . TL.toChunks . RenderText.renderHtml
setMessageI :: (MonadHandler m, RenderMessage (HandlerSite m) msg)
=> msg -> m ()
setMessageI msg = do
mr <- getMessageRender
setMessage $ toHtml $ mr msg
getMessage :: MonadHandler m => m (Maybe Html)
getMessage = do
mmsg <- liftM (fmap preEscapedToMarkup) $ lookupSession msgKey
deleteSession msgKey
return mmsg
sendFile :: MonadHandler m => ContentType -> FilePath -> m a
sendFile ct fp = handlerError $ HCSendFile ct fp Nothing
sendFilePart :: MonadHandler m
=> ContentType
-> FilePath
-> Integer
-> Integer
-> m a
sendFilePart ct fp off count = do
fs <- liftIO $ PC.getFileStatus fp
handlerError $ HCSendFile ct fp $ Just W.FilePart
{ W.filePartOffset = off
, W.filePartByteCount = count
, W.filePartFileSize = fromIntegral $ PC.fileSize fs
}
sendResponse :: (MonadHandler m, ToTypedContent c) => c -> m a
sendResponse = handlerError . HCContent H.status200 . toTypedContent
sendResponseStatus :: (MonadHandler m, ToTypedContent c) => H.Status -> c -> m a
sendResponseStatus s = handlerError . HCContent s . toTypedContent
sendResponseCreated :: MonadHandler m => Route (HandlerSite m) -> m a
sendResponseCreated url = do
r <- getUrlRender
handlerError $ HCCreated $ r url
sendWaiResponse :: MonadHandler m => W.Response -> m b
sendWaiResponse = handlerError . HCWai
sendWaiApplication :: MonadHandler m => W.Application -> m b
sendWaiApplication = handlerError . HCWaiApp
sendRawResponseNoConduit
:: (MonadHandler m, MonadBaseControl IO m)
=> (IO S8.ByteString -> (S8.ByteString -> IO ()) -> m ())
-> m a
sendRawResponseNoConduit raw = control $ \runInIO ->
liftIO $ throwIO $ HCWai $ flip W.responseRaw fallback
$ \src sink -> runInIO (raw src sink) >> return ()
where
fallback = W.responseLBS H.status500 [("Content-Type", "text/plain")]
"sendRawResponse: backend does not support raw responses"
sendRawResponse :: (MonadHandler m, MonadBaseControl IO m)
=> (Source IO S8.ByteString -> Sink S8.ByteString IO () -> m ())
-> m a
sendRawResponse raw = control $ \runInIO ->
liftIO $ throwIO $ HCWai $ flip W.responseRaw fallback
$ \src sink -> runInIO (raw (src' src) (CL.mapM_ sink)) >> return ()
where
fallback = W.responseLBS H.status500 [("Content-Type", "text/plain")]
"sendRawResponse: backend does not support raw responses"
src' src = do
bs <- liftIO src
unless (S.null bs) $ do
yield bs
src' src
notModified :: MonadHandler m => m a
notModified = sendWaiResponse $ W.responseBuilder H.status304 [] mempty
notFound :: MonadHandler m => m a
notFound = hcError NotFound
badMethod :: MonadHandler m => m a
badMethod = do
w <- waiRequest
hcError $ BadMethod $ W.requestMethod w
notAuthenticated :: MonadHandler m => m a
notAuthenticated = hcError NotAuthenticated
permissionDenied :: MonadHandler m => Text -> m a
permissionDenied = hcError . PermissionDenied
permissionDeniedI :: (RenderMessage (HandlerSite m) msg, MonadHandler m)
=> msg
-> m a
permissionDeniedI msg = do
mr <- getMessageRender
permissionDenied $ mr msg
invalidArgs :: MonadHandler m => [Text] -> m a
invalidArgs = hcError . InvalidArgs
invalidArgsI :: (MonadHandler m, RenderMessage (HandlerSite m) msg) => [msg] -> m a
invalidArgsI msg = do
mr <- getMessageRender
invalidArgs $ map mr msg
setCookie :: MonadHandler m => SetCookie -> m ()
setCookie = addHeaderInternal . AddCookie
getExpires :: MonadIO m
=> Int
-> m UTCTime
getExpires m = do
now <- liftIO getCurrentTime
return $ fromIntegral (m * 60) `addUTCTime` now
deleteCookie :: MonadHandler m
=> Text
-> Text
-> m ()
deleteCookie a = addHeaderInternal . DeleteCookie (encodeUtf8 a) . encodeUtf8
setLanguage :: MonadHandler m => Text -> m ()
setLanguage = setSession langKey
addHeader :: MonadHandler m => Text -> Text -> m ()
addHeader a = addHeaderInternal . Header (encodeUtf8 a) . encodeUtf8
setHeader :: MonadHandler m => Text -> Text -> m ()
setHeader = addHeader
cacheSeconds :: MonadHandler m => Int -> m ()
cacheSeconds i = setHeader "Cache-Control" $ T.concat
[ "max-age="
, T.pack $ show i
, ", public"
]
neverExpires :: MonadHandler m => m ()
neverExpires = do
askHandlerEnv >>= liftIO . rheGetMaxExpires >>= setHeader "Expires"
cacheSeconds oneYear
where
oneYear :: Int
oneYear = 60 * 60 * 24 * 365
alreadyExpired :: MonadHandler m => m ()
alreadyExpired = setHeader "Expires" "Thu, 01 Jan 1970 05:05:05 GMT"
expiresAt :: MonadHandler m => UTCTime -> m ()
expiresAt = setHeader "Expires" . formatRFC1123
setEtag :: MonadHandler m => Text -> m ()
setEtag etag = do
mmatch <- lookupHeader "if-none-match"
let matches = maybe [] parseMatch mmatch
if encodeUtf8 etag `elem` matches
then notModified
else addHeader "etag" $ T.concat ["\"", etag, "\""]
parseMatch :: S.ByteString -> [S.ByteString]
parseMatch =
map clean . S.split W8._comma
where
clean = stripQuotes . fst . S.spanEnd W8.isSpace . S.dropWhile W8.isSpace
stripQuotes bs
| S.length bs >= 2 && S.head bs == W8._quotedbl && S.last bs == W8._quotedbl
= S.init $ S.tail bs
| otherwise = bs
setSession :: MonadHandler m
=> Text
-> Text
-> m ()
setSession k = setSessionBS k . encodeUtf8
setSessionBS :: MonadHandler m
=> Text
-> S.ByteString
-> m ()
setSessionBS k = modify . modSession . Map.insert k
deleteSession :: MonadHandler m => Text -> m ()
deleteSession = modify . modSession . Map.delete
clearSession :: MonadHandler m => m ()
clearSession = modify $ \x -> x { ghsSession = Map.empty }
modSession :: (SessionMap -> SessionMap) -> GHState -> GHState
modSession f x = x { ghsSession = f $ ghsSession x }
addHeaderInternal :: MonadHandler m => Header -> m ()
addHeaderInternal = tell . Endo . (:)
class RedirectUrl master a where
toTextUrl :: (MonadHandler m, HandlerSite m ~ master) => a -> m Text
instance RedirectUrl master Text where
toTextUrl = return
instance RedirectUrl master String where
toTextUrl = toTextUrl . T.pack
instance RedirectUrl master (Route master) where
toTextUrl url = do
r <- getUrlRender
return $ r url
instance (key ~ Text, val ~ Text) => RedirectUrl master (Route master, [(key, val)]) where
toTextUrl (url, params) = do
r <- getUrlRenderParams
return $ r url params
instance (key ~ Text, val ~ Text) => RedirectUrl master (Route master, Map.Map key val) where
toTextUrl (url, params) = toTextUrl (url, Map.toList params)
data Fragment a b = a :#: b deriving (Show, Typeable)
instance (RedirectUrl master a, PathPiece b) => RedirectUrl master (Fragment a b) where
toTextUrl (a :#: b) = (\ua -> T.concat [ua, "#", toPathPiece b]) <$> toTextUrl a
lookupSession :: MonadHandler m => Text -> m (Maybe Text)
lookupSession = (liftM . fmap) (decodeUtf8With lenientDecode) . lookupSessionBS
lookupSessionBS :: MonadHandler m => Text -> m (Maybe S.ByteString)
lookupSessionBS n = do
m <- liftM ghsSession get
return $ Map.lookup n m
getSession :: MonadHandler m => m SessionMap
getSession = liftM ghsSession get
newIdent :: MonadHandler m => m Text
newIdent = do
x <- get
let i' = ghsIdent x + 1
put x { ghsIdent = i' }
return $ T.pack $ "hident" ++ show i'
redirectToPost :: (MonadHandler m, RedirectUrl (HandlerSite m) url)
=> url
-> m a
redirectToPost url = do
urlText <- toTextUrl url
withUrlRenderer [hamlet|
$newline never
$doctype 5
<html>
<head>
<title>Redirecting...
<body onload="document.getElementById('form').submit()">
<form id="form" method="post" action=#{urlText}>
<noscript>
<p>Javascript has been disabled; please click on the button below to be redirected.
<input type="submit" value="Continue">
|] >>= sendResponse
hamletToRepHtml :: MonadHandler m => HtmlUrl (Route (HandlerSite m)) -> m Html
hamletToRepHtml = withUrlRenderer
giveUrlRenderer :: MonadHandler m
=> ((Route (HandlerSite m) -> [(Text, Text)] -> Text) -> output)
-> m output
giveUrlRenderer = withUrlRenderer
withUrlRenderer :: MonadHandler m
=> ((Route (HandlerSite m) -> [(Text, Text)] -> Text) -> output)
-> m output
withUrlRenderer f = do
render <- getUrlRenderParams
return $ f render
waiRequest :: MonadHandler m => m W.Request
waiRequest = reqWaiRequest `liftM` getRequest
getMessageRender :: (MonadHandler m, RenderMessage (HandlerSite m) message)
=> m (message -> Text)
getMessageRender = do
env <- askHandlerEnv
l <- reqLangs `liftM` getRequest
return $ renderMessage (rheSite env) l
cached :: (MonadHandler m, Typeable a)
=> m a
-> m a
cached action = do
gs <- get
eres <- Cache.cached (ghsCache gs) action
case eres of
Right res -> return res
Left (newCache, res) -> do
put $ gs { ghsCache = newCache }
return res
cachedBy :: (MonadHandler m, Typeable a) => S.ByteString -> m a -> m a
cachedBy k action = do
gs <- get
eres <- Cache.cachedBy (ghsCacheBy gs) k action
case eres of
Right res -> return res
Left (newCache, res) -> do
put $ gs { ghsCacheBy = newCache }
return res
languages :: MonadHandler m => m [Text]
languages = reqLangs `liftM` getRequest
lookup' :: Eq a => a -> [(a, b)] -> [b]
lookup' a = map snd . filter (\x -> a == fst x)
lookupHeader :: MonadHandler m => CI S8.ByteString -> m (Maybe S8.ByteString)
lookupHeader = liftM listToMaybe . lookupHeaders
lookupHeaders :: MonadHandler m => CI S8.ByteString -> m [S8.ByteString]
lookupHeaders key = do
req <- waiRequest
return $ lookup' key $ W.requestHeaders req
lookupBasicAuth :: (MonadHandler m) => m (Maybe (Text, Text))
lookupBasicAuth = fmap (>>= getBA)
(lookupHeader "Authorization")
where
getBA bs = (\(x, y) -> ( decodeUtf8With lenientDecode x
, decodeUtf8With lenientDecode y))
<$> extractBasicAuth bs
lookupBearerAuth :: (MonadHandler m) => m (Maybe Text)
lookupBearerAuth = fmap (>>= getBR)
(lookupHeader "Authorization")
where
getBR bs = decodeUtf8With lenientDecode
<$> extractBearerAuth bs
lookupGetParams :: MonadHandler m => Text -> m [Text]
lookupGetParams pn = do
rr <- getRequest
return $ lookup' pn $ reqGetParams rr
lookupGetParam :: MonadHandler m => Text -> m (Maybe Text)
lookupGetParam = liftM listToMaybe . lookupGetParams
lookupPostParams :: (MonadResource m, MonadHandler m) => Text -> m [Text]
lookupPostParams pn = do
(pp, _) <- runRequestBody
return $ lookup' pn pp
lookupPostParam :: (MonadResource m, MonadHandler m)
=> Text
-> m (Maybe Text)
lookupPostParam = liftM listToMaybe . lookupPostParams
lookupFile :: (MonadHandler m, MonadResource m)
=> Text
-> m (Maybe FileInfo)
lookupFile = liftM listToMaybe . lookupFiles
lookupFiles :: (MonadHandler m, MonadResource m)
=> Text
-> m [FileInfo]
lookupFiles pn = do
(_, files) <- runRequestBody
return $ lookup' pn files
lookupCookie :: MonadHandler m => Text -> m (Maybe Text)
lookupCookie = liftM listToMaybe . lookupCookies
lookupCookies :: MonadHandler m => Text -> m [Text]
lookupCookies pn = do
rr <- getRequest
return $ lookup' pn $ reqCookies rr
selectRep :: MonadHandler m
=> Writer.Writer (Endo [ProvidedRep m]) ()
-> m TypedContent
selectRep w = do
cts <- liftM reqAccept getRequest
case mapMaybe tryAccept cts of
[] ->
case reps of
[] -> sendResponseStatus H.status500 ("No reps provided to selectRep" :: Text)
rep:_ ->
if null cts
then returnRep rep
else sendResponseStatus H.status406 explainUnaccepted
rep:_ -> returnRep rep
where
explainUnaccepted :: Text
explainUnaccepted = "no match found for accept header"
returnRep (ProvidedRep ct mcontent) =
mcontent >>= return . TypedContent ct
reps = appEndo (Writer.execWriter w) []
repMap = Map.unions $ map (\v@(ProvidedRep k _) -> Map.fromList
[ (k, v)
, (noSpace k, v)
, (simpleContentType k, v)
]) reps
mainTypeMap = Map.fromList $ reverse $ map
(\v@(ProvidedRep ct _) -> (fst $ contentTypeTypes ct, v)) reps
tryAccept ct =
if subType == "*"
then if mainType == "*"
then headMay reps
else Map.lookup mainType mainTypeMap
else lookupAccept ct
where
(mainType, subType) = contentTypeTypes ct
lookupAccept ct = Map.lookup ct repMap <|>
Map.lookup (noSpace ct) repMap <|>
Map.lookup (simpleContentType ct) repMap
noSpace = S8.filter (/= ' ')
data ProvidedRep m = ProvidedRep !ContentType !(m Content)
provideRep :: (Monad m, HasContentType a)
=> m a
-> Writer.Writer (Endo [ProvidedRep m]) ()
provideRep handler = provideRepType (getContentType handler) handler
provideRepType :: (Monad m, ToContent a)
=> ContentType
-> m a
-> Writer.Writer (Endo [ProvidedRep m]) ()
provideRepType ct handler =
Writer.tell $ Endo $ (ProvidedRep ct (liftM toContent handler):)
rawRequestBody :: MonadHandler m => Source m S.ByteString
rawRequestBody = do
req <- lift waiRequest
let loop = do
bs <- liftIO $ W.requestBody req
unless (S.null bs) $ do
yield bs
loop
loop
fileSource :: MonadResource m => FileInfo -> Source m S.ByteString
fileSource = transPipe liftResourceT . fileSourceRaw
respond :: (Monad m, ToContent a) => ContentType -> a -> m TypedContent
respond ct = return . TypedContent ct . toContent
respondSource :: ContentType
-> Source (HandlerT site IO) (Flush Builder)
-> HandlerT site IO TypedContent
respondSource ctype src = HandlerT $ \hd ->
return $ TypedContent ctype $ ContentSource
$ transPipe (lift . flip unHandlerT hd) src
sendChunk :: Monad m => ToFlushBuilder a => a -> Producer m (Flush Builder)
sendChunk = yield . toFlushBuilder
sendFlush :: Monad m => Producer m (Flush Builder)
sendFlush = yield Flush
sendChunkBS :: Monad m => S.ByteString -> Producer m (Flush Builder)
sendChunkBS = sendChunk
sendChunkLBS :: Monad m => L.ByteString -> Producer m (Flush Builder)
sendChunkLBS = sendChunk
sendChunkText :: Monad m => T.Text -> Producer m (Flush Builder)
sendChunkText = sendChunk
sendChunkLazyText :: Monad m => TL.Text -> Producer m (Flush Builder)
sendChunkLazyText = sendChunk
sendChunkHtml :: Monad m => Html -> Producer m (Flush Builder)
sendChunkHtml = sendChunk
stripHandlerT :: HandlerT child (HandlerT parent m) a
-> (parent -> child)
-> (Route child -> Route parent)
-> Maybe (Route child)
-> HandlerT parent m a
stripHandlerT (HandlerT f) getSub toMaster newRoute = HandlerT $ \hd -> do
let env = handlerEnv hd
($ hd) $ unHandlerT $ f hd
{ handlerEnv = env
{ rheSite = getSub $ rheSite env
, rheRoute = newRoute
, rheRender = \url params -> rheRender env (toMaster url) params
}
, handlerToParent = toMaster
}
defaultCsrfCookieName :: S8.ByteString
defaultCsrfCookieName = "XSRF-TOKEN"
setCsrfCookie :: MonadHandler m => m ()
setCsrfCookie = setCsrfCookieWithCookie def { setCookieName = defaultCsrfCookieName }
setCsrfCookieWithCookie :: MonadHandler m => SetCookie -> m ()
setCsrfCookieWithCookie cookie = do
mCsrfToken <- reqToken <$> getRequest
Fold.forM_ mCsrfToken (\token -> setCookie $ cookie { setCookieValue = encodeUtf8 token })
defaultCsrfHeaderName :: CI S8.ByteString
defaultCsrfHeaderName = "X-XSRF-TOKEN"
checkCsrfHeaderNamed :: MonadHandler m => CI S8.ByteString -> m ()
checkCsrfHeaderNamed headerName = do
valid <- hasValidCsrfHeaderNamed headerName
unless valid (permissionDenied csrfErrorMessage)
hasValidCsrfHeaderNamed :: MonadHandler m => CI S8.ByteString -> m Bool
hasValidCsrfHeaderNamed headerName = do
mCsrfToken <- reqToken <$> getRequest
mXsrfHeader <- lookupHeader headerName
return $ validCsrf mCsrfToken mXsrfHeader
defaultCsrfParamName :: Text
defaultCsrfParamName = "_token"
checkCsrfParamNamed :: MonadHandler m => Text -> m ()
checkCsrfParamNamed paramName = do
valid <- hasValidCsrfParamNamed paramName
unless valid (permissionDenied csrfErrorMessage)
hasValidCsrfParamNamed :: MonadHandler m => Text -> m Bool
hasValidCsrfParamNamed paramName = do
mCsrfToken <- reqToken <$> getRequest
mCsrfParam <- lookupPostParam paramName
return $ validCsrf mCsrfToken (encodeUtf8 <$> mCsrfParam)
checkCsrfHeaderOrParam :: MonadHandler m
=> CI S8.ByteString
-> Text
-> m ()
checkCsrfHeaderOrParam headerName paramName = do
validHeader <- hasValidCsrfHeaderNamed headerName
validParam <- hasValidCsrfParamNamed paramName
unless (validHeader || validParam) (permissionDenied csrfErrorMessage)
validCsrf :: Maybe Text -> Maybe S.ByteString -> Bool
validCsrf (Just token) (Just param) = encodeUtf8 token `constEqBytes` param
validCsrf Nothing _param = True
validCsrf (Just _token) Nothing = False
csrfErrorMessage :: Text
csrfErrorMessage = "A valid CSRF token wasn't present in HTTP headers or POST parameters. Check the Yesod.Core.Handler docs of the yesod-core package for details on CSRF protection."