Commit | Line | Data |
---|---|---|
7f918cf1 CE |
1 | (* Copyright (C) 2009 Matthew Fluet. |
2 | * Copyright (C) 1999-2006 Henry Cejtin, Matthew Fluet, Suresh | |
3 | * Jagannathan, and Stephen Weeks. | |
4 | * | |
5 | * MLton is released under a BSD-style license. | |
6 | * See the file MLton-LICENSE for details. | |
7 | *) | |
8 | ||
9 | structure Base64: BASE64 = | |
10 | struct | |
11 | val chars = | |
12 | "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" | |
13 | ||
14 | fun word8ToChar(w: Word8.t): char = | |
15 | String.sub(chars, Word8.toInt w) | |
16 | ||
17 | val word8ToChar = | |
18 | Trace.traceAssert | |
19 | ("Base64.word8ToChar", Word8.layout, Char.layout, | |
20 | fn w => (0w0 <= w andalso w < 0w64, fn _ => true)) | |
21 | word8ToChar | |
22 | ||
23 | val charToWord8: char -> Word8.t option = | |
24 | Char.memoize(fn c => | |
25 | Option.map(String.peeki(chars, fn (_, c') => c = c'), | |
26 | fn (i, _) => Word8.fromInt i)) | |
27 | ||
28 | val charToWord8 = | |
29 | Trace.trace("Base64.charToWord8", | |
30 | Char.layout, Option.layout Word8.layout) | |
31 | charToWord8 | |
32 | ||
33 | val pad = #"=" | |
34 | ||
35 | fun 'a encodeGen{array: 'a, | |
36 | length: 'a -> int, | |
37 | sub: 'a * int -> Word8.t} = | |
38 | let | |
39 | val n = length array | |
40 | val sub = fn i => sub(array, i) | |
41 | val (d, m) = Int.divMod(n, 3) | |
42 | val (d, n') = if m = 0 | |
43 | then (d, n) | |
44 | else if m = 1 | |
45 | then (d + 1, n + 2) | |
46 | else (d + 1, n + 1) | |
47 | val sub = fn i => if i >= n then 0w0 else sub i | |
48 | val _ = Assert.assert("Base64.encodeGen", fn () => n' mod 3 = 0) | |
49 | val numChars: int = 4 * d | |
50 | val chars = CharArray.array(numChars, #"\000") | |
51 | fun updateChar(j, c) = CharArray.update(chars, j, c) | |
52 | fun update(j: int, w: Word8.t) = updateChar(j, word8ToChar w) | |
53 | fun loop(i: int, j: int) = | |
54 | if i = n' | |
55 | then j | |
56 | else | |
57 | let val w1 = sub i | |
58 | val w2 = sub(i + 1) | |
59 | val w3 = sub(i + 2) | |
60 | open Word8 | |
61 | val op + = Int.+ | |
62 | in update(j, >>(w1, 0w2)) | |
63 | ; update(j + 1, orb(<<(andb(w1, 0w3), 0w4), | |
64 | >>(w2, 0w4))) | |
65 | ; update(j + 2, orb(<<(andb(w2, 0wxF), 0w2), | |
66 | >>(w3, 0w6))) | |
67 | ; update(j + 3, andb(w3, 0wx3F)) | |
68 | ; loop(i + 3, j + 4) | |
69 | end | |
70 | val j = loop(0, 0) | |
71 | (* insert padding *) | |
72 | val _ = if m = 0 | |
73 | then () | |
74 | else (updateChar(j - 1, pad) | |
75 | ; if m = 1 | |
76 | then updateChar(j - 2, pad) | |
77 | else ()) | |
78 | (* need to patch for leftover bits *) | |
79 | in String.fromCharArray chars | |
80 | end | |
81 | ||
82 | fun encode s = encodeGen{array = s, | |
83 | length = String.size, | |
84 | sub = Char.toWord8 o String.sub} | |
85 | ||
86 | val encode = | |
87 | Trace.trace("Base64.encode", String.layout, String.layout) encode | |
88 | ||
89 | fun 'a decodeGen{string: string, | |
90 | new: int * Word8.t -> 'a, | |
91 | update: 'a * int * Word8.t -> unit}: 'a = | |
92 | let | |
93 | val n = String.size string | |
94 | val (d, m) = Int.divMod(n, 4) | |
95 | val _ = Assert.assert("Base64.decodeGen", fn () => m = 0) | |
96 | fun sub i = String.sub(string, i) | |
97 | val numPads = | |
98 | if pad = sub(n - 1) | |
99 | then if pad = sub(n - 2) | |
100 | then 2 | |
101 | else 1 | |
102 | else 0 | |
103 | val outputLength = d * 3 - numPads | |
104 | val a = new(outputLength, 0w0) | |
105 | fun loop(i: int, j: int): unit = | |
106 | if i = n | |
107 | then () | |
108 | else | |
109 | let | |
110 | val sub = | |
111 | fn i => | |
112 | let val c = sub i | |
113 | in if pad = c | |
114 | then 0w0 | |
115 | else | |
116 | case charToWord8 c of | |
117 | NONE => | |
118 | Error.bug (concat | |
119 | ["Base64.decodeGen: strange char ", | |
120 | Char.escapeSML c]) | |
121 | | SOME w => w | |
122 | end | |
123 | val w0 = sub i | |
124 | val w1 = sub(i + 1) | |
125 | val w2 = sub(i + 2) | |
126 | val w3 = sub(i + 3) | |
127 | val update = | |
128 | fn (k, w) => | |
129 | if j + k >= outputLength | |
130 | then () | |
131 | else update(a, j + k, w) | |
132 | val _ = | |
133 | let open Word8 | |
134 | in update(0, orb(<<(w0, 0w2), >>(w1, 0w4))) | |
135 | ; update(1, orb(<<(andb(w1, 0wxF), 0w4), >>(w2, 0w2))) | |
136 | ; update(2, orb(<<(andb(w2, 0w3), 0w6), w3)) | |
137 | end | |
138 | in loop(i + 4, j + 3) | |
139 | end | |
140 | val _ = loop(0, 0) | |
141 | in a | |
142 | end | |
143 | ||
144 | fun decode s = | |
145 | String.fromCharArray | |
146 | (decodeGen | |
147 | {string = s, | |
148 | new = fn (n, w) => CharArray.array(n, Char.fromWord8 w), | |
149 | update = fn (a, i, w) => CharArray.update(a, i, Char.fromWord8 w)}) | |
150 | ||
151 | val decode = | |
152 | Trace.trace("Base64.decode", String.layout, String.layout) decode | |
153 | end |