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.
202 lines
8.3 KiB
202 lines
8.3 KiB
|
14 years ago
|
{-# OPTIONS_HADDOCK hide #-}
|
||
|
|
|
||
|
14 years ago
|
-- This module deals with JIDs, also known as XMPP addresses. For more
|
||
|
14 years ago
|
-- information on JIDs, see RFC 6122: XMPP: Address Format.
|
||
|
|
|
||
|
14 years ago
|
module Network.Xmpp.JID
|
||
|
14 years ago
|
( JID(..)
|
||
|
|
, fromText
|
||
|
|
, fromStrings
|
||
|
|
, isBare
|
||
|
|
, isFull
|
||
|
|
) where
|
||
|
14 years ago
|
|
||
|
14 years ago
|
import Control.Applicative ((<$>),(<|>))
|
||
|
|
import Control.Monad(guard)
|
||
|
14 years ago
|
|
||
|
14 years ago
|
import qualified Data.Attoparsec.Text as AP
|
||
|
14 years ago
|
import Data.Maybe(fromJust)
|
||
|
|
import qualified Data.Set as Set
|
||
|
|
import Data.String (IsString(..))
|
||
|
14 years ago
|
import Data.Text (Text)
|
||
|
|
import qualified Data.Text as Text
|
||
|
|
import qualified Text.NamePrep as SP
|
||
|
|
import qualified Text.StringPrep as SP
|
||
|
14 years ago
|
|
||
|
14 years ago
|
data JID = JID { -- | The @localpart@ of a JID is an optional identifier placed
|
||
|
|
-- before the domainpart and separated from the latter by a
|
||
|
|
-- \'\@\' character. Typically a localpart uniquely identifies
|
||
|
|
-- the entity requesting and using network access provided by a
|
||
|
|
-- server (i.e., a local account), although it can also
|
||
|
|
-- represent other kinds of entities (e.g., a chat room
|
||
|
|
-- associated with a multi-user chat service). The entity
|
||
|
|
-- represented by an XMPP localpart is addressed within the
|
||
|
|
-- context of a specific domain (i.e.,
|
||
|
|
-- @localpart\@domainpart@).
|
||
|
14 years ago
|
localpart :: !(Maybe Text)
|
||
|
14 years ago
|
|
||
|
14 years ago
|
-- | The domainpart typically identifies the /home/ server to
|
||
|
|
-- which clients connect for XML routing and data management
|
||
|
|
-- functionality. However, it is not necessary for an XMPP
|
||
|
|
-- domainpart to identify an entity that provides core XMPP
|
||
|
|
-- server functionality (e.g., a domainpart can identify an
|
||
|
|
-- entity such as a multi-user chat service, a
|
||
|
|
-- publish-subscribe service, or a user directory).
|
||
|
14 years ago
|
, domainpart :: !Text
|
||
|
14 years ago
|
|
||
|
14 years ago
|
-- | The resourcepart of a JID is an optional identifier placed
|
||
|
|
-- after the domainpart and separated from the latter by the
|
||
|
|
-- \'\/\' character. A resourcepart can modify either a
|
||
|
|
-- @localpart\@domainpart@ address or a mere @domainpart@
|
||
|
|
-- address. Typically a resourcepart uniquely identifies a
|
||
|
|
-- specific connection (e.g., a device or location) or object
|
||
|
|
-- (e.g., an occupant in a multi-user chat room) belonging to
|
||
|
|
-- the entity associated with an XMPP localpart at a domain
|
||
|
|
-- (i.e., @localpart\@domainpart/resourcepart@).
|
||
|
14 years ago
|
, resourcepart :: !(Maybe Text)
|
||
|
14 years ago
|
} deriving Eq
|
||
|
14 years ago
|
|
||
|
|
instance Show JID where
|
||
|
|
show (JID nd dmn res) =
|
||
|
14 years ago
|
maybe "" ((++ "@") . Text.unpack) nd ++ Text.unpack dmn ++
|
||
|
|
maybe "" (('/' :) . Text.unpack) res
|
||
|
14 years ago
|
|
||
|
|
instance Read JID where
|
||
|
|
readsPrec _ x = case fromText (Text.pack x) of
|
||
|
14 years ago
|
Nothing -> []
|
||
|
|
Just j -> [(j,"")]
|
||
|
14 years ago
|
|
||
|
|
instance IsString JID where
|
||
|
|
fromString = fromJust . fromText . Text.pack
|
||
|
|
|
||
|
14 years ago
|
-- | Converts a Text to a JID.
|
||
|
14 years ago
|
fromText :: Text -> Maybe JID
|
||
|
|
fromText t = do
|
||
|
14 years ago
|
(l, d, r) <- eitherToMaybe $ AP.parseOnly jidParts t
|
||
|
|
fromStrings l d r
|
||
|
14 years ago
|
where
|
||
|
|
eitherToMaybe = either (const Nothing) Just
|
||
|
|
|
||
|
14 years ago
|
-- | Converts localpart, domainpart, and resourcepart strings to a JID. Runs the
|
||
|
|
-- appropriate stringprep profiles and validates the parts.
|
||
|
14 years ago
|
fromStrings :: Maybe Text -> Text -> Maybe Text -> Maybe JID
|
||
|
|
fromStrings l d r = do
|
||
|
14 years ago
|
localPart <- case l of
|
||
|
|
Nothing -> return Nothing
|
||
|
|
Just l'-> do
|
||
|
14 years ago
|
l'' <- SP.runStringPrep nodeprepProfile l'
|
||
|
|
guard $ validPartLength l''
|
||
|
14 years ago
|
let prohibMap = Set.fromList nodeprepExtraProhibitedCharacters
|
||
|
|
guard $ Text.all (`Set.notMember` prohibMap) l''
|
||
|
14 years ago
|
return $ Just l''
|
||
|
14 years ago
|
domainPart <- SP.runStringPrep (SP.namePrepProfile False) d
|
||
|
|
guard $ validDomainPart domainPart
|
||
|
|
resourcePart <- case r of
|
||
|
|
Nothing -> return Nothing
|
||
|
|
Just r' -> do
|
||
|
14 years ago
|
r'' <- SP.runStringPrep resourceprepProfile r'
|
||
|
|
guard $ validPartLength r''
|
||
|
|
return $ Just r''
|
||
|
14 years ago
|
return $ JID localPart domainPart resourcePart
|
||
|
14 years ago
|
where
|
||
|
14 years ago
|
validDomainPart :: Text -> Bool
|
||
|
14 years ago
|
validDomainPart _s = True -- TODO
|
||
|
14 years ago
|
|
||
|
14 years ago
|
validPartLength :: Text -> Bool
|
||
|
|
validPartLength p = Text.length p > 0 && Text.length p < 1024
|
||
|
14 years ago
|
|
||
|
14 years ago
|
-- | Returns True if the JID is /bare/, and False otherwise.
|
||
|
14 years ago
|
isBare :: JID -> Bool
|
||
|
|
isBare j | resourcepart j == Nothing = True
|
||
|
|
| otherwise = False
|
||
|
|
|
||
|
14 years ago
|
-- | Returns True if the JID is 'full', and False otherwise.
|
||
|
14 years ago
|
isFull :: JID -> Bool
|
||
|
14 years ago
|
isFull = not . isBare
|
||
|
14 years ago
|
|
||
|
14 years ago
|
-- Parses an JID string and returns its three parts. It performs no validation
|
||
|
|
-- or transformations.
|
||
|
14 years ago
|
jidParts :: AP.Parser (Maybe Text, Text, Maybe Text)
|
||
|
14 years ago
|
jidParts = do
|
||
|
14 years ago
|
-- Read until we reach an '@', a '/', or EOF.
|
||
|
|
a <- AP.takeWhile1 (AP.notInClass ['@', '/'])
|
||
|
|
-- Case 1: We found an '@', and thus the localpart. At least the domainpart
|
||
|
|
-- is remaining. Read the '@' and until a '/' or EOF.
|
||
|
14 years ago
|
do
|
||
|
14 years ago
|
b <- domainPartP
|
||
|
|
-- Case 1A: We found a '/' and thus have all the JID parts. Read the '/'
|
||
|
|
-- and until EOF.
|
||
|
|
do
|
||
|
|
c <- resourcePartP -- Parse resourcepart
|
||
|
|
return (Just a, b, Just c)
|
||
|
|
-- Case 1B: We have reached EOF; the JID is in the form
|
||
|
|
-- localpart@domainpart.
|
||
|
|
<|> do
|
||
|
|
AP.endOfInput
|
||
|
|
return (Just a, b, Nothing)
|
||
|
|
-- Case 2: We found a '/'; the JID is in the form
|
||
|
|
-- domainpart/resourcepart.
|
||
|
|
<|> do
|
||
|
|
b <- resourcePartP
|
||
|
|
AP.endOfInput
|
||
|
|
return (Nothing, a, Just b)
|
||
|
|
-- Case 3: We have reached EOF; we have an JID consisting of only a
|
||
|
|
-- domainpart.
|
||
|
|
<|> do
|
||
|
|
AP.endOfInput
|
||
|
|
return (Nothing, a, Nothing)
|
||
|
14 years ago
|
where
|
||
|
14 years ago
|
-- Read an '@' and everything until a '/'.
|
||
|
|
domainPartP :: AP.Parser Text
|
||
|
14 years ago
|
domainPartP = do
|
||
|
14 years ago
|
_ <- AP.char '@'
|
||
|
|
AP.takeWhile1 (/= '/')
|
||
|
|
-- Read everything until a '/'.
|
||
|
|
resourcePartP :: AP.Parser Text
|
||
|
14 years ago
|
resourcePartP = do
|
||
|
14 years ago
|
_ <- AP.char '/'
|
||
|
|
AP.takeText
|
||
|
14 years ago
|
|
||
|
14 years ago
|
-- The `nodeprep' StringPrep profile.
|
||
|
14 years ago
|
nodeprepProfile :: SP.StringPrepProfile
|
||
|
14 years ago
|
nodeprepProfile = SP.Profile { SP.maps = [SP.b1, SP.b2]
|
||
|
14 years ago
|
, SP.shouldNormalize = True
|
||
|
|
, SP.prohibited = [SP.a1
|
||
|
14 years ago
|
, SP.c11
|
||
|
|
, SP.c12
|
||
|
|
, SP.c21
|
||
|
|
, SP.c22
|
||
|
|
, SP.c3
|
||
|
|
, SP.c4
|
||
|
|
, SP.c5
|
||
|
|
, SP.c6
|
||
|
|
, SP.c7
|
||
|
|
, SP.c8
|
||
|
|
, SP.c9
|
||
|
|
]
|
||
|
14 years ago
|
, SP.shouldCheckBidi = True
|
||
|
|
}
|
||
|
14 years ago
|
|
||
|
14 years ago
|
-- These characters needs to be checked for after normalization.
|
||
|
14 years ago
|
nodeprepExtraProhibitedCharacters :: [Char]
|
||
|
14 years ago
|
nodeprepExtraProhibitedCharacters = ['\x22', '\x26', '\x27', '\x2F', '\x3A',
|
||
|
|
'\x3C', '\x3E', '\x40']
|
||
|
14 years ago
|
|
||
|
14 years ago
|
-- The `resourceprep' StringPrep profile.
|
||
|
14 years ago
|
resourceprepProfile :: SP.StringPrepProfile
|
||
|
14 years ago
|
resourceprepProfile = SP.Profile { SP.maps = [SP.b1]
|
||
|
14 years ago
|
, SP.shouldNormalize = True
|
||
|
|
, SP.prohibited = [ SP.a1
|
||
|
14 years ago
|
, SP.c12
|
||
|
|
, SP.c21
|
||
|
|
, SP.c22
|
||
|
|
, SP.c3
|
||
|
|
, SP.c4
|
||
|
|
, SP.c5
|
||
|
|
, SP.c6
|
||
|
|
, SP.c7
|
||
|
|
, SP.c8
|
||
|
|
, SP.c9
|
||
|
|
]
|
||
|
14 years ago
|
, SP.shouldCheckBidi = True
|
||
|
14 years ago
|
}
|