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