Browse Source

Hide `Context', add exports, extend documentation

As mentioned in #pontarius, `Context' is simply a bunch of thread
management features, and users that want that can build their own on
top of the `Connection' layer. The benefit of hiding `Context' is that
it makes the API clearer, and significantly decreases the complexity
of the library.

As the `Basic' module is simply an interface to `Connection', it was
renamed to `Connection'. The old `Connection' module was moved to
`Connection_'.

Exported the types of the fields of `Connection' (such as
`ConnectionState' and `ConnectionHandle' (previously `HandleLike').
master
Jon Kristensen 13 years ago
parent
commit
a205b23a6b
  1. 4
      pontarius-xmpp.cabal
  2. 13
      source/Network/Xmpp.hs
  3. 29
      source/Network/Xmpp/Basic.hs
  4. 4
      source/Network/Xmpp/Bind.hs
  5. 3
      source/Network/Xmpp/Concurrent/Channels/Types.hs
  6. 6
      source/Network/Xmpp/Concurrent/Monad.hs
  7. 6
      source/Network/Xmpp/Concurrent/Threads.hs
  8. 300
      source/Network/Xmpp/Connection.hs
  9. 266
      source/Network/Xmpp/Connection_.hs
  10. 6
      source/Network/Xmpp/Sasl.hs
  11. 2
      source/Network/Xmpp/Sasl/Common.hs
  12. 4
      source/Network/Xmpp/Sasl/Mechanisms/DigestMd5.hs
  13. 2
      source/Network/Xmpp/Sasl/Mechanisms/Plain.hs
  14. 34
      source/Network/Xmpp/Session.hs
  15. 26
      source/Network/Xmpp/Stream.hs
  16. 14
      source/Network/Xmpp/Tls.hs
  17. 61
      source/Network/Xmpp/Types.hs
  18. 2
      source/Network/Xmpp/Xep/InbandRegistration.hs
  19. 2
      source/Network/Xmpp/Xep/ServiceDiscovery.hs

4
pontarius-xmpp.cabal

@ -54,8 +54,8 @@ Library
, stringprep >=0.1.3 , stringprep >=0.1.3
, hslogger >=1.1.0 , hslogger >=1.1.0
Exposed-modules: Network.Xmpp Exposed-modules: Network.Xmpp
, Network.Xmpp.Connection
, Network.Xmpp.IM , Network.Xmpp.IM
, Network.Xmpp.Basic
Other-modules: Data.Conduit.Tls Other-modules: Data.Conduit.Tls
, Network.Xmpp.Bind , Network.Xmpp.Bind
, Network.Xmpp.Concurrent , Network.Xmpp.Concurrent
@ -68,7 +68,7 @@ Library
, Network.Xmpp.Concurrent.Channels.Types , Network.Xmpp.Concurrent.Channels.Types
, Network.Xmpp.Concurrent.Threads , Network.Xmpp.Concurrent.Threads
, Network.Xmpp.Concurrent.Monad , Network.Xmpp.Concurrent.Monad
, Network.Xmpp.Connection , Network.Xmpp.Connection_
, Network.Xmpp.IM.Message , Network.Xmpp.IM.Message
, Network.Xmpp.IM.Presence , Network.Xmpp.IM.Presence
, Network.Xmpp.Jid , Network.Xmpp.Jid

13
source/Network/Xmpp.hs

@ -1,7 +1,5 @@
-- | -- |
-- Module: $Header$ -- Module: $Header$
-- Description: RFC 6120 (XMPP: Core).
-- License: Apache License 2.0
-- --
-- Maintainer: info@jonkri.com -- Maintainer: info@jonkri.com
-- Stability: unstable -- Stability: unstable
@ -16,9 +14,12 @@
-- persistent XML streams among a distributed network of globally addressable, -- persistent XML streams among a distributed network of globally addressable,
-- presence-aware clients and servers. -- presence-aware clients and servers.
-- --
-- Pontarius is an XMPP client library, implementing the core capabilities of -- Pontarius XMPP is an XMPP client library, implementing the core capabilities
-- XMPP (RFC 6120): setup and teardown of XML streams, channel encryption, -- of XMPP (RFC 6120): setup and teardown of XML streams, channel encryption,
-- authentication, error handling, and communication primitives for messaging. -- authentication, error handling, and communication primitives for messaging.
--
-- For low-level access to Pontarius XMPP, see the "Network.Xmpp.Connection"
-- module.
{-# LANGUAGE NoMonomorphismRestriction, OverloadedStrings #-} {-# LANGUAGE NoMonomorphismRestriction, OverloadedStrings #-}
@ -142,6 +143,7 @@ module Network.Xmpp
, StreamErrorInfo(..) , StreamErrorInfo(..)
, StreamErrorCondition(..) , StreamErrorCondition(..)
, TlsFailure(..) , TlsFailure(..)
, AuthFailure(..)
) where ) where
@ -152,11 +154,12 @@ import Network.Xmpp.Bind
import Network.Xmpp.Concurrent import Network.Xmpp.Concurrent
import Network.Xmpp.Concurrent.Channels import Network.Xmpp.Concurrent.Channels
import Network.Xmpp.Concurrent.Types import Network.Xmpp.Concurrent.Types
import Network.Xmpp.Connection import Network.Xmpp.Connection_
import Network.Xmpp.Marshal import Network.Xmpp.Marshal
import Network.Xmpp.Message import Network.Xmpp.Message
import Network.Xmpp.Presence import Network.Xmpp.Presence
import Network.Xmpp.Sasl import Network.Xmpp.Sasl
import Network.Xmpp.Sasl.Types
import Network.Xmpp.Session import Network.Xmpp.Session
import Network.Xmpp.Stream import Network.Xmpp.Stream
import Network.Xmpp.Tls import Network.Xmpp.Tls

29
source/Network/Xmpp/Basic.hs

@ -1,29 +0,0 @@
module Network.Xmpp.Basic
( Connection(..)
, ConnectionState(..)
, connectTcp
, newSession
, withConnection
, startTls
, simpleAuth
, auth
, scramSha1
, digestMd5
, plain
, closeConnection
, pushStanza
, pullStanza
, closeConnection
, endContext
, setConnectionClosedHandler
)
where
import Network.Xmpp.Connection
import Network.Xmpp.Sasl
import Network.Xmpp.Session
import Network.Xmpp.Stream
import Network.Xmpp.Tls
import Network.Xmpp.Types
import Network.Xmpp.Concurrent

4
source/Network/Xmpp/Bind.hs

@ -11,7 +11,7 @@ import Data.Text as Text
import Data.XML.Pickle import Data.XML.Pickle
import Data.XML.Types import Data.XML.Types
import Network.Xmpp.Connection import Network.Xmpp.Connection_
import Network.Xmpp.Pickle import Network.Xmpp.Pickle
import Network.Xmpp.Types import Network.Xmpp.Types
@ -38,7 +38,7 @@ xmppBind rsrc c = do
-> return jid -> return jid
| otherwise -> throw StreamOtherFailure | otherwise -> throw StreamOtherFailure
-- TODO: Log: ("Bind couldn't unpickle JID from " ++ show answer) -- TODO: Log: ("Bind couldn't unpickle JID from " ++ show answer)
withConnection (modify $ \s -> s{sJid = Just jid}) c withConnection (modify $ \s -> s{cJid = Just jid}) c
return jid return jid
where where
-- Extracts the character data in the `jid' element. -- Extracts the character data in the `jid' element.

3
source/Network/Xmpp/Concurrent/Channels/Types.hs

@ -8,8 +8,7 @@ import Data.Text (Text)
import Network.Xmpp.Concurrent.Types import Network.Xmpp.Concurrent.Types
import Network.Xmpp.Types import Network.Xmpp.Types
-- | The @Session@ object holds the current state of the XMPP connection, and is -- | A concurrent interface to Pontarius XMPP.
-- thus necessary for any interaction with it.
data Session = Session data Session = Session
{ context :: Context { context :: Context
, stanzaCh :: TChan Stanza -- All stanzas , stanzaCh :: TChan Stanza -- All stanzas

6
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.Connection_
@ -71,8 +71,8 @@ modifyHandlers f session = atomically $ modifyTVar (eventHandlers session) f
writeTVar var (f x) writeTVar var (f x)
-- | Sets the handler to be executed when the server connection is closed. -- | Sets the handler to be executed when the server connection is closed.
setConnectionClosedHandler :: (StreamFailure -> Context -> IO ()) -> Context -> IO () setConnectionClosedHandler_ :: (StreamFailure -> Context -> IO ()) -> Context -> IO ()
setConnectionClosedHandler eh session = do setConnectionClosedHandler_ eh session = do
modifyHandlers (\s -> s{connectionClosedHandler = modifyHandlers (\s -> s{connectionClosedHandler =
\e -> eh e session}) session \e -> eh e session}) session

6
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.Connection_
import Control.Concurrent.STM.TMVar import Control.Concurrent.STM.TMVar
@ -35,7 +35,7 @@ readWorker onStanza onConnectionClosed stateRef =
-- necessarily be interruptible -- necessarily be interruptible
s <- atomically $ do s <- atomically $ do
con <- readTMVar stateRef con <- readTMVar stateRef
state <- sConnectionState <$> readTMVar con state <- cState <$> readTMVar con
when (state == ConnectionClosed) when (state == ConnectionClosed)
retry retry
return con return con
@ -81,7 +81,7 @@ startThreadsWith :: (Stanza -> IO ())
TMVar (TMVar Connection), TMVar (TMVar Connection),
ThreadId) ThreadId)
startThreadsWith stanzaHandler eh con = do startThreadsWith stanzaHandler eh con = do
read <- withConnection' (gets $ cSend. cHand) con read <- withConnection' (gets $ cSend. cHandle) con
writeLock <- newTMVarIO read writeLock <- newTMVarIO read
conS <- newTMVarIO con conS <- newTMVarIO con
-- lw <- forkIO $ writeWorker outC writeLock -- lw <- forkIO $ writeWorker outC writeLock

300
source/Network/Xmpp/Connection.hs

@ -1,265 +1,43 @@
{-# OPTIONS_HADDOCK hide #-} -- |
{-# LANGUAGE ScopedTypeVariables #-} -- Module: $Header$
{-# LANGUAGE OverloadedStrings #-} --
-- Maintainer: info@jonkri.com
module Network.Xmpp.Connection where -- Stability: unstable
-- Portability: portable
import Control.Applicative((<$>)) --
import Control.Concurrent (forkIO, threadDelay) -- This module allows for low-level access to Pontarius XMPP. Generally, the
import Control.Monad -- "Network.Xmpp" module should be used instead.
import Control.Monad.IO.Class --
import Control.Monad.Trans.Class -- The 'Connection' object provides the most low-level access to the XMPP
--import Control.Monad.Trans.Resource -- stream: a simple and single-threaded interface which exposes the conduit
import qualified Control.Exception.Lifted as Ex -- 'Event' source, as well as the input and output byte streams. Custom stateful
import qualified GHC.IO.Exception as GIE -- 'Connection' functions can be executed using 'withConnection'.
import Control.Monad.State.Strict --
-- The TLS, SASL, and 'Session' functionalities of Pontarius XMPP are built on
import Data.ByteString as BS -- top of this API.
import Data.ByteString.Char8 as BSC8
import Data.Conduit module Network.Xmpp.Connection
import Data.Conduit.Binary as CB ( Connection(..)
import Data.Conduit.Internal as DCI , ConnectionState(..)
import qualified Data.Conduit.List as CL , ConnectionHandle(..)
import Data.IORef , ServerFeatures(..)
import Data.Text(Text) , connect
import qualified Data.Text as T , withConnection
import Data.XML.Pickle , startTls
import Data.XML.Types , simpleAuth
, auth
import Network , pushStanza
import Network.Xmpp.Types , pullStanza
import Network.Xmpp.Marshal , closeConnection
import Network.Xmpp.Pickle , newSession
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
-- 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
pushElement :: Element -> StateT Connection IO Bool
pushElement x = do
send <- gets (cSend . cHand)
liftIO . send $ renderElement x
-- | Encode and send stanza
pushStanza :: Stanza -> TMVar Connection -> IO 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 = do
con <- gets cHand
liftIO $ (cSend con) "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>"
pushOpenElement :: Element -> StateT Connection IO Bool
pushOpenElement e = do
sink <- gets (cSend . cHand )
liftIO . 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
source <- gets cEventSource
(src', r) <- lift $ source $$++ snk
modify (\s -> s{cEventSource = src'})
return r
pullElement :: StateT Connection IO Element
pullElement = do
Ex.catches (do
e <- runEventsSink (elements =$ await)
case e of
Nothing -> liftIO $ Ex.throwIO StreamOtherFailure
Just r -> return r
) )
[ Ex.Handler (\StreamEnd -> Ex.throwIO StreamEndFailure)
, Ex.Handler (\(InvalidXmppXml s) -- Invalid XML `Event' encountered, or missing element close tag
-> liftIO . Ex.throwIO $ StreamOtherFailure) -- TODO: Log: s
, Ex.Handler $ \(e :: InvalidEventStream) -- xml-conduit exception
-> liftIO . Ex.throwIO $ StreamOtherFailure -- TODO: Log: (show e)
]
-- Pulls an element and unpickles it.
pullUnpickle :: PU [Node] a -> StateT Connection IO a
pullUnpickle p = do
res <- unpickleElem p <$> pullElement
case res of
Left e -> liftIO $ Ex.throwIO e
Right r -> return r
-- | Pulls a stanza (or stream error) from the stream. Throws an error on a stream
-- error.
pullStanza :: TMVar Connection -> IO Stanza
pullStanza = withConnection' $ do
res <- pullUnpickle xpStreamStanza
case res of
Left e -> liftIO . Ex.throwIO $ StreamErrorFailure e
Right r -> return 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
{ cHand = Hand { cSend = \_ -> return False
, cRecv = \_ -> Ex.throwIO
$ StreamOtherFailure
, cFlush = return ()
, cClose = return ()
}
, cEventSource = DCI.ResumableSource zeroSource (return ())
, sFeatures = SF Nothing [] []
, sConnectionState = ConnectionClosed
, sHostname = Nothing
, sJid = Nothing
, sStreamLang = Nothing
, sStreamId = Nothing
, sPreferredLang = Nothing
, sToJid = Nothing
, sJidWhenPlain = False
, sFrom = Nothing
}
where
zeroSource :: Source IO output
zeroSource = liftIO . Ex.throwIO $ StreamOtherFailure
-- Connects to the given hostname on port 5222 (TODO: Make this dynamic) and
-- updates the XmppConMonad Connection state.
connectTcpRaw :: HostName -> PortID -> Text -> IO (TMVar Connection)
connectTcpRaw 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 = Hand { 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
{ cHand = hand
, cEventSource = eSource
, sFeatures = (SF Nothing [] [])
, sConnectionState = ConnectionPlain
, sHostname = (Just hostname)
, sJid = Nothing
, sPreferredLang = Nothing -- TODO: Allow user to set
, sStreamLang = Nothing
, sStreamId = Nothing
, sToJid = Nothing -- TODO: Allow user to set
, sJidWhenPlain = False -- TODO: Allow user to set
, sFrom = Nothing
}
mkConnection con
where 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 = withConnection $ do
cc <- gets (cClose . cHand)
err <- liftIO $ (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 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
unless
(iqID == iqResultID r) . liftIO . Ex.throwIO $
StreamOtherFailure
-- TODO: Log: ("In sendIQ' IDs don't match: " ++ show iqID ++
-- " /= " ++ show (iqResultID r) ++ " .")
return $ Right r
_ -> liftIO $ Ex.throwIO StreamOtherFailure
-- TODO: Log: "sendIQ': unexpected stanza type "
-- | Send "</stream:stream>" and wait for the server to finish processing and to import Network.Xmpp.Connection_
-- close the connection. Any remaining elements from the server and whether or import Network.Xmpp.Sasl
-- not we received a </stream:stream> element from the server is returned. import Network.Xmpp.Session
closeStreams :: TMVar Connection -> IO ([Element], Bool) import Network.Xmpp.Stream
closeStreams = withConnection $ do import Network.Xmpp.Tls
send <- gets (cSend . cHand) import Network.Xmpp.Types
cc <- gets (cClose . cHand) import Network.Xmpp.Concurrent
liftIO $ send "</stream:stream>"
void $ liftIO $ forkIO $ do
threadDelay 3000000
(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 es = do
result <- Ex.try pullElement
case result of
Left StreamEndFailure -> return (es, True)
Left _ -> return (es, False)
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 ()

266
source/Network/Xmpp/Connection_.hs

@ -0,0 +1,266 @@
{-# OPTIONS_HADDOCK hide #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE OverloadedStrings #-}
module Network.Xmpp.Connection_ where
import Control.Applicative((<$>))
import Control.Concurrent (forkIO, threadDelay)
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 Network.Xmpp.Pickle
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
-- 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
pushElement :: Element -> StateT Connection IO Bool
pushElement x = do
send <- gets (cSend . cHandle)
liftIO . send $ renderElement x
-- | Encode and send stanza
pushStanza :: Stanza -> TMVar Connection -> IO 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 = do
con <- gets cHandle
liftIO $ (cSend con) "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>"
pushOpenElement :: Element -> StateT Connection IO Bool
pushOpenElement e = do
sink <- gets (cSend . cHandle)
liftIO . 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
source <- gets cEventSource
(src', r) <- lift $ source $$++ snk
modify (\s -> s{cEventSource = src'})
return r
pullElement :: StateT Connection IO Element
pullElement = do
Ex.catches (do
e <- runEventsSink (elements =$ await)
case e of
Nothing -> liftIO $ Ex.throwIO StreamOtherFailure
Just r -> return r
)
[ Ex.Handler (\StreamEnd -> Ex.throwIO StreamEndFailure)
, Ex.Handler (\(InvalidXmppXml s) -- Invalid XML `Event' encountered, or missing element close tag
-> liftIO . Ex.throwIO $ StreamOtherFailure) -- TODO: Log: s
, Ex.Handler $ \(e :: InvalidEventStream) -- xml-conduit exception
-> liftIO . Ex.throwIO $ StreamOtherFailure -- TODO: Log: (show e)
]
-- Pulls an element and unpickles it.
pullUnpickle :: PU [Node] a -> StateT Connection IO a
pullUnpickle p = do
res <- unpickleElem p <$> pullElement
case res of
Left e -> liftIO $ Ex.throwIO e
Right r -> return r
-- | Pulls a stanza (or stream error) from the stream. Throws an error on a stream
-- error.
pullStanza :: TMVar Connection -> IO Stanza
pullStanza = withConnection' $ do
res <- pullUnpickle xpStreamStanza
case res of
Left e -> liftIO . Ex.throwIO $ StreamErrorFailure e
Right r -> return 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
StreamOtherFailure
, 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 $ StreamOtherFailure
connectTcp :: HostName -> PortID -> Text -> IO (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
}
mkConnection 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 = withConnection $ do
cc <- gets (cClose . cHandle)
err <- liftIO $ (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 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
unless
(iqID == iqResultID r) . liftIO . Ex.throwIO $
StreamOtherFailure
-- TODO: Log: ("In sendIQ' IDs don't match: " ++ show iqID ++
-- " /= " ++ show (iqResultID r) ++ " .")
return $ Right r
_ -> liftIO $ Ex.throwIO StreamOtherFailure
-- 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 and whether or
-- not we received a </stream:stream> element from the server is returned.
closeStreams :: TMVar Connection -> IO ([Element], Bool)
closeStreams = withConnection $ do
send <- gets (cSend . cHandle)
cc <- gets (cClose . cHandle)
liftIO $ send "</stream:stream>"
void $ liftIO $ forkIO $ do
threadDelay 3000000
(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 es = do
result <- Ex.try pullElement
case result of
Left StreamEndFailure -> return (es, True)
Left _ -> return (es, False)
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 ()

6
source/Network/Xmpp/Sasl.hs

@ -29,7 +29,7 @@ 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.Connection_
import Network.Xmpp.Stream import Network.Xmpp.Stream
import Network.Xmpp.Types import Network.Xmpp.Types
@ -50,11 +50,11 @@ xmppSasl :: [SaslHandler] -- ^ Acceptable authentication mechanisms and their
xmppSasl handlers = withConnection $ do xmppSasl handlers = withConnection $ 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 . sFeatures mechanisms <- gets $ saslMechanisms . cFeatures
case (filter (\(name, _) -> name `elem` mechanisms)) handlers of case (filter (\(name, _) -> name `elem` mechanisms)) handlers of
[] -> return . Left $ AuthNoAcceptableMechanism mechanisms [] -> return . Left $ AuthNoAcceptableMechanism mechanisms
(_name, handler):_ -> runErrorT $ do (_name, handler):_ -> runErrorT $ do
cs <- gets sConnectionState cs <- gets cState
case cs of case cs of
ConnectionClosed -> throwError AuthConnectionFailure ConnectionClosed -> throwError AuthConnectionFailure
_ -> do _ -> do

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.Connection_
import Network.Xmpp.Pickle import Network.Xmpp.Pickle
import Network.Xmpp.Sasl.StringPrep import Network.Xmpp.Sasl.StringPrep
import Network.Xmpp.Sasl.Types import Network.Xmpp.Sasl.Types

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

@ -31,7 +31,7 @@ import qualified Data.ByteString as BS
import Data.XML.Types import Data.XML.Types
import Network.Xmpp.Connection import Network.Xmpp.Connection_
import Network.Xmpp.Pickle import Network.Xmpp.Pickle
import Network.Xmpp.Stream import Network.Xmpp.Stream
import Network.Xmpp.Types import Network.Xmpp.Types
@ -47,7 +47,7 @@ xmppDigestMd5 :: Text -- ^ Authentication identity (authzid or username)
-> SaslM () -> SaslM ()
xmppDigestMd5 authcid authzid password = do xmppDigestMd5 authcid authzid password = do
(ac, az, pw) <- prepCredentials authcid authzid password (ac, az, pw) <- prepCredentials authcid authzid password
hn <- gets sHostname hn <- gets cHostName
case hn of case hn of
Just hn' -> do Just hn' -> do
xmppDigestMd5' hn' ac az pw xmppDigestMd5' hn' ac az pw

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

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

34
source/Network/Xmpp/Session.hs

@ -12,7 +12,7 @@ import qualified Network.TLS as TLS
import Network.Xmpp.Bind import Network.Xmpp.Bind
import Network.Xmpp.Concurrent.Types import Network.Xmpp.Concurrent.Types
import Network.Xmpp.Concurrent.Channels import Network.Xmpp.Concurrent.Channels
import Network.Xmpp.Connection import Network.Xmpp.Connection_
import Network.Xmpp.Marshal import Network.Xmpp.Marshal
import Network.Xmpp.Pickle import Network.Xmpp.Pickle
import Network.Xmpp.Sasl import Network.Xmpp.Sasl
@ -42,7 +42,7 @@ session :: HostName -- ^ Host to connect to
-- the server decide) -- the server decide)
-> IO Session -- TODO: ErrorT -> IO Session -- TODO: ErrorT
session hostname realm port tls sasl = do session hostname realm port tls sasl = do
con' <- connectTcp hostname port realm con' <- connect hostname port realm
con <- case con' of con <- case con' of
Left e -> Ex.throwIO e Left e -> Ex.throwIO e
Right c -> return c Right c -> return c
@ -50,33 +50,15 @@ session hostname realm port tls sasl = do
saslResponse <- if isJust sasl then auth (fst $ fromJust sasl) (snd $ fromJust sasl) con >> return () else return () -- TODO: Eats AuthFailure saslResponse <- if isJust sasl then auth (fst $ fromJust sasl) (snd $ fromJust sasl) con >> return () else return () -- TODO: Eats AuthFailure
newSession con newSession con
-- | Connect to host with given address. -- | Connects to the XMPP server and opens the XMPP stream against the given
connectTcp :: HostName -> PortID -> Text -> IO (Either StreamFailure (TMVar Connection)) -- host name, port, and realm.
connectTcp address port hostname = do connect :: HostName -> PortID -> Text -> IO (Either StreamFailure (TMVar Connection))
con <- connectTcpRaw address port hostname connect address port hostname = do
con <- connectTcp address port hostname
result <- withConnection startStream con result <- withConnection startStream con
case result of case result of
Left e -> do Left e -> return $ Left e -- TODO
withConnection (pushElement . pickleElem xpStreamError $ toError e)
con
closeStreams con
return $ Left e
Right () -> return $ Right con Right () -> return $ Right con
where
-- toError (StreamNotStreamElement _name) =
-- XmppStreamFailure StreamInvalidXml Nothing Nothing
-- toError (StreamInvalidStreamNamespace _ns) =
-- XmppStreamFailure StreamInvalidNamespace Nothing Nothing
-- toError (StreamInvalidStreamPrefix _prefix) =
-- XmppStreamFailure StreamBadNamespacePrefix Nothing Nothing
-- toError (StreamWrongVersion _ver) =
-- XmppStreamFailure StreamUnsupportedVersion Nothing Nothing
-- toError (StreamWrongLangTag _) =
-- XmppStreamFailure StreamInvalidXml Nothing Nothing
-- toError StreamUnknownError =
-- XmppStreamFailure StreamBadFormat Nothing Nothing
-- TODO: Catch remaining xmppStartStream errors.
toError _ = StreamErrorInfo StreamBadFormat Nothing Nothing
sessionXml :: Element sessionXml :: Element
sessionXml = pickleElem sessionXml = pickleElem

26
source/Network/Xmpp/Stream.hs

@ -20,7 +20,7 @@ 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.Connection_
import Network.Xmpp.Pickle import Network.Xmpp.Pickle
import Network.Xmpp.Types import Network.Xmpp.Types
import Network.Xmpp.Marshal import Network.Xmpp.Marshal
@ -71,11 +71,11 @@ startStream = runErrorT $ do
con <- liftIO $ mkConnection state con <- liftIO $ mkConnection 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 connection.
let expectedTo = case sConnectionState state of let expectedTo = case cState state of
ConnectionPlain -> if sJidWhenPlain state ConnectionPlain -> if cJidWhenPlain state
then sJid state else Nothing then cJid state else Nothing
ConnectionSecured -> sJid state ConnectionSecured -> cJid state
case sHostname state of case cHostName state of
Nothing -> throwError StreamOtherFailure -- TODO: When does this happen? Nothing -> throwError StreamOtherFailure -- TODO: When does this happen?
Just hostname -> lift $ do Just hostname -> lift $ do
pushXmlDecl pushXmlDecl
@ -84,7 +84,7 @@ startStream = runErrorT $ do
, expectedTo , expectedTo
, Just (Jid Nothing hostname Nothing) , Just (Jid Nothing hostname Nothing)
, Nothing , Nothing
, sPreferredLang state , cPreferredLang state
) )
response <- ErrorT $ runEventsSink $ runErrorT $ streamS expectedTo response <- ErrorT $ runEventsSink $ runErrorT $ streamS expectedTo
case response of case response of
@ -95,15 +95,15 @@ startStream = runErrorT $ do
| lt == Nothing -> | lt == Nothing ->
closeStreamWithError con StreamInvalidXml Nothing closeStreamWithError con 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 $ sHostname state) Nothing)) -> | isJust from && (from /= Just (Jid Nothing (fromJust $ cHostName state) Nothing)) ->
closeStreamWithError con StreamInvalidFrom Nothing closeStreamWithError con StreamInvalidFrom Nothing
| to /= expectedTo -> | to /= expectedTo ->
closeStreamWithError con StreamUndefinedCondition (Just $ Element "invalid-to" [] []) -- TODO: Suitable? closeStreamWithError con StreamUndefinedCondition (Just $ Element "invalid-to" [] []) -- TODO: Suitable?
| otherwise -> do | otherwise -> do
modify (\s -> s{ sFeatures = features modify (\s -> s{ cFeatures = features
, sStreamLang = lt , cStreamLang = lt
, sStreamId = id , cStreamId = id
, sFrom = from , cFrom = from
} ) } )
return () return ()
-- Unpickling failed - we investigate the element. -- Unpickling failed - we investigate the element.
@ -158,7 +158,7 @@ flattenAttrs attrs = Prelude.map (\(name, content) ->
-- and calls xmppStartStream. -- and calls xmppStartStream.
restartStream :: StateT Connection IO (Either StreamFailure ()) restartStream :: StateT Connection IO (Either StreamFailure ())
restartStream = do restartStream = do
raw <- gets (cRecv . cHand) raw <- gets (cRecv . cHandle)
let newSource = DCI.ResumableSource (loopRead raw $= XP.parseBytes def) let newSource = DCI.ResumableSource (loopRead raw $= XP.parseBytes def)
(return ()) (return ())
modify (\s -> s{cEventSource = newSource }) modify (\s -> s{cEventSource = newSource })

14
source/Network/Xmpp/Tls.hs

@ -17,7 +17,7 @@ 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.Connection_
import Network.Xmpp.Pickle(ppElement) import Network.Xmpp.Pickle(ppElement)
import Network.Xmpp.Stream import Network.Xmpp.Stream
import Network.Xmpp.Types import Network.Xmpp.Types
@ -80,13 +80,13 @@ startTls :: TLS.TLSParams -> TMVar Connection -> IO (Either TlsFailure ())
startTls params con = Ex.handle (return . Left . TlsError) startTls params con = Ex.handle (return . Left . TlsError)
. flip withConnection con . flip withConnection con
. runErrorT $ do . runErrorT $ do
features <- lift $ gets sFeatures features <- lift $ gets cFeatures
state <- gets sConnectionState state <- gets cState
case state of case state of
ConnectionPlain -> return () ConnectionPlain -> return ()
ConnectionClosed -> throwError TlsNoConnection ConnectionClosed -> throwError TlsNoConnection
ConnectionSecured -> throwError TlsConnectionSecured ConnectionSecured -> throwError TlsConnectionSecured
con <- lift $ gets cHand con <- lift $ gets cHandle
when (stls features == Nothing) $ throwError TlsNoServerSupport when (stls features == Nothing) $ throwError TlsNoServerSupport
lift $ pushElement starttlsE lift $ pushElement starttlsE
answer <- lift $ pullElement answer <- lift $ pullElement
@ -98,12 +98,12 @@ startTls params con = Ex.handle (return . Left . TlsError)
e -> lift $ Ex.throwIO StreamOtherFailure e -> lift $ Ex.throwIO StreamOtherFailure
-- TODO: Log: "Unexpected element: " ++ ppElement e -- TODO: Log: "Unexpected element: " ++ ppElement e
(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 = Hand { cSend = catchPush . psh let newHand = ConnectionHandle { 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 {cHand = newHand}) lift $ modify ( \x -> x {cHandle = newHand})
either (lift . Ex.throwIO) return =<< lift restartStream either (lift . Ex.throwIO) return =<< lift restartStream
modify (\s -> s{sConnectionState = ConnectionSecured}) modify (\s -> s{cState = ConnectionSecured})
return () return ()

61
source/Network/Xmpp/Types.hs

@ -31,7 +31,7 @@ module Network.Xmpp.Types
, StreamFailure(..) , StreamFailure(..)
, StreamErrorCondition(..) , StreamErrorCondition(..)
, Version(..) , Version(..)
, HandleLike(..) , ConnectionHandle(..)
, Connection(..) , Connection(..)
, withConnection , withConnection
, withConnection' , withConnection'
@ -739,55 +739,48 @@ data ServerFeatures = SF
, other :: ![Element] , other :: ![Element]
} deriving Show } deriving Show
-- | Signals the state of the connection.
data ConnectionState data ConnectionState
= ConnectionClosed -- ^ No connection at this point. = ConnectionClosed -- ^ No connection at this point.
| ConnectionPlain -- ^ Connection established, but not secured. | ConnectionPlain -- ^ Connection established, but not secured.
| ConnectionSecured -- ^ Connection established and secured via TLS. | ConnectionSecured -- ^ Connection established and secured via TLS.
deriving (Show, Eq, Typeable) deriving (Show, Eq, Typeable)
data HandleLike = Hand { cSend :: BS.ByteString -> IO Bool -- | Defines operations for sending, receiving, flushing, and closing on a
-- connection.
data ConnectionHandle =
ConnectionHandle { 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 -- This is to hold the state of the XML parser (otherwise
-- (otherwise we will receive lot's of EvenBegin -- we will receive EventBeginDocument events and forget
-- Document and forger about name prefixes) -- about name prefixes).
, cFlush :: IO () , cFlush :: IO ()
, cClose :: IO () , cClose :: IO ()
} }
data Connection = Connection data Connection = Connection
{ sConnectionState :: !ConnectionState -- ^ State of { cState :: !ConnectionState -- ^ State of connection
-- connection , cHandle :: ConnectionHandle -- ^ Handle to send, receive, flush, and close
, cHand :: HandleLike -- on the connection.
, cEventSource :: ResumableSource IO Event , cEventSource :: ResumableSource IO Event -- ^ Event conduit source, and
, sFeatures :: !ServerFeatures -- ^ Features the server -- its associated finalizer
-- advertised , cFeatures :: !ServerFeatures -- ^ Features as advertised by the server
, sHostname :: !(Maybe Text) -- ^ Hostname of the , cHostName :: !(Maybe Text) -- ^ Hostname of the server
-- server , cJid :: !(Maybe Jid) -- ^ Our JID
, sJid :: !(Maybe Jid) -- ^ Our JID , cPreferredLang :: !(Maybe LangTag) -- ^ Default language when no explicit
, sPreferredLang :: !(Maybe LangTag) -- ^ Default language
-- when no explicit
-- language tag is set -- language tag is set
, sStreamLang :: !(Maybe LangTag) -- ^ Will be a `Just' , cStreamLang :: !(Maybe LangTag) -- ^ Will be a `Just' value once connected
-- value once connected
-- to the server. -- to the server.
, sStreamId :: !(Maybe Text) -- ^ Stream ID as , cStreamId :: !(Maybe Text) -- ^ Stream ID as specified by the server.
-- specified by the , cToJid :: !(Maybe Jid) -- ^ JID to include in the stream element's `to'
-- server. -- attribute when the connection is secured. See
, sToJid :: !(Maybe Jid) -- ^ JID to include in the -- also below.
-- stream element's `to' , cJidWhenPlain :: !Bool -- ^ Whether or not to also include the Jid when
-- attribute when the -- the connection is plain.
-- connection is , cFrom :: !(Maybe Jid) -- ^ From as specified by the server in the stream
-- secured. See also below. -- element's `from' attribute.
, sJidWhenPlain :: !Bool -- ^ Whether or not to also
-- include the Jid when the
-- connection is plain.
, sFrom :: !(Maybe Jid) -- ^ From as specified by
-- the server in the
-- stream element's `from'
-- attribute.
} }
withConnection :: StateT Connection IO c -> TMVar Connection -> IO c withConnection :: StateT Connection IO c -> TMVar Connection -> IO c
withConnection action con = bracketOnError withConnection action con = bracketOnError
(atomically $ takeTMVar con) (atomically $ takeTMVar con)

2
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.Connection_
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

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

@ -28,7 +28,7 @@ import Network.Xmpp
import Network.Xmpp.Concurrent import Network.Xmpp.Concurrent
import Network.Xmpp.Concurrent.Channels import Network.Xmpp.Concurrent.Channels
import Network.Xmpp.Concurrent.Types import Network.Xmpp.Concurrent.Types
import Network.Xmpp.Connection import Network.Xmpp.Connection_
import Network.Xmpp.Pickle import Network.Xmpp.Pickle
import Network.Xmpp.Types import Network.Xmpp.Types
import Control.Concurrent.STM.TMVar import Control.Concurrent.STM.TMVar

Loading…
Cancel
Save