{- Copyright (C) 2009 John Millikin This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . -} module Network.Protocol.XMPP.Stream ( Stream ( streamHostName ,streamLanguage ,streamVersion ,streamFeatures ) ,StreamFeature ( FeatureStartTLS ,FeatureSASL ,FeatureRegister ) ,beginStream ,getTree ,putTree ) where import qualified System.IO as IO import Network (HostName, PortID, connectTo) import qualified Network.Protocol.XMPP.IncrementalXML as XML import Data.AssocList (lookupDef) import qualified Text.XML.HXT.DOM.QualifiedName as QN import qualified Text.XML.HXT.DOM.XmlNode as XN import Text.XML.HXT.DOM.TypeDefs (XmlTree) import Text.XML.HXT.DOM.FormatXmlTree (formatXmlTree) import Text.XML.HXT.DOM.Util (attrEscapeXml) import Text.XML.HXT.Arrow ((>>>), (>>.)) import Data.Tree.NTree.TypeDefs (NTree(NTree)) import qualified Text.XML.HXT.Arrow as A import Network.Protocol.XMPP.JID (JID) import Network.Protocol.XMPP.SASL (Mechanism, findMechanism) import Network.Protocol.XMPP.Stanzas (Stanza) import Network.Protocol.XMPP.XMLBuilder (eventsToTree) maxXMPPVersion = XMPPVersion 1 0 data Stream = Stream { streamHandle :: IO.Handle ,streamParser :: XML.Parser ,streamHostName :: HostName ,streamLanguage :: XMLLanguage ,streamVersion :: XMPPVersion ,streamFeatures :: [StreamFeature] } data StreamFeature = FeatureStartTLS Bool | FeatureSASL [Mechanism] | FeatureRegister | FeatureUnknown XmlTree | FeatureDebug String deriving (Show, Eq) newtype XMLLanguage = XMLLanguage String deriving (Show, Eq) data XMPPVersion = XMPPVersion Int Int deriving (Show, Eq) ------------------------------------------------------------------------------- beginStream :: JID -> HostName -> IO.Handle -> IO Stream beginStream jid host handle = do parser <- XML.newParser IO.hSetBuffering handle IO.NoBuffering -- Since only the opening tag should be written, normal XML -- serialization cannot be used. Be careful to escape any embedded -- attributes. IO.hPutStr handle $ "\n" ++ "" IO.hFlush handle events <- readEventsUntil endOfFeatures handle parser 1000 return $ beginStream' handle parser events where featuresName = QN.mkNsName "features" "http://etherx.jabber.org/streams" endOfFeatures depth event = case (depth, event) of (1, (XML.EndElement featuresName)) -> True otherwise -> False beginStream' handle parser (streamStart:events) = let -- TODO: parse from streamStart host = "localhost" language = XMLLanguage "en" version = XMPPVersion 1 0 featuresName = QN.mkNsName "features" "http://etherx.jabber.org/streams" eventTree = eventsToTree events featureRoots = A.runLA ( A.getChildren >>> A.hasQName featuresName) eventTree features = case featureRoots of [] -> [] (t:_) -> map parseFeature (A.runLA A.getChildren t) in Stream handle parser host language version features parseFeature :: XmlTree -> StreamFeature parseFeature t = lookupDef FeatureUnknown qname [ (("urn:ietf:params:xml:ns:xmpp-tls", "starttls"), parseFeatureTLS) ,(("urn:ietf:params:xml:ns:xmpp-sasl", "mechanisms"), parseFeatureSASL) ,(("http://jabber.org/features/iq-register", "register"), (\_ -> FeatureRegister)) ] t where qname = maybe ("", "") (\n -> (QN.namespaceUri n, QN.localPart n)) (XN.getName t) parseFeatureTLS :: XmlTree -> StreamFeature parseFeatureTLS t = FeatureStartTLS True -- TODO: detect whether or not required parseFeatureSASL :: XmlTree -> StreamFeature parseFeatureSASL t = let mechName = QN.mkNsName "mechanism" "urn:ietf:params:xml:ns:xmpp-sasl" rawMechanisms = A.runLA ( A.getChildren >>> A.hasQName mechName >>> A.getChildren >>> A.getText) t -- TODO: validate mechanism names according to SASL rules -- <20 chars, uppercase, alphanum, etc in FeatureSASL (map findMechanism rawMechanisms) ------------------------------------------------------------------------------- getTree :: Stream -> IO XmlTree getTree s = do events <- readEventsUntil finished (streamHandle s) (streamParser s) 1000 return $ eventsToTree events where finished 0 (XML.EndElement _) = True finished _ _ = False putTree :: Stream -> XmlTree -> IO () putTree s t = do let root = XN.mkRoot [] [t] let h = streamHandle s [text] <- A.runX (A.constA root >>> A.writeDocumentToString [ (A.a_no_xml_pi, "1") ]) IO.hPutStr h text IO.hFlush h ------------------------------------------------------------------------------- readEventsUntil :: (Int -> XML.Event -> Bool) -> IO.Handle -> XML.Parser -> Int -> IO [XML.Event] readEventsUntil done h parser timeout = readEventsUntil' done 0 [] $ do char <- IO.hGetChar h XML.incrementalParse parser [char] readEventsUntil' done depth accum getEvents = do events <- getEvents let (done', depth', accum') = readEventsStep done events depth accum if done' then return accum' else readEventsUntil' done depth' accum' getEvents readEventsStep _ [] depth accum = (False, depth, accum) readEventsStep done (e:es) depth accum = let depth' = depth + case e of (XML.BeginElement _ _) -> 1 (XML.EndElement _) -> (- 1) otherwise -> 0 accum' = accum ++ [e] in if done depth' e then (True, depth', accum') else readEventsStep done es depth' accum'