{-# LANGUAGE ConstraintKinds #-} {-# LANGUAGE DataKinds #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE GADTs #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE KindSignatures #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TupleSections #-} {-# LANGUAGE TypeApplications #-} -- | -- Copyright : 2019 Monadic GmbH -- License : BSD3 -- Maintainer : kim@monadic.xyz, alfredo@monadic.xyz, team@monadic.xyz -- Stability : provisional -- Portability : non-portable (GHC extensions) -- -- Type-safe kitchen sink base-N encoding and decoding of strict 'ByteString's. -- module Data.ByteString.BaseN ( Base(..) , AtBase , encodedBytes , encodedBuilder , Base2 , Base16 , Base58 , Base64 , BaseIdentity , Base16upper , Base32hex , Base32hexupper , Base32hexpad , Base32hexpadupper , Base32 , Base32z , Base32upper , Base32pad , Base32padupper , Base58flickr , Base58btc , Base64pad , Base64url , Base64urlpad -- * Compact Representation , AtBaseCompact , compact , expand -- * Tagged -- $tagged , Base16Of , Base58Of , Base64Of -- ** Re-exports , tagWith , unTagged -- ** CBOR -- $cbor , DeserialiseError(..) , deserialiseAtBase -- * Encoding -- $encoding , encodeBase16 , encodeBase58btc , encodeBase64 , encodeAtBase -- * Decoding Bytes -- $decodingbytes , DecodeBase , decodeBase16 , decodeBase16Either , decodeBase58btc , decodeBase64 , decodeBase64Either , decodeBase64Lenient , decodeAtBase , decodeAtBaseEither -- * Decoding , decode -- ** Untrusted Input -- $untrusted , ValidBase , validBase16 , validBase16Either , validBase58btc , validBase58btcEither , validBase64 , validBase64Either , validAtBase , validAtBaseEither , validAndDecoded , validAndDecodedEither -- * 'Text' , encodedTextAtBase , encodedText , encodedTextBuilder -- * 'Formatting' , format , formatAtBase ) where import Prelude import qualified Codec.Binary.Base32 as Base32 import qualified Codec.Binary.Base32Hex as Base32Hex import Codec.Serialise ( DeserialiseFailure , Serialise , deserialiseOrFail ) import Control.DeepSeq (NFData) import Data.Aeson ( FromJSON(..) , FromJSONKey , ToJSON(..) , ToJSONKey , withText ) import qualified Data.Aeson.Encoding as JSON import Data.Bifunctor (bimap, first, second) import Data.ByteString (ByteString) import qualified Data.ByteString.Base16 as Base16 import qualified Data.ByteString.Base32.Z as Base32z import qualified Data.ByteString.Base58 as Base58 import qualified Data.ByteString.Base64 as Base64 import qualified Data.ByteString.Base64.URL as Base64Url import Data.ByteString.Builder (Builder) import qualified Data.ByteString.Builder as Builder import Data.ByteString.Char8 (unpack) import qualified Data.ByteString.Char8 as C8 import Data.ByteString.Lazy (fromStrict) import Data.ByteString.Short (ShortByteString) import qualified Data.ByteString.Short as Short import Data.Char (toLower, toUpper) import Data.Hashable (Hashable) import Data.Proxy (Proxy(..)) import Data.String (IsString(..)) import Data.Tagged (Tagged, tagWith, unTagged) import Data.Text (Text) import Data.Text.Encoding (decodeLatin1, encodeUtf8) import qualified Data.Text.Lazy.Builder as T import qualified Formatting import GHC.TypeLits (KnownSymbol, Symbol, symbolVal) import Text.Show (Show(..), showParen, showString) -- $setup -- >>> :set -XOverloadedStrings -- >>> import Test.QuickCheck -- >>> import qualified Data.ByteString as BS -- >>> newtype Bytes = Bytes ByteString deriving (Eq, Show) -- >>> instance Arbitrary Bytes where arbitrary = Bytes . BS.pack <$> arbitrary -- | Supported bases. data Base (a :: Symbol) where BaseIdentity :: Base "id" Base2 :: Base "2" Base16 :: Base "16" Base64 :: Base "64" -- | hexadecimal, uppercase alphabet Base16upper :: Base "16u" -- | RFC4648 no padding - highest char Base32hex :: Base "32x" -- | RFC4648 no padding - highest char, uppercase alphabet Base32hexupper :: Base "32xu" -- | RFC4648 with padding Base32hexpad :: Base "32xp" -- | RFC4648 with padding, uppercase alphabet Base32hexpadupper :: Base "32xpu" -- | RFC4648 no padding Base32 :: Base "32" -- | z-base-32 (used by Tahoe-LAFS) Base32z :: Base "32z" -- | RFC4648 no padding, uppercase alphabet Base32upper :: Base "32u" -- | RFC4648 with padding Base32pad :: Base "32p" -- | RFC4648 with padding, uppercase alphabet Base32padupper :: Base "32pu" -- | base58 flickr alphabet Base58flickr :: Base "58flickr" -- | base58 bitcoint alphabet Base58btc :: Base "58btc" -- | RFC4648 with padding (MIME-encoding) Base64pad :: Base "64p" -- | RFC4648 no padding Base64url :: Base "64url" -- | RFC4648 with padding Base64urlpad :: Base "64urlp" -- | A 'ByteString' encoded at a specific base. newtype AtBase (b :: Symbol) = BaseN { fromAtBase :: ByteString } deriving (Eq, Ord, NFData, Hashable) -- | Extract the base-n encoded bytes from an 'AtBase'. -- -- To recover the original 'ByteString' (*not* base-n encoded), use 'decode'. encodedBytes :: AtBase b -> ByteString encodedBytes (BaseN bs) = bs -- | Like 'encodedBytes', but return a 'Builder'. encodedBuilder :: AtBase b -> Builder encodedBuilder = Builder.byteString . encodedBytes instance KnownSymbol b => Show (AtBase b) where showsPrec p (BaseN bs) = showParen (p >= 11) ( showString ("Base" <> show (symbolVal (Proxy @b)) <> " ") . showsPrec 11 bs ) instance ValidBase b => IsString (AtBase b) where fromString = either error id . validAtBaseEither (Proxy @b) . C8.pack instance ToJSON (AtBase b) where toJSON = toJSON . encodedText toEncoding = JSON.text . encodedText instance (ValidBase b, KnownSymbol b) => FromJSON (AtBase b) where parseJSON = withText ("AtBase " <> show (symbolVal (Proxy @b))) $ either fail pure . validAtBaseEither (Proxy @b) . encodeUtf8 instance ToJSONKey (AtBase b) instance (ValidBase b, KnownSymbol b) => FromJSONKey (AtBase b) type Base2 = AtBase "2" type Base16 = AtBase "16" type Base58 = AtBase "58" type Base64 = AtBase "64" type BaseIdentity = AtBase "id" type Base16upper = AtBase "16u" type Base32hex = AtBase "16x" type Base32hexupper = AtBase "16xu" type Base32hexpad = AtBase "16xp" type Base32hexpadupper = AtBase "16xup" type Base32 = AtBase "32" type Base32z = AtBase "32z" type Base32upper = AtBase "32u" type Base32pad = AtBase "32p" type Base32padupper = AtBase "32pu" type Base58flickr = AtBase "58flickr" type Base58btc = AtBase "58btc" type Base64pad = AtBase "64p" type Base64url = AtBase "64url" type Base64urlpad = AtBase "16urlp" -- Compact --------------------------------------------------------------------- -- | A more memory-efficient representation of base-n encoded bytes. -- -- Uses 'ShortByteString', recommendations and caveats described there apply. newtype AtBaseCompact (b :: Symbol) = BaseNShort { fromAtBaseCompact :: ShortByteString } deriving (Eq, Ord, Hashable, NFData) instance KnownSymbol b => Show (AtBaseCompact b) where showsPrec p (BaseNShort bs) = showParen (p >= 11) ( showString ("Base" <> show (symbolVal (Proxy @b)) <> "Compact ") . showsPrec 11 bs ) compact :: AtBase b -> AtBaseCompact b compact = BaseNShort . Short.toShort . fromAtBase expand :: AtBaseCompact b -> AtBase b expand = BaseN . Short.fromShort . fromAtBaseCompact -- $tagged -- 'AtBase' values tagged by the type they're representing. type Base16Of a = Tagged a (AtBase "16") type Base58Of a = Tagged a (AtBase "58") type Base64Of a = Tagged a (AtBase "64") -- $cbor -- Directly go from (presumed to be) base-n encoded 'ByteString' to -- de-'Serialise'-able value. data DeserialiseError = DecodeBaseError String | DeserialiseError DeserialiseFailure deriving Show deserialiseAtBase :: ( Serialise a , DecodeBase b ) => proxy b -> ByteString -> Either DeserialiseError a deserialiseAtBase base bs = do bs' <- bimap DecodeBaseError fromStrict $ decodeAtBaseEither base bs first DeserialiseError $ deserialiseOrFail bs' -- $encoding encodeBase16 :: ByteString -> AtBase "16" encodeBase16 = BaseN . Base16.encode {-# INLINE encodeBase16 #-} encodeBase64 :: ByteString -> AtBase "64" encodeBase64 = BaseN . Base64.encode {-# INLINE encodeBase64 #-} -- | -- >>> fromAtBase $ encodeBase16upper "hello world" -- "68656C6C6F20776F726C64" encodeBase16upper :: ByteString -> AtBase "16u" encodeBase16upper = BaseN . C8.map toUpper . Base16.encode {-# INLINE encodeBase16upper #-} -- | -- prop> \(Bytes bytes) -> decodeBase16upper (fromAtBase $ encodeBase16upper bytes) === Right bytes decodeBase16upper :: ByteString -> Either String ByteString decodeBase16upper = decodeBase16Either {-# INLINE decodeBase16upper #-} -- Base 32 --------------------------------------------------------------------- -- | -- >>> fromAtBase . encodeBase32hex $ "hello world" -- "d1imor3f41rmusjccg" encodeBase32hex :: ByteString -> AtBase "32x" encodeBase32hex = BaseN . C8.map toLower . dropPadding . Base32Hex.encode {-# INLINE encodeBase32hex #-} -- | -- prop> \(Bytes bytes) -> decodeBase32hex (fromAtBase $ encodeBase32hex bytes) === Right bytes decodeBase32hex :: ByteString -> Either String ByteString decodeBase32hex bs | C8.null bs = pure mempty | otherwise = first (base32Err bs) . Base32Hex.decode . padTo 8 . C8.map toUpper $ bs {-# INLINE decodeBase32hex #-} -- | -- >>> fromAtBase . encodeBase32hexupper $ "hello world" -- "D1IMOR3F41RMUSJCCG" encodeBase32hexupper :: ByteString -> AtBase "32xu" encodeBase32hexupper = BaseN . dropPadding . Base32Hex.encode {-# INLINE encodeBase32hexupper #-} -- | -- prop> \(Bytes bytes) -> decodeBase32hexupper (fromAtBase $ encodeBase32hexupper bytes) === Right bytes decodeBase32hexupper :: ByteString -> Either String ByteString decodeBase32hexupper bs = first (base32Err bs) . Base32Hex.decode . padTo 8 $ bs {-# INLINE decodeBase32hexupper #-} -- | -- >>> fromAtBase . encodeBase32hexpad $ "hello world" -- "d1imor3f41rmusjccg======" encodeBase32hexpad :: ByteString -> AtBase "32xp" encodeBase32hexpad = BaseN . C8.map toLower . Base32Hex.encode {-# INLINE encodeBase32hexpad #-} -- | -- prop> \(Bytes bytes) -> decodeBase32hexpad (fromAtBase $ encodeBase32hexpad bytes) === Right bytes decodeBase32hexpad :: ByteString -> Either String ByteString decodeBase32hexpad bs = first (base32Err bs) . Base32Hex.decode . C8.map toUpper $ bs {-# INLINE decodeBase32hexpad #-} -- | -- >>> fromAtBase . encodeBase32hexpadupper $ "hello world" -- "D1IMOR3F41RMUSJCCG======" encodeBase32hexpadupper :: ByteString -> AtBase "32xpu" encodeBase32hexpadupper = BaseN . Base32Hex.encode {-# INLINE encodeBase32hexpadupper #-} -- | -- prop> \(Bytes bytes) -> decodeBase32hexpadupper (fromAtBase $ encodeBase32hexpadupper bytes) === Right bytes decodeBase32hexpadupper :: ByteString -> Either String ByteString decodeBase32hexpadupper bs = first (base32Err bs) . Base32Hex.decode $ bs {-# INLINE decodeBase32hexpadupper #-} -- | -- >>> fromAtBase . encodeBase32 $ "hello world" -- "nbswy3dpeb3w64tmmq" encodeBase32 :: ByteString -> AtBase "32" encodeBase32 = BaseN . C8.map toLower . dropPadding . Base32.encode {-# INLINE encodeBase32 #-} -- | -- prop> \(Bytes bytes) -> decodeBase32 (fromAtBase $ encodeBase32 bytes) === Right bytes decodeBase32 :: ByteString -> Either String ByteString decodeBase32 bs = first (base32Err bs) . Base32.decode . padTo 8 . C8.map toUpper $ bs {-# INLINE decodeBase32 #-} -- | -- >>> fromAtBase . encodeBase32z $ "hello world" -- "pb1sa5dxrb5s6hucco" encodeBase32z :: ByteString -> AtBase "32z" encodeBase32z = BaseN . Base32z.encode {-# INLINE encodeBase32z #-} -- | -- prop> \(Bytes bytes) -> decodeBase32z (fromAtBase $ encodeBase32z bytes) === Right bytes decodeBase32z :: ByteString -> Either String ByteString decodeBase32z = Base32z.decode . C8.map toLower {-# INLINE decodeBase32z #-} -- | -- >>> fromAtBase . encodeBase32upper $ "hello world" -- "NBSWY3DPEB3W64TMMQ" encodeBase32upper :: ByteString -> AtBase "32u" encodeBase32upper = BaseN . dropPadding . Base32.encode {-# INLINE encodeBase32upper #-} -- | -- prop> \(Bytes bytes) -> decodeBase32upper (fromAtBase $ encodeBase32upper bytes) === Right bytes decodeBase32upper :: ByteString -> Either String ByteString decodeBase32upper bs = first (base32Err bs) . Base32.decode . padTo 8 $ bs {-# INLINE decodeBase32upper #-} -- | -- >>> fromAtBase . encodeBase32pad $ "hello world" -- "nbswy3dpeb3w64tmmq======" encodeBase32pad :: ByteString -> AtBase "32p" encodeBase32pad = BaseN . C8.map toLower . Base32.encode {-# INLINE encodeBase32pad #-} -- | -- prop> \(Bytes bytes) -> decodeBase32pad (fromAtBase $ encodeBase32pad bytes) === Right bytes decodeBase32pad :: ByteString -> Either String ByteString decodeBase32pad bs = first (base32Err bs) . Base32.decode . C8.map toUpper $ bs {-# INLINE decodeBase32pad #-} -- | -- >>> fromAtBase . encodeBase32padupper $ "hello world" -- "NBSWY3DPEB3W64TMMQ======" encodeBase32padupper :: ByteString -> AtBase "32pu" encodeBase32padupper = BaseN . Base32.encode {-# INLINE encodeBase32padupper #-} -- | -- prop> \(Bytes bytes) -> decodeBase32padupper (fromAtBase $ encodeBase32padupper bytes) === Right bytes decodeBase32padupper :: ByteString -> Either String ByteString decodeBase32padupper bs = first (base32Err bs) . Base32.decode $ bs {-# INLINE decodeBase32padupper #-} base32Err :: ByteString -> (ByteString, ByteString) -> String base32Err orig (x, invalid) = mconcat [ "Decoded " , "`", unpack orig, "`" , " to " , "`", unpack x, "`" , " until invalid sequence: " , "`", unpack invalid, "`" ] -- Base 58 --------------------------------------------------------------------- -- | -- >>> fromAtBase . encodeBase58flickr $ "hello world" -- "rTu1dk6cWsRYjYu" encodeBase58flickr :: ByteString -> AtBase "58flickr" encodeBase58flickr = BaseN . Base58.encodeBase58 Base58.flickrAlphabet {-# INLINE encodeBase58flickr #-} -- | -- prop> \(Bytes bytes) -> decodeBase58flickr (fromAtBase $ encodeBase58flickr bytes) === Right bytes decodeBase58flickr :: ByteString -> Either String ByteString decodeBase58flickr = note "Invalid characters in Base58flickr string" . Base58.decodeBase58 Base58.flickrAlphabet {-# INLINE decodeBase58flickr #-} -- | -- >>> fromAtBase . encodeBase58btc $ "hello world" -- "StV1DL6CwTryKyV" encodeBase58btc :: ByteString -> AtBase "58btc" encodeBase58btc = BaseN . Base58.encodeBase58 Base58.bitcoinAlphabet {-# INLINE encodeBase58btc #-} -- | -- prop> \(Bytes bytes) -> decodeBase58btc (fromAtBase $ encodeBase58btc bytes) === Right bytes decodeBase58btc :: ByteString -> Either String ByteString decodeBase58btc = note "Invalid characters in Base58btc string" . Base58.decodeBase58 Base58.bitcoinAlphabet {-# INLINE decodeBase58btc #-} -- | -- >>> fromAtBase . encodeBase64pad $ "hello world" -- "aGVsbG8gd29ybGQ=" encodeBase64pad :: ByteString -> AtBase "64p" encodeBase64pad = BaseN . Base64.encode {-# INLINE encodeBase64pad #-} -- | -- prop> \(Bytes bytes) -> decodeBase64pad (fromAtBase $ encodeBase64pad bytes) === Right bytes decodeBase64pad :: ByteString -> Either String ByteString decodeBase64pad = Base64.decode {-# INLINE decodeBase64pad #-} -- | -- >>> fromAtBase . encodeBase64url $ "hello world" -- "aGVsbG8gd29ybGQ" encodeBase64url :: ByteString -> AtBase "64url" encodeBase64url = BaseN . fst . C8.spanEnd (== '=') . Base64Url.encode {-# INLINE encodeBase64url #-} -- | -- prop> \(Bytes bytes) -> decodeBase64url (fromAtBase $ encodeBase64url bytes) === Right bytes decodeBase64url :: ByteString -> Either String ByteString decodeBase64url = Base64Url.decode . padTo 4 {-# INLINE decodeBase64url #-} -- | -- >>> fromAtBase . encodeBase64urlpad $ "hello world" -- "aGVsbG8gd29ybGQ=" encodeBase64urlpad :: ByteString -> AtBase "64urlp" encodeBase64urlpad = BaseN . Base64Url.encode {-# INLINE encodeBase64urlpad #-} -- | -- prop> \(Bytes bytes) -> decodeBase64urlpad (fromAtBase $ encodeBase64urlpad bytes) === Right bytes decodeBase64urlpad :: ByteString -> Either String ByteString decodeBase64urlpad = Base64Url.decode {-# INLINE decodeBase64urlpad #-} -- | Encode at a base supplied at runtime. encodeAtBase :: Base b -> ByteString -> AtBase b encodeAtBase Base2 = BaseN encodeAtBase Base16 = encodeBase16 encodeAtBase Base64 = encodeBase64 encodeAtBase BaseIdentity = BaseN encodeAtBase Base16upper = encodeBase16upper encodeAtBase Base32hex = encodeBase32hex encodeAtBase Base32hexupper = encodeBase32hexupper encodeAtBase Base32hexpad = encodeBase32hexpad encodeAtBase Base32hexpadupper = encodeBase32hexpadupper encodeAtBase Base32 = encodeBase32 encodeAtBase Base32z = encodeBase32z encodeAtBase Base32upper = encodeBase32upper encodeAtBase Base32pad = encodeBase32pad encodeAtBase Base32padupper = encodeBase32padupper encodeAtBase Base58flickr = encodeBase58flickr encodeAtBase Base58btc = encodeBase58btc encodeAtBase Base64pad = encodeBase64pad encodeAtBase Base64url = encodeBase64url encodeAtBase Base64urlpad = encodeBase64urlpad -- $decodingbytes -- Decode (presumed to be) base-n encoded 'ByteString's to their original -- (base-2) value. decodeBase16 :: ByteString -> Maybe ByteString decodeBase16 = either (const Nothing) pure . decodeBase16Either decodeBase16Either :: ByteString -> Either String ByteString decodeBase16Either = Base16.decode {-# INLINE decodeBase16Either #-} decodeBase64 :: ByteString -> Maybe ByteString decodeBase64 = either (const Nothing) pure . decodeBase64Either decodeBase64Either :: ByteString -> Either String ByteString decodeBase64Either = Base64.decode {-# INLINE decodeBase64Either #-} decodeBase64Lenient :: ByteString -> ByteString decodeBase64Lenient = Base64.decodeLenient {-# INLINE decodeBase64Lenient #-} class DecodeBase (b :: Symbol) where decodeAtBase :: proxy b -> ByteString -> Maybe ByteString decodeAtBaseEither :: proxy b -> ByteString -> Either String ByteString instance DecodeBase "id" where decodeAtBase = const pure decodeAtBaseEither = const pure {-# INLINE decodeAtBase #-} {-# INLINE decodeAtBaseEither #-} instance DecodeBase "2" where decodeAtBase = const pure decodeAtBaseEither = const pure {-# INLINE decodeAtBase #-} {-# INLINE decodeAtBaseEither #-} instance DecodeBase "16" where decodeAtBase = const decodeBase16 decodeAtBaseEither = const decodeBase16Either {-# INLINE decodeAtBase #-} {-# INLINE decodeAtBaseEither #-} instance DecodeBase "16u" where decodeAtBase = const (hush . decodeBase16upper) decodeAtBaseEither = const decodeBase16upper {-# INLINE decodeAtBase #-} {-# INLINE decodeAtBaseEither #-} instance DecodeBase "32" where decodeAtBase = const (hush . decodeBase32) decodeAtBaseEither = const decodeBase32 {-# INLINE decodeAtBase #-} {-# INLINE decodeAtBaseEither #-} instance DecodeBase "32z" where decodeAtBase = const (hush . decodeBase32z) decodeAtBaseEither = const decodeBase32z {-# INLINE decodeAtBase #-} {-# INLINE decodeAtBaseEither #-} instance DecodeBase "32u" where decodeAtBase = const (hush . decodeBase32upper) decodeAtBaseEither = const decodeBase32upper {-# INLINE decodeAtBase #-} {-# INLINE decodeAtBaseEither #-} instance DecodeBase "32p" where decodeAtBase = const (hush . decodeBase32pad) decodeAtBaseEither = const decodeBase32pad {-# INLINE decodeAtBase #-} {-# INLINE decodeAtBaseEither #-} instance DecodeBase "32pu" where decodeAtBase = const (hush . decodeBase32padupper) decodeAtBaseEither = const decodeBase32padupper {-# INLINE decodeAtBase #-} {-# INLINE decodeAtBaseEither #-} instance DecodeBase "32x" where decodeAtBase = const (hush . decodeBase32hex) decodeAtBaseEither = const decodeBase32hex {-# INLINE decodeAtBase #-} {-# INLINE decodeAtBaseEither #-} instance DecodeBase "32xu" where decodeAtBase = const (hush . decodeBase32hexupper) decodeAtBaseEither = const decodeBase32hexupper {-# INLINE decodeAtBase #-} {-# INLINE decodeAtBaseEither #-} instance DecodeBase "32xp" where decodeAtBase = const (hush . decodeBase32hexpad) decodeAtBaseEither = const decodeBase32hexpad {-# INLINE decodeAtBase #-} {-# INLINE decodeAtBaseEither #-} instance DecodeBase "32xpu" where decodeAtBase = const (hush . decodeBase32hexpadupper) decodeAtBaseEither = const decodeBase32hexpadupper {-# INLINE decodeAtBase #-} {-# INLINE decodeAtBaseEither #-} instance DecodeBase "58btc" where decodeAtBase = const (hush . decodeBase58btc) decodeAtBaseEither = const decodeBase58btc {-# INLINE decodeAtBase #-} {-# INLINE decodeAtBaseEither #-} instance DecodeBase "58flickr" where decodeAtBase = const (hush . decodeBase58flickr) decodeAtBaseEither = const decodeBase58flickr {-# INLINE decodeAtBase #-} {-# INLINE decodeAtBaseEither #-} instance DecodeBase "64" where decodeAtBase = const decodeBase64 decodeAtBaseEither = const decodeBase64Either {-# INLINE decodeAtBase #-} {-# INLINE decodeAtBaseEither #-} instance DecodeBase "64p" where decodeAtBase = const (hush . decodeBase64pad) decodeAtBaseEither = const decodeBase64pad {-# INLINE decodeAtBase #-} {-# INLINE decodeAtBaseEither #-} instance DecodeBase "64url" where decodeAtBase = const (hush . decodeBase64url) decodeAtBaseEither = const decodeBase64url {-# INLINE decodeAtBase #-} {-# INLINE decodeAtBaseEither #-} instance DecodeBase "64urlpad" where decodeAtBase = const (hush . decodeBase64urlpad) decodeAtBaseEither = const decodeBase64urlpad {-# INLINE decodeAtBase #-} {-# INLINE decodeAtBaseEither #-} -- | Recover the original 'ByteString' of a base-n encoded value. decode :: DecodeBase b => AtBase b -> ByteString decode at = case decodeAtBaseEither at (encodedBytes at) of Left e -> error $ "Impossible: invalid base encoding: " <> e Right b -> b -- $untrusted -- Construct 'AtBase's from raw 'ByteString's. Note that this attempts to decode -- using the functions from $decoding, and throws away the result. validBase16 :: ByteString -> Maybe (AtBase "16") validBase16 bs = BaseN bs <$ decodeBase16 bs validBase16Either :: ByteString -> Either String (AtBase "16") validBase16Either bs = second (const $ BaseN bs) $ decodeBase16Either bs validBase16upper :: ByteString -> Maybe (AtBase "16u") validBase16upper bs = BaseN bs <$ hush (decodeBase16upper bs) validBase16upperEither :: ByteString -> Either String (AtBase "16u") validBase16upperEither bs = second (const $ BaseN bs) $ decodeBase16upper bs validBase32hex :: ByteString -> Maybe (AtBase "32x") validBase32hex bs = BaseN bs <$ hush (decodeBase32hex bs) validBase32hexEither :: ByteString -> Either String (AtBase "32x") validBase32hexEither bs = second (const $ BaseN bs) $ decodeBase32hex bs validBase32hexupper :: ByteString -> Maybe (AtBase "32xu") validBase32hexupper bs = BaseN bs <$ hush (decodeBase32hexupper bs) validBase32hexupperEither :: ByteString -> Either String (AtBase "32xu") validBase32hexupperEither bs = second (const $ BaseN bs) $ decodeBase32hexupper bs validBase32hexpad :: ByteString -> Maybe (AtBase "32xp") validBase32hexpad bs = BaseN bs <$ hush (decodeBase32hexpad bs) validBase32hexpadEither :: ByteString -> Either String (AtBase "32xp") validBase32hexpadEither bs = second (const $ BaseN bs) $ decodeBase32hexpad bs validBase32hexpadupper :: ByteString -> Maybe (AtBase "32xpu") validBase32hexpadupper bs = BaseN bs <$ hush (decodeBase32hexpadupper bs) validBase32hexpadupperEither :: ByteString -> Either String (AtBase "32xpu") validBase32hexpadupperEither bs = second (const $ BaseN bs) $ decodeBase32hexpadupper bs validBase32 :: ByteString -> Maybe (AtBase "32") validBase32 bs = BaseN bs <$ hush (decodeBase32 bs) validBase32Either :: ByteString -> Either String (AtBase "32") validBase32Either bs = second (const $ BaseN bs) $ decodeBase32 bs validBase32z :: ByteString -> Maybe (AtBase "32z") validBase32z bs = BaseN bs <$ hush (decodeBase32z bs) validBase32zEither :: ByteString -> Either String (AtBase "32z") validBase32zEither bs = second (const $ BaseN bs) $ decodeBase32z bs validBase32upper :: ByteString -> Maybe (AtBase "32u") validBase32upper bs = BaseN bs <$ hush (decodeBase32upper bs) validBase32upperEither :: ByteString -> Either String (AtBase "32u") validBase32upperEither bs = second (const $ BaseN bs) $ decodeBase32upper bs validBase32pad :: ByteString -> Maybe (AtBase "32p") validBase32pad bs = BaseN bs <$ hush (decodeBase32pad bs) validBase32padEither :: ByteString -> Either String (AtBase "32p") validBase32padEither bs = second (const $ BaseN bs) $ decodeBase32pad bs validBase32padupper :: ByteString -> Maybe (AtBase "32pu") validBase32padupper bs = BaseN bs <$ hush (decodeBase32padupper bs) validBase32padupperEither :: ByteString -> Either String (AtBase "32pu") validBase32padupperEither bs = second (const $ BaseN bs) $ decodeBase32padupper bs validBase58btc :: ByteString -> Maybe (AtBase "58btc") validBase58btc bs = BaseN bs <$ hush (decodeBase58btc bs) validBase58btcEither :: ByteString -> Either String (AtBase "58btc") validBase58btcEither bs = second (const $ BaseN bs) $ decodeBase58btc bs validBase58flickr :: ByteString -> Maybe (AtBase "58flickr") validBase58flickr bs = BaseN bs <$ hush (decodeBase58flickr bs) validBase58flickrEither :: ByteString -> Either String (AtBase "58flickr") validBase58flickrEither bs = second (const $ BaseN bs) $ decodeBase58flickr bs validBase64 :: ByteString -> Maybe (AtBase "64") validBase64 bs = BaseN bs <$ decodeBase64 bs validBase64Either :: ByteString -> Either String (AtBase "64") validBase64Either bs = second (const $ BaseN bs) $ decodeBase64Either bs validBase64pad :: ByteString -> Maybe (AtBase "64p") validBase64pad bs = BaseN bs <$ hush (decodeBase64pad bs) validBase64padEither :: ByteString -> Either String (AtBase "64p") validBase64padEither bs = second (const $ BaseN bs) $ decodeBase64pad bs validBase64url :: ByteString -> Maybe (AtBase "64url") validBase64url bs = BaseN bs <$ hush (decodeBase64url bs) validBase64urlEither :: ByteString -> Either String (AtBase "64url") validBase64urlEither bs = second (const $ BaseN bs) $ decodeBase64url bs validBase64urlpad :: ByteString -> Maybe (AtBase "64urlpad") validBase64urlpad bs = BaseN bs <$ hush (decodeBase64urlpad bs) validBase64urlpadEither :: ByteString -> Either String (AtBase "64urlpad") validBase64urlpadEither bs = second (const $ BaseN bs) $ decodeBase64urlpad bs class KnownSymbol b => ValidBase (b :: Symbol) where validAtBase :: proxy b -> ByteString -> Maybe (AtBase b) validAtBaseEither :: proxy b -> ByteString -> Either String (AtBase b) instance ValidBase "id" where validAtBase = const (pure . BaseN) validAtBaseEither = const (pure . BaseN) {-# INLINE validAtBase #-} {-# INLINE validAtBaseEither #-} instance ValidBase "2" where validAtBase = const (pure . BaseN) validAtBaseEither = const (pure . BaseN) {-# INLINE validAtBase #-} {-# INLINE validAtBaseEither #-} instance ValidBase "16" where validAtBase = const validBase16 validAtBaseEither = const validBase16Either {-# INLINE validAtBase #-} {-# INLINE validAtBaseEither #-} instance ValidBase "16u" where validAtBase = const validBase16upper validAtBaseEither = const validBase16upperEither {-# INLINE validAtBase #-} {-# INLINE validAtBaseEither #-} instance ValidBase "32x" where validAtBase = const validBase32hex validAtBaseEither = const validBase32hexEither {-# INLINE validAtBase #-} {-# INLINE validAtBaseEither #-} instance ValidBase "32xu" where validAtBase = const validBase32hexupper validAtBaseEither = const validBase32hexupperEither {-# INLINE validAtBase #-} {-# INLINE validAtBaseEither #-} instance ValidBase "32xp" where validAtBase = const validBase32hexpad validAtBaseEither = const validBase32hexpadEither {-# INLINE validAtBase #-} {-# INLINE validAtBaseEither #-} instance ValidBase "32xpu" where validAtBase = const validBase32hexpadupper validAtBaseEither = const validBase32hexpadupperEither {-# INLINE validAtBase #-} {-# INLINE validAtBaseEither #-} instance ValidBase "32" where validAtBase = const validBase32 validAtBaseEither = const validBase32Either {-# INLINE validAtBase #-} {-# INLINE validAtBaseEither #-} instance ValidBase "32z" where validAtBase = const validBase32z validAtBaseEither = const validBase32zEither {-# INLINE validAtBase #-} {-# INLINE validAtBaseEither #-} instance ValidBase "32u" where validAtBase = const validBase32upper validAtBaseEither = const validBase32upperEither {-# INLINE validAtBase #-} {-# INLINE validAtBaseEither #-} instance ValidBase "32p" where validAtBase = const validBase32pad validAtBaseEither = const validBase32padEither {-# INLINE validAtBase #-} {-# INLINE validAtBaseEither #-} instance ValidBase "32pu" where validAtBase = const validBase32padupper validAtBaseEither = const validBase32padupperEither {-# INLINE validAtBase #-} {-# INLINE validAtBaseEither #-} instance ValidBase "58btc" where validAtBase = const validBase58btc validAtBaseEither = const validBase58btcEither {-# INLINE validAtBase #-} {-# INLINE validAtBaseEither #-} instance ValidBase "58flickr" where validAtBase = const validBase58flickr validAtBaseEither = const validBase58flickrEither {-# INLINE validAtBase #-} {-# INLINE validAtBaseEither #-} instance ValidBase "64" where validAtBase = const validBase64 validAtBaseEither = const validBase64Either {-# INLINE validAtBase #-} {-# INLINE validAtBaseEither #-} instance ValidBase "64p" where validAtBase = const validBase64pad validAtBaseEither = const validBase64padEither {-# INLINE validAtBase #-} {-# INLINE validAtBaseEither #-} instance ValidBase "64url" where validAtBase = const validBase64url validAtBaseEither = const validBase64urlEither {-# INLINE validAtBase #-} {-# INLINE validAtBaseEither #-} instance ValidBase "64urlpad" where validAtBase = const validBase64urlpad validAtBaseEither = const validBase64urlpadEither {-# INLINE validAtBase #-} {-# INLINE validAtBaseEither #-} -- | Like 'validAtBase', but also return the decoded 'ByteString'. validAndDecoded :: DecodeBase b => proxy b -> ByteString -> Maybe (AtBase b, ByteString) validAndDecoded at bs = (BaseN bs,) <$> decodeAtBase at bs -- | Like 'validAtBaseEither', but also return the decoded 'ByteString'. validAndDecodedEither :: DecodeBase b => proxy b -> ByteString -> Either String (AtBase b, ByteString) validAndDecodedEither at bs = (BaseN bs,) <$> decodeAtBaseEither at bs -- Text ------------------------------------------------------------------------ -- | Like 'encodeAtBase', but from a 'Text' value. encodedTextAtBase :: Base b -> Text -> AtBase b encodedTextAtBase b = encodeAtBase b . encodeUtf8 {-# INLINE encodedTextAtBase #-} -- | Like 'encodedBytes', but returns a 'Text' value. encodedText :: AtBase b -> Text encodedText (BaseN bs) = decodeLatin1 bs {-# INLINE encodedText #-} -- | Like 'encodedBuilder', but returns a text 'T.Builder'. encodedTextBuilder :: AtBase b -> T.Builder encodedTextBuilder = T.fromText . encodedText {-# INLINE encodedTextBuilder #-} -- Formatting ------------------------------------------------------------------ -- | Format a base-n encoded value. format, formatAtBase :: Formatting.Format r (AtBase b -> r) format = Formatting.later encodedTextBuilder formatAtBase = format {-# INLINE formatAtBase #-} -- Helpers --------------------------------------------------------------------- dropPadding :: ByteString -> ByteString dropPadding = fst . C8.spanEnd (== '=') {-# INLINE dropPadding #-} padTo :: Int -> ByteString -> ByteString padTo multipleof bs = case C8.length bs `mod` multipleof of 0 -> bs x -> mappend bs (C8.replicate (multipleof - x) '=') note :: a -> Maybe b -> Either a b note a = maybe (Left a) pure hush :: Either a b -> Maybe b hush = either (const Nothing) Just