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 ];