packages feed

base16 0.1.3.0 → 0.2.0.0

raw patch · 14 files changed

+373/−169 lines, 14 filesPVP ok

version bump matches the API change (PVP)

API changes (from Hackage documentation)

+ Data.ByteString.Base16: decodeBase16Lenient :: ByteString -> ByteString
+ Data.ByteString.Lazy.Base16: decodeBase16Lenient :: ByteString -> ByteString
+ Data.Text.Encoding.Base16: decodeBase16Lenient :: Text -> Text
+ Data.Text.Encoding.Base16: decodeBase16With :: (ByteString -> Either err Text) -> Text -> Either (Base16Error err) Text
+ Data.Text.Encoding.Base16.Error: ConversionError :: e -> Base16Error e
+ Data.Text.Encoding.Base16.Error: DecodeError :: Text -> Base16Error e
+ Data.Text.Encoding.Base16.Error: data Base16Error e
+ Data.Text.Encoding.Base16.Error: instance GHC.Classes.Eq e => GHC.Classes.Eq (Data.Text.Encoding.Base16.Error.Base16Error e)
+ Data.Text.Encoding.Base16.Error: instance GHC.Show.Show e => GHC.Show.Show (Data.Text.Encoding.Base16.Error.Base16Error e)
+ Data.Text.Lazy.Encoding.Base16: decodeBase16Lenient :: Text -> Text
+ Data.Text.Lazy.Encoding.Base16: decodeBase16With :: (ByteString -> Either err Text) -> Text -> Either (Base16Error err) Text

Files

CHANGELOG.md view
@@ -1,5 +1,14 @@ # Revision history for base16 +## 0.2.0++* Add lenient decoders+* Fix bug in `Text` `decodeBase16` which failed on invalid UTF-8 values as a result of decoding+* Add `decodeBase16With` combinators++## 0.1.3++* Add lazy variants for `Text` and `ByteString` values  ## 0.1.2.1 -- 2020-02-17 
base16.cabal view
@@ -1,6 +1,6 @@ cabal-version:      2.0 name:               base16-version:            0.1.3.0+version:            0.2.0.0 synopsis:           RFC 4648-compliant Base16 encodings/decodings description:   RFC 4648-compliant Base16 encodings and decodings.@@ -31,6 +31,7 @@     Data.ByteString.Base16     Data.ByteString.Lazy.Base16     Data.Text.Encoding.Base16+    Data.Text.Encoding.Base16.Error     Data.Text.Lazy.Encoding.Base16    other-modules:
benchmarks/Base16Bench.hs view
@@ -10,8 +10,8 @@ import Data.ByteString.Lazy (fromStrict) import "memory" Data.ByteArray.Encoding as Mem import Data.ByteString-import "base16" Data.ByteString.Lazy.Base16 as B16-import "base16-bytestring" Data.ByteString.Base16.Lazy as Bos+import "base16" Data.ByteString.Base16 as B16+import "base16-bytestring" Data.ByteString.Base16 as Bos import Data.ByteString.Random (random)  @@ -22,66 +22,66 @@       bgroup "encode"       [ bgroup "25"         [ bench "memory" $ whnf ctob bs25-        , bench "base16-bytestring" $ whnf Bos.encode bs25L-        , bench "base16" $ whnf B16.encodeBase16' bs25L+        , bench "base16-bytestring" $ whnf Bos.encode bs25+        , bench "base16" $ whnf B16.encodeBase16' bs25         ]       , bgroup "100"         [ bench "memory" $ whnf ctob bs100-        , bench "base16-bytestring" $ whnf Bos.encode bs100L-        , bench "base16" $ whnf B16.encodeBase16' bs100L+        , bench "base16-bytestring" $ whnf Bos.encode bs100+        , bench "base16" $ whnf B16.encodeBase16' bs100         ]       , bgroup "1k"         [ bench "memory" $ whnf ctob bs1k-        , bench "base16-bytestring" $ whnf Bos.encode bs1kL-        , bench "base16" $ whnf B16.encodeBase16' bs1kL+        , bench "base16-bytestring" $ whnf Bos.encode bs1k+        , bench "base16" $ whnf B16.encodeBase16' bs1k         ]       , bgroup "10k"         [ bench "memory" $ whnf ctob bs10k-        , bench "base16-bytestring" $ whnf Bos.encode bs10kL-        , bench "base16" $ whnf B16.encodeBase16' bs10kL+        , bench "base16-bytestring" $ whnf Bos.encode bs10k+        , bench "base16" $ whnf B16.encodeBase16' bs10k         ]       , bgroup "100k"         [ bench "memory" $ whnf ctob bs100k-        , bench "base16-bytestring" $ whnf Bos.encode bs100kL-        , bench "base16" $ whnf B16.encodeBase16' bs100kL+        , bench "base16-bytestring" $ whnf Bos.encode bs100k+        , bench "base16" $ whnf B16.encodeBase16' bs100k         ]       , bgroup "1mm"         [ bench "memory" $ whnf ctob bs1mm-        , bench "base16-bytestring" $ whnf Bos.encode bs1mmL-        , bench "base16" $ whnf B16.encodeBase16' bs1mmL+        , bench "base16-bytestring" $ whnf Bos.encode bs1mm+        , bench "base16" $ whnf B16.encodeBase16' bs1mm         ]       ]     , env bs' $ \ ~((bs25,bs100,bs1k,bs10k,bs100k,bs1mm),(bs25L,bs100L,bs1kL,bs10kL,bs100kL,bs1mmL)) ->       bgroup "decode"       [ bgroup "25"         [ bench "memory" $ whnf cfob bs25-        , bench "base16-bytestring" $ whnf Bos.decode bs25L-        , bench "base16" $ whnf B16.decodeBase16 bs25L+        , bench "base16-bytestring" $ whnf Bos.decode bs25+        , bench "base16" $ whnf B16.decodeBase16Lenient bs25         ]       , bgroup "100"         [ bench "memory" $ whnf cfob bs100-        , bench "base16-bytestring" $ whnf Bos.decode bs100L-        , bench "base16" $ whnf B16.decodeBase16 bs100L+        , bench "base16-bytestring" $ whnf Bos.decode bs100+        , bench "base16" $ whnf B16.decodeBase16Lenient bs100         ]       , bgroup "1k"         [ bench "memory" $ whnf cfob bs1k-        , bench "base16-bytestring" $ whnf Bos.decode bs1kL-        , bench "base16" $ whnf B16.decodeBase16 bs1kL+        , bench "base16-bytestring" $ whnf Bos.decode bs1k+        , bench "base16" $ whnf B16.decodeBase16Lenient bs1k         ]       , bgroup "10k"         [ bench "memory" $ whnf cfob bs10k-        , bench "base16-bytestring" $ whnf Bos.decode bs10kL-        , bench "base16" $ whnf B16.decodeBase16 bs10kL+        , bench "base16-bytestring" $ whnf Bos.decode bs10k+        , bench "base16" $ whnf B16.decodeBase16Lenient bs10k         ]       , bgroup "100k"         [ bench "memory" $ whnf cfob bs100k-        , bench "base16-bytestring" $ whnf Bos.decode bs100kL-        , bench "base16" $ whnf B16.decodeBase16 bs100kL+        , bench "base16-bytestring" $ whnf Bos.decode bs100k+        , bench "base16" $ whnf B16.decodeBase16Lenient bs100k         ]       , bgroup "1mm"         [ bench "memory" $ whnf cfob bs1mm-        , bench "base16-bytestring" $ whnf Bos.decode bs1mmL-        , bench "base16" $ whnf B16.decodeBase16 bs1mmL+        , bench "base16-bytestring" $ whnf Bos.decode bs1mm+        , bench "base16" $ whnf B16.decodeBase16Lenient bs1mm         ]       ]     ]
src/Data/ByteString/Base16.hs view
@@ -18,6 +18,7 @@ ( encodeBase16 , encodeBase16' , decodeBase16+, decodeBase16Lenient , isBase16 , isValidBase16 ) where@@ -48,13 +49,22 @@ encodeBase16' = encodeBase16_ {-# INLINE encodeBase16' #-} --- | Decode a padded Base16-encoded 'ByteString' value.+-- | Decode a Base16-encoded 'ByteString' value. -- -- See: <https://tools.ietf.org/html/rfc4648#section-8 RFC-4648 section 8> -- decodeBase16 :: ByteString -> Either Text ByteString decodeBase16 = decodeBase16_ {-# INLINE decodeBase16 #-}++-- | Decode a Base16-encoded 'ByteString' value leniently, using a+-- strategy that never fails+--+-- N.B.: this is not RFC 4648-compliant+--+decodeBase16Lenient :: ByteString -> ByteString+decodeBase16Lenient = decodeBase16Lenient_+{-# INLINE decodeBase16Lenient #-}  -- | Tell whether a 'ByteString' value is base16 encoded. --
src/Data/ByteString/Base16/Internal/Head.hs view
@@ -6,11 +6,13 @@ module Data.ByteString.Base16.Internal.Head ( encodeBase16_ , decodeBase16_+, decodeBase16Lenient_ ) where   #include "MachDeps.h" +import Data.ByteString (empty) import Data.ByteString.Internal import Data.ByteString.Base16.Internal.Tables #if WORD_SIZE_IN_BITS == 32@@ -60,5 +62,27 @@           (castPtr dptr)           (castPtr (plusPtr sptr soff))           (plusPtr sptr (soff + slen))+          0   where     (!q, !r) = slen `divMod` 2++decodeBase16Lenient_ :: ByteString -> ByteString+decodeBase16Lenient_ (PS !sfp !soff !slen)+  | slen == 0 = empty+  | otherwise = unsafeDupablePerformIO $ do+    dfp <- mallocPlainForeignPtrBytes dlen+    withForeignPtr dfp $ \dptr ->+      withForeignPtr dtableHi $ \hi ->+      withForeignPtr dtableLo $ \lo ->+      withForeignPtr sfp $ \sptr ->+        lenientLoop+          dfp+          hi+          lo+          (castPtr dptr)+          (castPtr (plusPtr sptr soff))+          (plusPtr sptr (soff + slen))+          0+  where+    (!q, _) = slen `divMod` 2+    !dlen = q * 2
src/Data/ByteString/Base16/Internal/Utils.hs view
@@ -4,9 +4,13 @@ ( aix , w32 , w64+, reChunk , writeNPlainForeignPtrBytes ) where +import Data.ByteString (ByteString)+import qualified Data.ByteString as B+ import Foreign.ForeignPtr import Foreign.Ptr import Foreign.Storable@@ -17,6 +21,7 @@  import System.IO.Unsafe + -- | Read 'Word8' index off alphabet addr -- aix :: Word8 -> Addr# -> Word8@@ -47,3 +52,22 @@   where     go !_ [] = return ()     go !p (x:xs) = poke p x >> go (plusPtr p 1) xs++-- | Form a list of chunks, and rechunk the list of bytestrings+-- into length multiples of 2+--+reChunk :: [ByteString] -> [ByteString]+reChunk [] = []+reChunk (c:cs) = case B.length c `divMod` 2 of+    (_, 0) -> c : reChunk cs+    (n, _) -> case B.splitAt (n * 2) c of+      ~(m, q) -> m : cont_ q cs+  where+    cont_ q [] = [q]+    cont_ q (a:as) = case B.splitAt 1 a of+      ~(x, y) -> let q' = B.append q x+        in if B.length q' == 2+          then+            let as' = if B.null y then as else y:as+            in q' : reChunk as'+          else cont_ q' as
src/Data/ByteString/Base16/Internal/W16/Loop.hs view
@@ -1,6 +1,7 @@ {-# LANGUAGE BangPatterns #-} {-# LANGUAGE CPP #-} {-# LANGUAGE MagicHash #-}+{-# LANGUAGE MultiWayIf #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE TypeApplications #-} -- |@@ -17,6 +18,7 @@ module Data.ByteString.Base16.Internal.W16.Loop ( innerLoop , decodeLoop+, lenientLoop ) where  @@ -71,9 +73,14 @@   -> Ptr Word8   -> Ptr Word8   -> Ptr Word8+  -> Int   -> IO (Either Text ByteString)-decodeLoop !dfp !hi !lo !dptr !sptr !end = go dptr sptr 0+decodeLoop !dfp !hi !lo !dptr !sptr !end !nn = go dptr sptr nn   where+    err !src = return . Left . T.pack+      $ "invalid character at offset: "+      ++ show (src `minusPtr` sptr)+     go !dst !src !n       | src == end = return (Right (PS dfp 0 n))       | otherwise = do@@ -83,10 +90,48 @@         !a <- peekByteOff hi (fromIntegral x)         !b <- peekByteOff lo (fromIntegral y) -        if a == 0xff || b == 0xff-        then return . Left . T.pack-          $ "invalid character at offset: "-          ++ show (src `minusPtr` sptr)-        else do-          poke dst (a .|. b)-          go (plusPtr dst 1) (plusPtr src 2) (n + 1)+        if+          | a == 0xff -> err src+          | b == 0xff -> err (plusPtr src 1)+          | otherwise -> do+            poke dst (a .|. b)+            go (plusPtr dst 1) (plusPtr src 2) (n + 1)+{-# INLINE decodeLoop #-}+++-- | Lenient lazy hex decoding loop optimized for 16-bit architectures.+-- When the 'Right' case is returned, a byte+--+lenientLoop+  :: ForeignPtr Word8+  -> Ptr Word8+  -> Ptr Word8+  -> Ptr Word8+  -> Ptr Word8+  -> Ptr Word8+  -> Int+  -> IO ByteString+lenientLoop !dfp !hi !lo !dptr !sptr !end !nn = goHi dptr sptr nn+  where+    goHi !dst !src !n+      | src == end = return (PS dfp 0 n)+      | otherwise = do+        !x <- peek @Word8 src+        !a <- peekByteOff hi (fromIntegral x)++        if+          | a == 0xff -> goHi dst (plusPtr src 1) n+          | otherwise -> goLo dst (plusPtr src 1) a n++    goLo !dst !src !a !n+      | src == end = return (PS dfp 0 n)+      | otherwise = do+        !y <- peek @Word8 src+        !b <- peekByteOff lo (fromIntegral y)++        if+          | b == 0xff -> goLo dst (plusPtr src 1) a n+          | otherwise -> do+            poke dst (a .|. b)+            goHi (plusPtr dst 1) (plusPtr src 1) (n + 1)+{-# LANGUAGE lenientLoop #-}
src/Data/ByteString/Base16/Internal/W32/Loop.hs view
@@ -17,12 +17,14 @@ module Data.ByteString.Base16.Internal.W32.Loop ( innerLoop , decodeLoop+, lenientLoop ) where   import Data.Bits import Data.ByteString.Internal import Data.ByteString.Base16.Internal.Utils+import qualified Data.ByteString.Base16.Internal.W16.Loop as W16 import Data.Text (Text) import qualified Data.Text as T @@ -47,22 +49,9 @@      !alphabet = "0123456789abcdef"# -    tailRound16 !dst !src-      | src == end = return ()-      | otherwise = do-        !t <- peek @Word8 src--        let !a = fromIntegral (lix (unsafeShiftR t 4))-            !b = fromIntegral (lix t)--        let !w = a .|. (unsafeShiftL b 8)--        poke @Word16 dst w--        tailRound16 (plusPtr dst 2) (plusPtr src 1)-     go !dst !src-      | plusPtr src 3 >= end = tailRound16 (castPtr dst) (castPtr src)+      | plusPtr src 3 >= end =+        W16.innerLoop (castPtr dst) (castPtr src) end       | otherwise = do #ifdef WORDS_BIGENDIAN         !t <- peek src@@ -86,6 +75,7 @@         poke @Word32 dst xx          go (plusPtr dst 4) (plusPtr src 2)+{-# INLINE innerLoop #-}  -- | Hex decoding loop optimized for 32-bit architectures --@@ -96,33 +86,17 @@   -> Ptr Word16   -> Ptr Word32   -> Ptr Word8+  -> Int   -> IO (Either Text ByteString)-decodeLoop !dfp !hi !lo !dptr !sptr !end = go dptr sptr 0+decodeLoop !dfp !hi !lo !dptr !sptr !end !nn = go dptr sptr nn   where     err !src = return . Left . T.pack       $ "invalid character at offset: "       ++ show (src `minusPtr` sptr) -    tailRound16 !dst !src !n-      | src == end = return (Right (PS dfp 0 n))-      | otherwise = do-        !x <- peek @Word8 src-        !y <- peek @Word8 (plusPtr src 1)--        !a <- peekByteOff hi (fromIntegral x)-        !b <- peekByteOff lo (fromIntegral y)--        if a == 0xff || b == 0xff-        then return . Left . T.pack-          $ "invalid character at offset: "-          ++ show (src `minusPtr` sptr)-        else do-          poke @Word8 dst (a .|. b)-          return (Right (PS dfp 0 (n + 1)))--     go !dst !src !n-      | plusPtr src 3 >= end = tailRound16 (castPtr dst) (castPtr src) n+      | plusPtr src 3 >= end =+        W16.decodeLoop dfp hi lo (castPtr dst) (castPtr src) end n       | otherwise = do #ifdef WORDS_BIGENDIAN         !t <- peek @Word32 src@@ -150,3 +124,15 @@             poke @Word16 dst zz             go (plusPtr dst 2) (plusPtr src 4) (n + 2) {-# INLINE decodeLoop #-}++lenientLoop+  :: ForeignPtr Word8+  -> Ptr Word8+  -> Ptr Word8+  -> Ptr Word8+  -> Ptr Word8+  -> Ptr Word8+  -> Int+  -> IO ByteString+lenientLoop !dfp !hi !lo !dptr !sptr !end !nn =+  W16.lenientLoop dfp hi lo dptr sptr end nn
src/Data/ByteString/Base16/Internal/W64/Loop.hs view
@@ -17,12 +17,14 @@ module Data.ByteString.Base16.Internal.W64.Loop ( innerLoop , decodeLoop+, lenientLoop ) where   import Data.Bits import Data.ByteString.Internal import Data.ByteString.Base16.Internal.Utils+import qualified Data.ByteString.Base16.Internal.W32.Loop as W32 import Data.Text (Text) import qualified Data.Text as T @@ -47,48 +49,9 @@      !alphabet = "0123456789abcdef"# -    tailRound16 !dst !src-      | src == end = return ()-      | otherwise = do-        !t <- peek @Word8 src--        let !a = fromIntegral (lix (unsafeShiftR t 4))-            !b = fromIntegral (lix t)--        let !w = a .|. (unsafeShiftL b 8)--        poke @Word16 dst w--        tailRound16 (plusPtr dst 2) (plusPtr src 1)--    tailRound32 !dst !src-      | plusPtr src 3 >= end = tailRound16 (castPtr dst) (castPtr src)-      | otherwise = do-#ifdef WORDS_BIGENDIAN-        !t <- peek src-#else-        !t <- byteSwap16 <$> peek @Word16 src-#endif-        let !a = unsafeShiftR t 12-            !b = unsafeShiftR t 8-            !c = unsafeShiftR t 4--        let !w = w32 (lix a)-            !x = w32 (lix b)-            !y = w32 (lix c)-            !z = w32 (lix t)--        let !xx = w-              .|. (unsafeShiftL x 8)-              .|. (unsafeShiftL y 16)-              .|. (unsafeShiftL z 24)--        poke @Word32 dst xx--        tailRound32 (plusPtr dst 4) (plusPtr src 2)-     go !dst !src-      | plusPtr src 7 >= end = tailRound32 (castPtr dst) (castPtr src)+      | plusPtr src 7 >= end =+        W32.innerLoop (castPtr dst) (castPtr src) end       | otherwise = do #ifdef WORDS_BIGENDIAN         !t <- peek src@@ -139,61 +102,17 @@   -> Ptr Word32   -> Ptr Word64   -> Ptr Word8+  -> Int   -> IO (Either Text ByteString)-decodeLoop !dfp !hi !lo !dptr !sptr !end = go dptr sptr 0+decodeLoop !dfp !hi !lo !dptr !sptr !end !nn = go dptr sptr nn   where     err !src = return . Left . T.pack       $ "invalid character at offset: "       ++ show (src `minusPtr` sptr) -    tailRound16 !dst !src !n-      | src == end = return (Right (PS dfp 0 n))-      | otherwise = do-        !x <- peek @Word8 src-        !y <- peek @Word8 (plusPtr src 1)--        !a <- peekByteOff @Word8 hi (fromIntegral x)-        !b <- peekByteOff @Word8 lo (fromIntegral y)--        if-          | a == 0xff -> err src-          | b == 0xff -> err (plusPtr src 1)-          | otherwise -> do-            poke dst (a .|. b)-            return (Right (PS dfp 0 (n + 1)))--    tailRound32 !dst !src !n-      | plusPtr src 3 >= end = tailRound16 (castPtr dst) (castPtr src) n-      | otherwise = do-#ifdef WORDS_BIGENDIAN-        !t <- peek @Word32 src-#else-        !t <- byteSwap32 <$> peek @Word32 src-#endif-        let !w = fromIntegral ((unsafeShiftR t 24) .&. 0xff)-            !x = fromIntegral ((unsafeShiftR t 16) .&. 0xff)-            !y = fromIntegral ((unsafeShiftR t 8) .&. 0xff)-            !z = (fromIntegral (t .&. 0xff))--        !a <- peekByteOff @Word8 hi w-        !b <- peekByteOff @Word8 lo x-        !c <- peekByteOff @Word8 hi y-        !d <- peekByteOff @Word8 lo z--        let !zz = fromIntegral (a .|. b)-               .|. (unsafeShiftL (fromIntegral (c .|. d)) 8)--        if-          | a == 0xff -> err src-          | b == 0xff -> err (plusPtr src 1)-          | c == 0xff -> err (plusPtr src 2)-          | d == 0xff -> err (plusPtr src 3)-          | otherwise -> do-            poke @Word16 dst zz-            tailRound16 (plusPtr dst 2) (plusPtr src 4) (n + 2)-     go !dst !src !n-      | plusPtr src 7 >= end = tailRound32 (castPtr dst) (castPtr src) n+      | plusPtr src 7 >= end =+        W32.decodeLoop dfp hi lo (castPtr dst) (castPtr src) end n       | otherwise = do #ifdef WORDS_BIGENDIAN         !tt <- peek @Word64 src@@ -236,3 +155,15 @@             poke @Word32 dst zz             go (plusPtr dst 4) (plusPtr src 8) (n + 4) {-# INLINE decodeLoop #-}++lenientLoop+  :: ForeignPtr Word8+  -> Ptr Word8+  -> Ptr Word8+  -> Ptr Word8+  -> Ptr Word8+  -> Ptr Word8+  -> Int+  -> IO ByteString+lenientLoop !dfp !hi !lo !dptr !sptr !end !nn =+  W32.lenientLoop dfp hi lo dptr sptr end nn
src/Data/ByteString/Lazy/Base16.hs view
@@ -16,6 +16,7 @@ ( encodeBase16 , encodeBase16' , decodeBase16+, decodeBase16Lenient , isBase16 , isValidBase16 ) where@@ -23,9 +24,11 @@  import Prelude hiding (all, elem) -import Data.ByteString.Lazy (all, elem)+import qualified Data.ByteString as B+import Data.ByteString.Lazy (all, elem, fromChunks, toChunks) import Data.ByteString.Lazy.Internal (ByteString(..)) import qualified Data.ByteString.Base16.Internal.Head as B16+import Data.ByteString.Base16.Internal.Utils (reChunk) import Data.Either import qualified Data.Text as T import Data.Text.Lazy (Text)@@ -57,6 +60,19 @@ decodeBase16 Empty = Right Empty decodeBase16 (Chunk b bs) = Chunk <$> B16.decodeBase16_ b <*> decodeBase16 bs {-# INLINE decodeBase16 #-}++-- | Decode a Base16-encoded 'ByteString' value leniently, using a+-- strategy that never fails+--+-- N.B.: this is not RFC 4648-compliant. It may give you garbage if you're not careful!+--+decodeBase16Lenient :: ByteString -> ByteString+decodeBase16Lenient = fromChunks+  . fmap B16.decodeBase16Lenient_+  . reChunk+  . fmap (B.filter (flip elem "0123456789abcdef"))+  . toChunks+{-# INLINE decodeBase16Lenient #-}  -- | Tell whether a lazy 'ByteString' value is base16 encoded. --
src/Data/Text/Encoding/Base16.hs view
@@ -14,14 +14,19 @@ module Data.Text.Encoding.Base16 ( encodeBase16 , decodeBase16+, decodeBase16With+, decodeBase16Lenient , isBase16 , isValidBase16 ) where  +import Data.Bifunctor (first)+import Data.ByteString (ByteString) import qualified Data.ByteString.Base16 as B16- import Data.Text (Text)+import qualified Data.Text as T+import Data.Text.Encoding.Base16.Error (Base16Error(..)) import qualified Data.Text.Encoding as T  -- | Encode a 'Text' value in Base16 with padding.@@ -32,13 +37,47 @@ encodeBase16 = B16.encodeBase16 . T.encodeUtf8 {-# INLINE encodeBase16 #-} --- | Decode a padded Base16-encoded 'Text' value+-- | Decode a Base16-encoded lazy 'Text' value. -- -- See: <https://tools.ietf.org/html/rfc4648#section-8 RFC-4648 section 8> ---decodeBase16 :: Text -> Either Text Text-decodeBase16 = fmap T.decodeUtf8 . B16.decodeBase16 . T.encodeUtf8+decodeBase16 :: Text -> Either T.Text Text+decodeBase16 = fmap T.decodeLatin1 . B16.decodeBase16 . T.encodeUtf8 {-# INLINE decodeBase16 #-}++-- | Attempt to decode a lazy 'Text' value as Base16, converting from+-- 'ByteString' to 'Text' according to some encoding function. In practice,+-- This is something like 'decodeUtf8'', which may produce an error.+--+-- See: <https://tools.ietf.org/html/rfc4648#section-8 RFC-4648 section 8>+--+-- Example:+--+-- @+-- 'decodeBase16With' 'T.decodeUtf8''+--   :: 'Text' -> 'Either' ('Base16Error' 'UnicodeException') 'Text'+-- @+--+decodeBase16With+    :: (ByteString -> Either err Text)+      -- ^ convert a bytestring to text (e.g. 'T.decodeUtf8'')+    -> Text+      -- ^ Input text to decode+    -> Either (Base16Error err) Text+decodeBase16With f t = case B16.decodeBase16 $ T.encodeUtf8 t of+  Left de -> Left $ DecodeError de+  Right a -> first ConversionError (f a)+{-# INLINE decodeBase16With #-}++-- | Decode a Base16-encoded lazy 'Text' value leniently, using a+-- strategy that never fails, catching unicode exceptions raised in the+-- process of converting to text values.+--+-- N.B.: this is not RFC 4648-compliant.+--+decodeBase16Lenient :: Text -> Text+decodeBase16Lenient = T.decodeLatin1 . B16.decodeBase16Lenient . T.encodeUtf8+{-# INLINE decodeBase16Lenient #-}  -- | Tell whether a 'Text' value is Base16-encoded. --
+ src/Data/Text/Encoding/Base16/Error.hs view
@@ -0,0 +1,32 @@+-- |+-- Module       : Data.Text.Encoding.Base16.Error+-- Copyright 	: (c) 2019 Emily Pillmore+-- License	: BSD-style+--+-- Maintainer	: Emily Pillmore <emilypi@cohomolo.gy>+-- Stability	: Experimental+-- Portability	: portable+--+-- This module contains the error types raised (not as exceptions!)+-- in the decoding process.+--+module Data.Text.Encoding.Base16.Error+( Base16Error(..)+) where+++import Data.Text (Text)++-- | This data type represents the type of decoding errors of+-- various kinds as they pertain to decoding 'Text' values.+-- Namely, to distinguish between decoding errors from opaque+-- unicode exceptions caught in the unicode decoding process.+--+data Base16Error e+  = DecodeError Text+    -- ^ The error associated with decoding failure+    -- as a result of the Base16 decoding process+  | ConversionError e+    -- ^ The error associated with the decoding failure+    -- as a result of the conversion process+  deriving (Eq, Show)
src/Data/Text/Lazy/Encoding/Base16.hs view
@@ -14,14 +14,18 @@ module Data.Text.Lazy.Encoding.Base16 ( encodeBase16 , decodeBase16+, decodeBase16With+, decodeBase16Lenient , isBase16 , isValidBase16 ) where  +import Data.Bifunctor (first)+import Data.ByteString.Lazy (ByteString) import qualified Data.ByteString.Lazy.Base16 as B16L- import qualified Data.Text as T+import Data.Text.Encoding.Base16.Error (Base16Error(..)) import Data.Text.Lazy (Text) import qualified Data.Text.Lazy.Encoding as TL @@ -33,13 +37,50 @@ encodeBase16 = B16L.encodeBase16 . TL.encodeUtf8 {-# INLINE encodeBase16 #-} --- | Decode a padded Base16-encoded lazy 'Text' value+-- | Decode a Base16-encoded lazy 'Text' value. -- -- See: <https://tools.ietf.org/html/rfc4648#section-8 RFC-4648 section 8> -- decodeBase16 :: Text -> Either T.Text Text-decodeBase16 = fmap TL.decodeUtf8 . B16L.decodeBase16 . TL.encodeUtf8+decodeBase16 = fmap TL.decodeLatin1 . B16L.decodeBase16 . TL.encodeUtf8 {-# INLINE decodeBase16 #-}++-- | Attempt to decode a lazy 'Text' value as Base16, converting from+-- 'ByteString' to 'Text' according to some encoding function. In practice,+-- This is something like 'decodeUtf8'', which may produce an error.+--+-- See: <https://tools.ietf.org/html/rfc4648#section-8 RFC-4648 section 8>+--+-- Example:+--+-- @+-- 'decodeBase16With' 'T.decodeUtf8''+--   :: 'Text' -> 'Either' ('Base16Error' 'UnicodeException') 'Text'+-- @+--+decodeBase16With+    :: (ByteString -> Either err Text)+      -- ^ convert a bytestring to text (e.g. 'T.decodeUtf8'')+    -> Text+      -- ^ Input text to decode+    -> Either (Base16Error err) Text+decodeBase16With f t = case B16L.decodeBase16 $ TL.encodeUtf8 t of+  Left de -> Left $ DecodeError de+  Right a -> first ConversionError (f a)+{-# INLINE decodeBase16With #-}++-- | Decode a Base16-encoded lazy 'Text' value leniently, using a+-- strategy that never fails.+--+-- /Warning/: in the conversion to unicode text, exceptions may be thrown.+-- Please use 'decodeBase16'' if you are unsure if you are working with+-- base16-encoded values, or if you expect garbage.+--+-- N.B.: this is not RFC 4648-compliant. It may give you garbage if you're not careful!+--+decodeBase16Lenient :: Text -> Text+decodeBase16Lenient = TL.decodeLatin1 . B16L.decodeBase16Lenient . TL.encodeUtf8+{-# INLINE decodeBase16Lenient #-}  -- | Tell whether a lazy 'Text' value is Base16-encoded. --
test/Base16Tests.hs view
@@ -1,3 +1,4 @@+{-# LANGUAGE BangPatterns #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE PackageImports #-} {-# LANGUAGE TypeApplications #-}@@ -9,7 +10,7 @@  import Data.Bifunctor import Data.ByteString (ByteString)-import Data.ByteString.Lazy (fromStrict)+import Data.ByteString.Lazy (fromStrict, toStrict) import "base16" Data.ByteString.Base16 as B16 import "base16" Data.ByteString.Lazy.Base16 as B16L import "memory" Data.ByteArray.Encoding as Mem@@ -30,6 +31,7 @@     [ testVectors     , sanityTests     , alphabetTests+    , lenientTests     ]  testVectors :: TestTree@@ -157,12 +159,18 @@       , conforms 4       , conforms 5       , conforms 6+      , conforms 32+      , conforms 33+      , conforms 1001       ]     , testGroup "Lazy"       [ conformsL 0       , conformsL 4       , conformsL 5       , conformsL 6+      , conformsL 32+      , conformsL 33+      , conformsL 1001       ]     ]   where@@ -177,3 +185,41 @@       let b = B16L.encodeBase16' bs       assertBool ("failed validity: " ++ show b) $ B16L.isValidBase16 b       assertBool ("failed correctness: " ++ show b) $ B16L.isBase16 b++lenientTests :: TestTree+lenientTests = testGroup "Lenient Tests"+    [ testGroup "strict encode/lenient decode"+      [ testCaseB16 "" ""+      , testCaseB16 "f" "6+6"+      , testCaseB16 "fo" "6$6+6|f"+      , testCaseB16 "foo" "==========6$$66()*f6f"+      , testCaseB16 "foob" "66^%$&^6f6f62"+      , testCaseB16 "fooba" "666f()*#@6f#)(@*)6()*)2()61"+      , testCaseB16 "foobar" "6@6@6@f@6@f@6@2@6@1@7@2++++++++++++++++++++++++"+      ]+    , testGroup "lazy encode/decode"+      [ testCaseB16L "" ""+      , testCaseB16L "f" "6+++++++____++++++======*%$@#%#^*$^6"+      , testCaseB16L "fo" "6$6+6|f"+      , testCaseB16L "foo" "==========6$$66()*f6f"+      , testCaseB16L "foob" "66^%$&^6f6f62"+      , testCaseB16L "fooba" "666f()*#@6f#)(@*)6()*)2()61"+      , testCaseB16L "foobar" "6@6@6@f@6@f@6@2@6@1@7@2++++++++++++++++++++++++"+      ]+    ]+  where+    testCaseB16 s t =+      testCaseSteps (show $ if s == "" then "empty" else s) $ \step -> do+        let t0 = B16.decodeBase16 (B16.encodeBase16' s)+            t1 = B16.decodeBase16Lenient t++        step "compare decoding"+        t0 @=? Right t1++    testCaseB16L s t =+      testCaseSteps (show $ if s == "" then "empty" else s) $ \step -> do+        let t0 = fmap toStrict $ B16L.decodeBase16 (B16L.encodeBase16' s)+            t1 = Right . toStrict $ B16L.decodeBase16Lenient t++        step "compare decoding"+        t0 @=? t1