1 /**
2 * Base64 Encoding and Decoding
3 * 
4 * Copyright:
5 * (C) 2010 Jack Lloyd
6 * (C) 2014-2015 Etienne Cimon
7 *
8 * License:
9 * Botan is released under the Simplified BSD License (see LICENSE.md)
10 */
11 module botan.codec.base64;
12 import memutils.vector;
13 import botan.codec.base64;
14 import botan.utils.mem_ops;
15 import botan.utils.rounding;
16 import botan.utils.types;
17 import std.exception;
18 // import string;
19 
20 /**
21 * Perform base64 encoding
22 * Params:
23 *  output = an array of at least input_length*4/3 bytes
24 *  input = is some binary data
25 *  input_length = length of input in bytes
26 *  input_consumed = is an output parameter which says how many
27 *          bytes of input were actually consumed. If less than
28 *          input_length, then the range input[consumed:length]
29 *          should be passed in later along with more input.
30 *  final_inputs = true iff this is the last input, in which case
31             padding chars will be applied if needed
32 * Returns: number of bytes written to output
33 */
34 size_t base64Encode(char* output,
35                      const(ubyte)* input,
36                      size_t input_length,
37                      ref size_t input_consumed,
38                      bool final_inputs)
39 {
40 	if (input_length == 0) return 0;
41     input_consumed = 0;
42     
43     size_t input_remaining = input_length;
44     size_t output_produced = 0;
45     
46     while (input_remaining >= 3)
47     {
48         doBase64Encode((output + output_produced)[0..4], (input + input_consumed)[0..3]);
49         
50         input_consumed += 3;
51         output_produced += 4;
52         input_remaining -= 3;
53     }
54     
55     if (final_inputs && input_remaining)
56     {
57         ubyte[3] remainder;
58         foreach (size_t i; 0 .. input_remaining)
59             remainder[i] = input[input_consumed + i];
60         
61         doBase64Encode((output + output_produced)[0..4], remainder);
62         
63         size_t empty_bits = 8 * (3 - input_remaining);
64         size_t index = output_produced + 4 - 1;
65         while (empty_bits >= 8)
66         {
67             output[index--] = '=';
68             empty_bits -= 6;
69         }
70         
71         input_consumed += input_remaining;
72         output_produced += 4;
73     }
74     
75     return output_produced;
76 }
77 
78 /**
79 * Perform base64 encoding
80 * Params:
81 *  input = some input
82 *  input_length = length of input in bytes
83 * Returns: base64adecimal representation of input
84 */
85 
86 string base64Encode(const(ubyte)* input,
87                      size_t input_length)
88 {
89     import std.conv : to;
90     char[] output;
91     output.length = roundUp!size_t(input_length, 3) / 3 * 4;
92     
93     size_t consumed = 0;
94     size_t produced = base64Encode(output.ptr,
95                                     input, input_length,
96                                     consumed, true);
97 
98     assert(consumed == input_length, "Consumed the entire input");
99     assert(produced == output.length, "Produced expected size");
100     
101     return cast(string)output;
102 }
103 
104 /**
105 * Perform base64 encoding
106 * Params:
107 *  input = some input
108 * Returns: base64adecimal representation of input
109 */
110 string base64Encode(Alloc)(auto const ref Vector!( ubyte, Alloc ) input)
111 {
112     return base64Encode(input.ptr, input.length);
113 }
114 
115 /**
116 * Perform base64 decoding
117 * Params:
118 *  output = an array of at least input_length*3/4 bytes
119 *  input = some base64 input
120 *  input_length = length of input in bytes
121 *  input_consumed = is an output parameter which says how many
122 *          bytes of input were actually consumed. If less than
123 *          input_length, then the range input[consumed:length]
124 *          should be passed in later along with more input.
125 *  final_inputs = true iff this is the last input, in which case
126             padding is allowed
127 *  ignore_ws = ignore whitespace on input; if false, throw new an
128                          exception if whitespace is encountered
129 * Returns: number of bytes written to output
130 */
131 size_t base64Decode(ubyte* output,
132                      const(char)* input, size_t input_length,
133                      ref size_t input_consumed,
134                      bool final_inputs,
135                      bool ignore_ws = true)
136 {
137 	if (input_length == 0) return 0;
138     /*
139     * Base64 Decoder Lookup Table
140     * Warning: assumes ASCII encodings
141     */
142     __gshared immutable ubyte[256] BASE64_TO_BIN = [
143         0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80,
144         0x80, 0xFF, 0xFF, 0x80, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
145         0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
146         0xFF, 0xFF, 0x80, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
147         0xFF, 0xFF, 0xFF, 0x3E, 0xFF, 0xFF, 0xFF, 0x3F, 0x34, 0x35,
148         0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0xFF, 0xFF,
149         0xFF, 0x81, 0xFF, 0xFF, 0xFF, 0x00, 0x01, 0x02, 0x03, 0x04,
150         0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E,
151         0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18,
152         0x19, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x1A, 0x1B, 0x1C,
153         0x1D, 0x1E, 0x1F, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26,
154         0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, 0x30,
155         0x31, 0x32, 0x33, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
156         0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
157         0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
158         0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
159         0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
160         0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
161         0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
162         0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
163         0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
164         0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
165         0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
166         0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
167         0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
168         0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF ];
169     
170     ubyte* out_ptr = output;
171     ubyte[4] decode_buf;
172     size_t decode_buf_pos = 0;
173     size_t final_truncate = 0;
174     
175     clearMem(output, input_length * 3 / 4);
176     
177     foreach (size_t i; 0 .. input_length)
178     {
179         const ubyte bin = BASE64_TO_BIN[cast(ubyte)(input[i])];
180         
181         if (bin <= 0x3F)
182         {
183             decode_buf[decode_buf_pos] = bin;
184             decode_buf_pos += 1;
185         }
186         else if (!(bin == 0x81 || (bin == 0x80 && ignore_ws)))
187         {
188             string bad_char;
189             if (input[i] == '\t')
190                 bad_char = "\\t";
191             else if (input[i] == '\n')
192                 bad_char = "\\n";
193             else if (input[i] == '\r')
194                 bad_char = "\\r";
195             else
196                 bad_char = input[i].to!string;
197 
198             throw new InvalidArgument("base64Decode: invalid base64 character (ASCII '" ~ (cast(ubyte) bad_char[0]).to!string ~ "') in " ~ cast(string)(input[0 .. input_length]));
199         }
200 
201         /*
202         * If we're at the end of the input, pad with 0s and truncate
203         */
204         if (final_inputs && (i == input_length - 1))
205         {
206             if (decode_buf_pos)
207             {
208                 foreach (size_t j; decode_buf_pos .. 4)
209                     decode_buf[j] = 0;
210                 final_truncate = (4 - decode_buf_pos);
211                 decode_buf_pos = 4;
212             }
213         }
214 
215         if (decode_buf_pos == 4)
216         {
217             out_ptr[0] = cast(ubyte) ((decode_buf[0] << 2) | (decode_buf[1] >> 4));
218             out_ptr[1] = cast(ubyte) ((decode_buf[1] << 4) | (decode_buf[2] >> 2));
219             out_ptr[2] = cast(ubyte) ((decode_buf[2] << 6) | decode_buf[3]);
220 
221             out_ptr += 3;
222             decode_buf_pos = 0;
223             input_consumed = i+1;
224         }
225     }
226 
227     while (input_consumed < input_length && BASE64_TO_BIN[cast(ubyte)(input[input_consumed])] == 0x80)
228     {
229         ++input_consumed;
230     }
231 
232     size_t written = (out_ptr - output) - final_truncate;
233 
234     return written;
235 }
236 
237 
238 /**
239 * Perform base64 decoding
240 * Params:
241 *  output = an array of at least input_length*3/4 bytes
242 *  input = some base64 input
243 *  input_length = length of input in bytes
244 *  ignore_ws = ignore whitespace on input; if false, throw new an
245                          exception if whitespace is encountered
246 * Returns: number of bytes written to output
247 */
248 size_t base64Decode(ubyte* output, const(char)* input, size_t input_length, bool ignore_ws = true)
249 {
250     size_t consumed = 0;
251     size_t written = base64Decode(output, input, input_length, consumed, true, ignore_ws);
252     
253     if (consumed != input_length)
254         throw new InvalidArgument("base64Decode: input did not have full bytes");
255     
256     return written;
257 }
258 
259 /**
260 * Perform base64 decoding
261 * Params:
262 *  output = an array of at least input_length/3*4 bytes
263 *  input = some base64 input
264 *  ignore_ws = ignore whitespace on input; if false, throw new an
265                          exception if whitespace is encountered
266 * Returns: number of bytes written to output
267 */
268 size_t base64Decode(ubyte* output, in string input, bool ignore_ws = true)
269 {
270     return base64Decode(output, input, ignore_ws);
271 }
272 
273 
274 /**
275 * Perform base64 decoding
276 * Params:
277 *  input = some base64 input string
278 *  ignore_ws = ignore whitespace on input; if false, throw new an
279                          exception if whitespace is encountered
280 * Returns: decoded base64 output
281 */
282 SecureVector!ubyte base64Decode(in string input, bool ignore_ws = true)
283 {
284     SecureVector!ubyte bin;
285     bin.resize((roundUp!size_t(input.length, 4) * 3) / 4);
286 
287     size_t written = base64Decode(bin.ptr, input.ptr, input.length, ignore_ws);
288     bin.resize(written);
289     return bin;
290 }
291 
292 
293 /**
294 * Perform base64 decoding
295 * Params:
296 *  input = some base64 input
297 *  ignore_ws = ignore whitespace on input; if false, throw new an
298                          exception if whitespace is encountered
299 * Returns: decoded base64 output
300 */
301 SecureVector!ubyte base64Decode(const ref Vector!char input, bool ignore_ws = true)
302 {
303     return base64Decode(cast(string)input[], ignore_ws);
304 }
305 
306 
307 
308 package:
309     
310 __gshared immutable ubyte[64] BIN_TO_BASE64 = [
311     'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
312     'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
313     'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
314     'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
315     '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'
316 ];
317 
318 void doBase64Encode(ref char[4] output, in ubyte[3] input)
319 {
320     output[0] = BIN_TO_BASE64[((input[0] & 0xFC) >> 2)];
321     output[1] = BIN_TO_BASE64[((input[0] & 0x03) << 4) | (input[1] >> 4)];
322     output[2] = BIN_TO_BASE64[((input[1] & 0x0F) << 2) | (input[2] >> 6)];
323     output[3] = BIN_TO_BASE64[((input[2] & 0x3F)      )];
324 }