git-annex/Logs/Remote/Pure.hs
Joey Hess ce48eb797c
make DropDead transition minimize remote.log for dead sameas remotes
All that needs to be retained in remote.log is the sameas-uuid.
The rest of the config is eliminated. This doesn't save enough space to
bother with, but it prevents anything sensitive in the config of the
dead sameas remote from lingering around.

Note that minimizesameasdead does not update the VectorClock when
changing the log line. That's normally a no-no, but in this case,
it makes each DropDead result in the exact same file contents,
and vector clocks are not needed because the transition breaks
the history chain.
2019-10-15 11:39:25 -04:00

104 lines
3 KiB
Haskell

{- git-annex remote log, pure operations
-
- Copyright 2011-2019 Joey Hess <id@joeyh.name>
-
- Licensed under the GNU AGPL version 3 or higher.
-}
module Logs.Remote.Pure (
calcRemoteConfigMap,
parseRemoteConfigLog,
buildRemoteConfigLog,
keyValToConfig,
configToKeyVal,
showConfig,
prop_isomorphic_configEscape,
prop_parse_show_Config,
) where
import Annex.Common
import Types.Remote
import Logs.UUIDBased
import Annex.SpecialRemote.Config
import qualified Data.ByteString.Lazy as L
import qualified Data.Map as M
import Data.Char
import qualified Data.Attoparsec.ByteString.Lazy as A
import Data.ByteString.Builder
calcRemoteConfigMap :: L.ByteString -> M.Map UUID RemoteConfig
calcRemoteConfigMap = (\m -> M.map (addSameasInherited m) m)
. simpleMap
. parseRemoteConfigLog
parseRemoteConfigLog :: L.ByteString -> Log RemoteConfig
parseRemoteConfigLog = parseLogOld remoteConfigParser
buildRemoteConfigLog :: Log RemoteConfig -> Builder
buildRemoteConfigLog = buildLogOld (byteString . encodeBS . showConfig)
remoteConfigParser :: A.Parser RemoteConfig
remoteConfigParser = keyValToConfig . words . decodeBS <$> A.takeByteString
showConfig :: RemoteConfig -> String
showConfig = unwords . configToKeyVal
{- Given Strings like "key=value", generates a RemoteConfig. -}
keyValToConfig :: [String] -> RemoteConfig
keyValToConfig ws = M.fromList $ map (/=/) ws
where
(/=/) s = (k, v)
where
k = takeWhile (/= '=') s
v = configUnEscape $ drop (1 + length k) s
configToKeyVal :: M.Map String String -> [String]
configToKeyVal m = map toword $ sort $ M.toList m
where
toword (k, v) = k ++ "=" ++ configEscape v
configEscape :: String -> String
configEscape = concatMap escape
where
escape c
| isSpace c || c `elem` "&" = "&" ++ show (ord c) ++ ";"
| otherwise = [c]
configUnEscape :: String -> String
configUnEscape = unescape
where
unescape [] = []
unescape (c:rest)
| c == '&' = entity rest
| otherwise = c : unescape rest
entity s
| not (null num) && ";" `isPrefixOf` r =
chr (Prelude.read num) : unescape rest
| otherwise =
'&' : unescape s
where
num = takeWhile isNumber s
r = drop (length num) s
rest = drop 1 r
{- for quickcheck -}
prop_isomorphic_configEscape :: String -> Bool
prop_isomorphic_configEscape s = s == (configUnEscape . configEscape) s
prop_parse_show_Config :: RemoteConfig -> Bool
prop_parse_show_Config c
-- whitespace and '=' are not supported in config keys
| any (\k -> any isSpace k || elem '=' k) (M.keys c) = True
| any (any excluded) (M.keys c) = True
| any (any excluded) (M.elems c) = True
| otherwise = A.parseOnly remoteConfigParser (encodeBS $ showConfig c) ~~ Right c
where
normalize v = sort . M.toList <$> v
a ~~ b = normalize a == normalize b
-- limit to ascii alphanumerics for simplicity; characters not
-- allowed by the current character set in the config may not
-- round-trip in an identical representation due to the use of the
-- filesystem encoding.
excluded ch = not (isAlphaNum ch) || not (isAscii ch)