module Codec.MIME.Base64
( encodeRaw
, encodeRawString
, encodeRawPrim
, formatOutput
, decode
, decodeToString
, decodePrim
) where
import Data.Bits
import Data.Char
import Data.Word
import Data.Maybe
encodeRawString :: Bool -> String -> String
encodeRawString trail xs = encodeRaw trail (map (fromIntegral.ord) xs)
formatOutput :: Int -> Maybe String -> String -> String
formatOutput n mbTerm str
| n <= 0 = error ("Codec.MIME.Base64.formatOutput: negative line length " ++ show n)
| otherwise = chop n str
where
crlf :: String
crlf = fromMaybe "\r\n" mbTerm
chop _ "" = ""
chop i xs =
case splitAt i xs of
(as,"") -> as
(as,bs) -> as ++ crlf ++ chop i bs
encodeRaw :: Bool -> [Word8] -> String
encodeRaw trail bs = encodeRawPrim trail '+' '/' bs
encodeRawPrim :: Bool -> Char -> Char -> [Word8] -> String
encodeRawPrim trail ch62 ch63 ls = encoder ls
where
trailer xs ys
| not trail = xs
| otherwise = xs ++ ys
f = fromB64 ch62 ch63
encoder [] = []
encoder [x] = trailer (take 2 (encode3 f x 0 0 "")) "=="
encoder [x,y] = trailer (take 3 (encode3 f x y 0 "")) "="
encoder (x:y:z:ws) = encode3 f x y z (encoder ws)
encode3 :: (Word8 -> Char) -> Word8 -> Word8 -> Word8 -> String -> String
encode3 f a b c rs =
f (low6 (w24 `shiftR` 18)) :
f (low6 (w24 `shiftR` 12)) :
f (low6 (w24 `shiftR` 6)) :
f (low6 w24) : rs
where
w24 :: Word32
w24 = (fromIntegral a `shiftL` 16) +
(fromIntegral b `shiftL` 8) +
fromIntegral c
decodeToString :: String -> String
decodeToString str = map (chr.fromIntegral) $ decode str
decode :: String -> [Word8]
decode str = decodePrim '+' '/' str
decodePrim :: Char -> Char -> String -> [Word8]
decodePrim ch62 ch63 str = decoder $ takeUntilEnd str
where
takeUntilEnd "" = []
takeUntilEnd ('=':_) = []
takeUntilEnd (x:xs) =
case toB64 ch62 ch63 x of
Nothing -> takeUntilEnd xs
Just b -> b : takeUntilEnd xs
decoder :: [Word8] -> [Word8]
decoder [] = []
decoder [x] = take 1 (decode4 x 0 0 0 [])
decoder [x,y] = take 1 (decode4 x y 0 0 [])
decoder [x,y,z] = take 2 (decode4 x y z 0 [])
decoder (x:y:z:w:xs) = decode4 x y z w (decoder xs)
decode4 :: Word8 -> Word8 -> Word8 -> Word8 -> [Word8] -> [Word8]
decode4 a b c d rs =
(lowByte (w24 `shiftR` 16)) :
(lowByte (w24 `shiftR` 8)) :
(lowByte w24) : rs
where
w24 :: Word32
w24 =
(fromIntegral a) `shiftL` 18 .|.
(fromIntegral b) `shiftL` 12 .|.
(fromIntegral c) `shiftL` 6 .|.
(fromIntegral d)
toB64 :: Char -> Char -> Char -> Maybe Word8
toB64 a b ch
| ch >= 'A' && ch <= 'Z' = Just (fromIntegral (ord ch ord 'A'))
| ch >= 'a' && ch <= 'z' = Just (26 + fromIntegral (ord ch ord 'a'))
| ch >= '0' && ch <= '9' = Just (52 + fromIntegral (ord ch ord '0'))
| ch == a = Just 62
| ch == b = Just 63
| otherwise = Nothing
fromB64 :: Char -> Char -> Word8 -> Char
fromB64 ch62 ch63 x
| x < 26 = chr (ord 'A' + xi)
| x < 52 = chr (ord 'a' + (xi26))
| x < 62 = chr (ord '0' + (xi52))
| x == 62 = ch62
| x == 63 = ch63
| otherwise = error ("fromB64: index out of range " ++ show x)
where
xi :: Int
xi = fromIntegral x
low6 :: Word32 -> Word8
low6 x = fromIntegral (x .&. 0x3f)
lowByte :: Word32 -> Word8
lowByte x = (fromIntegral x) .&. 0xff