1 /*
2 	This module is used to decode and encode base64 char[] arrays.
3 
4 	Example:
5 	---
6 	auto str = "Hello there, my name is Jeff.";
7 	scope encodebuf = new char[encodedSize(cast(ubyte[])str)];
8 	char[] encoded = encode(cast(ubyte[])str, encodebuf);
9 
10 	scope decodebuf = new ubyte[encoded.length];
11 	assert(cast(char[])decode(encoded, decodebuf) == "Hello there, my name is Jeff.");
12 	---
13 */
14 module tame.base64;
15 
16 pure nothrow @nogc:
17 
18 /*
19 	calculates and returns the size needed to encode the length of the
20 	array passed.
21 
22 	Params:
23 	data = An array that will be encoded
24 */
25 
26 size_t encodedSize(in void[] data) {
27 	return encodedSize(data.length);
28 }
29 
30 /*
31 	calculates and returns the size needed to encode the length passed.
32 
33 	Params:
34 	length = Number of bytes to be encoded
35 */
36 
37 size_t encodedSize(size_t length) {
38 	return (length + 2) / 3 * 4; // for every 3 bytes we need 4 bytes to encode, with any fraction needing an additional 4 bytes with padding
39 }
40 
41 /*
42 	encodes data into buff and returns the number of bytes encoded.
43 	this will not terminate and pad any "leftover" bytes, and will instead
44 	only encode up to the highest number of bytes divisible by three.
45 
46 	returns the number of bytes left to encode
47 
48 	Params:
49 	data = what is to be encoded
50 	buff = buffer large enough to hold encoded data
51 	bytesEncoded = ref that returns how much of the buffer was filled
52 */
53 
54 size_t encodeChunk(const(ubyte[]) data, char[] buff, ref size_t bytesEncoded) {
55 	size_t tripletCount = data.length / 3;
56 	size_t rtn;
57 	char* rtnPtr = buff.ptr;
58 	const(ubyte)* dataPtr = data.ptr;
59 
60 	if (data.length > 0) {
61 		rtn = tripletCount * 3;
62 		bytesEncoded = tripletCount * 4;
63 		for (size_t i; i < tripletCount; i++) {
64 			*rtnPtr++ = _encodeTable[((dataPtr[0] & 0xFC) >> 2)];
65 			*rtnPtr++ = _encodeTable[(((dataPtr[0] & 0x03) << 4) | ((dataPtr[1] & 0xF0) >> 4))];
66 			*rtnPtr++ = _encodeTable[(((dataPtr[1] & 0x0F) << 2) | ((dataPtr[2] & 0xC0) >> 6))];
67 			*rtnPtr++ = _encodeTable[(dataPtr[2] & 0x3F)];
68 			dataPtr += 3;
69 		}
70 	}
71 
72 	return rtn;
73 }
74 
75 /*
76 	encodes data and returns as an ASCII base64 string.
77 
78 	Params:data = what is to be encoded
79 	buff = buffer large enough to hold encoded data
80 
81 	Example:
82 	---
83 	char[512] encodebuf;
84 	char[] encodedString = encode(cast(ubyte[])"Hello, how are you today?", encodebuf);
85 	assert(encodedString == "SGVsbG8sIGhvdyBhcmUgeW91IHRvZGF5Pw==")
86 	---
87 */
88 
89 char[] encode(const(ubyte[]) data, char[] buff)
90 in (data)
91 in (buff.length >= encodedSize(data)) {
92 	char[] rtn;
93 
94 	if (data.length) {
95 		size_t bytesEncoded = 0;
96 		size_t numBytes = encodeChunk(data, buff, bytesEncoded);
97 		char* rtnPtr = buff.ptr + bytesEncoded;
98 		const(ubyte)* dataPtr = data.ptr + numBytes;
99 		size_t tripletFraction = data.length - (dataPtr - data.ptr);
100 
101 		switch (tripletFraction) {
102 		case 2:
103 			*rtnPtr++ = _encodeTable[((dataPtr[0] & 0xFC) >> 2)];
104 			*rtnPtr++ = _encodeTable[(((dataPtr[0] & 0x03) << 4) | ((dataPtr[1] & 0xF0) >> 4))];
105 			*rtnPtr++ = _encodeTable[((dataPtr[1] & 0x0F) << 2)];
106 			*rtnPtr++ = '=';
107 			break;
108 		case 1:
109 			*rtnPtr++ = _encodeTable[((dataPtr[0] & 0xFC) >> 2)];
110 			*rtnPtr++ = _encodeTable[((dataPtr[0] & 0x03) << 4)];
111 			*rtnPtr++ = '=';
112 			*rtnPtr++ = '=';
113 			break;
114 		default:
115 			break;
116 		}
117 		rtn = buff[0 .. (rtnPtr - buff.ptr)];
118 	}
119 
120 	return rtn;
121 }
122 
123 /*
124 	decodes an ASCCI base64 string and returns it as ubyte[] data.
125 
126 	This decoder will ignore non-base64 characters. So:
127 	SGVsbG8sIGhvd
128 	yBhcmUgeW91IH
129 	RvZGF5Pw==
130 
131 	Is valid.
132 
133 	Params:
134 	data = what is to be decoded
135 	buff = a big enough array to hold the decoded data
136 
137 	Example:
138 	---
139 	ubyte[512] decodebuf;
140 	auto decodedString = cast(char[])decode("SGVsbG8sIGhvdyBhcmUgeW91IHRvZGF5Pw==", decodebuf);
141 	Stdout(decodedString).newline; // Hello, how are you today?
142 	---
143 */
144 
145 ubyte[] decode(const(char[]) data, ubyte[] buff)
146 in (data) {
147 	ubyte[] rtn;
148 
149 	if (data.length) {
150 		ubyte[4] base64Quad;
151 		ubyte* quadPtr = base64Quad.ptr;
152 		ubyte* endPtr = base64Quad.ptr + 4;
153 		ubyte* rtnPt = buff.ptr;
154 		size_t encodedLength;
155 
156 		ubyte padCount;
157 		ubyte endCount;
158 		ubyte paddedPos;
159 		foreach_reverse (char piece; data) {
160 			paddedPos++;
161 			ubyte current = _decodeTable[piece];
162 			if (current || piece == 'A') {
163 				endCount++;
164 				if (current == BASE64_PAD)
165 					padCount++;
166 			}
167 			if (endCount == 4)
168 				break;
169 		}
170 
171 		if (padCount > 2)
172 			return rtn; // Improperly terminated base64 string.
173 		if (padCount == 0)
174 			paddedPos = 0;
175 
176 		auto nonPadded = data[0 .. ($ - paddedPos)];
177 		foreach (piece; nonPadded) {
178 			ubyte next = _decodeTable[piece];
179 			if (next || piece == 'A')
180 				*quadPtr++ = next;
181 			if (quadPtr is endPtr) {
182 				rtnPt[0] = cast(ubyte)((base64Quad[0] << 2) | (base64Quad[1] >> 4));
183 				rtnPt[1] = cast(ubyte)((base64Quad[1] << 4) | (base64Quad[2] >> 2));
184 				rtnPt[2] = cast(ubyte)((base64Quad[2] << 6) | base64Quad[3]);
185 				encodedLength += 3;
186 				quadPtr = base64Quad.ptr;
187 				rtnPt += 3;
188 			}
189 		}
190 
191 		// this will try and decode whatever is left, even if it isn't terminated properly (ie: missing last one or two =)
192 		if (paddedPos) {
193 			const(char)[] padded = data[($ - paddedPos) .. $];
194 			foreach (char piece; padded) {
195 				ubyte next = _decodeTable[piece];
196 				if (next || piece == 'A')
197 					*quadPtr++ = next;
198 				if (quadPtr is endPtr) {
199 					*rtnPt++ = cast(ubyte)((base64Quad[0] << 2) | (base64Quad[1]) >> 4);
200 					if (base64Quad[2] != BASE64_PAD) {
201 						*rtnPt++ = cast(ubyte)((base64Quad[1] << 4) | (base64Quad[2] >> 2));
202 						encodedLength += 2;
203 						break;
204 					} else {
205 						encodedLength++;
206 						break;
207 					}
208 				}
209 			}
210 		}
211 
212 		rtn = buff[0 .. encodedLength];
213 	}
214 
215 	return rtn;
216 }
217 
218 // dfmt off
219 
220 private:
221 
222 enum BASE64_PAD = 64;
223 immutable _encodeTable = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
224 
225 immutable ubyte[] _decodeTable = [
226 	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
227 	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
228 	0,0,0,62,0,0,0,63,52,53,54,55,56,57,58,
229 	59,60,61,0,0,0,BASE64_PAD,0,0,0,0,1,2,3,
230 	4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,
231 	19,20,21,22,23,24,25,0,0,0,0,0,0,26,27,
232 	28,29,30,31,32,33,34,35,36,37,38,39,40,
233 	41,42,43,44,45,46,47,48,49,50,51,
234 	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
235 	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
236 	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
237 	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
238 	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
239 	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
240 	0,0,0,0,0,0,0,0,0,0,0,0,0
241 ];