summaryrefslogtreecommitdiff
path: root/Presence/IDMangler.hs
blob: 664d4f54d40025ba2204136b2eb78dac59669989 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
---------------------------------------------------------------------------
-- |
-- Module : IDMangler
--
-- This library is useful for generating id attributes for use in an XMPP
-- application.  It conveniently encodes a key value for looking up context and
-- an original id attribute in case of forwarded messages.
--
-- For example, an id attribute with an embedded 'XMPPServer.ConnectionKey'
-- for a forwarded message with an original id attribute of \"purplecfa6168a\"
-- might look something like this:
--
-- > AAAAAAAAAAIBksnqOQiYmtmupcLxbXakI9zcmUl4:purplecfa6168a
--
{-# LANGUAGE OverloadedStrings #-}
module IDMangler
    ( IDMangler
    , newIDMangler
    , generateUniqueID
    , unmangleId
    ) where

import Control.Monad.STM
import Control.Concurrent.STM
import Data.Text (Text)
import qualified Data.Text as Text
import qualified Data.ByteString.Lazy as LazyByteString
import Data.Binary
import qualified Codec.Binary.Base64 as Base64
import Control.Monad
import Data.Monoid ( (<>) )


data IDMangler k
 = IDMangler { idmCounter :: TVar Int }

newIDMangler :: IO (IDMangler k)
newIDMangler = do
    nv <- atomically $ newTVar 0
    return $ IDMangler nv

-- | Use the given state and optional data to generate a unique id attribute
-- suitable for xml.  To recover the optional encoded data, see 'unmangleId'.
generateUniqueID :: Binary k =>
    IDMangler k   -- ^ the state (a counter) for ensuring uniqueness
    -> Maybe k    -- ^ optional recoverable key for context
    -> Maybe Text -- ^ optional recoverable auxilary id attribute
    -> IO Text    -- ^ unique id attribute with encoded data
generateUniqueID mangler mkey mid = do
    n <- atomically $ do
        modifyTVar' (idmCounter mangler) (+1)
        readTVar (idmCounter mangler)
    let bs = encode (n,mkey)
        base64 = Base64.encode (LazyByteString.unpack bs)
        suf = maybe "" (":" <>) mid
    return $ Text.pack base64 <> suf

-- | Recover data from an encoded id attribute.
unmangleId :: Binary k => Text -> (Maybe k, Maybe Text)
unmangleId encoded = (k,mid)
 where
    (e,postcolon) = Text.span (/=':') encoded
    bytes = Base64.decode (Text.unpack e)
    decoded = fmap (decode . LazyByteString.pack) bytes
    k = decoded >>= (\(n,k) -> let _ = n :: Int in k)
    mid = do guard (not . Text.null $ postcolon)
             return $ Text.drop 1 postcolon