You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
171 lines
6.6 KiB
171 lines
6.6 KiB
|
13 years ago
|
{-# LANGUAGE PatternGuards #-}
|
||
|
|
{-# LANGUAGE NoMonomorphismRestriction #-}
|
||
|
|
{-# LANGUAGE OverloadedStrings #-}
|
||
|
|
|
||
|
|
module Network.Xmpp.IM.Roster
|
||
|
|
where
|
||
|
|
|
||
|
|
import Control.Concurrent.STM
|
||
|
|
import Control.Monad
|
||
|
|
import Data.Text (Text)
|
||
|
|
import Data.XML.Pickle
|
||
|
|
import Data.XML.Types
|
||
|
|
import Network.Xmpp
|
||
|
|
import Network.Xmpp.Marshal
|
||
|
|
import System.Log.Logger
|
||
|
|
import qualified Data.Map.Strict as Map
|
||
|
|
|
||
|
|
import Network.Xmpp.Types
|
||
|
|
|
||
|
|
data Subscription = None | To | From | Both | Remove deriving Eq
|
||
|
|
|
||
|
|
instance Show Subscription where
|
||
|
|
show None = "none"
|
||
|
|
show To = "to"
|
||
|
|
show From = "from"
|
||
|
|
show Both = "both"
|
||
|
|
show Remove = "remove"
|
||
|
|
|
||
|
|
instance Read Subscription where
|
||
|
|
readsPrec _ "none" = [(None ,"")]
|
||
|
|
readsPrec _ "to" = [(To ,"")]
|
||
|
|
readsPrec _ "from" = [(From ,"")]
|
||
|
|
readsPrec _ "both" = [(Both ,"")]
|
||
|
|
readsPrec _ "remove" = [(Remove ,"")]
|
||
|
|
readsPrec _ _ = []
|
||
|
|
|
||
|
|
data Roster = Roster { ver :: Maybe Text
|
||
|
|
, items :: Map.Map Jid Item }
|
||
|
|
|
||
|
|
|
||
|
|
data Item = Item { approved :: Bool
|
||
|
|
, ask :: Bool
|
||
|
|
, jid :: Jid
|
||
|
|
, name :: Maybe Text
|
||
|
|
, subscription :: Subscription
|
||
|
|
, groups :: [Text]
|
||
|
|
} deriving Show
|
||
|
|
|
||
|
|
data QueryItem = QueryItem { qiApproved :: Maybe Bool
|
||
|
|
, qiAsk :: Bool
|
||
|
|
, qiJid :: Jid
|
||
|
|
, qiName :: Maybe Text
|
||
|
|
, qiSubscription :: Maybe Subscription
|
||
|
|
, qiGroups :: [Text]
|
||
|
|
} deriving Show
|
||
|
|
|
||
|
|
data Query = Query { queryVer :: Maybe Text
|
||
|
|
, queryItems :: [QueryItem]
|
||
|
|
} deriving Show
|
||
|
|
|
||
|
|
|
||
|
|
withRoster :: Maybe Roster
|
||
|
|
-> SessionConfiguration
|
||
|
|
-> (SessionConfiguration -> IO (Either XmppFailure Session))
|
||
|
|
-> IO (Either XmppFailure (TVar Roster, Session))
|
||
|
|
withRoster oldRoster conf startSession = do
|
||
|
|
rosterRef <- newTVarIO $ Roster Nothing Map.empty
|
||
|
|
mbSess <- startSession conf{extraStanzaHandlers = handleRoster rosterRef :
|
||
|
|
extraStanzaHandlers conf}
|
||
|
|
case mbSess of
|
||
|
|
Left e -> return $ Left e
|
||
|
|
Right sess -> do
|
||
|
|
mbRoster <- getRoster oldRoster sess
|
||
|
|
case mbRoster of
|
||
|
|
Nothing -> errorM "Pontarius.Xmpp" "Server did not return a roster"
|
||
|
|
Just roster -> atomically $ writeTVar rosterRef roster
|
||
|
|
return $ Right (rosterRef, sess)
|
||
|
|
|
||
|
|
handleRoster :: TVar Roster -> TChan Stanza -> Stanza -> IO Bool
|
||
|
|
handleRoster rosterRef outC sta = do
|
||
|
|
case sta of
|
||
|
|
IQRequestS (iqr@IQRequest{iqRequestPayload =
|
||
|
|
iqb@Element{elementName = en}})
|
||
|
|
| nameNamespace en == Just "jabber:iq:roster" -> do
|
||
|
|
case iqRequestFrom iqr of
|
||
|
|
Just _from -> return True -- Don't handle roster pushes from
|
||
|
|
-- unauthorized sources
|
||
|
|
Nothing -> case unpickleElem xpQuery iqb of
|
||
|
|
Right Query{ queryVer = v
|
||
|
|
, queryItems = [update]
|
||
|
|
} -> do
|
||
|
|
handleUpdate v update
|
||
|
|
atomically . writeTChan outC $ result iqr
|
||
|
|
return False
|
||
|
|
_ -> do
|
||
|
|
errorM "Pontarius.Xmpp" "Invalid roster query"
|
||
|
|
atomically . writeTChan outC $ badRequest iqr
|
||
|
|
return False
|
||
|
|
_ -> return True
|
||
|
|
where
|
||
|
|
handleUpdate v' update = atomically $ modifyTVar rosterRef $ \(Roster v is) ->
|
||
|
|
Roster (v' `mplus` v) $ case qiSubscription update of
|
||
|
|
Just Remove -> Map.delete (qiJid update) is
|
||
|
|
_ -> Map.insert (qiJid update) (toItem update) is
|
||
|
|
|
||
|
|
badRequest (IQRequest iqid from _to lang _tp bd) =
|
||
|
|
IQErrorS $ IQError iqid Nothing from lang errBR (Just bd)
|
||
|
|
errBR = StanzaError Cancel BadRequest Nothing Nothing
|
||
|
|
result (IQRequest iqid from _to lang _tp _bd) =
|
||
|
|
IQResultS $ IQResult iqid Nothing from lang Nothing
|
||
|
|
|
||
|
|
getRoster :: Maybe Roster -> Session -> IO (Maybe Roster)
|
||
|
|
getRoster oldRoster sess = do
|
||
|
|
res <- sendIQ' Nothing Get Nothing
|
||
|
|
(pickleElem xpQuery (Query (ver =<< oldRoster) []))
|
||
|
|
sess
|
||
|
|
case res of
|
||
|
|
IQResponseResult (IQResult{iqResultPayload = Just ros})
|
||
|
|
-> case unpickleElem xpQuery ros of
|
||
|
|
Left _e -> do
|
||
|
|
errorM "Pontarius.Xmpp.Roster" "getRoster: invalid query element"
|
||
|
|
return Nothing
|
||
|
|
Right roster -> return . Just $ toRoster roster
|
||
|
|
IQResponseResult (IQResult{iqResultPayload = Nothing}) -> do
|
||
|
|
return $ oldRoster
|
||
|
|
-- sever indicated that no roster updates are necessary
|
||
|
|
IQResponseTimeout -> do
|
||
|
|
errorM "Pontarius.Xmpp.Roster" "getRoster: request timed out"
|
||
|
|
return Nothing
|
||
|
|
IQResponseError e -> do
|
||
|
|
errorM "Pontarius.Xmpp.Roster" $ "getRoster: server returned error"
|
||
|
|
++ show e
|
||
|
|
return Nothing
|
||
|
|
where
|
||
|
|
toRoster (Query v is) = Roster v (Map.fromList
|
||
|
|
$ map (\i -> (qiJid i, toItem i))
|
||
|
|
is)
|
||
|
|
|
||
|
|
toItem :: QueryItem -> Item
|
||
|
|
toItem qi = Item { approved = maybe False id (qiApproved qi)
|
||
|
|
, ask = qiAsk qi
|
||
|
|
, jid = qiJid qi
|
||
|
|
, name = qiName qi
|
||
|
|
, subscription = maybe None id (qiSubscription qi)
|
||
|
|
, groups = qiGroups qi
|
||
|
|
}
|
||
|
|
|
||
|
|
xpItems :: PU [Node] [QueryItem]
|
||
|
|
xpItems = xpWrap (map (\((app_, ask_, jid_, name_, sub_), groups_) ->
|
||
|
|
QueryItem app_ ask_ jid_ name_ sub_ groups_))
|
||
|
|
(map (\(QueryItem app_ ask_ jid_ name_ sub_ groups_) ->
|
||
|
|
((app_, ask_, jid_, name_, sub_), groups_))) $
|
||
|
|
xpElems "{jabber:iq:roster}item"
|
||
|
|
(xp5Tuple
|
||
|
|
(xpAttribute' "approved" xpBool)
|
||
|
|
(xpWrap (maybe False (const True))
|
||
|
|
(\p -> if p then Just () else Nothing) $
|
||
|
|
xpOption $ xpAttribute_ "ask" "subscribe")
|
||
|
|
(xpAttribute "jid" xpPrim)
|
||
|
|
(xpAttribute' "name" xpText)
|
||
|
|
(xpAttribute' "subscription" xpPrim)
|
||
|
|
)
|
||
|
|
(xpFindMatches $ xpElemText "{jabber:iq:roster}group")
|
||
|
|
|
||
|
|
xpQuery :: PU [Node] Query
|
||
|
|
xpQuery = xpWrap (\(ver_, items_) -> Query ver_ items_ )
|
||
|
|
(\(Query ver_ items_) -> (ver_, items_)) $
|
||
|
|
xpElem "{jabber:iq:roster}query"
|
||
|
|
(xpAttribute' "ver" xpText)
|
||
|
|
xpItems
|