Browse Source

work in JID.hs

master
Philipp Balzarek 14 years ago
parent
commit
113ca4034f
  1. 232
      src/Network/XMPP/JID.hs

232
src/Network/XMPP/JID.hs

@ -15,183 +15,137 @@
-- --
-- This module does not internationalize hostnames. -- This module does not internationalize hostnames.
module Network.XMPP.JID (fromString, fromStrings, isBare, isFull) where module Network.XMPP.JID (fromString, fromStrings, isBare, isFull) where
import Network.XMPP.Types import Control.Applicative ((<$>),(<|>))
import Control.Monad(guard)
import Data.Maybe (fromJust, isJust)
import Text.Parsec ((<|>), anyToken, char, eof, many, noneOf, parse)
import Text.Parsec.ByteString (GenParser)
import Text.StringPrep (StringPrepProfile (..), a1, b1, b2, c11, c12, c21, c22,
c3, c4, c5, c6, c7, c8, c9, runStringPrep)
import Text.NamePrep (namePrepProfile)
import Network.URI (isIPv4address, isIPv6address) import qualified Data.Attoparsec.Text as AP
import Data.Text (Text)
import qualified Data.Text as Text
import qualified Data.ByteString.Char8 as DBC (pack) -- import Network.URI (isIPv4address, isIPv6address)
import qualified Data.Text as DT (pack, unpack) import Network.XMPP.Types
import qualified Text.NamePrep as SP
import qualified Text.StringPrep as SP
-- | -- |
-- Converts a string to a JID. -- Converts a string to a JID.
fromString :: String -> Maybe JID fromString :: String -> Maybe JID
fromString s = fromStrings l d r
fromString s = fromStrings localpart domainpart resourcepart
where where
Right (localpart, domainpart, resourcepart) = Right (l, d, r) =
parse jidParts "" (DBC.pack s) AP.parseOnly jidParts (Text.pack s)
-- | -- |
-- Converts localpart, domainpart, and resourcepart strings to a JID. -- Converts localpart, domainpart, and resourcepart strings to a JID.
-- Runs the appropriate stringprep profiles and validates the parts. -- Runs the appropriate stringprep profiles and validates the parts.
fromStrings :: Maybe Text -> Text -> Maybe Text -> Maybe JID
fromStrings :: Maybe String -> String -> Maybe String -> Maybe JID fromStrings l d r = do
localPart <- case l of
fromStrings l s r Nothing -> return Nothing
| domainpart == Nothing = Nothing Just l'-> do
| otherwise = if validateNonDomainpart localpart && l'' <- SP.runStringPrep nodeprepProfile l'
isJust domainpart' && guard $ validPartLength l''
validateNonDomainpart resourcepart return $ Just l''
then Just (JID localpart (fromJust domainpart') resourcepart) domainPart <- SP.runStringPrep (SP.namePrepProfile False) d
else Nothing guard $ validDomainPart domainPart
resourcePart <- case r of
Nothing -> return Nothing
Just r' -> do
r'' <- SP.runStringPrep resourceprepProfile r'
guard $ validPartLength r''
return $ Just r''
return $ JID localPart domainPart resourcePart
where where
-- Applies the nodeprep profile on the localpart string, if any.
localpart :: Maybe String
localpart = case l of
Just l' -> case runStringPrep nodeprepProfile (DT.pack l') of
Just l'' -> Just $ DT.unpack l''
Nothing -> Nothing
Nothing -> Nothing
-- Applies the nameprep profile on the domainpart string.
-- TODO: Allow unassigned?
domainpart :: Maybe String
domainpart = case runStringPrep (namePrepProfile False) (DT.pack s) of
Just s' -> Just $ DT.unpack s'
Nothing -> Nothing
-- Applies the resourceprep profile on the resourcepart string, if
-- any.
resourcepart :: Maybe String
resourcepart = case r of
Just r' -> case runStringPrep resourceprepProfile (DT.pack r') of
Just r'' -> Just $ DT.unpack r''
Nothing -> Nothing
Nothing -> Nothing
-- Returns the domainpart if it was a valid IP or if the toASCII -- Returns the domainpart if it was a valid IP or if the toASCII
-- function was successful, or Nothing otherwise. -- function was successful, or Nothing otherwise.
domainpart' :: Maybe String validDomainPart _s = True -- TODO
domainpart' | isIPv4address s || isIPv6address s = Just s -- isIPv4address s || isIPv6address s || validHostname s
| validHostname s = Just s
| otherwise = Nothing
-- Validates that non-domainpart strings have an appropriate
-- length.
validateNonDomainpart :: Maybe String -> Bool
validateNonDomainpart Nothing = True
validateNonDomainpart (Just l) = validPartLength l
where
validPartLength :: String -> Bool
validPartLength p = length p > 0 && length p < 1024
validPartLength :: Text -> Bool
validPartLength p = Text.length p > 0 && Text.length p < 1024
-- Validates a host name -- Validates a host name
validHostname :: String -> Bool -- validHostname :: Text -> Bool
validHostname _ = True -- TODO -- validHostname _ = True -- TODO
-- | Returns True if the JID is `bare', and False otherwise. -- | Returns True if the JID is `bare', and False otherwise.
isBare :: JID -> Bool isBare :: JID -> Bool
isBare j | resourcepart j == Nothing = True isBare j | resourcepart j == Nothing = True
| otherwise = False | otherwise = False
-- | Returns True if the JID is `full', and False otherwise. -- | Returns True if the JID is `full', and False otherwise.
isFull :: JID -> Bool isFull :: JID -> Bool
isFull jid = not $ isBare jid isFull jid = not $ isBare jid
-- Parses an JID string and returns its three parts. It performs no -- Parses an JID string and returns its three parts. It performs no
-- validation or transformations. We are using Parsec to parse the -- validation or transformations. We are using Parsec to parse the
-- JIDs. There is no input for which 'jidParts' fails. -- JIDs. There is no input for which 'jidParts' fails.
jidParts :: AP.Parser (Maybe Text, Text, Maybe Text)
jidParts :: GenParser Char st (Maybe String, String, Maybe String)
jidParts = do jidParts = do
a <- firstPartP
-- Read until we reach an '@', a '/', or EOF. b <- Just <$> domainPartP <|> (return Nothing)
a <- many $ noneOf ['@', '/'] c <- Just <$> resourcePartP <|> (return Nothing)
case (a,b,c) of
-- Case 1: We found an '@', and thus the localpart. At least the -- Whether or not we have a resource part, if there is no "@"
-- domainpart is remaining. Read the '@' and until a '/' or EOF. -- x is the domain
do (x, Nothing, z) -> return (Nothing, x, z)
char '@' -- When we do have an "@", x is the localpart
b <- many $ noneOf ['/'] (x, Just y, z) -> return (Just x, y, z)
where
-- Case 1A: We found a '/' and thus have all the JID parts. Read firstPartP = AP.takeWhile1 (AP.notInClass ['@', '/'])
-- the '/' and until EOF. domainPartP = do
do _ <- AP.char '@'
char '/' -- Resourcepart remaining AP.takeWhile1 (/= '/')
c <- many $ anyToken -- Parse resourcepart resourcePartP = do
eof _ <- AP.char '/'
return (Just a, b, Just c) AP.takeText
-- Case 1B: We have reached EOF; the JID is in the form
-- localpart@domainpart. nodeprepProfile :: SP.StringPrepProfile
<|> do nodeprepProfile = SP.Profile
eof { SP.maps = [SP.b1, SP.b2]
return (Just a, b, Nothing) , SP.shouldNormalize = True
, SP.prohibited = [SP.a1
-- Case 2: We found a '/'; the JID is in the form , SP.c3
-- domainpart/resourcepart. , SP.c4
<|> do , SP.c5
char '/' , SP.c6
b <- many $ anyToken , SP.c7
eof , SP.c8
return (Nothing, a, Just b) , SP.c9
, SP.c11
-- Case 3: We have reached EOF; we have an JID consisting of only , SP.c12
-- a domainpart. , SP.c21
<|> do , SP.c22
eof ]
return (Nothing, a, Nothing) , SP.shouldCheckBidi = True
}
nodeprepProfile :: StringPrepProfile
nodeprepProfile = Profile { maps = [b1, b2]
, shouldNormalize = True
, prohibited = [a1] ++ [c11, c12, c21, c22,
c3, c4, c5, c6, c7,
c8, c9]
, shouldCheckBidi = True }
-- These needs to be checked for after normalization. We could also -- These needs to be checked for after normalization. We could also
-- look up the Unicode mappings and include a list of characters in -- look up the Unicode mappings and include a list of characters in
-- the prohibited field above. Let's defer that until we know that we -- the prohibited field above. Let's defer that until we know that we
-- are going to use stringprep. -- are going to use stringprep.
nodeprepExtraProhibitedCharacters :: [Char]
nodeprepExtraProhibitedCharacters = ['\x22', '\x26', '\x27', '\x2F', nodeprepExtraProhibitedCharacters = ['\x22', '\x26', '\x27', '\x2F',
'\x3A', '\x3C', '\x3E', '\x40'] '\x3A', '\x3C', '\x3E', '\x40']
resourceprepProfile :: SP.StringPrepProfile
resourceprepProfile = SP.Profile
resourceprepProfile :: StringPrepProfile { SP.maps = [SP.b1]
, SP.shouldNormalize = True
resourceprepProfile = Profile { maps = [b1] , SP.prohibited = [ SP.a1
, shouldNormalize = True , SP.c3
, prohibited = [a1] ++ [c12, c21, c22, , SP.c4
c3, c4, c5, c6, , SP.c5
c7, c8, c9] , SP.c6
, shouldCheckBidi = True } , SP.c7
, SP.c8
, SP.c9
, SP.c12
, SP.c21
, SP.c22
]
, SP.shouldCheckBidi = True
}

Loading…
Cancel
Save