summaryrefslogtreecommitdiff
path: root/Presence/XMPP.hs
diff options
context:
space:
mode:
Diffstat (limited to 'Presence/XMPP.hs')
-rw-r--r--Presence/XMPP.hs161
1 files changed, 158 insertions, 3 deletions
diff --git a/Presence/XMPP.hs b/Presence/XMPP.hs
index 6c802fb4..1c6336b9 100644
--- a/Presence/XMPP.hs
+++ b/Presence/XMPP.hs
@@ -24,12 +24,14 @@ import ControlMaybe
24import XMLToByteStrings 24import XMLToByteStrings
25import SendMessage 25import SendMessage
26import Logging 26import Logging
27import Todo
27 28
28import Data.Maybe (catMaybes) 29import Data.Maybe (catMaybes)
29import Data.HList 30import Data.HList
30import Network.Socket ( Family ) 31import Network.Socket ( Family )
31import Control.Concurrent.STM 32import Control.Concurrent.STM
32import Data.Conduit 33import Data.Conduit
34import Data.Maybe
33import Data.ByteString (ByteString) 35import Data.ByteString (ByteString)
34import qualified Data.ByteString.Lazy.Char8 as L 36import qualified Data.ByteString.Lazy.Char8 as L
35 ( fromChunks 37 ( fromChunks
@@ -42,7 +44,7 @@ import Control.Monad.Trans.Class
42import Control.Monad.Trans.Maybe 44import Control.Monad.Trans.Maybe
43import Text.XML.Stream.Parse (def,parseBytes,content) 45import Text.XML.Stream.Parse (def,parseBytes,content)
44import Data.XML.Types as XML 46import Data.XML.Types as XML
45import qualified Data.Text as S (takeWhile) 47import qualified Data.Text as S (Text,takeWhile)
46import Data.Text.Encoding as S (decodeUtf8,encodeUtf8) 48import Data.Text.Encoding as S (decodeUtf8,encodeUtf8)
47import Data.Text.Lazy.Encoding as L (decodeUtf8) 49import Data.Text.Lazy.Encoding as L (decodeUtf8)
48import Data.Text.Lazy (toStrict) 50import Data.Text.Lazy (toStrict)
@@ -383,6 +385,7 @@ handleClientPresence session stanza = do
383 log $ "requesting presence: "<++>bshow stat' 385 log $ "requesting presence: "<++>bshow stat'
384 return () 386 return ()
385 387
388
386fromClient :: (MonadThrow m,MonadIO m, JabberClientSession session) => 389fromClient :: (MonadThrow m,MonadIO m, JabberClientSession session) =>
387 session -> TChan ClientCommands -> Sink XML.Event m () 390 session -> TChan ClientCommands -> Sink XML.Event m ()
388fromClient session cmdChan = doNestingXML $ do 391fromClient session cmdChan = doNestingXML $ do
@@ -421,6 +424,7 @@ fromClient session cmdChan = doNestingXML $ do
421 -> clientRejectsSubscription session stanza 424 -> clientRejectsSubscription session stanza
422 _ | stanza `isClientPresenceOf` presenceTypeOnline 425 _ | stanza `isClientPresenceOf` presenceTypeOnline
423 -> handleClientPresence session stanza 426 -> handleClientPresence session stanza
427 _ | isMessageStanza stanza -> handleClientMessage session stanza
424 _ | otherwise -> unhandledStanza 428 _ | otherwise -> unhandledStanza
425 429
426 awaitCloser stanza_lvl 430 awaitCloser stanza_lvl
@@ -474,6 +478,7 @@ toClient session pchan cmdChan rchan = toClient' False False
474 CmdChan InterestedInRoster -> do 478 CmdChan InterestedInRoster -> do
475 liftIO . debugStr $ "Roster: interested" 479 liftIO . debugStr $ "Roster: interested"
476 toClient' isBound True 480 toClient' isBound True
481 CmdChan (Chat msg) -> return () -- TODO
477 -- CmdChan cmd -> liftIO (debugStr $ "unhandled event: "++show cmd) >> loop 482 -- CmdChan cmd -> liftIO (debugStr $ "unhandled event: "++show cmd) >> loop
478 RChan (RequestedSubscription who contact) -> do 483 RChan (RequestedSubscription who contact) -> do
479 jid <- liftIO $ getJID session 484 jid <- liftIO $ getJID session
@@ -667,6 +672,23 @@ handlePeerPresence session stanza True = do
667 liftIO $ announcePresence session (Presence pjid stat') 672 liftIO $ announcePresence session (Presence pjid stat')
668 log $ bshow (Presence pjid stat') 673 log $ bshow (Presence pjid stat')
669 674
675handlePeerMessage session stanza = do
676 withJust (lookupAttrib "from" (tagAttrs stanza)) $ \fromstr-> do
677 withJust (lookupAttrib "to" (tagAttrs stanza)) $ \tostr -> do
678 fromjid <- liftIO $ parseAddressJID (textToByteString fromstr)
679 tojid <- liftIO $ parseAddressJID (textToByteString tostr)
680 let log = liftIO . debugL . ("(P) " <++>)
681 log $ "handlePeerMessage "<++>bshow stanza
682 msg <- parseMessage ("{jabber:server}body"
683 ,"{jabber:server}subject"
684 ,"{jabber:server}thread"
685 )
686 log
687 fromjid
688 tojid
689 stanza
690 liftIO $ sendChatToClient session msg
691
670matchAttribMaybe name (Just value) attrs = 692matchAttribMaybe name (Just value) attrs =
671 case find ( (==name) . fst) attrs of 693 case find ( (==name) . fst) attrs of
672 Just (_,[ContentText x]) | x==value -> True 694 Just (_,[ContentText x]) | x==value -> True
@@ -692,6 +714,14 @@ isPresenceOf (EventBeginElement name attrs) testType
692 = True 714 = True
693isPresenceOf _ _ = False 715isPresenceOf _ _ = False
694 716
717isMessageStanza (EventBeginElement name attrs)
718 | name=="{jabber:client}message"
719 = True
720isMessageStanza (EventBeginElement name attrs)
721 | name=="{jabber:server}message"
722 = True
723isMessageStanza _ = False
724
695isClientPresenceOf (EventBeginElement name attrs) testType 725isClientPresenceOf (EventBeginElement name attrs) testType
696 | name=="{jabber:client}presence" 726 | name=="{jabber:client}presence"
697 && matchAttribMaybe "type" testType attrs 727 && matchAttribMaybe "type" testType attrs
@@ -878,6 +908,8 @@ fromPeer session = doNestingXML $ do
878 -> peerApprovesSubscription session stanza 908 -> peerApprovesSubscription session stanza
879 _ | stanza `isPresenceOf` presenceTypeUnsubscribed 909 _ | stanza `isPresenceOf` presenceTypeUnsubscribed
880 -> peerRejectsSubscription session stanza 910 -> peerRejectsSubscription session stanza
911 _ | isMessageStanza stanza
912 -> handlePeerMessage session stanza
881 _ -> unhandledStanza 913 _ -> unhandledStanza
882 914
883 awaitCloser stanza_lvl 915 awaitCloser stanza_lvl
@@ -914,6 +946,7 @@ instance CommandCache CachedMessages where
914 cache { approvals= mmInsert (True,from) to $ approvals cache } 946 cache { approvals= mmInsert (True,from) to $ approvals cache }
915 updateCache (Rejection from to) cache = 947 updateCache (Rejection from to) cache =
916 cache { approvals= mmInsert (False,from) to $ approvals cache } 948 cache { approvals= mmInsert (False,from) to $ approvals cache }
949 updateCache (OutBoundMessage msg) cache = cache -- TODO
917 950
918instance ThreadChannelCommand OutBoundMessage where 951instance ThreadChannelCommand OutBoundMessage where
919 isQuitCommand Disconnect = True 952 isQuitCommand Disconnect = True
@@ -991,12 +1024,15 @@ toPeer sock cache chan fail = do
991 (if approve then "subscribed" else "unsubscribed")) 1024 (if approve then "subscribed" else "unsubscribed"))
992 (if approve then Approval from to 1025 (if approve then Approval from to
993 else Rejection from to) 1026 else Rejection from to)
1027 sendMessage msg =
1028 sendOrFail (xmlifyMessageForPeer sock msg)
1029 (OutBoundMessage msg)
994 1030
995 1031
996 send greetPeer 1032 send greetPeer
997 forM_ (Map.assocs . approvals $ cache) $ \(to,froms) -> do 1033 forM_ (Map.assocs . approvals $ cache) $ \(to,froms) -> do
998 forM_ (Set.toList froms) $ \(approve,from) -> do 1034 forM_ (Set.toList froms) $ \(approve,from) -> do
999 liftIO $ debugL "sending cached approval..." 1035 liftIO $ debugL "sending cached approval/rejection..."
1000 sendApproval approve from to 1036 sendApproval approve from to
1001 forM_ (Map.assocs . presences $ cache) $ \(jid,st) -> do 1037 forM_ (Map.assocs . presences $ cache) $ \(jid,st) -> do
1002 sendPresence (Presence jid st) 1038 sendPresence (Presence jid st)
@@ -1027,9 +1063,13 @@ toPeer sock cache chan fail = do
1027 Rejection from to -> do 1063 Rejection from to -> do
1028 liftIO . debugL $ "sending rejection "<++>bshow (from,to) 1064 liftIO . debugL $ "sending rejection "<++>bshow (from,to)
1029 sendApproval False from to 1065 sendApproval False from to
1066 OutBoundMessage msg -> sendMessage msg
1030 Disconnect -> return () 1067 Disconnect -> return ()
1031 when (not . isQuitCommand $ event) loop 1068 when (not . isQuitCommand $ event) loop
1032 send goodbyePeer 1069 return ()
1070 -- send goodbyePeer -- TODO: why does this cause an exception?
1071 -- Text/XML/Stream/Render.hs:169:5-15:
1072 -- Irrefutable pattern failed for pattern (sl : s')
1033 1073
1034 1074
1035 1075
@@ -1079,3 +1119,118 @@ xmlifyPresenceForPeer sock (Presence jid stat) = do
1079 [ EventBeginElement "{jabber:server}show" [] 1119 [ EventBeginElement "{jabber:server}show" []
1080 , EventContent (ContentText stat) 1120 , EventContent (ContentText stat)
1081 , EventEndElement "{jabber:server}show" ] 1121 , EventEndElement "{jabber:server}show" ]
1122
1123xmlifyMessageForPeer sock msg = do
1124 addr <- getSocketName sock
1125 remote <- getPeerName sock
1126 let n = name (msgFrom msg)
1127 rsc = resource (msgFrom msg)
1128 jidstr = toStrict . L.decodeUtf8
1129 $ n <$++> "@" <?++> showPeer (RemotePeer addr) <++?> "/" <++$> rsc
1130 tostr = toStrict . L.decodeUtf8
1131 $ name (msgTo msg) <$++> "@"
1132 <?++> showPeer (RemotePeer remote) <++?> "/"
1133 <++$> resource (msgTo msg)
1134 return $
1135 [ EventBeginElement "{jabber:server}message"
1136 [ attr "from" jidstr
1137 , attr "to" tostr
1138 ]
1139 ]
1140 ++ xmlifyMsgElements (msgLangMap msg) ++
1141 [ EventEndElement "{jabber:server}message" ]
1142
1143xmlifyMsgElements langmap = concatMap (uncurry langElements) . Map.toList $ langmap
1144
1145langElements lang msg =
1146 ( maybeToList (msgSubject msg)
1147 >>= wrap "{jabber:server}subject" )
1148 ++ ( maybeToList (msgBody msg)
1149 >>= wrap "{jabber:server}body" )
1150 ++ ( Set.toList (msgElements msg)
1151 >>= wrapTriple )
1152 where
1153 wrap name content =
1154 [ EventBeginElement name
1155 ( if lang/="" then [attr "xml:lang" lang]
1156 else [] )
1157 , EventContent (ContentText content)
1158 , EventEndElement name
1159 ]
1160 wrapTriple (name,attrs,content) =
1161 [ EventBeginElement name attrs -- Note: we assume lang specified in attrs
1162 , EventContent (ContentText content)
1163 , EventEndElement name
1164 ]
1165
1166
1167handleClientMessage session stanza = do
1168 let log = liftIO . debugL . ("(C) " <++>)
1169 log $ "handleClientMessage "<++>bshow stanza
1170 from <- liftIO $ getJID session
1171 withJust (lookupAttrib "to" (tagAttrs stanza)) $ \to_str -> do
1172 log $ " to = "<++>bshow to_str
1173 tojid <- liftIO $ parseHostNameJID (textToByteString to_str)
1174 msg <- parseMessage ("{jabber:client}body"
1175 ,"{jabber:client}subject"
1176 ,"{jabber:client}thread"
1177 )
1178 log
1179 from
1180 tojid
1181 stanza
1182 liftIO $ sendChat session msg
1183
1184{-
1185unhandled-C: <message
1186unhandled-C: type="chat"
1187unhandled-C: id="purplea0a7fd24"
1188unhandled-C: to="user@vm2"
1189unhandled-C: xmlns="jabber:client">
1190unhandled-C: <active xmlns="http://jabber.org/protocol/chatstates"/>
1191unhandled-C: <body>
1192unhandled-C: hello dude
1193unhandled-C: </body>
1194unhandled-C: </message>
1195-}
1196parseMessage (bodytag,subjecttag,threadtag) log from tojid stanza = do
1197 let emptyMsg = LangSpecificMessage { msgBody=Nothing, msgSubject=Nothing, msgElements=Set.empty }
1198 parseChildren (th,cmap) = do
1199 child <- nextElement
1200 lvl <- nesting
1201 xmllang <- xmlLang
1202 let lang = maybe "" id xmllang
1203 let c = maybe emptyMsg id (Map.lookup lang cmap)
1204 log $ " child: "<++> bshow child
1205 case child of
1206 Just tag | tagName tag==bodytag
1207 -> do
1208 txt <- lift content
1209 awaitCloser lvl
1210 parseChildren (th,Map.insert lang (c { msgBody=Just txt }) cmap)
1211 Just tag | tagName tag==subjecttag
1212 -> do
1213 txt <- lift content
1214 awaitCloser lvl
1215 parseChildren (th,Map.insert lang (c { msgSubject=Just txt }) cmap)
1216 Just tag | tagName tag==threadtag
1217 -> do
1218 txt <- lift content
1219 awaitCloser lvl
1220 parseChildren (th {msgThreadContent=txt},cmap)
1221 Just tag -> do
1222 let nm = tagName tag
1223 attrs = tagAttrs tag
1224 elems = msgElements c
1225 txt <- lift content
1226 awaitCloser lvl
1227 parseChildren (th,Map.insert lang (c {msgElements=Set.insert (nm,attrs,txt) elems}) cmap)
1228 Nothing -> return (th,cmap)
1229 (th,langmap) <- parseChildren ( MessageThread {msgThreadParent=Nothing, msgThreadContent=""}
1230 , Map.empty )
1231 return Message {
1232 msgTo = tojid,
1233 msgFrom = from,
1234 msgLangMap = langmap,
1235 msgThread = if msgThreadContent th/="" then Just th else Nothing
1236 }