simplify base64 to only use ByteString

Note the use of fromString and toString from Data.ByteString.UTF8 dated
back to commit 9b93278e8a. Back then it
was using the dataenc package for base64, which operated on Word8 and
String. But with the switch to sandi, it uses ByteString, and indeed
fromB64' and toB64' were already using ByteString without that
complication. So I think there is no risk of such an encoding related
breakage.

I also tested the case that 9b93278e8a
fixed:

	git-annex metadata -s foo='a …' x
	git-annex metadata x
	metadata x
	  foo=a …

In Remote.Helper.Encryptable, it was avoiding using Utility.Base64
because of that UTF8 conversion. Since that's no longer done, it can
just use it now.
This commit is contained in:
Joey Hess 2023-10-26 12:42:32 -04:00
parent 985dd38847
commit 3742263c99
No known key found for this signature in database
GPG key ID: DB12DB0FF05F8F38
7 changed files with 27 additions and 71 deletions

View file

@ -38,14 +38,14 @@ toTaggedBranch :: UUID -> Maybe String -> Git.Branch -> Git.Ref
toTaggedBranch u info b = Git.Ref $ S.intercalate "/" $ catMaybes toTaggedBranch u info b = Git.Ref $ S.intercalate "/" $ catMaybes
[ Just "refs/synced" [ Just "refs/synced"
, Just $ fromUUID u , Just $ fromUUID u
, toB64' . encodeBS <$> info , toB64 . encodeBS <$> info
, Just $ Git.fromRef' $ Git.Ref.base b , Just $ Git.fromRef' $ Git.Ref.base b
] ]
fromTaggedBranch :: Git.Ref -> Maybe (UUID, Maybe String) fromTaggedBranch :: Git.Ref -> Maybe (UUID, Maybe S.ByteString)
fromTaggedBranch b = case splitc '/' $ Git.fromRef b of fromTaggedBranch b = case splitc '/' $ Git.fromRef b of
("refs":"synced":u:info:_base) -> ("refs":"synced":u:info:_base) ->
Just (toUUID u, fromB64Maybe info) Just (toUUID u, fromB64Maybe (encodeBS info))
("refs":"synced":u:_base) -> ("refs":"synced":u:_base) ->
Just (toUUID u, Nothing) Just (toUUID u, Nothing)
_ -> Nothing _ -> Nothing

View file

@ -100,10 +100,10 @@ setRemoteCredPair' pc encsetup gc storage mcreds = case mcreds of
cmd <- gpgCmd <$> Annex.getGitConfig cmd <- gpgCmd <$> Annex.getGitConfig
s <- liftIO $ encrypt cmd (pc, gc) cipher s <- liftIO $ encrypt cmd (pc, gc) cipher
(feedBytes $ L.pack $ encodeCredPair creds) (feedBytes $ L.pack $ encodeCredPair creds)
(readBytesStrictly $ return . S.unpack) (readBytesStrictly return)
storeconfig' key (Accepted (toB64 s)) storeconfig' key (Accepted (decodeBS (toB64 s)))
storeconfig creds key Nothing = storeconfig creds key Nothing =
storeconfig' key (Accepted (toB64 $ encodeCredPair creds)) storeconfig' key (Accepted (decodeBS $ toB64 $ encodeBS $ encodeCredPair creds))
storeconfig' key val = return $ pc storeconfig' key val = return $ pc
{ parsedRemoteConfigMap = M.insert key (RemoteConfigValue val) (parsedRemoteConfigMap pc) { parsedRemoteConfigMap = M.insert key (RemoteConfigValue val) (parsedRemoteConfigMap pc)
@ -129,13 +129,13 @@ getRemoteCredPair c gc storage = maybe fromcache (return . Just) =<< fromenv
case (getval, mcipher) of case (getval, mcipher) of
(Nothing, _) -> return Nothing (Nothing, _) -> return Nothing
(Just enccreds, Just (cipher, storablecipher)) -> (Just enccreds, Just (cipher, storablecipher)) ->
fromenccreds enccreds cipher storablecipher fromenccreds (encodeBS enccreds) cipher storablecipher
(Just bcreds, Nothing) -> (Just bcreds, Nothing) ->
fromcreds $ fromB64 bcreds fromcreds $ decodeBS $ fromB64 $ encodeBS bcreds
fromenccreds enccreds cipher storablecipher = do fromenccreds enccreds cipher storablecipher = do
cmd <- gpgCmd <$> Annex.getGitConfig cmd <- gpgCmd <$> Annex.getGitConfig
mcreds <- liftIO $ catchMaybeIO $ decrypt cmd (c, gc) cipher mcreds <- liftIO $ catchMaybeIO $ decrypt cmd (c, gc) cipher
(feedBytes $ L.pack $ fromB64 enccreds) (feedBytes $ L.fromStrict $ fromB64 enccreds)
(readBytesStrictly $ return . S.unpack) (readBytesStrictly $ return . S.unpack)
case mcreds of case mcreds of
Just creds -> fromcreds creds Just creds -> fromcreds creds
@ -146,7 +146,7 @@ getRemoteCredPair c gc storage = maybe fromcache (return . Just) =<< fromenv
case storablecipher of case storablecipher of
SharedCipher {} -> showLongNote "gpg error above was caused by an old git-annex bug in credentials storage. Working around it.." SharedCipher {} -> showLongNote "gpg error above was caused by an old git-annex bug in credentials storage. Working around it.."
_ -> giveup "*** Insecure credentials storage detected for this remote! See https://git-annex.branchable.com/upgrades/insecure_embedded_creds/" _ -> giveup "*** Insecure credentials storage detected for this remote! See https://git-annex.branchable.com/upgrades/insecure_embedded_creds/"
fromcreds $ fromB64 enccreds fromcreds $ decodeBS $ fromB64 enccreds
fromcreds creds = case decodeCredPair creds of fromcreds creds = case decodeCredPair creds of
Just credpair -> do Just credpair -> do
writeCacheCredPair credpair storage writeCacheCredPair credpair storage

View file

@ -41,7 +41,7 @@ buildContentIdentifierList l = case l of
where where
buildcid (ContentIdentifier c) buildcid (ContentIdentifier c)
| S8.any (`elem` [':', '\r', '\n']) c || "!" `S8.isPrefixOf` c = | S8.any (`elem` [':', '\r', '\n']) c || "!" `S8.isPrefixOf` c =
charUtf8 '!' <> byteString (toB64' c) charUtf8 '!' <> byteString (toB64 c)
| otherwise = byteString c | otherwise = byteString c
go [] = mempty go [] = mempty
go (c:[]) = buildcid c go (c:[]) = buildcid c
@ -58,7 +58,7 @@ parseContentIdentifierList = do
cidparser = do cidparser = do
b <- A8.takeWhile (/= ':') b <- A8.takeWhile (/= ':')
return $ if "!" `S8.isPrefixOf` b return $ if "!" `S8.isPrefixOf` b
then ContentIdentifier $ fromMaybe b (fromB64Maybe' (S.drop 1 b)) then ContentIdentifier $ fromMaybe b (fromB64Maybe (S.drop 1 b))
else ContentIdentifier b else ContentIdentifier b
listparser first rest = ifM A8.atEnd listparser first rest = ifM A8.atEnd
( return (first :| reverse rest) ( return (first :| reverse rest)

View file

@ -28,8 +28,6 @@ module Remote.Helper.Encryptable (
import qualified Data.Map as M import qualified Data.Map as M
import qualified Data.Set as S import qualified Data.Set as S
import qualified "sandi" Codec.Binary.Base64 as B64
import qualified Data.ByteString as B
import Control.Concurrent.STM import Control.Concurrent.STM
import Annex.Common import Annex.Common
@ -39,6 +37,7 @@ import Types.Crypto
import Types.ProposedAccepted import Types.ProposedAccepted
import qualified Annex import qualified Annex
import Annex.SpecialRemote.Config import Annex.SpecialRemote.Config
import Utility.Base64
-- Used to ensure that encryption has been set up before trying to -- Used to ensure that encryption has been set up before trying to
-- eg, store creds in the remote config that would need to use the -- eg, store creds in the remote config that would need to use the
@ -272,7 +271,7 @@ storeCipher cip = case cip of
(EncryptedCipher t _ ks) -> addcipher t . storekeys ks cipherkeysField (EncryptedCipher t _ ks) -> addcipher t . storekeys ks cipherkeysField
(SharedPubKeyCipher t ks) -> addcipher t . storekeys ks pubkeysField (SharedPubKeyCipher t ks) -> addcipher t . storekeys ks pubkeysField
where where
addcipher t = M.insert cipherField (Accepted (toB64bs t)) addcipher t = M.insert cipherField (Accepted (decodeBS (toB64 (encodeBS t))))
storekeys (KeyIds l) n = M.insert n (Accepted (intercalate "," l)) storekeys (KeyIds l) n = M.insert n (Accepted (intercalate "," l))
{- Extracts an StorableCipher from a remote's configuration. -} {- Extracts an StorableCipher from a remote's configuration. -}
@ -281,13 +280,13 @@ extractCipher c = case (getRemoteConfigValue cipherField c,
(getRemoteConfigValue cipherkeysField c <|> getRemoteConfigValue pubkeysField c), (getRemoteConfigValue cipherkeysField c <|> getRemoteConfigValue pubkeysField c),
getRemoteConfigValue encryptionField c) of getRemoteConfigValue encryptionField c) of
(Just t, Just ks, Just HybridEncryption) -> (Just t, Just ks, Just HybridEncryption) ->
Just $ EncryptedCipher (fromB64bs t) Hybrid (readkeys ks) Just $ EncryptedCipher (decodeBS (fromB64 (encodeBS t))) Hybrid (readkeys ks)
(Just t, Just ks, Just PubKeyEncryption) -> (Just t, Just ks, Just PubKeyEncryption) ->
Just $ EncryptedCipher (fromB64bs t) PubKey (readkeys ks) Just $ EncryptedCipher (decodeBS (fromB64 (encodeBS t))) PubKey (readkeys ks)
(Just t, Just ks, Just SharedPubKeyEncryption) -> (Just t, Just ks, Just SharedPubKeyEncryption) ->
Just $ SharedPubKeyCipher (fromB64bs t) (readkeys ks) Just $ SharedPubKeyCipher (decodeBS (fromB64 (encodeBS t))) (readkeys ks)
(Just t, Nothing, Just SharedEncryption) -> (Just t, Nothing, Just SharedEncryption) ->
Just $ SharedCipher (fromB64bs t) Just $ SharedCipher (decodeBS (fromB64 (encodeBS t)))
_ -> Nothing _ -> Nothing
where where
readkeys = KeyIds . splitc ',' readkeys = KeyIds . splitc ','
@ -321,14 +320,3 @@ describeCipher c = case c of
(SharedPubKeyCipher _ ks) -> showkeys ks (SharedPubKeyCipher _ ks) -> showkeys ks
where where
showkeys (KeyIds { keyIds = ks }) = "to gpg keys: " ++ unwords ks showkeys (KeyIds { keyIds = ks }) = "to gpg keys: " ++ unwords ks
{- Not using Utility.Base64 because these "Strings" are really
- bags of bytes and that would convert to unicode and not round-trip
- cleanly. -}
toB64bs :: String -> String
toB64bs = w82s . B.unpack . B64.encode . B.pack . s2w8
fromB64bs :: String -> String
fromB64bs s = either (const bad) (w82s . B.unpack) (B64.decode $ B.pack $ s2w8 s)
where
bad = giveup "bad base64 encoded data"

View file

@ -77,7 +77,6 @@ import qualified Utility.Hash
import qualified Utility.Scheduled import qualified Utility.Scheduled
import qualified Utility.Scheduled.QuickCheck import qualified Utility.Scheduled.QuickCheck
import qualified Utility.HumanTime import qualified Utility.HumanTime
import qualified Utility.Base64
import qualified Utility.Tmp.Dir import qualified Utility.Tmp.Dir
import qualified Utility.FileSystemEncoding import qualified Utility.FileSystemEncoding
import qualified Utility.Aeson import qualified Utility.Aeson
@ -184,7 +183,6 @@ properties = localOption (QuickCheckTests 1000) $ testGroup "QuickCheck" $
, testProperty "prop_viewPath_roundtrips" Annex.View.prop_viewPath_roundtrips , testProperty "prop_viewPath_roundtrips" Annex.View.prop_viewPath_roundtrips
, testProperty "prop_view_roundtrips" Annex.View.prop_view_roundtrips , testProperty "prop_view_roundtrips" Annex.View.prop_view_roundtrips
, testProperty "prop_viewedFile_rountrips" Annex.View.ViewedFile.prop_viewedFile_roundtrips , testProperty "prop_viewedFile_rountrips" Annex.View.ViewedFile.prop_viewedFile_roundtrips
, testProperty "prop_b64_roundtrips" Utility.Base64.prop_b64_roundtrips
, testProperty "prop_standardGroups_parse" Logs.PreferredContent.prop_standardGroups_parse , testProperty "prop_standardGroups_parse" Logs.PreferredContent.prop_standardGroups_parse
] ++ map (uncurry testProperty) combos ] ++ map (uncurry testProperty) combos
where where

View file

@ -137,14 +137,14 @@ instance MetaSerializable MetaValue where
serialize (MetaValue isset v) = serialize (MetaValue isset v) =
serialize isset <> serialize isset <>
if B8.any (`elem` [' ', '\r', '\n']) v || "!" `B8.isPrefixOf` v if B8.any (`elem` [' ', '\r', '\n']) v || "!" `B8.isPrefixOf` v
then "!" <> toB64' v then "!" <> toB64 v
else v else v
deserialize b = do deserialize b = do
(isset, b') <- B8.uncons b (isset, b') <- B8.uncons b
case B8.uncons b' of case B8.uncons b' of
Just ('!', b'') -> MetaValue Just ('!', b'') -> MetaValue
<$> deserialize (B8.singleton isset) <$> deserialize (B8.singleton isset)
<*> fromB64Maybe' b'' <*> fromB64Maybe b''
_ -> MetaValue _ -> MetaValue
<$> deserialize (B8.singleton isset) <$> deserialize (B8.singleton isset)
<*> pure b' <*> pure b'

View file

@ -1,55 +1,25 @@
{- Simple Base64 encoding {- Simple Base64 encoding
- -
- Copyright 2011-2019 Joey Hess <id@joeyh.name> - Copyright 2011-2023 Joey Hess <id@joeyh.name>
- -
- License: BSD-2-clause - License: BSD-2-clause
-} -}
{-# LANGUAGE PackageImports #-}
module Utility.Base64 where module Utility.Base64 where
import Utility.FileSystemEncoding
import Utility.QuickCheck
import Utility.Exception import Utility.Exception
import qualified "sandi" Codec.Binary.Base64 as B64 import Codec.Binary.Base64 as B64
import Data.Maybe import Data.Maybe
import qualified Data.ByteString as B import qualified Data.ByteString as B
import Data.ByteString.UTF8 (fromString, toString)
import Data.Char
-- | This uses the FileSystemEncoding, so it can be used on Strings toB64 :: B.ByteString -> B.ByteString
-- that represent filepaths containing arbitrarily encoded characters. toB64 = B64.encode
toB64 :: String -> String
toB64 = toString . B64.encode . encodeBS
toB64' :: B.ByteString -> B.ByteString fromB64Maybe :: B.ByteString -> Maybe (B.ByteString)
toB64' = B64.encode fromB64Maybe = either (const Nothing) Just . B64.decode
fromB64Maybe :: String -> Maybe String fromB64 :: B.ByteString -> B.ByteString
fromB64Maybe s = either (const Nothing) (Just . decodeBS)
(B64.decode $ fromString s)
fromB64Maybe' :: B.ByteString -> Maybe (B.ByteString)
fromB64Maybe' = either (const Nothing) Just . B64.decode
fromB64 :: String -> String
fromB64 = fromMaybe bad . fromB64Maybe fromB64 = fromMaybe bad . fromB64Maybe
where where
bad = giveup "bad base64 encoded data" bad = giveup "bad base64 encoded data"
fromB64' :: B.ByteString -> B.ByteString
fromB64' = fromMaybe bad . fromB64Maybe'
where
bad = giveup "bad base64 encoded data"
-- Only ascii strings are tested, because an arbitrary string may contain
-- characters not encoded using the FileSystemEncoding, which would thus
-- not roundtrip, as decodeBS always generates an output encoded that way.
prop_b64_roundtrips :: TestableString -> Bool
prop_b64_roundtrips ts
| all (isAscii) s = s == decodeBS (fromB64' (toB64' (encodeBS s)))
| otherwise = True
where
s = fromTestableString ts