commit ghc-yesod-core for openSUSE:Factory
Hello community,
here is the log from the commit of package ghc-yesod-core for openSUSE:Factory checked in at 2016-07-07 15:10:29
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/ghc-yesod-core (Old)
and /work/SRC/openSUSE:Factory/.ghc-yesod-core.new (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "ghc-yesod-core"
Changes:
--------
--- /work/SRC/openSUSE:Factory/ghc-yesod-core/ghc-yesod-core.changes 2016-07-01 09:58:53.000000000 +0200
+++ /work/SRC/openSUSE:Factory/.ghc-yesod-core.new/ghc-yesod-core.changes 2016-07-07 15:10:29.000000000 +0200
@@ -1,0 +2,6 @@
+Thu Jun 30 13:28:15 UTC 2016 - mimi.vx@gmail.com
+
+- update to 1.4.22
+* Proper handling of impure exceptions within HandlerError values
+
+-------------------------------------------------------------------
Old:
----
yesod-core-1.4.21.tar.gz
New:
----
yesod-core-1.4.22.tar.gz
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Other differences:
------------------
++++++ ghc-yesod-core.spec ++++++
--- /var/tmp/diff_new_pack.GNQHCg/_old 2016-07-07 15:10:31.000000000 +0200
+++ /var/tmp/diff_new_pack.GNQHCg/_new 2016-07-07 15:10:31.000000000 +0200
@@ -19,7 +19,7 @@
%global pkg_name yesod-core
%bcond_with tests
Name: ghc-%{pkg_name}
-Version: 1.4.21
+Version: 1.4.22
Release: 0
Summary: Creation of type-safe, RESTful web applications
License: MIT
@@ -44,6 +44,7 @@
BuildRequires: ghc-cookie-devel
BuildRequires: ghc-data-default-devel
BuildRequires: ghc-deepseq-devel
+BuildRequires: ghc-deepseq-generics-devel
BuildRequires: ghc-directory-devel
BuildRequires: ghc-exceptions-devel
BuildRequires: ghc-fast-logger-devel
++++++ yesod-core-1.4.21.tar.gz -> yesod-core-1.4.22.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/yesod-core-1.4.21/ChangeLog.md new/yesod-core-1.4.22/ChangeLog.md
--- old/yesod-core-1.4.21/ChangeLog.md 2016-06-20 16:31:06.000000000 +0200
+++ new/yesod-core-1.4.22/ChangeLog.md 2016-06-27 09:45:17.000000000 +0200
@@ -1,3 +1,7 @@
+## 1.4.22
+
+* Proper handling of impure exceptions within `HandlerError` values
+
## 1.4.21
* Add support for `Encoding` from `aeson-0.11` [#1241](https://github.com/yesodweb/yesod/pull/1241)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/yesod-core-1.4.21/Yesod/Core/Internal/Run.hs new/yesod-core-1.4.22/Yesod/Core/Internal/Run.hs
--- old/yesod-core-1.4.21/Yesod/Core/Internal/Run.hs 2016-06-20 16:31:06.000000000 +0200
+++ new/yesod-core-1.4.22/Yesod/Core/Internal/Run.hs 2016-06-27 09:45:17.000000000 +0200
@@ -4,43 +4,36 @@
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE TemplateHaskell #-}
+{-# LANGUAGE TupleSections #-}
{-# LANGUAGE FlexibleContexts #-}
module Yesod.Core.Internal.Run where
#if __GLASGOW_HASKELL__ < 710
-import Data.Monoid (mempty)
+import Data.Monoid (Monoid, mempty)
#endif
import Yesod.Core.Internal.Response
import Blaze.ByteString.Builder (toByteString)
import Control.Exception (fromException, evaluate)
import qualified Control.Exception as E
-import Control.Exception.Lifted (catch)
-import Control.Monad (mplus)
-import Control.Monad.IO.Class (MonadIO)
-import Control.Monad.IO.Class (liftIO)
+import Control.Monad.IO.Class (MonadIO, liftIO)
import Control.Monad.Logger (LogLevel (LevelError), LogSource,
liftLoc)
-import Control.Monad.Trans.Resource (runResourceT, withInternalState, runInternalState)
+import Control.Monad.Trans.Resource (runResourceT, withInternalState, runInternalState, InternalState)
import qualified Data.ByteString as S
import qualified Data.ByteString.Char8 as S8
import qualified Data.IORef as I
import qualified Data.Map as Map
-import Data.Maybe (isJust)
-import Data.Maybe (fromMaybe)
+import Data.Maybe (isJust, fromMaybe)
import Data.Monoid (appEndo)
import Data.Text (Text)
import qualified Data.Text as T
-import Data.Text.Encoding (encodeUtf8)
-import Data.Text.Encoding (decodeUtf8With)
+import Data.Text.Encoding (encodeUtf8, decodeUtf8With)
import Data.Text.Encoding.Error (lenientDecode)
import Language.Haskell.TH.Syntax (Loc, qLocation)
import qualified Network.HTTP.Types as H
import Network.Wai
import Network.Wai.Internal
-#if !MIN_VERSION_base(4, 6, 0)
-import Prelude hiding (catch)
-#endif
import System.Log.FastLogger (LogStr, toLogStr)
import Yesod.Core.Content
import Yesod.Core.Class.Yesod
@@ -49,31 +42,73 @@
tooLargeResponse)
import Yesod.Core.Internal.Util (getCurrentMaxExpiresRFC1123)
import Yesod.Routes.Class (Route, renderRoute)
-import Control.DeepSeq (($!!))
+import Control.DeepSeq (($!!), NFData)
-returnDeepSessionMap :: Monad m => SessionMap -> m SessionMap
-#if MIN_VERSION_bytestring(0, 10, 0)
-returnDeepSessionMap sm = return $!! sm
-#else
-returnDeepSessionMap sm = fmap unWrappedBS `liftM` (return $!! fmap WrappedBS sm)
-
--- | Work around missing NFData instance for bytestring 0.9.
-newtype WrappedBS = WrappedBS { unWrappedBS :: S8.ByteString }
-instance NFData WrappedBS
-#endif
+-- | Catch all synchronous exceptions, ignoring asynchronous
+-- exceptions.
+--
+-- Ideally we'd use this from a different library
+catchSync :: IO a -> (E.SomeException -> IO a) -> IO a
+catchSync thing after = thing `E.catch` \e ->
+ if isAsyncException e
+ then E.throwIO e
+ else after e
--- | Function used internally by Yesod in the process of converting a
--- 'HandlerT' into an 'Application'. Should not be needed by users.
-runHandler :: ToTypedContent c
- => RunHandlerEnv site
- -> HandlerT site IO c
- -> YesodApp
-runHandler rhe@RunHandlerEnv {..} handler yreq = withInternalState $ \resState -> do
- let toErrorHandler e =
+-- | Determine if an exception is asynchronous
+--
+-- Also worth being upstream
+isAsyncException :: E.SomeException -> Bool
+isAsyncException e =
+ case fromException e of
+ Just E.SomeAsyncException{} -> True
+ Nothing -> False
+
+-- | Convert an exception into an ErrorResponse
+toErrorHandler :: E.SomeException -> IO ErrorResponse
+toErrorHandler e0 = flip catchSync errFromShow $
+ case fromException e0 of
+ Just (HCError x) -> evaluate $!! x
+ _
+ | isAsyncException e0 -> E.throwIO e0
+ | otherwise -> errFromShow e0
+
+-- | Generate an @ErrorResponse@ based on the shown version of the exception
+errFromShow :: E.SomeException -> IO ErrorResponse
+errFromShow x = evaluate $!! InternalError $! T.pack $! show x
+
+-- | Do a basic run of a handler, getting some contents and the final
+-- @GHState@. The @GHState@ unfortunately may contain some impure
+-- exceptions, but all other synchronous exceptions will be caught and
+-- represented by the @HandlerContents@.
+basicRunHandler :: ToTypedContent c
+ => RunHandlerEnv site
+ -> HandlerT site IO c
+ -> YesodRequest
+ -> InternalState
+ -> IO (GHState, HandlerContents)
+basicRunHandler rhe handler yreq resState = do
+ -- Create a mutable ref to hold the state. We use mutable refs so
+ -- that the updates will survive runtime exceptions.
+ istate <- I.newIORef defState
+
+ -- Run the handler itself, capturing any runtime exceptions and
+ -- converting them into a @HandlerContents@
+ contents' <- catchSync
+ (do
+ res <- unHandlerT handler (hd istate)
+ tc <- evaluate (toTypedContent res)
+ -- Success! Wrap it up in an @HCContent@
+ return (HCContent defaultStatus tc))
+ (\e ->
case fromException e of
- Just (HCError x) -> x
- _ -> InternalError $ T.pack $ show e
- istate <- liftIO $ I.newIORef GHState
+ Just e' -> return e'
+ Nothing -> fmap HCError $ toErrorHandler e)
+
+ -- Get the raw state and return
+ state <- I.readIORef istate
+ return (state, contents')
+ where
+ defState = GHState
{ ghsSession = reqSession yreq
, ghsRBC = Nothing
, ghsIdent = 1
@@ -81,56 +116,57 @@
, ghsCacheBy = mempty
, ghsHeaders = mempty
}
- let hd = HandlerData
- { handlerRequest = yreq
- , handlerEnv = rhe
- , handlerState = istate
- , handlerToParent = const ()
- , handlerResource = resState
+ hd istate = HandlerData
+ { handlerRequest = yreq
+ , handlerEnv = rhe
+ , handlerState = istate
+ , handlerToParent = const ()
+ , handlerResource = resState
+ }
+
+-- | Convert an @ErrorResponse@ into a @YesodResponse@
+handleError :: RunHandlerEnv site
+ -> YesodRequest
+ -> InternalState
+ -> Map.Map Text S8.ByteString
+ -> [Header]
+ -> ErrorResponse
+ -> IO YesodResponse
+handleError rhe yreq resState finalSession headers e0 = do
+ -- Find any evil hidden impure exceptions
+ e <- (evaluate $!! e0) `catchSync` errFromShow
+
+ -- Generate a response, leveraging the updated session and
+ -- response headers
+ flip runInternalState resState $ do
+ yar <- rheOnError rhe e yreq
+ { reqSession = finalSession
}
- contents' <- catch (fmap Right $ unHandlerT handler hd)
- (\e -> return $ Left $ maybe (HCError $ toErrorHandler e) id
- $ fromException e)
- state <- liftIO $ I.readIORef istate
-
- (finalSession, mcontents1) <- (do
- finalSession <- returnDeepSessionMap (ghsSession state)
- return (finalSession, Nothing)) `E.catch` \e -> return
- (Map.empty, Just $! HCError $! InternalError $! T.pack $! show (e :: E.SomeException))
-
- (headers, mcontents2) <- (do
- headers <- return $!! appEndo (ghsHeaders state) []
- return (headers, Nothing)) `E.catch` \e -> return
- ([], Just $! HCError $! InternalError $! T.pack $! show (e :: E.SomeException))
-
- let contents =
- case mcontents1 `mplus` mcontents2 of
- Just x -> x
- Nothing -> either id (HCContent defaultStatus . toTypedContent) contents'
- let handleError e = flip runInternalState resState $ do
- yar <- rheOnError e yreq
- { reqSession = finalSession
- }
- case yar of
- YRPlain status' hs ct c sess ->
- let hs' = headers ++ hs
- status
- | status' == defaultStatus = getStatus e
- | otherwise = status'
- in return $ YRPlain status hs' ct c sess
- YRWai _ -> return yar
- YRWaiApp _ -> return yar
- let sendFile' ct fp p =
- return $ YRPlain H.status200 headers ct (ContentFile fp p) finalSession
- contents1 <- evaluate contents `E.catch` \e -> return
- (HCError $! InternalError $! T.pack $! show (e :: E.SomeException))
- case contents1 of
+ case yar of
+ YRPlain status' hs ct c sess ->
+ let hs' = headers ++ hs
+ status
+ | status' == defaultStatus = getStatus e
+ | otherwise = status'
+ in return $ YRPlain status hs' ct c sess
+ YRWai _ -> return yar
+ YRWaiApp _ -> return yar
+
+-- | Convert a @HandlerContents@ into a @YesodResponse@
+handleContents :: (ErrorResponse -> IO YesodResponse)
+ -> Map.Map Text S8.ByteString
+ -> [Header]
+ -> HandlerContents
+ -> IO YesodResponse
+handleContents handleError' finalSession headers contents =
+ case contents of
HCContent status (TypedContent ct c) -> do
- ec' <- liftIO $ evaluateContent c
+ -- Check for impure exceptions hiding in the contents
+ ec' <- evaluateContent c
case ec' of
- Left e -> handleError e
+ Left e -> handleError' e
Right c' -> return $ YRPlain status headers ct c' finalSession
- HCError e -> handleError e
+ HCError e -> handleError' e
HCRedirect status loc -> do
let disable_caching x =
Header "Cache-Control" "no-cache, must-revalidate"
@@ -141,20 +177,54 @@
return $ YRPlain
status hs typePlain emptyContent
finalSession
- HCSendFile ct fp p -> catch
- (sendFile' ct fp p)
- (handleError . toErrorHandler)
- HCCreated loc -> do
- let hs = Header "Location" (encodeUtf8 loc) : headers
- return $ YRPlain
- H.status201
- hs
- typePlain
- emptyContent
- finalSession
+ HCSendFile ct fp p -> return $ YRPlain
+ H.status200
+ headers
+ ct
+ (ContentFile fp p)
+ finalSession
+ HCCreated loc -> return $ YRPlain
+ H.status201
+ (Header "Location" (encodeUtf8 loc) : headers)
+ typePlain
+ emptyContent
+ finalSession
HCWai r -> return $ YRWai r
HCWaiApp a -> return $ YRWaiApp a
+-- | Evaluate the given value. If an exception is thrown, use it to
+-- replace the provided contents and then return @mempty@ in place of the
+-- evaluated value.
+evalFallback :: (Monoid w, NFData w)
+ => HandlerContents
+ -> w
+ -> IO (w, HandlerContents)
+evalFallback contents val = catchSync
+ (fmap (, contents) (evaluate $!! val))
+ (fmap ((mempty, ) . HCError) . toErrorHandler)
+
+-- | Function used internally by Yesod in the process of converting a
+-- 'HandlerT' into an 'Application'. Should not be needed by users.
+runHandler :: ToTypedContent c
+ => RunHandlerEnv site
+ -> HandlerT site IO c
+ -> YesodApp
+runHandler rhe@RunHandlerEnv {..} handler yreq = withInternalState $ \resState -> do
+ -- Get the raw state and original contents
+ (state, contents0) <- basicRunHandler rhe handler yreq resState
+
+ -- Evaluate the unfortunately-lazy session and headers,
+ -- propagating exceptions into the contents
+ (finalSession, contents1) <- evalFallback contents0 (ghsSession state)
+ (headers, contents2) <- evalFallback contents1 (appEndo (ghsHeaders state) [])
+
+ -- Convert the HandlerContents into the final YesodResponse
+ handleContents
+ (handleError rhe yreq resState finalSession headers)
+ finalSession
+ headers
+ contents2
+
safeEh :: (Loc -> LogSource -> LogLevel -> LogStr -> IO ())
-> ErrorResponse
-> YesodApp
@@ -197,8 +267,7 @@
runFakeHandler fakeSessionMap logger site handler = liftIO $ do
ret <- I.newIORef (Left $ InternalError "runFakeHandler: no result")
maxExpires <- getCurrentMaxExpiresRFC1123
- let handler' = do liftIO . I.writeIORef ret . Right =<< handler
- return ()
+ let handler' = liftIO . I.writeIORef ret . Right =<< handler
let yapp = runHandler
RunHandlerEnv
{ rheRender = yesodRender site $ resolveApproot site fakeWaiRequest
@@ -233,6 +302,10 @@
, vault = mempty
, requestBodyLength = KnownLength 0
, requestHeaderRange = Nothing
+#if MIN_VERSION_wai(3,2,0)
+ , requestHeaderReferer = Nothing
+ , requestHeaderUserAgent = Nothing
+#endif
}
fakeRequest =
YesodRequest
@@ -256,7 +329,7 @@
| Just maxLen <- mmaxLen, KnownLength len <- requestBodyLength req, maxLen < len = sendResponse tooLargeResponse
| otherwise = do
let dontSaveSession _ = return []
- (session, saveSession) <- liftIO $ do
+ (session, saveSession) <- liftIO $
maybe (return (Map.empty, dontSaveSession)) (\sb -> sbLoadSession sb req) yreSessionBackend
maxExpires <- yreGetMaxExpires
let mkYesodReq = parseWaiRequest req session (isJust yreSessionBackend) mmaxLen
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/yesod-core-1.4.21/Yesod/Core/Types.hs new/yesod-core-1.4.22/Yesod/Core/Types.hs
--- old/yesod-core-1.4.21/Yesod/Core/Types.hs 2016-06-20 16:31:06.000000000 +0200
+++ new/yesod-core-1.4.22/Yesod/Core/Types.hs 2016-06-27 09:45:17.000000000 +0200
@@ -1,5 +1,6 @@
{-# LANGUAGE CPP #-}
{-# LANGUAGE DeriveDataTypeable #-}
+{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
@@ -41,6 +42,7 @@
import qualified Data.Text.Lazy.Builder as TBuilder
import Data.Time (UTCTime)
import Data.Typeable (Typeable)
+import GHC.Generics (Generic)
import Language.Haskell.TH.Syntax (Loc)
import qualified Network.HTTP.Types as H
import Network.Wai (FilePart,
@@ -62,6 +64,7 @@
import Prelude hiding (catch)
#endif
import Control.DeepSeq (NFData (rnf))
+import Control.DeepSeq.Generics (genericRnf)
import Data.Conduit.Lazy (MonadActive, monadActive)
import Yesod.Core.TypeCache (TypeMap, KeyedTypeMap)
#if MIN_VERSION_monad_logger(0, 3, 10)
@@ -314,7 +317,9 @@
| NotAuthenticated
| PermissionDenied Text
| BadMethod H.Method
- deriving (Show, Eq, Typeable)
+ deriving (Show, Eq, Typeable, Generic)
+instance NFData ErrorResponse where
+ rnf = genericRnf
----- header stuff
-- | Headers to be added to a 'Result'.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/yesod-core-1.4.21/test/YesodCoreTest/Exceptions.hs new/yesod-core-1.4.22/test/YesodCoreTest/Exceptions.hs
--- old/yesod-core-1.4.21/test/YesodCoreTest/Exceptions.hs 2016-06-20 16:31:06.000000000 +0200
+++ new/yesod-core-1.4.22/test/YesodCoreTest/Exceptions.hs 2016-06-27 09:45:17.000000000 +0200
@@ -6,6 +6,8 @@
import Test.Hspec
import Yesod.Core
+import Yesod.Core.Types (HandlerContents (HCError))
+import Control.Exception (throwIO)
import Network.Wai
import Network.Wai.Test
import Network.HTTP.Types (status301)
@@ -14,11 +16,15 @@
mkYesod "Y" [parseRoutes|
/ RootR GET
/redirect RedirR GET
+/impure ImpureR GET
|]
instance Yesod Y where
approot = ApprootStatic "http://test"
- errorHandler (InternalError e) = return $ toTypedContent e
+ errorHandler (InternalError e) = do
+ _ <- return $! e
+ addHeader "ERROR" "HANDLER"
+ return $ toTypedContent e
errorHandler x = defaultErrorHandler x
getRootR :: Handler ()
@@ -29,10 +35,14 @@
addHeader "foo" "bar"
redirectWith status301 RootR
+getImpureR :: Handler ()
+getImpureR = liftIO $ throwIO $ HCError $ InternalError $ error "impure!"
+
exceptionsTest :: Spec
exceptionsTest = describe "Test.Exceptions" $ do
it "500" case500
it "redirect keeps headers" caseRedirect
+ it "deals with impure InternalError values" caseImpure
runner :: Session () -> IO ()
runner f = toWaiApp Y >>= runSession f
@@ -48,3 +58,10 @@
res <- request defaultRequest { pathInfo = ["redirect"] }
assertStatus 301 res
assertHeader "foo" "bar" res
+
+caseImpure :: IO ()
+caseImpure = runner $ do
+ res <- request defaultRequest { pathInfo = ["impure"] }
+ assertStatus 500 res
+ assertBodyContains "impure!" res
+ assertHeader "ERROR" "HANDLER" res
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/yesod-core-1.4.21/yesod-core.cabal new/yesod-core-1.4.22/yesod-core.cabal
--- old/yesod-core-1.4.21/yesod-core.cabal 2016-06-20 16:31:06.000000000 +0200
+++ new/yesod-core-1.4.22/yesod-core.cabal 2016-06-27 09:45:17.000000000 +0200
@@ -1,5 +1,5 @@
name: yesod-core
-version: 1.4.21
+version: 1.4.22
license: MIT
license-file: LICENSE
author: Michael Snoyman
participants (1)
-
root@hilbert.suse.de