{-# LANGUAGE DataKinds #-} {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE TypeOperators #-} {-# LANGUAGE OverloadedStrings #-} {-# OPTIONS_GHC -fno-warn-orphans #-} {- Copyright 2016 Joey Hess - - Licensed under the GNU AGPL version 3 or higher. -} module HTTP where import Types import Types.Storage import Serialization () import Servant.API import Data.Text import Data.Aeson.Types import GHC.Generics hiding (V1) import qualified Data.Text.Encoding as T import qualified Data.ByteString as B import qualified Data.ByteString.Lazy as L import qualified Raaz.Core.Encode as Raaz -- | Keysafe's http API type HttpAPI = "keysafe" :> V1 :> "motd" :> Get '[JSON] Motd :<|> "keysafe" :> V1 :> "objects" :> ObjectIdent :> POWParam :> Get '[JSON] (ProofOfWorkRequirement StorableObject) :<|> "keysafe" :> V1 :> "objects" :> ObjectIdent :> POWParam :> ReqBody '[OctetStream] StorableObject :> Put '[JSON] (ProofOfWorkRequirement StoreResult) :<|> "keysafe" :> V1 :> "stats" :> "countobjects" :> POWParam :> Get '[JSON] (ProofOfWorkRequirement CountResult) type V1 = "v1" newtype Motd = Motd Text deriving (Generic) data ProofOfWorkRequirement t = Result t | ProofOfWorkRequirement { leadingZeros :: Int , argon2Iterations :: Int } deriving (Generic) newtype ProofOfWork = ProofOfWork Text type POWParam = QueryParam "proofofwork" ProofOfWork type ObjectIdent = Capture "ident" StorableObjectIdent instance ToJSON Motd instance FromJSON Motd instance ToJSON t => ToJSON (ProofOfWorkRequirement t) instance FromJSON t => FromJSON (ProofOfWorkRequirement t) instance FromHttpApiData ProofOfWork where parseUrlPiece = Right . ProofOfWork instance ToHttpApiData ProofOfWork where toUrlPiece (ProofOfWork t) = t -- StorableObjectIdent contains a hash, which is valid UTF-8. instance ToHttpApiData StorableObjectIdent where toUrlPiece (StorableObjectIdent b) = T.decodeUtf8 b instance FromHttpApiData StorableObjectIdent where parseUrlPiece = Right . StorableObjectIdent . T.encodeUtf8 instance MimeRender OctetStream StorableObject where mimeRender _ = L.fromStrict . Raaz.toByteString instance MimeUnrender OctetStream StorableObject where mimeUnrender _ = maybe (Left "object encoding error") Right . Raaz.fromByteString . L.toStrict -- StorableObject contains an arbitrary bytestring; it is not UTF-8 encoded. -- So, to convert it to Text for Aeson, base64 encode it. instance ToJSON StorableObject where toJSON (StorableObject b) = object [ "data" .= b64 b ] instance FromJSON StorableObject where parseJSON (Object v) = StorableObject <$> (unb64 =<< v .: "data") parseJSON invalid = typeMismatch "StorableObject" invalid b64 :: B.ByteString -> Text b64 v = T.decodeUtf8 $ Raaz.toByteString (Raaz.encode v :: Raaz.Base64) unb64 :: Monad m => Text -> m B.ByteString unb64 t = maybe (fail "bad base64 data") (return . Raaz.decodeFormat) f where f = Raaz.fromByteString (T.encodeUtf8 t) :: Maybe Raaz.Base64