Browse Source

Join `Stream' and `Connection'

The `Stream' and the `Connection' modules/concepts has been quite
similar and, in some ways, overlapping. The `Stream' and `Connection'
modules allow for direct access to the XML stream. The `Stream' module
exported an API for opening, restarting, and closing the stream. The
`Connection' module, on the other hand, allowed for pushing and
pulling elements and stanzas to and from the stream. Furthermore, the
`Connection' type was used in both the `Connection' module and the
higher-level `Stream' module.

This patch joins the two modules into one `Stream' module, and renames
the `Connection' type to `Stream'. It also renames most other
connection-related functions and types. Also, `connect' is renamed
`openStream' and `closeStreams' is renamed `closeStream' (the stream
is `bidirectional' in RFC 6120 terminology).
master
Jon Kristensen 13 years ago
parent
commit
eb7391061a
  1. 2
      pontarius-xmpp.cabal
  2. 2
      source/Network/Xmpp.hs
  3. 10
      source/Network/Xmpp/Concurrent.hs
  4. 4
      source/Network/Xmpp/Concurrent/Monad.hs
  5. 12
      source/Network/Xmpp/Concurrent/Threads.hs
  6. 6
      source/Network/Xmpp/Concurrent/Types.hs
  7. 284
      source/Network/Xmpp/Connection.hs
  8. 16
      source/Network/Xmpp/Internal.hs
  9. 17
      source/Network/Xmpp/Sasl.hs
  10. 2
      source/Network/Xmpp/Sasl/Common.hs
  11. 1
      source/Network/Xmpp/Sasl/Mechanisms/DigestMd5.hs
  12. 1
      source/Network/Xmpp/Sasl/Mechanisms/Plain.hs
  13. 6
      source/Network/Xmpp/Sasl/Types.hs
  14. 325
      source/Network/Xmpp/Stream.hs
  15. 15
      source/Network/Xmpp/Tls.hs
  16. 60
      source/Network/Xmpp/Types.hs
  17. 12
      source/Network/Xmpp/Xep/InbandRegistration.hs
  18. 4
      source/Network/Xmpp/Xep/ServiceDiscovery.hs

2
pontarius-xmpp.cabal

@ -58,7 +58,6 @@ Library
, Network.Xmpp.Internal , Network.Xmpp.Internal
, Network.Xmpp.IM , Network.Xmpp.IM
Other-modules: Data.Conduit.Tls Other-modules: Data.Conduit.Tls
, Network.Xmpp.Bind
, Network.Xmpp.Concurrent , Network.Xmpp.Concurrent
, Network.Xmpp.Concurrent.Types , Network.Xmpp.Concurrent.Types
, Network.Xmpp.Concurrent.Basic , Network.Xmpp.Concurrent.Basic
@ -67,7 +66,6 @@ Library
, Network.Xmpp.Concurrent.Presence , Network.Xmpp.Concurrent.Presence
, Network.Xmpp.Concurrent.Threads , Network.Xmpp.Concurrent.Threads
, Network.Xmpp.Concurrent.Monad , Network.Xmpp.Concurrent.Monad
, Network.Xmpp.Connection
, Network.Xmpp.IM.Message , Network.Xmpp.IM.Message
, Network.Xmpp.IM.Presence , Network.Xmpp.IM.Presence
, Network.Xmpp.Marshal , Network.Xmpp.Marshal

2
source/Network/Xmpp.hs

@ -145,7 +145,7 @@ module Network.Xmpp
, AuthFailure( AuthXmlFailure -- Does not export AuthStreamFailure , AuthFailure( AuthXmlFailure -- Does not export AuthStreamFailure
, AuthNoAcceptableMechanism , AuthNoAcceptableMechanism
, AuthChallengeFailure , AuthChallengeFailure
, AuthNoConnection , AuthNoStream
, AuthFailure , AuthFailure
, AuthSaslFailure , AuthSaslFailure
, AuthStringPrepFailure ) , AuthStringPrepFailure )

10
source/Network/Xmpp/Concurrent.hs

@ -83,14 +83,14 @@ toChans stanzaC iqHands sta = atomically $ do
-- | Creates and initializes a new Xmpp context. -- | Creates and initializes a new Xmpp context.
newSession :: TMVar Connection -> IO (Either XmppFailure Session) newSession :: TMVar Stream -> IO (Either XmppFailure Session)
newSession con = runErrorT $ do newSession stream = runErrorT $ do
outC <- lift newTChanIO outC <- lift newTChanIO
stanzaChan <- lift newTChanIO stanzaChan <- lift newTChanIO
iqHandlers <- lift $ newTVarIO (Map.empty, Map.empty) iqHandlers <- lift $ newTVarIO (Map.empty, Map.empty)
eh <- lift $ newTVarIO $ EventHandlers { connectionClosedHandler = \_ -> return () } eh <- lift $ newTVarIO $ EventHandlers { connectionClosedHandler = \_ -> return () }
let stanzaHandler = toChans stanzaChan iqHandlers let stanzaHandler = toChans stanzaChan iqHandlers
(kill, wLock, conState, readerThread) <- ErrorT $ startThreadsWith stanzaHandler eh con (kill, wLock, streamState, readerThread) <- ErrorT $ startThreadsWith stanzaHandler eh stream
writer <- lift $ forkIO $ writeWorker outC wLock writer <- lift $ forkIO $ writeWorker outC wLock
idRef <- lift $ newTVarIO 1 idRef <- lift $ newTVarIO 1
let getId = atomically $ do let getId = atomically $ do
@ -103,7 +103,7 @@ newSession con = runErrorT $ do
, writeRef = wLock , writeRef = wLock
, readerThread = readerThread , readerThread = readerThread
, idGenerator = getId , idGenerator = getId
, conRef = conState , streamRef = streamState
, eventHandlers = eh , eventHandlers = eh
, stopThreads = kill >> killThread writer , stopThreads = kill >> killThread writer
} }
@ -139,7 +139,7 @@ session :: HostName -- ^ Host to connect to
-- the server decide) -- the server decide)
-> IO (Either XmppFailure (Session, Maybe AuthFailure)) -> IO (Either XmppFailure (Session, Maybe AuthFailure))
session hostname realm port tls sasl = runErrorT $ do session hostname realm port tls sasl = runErrorT $ do
con <- ErrorT $ connect hostname port realm con <- ErrorT $ openStream hostname port realm
if isJust tls if isJust tls
then ErrorT $ startTls (fromJust tls) con then ErrorT $ startTls (fromJust tls) con
else return () else return ()

4
source/Network/Xmpp/Concurrent/Monad.hs

@ -9,7 +9,7 @@ import qualified Control.Exception.Lifted as Ex
import Control.Monad.Reader import Control.Monad.Reader
import Network.Xmpp.Concurrent.Types import Network.Xmpp.Concurrent.Types
import Network.Xmpp.Connection import Network.Xmpp.Stream
@ -94,6 +94,6 @@ closeConnection :: Session -> IO ()
closeConnection session = Ex.mask_ $ do closeConnection session = Ex.mask_ $ do
(_send, connection) <- atomically $ liftM2 (,) (_send, connection) <- atomically $ liftM2 (,)
(takeTMVar $ writeRef session) (takeTMVar $ writeRef session)
(takeTMVar $ conRef session) (takeTMVar $ streamRef session)
_ <- closeStreams connection _ <- closeStreams connection
return () return ()

12
source/Network/Xmpp/Concurrent/Threads.hs

@ -16,7 +16,7 @@ import Control.Monad.State.Strict
import qualified Data.ByteString as BS import qualified Data.ByteString as BS
import Network.Xmpp.Concurrent.Types import Network.Xmpp.Concurrent.Types
import Network.Xmpp.Connection import Network.Xmpp.Stream
import Control.Concurrent.STM.TMVar import Control.Concurrent.STM.TMVar
@ -28,7 +28,7 @@ import Control.Monad.Error
-- all listener threads. -- all listener threads.
readWorker :: (Stanza -> IO ()) readWorker :: (Stanza -> IO ())
-> (XmppFailure -> IO ()) -> (XmppFailure -> IO ())
-> TMVar (TMVar Connection) -> TMVar (TMVar Stream)
-> IO a -> IO a
readWorker onStanza onConnectionClosed stateRef = readWorker onStanza onConnectionClosed stateRef =
Ex.mask_ . forever $ do Ex.mask_ . forever $ do
@ -38,7 +38,7 @@ readWorker onStanza onConnectionClosed stateRef =
s <- atomically $ do s <- atomically $ do
con <- readTMVar stateRef con <- readTMVar stateRef
state <- cState <$> readTMVar con state <- cState <$> readTMVar con
when (state == ConnectionClosed) when (state == Closed)
retry retry
return con return con
allowInterrupt allowInterrupt
@ -77,13 +77,13 @@ readWorker onStanza onConnectionClosed stateRef =
-- connection. -- connection.
startThreadsWith :: (Stanza -> IO ()) startThreadsWith :: (Stanza -> IO ())
-> TVar EventHandlers -> TVar EventHandlers
-> TMVar Connection -> TMVar Stream
-> IO (Either XmppFailure (IO (), -> IO (Either XmppFailure (IO (),
TMVar (BS.ByteString -> IO Bool), TMVar (BS.ByteString -> IO Bool),
TMVar (TMVar Connection), TMVar (TMVar Stream),
ThreadId)) ThreadId))
startThreadsWith stanzaHandler eh con = do startThreadsWith stanzaHandler eh con = do
read <- withConnection' (gets $ cSend . cHandle >>= \d -> return $ Right d) con read <- withStream' (gets $ cSend . cHandle >>= \d -> return $ Right d) con
case read of case read of
Left e -> return $ Left e Left e -> return $ Left e
Right read' -> do Right read' -> do

6
source/Network/Xmpp/Concurrent/Types.hs

@ -42,9 +42,9 @@ data Session = Session
, writeRef :: TMVar (BS.ByteString -> IO Bool) , writeRef :: TMVar (BS.ByteString -> IO Bool)
, readerThread :: ThreadId , readerThread :: ThreadId
, idGenerator :: IO StanzaId , idGenerator :: IO StanzaId
-- | Lock (used by withConnection) to make sure that a maximum of one -- | Lock (used by withStream) to make sure that a maximum of one
-- XmppConMonad action is executed at any given time. -- Stream action is executed at any given time.
, conRef :: TMVar (TMVar Connection) , streamRef :: TMVar (TMVar Stream)
, eventHandlers :: TVar EventHandlers , eventHandlers :: TVar EventHandlers
, stopThreads :: IO () , stopThreads :: IO ()
} }

284
source/Network/Xmpp/Connection.hs

@ -1,284 +0,0 @@
{-# OPTIONS_HADDOCK hide #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE OverloadedStrings #-}
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
--import Control.Monad.Trans.Resource
import qualified Control.Exception.Lifted as Ex
import qualified GHC.IO.Exception as GIE
import Control.Monad.State.Strict
import Data.ByteString as BS
import Data.ByteString.Char8 as BSC8
import Data.Conduit
import Data.Conduit.Binary as CB
import Data.Conduit.Internal as DCI
import qualified Data.Conduit.List as CL
import Data.IORef
import Data.Text(Text)
import qualified Data.Text as T
import Data.XML.Pickle
import Data.XML.Types
import Network
import Network.Xmpp.Types
import Network.Xmpp.Marshal
import System.IO
import Text.Xml.Stream.Elements
import Text.XML.Stream.Parse as XP
import Text.XML.Unresolved(InvalidEventStream(..))
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,
-- prefixed with "in: " and "out: " respectively
debug :: Bool
debug = False
-- 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)
wrapIOException $ send $ renderElement x
-- | Encode and send stanza
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 (Either XmppFailure Bool)
pushXmlDecl = do
con <- gets cHandle
wrapIOException $ (cSend con) "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>"
pushOpenElement :: Element -> StateT Connection IO (Either XmppFailure Bool)
pushOpenElement e = do
sink <- gets (cSend . cHandle)
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 (Either XmppFailure b)
runEventsSink snk = do -- TODO: Wrap exceptions?
source <- gets cEventSource
(src', r) <- lift $ source $$++ snk
modify (\s -> s{cEventSource = src'})
return $ Right r
pullElement :: StateT Connection IO (Either XmppFailure Element)
pullElement = do
Ex.catches (do
e <- runEventsSink (elements =$ await)
case e of
Left f -> return $ Left f
Right Nothing -> return $ Left XmppOtherFailure -- TODO
Right (Just r) -> return $ Right r
)
[ Ex.Handler (\StreamEnd -> return $ Left StreamEndFailure)
, Ex.Handler (\(InvalidXmppXml s) -- Invalid XML `Event' encountered, or missing element close tag
-> return $ Left XmppOtherFailure) -- TODO: Log: s
, Ex.Handler $ \(e :: InvalidEventStream) -- xml-conduit exception
-> return $ Left XmppOtherFailure -- TODO: Log: (show e)
]
-- Pulls an element and unpickles it.
pullUnpickle :: PU [Node] a -> StateT Connection IO (Either XmppFailure a)
pullUnpickle p = do
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.
pullStanza :: TMVar Connection -> IO (Either XmppFailure Stanza)
pullStanza = withConnection' $ do
res <- pullUnpickle xpStreamStanza
case res of
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
catchPush :: IO () -> IO Bool
catchPush p = Ex.catch
(p >> return True)
(\e -> case GIE.ioe_type e of
GIE.ResourceVanished -> return False
GIE.IllegalOperation -> return False
_ -> Ex.throwIO e
)
-- Connection state used when there is no connection.
xmppNoConnection :: Connection
xmppNoConnection = Connection
{ cHandle = ConnectionHandle { cSend = \_ -> return False
, cRecv = \_ -> Ex.throwIO
XmppOtherFailure
, cFlush = return ()
, cClose = return ()
}
, cEventSource = DCI.ResumableSource zeroSource (return ())
, cFeatures = SF Nothing [] []
, cState = ConnectionClosed
, cHostName = Nothing
, cJid = Nothing
, cStreamLang = Nothing
, cStreamId = Nothing
, cPreferredLang = Nothing
, cToJid = Nothing
, cJidWhenPlain = False
, cFrom = Nothing
}
where
zeroSource :: Source IO output
zeroSource = liftIO . Ex.throwIO $ XmppOtherFailure
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 " ++
(show portNumber) ++ " through the realm " ++ (T.unpack hostname) ++ "."
h <- connectTo host port
debugM "Pontarius.Xmpp" "Setting NoBuffering mode on handle."
hSetBuffering h NoBuffering
let eSource = DCI.ResumableSource
((sourceHandle h $= logConduit) $= XP.parseBytes def)
(return ())
let hand = ConnectionHandle { cSend = \d -> do
let d64 = encode d
debugM "Pontarius.Xmpp" $
"Sending TCP data: " ++ (BSC8.unpack d64)
++ "."
catchPush $ BS.hPut h d
, cRecv = \n -> do
d <- BS.hGetSome h n
let d64 = encode d
debugM "Pontarius.Xmpp" $
"Received TCP data: " ++
(BSC8.unpack d64) ++ "."
return d
, cFlush = hFlush h
, cClose = hClose h
}
let con = Connection
{ cHandle = hand
, cEventSource = eSource
, cFeatures = (SF Nothing [] [])
, cState = ConnectionPlain
, cHostName = (Just hostname)
, cJid = Nothing
, cPreferredLang = Nothing -- TODO: Allow user to set
, cStreamLang = Nothing
, cStreamId = Nothing
, cToJid = Nothing -- TODO: Allow user to set
, cJidWhenPlain = False -- TODO: Allow user to set
, cFrom = Nothing
}
con' <- mkConnection con
return $ Right con'
where
logConduit :: Conduit ByteString IO ByteString
logConduit = CL.mapM $ \d -> do
let d64 = encode d
debugM "Pontarius.Xmpp" $ "Received TCP data: " ++ (BSC8.unpack d64) ++
"."
return d
-- Closes the connection and updates the XmppConMonad Connection state.
-- killConnection :: TMVar Connection -> IO (Either Ex.SomeException ())
killConnection :: TMVar Connection -> IO (Either XmppFailure ())
killConnection = withConnection $ do
cc <- gets (cClose . cHandle)
err <- wrapIOException cc
-- (Ex.try cc :: IO (Either Ex.SomeException ()))
put xmppNoConnection
return err
-- Sends an IQ request and waits for the response. If the response ID does not
-- match the outgoing ID, an error is thrown.
pushIQ' :: StanzaId
-> Maybe Jid
-> IQRequestType
-> Maybe LangTag
-> Element
-> TMVar Connection
-> 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
Left e -> return $ Left e
Right (IQErrorS e) -> return $ Right $ Left e
Right (IQResultS r) -> do
unless
(iqID == iqResultID r) . liftIO . Ex.throwIO $
XmppOtherFailure
-- TODO: Log: ("In sendIQ' IDs don't match: " ++ show iqID ++
-- " /= " ++ show (iqResultID r) ++ " .")
return $ Right $ Right r
_ -> return $ Left XmppOtherFailure
-- TODO: Log: "sendIQ': unexpected stanza type "
-- | Send "</stream:stream>" and wait for the server to finish processing and to
-- 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 "</stream:stream>"
void $ liftIO $ forkIO $ do
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 (Either XmppFailure [Element])
collectElems es = do
result <- pullElement
case result of
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
debugConduit = forever $ do
s' <- await
case s' of
Just s -> do
liftIO $ BS.putStrLn (BS.append "in: " s)
yield s
Nothing -> return ()

16
source/Network/Xmpp/Internal.hs

@ -8,21 +8,21 @@
-- This module allows for low-level access to Pontarius XMPP. Generally, the -- This module allows for low-level access to Pontarius XMPP. Generally, the
-- "Network.Xmpp" module should be used instead. -- "Network.Xmpp" module should be used instead.
-- --
-- The 'Connection' object provides the most low-level access to the XMPP -- The 'Stream' object provides the most low-level access to the XMPP
-- stream: a simple and single-threaded interface which exposes the conduit -- stream: a simple and single-threaded interface which exposes the conduit
-- 'Event' source, as well as the input and output byte streams. Custom stateful -- 'Event' source, as well as the input and output byte streams. Custom stateful
-- 'Connection' functions can be executed using 'withConnection'. -- 'Stream' functions can be executed using 'withStream'.
-- --
-- The TLS, SASL, and 'Session' functionalities of Pontarius XMPP are built on -- The TLS, SASL, and 'Session' functionalities of Pontarius XMPP are built on
-- top of this API. -- top of this API.
module Network.Xmpp.Internal module Network.Xmpp.Internal
( Connection(..) ( Stream(..)
, ConnectionState(..) , StreamState(..)
, ConnectionHandle(..) , StreamHandle(..)
, ServerFeatures(..) , ServerFeatures(..)
, connect , openStream
, withConnection , withStream
, startTls , startTls
, simpleAuth , simpleAuth
, auth , auth
@ -32,7 +32,7 @@ module Network.Xmpp.Internal
where where
import Network.Xmpp.Connection import Network.Xmpp.Stream
import Network.Xmpp.Sasl import Network.Xmpp.Sasl
import Network.Xmpp.Tls import Network.Xmpp.Tls
import Network.Xmpp.Types import Network.Xmpp.Types

17
source/Network/Xmpp/Sasl.hs

@ -36,7 +36,6 @@ import qualified Data.Text as Text
import Data.Text (Text) import Data.Text (Text)
import qualified Data.Text.Encoding as Text import qualified Data.Text.Encoding as Text
import Network.Xmpp.Connection
import Network.Xmpp.Stream import Network.Xmpp.Stream
import Network.Xmpp.Types import Network.Xmpp.Types
@ -67,9 +66,9 @@ import Control.Monad.Error
-- authentication fails, or an `XmppFailure' if anything else fails. -- authentication fails, or an `XmppFailure' if anything else fails.
xmppSasl :: [SaslHandler] -- ^ Acceptable authentication mechanisms and their xmppSasl :: [SaslHandler] -- ^ Acceptable authentication mechanisms and their
-- corresponding handlers -- corresponding handlers
-> TMVar Connection -> TMVar Stream
-> IO (Either XmppFailure (Maybe AuthFailure)) -> IO (Either XmppFailure (Maybe AuthFailure))
xmppSasl handlers = withConnection $ do xmppSasl handlers = withStream $ do
-- Chooses the first mechanism that is acceptable by both the client and the -- Chooses the first mechanism that is acceptable by both the client and the
-- server. -- server.
mechanisms <- gets $ saslMechanisms . cFeatures mechanisms <- gets $ saslMechanisms . cFeatures
@ -78,7 +77,7 @@ xmppSasl handlers = withConnection $ do
(_name, handler):_ -> do (_name, handler):_ -> do
cs <- gets cState cs <- gets cState
case cs of case cs of
ConnectionClosed -> return . Right $ Just AuthNoConnection Closed -> return . Right $ Just AuthNoStream
_ -> do _ -> do
r <- runErrorT handler r <- runErrorT handler
case r of case r of
@ -91,7 +90,7 @@ xmppSasl handlers = withConnection $ do
-- resource. -- resource.
auth :: [SaslHandler] auth :: [SaslHandler]
-> Maybe Text -> Maybe Text
-> TMVar Connection -> TMVar Stream
-> IO (Either XmppFailure (Maybe AuthFailure)) -> IO (Either XmppFailure (Maybe AuthFailure))
auth mechanisms resource con = runErrorT $ do auth mechanisms resource con = runErrorT $ do
ErrorT $ xmppSasl mechanisms con ErrorT $ xmppSasl mechanisms con
@ -107,7 +106,7 @@ simpleAuth :: Text.Text -- ^ The username
-> Text.Text -- ^ The password -> Text.Text -- ^ The password
-> Maybe Text -- ^ The desired resource or 'Nothing' to let the -> Maybe Text -- ^ The desired resource or 'Nothing' to let the
-- server assign one -- server assign one
-> TMVar Connection -> TMVar Stream
-> IO (Either XmppFailure (Maybe AuthFailure)) -> IO (Either XmppFailure (Maybe AuthFailure))
simpleAuth username passwd resource = flip auth resource $ simpleAuth username passwd resource = flip auth resource $
[ -- TODO: scramSha1Plus [ -- TODO: scramSha1Plus
@ -126,7 +125,7 @@ bindBody = pickleElem $
-- Sends a (synchronous) IQ set request for a (`Just') given or server-generated -- Sends a (synchronous) IQ set request for a (`Just') given or server-generated
-- resource and extract the JID from the non-error response. -- resource and extract the JID from the non-error response.
xmppBind :: Maybe Text -> TMVar Connection -> IO (Either XmppFailure Jid) xmppBind :: Maybe Text -> TMVar Stream -> IO (Either XmppFailure Jid)
xmppBind rsrc c = runErrorT $ do xmppBind rsrc c = runErrorT $ do
answer <- ErrorT $ pushIQ' "bind" Nothing Set Nothing (bindBody rsrc) c answer <- ErrorT $ pushIQ' "bind" Nothing Set Nothing (bindBody rsrc) c
case answer of case answer of
@ -134,7 +133,7 @@ xmppBind rsrc c = runErrorT $ do
let jid = unpickleElem xpJid b let jid = unpickleElem xpJid b
case jid of case jid of
Right jid' -> do Right jid' -> do
ErrorT $ withConnection (do ErrorT $ withStream (do
modify $ \s -> s{cJid = Just jid'} modify $ \s -> s{cJid = Just jid'}
return $ Right jid') c -- not pretty return $ Right jid') c -- not pretty
return jid' return jid'
@ -167,7 +166,7 @@ sessionIQ = IQRequestS $ IQRequest { iqRequestID = "sess"
-- Sends the session IQ set element and waits for an answer. Throws an error if -- Sends the session IQ set element and waits for an answer. Throws an error if
-- if an IQ error stanza is returned from the server. -- if an IQ error stanza is returned from the server.
startSession :: TMVar Connection -> IO () startSession :: TMVar Stream -> IO ()
startSession con = do startSession con = do
answer <- pushIQ' "session" Nothing Set Nothing sessionXml con answer <- pushIQ' "session" Nothing Set Nothing sessionXml con
case answer of case answer of

2
source/Network/Xmpp/Sasl/Common.hs

@ -22,7 +22,7 @@ import Data.Word (Word8)
import Data.XML.Pickle import Data.XML.Pickle
import Data.XML.Types import Data.XML.Types
import Network.Xmpp.Connection import Network.Xmpp.Stream
import Network.Xmpp.Sasl.StringPrep import Network.Xmpp.Sasl.StringPrep
import Network.Xmpp.Sasl.Types import Network.Xmpp.Sasl.Types
import Network.Xmpp.Marshal import Network.Xmpp.Marshal

1
source/Network/Xmpp/Sasl/Mechanisms/DigestMd5.hs

@ -31,7 +31,6 @@ import qualified Data.ByteString as BS
import Data.XML.Types import Data.XML.Types
import Network.Xmpp.Connection
import Network.Xmpp.Stream import Network.Xmpp.Stream
import Network.Xmpp.Types import Network.Xmpp.Types
import Network.Xmpp.Sasl.Common import Network.Xmpp.Sasl.Common

1
source/Network/Xmpp/Sasl/Mechanisms/Plain.hs

@ -35,7 +35,6 @@ import qualified Data.ByteString as BS
import Data.XML.Types import Data.XML.Types
import Network.Xmpp.Connection
import Network.Xmpp.Stream import Network.Xmpp.Stream
import Network.Xmpp.Types import Network.Xmpp.Types

6
source/Network/Xmpp/Sasl/Types.hs

@ -15,7 +15,7 @@ data AuthFailure = AuthXmlFailure
-- itself -- itself
| AuthStreamFailure XmppFailure -- ^ Stream error on stream restart | AuthStreamFailure XmppFailure -- ^ Stream error on stream restart
-- TODO: Rename AuthConnectionFailure? -- TODO: Rename AuthConnectionFailure?
| AuthNoConnection | AuthNoStream
| AuthFailure -- General instance used for the Error instance | AuthFailure -- General instance used for the Error instance
| AuthSaslFailure SaslFailure -- ^ Defined SASL error condition | AuthSaslFailure SaslFailure -- ^ Defined SASL error condition
| AuthStringPrepFailure -- ^ StringPrep failed | AuthStringPrepFailure -- ^ StringPrep failed
@ -27,9 +27,9 @@ instance Error AuthFailure where
data SaslElement = SaslSuccess (Maybe Text.Text) data SaslElement = SaslSuccess (Maybe Text.Text)
| SaslChallenge (Maybe Text.Text) | SaslChallenge (Maybe Text.Text)
-- | SASL mechanism XmppConnection computation, with the possibility of throwing -- | SASL mechanism Stream computation, with the possibility of throwing
-- an authentication error. -- an authentication error.
type SaslM a = ErrorT AuthFailure (StateT Connection IO) a type SaslM a = ErrorT AuthFailure (StateT Stream IO) a
type Pairs = [(ByteString, ByteString)] type Pairs = [(ByteString, ByteString)]

325
source/Network/Xmpp/Stream.hs

@ -1,6 +1,7 @@
{-# OPTIONS_HADDOCK hide #-} {-# OPTIONS_HADDOCK hide #-}
{-# LANGUAGE NoMonomorphismRestriction, OverloadedStrings #-} {-# LANGUAGE NoMonomorphismRestriction, OverloadedStrings #-}
{-# LANGUAGE TupleSections #-} {-# LANGUAGE TupleSections #-}
{-# LANGUAGE ScopedTypeVariables #-}
module Network.Xmpp.Stream where module Network.Xmpp.Stream where
@ -20,16 +21,34 @@ import Data.Void (Void)
import Data.XML.Pickle import Data.XML.Pickle
import Data.XML.Types import Data.XML.Types
import Network.Xmpp.Connection
import Network.Xmpp.Types import Network.Xmpp.Types
import Network.Xmpp.Marshal import Network.Xmpp.Marshal
import Text.Xml.Stream.Elements import Text.Xml.Stream.Elements
import Text.XML.Stream.Parse as XP import Text.XML.Stream.Parse as XP
import Control.Concurrent (forkIO, threadDelay)
import Network import Network
import Control.Concurrent.STM import Control.Concurrent.STM
import Data.ByteString as BS
import Data.ByteString.Base64
import System.Log.Logger
import qualified GHC.IO.Exception as GIE
import Control.Monad
import Control.Monad.IO.Class
import Control.Monad.Trans.Class
import System.IO.Error (tryIOError)
import System.IO
import Data.Conduit
import Data.Conduit.Binary as CB
import Data.Conduit.Internal as DCI
import qualified Data.Conduit.List as CL
import qualified Data.Text as T
import Data.ByteString.Char8 as BSC8
import Text.XML.Unresolved(InvalidEventStream(..))
import qualified Control.Exception.Lifted as ExL
-- import Text.XML.Stream.Elements -- import Text.XML.Stream.Elements
-- Unpickles and returns a stream element. -- Unpickles and returns a stream element.
@ -67,16 +86,16 @@ openElementFromEvents = do
-- server responds in a way that is invalid, an appropriate stream error will be -- 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 XmppFailure -- generated, the connection to the server will be closed, and a XmppFailure
-- will be produced. -- will be produced.
startStream :: StateT Connection IO (Either XmppFailure ()) startStream :: StateT Stream IO (Either XmppFailure ())
startStream = runErrorT $ do startStream = runErrorT $ do
state <- lift $ get state <- lift $ get
con <- liftIO $ mkConnection state stream <- liftIO $ mkStream state
-- Set the `from' (which is also the expected to) attribute depending on the -- Set the `from' (which is also the expected to) attribute depending on the
-- state of the connection. -- state of the stream.
let expectedTo = case cState state of let expectedTo = case cState state of
ConnectionPlain -> if cJidWhenPlain state Plain -> if cJidWhenPlain state
then cJid state else Nothing then cJid state else Nothing
ConnectionSecured -> cJid state Secured -> cJid state
case cHostName state of case cHostName state of
Nothing -> throwError XmppOtherFailure -- TODO: When does this happen? Nothing -> throwError XmppOtherFailure -- TODO: When does this happen?
Just hostname -> lift $ do Just hostname -> lift $ do
@ -93,15 +112,15 @@ startStream = runErrorT $ do
Left e -> throwError e Left e -> throwError e
-- Successful unpickling of stream element. -- Successful unpickling of stream element.
Right (Right (ver, from, to, id, lt, features)) Right (Right (ver, from, to, id, lt, features))
| (unpack ver) /= "1.0" -> | (T.unpack ver) /= "1.0" ->
closeStreamWithError con StreamUnsupportedVersion Nothing closeStreamWithError stream StreamUnsupportedVersion Nothing
| lt == Nothing -> | lt == Nothing ->
closeStreamWithError con StreamInvalidXml Nothing closeStreamWithError stream StreamInvalidXml Nothing
-- If `from' is set, we verify that it's the correct one. TODO: Should we check against the realm instead? -- If `from' is set, we verify that it's the correct one. TODO: Should we check against the realm instead?
| isJust from && (from /= Just (Jid Nothing (fromJust $ cHostName state) Nothing)) -> | isJust from && (from /= Just (Jid Nothing (fromJust $ cHostName state) Nothing)) ->
closeStreamWithError con StreamInvalidFrom Nothing closeStreamWithError stream StreamInvalidFrom Nothing
| to /= expectedTo -> | to /= expectedTo ->
closeStreamWithError con StreamUndefinedCondition (Just $ Element "invalid-to" [] []) -- TODO: Suitable? closeStreamWithError stream StreamUndefinedCondition (Just $ Element "invalid-to" [] []) -- TODO: Suitable?
| otherwise -> do | otherwise -> do
modify (\s -> s{ cFeatures = features modify (\s -> s{ cFeatures = features
, cStreamLang = lt , cStreamLang = lt
@ -112,36 +131,36 @@ startStream = runErrorT $ do
-- Unpickling failed - we investigate the element. -- Unpickling failed - we investigate the element.
Right (Left (Element name attrs children)) Right (Left (Element name attrs children))
| (nameLocalName name /= "stream") -> | (nameLocalName name /= "stream") ->
closeStreamWithError con StreamInvalidXml Nothing closeStreamWithError stream StreamInvalidXml Nothing
| (nameNamespace name /= Just "http://etherx.jabber.org/streams") -> | (nameNamespace name /= Just "http://etherx.jabber.org/streams") ->
closeStreamWithError con StreamInvalidNamespace Nothing closeStreamWithError stream StreamInvalidNamespace Nothing
| (isJust $ namePrefix name) && (fromJust (namePrefix name) /= "stream") -> | (isJust $ namePrefix name) && (fromJust (namePrefix name) /= "stream") ->
closeStreamWithError con StreamBadNamespacePrefix Nothing closeStreamWithError stream StreamBadNamespacePrefix Nothing
| otherwise -> ErrorT $ checkchildren con (flattenAttrs attrs) | otherwise -> ErrorT $ checkchildren stream (flattenAttrs attrs)
where where
-- closeStreamWithError :: MonadIO m => TMVar Connection -> StreamErrorCondition -> -- closeStreamWithError :: MonadIO m => TMVar Stream -> StreamErrorCondition ->
-- Maybe Element -> ErrorT XmppFailure m () -- Maybe Element -> ErrorT XmppFailure m ()
closeStreamWithError con sec el = do closeStreamWithError stream sec el = do
liftIO $ do liftIO $ do
withConnection (pushElement . pickleElem xpStreamError $ withStream (pushElement . pickleElem xpStreamError $
StreamErrorInfo sec Nothing el) con StreamErrorInfo sec Nothing el) stream
closeStreams con closeStreams stream
throwError XmppOtherFailure throwError XmppOtherFailure
checkchildren con children = checkchildren stream children =
let to' = lookup "to" children let to' = lookup "to" children
ver' = lookup "version" children ver' = lookup "version" children
xl = lookup xmlLang children xl = lookup xmlLang children
in case () of () | Just (Nothing :: Maybe Jid) == (safeRead <$> to') -> in case () of () | Just (Nothing :: Maybe Jid) == (safeRead <$> to') ->
runErrorT $ closeStreamWithError con runErrorT $ closeStreamWithError stream
StreamBadNamespacePrefix Nothing StreamBadNamespacePrefix Nothing
| Nothing == ver' -> | Nothing == ver' ->
runErrorT $ closeStreamWithError con runErrorT $ closeStreamWithError stream
StreamUnsupportedVersion Nothing StreamUnsupportedVersion Nothing
| Just (Nothing :: Maybe LangTag) == (safeRead <$> xl) -> | Just (Nothing :: Maybe LangTag) == (safeRead <$> xl) ->
runErrorT $ closeStreamWithError con runErrorT $ closeStreamWithError stream
StreamInvalidXml Nothing StreamInvalidXml Nothing
| otherwise -> | otherwise ->
runErrorT $ closeStreamWithError con runErrorT $ closeStreamWithError stream
StreamBadFormat Nothing StreamBadFormat Nothing
safeRead x = case reads $ Text.unpack x of safeRead x = case reads $ Text.unpack x of
[] -> Nothing [] -> Nothing
@ -159,7 +178,7 @@ flattenAttrs attrs = Prelude.map (\(name, content) ->
-- Sets a new Event source using the raw source (of bytes) -- Sets a new Event source using the raw source (of bytes)
-- and calls xmppStartStream. -- and calls xmppStartStream.
restartStream :: StateT Connection IO (Either XmppFailure ()) restartStream :: StateT Stream IO (Either XmppFailure ())
restartStream = do restartStream = do
raw <- gets (cRecv . cHandle) raw <- gets (cRecv . cHandle)
let newSource = DCI.ResumableSource (loopRead raw $= XP.parseBytes def) let newSource = DCI.ResumableSource (loopRead raw $= XP.parseBytes def)
@ -251,12 +270,252 @@ xpStreamFeatures = xpWrap
-- | Connects to the XMPP server and opens the XMPP stream against the given -- | Connects to the XMPP server and opens the XMPP stream against the given
-- host name, port, and realm. -- host name, port, and realm.
connect :: HostName -> PortID -> Text -> IO (Either XmppFailure (TMVar Connection)) openStream :: HostName -> PortID -> Text -> IO (Either XmppFailure (TMVar Stream))
connect address port hostname = do openStream address port hostname = do
con <- connectTcp address port hostname stream <- connectTcp address port hostname
case con of case stream of
Right con' -> do Right stream' -> do
result <- withConnection startStream con' result <- withStream startStream stream'
return $ Right con' return $ Right stream'
Left e -> do Left e -> do
return $ Left e return $ Left e
-- | Send "</stream:stream>" and wait for the server to finish processing and to
-- close the connection. Any remaining elements from the server are returned.
-- Surpresses StreamEndFailure exceptions, but may throw a StreamCloseError.
closeStreams :: TMVar Stream -> IO (Either XmppFailure [Element])
closeStreams = withStream $ do
send <- gets (cSend . cHandle)
cc <- gets (cClose . cHandle)
liftIO $ send "</stream:stream>"
void $ liftIO $ forkIO $ do
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 Stream IO (Either XmppFailure [Element])
collectElems es = do
result <- pullElement
case result of
Left StreamEndFailure -> return $ Right es
Left e -> return $ Left $ StreamCloseError (es, e)
Right e -> collectElems (e:es)
-- Enable/disable debug output
-- This will dump all incoming and outgoing network taffic to the console,
-- prefixed with "in: " and "out: " respectively
debug :: Bool
debug = False
-- TODO: Can the TLS send/recv functions throw something other than an IO error?
wrapIOException :: IO a -> StateT Stream 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 Stream IO (Either XmppFailure Bool)
pushElement x = do
send <- gets (cSend . cHandle)
wrapIOException $ send $ renderElement x
-- | Encode and send stanza
pushStanza :: Stanza -> TMVar Stream -> IO (Either XmppFailure Bool)
pushStanza s = withStream' . 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 Stream IO (Either XmppFailure Bool)
pushXmlDecl = do
con <- gets cHandle
wrapIOException $ (cSend con) "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>"
pushOpenElement :: Element -> StateT Stream IO (Either XmppFailure Bool)
pushOpenElement e = do
sink <- gets (cSend . cHandle)
wrapIOException $ sink $ renderOpenElement e
-- `Connect-and-resumes' the given sink to the stream source, and pulls a
-- `b' value.
runEventsSink :: Sink Event IO b -> StateT Stream IO (Either XmppFailure b)
runEventsSink snk = do -- TODO: Wrap exceptions?
source <- gets cEventSource
(src', r) <- lift $ source $$++ snk
modify (\s -> s{cEventSource = src'})
return $ Right r
pullElement :: StateT Stream IO (Either XmppFailure Element)
pullElement = do
ExL.catches (do
e <- runEventsSink (elements =$ await)
case e of
Left f -> return $ Left f
Right Nothing -> return $ Left XmppOtherFailure -- TODO
Right (Just r) -> return $ Right r
)
[ ExL.Handler (\StreamEnd -> return $ Left StreamEndFailure)
, ExL.Handler (\(InvalidXmppXml s) -- Invalid XML `Event' encountered, or missing element close tag
-> return $ Left XmppOtherFailure) -- TODO: Log: s
, ExL.Handler $ \(e :: InvalidEventStream) -- xml-conduit exception
-> return $ Left XmppOtherFailure -- TODO: Log: (show e)
]
-- Pulls an element and unpickles it.
pullUnpickle :: PU [Node] a -> StateT Stream IO (Either XmppFailure a)
pullUnpickle p = do
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.
pullStanza :: TMVar Stream -> IO (Either XmppFailure Stanza)
pullStanza = withStream' $ do
res <- pullUnpickle xpStreamStanza
case res of
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
catchPush :: IO () -> IO Bool
catchPush p = ExL.catch
(p >> return True)
(\e -> case GIE.ioe_type e of
GIE.ResourceVanished -> return False
GIE.IllegalOperation -> return False
_ -> ExL.throwIO e
)
-- Stream state used when there is no connection.
xmppNoStream :: Stream
xmppNoStream = Stream
{ cHandle = StreamHandle { cSend = \_ -> return False
, cRecv = \_ -> ExL.throwIO
XmppOtherFailure
, cFlush = return ()
, cClose = return ()
}
, cEventSource = DCI.ResumableSource zeroSource (return ())
, cFeatures = SF Nothing [] []
, cState = Closed
, cHostName = Nothing
, cJid = Nothing
, cStreamLang = Nothing
, cStreamId = Nothing
, cPreferredLang = Nothing
, cToJid = Nothing
, cJidWhenPlain = False
, cFrom = Nothing
}
where
zeroSource :: Source IO output
zeroSource = liftIO . ExL.throwIO $ XmppOtherFailure
connectTcp :: HostName -> PortID -> Text -> IO (Either XmppFailure (TMVar Stream))
connectTcp host port hostname = do
let PortNumber portNumber = port
debugM "Pontarius.Xmpp" $ "Connecting to " ++ host ++ " on port " ++
(show portNumber) ++ " through the realm " ++ (T.unpack hostname) ++ "."
h <- connectTo host port
debugM "Pontarius.Xmpp" "Setting NoBuffering mode on handle."
hSetBuffering h NoBuffering
let eSource = DCI.ResumableSource
((sourceHandle h $= logConduit) $= XP.parseBytes def)
(return ())
let hand = StreamHandle { cSend = \d -> do
let d64 = encode d
debugM "Pontarius.Xmpp" $
"Sending TCP data: " ++ (BSC8.unpack d64)
++ "."
catchPush $ BS.hPut h d
, cRecv = \n -> do
d <- BS.hGetSome h n
let d64 = encode d
debugM "Pontarius.Xmpp" $
"Received TCP data: " ++
(BSC8.unpack d64) ++ "."
return d
, cFlush = hFlush h
, cClose = hClose h
}
let stream = Stream
{ cHandle = hand
, cEventSource = eSource
, cFeatures = (SF Nothing [] [])
, cState = Plain
, cHostName = (Just hostname)
, cJid = Nothing
, cPreferredLang = Nothing -- TODO: Allow user to set
, cStreamLang = Nothing
, cStreamId = Nothing
, cToJid = Nothing -- TODO: Allow user to set
, cJidWhenPlain = False -- TODO: Allow user to set
, cFrom = Nothing
}
stream' <- mkStream stream
return $ Right stream'
where
logConduit :: Conduit ByteString IO ByteString
logConduit = CL.mapM $ \d -> do
let d64 = encode d
debugM "Pontarius.Xmpp" $ "Received TCP data: " ++ (BSC8.unpack d64) ++
"."
return d
-- Closes the connection and updates the XmppConMonad Stream state.
-- killStream :: TMVar Stream -> IO (Either ExL.SomeException ())
killStream :: TMVar Stream -> IO (Either XmppFailure ())
killStream = withStream $ do
cc <- gets (cClose . cHandle)
err <- wrapIOException cc
-- (ExL.try cc :: IO (Either ExL.SomeException ()))
put xmppNoStream
return err
-- Sends an IQ request and waits for the response. If the response ID does not
-- match the outgoing ID, an error is thrown.
pushIQ' :: StanzaId
-> Maybe Jid
-> IQRequestType
-> Maybe LangTag
-> Element
-> TMVar Stream
-> IO (Either XmppFailure (Either IQError IQResult))
pushIQ' iqID to tp lang body stream = do
pushStanza (IQRequestS $ IQRequest iqID Nothing to lang tp body) stream
res <- pullStanza stream
case res of
Left e -> return $ Left e
Right (IQErrorS e) -> return $ Right $ Left e
Right (IQResultS r) -> do
unless
(iqID == iqResultID r) . liftIO . ExL.throwIO $
XmppOtherFailure
-- TODO: Log: ("In sendIQ' IDs don't match: " ++ show iqID ++
-- " /= " ++ show (iqResultID r) ++ " .")
return $ Right $ Right r
_ -> return $ Left XmppOtherFailure
-- TODO: Log: "sendIQ': unexpected stanza type "
debugConduit :: Pipe l ByteString ByteString u IO b
debugConduit = forever $ do
s' <- await
case s' of
Just s -> do
liftIO $ BS.putStrLn (BS.append "in: " s)
yield s
Nothing -> return ()

15
source/Network/Xmpp/Tls.hs

@ -17,7 +17,6 @@ import Data.Conduit.Tls as TLS
import Data.Typeable import Data.Typeable
import Data.XML.Types import Data.XML.Types
import Network.Xmpp.Connection
import Network.Xmpp.Stream import Network.Xmpp.Stream
import Network.Xmpp.Types import Network.Xmpp.Types
@ -75,16 +74,16 @@ exampleParams = TLS.defaultParamsClient
-- Pushes "<starttls/>, waits for "<proceed/>", performs the TLS handshake, and -- Pushes "<starttls/>, waits for "<proceed/>", performs the TLS handshake, and
-- restarts the stream. -- restarts the stream.
startTls :: TLS.TLSParams -> TMVar Connection -> IO (Either XmppFailure ()) startTls :: TLS.TLSParams -> TMVar Stream -> IO (Either XmppFailure ())
startTls params con = Ex.handle (return . Left . TlsError) startTls params con = Ex.handle (return . Left . TlsError)
. flip withConnection con . flip withStream con
. runErrorT $ do . runErrorT $ do
features <- lift $ gets cFeatures features <- lift $ gets cFeatures
state <- gets cState state <- gets cState
case state of case state of
ConnectionPlain -> return () Plain -> return ()
ConnectionClosed -> throwError XmppNoConnection Closed -> throwError XmppNoStream
ConnectionSecured -> throwError TlsConnectionSecured Secured -> throwError TlsStreamSecured
con <- lift $ gets cHandle con <- lift $ gets cHandle
when (stls features == Nothing) $ throwError TlsNoServerSupport when (stls features == Nothing) $ throwError TlsNoServerSupport
lift $ pushElement starttlsE lift $ pushElement starttlsE
@ -94,12 +93,12 @@ startTls params con = Ex.handle (return . Left . TlsError)
Right (Element "{urn:ietf:params:xml:ns:xmpp-tls}proceed" [] []) -> return $ Right () 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 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) (raw, _snk, psh, read, ctx) <- lift $ TLS.tlsinit debug params (mkBackend con)
let newHand = ConnectionHandle { cSend = catchPush . psh let newHand = StreamHandle { cSend = catchPush . psh
, cRecv = read , cRecv = read
, cFlush = contextFlush ctx , cFlush = contextFlush ctx
, cClose = bye ctx >> cClose con , cClose = bye ctx >> cClose con
} }
lift $ modify ( \x -> x {cHandle = newHand}) lift $ modify ( \x -> x {cHandle = newHand})
either (lift . Ex.throwIO) return =<< lift restartStream either (lift . Ex.throwIO) return =<< lift restartStream
modify (\s -> s{cState = ConnectionSecured}) modify (\s -> s{cState = Secured})
return () return ()

60
source/Network/Xmpp/Types.hs

@ -31,12 +31,12 @@ module Network.Xmpp.Types
, XmppFailure(..) , XmppFailure(..)
, StreamErrorCondition(..) , StreamErrorCondition(..)
, Version(..) , Version(..)
, ConnectionHandle(..) , StreamHandle(..)
, Connection(..) , Stream(..)
, withConnection , withStream
, withConnection' , withStream'
, mkConnection , mkStream
, ConnectionState(..) , StreamState(..)
, StreamErrorInfo(..) , StreamErrorInfo(..)
, langTag , langTag
, Jid(..) , Jid(..)
@ -652,8 +652,8 @@ data XmppFailure = StreamErrorFailure StreamErrorInfo -- ^ An error XML stream
-- far. -- far.
| TlsError TLS.TLSError | TlsError TLS.TLSError
| TlsNoServerSupport | TlsNoServerSupport
| XmppNoConnection | XmppNoStream
| TlsConnectionSecured -- ^ Connection already secured | TlsStreamSecured -- ^ Connection already secured
| XmppOtherFailure -- ^ Undefined condition. More | XmppOtherFailure -- ^ Undefined condition. More
-- information should be available -- information should be available
-- in the log. -- in the log.
@ -762,16 +762,16 @@ data ServerFeatures = SF
} deriving Show } deriving Show
-- | Signals the state of the connection. -- | Signals the state of the connection.
data ConnectionState data StreamState
= ConnectionClosed -- ^ No connection at this point. = Closed -- ^ No stream at this point.
| ConnectionPlain -- ^ Connection established, but not secured. | Plain -- ^ Stream established, but not secured.
| ConnectionSecured -- ^ Connection established and secured via TLS. | Secured -- ^ Stream established and secured via TLS.
deriving (Show, Eq, Typeable) deriving (Show, Eq, Typeable)
-- | Defines operations for sending, receiving, flushing, and closing on a -- | Defines operations for sending, receiving, flushing, and closing on a
-- connection. -- connection.
data ConnectionHandle = data StreamHandle =
ConnectionHandle { cSend :: BS.ByteString -> IO Bool StreamHandle { cSend :: BS.ByteString -> IO Bool
, cRecv :: Int -> IO BS.ByteString , cRecv :: Int -> IO BS.ByteString
-- This is to hold the state of the XML parser (otherwise -- This is to hold the state of the XML parser (otherwise
-- we will receive EventBeginDocument events and forget -- we will receive EventBeginDocument events and forget
@ -780,9 +780,9 @@ data ConnectionHandle =
, cClose :: IO () , cClose :: IO ()
} }
data Connection = Connection data Stream = Stream
{ cState :: !ConnectionState -- ^ State of connection { cState :: !StreamState -- ^ State of connection
, cHandle :: ConnectionHandle -- ^ Handle to send, receive, flush, and close , cHandle :: StreamHandle -- ^ Handle to send, receive, flush, and close
-- on the connection. -- on the connection.
, cEventSource :: ResumableSource IO Event -- ^ Event conduit source, and , cEventSource :: ResumableSource IO Event -- ^ Event conduit source, and
-- its associated finalizer -- its associated finalizer
@ -803,26 +803,26 @@ data Connection = Connection
-- element's `from' attribute. -- element's `from' attribute.
} }
withConnection :: StateT Connection IO (Either XmppFailure c) -> TMVar Connection -> IO (Either XmppFailure c) withStream :: StateT Stream IO (Either XmppFailure c) -> TMVar Stream -> IO (Either XmppFailure c)
withConnection action con = bracketOnError withStream action stream = bracketOnError
(atomically $ takeTMVar con) (atomically $ takeTMVar stream)
(atomically . putTMVar con ) (atomically . putTMVar stream)
(\c -> do (\s -> do
(r, c') <- runStateT action c (r, s') <- runStateT action s
atomically $ putTMVar con c' atomically $ putTMVar stream s'
return r return r
) )
-- nonblocking version. Changes to the connection are ignored! -- nonblocking version. Changes to the connection are ignored!
withConnection' :: StateT Connection IO (Either XmppFailure b) -> TMVar Connection -> IO (Either XmppFailure b) withStream' :: StateT Stream IO (Either XmppFailure b) -> TMVar Stream -> IO (Either XmppFailure b)
withConnection' action con = do withStream' action stream = do
con_ <- atomically $ readTMVar con stream_ <- atomically $ readTMVar stream
(r, _) <- runStateT action con_ (r, _) <- runStateT action stream_
return r return r
mkConnection :: Connection -> IO (TMVar Connection) mkStream :: Stream -> IO (TMVar Stream)
mkConnection con = {- Connection `fmap` -} (atomically $ newTMVar con) mkStream con = {- Stream `fmap` -} (atomically $ newTMVar con)
--------------- ---------------
-- JID -- JID

12
source/Network/Xmpp/Xep/InbandRegistration.hs

@ -19,7 +19,7 @@ import qualified Data.Text as Text
import Data.XML.Pickle import Data.XML.Pickle
import qualified Data.XML.Types as XML import qualified Data.XML.Types as XML
import Network.Xmpp.Connection import Network.Xmpp.Stream
import Network.Xmpp.Pickle import Network.Xmpp.Pickle
import Network.Xmpp.Types import Network.Xmpp.Types
import Network.Xmpp.Xep.ServiceDiscovery import Network.Xmpp.Xep.ServiceDiscovery
@ -32,7 +32,7 @@ ibrns = "jabber:iq:register"
ibrName x = (XML.Name x (Just ibrns) Nothing) ibrName x = (XML.Name x (Just ibrns) Nothing)
data IbrError = IbrNotSupported data IbrError = IbrNotSupported
| IbrNoConnection | IbrNoStream
| IbrIQError IQError | IbrIQError IQError
deriving (Show) deriving (Show)
@ -61,7 +61,7 @@ emptyQuery = Query Nothing False False []
-- hn' <- gets sHostname -- hn' <- gets sHostname
-- hn <- case hn' of -- hn <- case hn' of
-- Just h -> return (Jid Nothing h Nothing) -- Just h -> return (Jid Nothing h Nothing)
-- Nothing -> throwError IbrNoConnection -- Nothing -> throwError IbrNoStream
-- qi <- lift $ xmppQueryInfo Nothing Nothing -- qi <- lift $ xmppQueryInfo Nothing Nothing
-- case qi of -- case qi of
-- Left e -> return False -- Left e -> return False
@ -71,7 +71,7 @@ emptyQuery = Query Nothing False False []
-- if r then return True else g -- if r then return True else g
query :: IQRequestType -> Query -> TMVar Connection -> IO (Either IbrError Query) query :: IQRequestType -> Query -> TMVar Stream -> IO (Either IbrError Query)
query queryType x con = do query queryType x con = do
answer <- pushIQ' "ibr" Nothing queryType Nothing (pickleElem xpQuery x) con answer <- pushIQ' "ibr" Nothing queryType Nothing (pickleElem xpQuery x) con
case answer of case answer of
@ -97,7 +97,7 @@ mapError f = mapErrorT (liftM $ left f)
-- | Retrieve the necessary fields and fill them in to register an account with -- | Retrieve the necessary fields and fill them in to register an account with
-- the server -- the server
registerWith :: [(Field, Text.Text)] registerWith :: [(Field, Text.Text)]
-> TMVar Connection -> TMVar Stream
-> IO (Either RegisterError Query) -> IO (Either RegisterError Query)
registerWith givenFields con = runErrorT $ do registerWith givenFields con = runErrorT $ do
fs <- mapError IbrError . ErrorT $ requestFields con fs <- mapError IbrError . ErrorT $ requestFields con
@ -114,7 +114,7 @@ registerWith givenFields con = runErrorT $ do
-- | Terminate your account on the server. You have to be logged in for this to -- | Terminate your account on the server. You have to be logged in for this to
-- work. You connection will most likely be terminated after unregistering. -- work. You connection will most likely be terminated after unregistering.
unregister :: TMVar Connection -> IO (Either IbrError Query) unregister :: TMVar Stream -> IO (Either IbrError Query)
unregister = query Set $ emptyQuery {remove = True} unregister = query Set $ emptyQuery {remove = True}
requestFields con = runErrorT $ do requestFields con = runErrorT $ do

4
source/Network/Xmpp/Xep/ServiceDiscovery.hs

@ -27,7 +27,7 @@ import Data.XML.Types
import Network.Xmpp import Network.Xmpp
import Network.Xmpp.Concurrent import Network.Xmpp.Concurrent
import Network.Xmpp.Concurrent.Types import Network.Xmpp.Concurrent.Types
import Network.Xmpp.Connection import Network.Xmpp.Stream
import Network.Xmpp.Marshal import Network.Xmpp.Marshal
import Network.Xmpp.Types import Network.Xmpp.Types
import Control.Concurrent.STM.TMVar import Control.Concurrent.STM.TMVar
@ -105,7 +105,7 @@ queryInfo to node context = do
xmppQueryInfo :: Maybe Jid xmppQueryInfo :: Maybe Jid
-> Maybe Text.Text -> Maybe Text.Text
-> TMVar Connection -> TMVar Stream
-> IO (Either DiscoError QueryInfoResult) -> IO (Either DiscoError QueryInfoResult)
xmppQueryInfo to node con = do xmppQueryInfo to node con = do
res <- pushIQ' "info" to Get Nothing queryBody con res <- pushIQ' "info" to Get Nothing queryBody con

Loading…
Cancel
Save