|
|
|
@ -13,7 +13,10 @@ |
|
|
|
{-# OPTIONS_HADDOCK hide #-} |
|
|
|
{-# OPTIONS_HADDOCK hide #-} |
|
|
|
|
|
|
|
|
|
|
|
module Network.Xmpp.Types |
|
|
|
module Network.Xmpp.Types |
|
|
|
( IQError(..) |
|
|
|
( NonemptyText(..) |
|
|
|
|
|
|
|
, nonEmpty |
|
|
|
|
|
|
|
, text |
|
|
|
|
|
|
|
, IQError(..) |
|
|
|
, IQRequest(..) |
|
|
|
, IQRequest(..) |
|
|
|
, IQRequestType(..) |
|
|
|
, IQRequestType(..) |
|
|
|
, IQResponse(..) |
|
|
|
, IQResponse(..) |
|
|
|
@ -68,8 +71,7 @@ module Network.Xmpp.Types |
|
|
|
, parseJid |
|
|
|
, parseJid |
|
|
|
, TlsBehaviour(..) |
|
|
|
, TlsBehaviour(..) |
|
|
|
, AuthFailure(..) |
|
|
|
, AuthFailure(..) |
|
|
|
) |
|
|
|
) where |
|
|
|
where |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
import Control.Applicative ((<$>), (<|>), many) |
|
|
|
import Control.Applicative ((<$>), (<|>), many) |
|
|
|
import Control.Concurrent.STM |
|
|
|
import Control.Concurrent.STM |
|
|
|
@ -77,9 +79,11 @@ import Control.Exception |
|
|
|
import Control.Monad.Error |
|
|
|
import Control.Monad.Error |
|
|
|
import qualified Data.Attoparsec.Text as AP |
|
|
|
import qualified Data.Attoparsec.Text as AP |
|
|
|
import qualified Data.ByteString as BS |
|
|
|
import qualified Data.ByteString as BS |
|
|
|
|
|
|
|
import Data.Char (isSpace) |
|
|
|
import Data.Conduit |
|
|
|
import Data.Conduit |
|
|
|
import Data.Default |
|
|
|
import Data.Default |
|
|
|
import qualified Data.Set as Set |
|
|
|
import qualified Data.Set as Set |
|
|
|
|
|
|
|
import Data.String (IsString, fromString) |
|
|
|
import Data.Text (Text) |
|
|
|
import Data.Text (Text) |
|
|
|
import qualified Data.Text as Text |
|
|
|
import qualified Data.Text as Text |
|
|
|
import Data.Typeable(Typeable) |
|
|
|
import Data.Typeable(Typeable) |
|
|
|
@ -87,6 +91,7 @@ import Data.XML.Types |
|
|
|
#if WITH_TEMPLATE_HASKELL |
|
|
|
#if WITH_TEMPLATE_HASKELL |
|
|
|
import Language.Haskell.TH |
|
|
|
import Language.Haskell.TH |
|
|
|
import Language.Haskell.TH.Quote |
|
|
|
import Language.Haskell.TH.Quote |
|
|
|
|
|
|
|
import qualified Language.Haskell.TH.Syntax as TH |
|
|
|
#endif |
|
|
|
#endif |
|
|
|
import Network |
|
|
|
import Network |
|
|
|
import Network.DNS |
|
|
|
import Network.DNS |
|
|
|
@ -95,6 +100,25 @@ import Network.TLS.Extra |
|
|
|
import qualified Text.StringPrep as SP |
|
|
|
import qualified Text.StringPrep as SP |
|
|
|
import qualified Text.StringPrep.Profiles as SP |
|
|
|
import qualified Text.StringPrep.Profiles as SP |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
-- | Type of Texts that contain at least on non-space character |
|
|
|
|
|
|
|
newtype NonemptyText = Nonempty {fromNonempty :: Text} |
|
|
|
|
|
|
|
deriving (Show, Read, Eq, Ord) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
instance IsString NonemptyText where |
|
|
|
|
|
|
|
fromString str = case nonEmpty (Text.pack str) of |
|
|
|
|
|
|
|
Nothing -> error $ "NonemptyText fromString called on empty or " ++ |
|
|
|
|
|
|
|
"all-whitespace string" |
|
|
|
|
|
|
|
Just r -> r |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
-- | Check that Text contains at least one non-space character wrap it |
|
|
|
|
|
|
|
nonEmpty :: Text -> Maybe NonemptyText |
|
|
|
|
|
|
|
nonEmpty txt = if Text.all isSpace txt then Nothing else Just (Nonempty txt) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
-- | Same as 'fromNonempty' |
|
|
|
|
|
|
|
text :: NonemptyText -> Text |
|
|
|
|
|
|
|
text (Nonempty txt) = txt |
|
|
|
|
|
|
|
{-# INLINE text #-} |
|
|
|
|
|
|
|
|
|
|
|
-- | The Xmpp communication primities (Message, Presence and Info/Query) are |
|
|
|
-- | The Xmpp communication primities (Message, Presence and Info/Query) are |
|
|
|
-- called stanzas. |
|
|
|
-- called stanzas. |
|
|
|
data Stanza = IQRequestS !IQRequest |
|
|
|
data Stanza = IQRequestS !IQRequest |
|
|
|
@ -151,8 +175,6 @@ data Message = Message { messageID :: !(Maybe Text) |
|
|
|
, messagePayload :: ![Element] |
|
|
|
, messagePayload :: ![Element] |
|
|
|
} deriving (Eq, Show) |
|
|
|
} deriving (Eq, Show) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
-- | An empty message |
|
|
|
-- | An empty message |
|
|
|
message :: Message |
|
|
|
message :: Message |
|
|
|
message = Message { messageID = Nothing |
|
|
|
message = Message { messageID = Nothing |
|
|
|
@ -272,7 +294,7 @@ data PresenceType = Subscribe | -- ^ Sender wants to subscribe to presence |
|
|
|
data StanzaError = StanzaError |
|
|
|
data StanzaError = StanzaError |
|
|
|
{ stanzaErrorType :: StanzaErrorType |
|
|
|
{ stanzaErrorType :: StanzaErrorType |
|
|
|
, stanzaErrorCondition :: StanzaErrorCondition |
|
|
|
, stanzaErrorCondition :: StanzaErrorCondition |
|
|
|
, stanzaErrorText :: Maybe (Maybe LangTag, Text) |
|
|
|
, stanzaErrorText :: Maybe (Maybe LangTag, NonemptyText) |
|
|
|
, stanzaErrorApplicationSpecificCondition :: Maybe Element |
|
|
|
, stanzaErrorApplicationSpecificCondition :: Maybe Element |
|
|
|
} deriving (Eq, Show) |
|
|
|
} deriving (Eq, Show) |
|
|
|
|
|
|
|
|
|
|
|
@ -291,8 +313,8 @@ data StanzaErrorCondition = BadRequest -- ^ Malformed XML. |
|
|
|
-- name already exists. |
|
|
|
-- name already exists. |
|
|
|
| FeatureNotImplemented |
|
|
|
| FeatureNotImplemented |
|
|
|
| Forbidden -- ^ Insufficient permissions. |
|
|
|
| Forbidden -- ^ Insufficient permissions. |
|
|
|
| Gone (Maybe Text) -- ^ Entity can no longer be |
|
|
|
| Gone (Maybe NonemptyText) -- ^ Entity can no longer |
|
|
|
-- contacted at this |
|
|
|
-- be contacted at this |
|
|
|
-- address. |
|
|
|
-- address. |
|
|
|
| InternalServerError |
|
|
|
| InternalServerError |
|
|
|
| ItemNotFound |
|
|
|
| ItemNotFound |
|
|
|
@ -309,8 +331,9 @@ data StanzaErrorCondition = BadRequest -- ^ Malformed XML. |
|
|
|
-- words that are prohibited |
|
|
|
-- words that are prohibited |
|
|
|
-- by the service) |
|
|
|
-- by the service) |
|
|
|
| RecipientUnavailable -- ^ Temporarily unavailable. |
|
|
|
| RecipientUnavailable -- ^ Temporarily unavailable. |
|
|
|
| Redirect (Maybe Text) -- ^ Redirecting to other |
|
|
|
| Redirect (Maybe NonemptyText) -- ^ Redirecting to |
|
|
|
-- entity, usually |
|
|
|
-- other entity, |
|
|
|
|
|
|
|
-- usually |
|
|
|
-- temporarily. |
|
|
|
-- temporarily. |
|
|
|
| RegistrationRequired |
|
|
|
| RegistrationRequired |
|
|
|
| RemoteServerNotFound |
|
|
|
| RemoteServerNotFound |
|
|
|
@ -484,7 +507,7 @@ data StreamErrorCondition |
|
|
|
-- | Encapsulates information about an XMPP stream error. |
|
|
|
-- | Encapsulates information about an XMPP stream error. |
|
|
|
data StreamErrorInfo = StreamErrorInfo |
|
|
|
data StreamErrorInfo = StreamErrorInfo |
|
|
|
{ errorCondition :: !StreamErrorCondition |
|
|
|
{ errorCondition :: !StreamErrorCondition |
|
|
|
, errorText :: !(Maybe (Maybe LangTag, Text)) |
|
|
|
, errorText :: !(Maybe (Maybe LangTag, NonemptyText)) |
|
|
|
, errorXml :: !(Maybe Element) |
|
|
|
, errorXml :: !(Maybe Element) |
|
|
|
} deriving (Show, Eq) |
|
|
|
} deriving (Show, Eq) |
|
|
|
|
|
|
|
|
|
|
|
@ -645,7 +668,7 @@ data StreamFeatures = StreamFeatures |
|
|
|
-- non-standard "optional" element |
|
|
|
-- non-standard "optional" element |
|
|
|
-- (observed with prosody). |
|
|
|
-- (observed with prosody). |
|
|
|
, streamOtherFeatures :: ![Element] -- TODO: All feature elements instead? |
|
|
|
, streamOtherFeatures :: ![Element] -- TODO: All feature elements instead? |
|
|
|
} deriving Show |
|
|
|
} deriving (Eq, Show) |
|
|
|
|
|
|
|
|
|
|
|
-- | Signals the state of the stream connection. |
|
|
|
-- | Signals the state of the stream connection. |
|
|
|
data ConnectionState |
|
|
|
data ConnectionState |
|
|
|
@ -736,21 +759,23 @@ newtype Stream = Stream { unStream :: TMVar StreamState } |
|
|
|
-- the entity associated with an XMPP localpart at a domain |
|
|
|
-- the entity associated with an XMPP localpart at a domain |
|
|
|
-- (i.e., @localpart\@domainpart/resourcepart@). |
|
|
|
-- (i.e., @localpart\@domainpart/resourcepart@). |
|
|
|
|
|
|
|
|
|
|
|
data Jid = Jid { localpart_ :: !(Maybe Text) |
|
|
|
data Jid = Jid { localpart_ :: !(Maybe NonemptyText) |
|
|
|
, domainpart_ :: !Text |
|
|
|
, domainpart_ :: !NonemptyText |
|
|
|
, resourcepart_ :: !(Maybe Text) |
|
|
|
, resourcepart_ :: !(Maybe NonemptyText) |
|
|
|
} deriving (Eq, Ord) |
|
|
|
} deriving (Eq, Ord) |
|
|
|
|
|
|
|
|
|
|
|
-- | Converts a JID to a Text. |
|
|
|
-- | Converts a JID to a Text. |
|
|
|
jidToText :: Jid -> Text |
|
|
|
jidToText :: Jid -> Text |
|
|
|
jidToText (Jid nd dmn res) = |
|
|
|
jidToText (Jid nd dmn res) = Text.concat . concat $ |
|
|
|
Text.pack $ (maybe "" ((++ "@") . Text.unpack) nd) ++ (Text.unpack dmn) ++ |
|
|
|
[ maybe [] (:["@"]) (text <$> nd) |
|
|
|
maybe "" (('/' :) . Text.unpack) res |
|
|
|
, [text dmn] |
|
|
|
|
|
|
|
, maybe [] (\r -> ["/",r]) (text <$> res) |
|
|
|
|
|
|
|
] |
|
|
|
|
|
|
|
|
|
|
|
-- | Converts a JID to up to three Text values: (the optional) localpart, the |
|
|
|
-- | Converts a JID to up to three Text values: (the optional) localpart, the |
|
|
|
-- domainpart, and (the optional) resourcepart. |
|
|
|
-- domainpart, and (the optional) resourcepart. |
|
|
|
jidToTexts :: Jid -> (Maybe Text, Text, Maybe Text) |
|
|
|
jidToTexts :: Jid -> (Maybe Text, Text, Maybe Text) |
|
|
|
jidToTexts (Jid nd dmn res) = (nd, dmn, res) |
|
|
|
jidToTexts (Jid nd dmn res) = (text <$> nd, text dmn, text <$> res) |
|
|
|
|
|
|
|
|
|
|
|
-- Produces a Jid value in the format "parseJid \"<jid>\"". |
|
|
|
-- Produces a Jid value in the format "parseJid \"<jid>\"". |
|
|
|
instance Show Jid where |
|
|
|
instance Show Jid where |
|
|
|
@ -775,6 +800,17 @@ instance Read Jid where |
|
|
|
-- or the `parseJid' error message (see below) |
|
|
|
-- or the `parseJid' error message (see below) |
|
|
|
|
|
|
|
|
|
|
|
#if WITH_TEMPLATE_HASKELL |
|
|
|
#if WITH_TEMPLATE_HASKELL |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
instance TH.Lift Jid where |
|
|
|
|
|
|
|
lift (Jid lp dp rp) = [| Jid $(mbTextE $ text <$> lp) |
|
|
|
|
|
|
|
$(textE $ text dp) |
|
|
|
|
|
|
|
$(mbTextE $ text <$> rp) |
|
|
|
|
|
|
|
|] |
|
|
|
|
|
|
|
where |
|
|
|
|
|
|
|
textE t = [| Nonempty $ Text.pack $(stringE $ Text.unpack t) |] |
|
|
|
|
|
|
|
mbTextE Nothing = [| Nothing |] |
|
|
|
|
|
|
|
mbTextE (Just s) = [| Just $(textE s) |] |
|
|
|
|
|
|
|
|
|
|
|
-- | Constructs a @Jid@ value at compile time. |
|
|
|
-- | Constructs a @Jid@ value at compile time. |
|
|
|
-- |
|
|
|
-- |
|
|
|
-- Syntax: |
|
|
|
-- Syntax: |
|
|
|
@ -788,18 +824,12 @@ jidQ = QuasiQuoter { quoteExp = \s -> do |
|
|
|
when (Text.last t == ' ') . reportWarning $ "Trailing whitespace in JID " ++ show s |
|
|
|
when (Text.last t == ' ') . reportWarning $ "Trailing whitespace in JID " ++ show s |
|
|
|
case jidFromText t of |
|
|
|
case jidFromText t of |
|
|
|
Nothing -> fail $ "Could not parse JID " ++ s |
|
|
|
Nothing -> fail $ "Could not parse JID " ++ s |
|
|
|
Just j -> [| Jid $(mbTextE $ localpart_ j) |
|
|
|
Just j -> TH.lift j |
|
|
|
$(textE $ domainpart_ j) |
|
|
|
|
|
|
|
$(mbTextE $ resourcepart_ j) |
|
|
|
|
|
|
|
|] |
|
|
|
|
|
|
|
, quotePat = fail "Jid patterns aren't implemented" |
|
|
|
, quotePat = fail "Jid patterns aren't implemented" |
|
|
|
, quoteType = fail "jid QQ can't be used in type context" |
|
|
|
, quoteType = fail "jid QQ can't be used in type context" |
|
|
|
, quoteDec = fail "jid QQ can't be used in declaration context" |
|
|
|
, quoteDec = fail "jid QQ can't be used in declaration context" |
|
|
|
} |
|
|
|
} |
|
|
|
where |
|
|
|
|
|
|
|
textE t = [| Text.pack $(stringE $ Text.unpack t) |] |
|
|
|
|
|
|
|
mbTextE Nothing = [| Nothing |] |
|
|
|
|
|
|
|
mbTextE (Just s) = [| Just $(textE s) |] |
|
|
|
|
|
|
|
#endif |
|
|
|
#endif |
|
|
|
|
|
|
|
|
|
|
|
-- Produces a LangTag value in the format "parseLangTag \"<jid>\"". |
|
|
|
-- Produces a LangTag value in the format "parseLangTag \"<jid>\"". |
|
|
|
@ -874,15 +904,18 @@ jidFromTexts l d r = do |
|
|
|
guard $ validPartLength l'' |
|
|
|
guard $ validPartLength l'' |
|
|
|
let prohibMap = Set.fromList nodeprepExtraProhibitedCharacters |
|
|
|
let prohibMap = Set.fromList nodeprepExtraProhibitedCharacters |
|
|
|
guard $ Text.all (`Set.notMember` prohibMap) l'' |
|
|
|
guard $ Text.all (`Set.notMember` prohibMap) l'' |
|
|
|
return $ Just l'' |
|
|
|
l''' <- nonEmpty l'' |
|
|
|
domainPart <- SP.runStringPrep (SP.namePrepProfile False) d |
|
|
|
return $ Just l''' |
|
|
|
guard $ validDomainPart domainPart |
|
|
|
domainPart' <- SP.runStringPrep (SP.namePrepProfile False) d |
|
|
|
|
|
|
|
guard $ validDomainPart domainPart' |
|
|
|
|
|
|
|
domainPart <- nonEmpty domainPart' |
|
|
|
resourcePart <- case r of |
|
|
|
resourcePart <- case r of |
|
|
|
Nothing -> return Nothing |
|
|
|
Nothing -> return Nothing |
|
|
|
Just r' -> do |
|
|
|
Just r' -> do |
|
|
|
r'' <- SP.runStringPrep resourceprepProfile r' |
|
|
|
r'' <- SP.runStringPrep resourceprepProfile r' |
|
|
|
guard $ validPartLength r'' |
|
|
|
guard $ validPartLength r'' |
|
|
|
return $ Just r'' |
|
|
|
r''' <- nonEmpty r'' |
|
|
|
|
|
|
|
return $ Just r''' |
|
|
|
return $ Jid localPart domainPart resourcePart |
|
|
|
return $ Jid localPart domainPart resourcePart |
|
|
|
where |
|
|
|
where |
|
|
|
validDomainPart :: Text -> Bool |
|
|
|
validDomainPart :: Text -> Bool |
|
|
|
@ -907,15 +940,15 @@ toBare j = j{resourcepart_ = Nothing} |
|
|
|
|
|
|
|
|
|
|
|
-- | Returns the localpart of the @Jid@ (if any). |
|
|
|
-- | Returns the localpart of the @Jid@ (if any). |
|
|
|
localpart :: Jid -> Maybe Text |
|
|
|
localpart :: Jid -> Maybe Text |
|
|
|
localpart = localpart_ |
|
|
|
localpart = fmap text . localpart_ |
|
|
|
|
|
|
|
|
|
|
|
-- | Returns the domainpart of the @Jid@. |
|
|
|
-- | Returns the domainpart of the @Jid@. |
|
|
|
domainpart :: Jid -> Text |
|
|
|
domainpart :: Jid -> Text |
|
|
|
domainpart = domainpart_ |
|
|
|
domainpart = text . domainpart_ |
|
|
|
|
|
|
|
|
|
|
|
-- | Returns the resourcepart of the @Jid@ (if any). |
|
|
|
-- | Returns the resourcepart of the @Jid@ (if any). |
|
|
|
resourcepart :: Jid -> Maybe Text |
|
|
|
resourcepart :: Jid -> Maybe Text |
|
|
|
resourcepart = resourcepart_ |
|
|
|
resourcepart = fmap text . resourcepart_ |
|
|
|
|
|
|
|
|
|
|
|
jidParts :: AP.Parser (Maybe Text, Text, Maybe Text) |
|
|
|
jidParts :: AP.Parser (Maybe Text, Text, Maybe Text) |
|
|
|
jidParts = do |
|
|
|
jidParts = do |
|
|
|
|