base64-bytestring 0.1.2.0 → 1.0.0.0
raw patch · 5 files changed
+271/−73 lines, 5 filesPVP ok
version bump matches the API change (PVP)
API changes (from Hackage documentation)
+ Data.ByteString.Base64.Lazy: decode :: ByteString -> Either String ByteString
+ Data.ByteString.Base64.Lazy: decodeLenient :: ByteString -> ByteString
+ Data.ByteString.Base64.Lazy: encode :: ByteString -> ByteString
+ Data.ByteString.Base64.URL.Lazy: decode :: ByteString -> Either String ByteString
+ Data.ByteString.Base64.URL.Lazy: decodeLenient :: ByteString -> ByteString
+ Data.ByteString.Base64.URL.Lazy: encode :: ByteString -> ByteString
Files
- Data/ByteString/Base64/Internal.hs +25/−0
- Data/ByteString/Base64/Lazy.hs +61/−0
- Data/ByteString/Base64/URL/Lazy.hs +61/−0
- base64-bytestring.cabal +4/−2
- tests/Tests.hs +120/−71
Data/ByteString/Base64/Internal.hs view
@@ -20,6 +20,7 @@ , joinWith , done , peek8, poke8, peek8_32+ , reChunkIn ) where import Data.Bits ((.|.), (.&.), shiftL, shiftR)@@ -258,3 +259,27 @@ done :: Integral a => a done = 99 {-# INLINE done #-}++-- This takes a list of ByteStrings, and returns a list in which each+-- (apart from possibly the last) has length that is a multiple of n+reChunkIn :: Int -> [ByteString] -> [ByteString]+reChunkIn _ [] = []+reChunkIn _ [y] = [y]+reChunkIn n (y : ys) = case B.length y `divMod` n of+ (_, 0) ->+ y : reChunkIn n ys+ (d, _) ->+ case B.splitAt (d * n) y of+ (prefix, suffix) -> prefix : fixup suffix ys+ where fixup acc [] = [acc]+ fixup acc (z : zs) = case B.splitAt (n - B.length acc) z of+ (prefix, suffix) ->+ let acc' = acc `B.append` prefix+ in if B.length acc' == n+ then let zs' = if B.null suffix+ then zs+ else suffix : zs+ in acc' : reChunkIn n zs'+ else -- suffix must be null+ fixup acc' zs+
+ Data/ByteString/Base64/Lazy.hs view
@@ -0,0 +1,61 @@+{-# LANGUAGE CPP #-}+#if __GLASGOW_HASKELL__ >= 702+{-# LANGUAGE Trustworthy #-}+#endif++-- |+-- Module : Data.ByteString.Base64.Lazy+-- Copyright : (c) 2012 Ian Lynagh+--+-- License : BSD-style+-- Maintainer : bos@serpentine.com+-- Stability : experimental+-- Portability : GHC+--+-- Fast and efficient encoding and decoding of base64-encoded+-- lazy bytestrings.++module Data.ByteString.Base64.Lazy+ (+ encode+ , decode+ , decodeLenient+ ) where++import Data.ByteString.Base64.Internal+import qualified Data.ByteString.Base64 as B64+import qualified Data.ByteString as S+import qualified Data.ByteString.Lazy as L+import qualified Data.ByteString.Lazy.Char8 as LC+import Data.Char++-- | Encode a string into base64 form. The result will always be a+-- multiple of 4 bytes in length.+encode :: L.ByteString -> L.ByteString+encode = L.fromChunks . map B64.encode . reChunkIn 3 . L.toChunks++-- | Decode a base64-encoded string. This function strictly follows+-- the specification in RFC 4648,+-- <http://www.apps.ietf.org/rfc/rfc4648.html>.+decode :: L.ByteString -> Either String L.ByteString+decode b = -- Returning an Either type means that the entire result will+ -- need to be in memory at once anyway, so we may as well+ -- keep it simple and just convert to and from a strict byte+ -- string+ -- TODO: Use L.{fromStrict,toStrict} once we can rely on+ -- a new enough bytestring+ case B64.decode $ S.concat $ L.toChunks b of+ Left err -> Left err+ Right b' -> Right $ L.fromChunks [b']++-- | Decode a base64-encoded string. This function is lenient in+-- following the specification from RFC 4648,+-- <http://www.apps.ietf.org/rfc/rfc4648.html>, and will not generate+-- parse errors no matter how poor its input.+decodeLenient :: L.ByteString -> L.ByteString+decodeLenient = L.fromChunks . map B64.decodeLenient . reChunkIn 4 . L.toChunks+ . LC.filter goodChar+ where -- We filter out and '=' padding here, but B64.decodeLenient+ -- handles that+ goodChar c = isAlphaNum c || c == '+' || c == '/'+
+ Data/ByteString/Base64/URL/Lazy.hs view
@@ -0,0 +1,61 @@+{-# LANGUAGE CPP #-}+#if __GLASGOW_HASKELL__ >= 702+{-# LANGUAGE Trustworthy #-}+#endif++-- |+-- Module : Data.ByteString.Base64.URL.Lazy+-- Copyright : (c) 2012 Ian Lynagh+--+-- License : BSD-style+-- Maintainer : bos@serpentine.com+-- Stability : experimental+-- Portability : GHC+--+-- Fast and efficient encoding and decoding of base64-encoded+-- lazy bytestrings.++module Data.ByteString.Base64.URL.Lazy+ (+ encode+ , decode+ , decodeLenient+ ) where++import Data.ByteString.Base64.Internal+import qualified Data.ByteString.Base64.URL as B64+import qualified Data.ByteString as S+import qualified Data.ByteString.Lazy as L+import qualified Data.ByteString.Lazy.Char8 as LC+import Data.Char++-- | Encode a string into base64 form. The result will always be a+-- multiple of 4 bytes in length.+encode :: L.ByteString -> L.ByteString+encode = L.fromChunks . map B64.encode . reChunkIn 3 . L.toChunks++-- | Decode a base64-encoded string. This function strictly follows+-- the specification in RFC 4648,+-- <http://www.apps.ietf.org/rfc/rfc4648.html>.+decode :: L.ByteString -> Either String L.ByteString+decode b = -- Returning an Either type means that the entire result will+ -- need to be in memory at once anyway, so we may as well+ -- keep it simple and just convert to and from a strict byte+ -- string+ -- TODO: Use L.{fromStrict,toStrict} once we can rely on+ -- a new enough bytestring+ case B64.decode $ S.concat $ L.toChunks b of+ Left err -> Left err+ Right b' -> Right $ L.fromChunks [b']++-- | Decode a base64-encoded string. This function is lenient in+-- following the specification from RFC 4648,+-- <http://www.apps.ietf.org/rfc/rfc4648.html>, and will not generate+-- parse errors no matter how poor its input.+decodeLenient :: L.ByteString -> L.ByteString+decodeLenient = L.fromChunks . map B64.decodeLenient . reChunkIn 4 . L.toChunks+ . LC.filter goodChar+ where -- We filter out and '=' padding here, but B64.decodeLenient+ -- handles that+ goodChar c = isAlphaNum c || c == '-' || c == '_'+
base64-bytestring.cabal view
@@ -1,5 +1,5 @@ name: base64-bytestring-version: 0.1.2.0+version: 1.0.0.0 synopsis: Fast base64 encoding and deconding for ByteStrings description: Fast base64 encoding and deconding for ByteStrings homepage: https://github.com/bos/base64-bytestring@@ -8,7 +8,7 @@ license-file: LICENSE author: Bryan O'Sullivan <bos@serpentine.com> maintainer: Bryan O'Sullivan <bos@serpentine.com>-copyright: 2010-2011 Bryan O'Sullivan+copyright: 2010-2012 Bryan O'Sullivan category: Data build-type: Simple cabal-version: >=1.8@@ -23,6 +23,8 @@ exposed-modules: Data.ByteString.Base64 Data.ByteString.Base64.URL+ Data.ByteString.Base64.Lazy+ Data.ByteString.Base64.URL.Lazy other-modules: Data.ByteString.Base64.Internal
tests/Tests.hs view
@@ -1,4 +1,4 @@-{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE OverloadedStrings, ScopedTypeVariables #-} {-# OPTIONS_GHC -fno-warn-orphans #-} module Main (main) where@@ -10,44 +10,65 @@ import Test.QuickCheck (Arbitrary(..), Positive(..)) import Control.Monad (liftM)-import qualified Data.ByteString.Base64 as Base64-import qualified Data.ByteString.Base64.URL as Base64URL+import qualified Data.ByteString.Base64 as Base64+import qualified Data.ByteString.Base64.Lazy as LBase64+import qualified Data.ByteString.Base64.URL as Base64URL+import qualified Data.ByteString.Base64.URL.Lazy as LBase64URL import Data.ByteString (ByteString) import Data.ByteString.Char8 () import qualified Data.ByteString.Char8 as B+import qualified Data.ByteString.Lazy.Char8 as L+import Data.String import Test.HUnit hiding (Test) main :: IO () main = defaultMain tests +data Impl bs = Impl String+ (bs -> bs)+ (bs -> Either String bs)+ (bs -> bs)+ tests :: [Test] tests = [ testGroup "joinWith" [ testProperty "all_endsWith" joinWith_all_endsWith , testProperty "endsWith" joinWith_endsWith ]- , testGroup "Base64" [- testProperty "decodeEncode" $- genericDecodeEncode Base64.encode Base64.decode- , testProperty "decodeEncode Lenient" $- genericDecodeEncode Base64.encode- (liftM Right Base64.decodeLenient)- , testGroup "base64-string tests" base64_string_tests- ]- , testGroup "Base64URL" [+ , testsRegular $ Impl "Base64" Base64.encode Base64.decode Base64.decodeLenient+ , testsRegular $ Impl "LBase64" LBase64.encode LBase64.decode LBase64.decodeLenient+ , testsURL $ Impl "Base64URL" Base64URL.encode Base64URL.decode Base64URL.decodeLenient+ , testsURL $ Impl "LBase64URL" LBase64URL.encode LBase64URL.decode LBase64URL.decodeLenient+ ]++testsRegular :: (IsString bs, AllRepresentations bs, Show bs, Eq bs, Arbitrary bs) => Impl bs -> Test+testsRegular = testsWith base64_testData++testsURL :: (IsString bs, AllRepresentations bs, Show bs, Eq bs, Arbitrary bs) => Impl bs -> Test+testsURL = testsWith base64url_testData++testsWith :: (IsString bs, AllRepresentations bs, Show bs, Eq bs, Arbitrary bs)+ => [(bs, bs)] -> Impl bs -> Test+testsWith testData+ impl@(Impl name encode decode decodeLenient)+ = testGroup name [ testProperty "decodeEncode" $- genericDecodeEncode Base64URL.encode Base64URL.decode+ genericDecodeEncode encode decode , testProperty "decodeEncode Lenient" $- genericDecodeEncode Base64URL.encode- (liftM Right Base64URL.decodeLenient)- , testGroup "base64-string tests" base64url_string_tests+ genericDecodeEncode encode+ (liftM Right decodeLenient)+ , testGroup "base64-string tests" (string_tests testData impl) ]- ] instance Arbitrary ByteString where arbitrary = liftM B.pack arbitrary +-- Ideally the arbitrary instance would have arbitrary chunks as well as+-- arbitrary content+instance Arbitrary L.ByteString where+ arbitrary = liftM L.pack arbitrary+ joinWith_endsWith :: ByteString -> Positive Int -> ByteString -> Bool joinWith_endsWith brk (Positive int) str = brk `B.isSuffixOf` Base64.joinWith brk int str@@ -64,9 +85,10 @@ where k = B.length brk + min int (B.length str) -- | Decoding an encoded sintrg should produce the original string.-genericDecodeEncode :: (ByteString -> ByteString)- -> (ByteString -> Either String ByteString)- -> ByteString -> Bool+genericDecodeEncode :: (Arbitrary bs, Eq bs)+ => (bs -> bs)+ -> (bs -> Either String bs)+ -> bs -> Bool genericDecodeEncode enc dec x = case dec (enc x) of Left _ -> False Right x' -> x == x'@@ -76,60 +98,87 @@ -- Copyright (c) Ian Lynagh, 2005, 2007. -- -base64_string_tests :: [Test]-base64_string_tests =- base64_string_test Base64.encode Base64.decode testData ++- base64_string_test Base64.encode decodeURL testData- where decodeURL :: ByteString -> Either String ByteString- decodeURL = liftM Right Base64.decodeLenient- testData :: [(ByteString, ByteString)]- testData = [("", "")- ,("\0", "AA==")- ,("\255", "/w==")- ,("E", "RQ==")- ,("Ex", "RXg=")- ,("Exa", "RXhh")- ,("Exam", "RXhhbQ==")- ,("Examp", "RXhhbXA=")- ,("Exampl", "RXhhbXBs")- ,("Example", "RXhhbXBsZQ==")- ,("Ex\0am\254ple", "RXgAYW3+cGxl")- ,("Ex\0am\255ple", "RXgAYW3/cGxl")- ]+string_tests :: forall bs+ . (IsString bs, AllRepresentations bs, Show bs, Eq bs)+ => [(bs, bs)] -> Impl bs -> [Test]+string_tests testData (Impl _ encode decode decodeLenient) =+ base64_string_test encode decode testData +++ base64_string_test encode decodeLenient' testData+ where decodeLenient' :: bs -> Either String bs+ decodeLenient' = liftM Right decodeLenient --- | Same as the base64_string_tests but using the alternative alphabet-base64url_string_tests :: [Test]-base64url_string_tests =- base64_string_test Base64URL.encode Base64URL.decode testData ++- base64_string_test Base64URL.encode decodeURL testData- where decodeURL :: ByteString -> Either String ByteString- decodeURL = liftM Right Base64URL.decodeLenient- testData :: [(ByteString, ByteString)]- testData = [("", "")- ,("\0", "AA==")- ,("\255", "_w==")- ,("E", "RQ==")- ,("Ex", "RXg=")- ,("Exa", "RXhh")- ,("Exam", "RXhhbQ==")- ,("Examp", "RXhhbXA=")- ,("Exampl", "RXhhbXBs")- ,("Example", "RXhhbXBsZQ==")- ,("Ex\0am\254ple", "RXgAYW3-cGxl")- ,("Ex\0am\255ple", "RXgAYW3_cGxl")- ]+base64_testData :: IsString bs => [(bs, bs)]+base64_testData = [("", "")+ ,("\0", "AA==")+ ,("\255", "/w==")+ ,("E", "RQ==")+ ,("Ex", "RXg=")+ ,("Exa", "RXhh")+ ,("Exam", "RXhhbQ==")+ ,("Examp", "RXhhbXA=")+ ,("Exampl", "RXhhbXBs")+ ,("Example", "RXhhbXBsZQ==")+ ,("Ex\0am\254ple", "RXgAYW3+cGxl")+ ,("Ex\0am\255ple", "RXgAYW3/cGxl")+ ] +base64url_testData :: IsString bs => [(bs, bs)]+base64url_testData = [("", "")+ ,("\0", "AA==")+ ,("\255", "_w==")+ ,("E", "RQ==")+ ,("Ex", "RXg=")+ ,("Exa", "RXhh")+ ,("Exam", "RXhhbQ==")+ ,("Examp", "RXhhbXA=")+ ,("Exampl", "RXhhbXBs")+ ,("Example", "RXhhbXBsZQ==")+ ,("Ex\0am\254ple", "RXgAYW3-cGxl")+ ,("Ex\0am\255ple", "RXgAYW3_cGxl")+ ]+ -- | Generic test given encod enad decode funstions and a -- list of (plain, encoded) pairs-base64_string_test :: (ByteString -> ByteString)- -> (ByteString -> Either String ByteString)- -> [(ByteString, ByteString)] -> [Test]-base64_string_test enc dec testData = concat- [ [ testCase ("base64-string: Encode " ++ show plain)- (encoded_plain @?= encoded),- testCase ("base64-string: Decode " ++ show plain)- (decoded_encoded @?= Right plain) ]- | (plain, encoded) <- testData,+base64_string_test :: (AllRepresentations bs, Eq bs, Show bs)+ => (bs -> bs)+ -> (bs -> Either String bs)+ -> [(bs, bs)] -> [Test]+base64_string_test enc dec testData =+ [ testCase ("base64-string: Encode " ++ show plain)+ (encoded_plain @?= rawEncoded)+ | (rawPlain, rawEncoded) <- testData,+ -- For lazy ByteStrings, we want to check not only ["foo"], but+ -- also ["f","oo"], ["f", "o", "o"] and ["fo", "o"]. The+ -- allRepresentations function gives us all representations of a+ -- lazy ByteString.+ plain <- allRepresentations rawPlain, let encoded_plain = enc plain- decoded_encoded = dec encoded+ ] +++ [ testCase ("base64-string: Decode " ++ show encoded)+ (decoded_encoded @?= Right rawPlain)+ | (rawPlain, rawEncoded) <- testData,+ -- Again, we need to try all representations of lazy ByteStrings.+ encoded <- allRepresentations rawEncoded,+ let decoded_encoded = dec encoded ]++class AllRepresentations a where+ allRepresentations :: a -> [a]++instance AllRepresentations ByteString where+ allRepresentations bs = [bs]++instance AllRepresentations L.ByteString where+ -- TODO: Use L.toStrict instead of (B.concat . L.toChunks) once+ -- we can rely on a new enough bytestring+ allRepresentations = map L.fromChunks . allChunks . B.concat . L.toChunks+ where allChunks b+ | B.length b < 2 = [[b]]+ | otherwise+ = concat [ map (prefix :) (allChunks suffix)+ | let splits = zip (B.inits b) (B.tails b)+ -- We don't want the first split (empty prefix)+ -- The last split (empty suffix) gives us the+ -- [b] case (toChunks ignores an "" element).+ , (prefix, suffix) <- tail splits ]+