dbus-1.2.10: lib/DBus/Internal/Address.hs
-- 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 qualified Control.Exception
import Data.Char (digitToInt, ord, chr)
import Data.Maybe (listToMaybe)
import Data.List (intercalate)
import qualified Data.Map
import Data.Map (Map)
import qualified System.Environment
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 <- getenv "DBUS_SYSTEM_BUS_ADDRESS"
return (parseAddress (maybe system id env))
-- | Returns the first address in the environment variable
-- @DBUS_SESSION_BUS_ADDRESS@, which must be set.
--
-- Returns 'Nothing' if @DBUS_SYSTEM_BUS_ADDRESS@ is unset or contains an
-- invalid address.
getSessionAddress :: IO (Maybe Address)
getSessionAddress = do
env <- getenv "DBUS_SESSION_BUS_ADDRESS"
return (env >>= parseAddresses >>= listToMaybe)
-- | 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 <- getenv "DBUS_STARTER_ADDRESS"
return (env >>= parseAddress)
getenv :: String -> IO (Maybe String)
getenv name = Control.Exception.catch
(fmap Just (System.Environment.getEnv name))
(\(Control.Exception.SomeException _) -> return Nothing)
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