summaryrefslogtreecommitdiff
path: root/base64/base64.hs
blob: 91348fb895dab11d08fb8bba14fd0f43e3381fb6 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
import qualified Data.ByteString.Internal as BI
import qualified Data.ByteString.Lazy as BL
import qualified Data.ByteString as BS
import Data.Word (Word8, Word32)
import Data.Array.Unboxed(UArray,array)
import Data.Array.Base(unsafeAt)
import Data.Bits(shiftL,shiftR,(.&.))
import Foreign.Ptr (plusPtr)
import Foreign.Storable (peek, poke)
import Foreign.ForeignPtr (withForeignPtr)
import System.IO.Unsafe (unsafePerformIO)

-- |Perform base64 encoding of data from standard input
main :: IO()
main = BL.getContents>>=BL.putStr.BL.fromChunks.map encode64.reChunk.BL.toChunks

-- |Base64 index table 
tab64 :: UArray Word32 Word8
tab64 = array (0,63) $ zip [0..] $ map (BI.c2w) $ 
        ['A'..'Z']++['a'..'z']++['0'..'9']++['+','/']

-- |Encodes 3 octets into 4 sextets
enc64 :: (Word8,Word8,Word8)->(Word8,Word8,Word8,Word8)
enc64 (b1,b2,b3) = (t 3,t 2,t 1,t 0)
    where t x   = tab64 `unsafeAt` (n `shiftR` (x*6) .&. 63)
          f b n = fromIntegral b `shiftL` n
          n     = f b1 16 + f b2 8 + f b3 0

-- |Transforms list of ByteStrings to a new list of ByteStrings with 
-- lengths guaranteed to be multiples of 3 (excepting the last one)
-- Assumes that all input ByteStrings (excepting the last one) have 
-- at least a length of 3.
reChunk :: [BS.ByteString] -> [BS.ByteString]
reChunk (y:[]) = [y]
reChunk (y:z:zs) = let c = BS.length y `mod` 3 
                    in BS.append y (BS.take 3 z):(reChunk $ (BS.drop 3 z):zs)

-- |Wraps Base64 enode in encode64io in unsafePerformIO to use in 
-- pure code, Use this only if you trust my 'encode64io' code is free 
-- of side effects and indepedent of the environment. Good Luck!
encode64 :: BS.ByteString -> BS.ByteString
encode64 = unsafePerformIO . encode64io

-- |Base64 encode a strict ByteString using foreign pointers within the 
-- IO monad.
encode64io :: BS.ByteString -> IO BS.ByteString
encode64io (BI.PS ptr offs len) = do 
    bs <- BI.mallocByteString ln
    withForeignPtr ptr $ \fp -> do 
        withForeignPtr bs $ \fbs -> do 
            let end = fp `plusPtr` (len+offs) 
                in go (fp`plusPtr`offs) fbs end bs
    where ln=((len + 2) `div` 3) * 4
          go fp fbs end bs
            | fp >= end = return $ BI.fromForeignPtr bs 0 ln
            | fp `plusPtr`1 == end = cnv 1
            | fp `plusPtr`2 == end = cnv 2
            | otherwise = cnv 3
            where pok nn n v  = if n>1 && nn<n 
                                    then poke (fbs`plusPtr`n) (61::Word8) 
                                    else poke (fbs`plusPtr`n) v
                  pek nn n = if nn<n then return 0 else peek (fp`plusPtr`n)
                  cnv :: Int -> IO BS.ByteString
                  cnv n = pek n 0>>= \b1->pek n 1>>= \b2->pek n 2>>= \b3->
                             let (e1,e2,e3,e4) = enc64(b1,b2,b3) in
                             pok n 0 e1>>pok n 1 e2>>pok n 2 e3>>pok n 3 e4 >>
                                 go (fp `plusPtr` 3) (fbs`plusPtr`4) end bs