{-# LANGUAGE DataKinds #-} {-# LANGUAGE ExplicitNamespaces #-} {-# LANGUAGE PackageImports #-} {-# OPTIONS_GHC -Wno-missing-signatures #-} module TahoeLAFS.Storage.Client ( -- General server info version, -- Mutable or immutable renewLease, -- Immutable operations createImmutableStorageIndex, writeImmutableShare, abortImmutableUpload, readImmutableShare, getImmutableShareNumbers, adviseCorruptImmutableShare, -- Mutable operations readTestWrite, readMutableShares, getMutableShareNumbers, adviseCorruptMutableShare, parseNURL, runGBS, NURL (..), ) where import Control.Monad ((>=>)) import qualified "base64" Data.ByteString.Base64.URL as Base64URL import Data.Proxy (Proxy (..)) import qualified Data.Text as T import qualified Data.Text.Encoding as T import Network.HTTP.Client.TLS ( newTlsManagerWith, ) import Network.Socket (HostName, PortNumber) import Network.URI ( -- URI (URI, uriAuthority, uriPath), URIAuth (URIAuth, uriPort, uriRegName, uriUserInfo), parseURI, ) import Servant ( URI (URI, uriAuthority, uriFragment, uriPath), type (:<|>) ((:<|>)), ) import Servant.Client ( BaseUrl (BaseUrl), ClientError, ClientM, Scheme (Https), client, mkClientEnv, runClientM, ) import TahoeLAFS.Internal.Client (SPKIHash (SPKIHash), mkGBSManagerSettings) import TahoeLAFS.Storage.API ( StorageAPI, ) import Text.Read (readMaybe) newApi :: Proxy StorageAPI newApi = Proxy ( version :<|> renewLease :<|> createImmutableStorageIndex :<|> writeImmutableShare :<|> abortImmutableUpload :<|> readImmutableShare :<|> getImmutableShareNumbers :<|> adviseCorruptImmutableShare :<|> readTestWrite :<|> readMutableShares :<|> getMutableShareNumbers :<|> adviseCorruptMutableShare ) = client newApi -- | Represent a "new" style service URL. data NURL = NURLv1 { -- | The cryptographic fingerprint of the server hosting the service. nurlv1Fingerprint :: SPKIHash , -- | A hint about the network location of the server hosting the service. nurlv1Address :: (HostName, PortNumber) , -- | The secret identifier for the service within the scope of the server. nurlv1Swissnum :: T.Text } deriving (Ord, Eq, Show) -- | Parse a Great Black Swamp NURL from text. parseNURL :: T.Text -> Maybe NURL parseNURL = parseURI . T.unpack >=> uriToNURL uriToNURL :: URI -> Maybe NURL uriToNURL URI{uriAuthority = Just URIAuth{uriUserInfo, uriRegName = hostname, uriPort = (':' : port)}, uriPath = ('/' : swissnum), uriFragment = "#v=1"} = case (requiredHashE, portM) of (Left _, _) -> Nothing (_, Nothing) -> Nothing (Right requiredHash, Just portNum) -> Just NURLv1{nurlv1Fingerprint = requiredHash, nurlv1Address = (hostname, portNum), nurlv1Swissnum = T.pack swissnum} where requiredHashE = fmap SPKIHash . Base64URL.decodeBase64 . T.encodeUtf8 . T.pack . dropLast 1 $ uriUserInfo portM = readMaybe port uriToNURL _ = Nothing {- | Execute some client operations against the Great Black Swamp server at the location indicated by the given NURL. -} runGBS :: NURL -> ClientM a -> IO (Either ClientError a) runGBS NURLv1{nurlv1Fingerprint, nurlv1Address = (hostname, port), nurlv1Swissnum} action = do manager <- newTlsManagerWith (mkGBSManagerSettings nurlv1Fingerprint nurlv1Swissnum) let clientEnv = mkClientEnv manager (BaseUrl Https hostname (fromIntegral port) "") runClientM action clientEnv dropLast :: Int -> [a] -> [a] dropLast n xs = take (length xs - n) xs