File: Address.hs

package info (click to toggle)
haskell-dbus 1.3.3-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 536 kB
  • sloc: haskell: 7,693; xml: 90; makefile: 2
file content (175 lines) | stat: -rw-r--r-- 6,158 bytes parent folder | download | duplicates (2)
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
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
{-# Language LambdaCase #-}
-- Copyright (C) 2009-2012 John Millikin <john@john-millikin.com>
--
-- Licensed under the Apache License, Version 2.0 (the "License");
-- you may not use this file except in compliance with the License.
-- You may obtain a copy of the License at
--
--     http://www.apache.org/licenses/LICENSE-2.0
--
-- Unless required by applicable law or agreed to in writing, software
-- distributed under the License is distributed on an "AS IS" BASIS,
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-- See the License for the specific language governing permissions and
-- limitations under the License.

module DBus.Internal.Address where

import           Data.Char (digitToInt, ord, chr)
import           Data.Maybe (listToMaybe, fromMaybe)
import           Data.List (intercalate)
import qualified Data.Map
import           Data.Map (Map)
import           System.Environment (lookupEnv)
import           Text.Printf (printf)

import           Text.ParserCombinators.Parsec

-- | When a D-Bus server must listen for connections, or a client must connect
-- to a server, the listening socket's configuration is specified with an
-- /address/. An address contains the /method/, which determines the
-- protocol and transport mechanism, and /parameters/, which provide
-- additional method-specific information about the address.
data Address = Address String (Map String String)
    deriving (Eq)

addressMethod :: Address -> String
addressMethod (Address x _ ) = x

addressParameters :: Address -> Map String String
addressParameters (Address _ x) = x

-- | Try to convert a method string and parameter map to an 'Address'.
--
-- Returns 'Nothing' if the method or parameters are invalid.
address :: String -> Map String String -> Maybe Address
address method params = if validMethod method && validParams params
    then if null method && Data.Map.null params
        then Nothing
        else Just (Address method params)
    else Nothing

validMethod :: String -> Bool
validMethod = all validChar where
    validChar c = c /= ';' && c /= ':'

validParams :: Map String String -> Bool
validParams = all validItem . Data.Map.toList where
    validItem (k, v) = notNull k && notNull v && validKey k
    validKey = all validChar
    validChar c = c /= ';' && c /= ',' && c /= '='
    notNull = not . null

optionallyEncoded :: [Char]
optionallyEncoded = concat
    [ ['0'..'9']
    , ['a'..'z']
    , ['A'..'Z']
    , ['-', '_', '/', '\\', '*', '.']
    ]

-- | Convert an address to a string in the format expected by 'parseAddress'.
formatAddress :: Address -> String
formatAddress (Address method params) = concat [method, ":", csvParams] where
    csvParams = intercalate "," $ do
        (k, v) <- Data.Map.toList params
        let v' = concatMap escape v
        return (concat [k, "=", v'])

    escape c = if elem c optionallyEncoded
        then [c]
        else printf "%%%02X" (ord c)

-- | Convert a list of addresses to a string in the format expected by
-- 'parseAddresses'.
formatAddresses :: [Address] -> String
formatAddresses = intercalate ";" . map formatAddress

instance Show Address where
    showsPrec d x = showParen (d > 10) $
        showString "Address " .
        shows (formatAddress x)

-- | Try to parse a string containing one valid address.
--
-- An address string is in the format @method:key1=val1,key2=val2@. There
-- are some limitations on the characters allowed within methods and
-- parameters; see the D-Bus specification for full details.
parseAddress :: String -> Maybe Address
parseAddress = maybeParseString $ do
    addr <- parsecAddress
    eof
    return addr

-- | Try to parse a string containing one or more valid addresses.
--
-- Addresses are separated by semicolons. See 'parseAddress' for the format
-- of addresses.
parseAddresses :: String -> Maybe [Address]
parseAddresses = maybeParseString $ do
    addrs <- sepEndBy parsecAddress (char ';')
    eof
    return addrs

parsecAddress :: Parser Address
parsecAddress = p where
    p = do
        method <- many (noneOf ":;")
        _ <- char ':'
        params <- sepEndBy param (char ',')
        return (Address method (Data.Map.fromList params))

    param = do
        key <- many1 (noneOf "=;,")
        _ <- char '='
        value <- many1 valueChar
        return (key, value)

    valueChar = encoded <|> unencoded
    encoded = do
        _ <- char '%'
        hex <- count 2 hexDigit
        return (chr (hexToInt hex))
    unencoded = oneOf optionallyEncoded

-- | Returns the address in the environment variable
-- @DBUS_SYSTEM_BUS_ADDRESS@, or
-- @unix:path=\/var\/run\/dbus\/system_bus_socket@ if @DBUS_SYSTEM_BUS_ADDRESS@
-- is not set.
--
-- Returns 'Nothing' if @DBUS_SYSTEM_BUS_ADDRESS@ contains an invalid address.
getSystemAddress :: IO (Maybe Address)
getSystemAddress = do
    let system = "unix:path=/var/run/dbus/system_bus_socket"
    env <- lookupEnv "DBUS_SYSTEM_BUS_ADDRESS"
    return (parseAddress (fromMaybe system env))

-- | Returns the first address in the environment variable
-- @DBUS_SESSION_BUS_ADDRESS@, which must be set.
--
-- Returns 'Nothing' if @DBUS_SYSTEM_BUS_ADDRESS@ contains an invalid address
-- or @DBUS_SESSION_BUS_ADDRESS@ is unset @XDG_RUNTIME_DIR@ doesn't have @/bus@.
getSessionAddress :: IO (Maybe Address)
getSessionAddress = lookupEnv "DBUS_SESSION_BUS_ADDRESS" >>= \case
    Just addrs -> pure (parseAddresses addrs >>= listToMaybe)
    Nothing -> (>>= parseFallback) <$> lookupEnv "XDG_RUNTIME_DIR"
  where
    parseFallback dir = parseAddress ("unix:path=" ++ dir ++ "/bus")

-- | Returns the address in the environment variable
-- @DBUS_STARTER_ADDRESS@, which must be set.
--
-- Returns 'Nothing' if @DBUS_STARTER_ADDRESS@ is unset or contains an
-- invalid address.
getStarterAddress :: IO (Maybe Address)
getStarterAddress = do
    env <- lookupEnv "DBUS_STARTER_ADDRESS"
    return (env >>= parseAddress)

hexToInt :: String -> Int
hexToInt = foldl ((+) . (16 *)) 0 . map digitToInt

maybeParseString :: Parser a -> String -> Maybe a
maybeParseString p str = case runParser p () "" str of
    Left _ -> Nothing
    Right a -> Just a