Browse Source

implemented xml-enumerator-based elementToString functions, re-arranged the stanza types, rewrote parts of the Stream module

master
Jon Kristensen 15 years ago
parent
commit
1cf6a09413
  1. 9
      Network/XMPP.hs
  2. 37
      Network/XMPP/Session.hs
  3. 57
      Network/XMPP/Stanza.hs
  4. 355
      Network/XMPP/Stream.hs
  5. 197
      Network/XMPP/Types.hs
  6. 77
      Network/XMPP/Utilities.hs

9
Network/XMPP.hs

@ -61,7 +61,7 @@ module Network.XMPP ( -- Network.XMPP.JID @@ -61,7 +61,7 @@ module Network.XMPP ( -- Network.XMPP.JID
, StanzaID (SID)
, From
, To
, XMLLang
, LangTag
, MessageType (..)
, Message (..)
, PresenceType (..)
@ -70,12 +70,7 @@ module Network.XMPP ( -- Network.XMPP.JID @@ -70,12 +70,7 @@ module Network.XMPP ( -- Network.XMPP.JID
, iqPayloadNamespace
, iqPayload
, injectAction
-- Network.XMPP.Utilities
, elementToString
, elementsToString
, getID ) where
, injectAction ) where
import Network.XMPP.Address
import Network.XMPP.SASL

37
Network/XMPP/Session.hs

@ -586,7 +586,9 @@ processEvent e = get >>= \ state -> @@ -586,7 +586,9 @@ processEvent e = get >>= \ state ->
put $ state { stateClientState = clientState', stateTimeoutStanzaIDs = newTimeouts }
return Nothing
IEE (EnumeratorXML (XEPresence presenceEvent)) -> do
-- TODO: Known bug - does not work with PresenceError
IEE (EnumeratorXML (XEPresence (Right presenceEvent))) -> do
let stanzaID' = presenceID $ presenceEvent
let newTimeouts = case stanzaID' of
Just stanzaID'' ->
@ -603,7 +605,8 @@ processEvent e = get >>= \ state -> @@ -603,7 +605,8 @@ processEvent e = get >>= \ state ->
put $ state { stateClientState = clientState', stateTimeoutStanzaIDs = newTimeouts }
return Nothing
IEE (EnumeratorXML (XEMessage messageEvent)) -> do
-- TODO: Does not work with message errors
IEE (EnumeratorXML (XEMessage (Right messageEvent))) -> do
let stanzaID' = messageID $ messageEvent
let newTimeouts = case stanzaID' of
Just stanzaID'' ->
@ -633,8 +636,8 @@ processEvent e = get >>= \ state -> @@ -633,8 +636,8 @@ processEvent e = get >>= \ state ->
put $ state { stateTimeoutStanzaIDs = stanzaID':(stateTimeoutStanzaIDs state) }
Nothing ->
return ()
let xml = presenceToXML presence'
lift $ liftIO $ send xml handleOrTLSCtx
let xml = presenceToXML $ Right presence'
lift $ liftIO $ send (elementToString $ Just xml) handleOrTLSCtx
return Nothing
IEC (CEMessage message stanzaCallback timeoutCallback streamErrorCallback) -> do
@ -650,25 +653,19 @@ processEvent e = get >>= \ state -> @@ -650,25 +653,19 @@ processEvent e = get >>= \ state ->
put $ state { stateTimeoutStanzaIDs = stanzaID':(stateTimeoutStanzaIDs state) }
Nothing ->
return ()
let xml = messageToXML message'
lift $ liftIO $ send xml handleOrTLSCtx
let xml = messageToXML $ Right message'
lift $ liftIO $ send (elementToString $ Just xml) handleOrTLSCtx
return Nothing
-- TODO: Known bugs until Session rewritten - new ID everytime, callback not called
IEC (CEIQ iq stanzaCallback timeoutCallback stanzaErrorCallback) -> do
iq' <- case iqID iq of
Nothing -> do
iq' <- do -- case iqID iq of
-- Nothing -> do
id <- liftIO $ nextID $ stateIDGenerator state
return $ case iq of
IQReq r -> do
IQReq (r { iqRequestID = Just (SID id) })
IQRes r -> do
IQRes (r { iqResponseID = Just (SID id) })
_ -> return iq
case stanzaCallback of
Just callback' -> case iq of
IQReq {} -> put $ state { stateIQCallbacks = (fromJust $ iqID iq, callback'):(stateIQCallbacks state) }
_ -> return ()
Nothing -> return ()
return iq
let callback' = fromJust stanzaCallback
put $ state { stateIQCallbacks = (fromJust $ iqID iq, callback'):(stateIQCallbacks state) }
case timeoutCallback of
Just (t, timeoutCallback') ->
let stanzaID' = (fromJust $ iqID iq') in do
@ -678,7 +675,7 @@ processEvent e = get >>= \ state -> @@ -678,7 +675,7 @@ processEvent e = get >>= \ state ->
return ()
-- TODO: Bind ID to callback
let xml = iqToXML iq'
lift $ liftIO $ send xml handleOrTLSCtx
lift $ liftIO $ send (elementToString $ Just xml) handleOrTLSCtx
return Nothing
IEC (CEAction predicate callback) -> do

57
Network/XMPP/Stanza.hs

@ -15,7 +15,7 @@ module Network.XMPP.Stanza ( @@ -15,7 +15,7 @@ module Network.XMPP.Stanza (
iqID,
iqFrom,
iqTo,
iqXMLLang,
iqLangTag,
iqPayload,
iqPayloadNamespace,
iqRequestPayloadNamespace,
@ -37,8 +37,16 @@ import Data.Text (unpack) @@ -37,8 +37,16 @@ import Data.Text (unpack)
iqID :: IQ -> Maybe StanzaID
iqID (IQReq i) = iqRequestID i
iqID (IQRes i) = iqResponseID i
iqID (Left req) = iqRequestID req
iqID (Right res) = iqResponseID res
-- TODO: Maybe?
iqResponseID :: IQResponse -> Maybe StanzaID
iqResponseID (Left err) = iqErrorID err
iqResponseID (Right res) = iqResultID res
-- |
@ -46,8 +54,8 @@ iqID (IQRes i) = iqResponseID i @@ -46,8 +54,8 @@ iqID (IQRes i) = iqResponseID i
iqFrom :: IQ -> Maybe From
iqFrom (IQReq i) = iqRequestFrom i
iqFrom (IQRes i) = iqResponseFrom i
iqFrom (Left req) = iqRequestFrom req
iqFrom (Right res) = iqResponseFrom res
-- |
@ -55,17 +63,36 @@ iqFrom (IQRes i) = iqResponseFrom i @@ -55,17 +63,36 @@ iqFrom (IQRes i) = iqResponseFrom i
iqTo :: IQ -> Maybe To
iqTo (IQReq i) = iqRequestTo i
iqTo (IQRes i) = iqResponseTo i
iqTo (Left req) = iqRequestTo req
iqTo (Right res) = iqResponseTo res
-- |
-- Returns the @XMLLang@ value of the @IQ@, if any.
iqXMLLang :: IQ -> Maybe XMLLang
iqLangTag :: IQ -> Maybe LangTag
iqLangTag (Left req) = iqRequestLangTag req
iqLangTag (Right res) = iqResponseLangTag res
iqResponseLangTag :: IQResponse -> Maybe LangTag
iqResponseLangTag (Left err) = iqErrorLangTag err
iqResponseLangTag (Right res) = iqResultLangTag res
iqResponseFrom :: IQResponse -> Maybe From
iqResponseFrom (Left err) = iqErrorFrom err
iqResponseFrom (Right res) = iqResultFrom res
iqResponseTo :: IQResponse -> Maybe To
iqResponseTo (Left err) = iqErrorTo err
iqResponseTo (Right res) = iqResultTo res
iqXMLLang (IQReq i) = iqRequestXMLLang i
iqXMLLang (IQRes i) = iqResponseXMLLang i
-- |
@ -74,8 +101,14 @@ iqXMLLang (IQRes i) = iqResponseXMLLang i @@ -74,8 +101,14 @@ iqXMLLang (IQRes i) = iqResponseXMLLang i
iqPayload :: IQ -> Maybe Element
iqPayload (IQReq i) = Just (iqRequestPayload i)
iqPayload (IQRes i) = iqResponsePayload i
iqPayload (Left req) = Just (iqRequestPayload req)
iqPayload (Right res) = iqResponsePayload res
iqResponsePayload :: IQResponse -> Maybe Element
iqResponsePayload (Left err) = iqErrorPayload err
iqResponsePayload (Right res) = iqResultPayload res
-- |

355
Network/XMPP/Stream.hs

@ -3,6 +3,8 @@ @@ -3,6 +3,8 @@
{-# OPTIONS_HADDOCK hide #-}
{-# LANGUAGE OverloadedStrings #-}
module Network.XMPP.Stream (
isTLSSecured,
xmlEnumerator,
@ -54,9 +56,6 @@ import Text.Parsec.ByteString (GenParser) @@ -54,9 +56,6 @@ import Text.Parsec.ByteString (GenParser)
import qualified Data.ByteString.Char8 as DBC (pack)
import Data.List (intersperse)
import Data.Char (toLower)
isTLSSecured :: TLSState -> Bool
isTLSSecured (PostHandshake _) = True
@ -151,7 +150,7 @@ processEventList e @@ -151,7 +150,7 @@ processEventList e
| nameLocalName name == DT.pack "iq" = XEIQ $ parseIQ $ eventsToElement e
| nameLocalName name == DT.pack "presence" = XEPresence $ parsePresence $ eventsToElement e
| nameLocalName name == DT.pack "message" = XEMessage $ parseMessage $ eventsToElement e
| otherwise = XEOther $ elementToString $ Just (eventsToElement e)
| otherwise = XEOther "TODO: Element instead of String" -- Just (eventsToElement e)
where
(EventBeginElement name attribs) = head e
es = tail e
@ -169,314 +168,105 @@ counter c (Just (EventBeginElement _ _)) = (c + 1) @@ -169,314 +168,105 @@ counter c (Just (EventBeginElement _ _)) = (c + 1)
counter c (Just (EventEndElement _) ) = (c - 1)
counter c _ = c
presenceToXML :: Presence -> String
presenceToXML p = "<presence" ++ from ++ id' ++ to ++ type' ++ ">" ++
(elementsToString $ presencePayload p) ++ "</presence>"
where
from :: String
from = case presenceFrom p of
-- TODO: Lower-case
Just s -> " from='" ++ (show s) ++ "'"
Nothing -> ""
id' :: String
id' = case presenceID p of
Just (SID s) -> " id='" ++ s ++ "'"
Nothing -> ""
to :: String
to = case presenceTo p of
-- TODO: Lower-case
Just s -> " to='" ++ (show s) ++ "'"
Nothing -> ""
type' :: String
type' = case presenceType p of
Available -> ""
t -> " type='" ++ (presenceTypeToString t) ++ "'"
iqToXML :: IQ -> String
iqToXML (IQReq (IQGet { iqRequestID = i, iqRequestPayload = p, iqRequestFrom = f, iqRequestTo = t })) =
let type' = " type='get'" in "<iq" ++ from ++ id' ++ to ++ type' ++ ">" ++ (elementToString (Just p)) ++ "</iq>"
where
from :: String
from = case f of
-- TODO: Lower-case
Just s -> " from='" ++ (show s) ++ "'"
Nothing -> ""
id' :: String
id' = case i of
Just (SID s) -> " id='" ++ s ++ "'"
Nothing -> ""
to :: String
to = case t of
-- TODO: Lower-case
Just s -> " to='" ++ (show s) ++ "'"
Nothing -> ""
iqToXML (IQReq (IQSet { iqRequestID = i, iqRequestPayload = p, iqRequestFrom = f, iqRequestTo = t })) =
let type' = " type='set'" in "<iq" ++ from ++ id' ++ to ++ type' ++ ">" ++ (elementToString (Just p)) ++ "</iq>"
where
from :: String
from = case f of
-- TODO: Lower-case
Just s -> " from='" ++ (show s) ++ "'"
Nothing -> ""
id' :: String
id' = case i of
Just (SID s) -> " id='" ++ s ++ "'"
Nothing -> ""
to :: String
to = case t of
-- TODO: Lower-case
Just s -> " to='" ++ (show s) ++ "'"
Nothing -> ""
iqToXML (IQRes (IQResult { iqResponseID = i, iqResponsePayload = p, iqResponseFrom = f, iqResponseTo = t })) =
let type' = " type='result'" in "<iq" ++ from ++ id' ++ to ++ type' ++ ">" ++ (elementToString p) ++ "</iq>"
where
from :: String
from = case f of
-- TODO: Lower-case
Just s -> " from='" ++ (show s) ++ "'"
Nothing -> ""
id' :: String
id' = case i of
Just (SID s) -> " id='" ++ s ++ "'"
Nothing -> ""
to :: String
to = case t of
-- TODO: Lower-case
Just s -> " to='" ++ (show s) ++ "'"
Nothing -> ""
-- TODO: Turn message errors into XML.
messageToXML :: Message -> String
messageToXML Message { messageID = i, messageFrom = f, messageTo = t, messagePayload = p, messageType = ty } = "<message" ++ from ++ id' ++ to ++ type' ++ ">" ++
(elementsToString $ p) ++ "</message>"
where
from :: String
from = case f of
-- TODO: Lower-case
Just s -> " from='" ++ (show s) ++ "'"
Nothing -> ""
id' :: String
id' = case i of
Just (SID s) -> " id='" ++ s ++ "'"
Nothing -> ""
to :: String
to = case t of
-- TODO: Lower-case
Just s -> " to='" ++ (show s) ++ "'"
Nothing -> ""
type' :: String
type' = case ty of
Normal -> ""
t -> " type='" ++ (messageTypeToString t) ++ "'"
parseIQ :: Element -> IQ
parseIQ e | typeAttr == "get" = let (Just payloadMust) = payload
in IQReq (IQGet idAttr fromAttr toAttr Nothing
payloadMust)
| typeAttr == "set" = let (Just payloadMust) = payload
in IQReq (IQSet idAttr fromAttr toAttr Nothing
payloadMust)
| typeAttr == "result" = IQRes (IQResult idAttr fromAttr toAttr
Nothing payload)
presenceToXML :: InternalPresence -> Element
presenceToXML (Right p) = Element "presence" attribs nodes
where
-- TODO: Many duplicate functions from parsePresence.
payload :: Maybe Element
payload = case null (elementChildren e) of
True -> Nothing
False -> Just $ head $ elementChildren e
typeAttr :: String
typeAttr = case attributeText typeName e of
-- Nothing -> Nothing
Just a -> DT.unpack a
fromAttr :: Maybe Address
fromAttr = case attributeText fromName e of
Nothing -> Nothing
Just a -> X.fromString $ DT.unpack a
toAttr :: Maybe Address
toAttr = case attributeText toName e of
Nothing -> Nothing
Just a -> X.fromString $ DT.unpack a
idAttr :: Maybe StanzaID
idAttr = case attributeText idName e of
Nothing -> Nothing
Just a -> Just (SID (DT.unpack a))
typeName :: Name
typeName = fromString "type"
attribs :: [(Name, [Content])]
attribs = stanzaNodes (presenceID p) (presenceFrom p) (presenceTo p) (presenceLangTag p) ++
[("type", [ContentText $ DT.pack $ show $ presenceType p])]
fromName :: Name
fromName = fromString "from"
nodes :: [Node]
nodes = map (\ x -> NodeElement x) (presencePayload p)
toName :: Name
toName = fromString "to"
idName :: Name
idName = fromString "id"
-- TODO: Parse xml:lang
parsePresence :: Element -> Presence
parsePresence e = Presence idAttr fromAttr toAttr Nothing typeAttr (elementChildren e)
presenceToXML (Left p) = Element "presence" attribs nodes
where
-- TODO: Many duplicate functions from parseIQ.
typeAttr :: PresenceType
typeAttr = case attributeText typeName e of
Just t -> stringToPresenceType $ DT.unpack t
Nothing -> Available
attribs :: [(Name, [Content])]
attribs = stanzaNodes (presenceErrorID p) (presenceErrorFrom p) (presenceErrorTo p) (presenceErrorLangTag p) ++
[("type", [ContentText $ DT.pack "error"])]
fromAttr :: Maybe Address
fromAttr = case attributeText fromName e of
Nothing -> Nothing
Just a -> X.fromString $ DT.unpack a
nodes :: [Node]
nodes = case presenceErrorPayload p of
Just elem -> map (\ x -> NodeElement x) elem
Nothing -> []
toAttr :: Maybe Address
toAttr = case attributeText toName e of
Nothing -> Nothing
Just a -> X.fromString $ DT.unpack a
idAttr :: Maybe StanzaID
idAttr = case attributeText idName e of
Nothing -> Nothing
Just a -> Just (SID (DT.unpack a))
iqToXML :: IQ -> Element
fromName :: Name
fromName = fromString "from"
iqToXML = iqToXML
typeName :: Name
typeName = fromString "type"
toName :: Name
toName = fromString "to"
messageToXML :: InternalMessage -> Element
idName :: Name
idName = fromString "id"
parseMessage :: Element -> Message
parseMessage e = Message idAttr fromAttr toAttr Nothing typeAttr (elementChildren e)
where
-- TODO: Many duplicate functions from parseIQ.
messageToXML = messageToXML
typeAttr :: MessageType
typeAttr = case attributeText typeName e of
Just t -> stringToMessageType $ DT.unpack t
Nothing -> Normal
fromAttr :: Maybe Address
fromAttr = case attributeText fromName e of
Nothing -> Nothing
Just a -> X.fromString $ DT.unpack a
stanzaNodes :: Maybe StanzaID -> Maybe From -> Maybe To -> Maybe LangTag -> [(Name, [Content])]
toAttr :: Maybe Address
toAttr = case attributeText toName e of
Nothing -> Nothing
Just a -> X.fromString $ DT.unpack a
stanzaNodes i f t l = if isJust $ i then [("id", [ContentText $ DT.pack $ show $ fromJust i])] else [] ++
if isJust $ f then [("from", [ContentText $ DT.pack $ show $ fromJust f])] else [] ++
if isJust $ t then [("to", [ContentText $ DT.pack $ show $ fromJust t])] else [] ++
if isJust $ l then [("xml:lang", [ContentText $ DT.pack $ show l])] else []
idAttr :: Maybe StanzaID
idAttr = case attributeText idName e of
Nothing -> Nothing
Just a -> Just (SID (DT.unpack a))
fromName :: Name
fromName = fromString "from"
typeName :: Name
typeName = fromString "type"
toName :: Name
toName = fromString "to"
idName :: Name
idName = fromString "id"
parseIQ :: Element -> IQ
-- stringToPresenceType "available" = Available
-- stringToPresenceType "away" = Away
-- stringToPresenceType "chat" = Chat
-- stringToPresenceType "dnd" = DoNotDisturb
-- stringToPresenceType "xa" = ExtendedAway
parseIQ = parseIQ
stringToPresenceType "available" = Available -- TODO: Some client sent this
stringToPresenceType "probe" = Probe
-- stringToPresenceType "error" = PresenceError -- TODO: Special case
parsePresence :: Element -> InternalPresence
stringToPresenceType "unavailable" = Unavailable
stringToPresenceType "subscribe" = Subscribe
stringToPresenceType "subscribed" = Subscribed
stringToPresenceType "unsubscribe" = Unsubscribe
stringToPresenceType "unsubscribed" = Unsubscribed
parsePresence = parsePresence
-- presenceTypeToString Available = "available"
-- presenceTypeToString Away = "away"
-- presenceTypeToString Chat = "chat"
-- presenceTypeToString DoNotDisturb = "dnd"
-- presenceTypeToString ExtendedAway = "xa"
parseMessage :: Element -> InternalMessage
presenceTypeToString Unavailable = "unavailable"
parseMessage = parseMessage
presenceTypeToString Probe = "probe"
-- presenceTypeToString PresenceError = "error" -- TODO: Special case
presenceTypeToString Subscribe = "subscribe"
presenceTypeToString Subscribed = "subscribed"
presenceTypeToString Unsubscribe = "unsubscribe"
presenceTypeToString Unsubscribed = "unsubscribed"
stringToPresenceType :: String -> Maybe (Maybe PresenceType)
stringToMessageType "chat" = Chat
stringToMessageType "error" = Error
stringToMessageType "groupchat" = Groupchat
stringToMessageType "headline" = Headline
stringToMessageType "normal" = Normal
stringToMessageType s = OtherMessageType s
stringToPresenceType "probe" = Just $ Just Probe
stringToPresenceType "unavailable" = Just $ Just Unavailable
stringToPresenceType "subscribe" = Just $ Just Subscribe
stringToPresenceType "subscribed" = Just $ Just Subscribed
stringToPresenceType "unsubscribe" = Just $ Just Unsubscribe
stringToPresenceType "unsubscribed" = Just $ Just Unsubscribed
stringToPresenceType "error" = Just Nothing
stringToPresenceType _ = Nothing
messageTypeToString Chat = "chat"
messageTypeToString Error = "error"
messageTypeToString Groupchat = "groupchat"
messageTypeToString Headline = "headline"
messageTypeToString Normal = "normal"
messageTypeToString (OtherMessageType s) = s
presenceTypeToString :: Maybe PresenceType -> String
data Version = Version { majorVersion :: Integer
, minorVersion :: Integer } deriving (Eq)
presenceTypeToString (Just Unavailable) = "unavailable"
presenceTypeToString (Just Probe) = "probe"
presenceTypeToString Nothing = "error"
presenceTypeToString (Just Subscribe) = "subscribe"
presenceTypeToString (Just Subscribed) = "subscribed"
presenceTypeToString (Just Unsubscribe) = "unsubscribe"
presenceTypeToString (Just Unsubscribed) = "unsubscribed"
-- Version numbers are displayed as "<major>.<minor>".
stringToMessageType :: String -> Maybe (Maybe MessageType)
instance Show Version where
show (Version major minor) = (show major) ++ "." ++ (show minor)
stringToMessageType "chat" = Just $ Just Chat
stringToMessageType "error" = Just $ Nothing
stringToMessageType "groupchat" = Just $ Just Groupchat
stringToMessageType "headline" = Just $ Just Headline
stringToMessageType "normal" = Just $ Just Normal
stringToMessageType _ = Nothing
-- If the major version numbers are not equal, compare them. Otherwise, compare
-- the minor version numbers.
messageTypeToString :: Maybe MessageType -> String
instance Ord Version where
compare (Version amajor aminor) (Version bmajor bminor)
| amajor /= bmajor = compare amajor bmajor
| otherwise = compare aminor bminor
messageTypeToString (Just Chat) = "chat"
messageTypeToString Nothing = "error"
messageTypeToString (Just Groupchat) = "groupchat"
messageTypeToString (Just Headline) = "headline"
messageTypeToString (Just Normal) = "normal"
-- Converts a "<major>.<minor>" numeric version number to a "Version" object.
@ -507,25 +297,6 @@ version = do @@ -507,25 +297,6 @@ version = do
return $ Version (read major) (read minor)
data LangTag = LangTag { primaryTag :: String
, subtags :: [String] }
-- Displays the language tag in the form of "en-US".
instance Show LangTag where
show (LangTag p []) = p
show (LangTag p s) = p ++ "-" ++ (concat $ intersperse "-" s)
-- Two language tags are considered equal of they contain the same tags (case-insensitive).
instance Eq LangTag where
(LangTag ap as) == (LangTag bp bs)
| length as == length bs && map toLower ap == map toLower bp = all (\ (a, b) -> map toLower a == map toLower b) $ zip as bs
| otherwise = False
-- |
-- Parses, validates, and possibly constructs a "LangTag" object.

197
Network/XMPP/Types.hs

@ -9,7 +9,7 @@ module Network.XMPP.Types ( @@ -9,7 +9,7 @@ module Network.XMPP.Types (
StanzaID (..),
From,
To,
IQ (..),
IQ,
IQRequest (..),
IQResponse (..),
Message (..),
@ -32,7 +32,7 @@ Address (..), @@ -32,7 +32,7 @@ Address (..),
Localpart,
Domainpart,
Resourcepart,
XMLLang,
LangTag (..),
InternalEvent (..),
XMLEvent (..),
ConnectionState (..),
@ -48,7 +48,14 @@ XMPPError (..), @@ -48,7 +48,14 @@ XMPPError (..),
Timeout,
TimeoutEvent (..),
StreamError (..),
IDGenerator (..)
IDGenerator (..),
Version (..),
IQError (..),
IQResult (..),
IQRequestType (..),
PresenceError (..),
InternalPresence (..),
InternalMessage (..)
) where
import GHC.IO.Handle (Handle, hPutStr, hFlush, hSetBuffering, hWaitForInput)
@ -61,7 +68,7 @@ import Control.Monad.State hiding (State) @@ -61,7 +68,7 @@ import Control.Monad.State hiding (State)
import Data.XML.Types
import Network.TLS
import Network.TLS hiding (Version)
import Network.TLS.Cipher
import qualified Control.Monad.Error as CME
@ -70,6 +77,9 @@ import Data.IORef @@ -70,6 +77,9 @@ import Data.IORef
import Data.Certificate.X509 (X509)
import Data.List (intersperse)
import Data.Char (toLower)
-- =============================================================================
-- STANZA TYPES
@ -110,42 +120,43 @@ type To = Address @@ -110,42 +120,43 @@ type To = Address
-- An Info/Query (IQ) stanza is either of the type "request" ("get" or "set") or
-- "response" ("result" or "error"). The @IQ@ type wraps these two sub-types.
data IQ = IQReq IQRequest | IQRes IQResponse deriving (Eq, Show)
type IQ = Either IQRequest IQResponse
-- |
-- A "request" Info/Query (IQ) stanza is one with either "get" or "set" as type.
-- They are guaranteed to always contain a payload.
data IQRequest = IQGet { iqRequestID :: Maybe StanzaID
, iqRequestFrom :: Maybe From
, iqRequestTo :: Maybe To
, iqRequestXMLLang :: Maybe XMLLang
, iqRequestPayload :: Element } |
IQSet { iqRequestID :: Maybe StanzaID
data IQRequest = IQRequest { iqRequestID :: Maybe StanzaID
, iqRequestFrom :: Maybe From
, iqRequestTo :: Maybe To
, iqRequestXMLLang :: Maybe XMLLang
, iqRequestLangTag :: Maybe LangTag
, iqRequestType :: IQRequestType
, iqRequestPayload :: Element }
deriving (Eq, Show)
deriving (Show)
-- |
-- A "response" Info/Query (IQ) stanza is one with either "result" or "error" as
-- type.
data IQResponse = IQResult { iqResponseID :: Maybe StanzaID
, iqResponseFrom :: Maybe From
, iqResponseTo :: Maybe To
, iqResponseXMLLang :: Maybe XMLLang
, iqResponsePayload :: Maybe Element } |
IQError { iqResponseID :: Maybe StanzaID
, iqResponseFrom :: Maybe From
, iqResponseTo :: Maybe To
, iqResponseXMLLang :: Maybe XMLLang
, iqResponsePayload :: Maybe Element
, iqResponseStanzaError :: StanzaError }
deriving (Eq, Show)
data IQRequestType = Get | Set deriving (Show)
type IQResponse = Either IQError IQResult
data IQResult = IQResult { iqResultID :: Maybe StanzaID
, iqResultFrom :: Maybe From
, iqResultTo :: Maybe To
, iqResultLangTag :: Maybe LangTag
, iqResultPayload :: Maybe Element }
deriving (Show)
data IQError = IQError { iqErrorID :: Maybe StanzaID
, iqErrorFrom :: Maybe From
, iqErrorTo :: Maybe To
, iqErrorLangTag :: Maybe LangTag
, iqErrorPayload :: Maybe Element
, iqErrorStanzaError :: Maybe StanzaError }
deriving (Show)
-- |
@ -154,51 +165,70 @@ data IQResponse = IQResult { iqResponseID :: Maybe StanzaID @@ -154,51 +165,70 @@ data IQResponse = IQResult { iqResponseID :: Maybe StanzaID
data Message = Message { messageID :: Maybe StanzaID
, messageFrom :: Maybe From
, messageTo :: Maybe To
, messageXMLLang :: Maybe XMLLang
, messageXMLLang :: Maybe LangTag
, messageType :: MessageType
, messagePayload :: [Element] } |
MessageError { messageID :: Maybe StanzaID
, messageFrom :: Maybe From
, messageTo :: Maybe To
, messageXMLLang :: Maybe XMLLang
, messagePayload :: [Element] }
deriving (Show)
data MessageError = MessageError { messageErrorID :: StanzaID
, messageErrorFrom :: Maybe From
, messageErrorTo :: Maybe To
, messageErrorXMLLang :: Maybe LangTag
, messageErrorPayload :: Maybe [Element]
, messageErrorStanzaError :: StanzaError }
deriving (Eq, Show)
deriving (Show)
type InternalMessage = Either MessageError Message
-- |
-- @MessageType@ holds XMPP message types as defined in XMPP-IM. @Normal@ is the
-- default message type.
-- default message type. The "error" message type is left out as errors are
-- using @MessageError@.
data MessageType = Chat |
Error |
Groupchat |
Headline |
Normal |
OtherMessageType String deriving (Eq, Show)
Normal deriving (Eq)
instance Show MessageType where
show Chat = "chat"
show Groupchat = "groupchat"
show Headline = "headline"
show Normal = "normal"
-- |
-- The presence stanza - either a presence or a presence error.
-- The presence stanza. It is used for both originating messages and replies.
-- For presence errors, see "PresenceError".
data Presence = Presence { presenceID :: Maybe StanzaID
, presenceFrom :: Maybe From
, presenceTo :: Maybe To
, presenceXMLLang :: Maybe XMLLang
, presenceType :: PresenceType
, presencePayload :: [Element] } |
PresenceError { presenceID :: Maybe StanzaID
, presenceFrom :: Maybe From
, presenceTo :: Maybe To
, presenceXMLLang :: Maybe XMLLang
, presenceLangTag :: Maybe LangTag
, presenceType :: Maybe PresenceType
, presencePayload :: [Element] }
deriving (Show)
data PresenceError = PresenceError { presenceErrorID :: Maybe StanzaID
, presenceErrorFrom :: Maybe From
, presenceErrorTo :: Maybe To
, presenceErrorLangTag :: Maybe LangTag
, presenceErrorPayload :: Maybe [Element]
, presenceErrorStanzaError :: StanzaError }
deriving (Eq, Show)
deriving (Show)
type InternalPresence = Either PresenceError Presence
-- |
-- @PresenceType@ holds XMPP presence types. When a presence type is not
-- provided, we assign the @PresenceType@ value @Available@.
-- @PresenceType@ holds XMPP presence types. The "error" message type is left
-- out as errors are using @PresenceError@.
data PresenceType = Subscribe | -- ^ Sender wants to subscribe to presence
Subscribed | -- ^ Sender has approved the subscription
@ -207,8 +237,16 @@ data PresenceType = Subscribe | -- ^ Sender wants to subscribe to presence @@ -207,8 +237,16 @@ data PresenceType = Subscribe | -- ^ Sender wants to subscribe to presence
-- subscription
Probe | -- ^ Sender requests current presence;
-- should only be used by servers
Available | -- ^ Sender did not specify a type attribute
Unavailable deriving (Eq, Show)
Unavailable deriving (Eq)
instance Show PresenceType where
show Subscribe = "subscribe"
show Subscribed = "subscribed"
show Unsubscribe = "unsubscribe"
show Unsubscribed = "unsubscribed"
show Probe = "probe"
show Unavailable = "unavailable"
-- |
@ -360,8 +398,8 @@ type Resource = String @@ -360,8 +398,8 @@ type Resource = String
data XMLEvent = XEBeginStream String | XEFeatures String |
XEChallenge Challenge | XESuccess Success |
XEEndStream | XEIQ IQ | XEPresence Presence |
XEMessage Message | XEProceed |
XEEndStream | XEIQ IQ | XEPresence InternalPresence |
XEMessage InternalMessage | XEProceed |
XEOther String deriving (Show)
data EnumeratorEvent = EnumeratorDone |
@ -472,12 +510,47 @@ data StreamError = StreamError @@ -472,12 +510,47 @@ data StreamError = StreamError
-- XML TYPES
-- =============================================================================
type XMLLang = String
-- Validate, protect. See:
-- http://tools.ietf.org/html/rfc6120#section-8.1.5
-- http://www.w3.org/TR/2008/REC-xml-20081126/
-- http://www.rfc-editor.org/rfc/bcp/bcp47.txt
-- http://www.ietf.org/rfc/rfc1766.txt
newtype IDGenerator = IDGenerator (IORef [String])
--- other stuff
data Version = Version { majorVersion :: Integer
, minorVersion :: Integer } deriving (Eq)
-- Version numbers are displayed as "<major>.<minor>".
instance Show Version where
show (Version major minor) = (show major) ++ "." ++ (show minor)
-- If the major version numbers are not equal, compare them. Otherwise, compare
-- the minor version numbers.
instance Ord Version where
compare (Version amajor aminor) (Version bmajor bminor)
| amajor /= bmajor = compare amajor bmajor
| otherwise = compare aminor bminor
data LangTag = LangTag { primaryTag :: String
, subtags :: [String] }
-- Displays the language tag in the form of "en-US".
instance Show LangTag where
show (LangTag p []) = p
show (LangTag p s) = p ++ "-" ++ (concat $ intersperse "-" s)
-- Two language tags are considered equal of they contain the same tags (case-insensitive).
instance Eq LangTag where
(LangTag ap as) == (LangTag bp bs)
| length as == length bs && map toLower ap == map toLower bp = all (\ (a, b) -> map toLower a == map toLower b) $ zip as bs
| otherwise = False

77
Network/XMPP/Utilities.hs

@ -3,13 +3,19 @@ @@ -3,13 +3,19 @@
{-# OPTIONS_HADDOCK hide #-}
{-# LANGUAGE OverloadedStrings #-}
-- TODO: Use -fno-cse? http://cvs.haskell.org/Hugs/pages/libraries/base/System-IO-Unsafe.html
-- TODO: Document this module
-- TODO: Make is possible to customize characters
-- TODO: Make it possible to customize length
module Network.XMPP.Utilities ( elementToString
, elementsToString ) where
, elementsToString, testElement ) where
import Prelude hiding (concat)
import Data.Word
import Data.XML.Types
import System.Crypto.Random
@ -17,48 +23,55 @@ import System.Random @@ -17,48 +23,55 @@ import System.Random
import qualified Data.ByteString as DB
import qualified Data.Map as DM
import qualified Data.Text as DT
import qualified Data.ByteString.Char8 as DBC
import Data.Enumerator (($$), Stream (Chunks), Enumerator, Iteratee, Step (Continue), continue, joinI,
run, run_, yield, returnI)
import Data.Enumerator.List (consume)
import Text.XML.Enumerator.Document (toEvents)
import Text.XML.Enumerator.Render (renderBytes)
import Data.Maybe (fromJust)
import Data.ByteString (concat, unpack)
import Data.List (tail)
import System.IO.Unsafe (unsafePerformIO)
{-# NOINLINE elementToString #-}
-- =============================================================================
-- XML Utilities
-- =============================================================================
-- TODO: Remove?
elementsToString :: [Element] -> String
elementsToString [] = ""
elementsToString (e:es) = (elementToString $ Just e) ++ elementsToString es
elementsToString (e:es) = (elementToString (Just e)) ++ (elementsToString es)
-- Converts the Element object to a document, converts it into Events, strips
-- the DocumentBegin event, generates a ByteString, and converts it into a
-- String.
elementToString :: Maybe Element -> String
elementToString Nothing = ""
elementToString (Just e) = "<" ++ nameToString (elementName e) ++ xmlns ++
attributes (elementAttributes e) ++
">" ++ (nodesToString $ elementNodes e) ++ "</" ++
nameToString (elementName e) ++ ">"
elementToString (Just elem) = DBC.unpack $ concat $ unsafePerformIO $ do
r <- run_ $ events $$ (joinI $ renderBytes $$ consume)
return r
where
xmlns :: String
xmlns = case nameNamespace $ elementName e of
Nothing -> ""
Just t -> " xmlns='" ++ (DT.unpack t) ++ "'"
nameToString :: Name -> String
nameToString Name { nameLocalName = n, namePrefix = Nothing } = DT.unpack n
nameToString Name { nameLocalName = n, namePrefix = Just p } =
(DT.unpack p) ++ ":" ++ (DT.unpack n)
contentToString :: Content -> String
contentToString (ContentText t) = DT.unpack t
contentToString (ContentEntity t) = DT.unpack t
attributes :: [(Name, [Content])] -> String
attributes [] = ""
attributes ((n, c):t) = (" " ++ (nameToString n) ++ "='" ++
concat (map contentToString c) ++ "'") ++
attributes t
nodesToString :: [Node] -> String
nodesToString [] = ""
nodesToString ((NodeElement e):ns) = (elementToString $ Just e) ++
(nodesToString ns)
nodesToString ((NodeContent c):ns) = (contentToString c) ++
(nodesToString ns)
-- Enumerator that "produces" the events to convert to the document
events :: Enumerator Event IO [DB.ByteString]
events (Continue more) = more $ Chunks (tail $ toEvents $ dummyDoc elem)
events step = returnI step
dummyDoc :: Element -> Document
dummyDoc e = Document (Prologue [] Nothing []) elem []
testElement :: Element
testElement = Element ("{http://example.com/ns/my-namespace}my-name" :: Name) [] []

Loading…
Cancel
Save