diff --git a/source/Network/Xmpp.hs b/source/Network/Xmpp.hs
index 93c78bb..3744248 100644
--- a/source/Network/Xmpp.hs
+++ b/source/Network/Xmpp.hs
@@ -139,11 +139,16 @@ module Network.Xmpp
, LangTag(..)
, exampleParams
, PortID(..)
- , StreamFailure(..)
+ , XmppFailure(..)
, StreamErrorInfo(..)
, StreamErrorCondition(..)
- , TlsFailure(..)
- , AuthFailure(..)
+ , AuthFailure( AuthXmlFailure -- Does not export AuthStreamFailure
+ , AuthNoAcceptableMechanism
+ , AuthChallengeFailure
+ , AuthNoConnection
+ , AuthFailure
+ , AuthSaslFailure
+ , AuthStringPrepFailure )
) where
diff --git a/source/Network/Xmpp/Bind.hs b/source/Network/Xmpp/Bind.hs
index 4d180ce..a3676e6 100644
--- a/source/Network/Xmpp/Bind.hs
+++ b/source/Network/Xmpp/Bind.hs
@@ -1,4 +1,3 @@
-{-# LANGUAGE PatternGuards #-}
{-# LANGUAGE OverloadedStrings #-}
{-# OPTIONS_HADDOCK hide #-}
@@ -19,6 +18,8 @@ import Control.Monad.State(modify)
import Control.Concurrent.STM.TMVar
+import Control.Monad.Error
+
-- Produces a `bind' element, optionally wrapping a resource.
bindBody :: Maybe Text -> Element
bindBody = pickleElem $
@@ -30,16 +31,21 @@ bindBody = pickleElem $
-- Sends a (synchronous) IQ set request for a (`Just') given or server-generated
-- resource and extract the JID from the non-error response.
-xmppBind :: Maybe Text -> TMVar Connection -> IO Jid
-xmppBind rsrc c = do
- answer <- pushIQ' "bind" Nothing Set Nothing (bindBody rsrc) c
- jid <- case () of () | Right IQResult{iqResultPayload = Just b} <- answer
- , Right jid <- unpickleElem xpJid b
- -> return jid
- | otherwise -> throw StreamOtherFailure
- -- TODO: Log: ("Bind couldn't unpickle JID from " ++ show answer)
- withConnection (modify $ \s -> s{cJid = Just jid}) c
- return jid
+xmppBind :: Maybe Text -> TMVar Connection -> IO (Either XmppFailure Jid)
+xmppBind rsrc c = runErrorT $ do
+ answer <- ErrorT $ pushIQ' "bind" Nothing Set Nothing (bindBody rsrc) c
+ case answer of
+ Right IQResult{iqResultPayload = Just b} -> do
+ let jid = unpickleElem xpJid b
+ case jid of
+ Right jid' -> do
+ ErrorT $ withConnection (do
+ modify $ \s -> s{cJid = Just jid'}
+ return $ Right jid') c -- not pretty
+ return jid'
+ otherwise -> throwError XmppOtherFailure
+ -- TODO: Log: ("Bind couldn't unpickle JID from " ++ show answer)
+ otherwise -> throwError XmppOtherFailure
where
-- Extracts the character data in the `jid' element.
xpJid :: PU [Node] Jid
diff --git a/source/Network/Xmpp/Concurrent.hs b/source/Network/Xmpp/Concurrent.hs
index 9156b33..fa94910 100644
--- a/source/Network/Xmpp/Concurrent.hs
+++ b/source/Network/Xmpp/Concurrent.hs
@@ -35,6 +35,8 @@ import Network.Xmpp.Pickle
import Network.Xmpp.Types
import Text.Xml.Stream.Elements
+import Control.Monad.Error
+
toChans :: TChan Stanza
-> TVar IQHandlers
-> Stanza
@@ -72,16 +74,16 @@ toChans stanzaC iqHands sta = atomically $ do
-- | Creates and initializes a new Xmpp context.
-newSession :: TMVar Connection -> IO Session
-newSession con = do
- outC <- newTChanIO
- stanzaChan <- newTChanIO
- iqHandlers <- newTVarIO (Map.empty, Map.empty)
- eh <- newTVarIO $ EventHandlers { connectionClosedHandler = \_ -> return () }
+newSession :: TMVar Connection -> IO (Either XmppFailure Session)
+newSession con = runErrorT $ do
+ outC <- lift newTChanIO
+ stanzaChan <- lift newTChanIO
+ iqHandlers <- lift $ newTVarIO (Map.empty, Map.empty)
+ eh <- lift $ newTVarIO $ EventHandlers { connectionClosedHandler = \_ -> return () }
let stanzaHandler = toChans stanzaChan iqHandlers
- (kill, wLock, conState, readerThread) <- startThreadsWith stanzaHandler eh con
- writer <- forkIO $ writeWorker outC wLock
- idRef <- newTVarIO 1
+ (kill, wLock, conState, readerThread) <- ErrorT $ startThreadsWith stanzaHandler eh con
+ writer <- lift $ forkIO $ writeWorker outC wLock
+ idRef <- lift $ newTVarIO 1
let getId = atomically $ do
curId <- readTVar idRef
writeTVar idRef (curId + 1 :: Integer)
diff --git a/source/Network/Xmpp/Concurrent/Monad.hs b/source/Network/Xmpp/Concurrent/Monad.hs
index 863c985..ff0f07a 100644
--- a/source/Network/Xmpp/Concurrent/Monad.hs
+++ b/source/Network/Xmpp/Concurrent/Monad.hs
@@ -71,7 +71,7 @@ modifyHandlers f session = atomically $ modifyTVar (eventHandlers session) f
writeTVar var (f x)
-- | Sets the handler to be executed when the server connection is closed.
-setConnectionClosedHandler_ :: (StreamFailure -> Session -> IO ()) -> Session -> IO ()
+setConnectionClosedHandler_ :: (XmppFailure -> Session -> IO ()) -> Session -> IO ()
setConnectionClosedHandler_ eh session = do
modifyHandlers (\s -> s{connectionClosedHandler =
\e -> eh e session}) session
diff --git a/source/Network/Xmpp/Concurrent/Threads.hs b/source/Network/Xmpp/Concurrent/Threads.hs
index 1ff2ff3..c55fc16 100644
--- a/source/Network/Xmpp/Concurrent/Threads.hs
+++ b/source/Network/Xmpp/Concurrent/Threads.hs
@@ -22,10 +22,12 @@ import Control.Concurrent.STM.TMVar
import GHC.IO (unsafeUnmask)
+import Control.Monad.Error
+
-- Worker to read stanzas from the stream and concurrently distribute them to
-- all listener threads.
readWorker :: (Stanza -> IO ())
- -> (StreamFailure -> IO ())
+ -> (XmppFailure -> IO ())
-> TMVar (TMVar Connection)
-> IO a
readWorker onStanza onConnectionClosed stateRef =
@@ -45,13 +47,14 @@ readWorker onStanza onConnectionClosed stateRef =
[ Ex.Handler $ \(Interrupt t) -> do
void $ handleInterrupts [t]
return Nothing
- , Ex.Handler $ \(e :: StreamFailure) -> do
+ , Ex.Handler $ \(e :: XmppFailure) -> do
onConnectionClosed e
return Nothing
]
case res of
- Nothing -> return () -- Caught an exception, nothing to do
- Just sta -> onStanza sta
+ Nothing -> return () -- Caught an exception, nothing to do. TODO: Can this happen?
+ Just (Left e) -> return ()
+ Just (Right sta) -> onStanza sta
where
-- Defining an Control.Exception.allowInterrupt equivalent for GHC 7
-- compatibility.
@@ -75,30 +78,32 @@ readWorker onStanza onConnectionClosed stateRef =
startThreadsWith :: (Stanza -> IO ())
-> TVar EventHandlers
-> TMVar Connection
- -> IO
- (IO (),
+ -> IO (Either XmppFailure (IO (),
TMVar (BS.ByteString -> IO Bool),
TMVar (TMVar Connection),
- ThreadId)
+ ThreadId))
startThreadsWith stanzaHandler eh con = do
- read <- withConnection' (gets $ cSend. cHandle) con
- writeLock <- newTMVarIO read
- conS <- newTMVarIO con
--- lw <- forkIO $ writeWorker outC writeLock
- cp <- forkIO $ connPersist writeLock
- rd <- forkIO $ readWorker stanzaHandler (noCon eh) conS
- return ( killConnection writeLock [rd, cp]
- , writeLock
- , conS
- , rd
- )
+ read <- withConnection' (gets $ cSend . cHandle >>= \d -> return $ Right d) con
+ case read of
+ Left e -> return $ Left e
+ Right read' -> do
+ writeLock <- newTMVarIO read'
+ conS <- newTMVarIO con
+ -- lw <- forkIO $ writeWorker outC writeLock
+ cp <- forkIO $ connPersist writeLock
+ rd <- forkIO $ readWorker stanzaHandler (noCon eh) conS
+ return $ Right ( killConnection writeLock [rd, cp]
+ , writeLock
+ , conS
+ , rd
+ )
where
killConnection writeLock threads = liftIO $ do
_ <- atomically $ takeTMVar writeLock -- Should we put it back?
_ <- forM threads killThread
return ()
-- Call the connection closed handlers.
- noCon :: TVar EventHandlers -> StreamFailure -> IO ()
+ noCon :: TVar EventHandlers -> XmppFailure -> IO ()
noCon h e = do
hands <- atomically $ readTVar h
_ <- forkIO $ connectionClosedHandler hands e
diff --git a/source/Network/Xmpp/Concurrent/Types.hs b/source/Network/Xmpp/Concurrent/Types.hs
index 212ea1e..decce8a 100644
--- a/source/Network/Xmpp/Concurrent/Types.hs
+++ b/source/Network/Xmpp/Concurrent/Types.hs
@@ -21,7 +21,7 @@ import Network.Xmpp.Types
-- | Handlers to be run when the Xmpp session ends and when the Xmpp connection is
-- closed.
data EventHandlers = EventHandlers
- { connectionClosedHandler :: StreamFailure -> IO ()
+ { connectionClosedHandler :: XmppFailure -> IO ()
}
-- | Interrupt is used to signal to the reader thread that it should stop. Th contained semphore signals the reader to resume it's work.
diff --git a/source/Network/Xmpp/Connection_.hs b/source/Network/Xmpp/Connection_.hs
index 8317ef1..38a7532 100644
--- a/source/Network/Xmpp/Connection_.hs
+++ b/source/Network/Xmpp/Connection_.hs
@@ -6,6 +6,7 @@ module Network.Xmpp.Connection_ where
import Control.Applicative((<$>))
import Control.Concurrent (forkIO, threadDelay)
+import System.IO.Error (tryIOError)
import Control.Monad
import Control.Monad.IO.Class
import Control.Monad.Trans.Class
@@ -41,7 +42,7 @@ import System.Log.Logger
import Data.ByteString.Base64
import Control.Concurrent.STM.TMVar
-
+import Control.Monad.Error
-- Enable/disable debug output
-- This will dump all incoming and outgoing network taffic to the console,
@@ -49,69 +50,83 @@ import Control.Concurrent.STM.TMVar
debug :: Bool
debug = False
-pushElement :: Element -> StateT Connection IO Bool
+-- TODO: Can the TLS send/recv functions throw something other than an IO error?
+
+wrapIOException :: IO a -> StateT Connection IO (Either XmppFailure a)
+wrapIOException action = do
+ r <- liftIO $ tryIOError action
+ case r of
+ Right b -> return $ Right b
+ Left e -> return $ Left $ XmppIOException e
+
+pushElement :: Element -> StateT Connection IO (Either XmppFailure Bool)
pushElement x = do
send <- gets (cSend . cHandle)
- liftIO . send $ renderElement x
+ wrapIOException $ send $ renderElement x
-- | Encode and send stanza
-pushStanza :: Stanza -> TMVar Connection -> IO Bool
+pushStanza :: Stanza -> TMVar Connection -> IO (Either XmppFailure Bool)
pushStanza s = withConnection' . pushElement $ pickleElem xpStanza s
-- XML documents and XMPP streams SHOULD be preceeded by an XML declaration.
-- UTF-8 is the only supported XMPP encoding. The standalone document
-- declaration (matching "SDDecl" in the XML standard) MUST NOT be included in
-- XMPP streams. RFC 6120 defines XMPP only in terms of XML 1.0.
-pushXmlDecl :: StateT Connection IO Bool
+pushXmlDecl :: StateT Connection IO (Either XmppFailure Bool)
pushXmlDecl = do
con <- gets cHandle
- liftIO $ (cSend con) ""
+ wrapIOException $ (cSend con) ""
-pushOpenElement :: Element -> StateT Connection IO Bool
+pushOpenElement :: Element -> StateT Connection IO (Either XmppFailure Bool)
pushOpenElement e = do
sink <- gets (cSend . cHandle)
- liftIO . sink $ renderOpenElement e
+ wrapIOException $ sink $ renderOpenElement e
-- `Connect-and-resumes' the given sink to the connection source, and pulls a
-- `b' value.
-runEventsSink :: Sink Event IO b -> StateT Connection IO b
-runEventsSink snk = do
+runEventsSink :: Sink Event IO b -> StateT Connection IO (Either XmppFailure b)
+runEventsSink snk = do -- TODO: Wrap exceptions?
source <- gets cEventSource
(src', r) <- lift $ source $$++ snk
modify (\s -> s{cEventSource = src'})
- return r
+ return $ Right r
-pullElement :: StateT Connection IO Element
+pullElement :: StateT Connection IO (Either XmppFailure Element)
pullElement = do
Ex.catches (do
e <- runEventsSink (elements =$ await)
case e of
- Nothing -> liftIO $ Ex.throwIO StreamOtherFailure
- Just r -> return r
+ Left f -> return $ Left f
+ Right Nothing -> return $ Left XmppOtherFailure -- TODO
+ Right (Just r) -> return $ Right r
)
- [ Ex.Handler (\StreamEnd -> Ex.throwIO StreamEndFailure)
+ [ Ex.Handler (\StreamEnd -> return $ Left StreamEndFailure)
, Ex.Handler (\(InvalidXmppXml s) -- Invalid XML `Event' encountered, or missing element close tag
- -> liftIO . Ex.throwIO $ StreamOtherFailure) -- TODO: Log: s
+ -> return $ Left XmppOtherFailure) -- TODO: Log: s
, Ex.Handler $ \(e :: InvalidEventStream) -- xml-conduit exception
- -> liftIO . Ex.throwIO $ StreamOtherFailure -- TODO: Log: (show e)
+ -> return $ Left XmppOtherFailure -- TODO: Log: (show e)
]
-- Pulls an element and unpickles it.
-pullUnpickle :: PU [Node] a -> StateT Connection IO a
+pullUnpickle :: PU [Node] a -> StateT Connection IO (Either XmppFailure a)
pullUnpickle p = do
- res <- unpickleElem p <$> pullElement
- case res of
- Left e -> liftIO $ Ex.throwIO e
- Right r -> return r
+ elem <- pullElement
+ case elem of
+ Left e -> return $ Left e
+ Right elem' -> do
+ let res = unpickleElem p elem'
+ case res of
+ Left e -> return $ Left XmppOtherFailure -- TODO: Log
+ Right r -> return $ Right r
--- | Pulls a stanza (or stream error) from the stream. Throws an error on a stream
--- error.
-pullStanza :: TMVar Connection -> IO Stanza
+-- | Pulls a stanza (or stream error) from the stream.
+pullStanza :: TMVar Connection -> IO (Either XmppFailure Stanza)
pullStanza = withConnection' $ do
res <- pullUnpickle xpStreamStanza
case res of
- Left e -> liftIO . Ex.throwIO $ StreamErrorFailure e
- Right r -> return r
+ Left e -> return $ Left e
+ Right (Left e) -> return $ Left $ StreamErrorFailure e
+ Right (Right r) -> return $ Right r
-- Performs the given IO operation, catches any errors and re-throws everything
-- except 'ResourceVanished' and IllegalOperation, in which case it will return False instead
@@ -129,7 +144,7 @@ xmppNoConnection :: Connection
xmppNoConnection = Connection
{ cHandle = ConnectionHandle { cSend = \_ -> return False
, cRecv = \_ -> Ex.throwIO
- StreamOtherFailure
+ XmppOtherFailure
, cFlush = return ()
, cClose = return ()
}
@@ -147,9 +162,9 @@ xmppNoConnection = Connection
}
where
zeroSource :: Source IO output
- zeroSource = liftIO . Ex.throwIO $ StreamOtherFailure
+ zeroSource = liftIO . Ex.throwIO $ XmppOtherFailure
-connectTcp :: HostName -> PortID -> Text -> IO (TMVar Connection)
+connectTcp :: HostName -> PortID -> Text -> IO (Either XmppFailure (TMVar Connection))
connectTcp host port hostname = do
let PortNumber portNumber = port
debugM "Pontarius.Xmpp" $ "Connecting to " ++ host ++ " on port " ++
@@ -190,7 +205,8 @@ connectTcp host port hostname = do
, cJidWhenPlain = False -- TODO: Allow user to set
, cFrom = Nothing
}
- mkConnection con
+ con' <- mkConnection con
+ return $ Right con'
where
logConduit :: Conduit ByteString IO ByteString
logConduit = CL.mapM $ \d -> do
@@ -201,10 +217,12 @@ connectTcp host port hostname = do
-- Closes the connection and updates the XmppConMonad Connection state.
-killConnection :: TMVar Connection -> IO (Either Ex.SomeException ())
+-- killConnection :: TMVar Connection -> IO (Either Ex.SomeException ())
+killConnection :: TMVar Connection -> IO (Either XmppFailure ())
killConnection = withConnection $ do
cc <- gets (cClose . cHandle)
- err <- liftIO $ (Ex.try cc :: IO (Either Ex.SomeException ()))
+ err <- wrapIOException cc
+ -- (Ex.try cc :: IO (Either Ex.SomeException ()))
put xmppNoConnection
return err
@@ -216,44 +234,45 @@ pushIQ' :: StanzaId
-> Maybe LangTag
-> Element
-> TMVar Connection
- -> IO (Either IQError IQResult)
+ -> IO (Either XmppFailure (Either IQError IQResult))
pushIQ' iqID to tp lang body con = do
pushStanza (IQRequestS $ IQRequest iqID Nothing to lang tp body) con
res <- pullStanza con
case res of
- IQErrorS e -> return $ Left e
- IQResultS r -> do
+ Left e -> return $ Left e
+ Right (IQErrorS e) -> return $ Right $ Left e
+ Right (IQResultS r) -> do
unless
(iqID == iqResultID r) . liftIO . Ex.throwIO $
- StreamOtherFailure
+ XmppOtherFailure
-- TODO: Log: ("In sendIQ' IDs don't match: " ++ show iqID ++
-- " /= " ++ show (iqResultID r) ++ " .")
- return $ Right r
- _ -> liftIO $ Ex.throwIO StreamOtherFailure
+ return $ Right $ Right r
+ _ -> return $ Left XmppOtherFailure
-- TODO: Log: "sendIQ': unexpected stanza type "
-- | Send "" and wait for the server to finish processing and to
--- close the connection. Any remaining elements from the server and whether or
--- not we received a element from the server is returned.
-closeStreams :: TMVar Connection -> IO ([Element], Bool)
+-- close the connection. Any remaining elements from the server are returned.
+-- Surpresses StreamEndFailure exceptions, but may throw a StreamCloseError.
+closeStreams :: TMVar Connection -> IO (Either XmppFailure [Element])
closeStreams = withConnection $ do
send <- gets (cSend . cHandle)
cc <- gets (cClose . cHandle)
liftIO $ send ""
void $ liftIO $ forkIO $ do
- threadDelay 3000000
+ threadDelay 3000000 -- TODO: Configurable value
(Ex.try cc) :: IO (Either Ex.SomeException ())
return ()
collectElems []
where
-- Pulls elements from the stream until the stream ends, or an error is
-- raised.
- collectElems :: [Element] -> StateT Connection IO ([Element], Bool)
+ collectElems :: [Element] -> StateT Connection IO (Either XmppFailure [Element])
collectElems es = do
- result <- Ex.try pullElement
+ result <- pullElement
case result of
- Left StreamEndFailure -> return (es, True)
- Left _ -> return (es, False)
+ Left StreamEndFailure -> return $ Right es
+ Left e -> return $ Left $ StreamCloseError (es, e)
Right e -> collectElems (e:es)
debugConduit :: Pipe l ByteString ByteString u IO b
diff --git a/source/Network/Xmpp/Pickle.hs b/source/Network/Xmpp/Pickle.hs
index e16cb2e..10b72a9 100644
--- a/source/Network/Xmpp/Pickle.hs
+++ b/source/Network/Xmpp/Pickle.hs
@@ -75,4 +75,4 @@ unpickleElem p x = unpickle (xpNodeElem p) x
-- Given a pickler and an object, produces an Element.
pickleElem :: PU [Node] a -> a -> Element
-pickleElem p = pickle $ xpNodeElem p
\ No newline at end of file
+pickleElem p = pickle $ xpNodeElem p
diff --git a/source/Network/Xmpp/Sasl.hs b/source/Network/Xmpp/Sasl.hs
index cbcc825..2a61ae2 100644
--- a/source/Network/Xmpp/Sasl.hs
+++ b/source/Network/Xmpp/Sasl.hs
@@ -42,22 +42,26 @@ import Control.Concurrent.STM.TMVar
-- | Uses the first supported mechanism to authenticate, if any. Updates the
-- state with non-password credentials and restarts the stream upon
--- success.
+-- success. Returns `Nothing' on success, an `AuthFailure' if
+-- authentication fails, or an `XmppFailure' if anything else fails.
xmppSasl :: [SaslHandler] -- ^ Acceptable authentication mechanisms and their
-- corresponding handlers
-> TMVar Connection
- -> IO (Either AuthFailure ())
+ -> IO (Either XmppFailure (Maybe AuthFailure))
xmppSasl handlers = withConnection $ do
-- Chooses the first mechanism that is acceptable by both the client and the
-- server.
mechanisms <- gets $ saslMechanisms . cFeatures
case (filter (\(name, _) -> name `elem` mechanisms)) handlers of
- [] -> return . Left $ AuthNoAcceptableMechanism mechanisms
- (_name, handler):_ -> runErrorT $ do
+ [] -> return $ Right $ Just $ AuthNoAcceptableMechanism mechanisms
+ (_name, handler):_ -> do
cs <- gets cState
case cs of
- ConnectionClosed -> throwError AuthConnectionFailure
+ ConnectionClosed -> return . Right $ Just AuthNoConnection
_ -> do
- r <- handler
- _ <- ErrorT $ left AuthStreamFailure <$> restartStream
- return r
+ r <- runErrorT handler
+ case r of
+ Left ae -> return $ Right $ Just ae
+ Right a -> do
+ _ <- runErrorT $ ErrorT restartStream
+ return $ Right $ Nothing
diff --git a/source/Network/Xmpp/Sasl/Common.hs b/source/Network/Xmpp/Sasl/Common.hs
index a83add5..e3dcc5c 100644
--- a/source/Network/Xmpp/Sasl/Common.hs
+++ b/source/Network/Xmpp/Sasl/Common.hs
@@ -107,16 +107,21 @@ quote :: BS.ByteString -> BS.ByteString
quote x = BS.concat ["\"",x,"\""]
saslInit :: Text.Text -> Maybe BS.ByteString -> SaslM Bool
-saslInit mechanism payload = lift . pushElement . saslInitE mechanism $
- Text.decodeUtf8 . B64.encode <$> payload
+saslInit mechanism payload = do
+ r <- lift . pushElement . saslInitE mechanism $
+ Text.decodeUtf8 . B64.encode <$> payload
+ case r of
+ Left e -> throwError $ AuthStreamFailure e
+ Right b -> return b
-- | Pull the next element.
pullSaslElement :: SaslM SaslElement
pullSaslElement = do
- el <- lift $ pullUnpickle (xpEither xpFailure xpSaslElement)
- case el of
- Left e ->throwError $ AuthSaslFailure e
- Right r -> return r
+ r <- lift $ pullUnpickle (xpEither xpFailure xpSaslElement)
+ case r of
+ Left e -> throwError $ AuthStreamFailure e
+ Right (Left e) -> throwError $ AuthSaslFailure e
+ Right (Right r) -> return r
-- | Pull the next element, checking that it is a challenge.
pullChallenge :: SaslM (Maybe BS.ByteString)
@@ -167,8 +172,11 @@ toPairs ctext = case pairs ctext of
-- | Send a SASL response element. The content will be base64-encoded.
respond :: Maybe BS.ByteString -> SaslM Bool
-respond = lift . pushElement . saslResponseE .
- fmap (Text.decodeUtf8 . B64.encode)
+respond m = do
+ r <- lift . pushElement . saslResponseE . fmap (Text.decodeUtf8 . B64.encode) $ m
+ case r of
+ Left e -> throwError $ AuthStreamFailure e
+ Right b -> return b
-- | Run the appropriate stringprep profiles on the credentials.
diff --git a/source/Network/Xmpp/Sasl/Mechanisms/DigestMd5.hs b/source/Network/Xmpp/Sasl/Mechanisms/DigestMd5.hs
index 9048842..f8fc03c 100644
--- a/source/Network/Xmpp/Sasl/Mechanisms/DigestMd5.hs
+++ b/source/Network/Xmpp/Sasl/Mechanisms/DigestMd5.hs
@@ -48,10 +48,7 @@ xmppDigestMd5 :: Text -- ^ Authentication identity (authzid or username)
xmppDigestMd5 authcid authzid password = do
(ac, az, pw) <- prepCredentials authcid authzid password
hn <- gets cHostName
- case hn of
- Just hn' -> do
- xmppDigestMd5' hn' ac az pw
- Nothing -> throwError AuthConnectionFailure
+ xmppDigestMd5' (fromJust hn) ac az pw
where
xmppDigestMd5' :: Text -> Text -> Maybe Text -> Text -> SaslM ()
xmppDigestMd5' hostname authcid authzid password = do
diff --git a/source/Network/Xmpp/Sasl/Types.hs b/source/Network/Xmpp/Sasl/Types.hs
index cd14c1d..90f20da 100644
--- a/source/Network/Xmpp/Sasl/Types.hs
+++ b/source/Network/Xmpp/Sasl/Types.hs
@@ -13,9 +13,9 @@ data AuthFailure = AuthXmlFailure
| AuthChallengeFailure
| AuthServerAuthFailure -- ^ The server failed to authenticate
-- itself
- | AuthStreamFailure StreamFailure -- ^ Stream error on stream restart
+ | AuthStreamFailure XmppFailure -- ^ Stream error on stream restart
-- TODO: Rename AuthConnectionFailure?
- | AuthConnectionFailure -- ^ Connection is closed
+ | AuthNoConnection
| AuthFailure -- General instance used for the Error instance
| AuthSaslFailure SaslFailure -- ^ Defined SASL error condition
| AuthStringPrepFailure -- ^ StringPrep failed
diff --git a/source/Network/Xmpp/Session.hs b/source/Network/Xmpp/Session.hs
index 7318067..67cf882 100644
--- a/source/Network/Xmpp/Session.hs
+++ b/source/Network/Xmpp/Session.hs
@@ -40,25 +40,29 @@ session :: HostName -- ^ Host to connect to
-> Maybe ([SaslHandler], Maybe Text) -- ^ SASL handlers and the desired
-- JID resource (or Nothing to let
-- the server decide)
- -> IO Session -- TODO: ErrorT
-session hostname realm port tls sasl = do
- con' <- connect hostname port realm
- con <- case con' of
- Left e -> Ex.throwIO e
- Right c -> return c
- if isJust tls then startTls (fromJust tls) con >> return () else return () -- TODO: Eats TlsFailure
- saslResponse <- if isJust sasl then auth (fst $ fromJust sasl) (snd $ fromJust sasl) con >> return () else return () -- TODO: Eats AuthFailure
- newSession con
-
+ -> IO (Either XmppFailure (Session, Maybe AuthFailure))
+session hostname realm port tls sasl = runErrorT $ do
+ con <- ErrorT $ connect hostname port realm
+ if isJust tls
+ then ErrorT $ startTls (fromJust tls) con
+ else return ()
+ aut <- if isJust sasl
+ then ErrorT $ auth (fst $ fromJust sasl) (snd $ fromJust sasl) con
+ else return Nothing
+ ses <- ErrorT $ newSession con
+ return (ses, aut)
+
-- | Connects to the XMPP server and opens the XMPP stream against the given
-- host name, port, and realm.
-connect :: HostName -> PortID -> Text -> IO (Either StreamFailure (TMVar Connection))
+connect :: HostName -> PortID -> Text -> IO (Either XmppFailure (TMVar Connection))
connect address port hostname = do
con <- connectTcp address port hostname
- result <- withConnection startStream con
- case result of
- Left e -> return $ Left e -- TODO
- Right () -> return $ Right con
+ case con of
+ Right con' -> do
+ result <- withConnection startStream con'
+ return $ Right con'
+ Left e -> do
+ return $ Left e
sessionXml :: Element
sessionXml = pickleElem
@@ -88,12 +92,12 @@ startSession con = do
auth :: [SaslHandler]
-> Maybe Text
-> TMVar Connection
- -> IO (Either AuthFailure Jid)
+ -> IO (Either XmppFailure (Maybe AuthFailure))
auth mechanisms resource con = runErrorT $ do
ErrorT $ xmppSasl mechanisms con
jid <- lift $ xmppBind resource con
lift $ startSession con
- return jid
+ return Nothing
-- | Authenticate to the server with the given username and password
-- and bind a resource.
@@ -104,7 +108,7 @@ simpleAuth :: Text.Text -- ^ The username
-> Maybe Text -- ^ The desired resource or 'Nothing' to let the
-- server assign one
-> TMVar Connection
- -> IO (Either AuthFailure Jid)
+ -> IO (Either XmppFailure (Maybe AuthFailure))
simpleAuth username passwd resource = flip auth resource $
[ -- TODO: scramSha1Plus
scramSha1 username Nothing passwd
diff --git a/source/Network/Xmpp/Stream.hs b/source/Network/Xmpp/Stream.hs
index 972017a..a4ce39e 100644
--- a/source/Network/Xmpp/Stream.hs
+++ b/source/Network/Xmpp/Stream.hs
@@ -36,12 +36,12 @@ streamUnpickleElem :: PU [Node] a
-> StreamSink a
streamUnpickleElem p x = do
case unpickleElem p x of
- Left l -> throwError $ StreamOtherFailure -- TODO: Log: StreamXmlError (show l)
+ Left l -> throwError $ XmppOtherFailure -- TODO: Log: StreamXmlError (show l)
Right r -> return r
-- This is the conduit sink that handles the stream XML events. We extend it
-- with ErrorT capabilities.
-type StreamSink a = ErrorT StreamFailure (Pipe Event Event Void () IO) a
+type StreamSink a = ErrorT XmppFailure (Pipe Event Event Void () IO) a
-- Discards all events before the first EventBeginElement.
throwOutJunk :: Monad m => Sink Event m ()
@@ -59,13 +59,13 @@ openElementFromEvents = do
hd <- lift CL.head
case hd of
Just (EventBeginElement name attrs) -> return $ Element name attrs []
- _ -> throwError $ StreamOtherFailure
+ _ -> throwError $ XmppOtherFailure
-- Sends the initial stream:stream element and pulls the server features. If the
-- server responds in a way that is invalid, an appropriate stream error will be
--- generated, the connection to the server will be closed, and a StreamFilure
+-- generated, the connection to the server will be closed, and a XmppFailure
-- will be produced.
-startStream :: StateT Connection IO (Either StreamFailure ())
+startStream :: StateT Connection IO (Either XmppFailure ())
startStream = runErrorT $ do
state <- lift $ get
con <- liftIO $ mkConnection state
@@ -76,7 +76,7 @@ startStream = runErrorT $ do
then cJid state else Nothing
ConnectionSecured -> cJid state
case cHostName state of
- Nothing -> throwError StreamOtherFailure -- TODO: When does this happen?
+ Nothing -> throwError XmppOtherFailure -- TODO: When does this happen?
Just hostname -> lift $ do
pushXmlDecl
pushOpenElement $
@@ -88,8 +88,9 @@ startStream = runErrorT $ do
)
response <- ErrorT $ runEventsSink $ runErrorT $ streamS expectedTo
case response of
+ Left e -> throwError e
-- Successful unpickling of stream element.
- Right (ver, from, to, id, lt, features)
+ Right (Right (ver, from, to, id, lt, features))
| (unpack ver) /= "1.0" ->
closeStreamWithError con StreamUnsupportedVersion Nothing
| lt == Nothing ->
@@ -107,7 +108,7 @@ startStream = runErrorT $ do
} )
return ()
-- Unpickling failed - we investigate the element.
- Left (Element name attrs children)
+ Right (Left (Element name attrs children))
| (nameLocalName name /= "stream") ->
closeStreamWithError con StreamInvalidXml Nothing
| (nameNamespace name /= Just "http://etherx.jabber.org/streams") ->
@@ -117,13 +118,13 @@ startStream = runErrorT $ do
| otherwise -> ErrorT $ checkchildren con (flattenAttrs attrs)
where
-- closeStreamWithError :: MonadIO m => TMVar Connection -> StreamErrorCondition ->
- -- Maybe Element -> ErrorT StreamFailure m ()
+ -- Maybe Element -> ErrorT XmppFailure m ()
closeStreamWithError con sec el = do
liftIO $ do
withConnection (pushElement . pickleElem xpStreamError $
StreamErrorInfo sec Nothing el) con
closeStreams con
- throwError StreamOtherFailure
+ throwError XmppOtherFailure
checkchildren con children =
let to' = lookup "to" children
ver' = lookup "version" children
@@ -156,7 +157,7 @@ flattenAttrs attrs = Prelude.map (\(name, content) ->
-- Sets a new Event source using the raw source (of bytes)
-- and calls xmppStartStream.
-restartStream :: StateT Connection IO (Either StreamFailure ())
+restartStream :: StateT Connection IO (Either XmppFailure ())
restartStream = do
raw <- gets (cRecv . cHandle)
let newSource = DCI.ResumableSource (loopRead raw $= XP.parseBytes def)
@@ -172,7 +173,7 @@ restartStream = do
-- Reads the (partial) stream:stream and the server features from the stream.
-- Returns the (unvalidated) stream attributes, the unparsed element, or
--- throwError throws a `StreamOtherFailure' (if something other than an element
+-- throwError throws a `XmppOtherFailure' (if something other than an element
-- was encountered at first, or if something other than stream features was
-- encountered second).
-- TODO: from.
@@ -195,7 +196,7 @@ streamS expectedTo = do
lift throwOutJunk
-- Get the stream:stream element (or whatever it is) from the server,
-- and validate what we get.
- el <- openElementFromEvents -- May throw `StreamOtherFailure' if an
+ el <- openElementFromEvents -- May throw `XmppOtherFailure' if an
-- element is not received
case unpickleElem xpStream el of
Left _ -> return $ Left el
@@ -204,7 +205,7 @@ streamS expectedTo = do
xmppStreamFeatures = do
e <- lift $ elements =$ CL.head
case e of
- Nothing -> throwError StreamOtherFailure
+ Nothing -> throwError XmppOtherFailure
Just r -> streamUnpickleElem xpStreamFeatures r
diff --git a/source/Network/Xmpp/Tls.hs b/source/Network/Xmpp/Tls.hs
index 75c73bf..957cdc4 100644
--- a/source/Network/Xmpp/Tls.hs
+++ b/source/Network/Xmpp/Tls.hs
@@ -76,7 +76,7 @@ exampleParams = TLS.defaultParamsClient
-- Pushes ", waits for "", performs the TLS handshake, and
-- restarts the stream.
-startTls :: TLS.TLSParams -> TMVar Connection -> IO (Either TlsFailure ())
+startTls :: TLS.TLSParams -> TMVar Connection -> IO (Either XmppFailure ())
startTls params con = Ex.handle (return . Left . TlsError)
. flip withConnection con
. runErrorT $ do
@@ -84,19 +84,16 @@ startTls params con = Ex.handle (return . Left . TlsError)
state <- gets cState
case state of
ConnectionPlain -> return ()
- ConnectionClosed -> throwError TlsNoConnection
+ ConnectionClosed -> throwError XmppNoConnection
ConnectionSecured -> throwError TlsConnectionSecured
con <- lift $ gets cHandle
when (stls features == Nothing) $ throwError TlsNoServerSupport
lift $ pushElement starttlsE
answer <- lift $ pullElement
case answer of
- Element "{urn:ietf:params:xml:ns:xmpp-tls}proceed" [] [] -> return ()
- Element "{urn:ietf:params:xml:ns:xmpp-tls}failure" _ _ ->
- lift $ Ex.throwIO StreamOtherFailure
- -- TODO: find something more suitable
- e -> lift $ Ex.throwIO StreamOtherFailure
- -- TODO: Log: "Unexpected element: " ++ ppElement e
+ Left e -> return $ Left e
+ Right (Element "{urn:ietf:params:xml:ns:xmpp-tls}proceed" [] []) -> return $ Right ()
+ Right (Element "{urn:ietf:params:xml:ns:xmpp-tls}failure" _ _) -> return $ Left XmppOtherFailure
(raw, _snk, psh, read, ctx) <- lift $ TLS.tlsinit debug params (mkBackend con)
let newHand = ConnectionHandle { cSend = catchPush . psh
, cRecv = read
diff --git a/source/Network/Xmpp/Types.hs b/source/Network/Xmpp/Types.hs
index 1a71d94..86dd602 100644
--- a/source/Network/Xmpp/Types.hs
+++ b/source/Network/Xmpp/Types.hs
@@ -28,7 +28,7 @@ module Network.Xmpp.Types
, StanzaErrorCondition(..)
, StanzaErrorType(..)
, StanzaId(..)
- , StreamFailure(..)
+ , XmppFailure(..)
, StreamErrorCondition(..)
, Version(..)
, ConnectionHandle(..)
@@ -39,7 +39,6 @@ module Network.Xmpp.Types
, ConnectionState(..)
, StreamErrorInfo(..)
, langTag
- , TlsFailure(..)
, module Network.Xmpp.Jid
)
where
@@ -629,17 +628,32 @@ data StreamErrorInfo = StreamErrorInfo
-- | Signals an XMPP stream error or another unpredicted stream-related
-- situation.
-data StreamFailure = StreamErrorFailure StreamErrorInfo -- ^ An error XML stream
+data XmppFailure = StreamErrorFailure StreamErrorInfo -- ^ An error XML stream
-- element has been
-- encountered.
- | StreamEndFailure -- ^ The server has closed the stream.
- | StreamOtherFailure -- ^ Undefined condition. More
- -- information should be available in
- -- the log.
- deriving (Show, Eq, Typeable)
-
-instance Exception StreamFailure
-instance Error StreamFailure where noMsg = StreamOtherFailure
+ | StreamEndFailure -- ^ The stream has been closed.
+ -- This exception is caught by the
+ -- concurrent implementation, and
+ -- will thus not be visible
+ -- through use of 'Session'.
+ | StreamCloseError ([Element], XmppFailure) -- ^ When an XmppFailure
+ -- is encountered in
+ -- closeStreams, this
+ -- constructor wraps the
+ -- elements collected so
+ -- far.
+ | TlsError TLS.TLSError
+ | TlsNoServerSupport
+ | XmppNoConnection
+ | TlsConnectionSecured -- ^ Connection already secured
+ | XmppOtherFailure -- ^ Undefined condition. More
+ -- information should be available
+ -- in the log.
+ | XmppIOException IOException
+ deriving (Show, Eq, Typeable)
+
+instance Exception XmppFailure
+instance Error XmppFailure where noMsg = XmppOtherFailure
-- =============================================================================
-- XML TYPES
@@ -781,7 +795,7 @@ data Connection = Connection
-- element's `from' attribute.
}
-withConnection :: StateT Connection IO c -> TMVar Connection -> IO c
+withConnection :: StateT Connection IO (Either XmppFailure c) -> TMVar Connection -> IO (Either XmppFailure c)
withConnection action con = bracketOnError
(atomically $ takeTMVar con)
(atomically . putTMVar con )
@@ -792,7 +806,7 @@ withConnection action con = bracketOnError
)
-- nonblocking version. Changes to the connection are ignored!
-withConnection' :: StateT Connection IO b -> TMVar Connection -> IO b
+withConnection' :: StateT Connection IO (Either XmppFailure b) -> TMVar Connection -> IO (Either XmppFailure b)
withConnection' action con = do
con_ <- atomically $ readTMVar con
(r, _) <- runStateT action con_
@@ -801,16 +815,3 @@ withConnection' action con = do
mkConnection :: Connection -> IO (TMVar Connection)
mkConnection con = {- Connection `fmap` -} (atomically $ newTMVar con)
-
-
--- | Failure conditions that may arise during TLS negotiation.
-data TlsFailure = TlsError TLS.TLSError
- | TlsNoServerSupport
- | TlsNoConnection
- | TlsConnectionSecured -- ^ Connection already secured
- | TlsStreamError StreamFailure
- | TlsFailureError -- General instance used for the Error instance (TODO)
- deriving (Show, Eq, Typeable)
-
-instance Error TlsFailure where
- noMsg = TlsFailureError
diff --git a/source/Network/Xmpp/Xep/ServiceDiscovery.hs b/source/Network/Xmpp/Xep/ServiceDiscovery.hs
index c025677..d5325e0 100644
--- a/source/Network/Xmpp/Xep/ServiceDiscovery.hs
+++ b/source/Network/Xmpp/Xep/ServiceDiscovery.hs
@@ -33,7 +33,7 @@ import Network.Xmpp.Types
import Control.Concurrent.STM.TMVar
data DiscoError = DiscoNoQueryElement
- | DiscoIQError IQError
+ | DiscoIQError (Maybe IQError)
| DiscoTimeout
| DiscoXmlError Element UnpickleError
@@ -92,7 +92,7 @@ queryInfo :: Jid -- ^ Entity to query
queryInfo to node context = do
res <- sendIQ' (Just to) Get Nothing queryBody context
return $ case res of
- IQResponseError e -> Left $ DiscoIQError e
+ IQResponseError e -> Left $ DiscoIQError (Just e)
IQResponseTimeout -> Left $ DiscoTimeout
IQResponseResult r -> case iqResultPayload r of
Nothing -> Left DiscoNoQueryElement
@@ -110,12 +110,14 @@ xmppQueryInfo :: Maybe Jid
xmppQueryInfo to node con = do
res <- pushIQ' "info" to Get Nothing queryBody con
return $ case res of
- Left e -> Left $ DiscoIQError e
- Right r -> case iqResultPayload r of
- Nothing -> Left DiscoNoQueryElement
- Just p -> case unpickleElem xpQueryInfo p of
- Left e -> Left $ DiscoXmlError p e
- Right r -> Right r
+ Left e -> Left $ DiscoIQError Nothing
+ Right res' -> case res' of
+ Left e -> Left $ DiscoIQError (Just e)
+ Right r -> case iqResultPayload r of
+ Nothing -> Left DiscoNoQueryElement
+ Just p -> case unpickleElem xpQueryInfo p of
+ Left e -> Left $ DiscoXmlError p e
+ Right r -> Right r
where
queryBody = pickleElem xpQueryInfo (QIR node [] [])
@@ -156,7 +158,7 @@ queryItems :: Jid -- ^ Entity to query
queryItems to node session = do
res <- sendIQ' (Just to) Get Nothing queryBody session
return $ case res of
- IQResponseError e -> Left $ DiscoIQError e
+ IQResponseError e -> Left $ DiscoIQError (Just e)
IQResponseTimeout -> Left $ DiscoTimeout
IQResponseResult r -> case iqResultPayload r of
Nothing -> Left DiscoNoQueryElement