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
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
|
--------------------------------------------------------------------
-- |
-- Module : Codec.MIME.Base64
-- Copyright : (c) 2006-2009, Galois, Inc.
-- License : BSD3
--
-- Maintainer: Sigbjorn Finne <sigbjorn.finne@gmail.com>
-- Stability : provisional
-- Portability: portable
--
--
-- Base64 decoding and encoding routines, multiple entry
-- points for either depending on use and level of control
-- wanted over the encoded output (and its input form on the
-- decoding side.)
--
--------------------------------------------------------------------
module Codec.MIME.Base64
( encodeRaw -- :: Bool -> String -> [Word8]
, encodeRawString -- :: Bool -> String -> String
, encodeRawPrim -- :: Bool -> Char -> Char -> [Word8] -> String
, formatOutput -- :: Int -> Maybe String -> String -> String
, decode -- :: String -> [Word8]
, decodeToString -- :: String -> String
, decodePrim -- :: Char -> Char -> String -> [Word8]
) 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 n mbLT str@ formats @str@, splitting it
-- into lines of length @n@. The optional value lets you control what
-- line terminator sequence to use; the default is CRLF (as per MIME.)
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@ lets you control what non-alphanum characters to use
-- (The base64url variation uses @*@ and @-@, for instance.)
-- No support for mapping these to multiple characters in the output though.
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 []) -- upper 4 bits of second val are known to be 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' + (xi-26))
| x < 62 = chr (ord '0' + (xi-52))
| 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
|