|
|
|
@ -17,7 +17,7 @@ import Control.Monad.State.Strict |
|
|
|
import Data.ByteString as BS |
|
|
|
import Data.ByteString as BS |
|
|
|
import Data.Conduit |
|
|
|
import Data.Conduit |
|
|
|
import Data.Conduit.Binary as CB |
|
|
|
import Data.Conduit.Binary as CB |
|
|
|
import Data.Conduit.BufferedSource |
|
|
|
import Data.Conduit.Internal as DCI |
|
|
|
import qualified Data.Conduit.List as CL |
|
|
|
import qualified Data.Conduit.List as CL |
|
|
|
import Data.IORef |
|
|
|
import Data.IORef |
|
|
|
import Data.Text(Text) |
|
|
|
import Data.Text(Text) |
|
|
|
@ -41,41 +41,42 @@ import Text.XML.Unresolved(InvalidEventStream(..)) |
|
|
|
debug :: Bool |
|
|
|
debug :: Bool |
|
|
|
debug = False |
|
|
|
debug = False |
|
|
|
|
|
|
|
|
|
|
|
pushElement :: Element -> XmppConMonad Bool |
|
|
|
pushElement :: Element -> StateT Connection_ IO Bool |
|
|
|
pushElement x = do |
|
|
|
pushElement x = do |
|
|
|
send <- gets (cSend . sCon) |
|
|
|
send <- gets (cSend . cHand) |
|
|
|
liftIO . send $ renderElement x |
|
|
|
liftIO . send $ renderElement x |
|
|
|
|
|
|
|
|
|
|
|
-- | Encode and send stanza |
|
|
|
-- | Encode and send stanza |
|
|
|
pushStanza :: Stanza -> XmppConMonad Bool |
|
|
|
pushStanza :: Stanza -> Connection -> IO Bool |
|
|
|
pushStanza = pushElement . pickleElem xpStanza |
|
|
|
pushStanza s = withConnection' . pushElement $ pickleElem xpStanza s |
|
|
|
|
|
|
|
|
|
|
|
-- XML documents and XMPP streams SHOULD be preceeded by an XML declaration. |
|
|
|
-- XML documents and XMPP streams SHOULD be preceeded by an XML declaration. |
|
|
|
-- UTF-8 is the only supported XMPP encoding. The standalone document |
|
|
|
-- UTF-8 is the only supported XMPP encoding. The standalone document |
|
|
|
-- declaration (matching "SDDecl" in the XML standard) MUST NOT be included in |
|
|
|
-- 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. |
|
|
|
-- XMPP streams. RFC 6120 defines XMPP only in terms of XML 1.0. |
|
|
|
pushXmlDecl :: XmppConMonad Bool |
|
|
|
pushXmlDecl :: StateT Connection_ IO Bool |
|
|
|
pushXmlDecl = do |
|
|
|
pushXmlDecl = do |
|
|
|
con <- gets sCon |
|
|
|
con <- gets cHand |
|
|
|
liftIO $ (cSend con) "<?xml version='1.0' encoding='UTF-8' ?>" |
|
|
|
liftIO $ (cSend con) "<?xml version='1.0' encoding='UTF-8' ?>" |
|
|
|
|
|
|
|
|
|
|
|
pushOpenElement :: Element -> XmppConMonad Bool |
|
|
|
pushOpenElement :: Element -> StateT Connection_ IO Bool |
|
|
|
pushOpenElement e = do |
|
|
|
pushOpenElement e = do |
|
|
|
sink <- gets (cSend . sCon ) |
|
|
|
sink <- gets (cSend . cHand ) |
|
|
|
liftIO . sink $ renderOpenElement e |
|
|
|
liftIO . sink $ renderOpenElement e |
|
|
|
|
|
|
|
|
|
|
|
-- `Connect-and-resumes' the given sink to the connection source, and pulls a |
|
|
|
-- `Connect-and-resumes' the given sink to the connection source, and pulls a |
|
|
|
-- `b' value. |
|
|
|
-- `b' value. |
|
|
|
pullToSinkEvents :: Sink Event IO b -> XmppConMonad b |
|
|
|
runEventsSink :: Sink Event IO b -> StateT Connection_ IO b |
|
|
|
pullToSinkEvents snk = do |
|
|
|
runEventsSink snk = do |
|
|
|
source <- gets (cEventSource . sCon) |
|
|
|
source <- gets cEventSource |
|
|
|
r <- lift $ source .$$+ snk |
|
|
|
(src', r) <- lift $ source $$++ snk |
|
|
|
|
|
|
|
modify (\s -> s{cEventSource = src'}) |
|
|
|
return r |
|
|
|
return r |
|
|
|
|
|
|
|
|
|
|
|
pullElement :: XmppConMonad Element |
|
|
|
pullElement :: StateT Connection_ IO Element |
|
|
|
pullElement = do |
|
|
|
pullElement = do |
|
|
|
Ex.catches (do |
|
|
|
Ex.catches (do |
|
|
|
e <- pullToSinkEvents (elements =$ await) |
|
|
|
e <- runEventsSink (elements =$ await) |
|
|
|
case e of |
|
|
|
case e of |
|
|
|
Nothing -> liftIO $ Ex.throwIO StreamConnectionError |
|
|
|
Nothing -> liftIO $ Ex.throwIO StreamConnectionError |
|
|
|
Just r -> return r |
|
|
|
Just r -> return r |
|
|
|
@ -85,12 +86,11 @@ pullElement = do |
|
|
|
-> liftIO . Ex.throwIO $ StreamXMLError s) |
|
|
|
-> liftIO . Ex.throwIO $ StreamXMLError s) |
|
|
|
, Ex.Handler $ \(e :: InvalidEventStream) |
|
|
|
, Ex.Handler $ \(e :: InvalidEventStream) |
|
|
|
-> liftIO . Ex.throwIO $ StreamXMLError (show e) |
|
|
|
-> liftIO . Ex.throwIO $ StreamXMLError (show e) |
|
|
|
|
|
|
|
|
|
|
|
] |
|
|
|
] |
|
|
|
|
|
|
|
|
|
|
|
-- Pulls an element and unpickles it. |
|
|
|
-- Pulls an element and unpickles it. |
|
|
|
pullPickle :: PU [Node] a -> XmppConMonad a |
|
|
|
pullUnpickle :: PU [Node] a -> StateT Connection_ IO a |
|
|
|
pullPickle p = do |
|
|
|
pullUnpickle p = do |
|
|
|
res <- unpickleElem p <$> pullElement |
|
|
|
res <- unpickleElem p <$> pullElement |
|
|
|
case res of |
|
|
|
case res of |
|
|
|
Left e -> liftIO . Ex.throwIO $ StreamXMLError (show e) |
|
|
|
Left e -> liftIO . Ex.throwIO $ StreamXMLError (show e) |
|
|
|
@ -98,17 +98,17 @@ pullPickle p = do |
|
|
|
|
|
|
|
|
|
|
|
-- | Pulls a stanza (or stream error) from the stream. Throws an error on a stream |
|
|
|
-- | Pulls a stanza (or stream error) from the stream. Throws an error on a stream |
|
|
|
-- error. |
|
|
|
-- error. |
|
|
|
pullStanza :: XmppConMonad Stanza |
|
|
|
pullStanza :: Connection -> IO Stanza |
|
|
|
pullStanza = do |
|
|
|
pullStanza = withConnection' $ do |
|
|
|
res <- pullPickle xpStreamStanza |
|
|
|
res <- pullUnpickle xpStreamStanza |
|
|
|
case res of |
|
|
|
case res of |
|
|
|
Left e -> liftIO . Ex.throwIO $ StreamError e |
|
|
|
Left e -> liftIO . Ex.throwIO $ StreamError e |
|
|
|
Right r -> return r |
|
|
|
Right r -> return r |
|
|
|
|
|
|
|
|
|
|
|
-- Performs the given IO operation, catches any errors and re-throws everything |
|
|
|
-- Performs the given IO operation, catches any errors and re-throws everything |
|
|
|
-- except 'ResourceVanished' and IllegalOperation, in which case it will return False instead |
|
|
|
-- except 'ResourceVanished' and IllegalOperation, in which case it will return False instead |
|
|
|
catchSend :: IO () -> IO Bool |
|
|
|
catchPush :: IO () -> IO Bool |
|
|
|
catchSend p = Ex.catch |
|
|
|
catchPush p = Ex.catch |
|
|
|
(p >> return True) |
|
|
|
(p >> return True) |
|
|
|
(\e -> case GIE.ioe_type e of |
|
|
|
(\e -> case GIE.ioe_type e of |
|
|
|
GIE.ResourceVanished -> return False |
|
|
|
GIE.ResourceVanished -> return False |
|
|
|
@ -116,16 +116,16 @@ catchSend p = Ex.catch |
|
|
|
_ -> Ex.throwIO e |
|
|
|
_ -> Ex.throwIO e |
|
|
|
) |
|
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
-- -- XmppConnection state used when there is no connection. |
|
|
|
-- -- Connection_ state used when there is no connection. |
|
|
|
xmppNoConnection :: XmppConnection |
|
|
|
xmppNoConnection :: Connection_ |
|
|
|
xmppNoConnection = XmppConnection |
|
|
|
xmppNoConnection = Connection_ |
|
|
|
{ sCon = Connection { cSend = \_ -> return False |
|
|
|
{ cHand = Hand { cSend = \_ -> return False |
|
|
|
, cRecv = \_ -> Ex.throwIO |
|
|
|
, cRecv = \_ -> Ex.throwIO |
|
|
|
$ StreamConnectionError |
|
|
|
$ StreamConnectionError |
|
|
|
, cEventSource = undefined |
|
|
|
|
|
|
|
, cFlush = return () |
|
|
|
, cFlush = return () |
|
|
|
, cClose = return () |
|
|
|
, cClose = return () |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
, cEventSource = DCI.ResumableSource zeroSource (return ()) |
|
|
|
, sFeatures = SF Nothing [] [] |
|
|
|
, sFeatures = SF Nothing [] [] |
|
|
|
, sConnectionState = XmppConnectionClosed |
|
|
|
, sConnectionState = XmppConnectionClosed |
|
|
|
, sHostname = Nothing |
|
|
|
, sHostname = Nothing |
|
|
|
@ -142,31 +142,30 @@ xmppNoConnection = XmppConnection |
|
|
|
zeroSource = liftIO . Ex.throwIO $ StreamConnectionError |
|
|
|
zeroSource = liftIO . Ex.throwIO $ StreamConnectionError |
|
|
|
|
|
|
|
|
|
|
|
-- Connects to the given hostname on port 5222 (TODO: Make this dynamic) and |
|
|
|
-- Connects to the given hostname on port 5222 (TODO: Make this dynamic) and |
|
|
|
-- updates the XmppConMonad XmppConnection state. |
|
|
|
-- updates the XmppConMonad Connection_ state. |
|
|
|
xmppConnectTCP :: HostName -> PortID -> Text -> XmppConMonad () |
|
|
|
connectTcpRaw :: HostName -> PortID -> Text -> IO Connection |
|
|
|
xmppConnectTCP host port hostname = do |
|
|
|
connectTcpRaw host port hostname = do |
|
|
|
hand <- liftIO $ do |
|
|
|
|
|
|
|
h <- connectTo host port |
|
|
|
h <- connectTo host port |
|
|
|
hSetBuffering h NoBuffering |
|
|
|
hSetBuffering h NoBuffering |
|
|
|
return h |
|
|
|
let eSource = DCI.ResumableSource (sourceHandle h $= XP.parseBytes def) |
|
|
|
eSource <- liftIO . bufferSource $ (sourceHandle hand) $= XP.parseBytes def |
|
|
|
(return ()) |
|
|
|
let con = Connection { cSend = if debug |
|
|
|
let hand = Hand { cSend = if debug |
|
|
|
then \d -> do |
|
|
|
then \d -> do |
|
|
|
BS.putStrLn (BS.append "out: " d) |
|
|
|
BS.putStrLn (BS.append "out: " d) |
|
|
|
catchSend $ BS.hPut hand d |
|
|
|
catchPush $ BS.hPut h d |
|
|
|
else catchSend . BS.hPut hand |
|
|
|
else catchPush . BS.hPut h |
|
|
|
, cRecv = if debug then |
|
|
|
, cRecv = if debug then |
|
|
|
\n -> do |
|
|
|
\n -> do |
|
|
|
bs <- BS.hGetSome hand n |
|
|
|
bs <- BS.hGetSome h n |
|
|
|
BS.putStrLn bs |
|
|
|
BS.putStrLn bs |
|
|
|
return bs |
|
|
|
return bs |
|
|
|
else BS.hGetSome hand |
|
|
|
else BS.hGetSome h |
|
|
|
, cEventSource = eSource |
|
|
|
, cFlush = hFlush h |
|
|
|
, cFlush = hFlush hand |
|
|
|
, cClose = hClose h |
|
|
|
, cClose = hClose hand |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
let st = XmppConnection |
|
|
|
let con = Connection_ |
|
|
|
{ sCon = con |
|
|
|
{ cHand = hand |
|
|
|
|
|
|
|
, cEventSource = eSource |
|
|
|
, sFeatures = (SF Nothing [] []) |
|
|
|
, sFeatures = (SF Nothing [] []) |
|
|
|
, sConnectionState = XmppConnectionPlain |
|
|
|
, sConnectionState = XmppConnectionPlain |
|
|
|
, sHostname = (Just hostname) |
|
|
|
, sHostname = (Just hostname) |
|
|
|
@ -178,55 +177,48 @@ xmppConnectTCP host port hostname = do |
|
|
|
, sJidWhenPlain = False -- TODO: Allow user to set |
|
|
|
, sJidWhenPlain = False -- TODO: Allow user to set |
|
|
|
, sFrom = Nothing |
|
|
|
, sFrom = Nothing |
|
|
|
} |
|
|
|
} |
|
|
|
put st |
|
|
|
mkConnection con |
|
|
|
|
|
|
|
|
|
|
|
-- Execute a XmppConMonad computation. |
|
|
|
|
|
|
|
xmppNewSession :: XmppConMonad a -> IO (a, XmppConnection) |
|
|
|
|
|
|
|
xmppNewSession action = runStateT action xmppNoConnection |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
-- Closes the connection and updates the XmppConMonad XmppConnection state. |
|
|
|
-- Closes the connection and updates the XmppConMonad Connection_ state. |
|
|
|
xmppKillConnection :: XmppConMonad (Either Ex.SomeException ()) |
|
|
|
killConnection :: Connection -> IO (Either Ex.SomeException ()) |
|
|
|
xmppKillConnection = do |
|
|
|
killConnection = withConnection $ do |
|
|
|
cc <- gets (cClose . sCon) |
|
|
|
cc <- gets (cClose . cHand) |
|
|
|
err <- liftIO $ (Ex.try cc :: IO (Either Ex.SomeException ())) |
|
|
|
err <- liftIO $ (Ex.try cc :: IO (Either Ex.SomeException ())) |
|
|
|
put xmppNoConnection |
|
|
|
put xmppNoConnection |
|
|
|
return err |
|
|
|
return err |
|
|
|
|
|
|
|
|
|
|
|
xmppReplaceConnection :: XmppConnection -> XmppConMonad (Either Ex.SomeException ()) |
|
|
|
|
|
|
|
xmppReplaceConnection newCon = do |
|
|
|
|
|
|
|
cc <- gets (cClose . sCon) |
|
|
|
|
|
|
|
err <- liftIO $ (Ex.try cc :: IO (Either Ex.SomeException ())) |
|
|
|
|
|
|
|
put newCon |
|
|
|
|
|
|
|
return err |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
-- Sends an IQ request and waits for the response. If the response ID does not |
|
|
|
-- Sends an IQ request and waits for the response. If the response ID does not |
|
|
|
-- match the outgoing ID, an error is thrown. |
|
|
|
-- match the outgoing ID, an error is thrown. |
|
|
|
xmppSendIQ' :: StanzaId |
|
|
|
pushIQ' :: StanzaId |
|
|
|
-> Maybe Jid |
|
|
|
-> Maybe Jid |
|
|
|
-> IQRequestType |
|
|
|
-> IQRequestType |
|
|
|
-> Maybe LangTag |
|
|
|
-> Maybe LangTag |
|
|
|
-> Element |
|
|
|
-> Element |
|
|
|
-> XmppConMonad (Either IQError IQResult) |
|
|
|
-> Connection |
|
|
|
xmppSendIQ' iqID to tp lang body = do |
|
|
|
-> IO (Either IQError IQResult) |
|
|
|
pushStanza . IQRequestS $ IQRequest iqID Nothing to lang tp body |
|
|
|
pushIQ' iqID to tp lang body con = do |
|
|
|
res <- pullPickle $ xpEither xpIQError xpIQResult |
|
|
|
pushStanza (IQRequestS $ IQRequest iqID Nothing to lang tp body) con |
|
|
|
|
|
|
|
res <- pullStanza con |
|
|
|
case res of |
|
|
|
case res of |
|
|
|
Left e -> return $ Left e |
|
|
|
IQErrorS e -> return $ Left e |
|
|
|
Right iq' -> do |
|
|
|
IQResultS r -> do |
|
|
|
unless |
|
|
|
unless |
|
|
|
(iqID == iqResultID iq') . liftIO . Ex.throwIO $ |
|
|
|
(iqID == iqResultID r) . liftIO . Ex.throwIO $ |
|
|
|
StreamXMLError |
|
|
|
StreamXMLError |
|
|
|
("In xmppSendIQ' IDs don't match: " ++ show iqID ++ " /= " ++ |
|
|
|
("In sendIQ' IDs don't match: " ++ show iqID ++ " /= " ++ |
|
|
|
show (iqResultID iq') ++ " .") |
|
|
|
show (iqResultID r) ++ " .") |
|
|
|
return $ Right iq' |
|
|
|
return $ Right r |
|
|
|
|
|
|
|
_ -> liftIO . Ex.throwIO . StreamXMLError $ |
|
|
|
|
|
|
|
"sendIQ': unexpected stanza type " |
|
|
|
|
|
|
|
|
|
|
|
-- | Send "</stream:stream>" and wait for the server to finish processing and to |
|
|
|
-- | 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 |
|
|
|
-- close the connection. Any remaining elements from the server and whether or |
|
|
|
-- not we received a </stream:stream> element from the server is returned. |
|
|
|
-- not we received a </stream:stream> element from the server is returned. |
|
|
|
xmppCloseStreams :: XmppConMonad ([Element], Bool) |
|
|
|
closeStreams :: Connection -> IO ([Element], Bool) |
|
|
|
xmppCloseStreams = do |
|
|
|
closeStreams = withConnection $ do |
|
|
|
send <- gets (cSend . sCon) |
|
|
|
send <- gets (cSend . cHand) |
|
|
|
cc <- gets (cClose . sCon) |
|
|
|
cc <- gets (cClose . cHand) |
|
|
|
liftIO $ send "</stream:stream>" |
|
|
|
liftIO $ send "</stream:stream>" |
|
|
|
void $ liftIO $ forkIO $ do |
|
|
|
void $ liftIO $ forkIO $ do |
|
|
|
threadDelay 3000000 |
|
|
|
threadDelay 3000000 |
|
|
|
@ -236,18 +228,18 @@ xmppCloseStreams = do |
|
|
|
where |
|
|
|
where |
|
|
|
-- Pulls elements from the stream until the stream ends, or an error is |
|
|
|
-- Pulls elements from the stream until the stream ends, or an error is |
|
|
|
-- raised. |
|
|
|
-- raised. |
|
|
|
collectElems :: [Element] -> XmppConMonad ([Element], Bool) |
|
|
|
collectElems :: [Element] -> StateT Connection_ IO ([Element], Bool) |
|
|
|
collectElems elems = do |
|
|
|
collectElems es = do |
|
|
|
result <- Ex.try pullElement |
|
|
|
result <- Ex.try pullElement |
|
|
|
case result of |
|
|
|
case result of |
|
|
|
Left StreamStreamEnd -> return (elems, True) |
|
|
|
Left StreamStreamEnd -> return (es, True) |
|
|
|
Left _ -> return (elems, False) |
|
|
|
Left _ -> return (es, False) |
|
|
|
Right elem -> collectElems (elem:elems) |
|
|
|
Right e -> collectElems (e:es) |
|
|
|
|
|
|
|
|
|
|
|
debugConduit :: Pipe l ByteString ByteString u IO b |
|
|
|
debugConduit :: Pipe l ByteString ByteString u IO b |
|
|
|
debugConduit = forever $ do |
|
|
|
debugConduit = forever $ do |
|
|
|
s <- await |
|
|
|
s' <- await |
|
|
|
case s of |
|
|
|
case s' of |
|
|
|
Just s -> do |
|
|
|
Just s -> do |
|
|
|
liftIO $ BS.putStrLn (BS.append "in: " s) |
|
|
|
liftIO $ BS.putStrLn (BS.append "in: " s) |
|
|
|
yield s |
|
|
|
yield s |
|
|
|
|