1 #include "protoBase64.h"
2 #include <string.h> // for memset()
3
4 // This class implements Base64 encoding (and decoding) per IETF RFC 4648
5 // By default, no maximum line length is imposed on encoder output and
6 // the output is fully padded per the RFC's recommendation. However, options
7 // are provided to enforce a maximum text line length and exclude padding if desired.
8
9 // Our static encoding / decoding tables
10 bool ProtoBase64::initialized = false;
11 const char ProtoBase64::PAD64 = '=';
12 const char ProtoBase64::BASE64_ENCODE[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
13 char ProtoBase64::BASE64_DECODE[255];
14
Init()15 void ProtoBase64::Init()
16 {
17 // Init to invalid value
18 memset(BASE64_DECODE, -1, 255);
19 for (unsigned int i = 0; i < 64; i++)
20 BASE64_DECODE[(unsigned char)BASE64_ENCODE[i]] = i;
21 initialized = true;
22 } // end ProtoBase64::Init()
23
ComputeEncodedSize(unsigned int numBytes,unsigned int maxLineLength,bool includePadding)24 unsigned int ProtoBase64::ComputeEncodedSize(unsigned int numBytes, unsigned int maxLineLength, bool includePadding)
25 {
26 unsigned int quads = numBytes / 3;
27 unsigned int size = 4 * quads;
28 unsigned int remainder = numBytes % 3;
29 if (0 != remainder)
30 size += (includePadding ? 4 : (remainder + 1));
31 if (maxLineLength > 0)
32 {
33 // Account for addition of CR/LF for each _full_ line.
34 unsigned int numLines = size / maxLineLength;
35 size += (numLines * 2);
36 }
37 return size;
38 } // end ProtoBase64::ComputeEncodedSize()
39
40
41 // This inline helper function sets a value in the encoder output buffer, making sure there is sufficient
42 // buffer space _and_ inserting CR/LF whenever maxLineLength (if applicable) is reached
43 // (This also updates the reference "outdex" and "lineLength" parameters passed in)
44 // Note this return "true" on success and "false" when there is insufficient buffer space
SetOutputValue(char value,char * buffer,unsigned int buflen,unsigned int & outdex,unsigned int & lineLength,unsigned int maxLineLength)45 static inline bool SetOutputValue(char value, char* buffer, unsigned int buflen, unsigned int& outdex, unsigned int& lineLength, unsigned int maxLineLength)
46 {
47 if (outdex < buflen)
48 {
49 buffer[outdex++] = value;
50 if (++lineLength == maxLineLength)
51 {
52 lineLength = 0;
53 if ((outdex + 1) < buflen)
54 {
55 buffer[outdex++] = '\r';
56 buffer[outdex++] = '\n';
57 }
58 else
59 {
60 return false; // insufficient output buffer space
61 }
62 }
63 return true;
64 }
65 else
66 {
67 return false; // insufficient output buffer space
68 }
69 } // end SetOutputValue()
70
Encode(const char * input,unsigned int numBytes,char * buffer,unsigned int buflen,unsigned int maxLineLength,bool includePadding)71 unsigned int ProtoBase64::Encode(const char* input,
72 unsigned int numBytes,
73 char* buffer,
74 unsigned int buflen,
75 unsigned int maxLineLength,
76 bool includePadding)
77 {
78 if (!initialized) ProtoBase64::Init();
79 // Every 3 bytes in yields 4 bytes out (plus CR/LF if maxLineLength >= 0)
80 unsigned int index = 0;
81 unsigned int outdex = 0;
82 unsigned int lineLength = 0;
83 while (numBytes > 0)
84 {
85 // At least one output value is generated
86 unsigned char morsel = (input[index] >> 2) & 0x3f;
87 if (!SetOutputValue(BASE64_ENCODE[morsel], buffer, buflen, outdex, lineLength, maxLineLength))
88 return 0; // insufficient output buffer space
89 morsel = (input[index++] << 4) & 0x3f;
90 switch (numBytes)
91 {
92 case 1: // Only 1 input byte remains
93 {
94 // Generate one more output value plus two pad characters (if including padding)
95 if (!SetOutputValue(BASE64_ENCODE[morsel], buffer, buflen, outdex, lineLength, maxLineLength))
96 return 0; // insufficient output buffer space
97 if (includePadding)
98 {
99 // Two pad characters are inserted in this case
100 if (!SetOutputValue(PAD64, buffer, buflen, outdex, lineLength, maxLineLength))
101 return 0; // insufficient output buffer space
102 if (!SetOutputValue(PAD64, buffer, buflen, outdex, lineLength, maxLineLength))
103 return 0; // insufficient output buffer space
104 }
105 numBytes = 0;
106 break;
107 }
108 case 2: // Only 2 input bytes remain
109 {
110 // Generate two more output values plus one pad character (if including padding)
111 morsel |= (input[index] >> 4) & 0x0f;
112 if (!SetOutputValue(BASE64_ENCODE[morsel], buffer, buflen, outdex, lineLength, maxLineLength))
113 return 0; // insufficient output buffer space
114 morsel = (input[index++] << 2) & 0x3f;
115 if (!SetOutputValue(BASE64_ENCODE[morsel], buffer, buflen, outdex, lineLength, maxLineLength))
116 return 0; // insufficient output buffer space
117 if (includePadding)
118 {
119 // Only on epad character is inserted in this case
120 if (!SetOutputValue(PAD64, buffer, buflen, outdex, lineLength, maxLineLength))
121 return 0; // insufficient output buffer space
122 }
123 numBytes = 0;
124 break;
125 }
126 default: // 3 or more input bytes remain
127 {
128 // Generate 3 more output values
129 morsel |= (input[index] >> 4) & 0x0f;
130 if (!SetOutputValue(BASE64_ENCODE[morsel], buffer, buflen, outdex, lineLength, maxLineLength))
131 return 0; // insufficient output buffer space
132 morsel = (input[index++] << 2) & 0x3f;
133 morsel |= (input[index] >> 6) & 0x03;
134 if (!SetOutputValue(BASE64_ENCODE[morsel], buffer, buflen, outdex, lineLength, maxLineLength))
135 return 0; // insufficient output buffer space
136 morsel = input[index++] & 0x3f;
137 if (!SetOutputValue(BASE64_ENCODE[morsel], buffer, buflen, outdex, lineLength, maxLineLength))
138 return 0; // insufficient output buffer space
139 numBytes -= 3;
140 break;
141 }
142 }
143 } // end while (numBytes > 0)
144 // NULL-terminate the encoded base64 text if there is space
145 if (outdex < buflen) buffer[outdex] = '\0';
146 return outdex;
147 } // end ProtoBase64::Encode()
148
EstimateDecodedSize(unsigned int numBytes,unsigned int maxLineLength)149 unsigned int ProtoBase64::EstimateDecodedSize(unsigned int numBytes, unsigned int maxLineLength)
150 {
151 // This is a _conservative_ estimate (i.e., may over-estimate size)
152 if (maxLineLength > 0)
153 {
154 // Account for CR/LF if line length limit is imposed
155 unsigned int numLines = numBytes / (maxLineLength + 2);
156 unsigned int lineFeedBytes = 2 * numLines;
157 // Note, to be conservative, we don't assume a short line has CR/LF
158 numBytes -= lineFeedBytes;
159 }
160 // Assume each 4 bytes of encoded data yields 3 bytes of decoded data
161 unsigned int quads = numBytes / 4;
162 unsigned int size = 3 * quads;
163 unsigned int remainder = numBytes % 4;
164 if (remainder > 1) // 1 byte remainder probably extraneous character
165 size += (remainder - 1);
166 return size;
167 } // end ProtoBase64::EstimateDecodedSize()
168
DetermineDecodedSize(const char * input,unsigned int numBytes)169 unsigned int ProtoBase64::DetermineDecodedSize(const char* input, unsigned int numBytes)
170 {
171 if (!initialized) Init();
172 // Each 4 bytes of input (CR/LF, etc withstanding) yields 3-bytes of output
173 unsigned int validBytes = 0;
174 for (unsigned int index = 0; index < numBytes; index++)
175 {
176 char value = input[index];
177 if (PAD64 == value) continue;
178 char bits = BASE64_DECODE[(unsigned char)value];
179 if (bits < 0) continue; // non-Base64 character
180 validBytes += 1;
181 }
182 // For every 4 "valid encoded bytes", 3 output bytes are generated
183 unsigned int quads = validBytes / 4;
184 unsigned int size = 3 * quads;
185 unsigned int remainder = validBytes % 4;
186 // Note "remainder" _should_ only be 0, 2, or 3
187 if (remainder > 1)
188 size += (remainder - 1);
189 return size;
190 } // end ProtoBase64::DetermineDecodedSize()
191
Decode(const char * input,unsigned int numBytes,char * buffer,unsigned int buflen)192 unsigned int ProtoBase64::Decode(const char* input, unsigned int numBytes, char* buffer, unsigned int buflen)
193 {
194 if (!initialized) Init();
195 // Each 4 bytes of input (CR/LF, etc withstanding) yields 3-bytes of output
196 char output = 0;
197 unsigned int outdex = 0;
198 unsigned int offset = 0;
199 for (unsigned int index = 0; index < numBytes; index++)
200 {
201 char value = input[index];
202 if (PAD64 == value) continue;
203 char bits = BASE64_DECODE[(unsigned char)value];
204 if (bits < 0) continue; // non-Base64 character
205 switch (offset) // 0, 1, 2, 3
206 {
207 case 0: // First 6 bits of 24 (just save 6 bits to "output")
208 output = bits << 2;
209 offset = 1;
210 break;
211 case 1: // Second 6 bits (use 2 bits with prev "output" and save 4)
212 output |= (bits >> 4) & 0x03;
213 if (outdex >= buflen) return 0; // insufficient buffer space
214 buffer[outdex++] = output;
215 output = bits << 4;
216 offset = 2;
217 break;
218 case 2: // Third 6 bits (use 4 bits with prev "output" and save 2)
219 output |= (bits >> 2) & 0x0f;
220 if (outdex >= buflen) return 0; // insufficient buffer space
221 buffer[outdex++] = output;
222 output = bits << 6;
223 offset = 3;
224 break;
225 case 3: // Fourth 6 bits (use all 6 bits with prev "output")
226 output |= bits;
227 if (outdex >= buflen) return 0; // insufficient buffer space
228 buffer[outdex++] = output;
229 offset = 0;
230 break;
231 }
232 } // end for (index = 0..numBytes)
233 return outdex;
234 } // end ProtoBase64::Decode()
235