initial load of upstream version 1.06.32
[xmlrpc-c] / src / cpp / base64.cpp
1 #include <cassert>
2 #include <string>
3 #include <vector>
4 #include <bitset>
5
6 #include "xmlrpc-c/girerr.hpp"
7 using girerr::error;
8 using girerr::throwf;
9 #include "xmlrpc-c/base64.hpp"
10
11 using namespace std;
12 using namespace xmlrpc_c;
13
14
15 namespace {
16
17 char const table_a2b_base64[] = {
18     -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
19     -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
20     -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,62, -1,-1,-1,63,
21     52,53,54,55, 56,57,58,59, 60,61,-1,-1, -1, 0,-1,-1, /* Note PAD->0 */
22     -1, 0, 1, 2,  3, 4, 5, 6,  7, 8, 9,10, 11,12,13,14,
23     15,16,17,18, 19,20,21,22, 23,24,25,-1, -1,-1,-1,-1,
24     -1,26,27,28, 29,30,31,32, 33,34,35,36, 37,38,39,40,
25     41,42,43,44, 45,46,47,48, 49,50,51,-1, -1,-1,-1,-1
26 };
27
28 char const base64Pad('=');
29 size_t const base64MaxChunkSize(57);
30      // Max binary chunk size (76 character line)
31 #define BASE64_LINE_SZ 128      /* Buffer size for a single line. */    
32
33 unsigned char const table_b2a_base64[] =
34    "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
35
36 } // namespace
37
38
39
40 class bitBuffer {
41 public:
42     bitBuffer() : bitsInBuffer(0) {};
43
44     void
45     shiftIn8Bits(unsigned char const newBits) {
46         // Shift in 8 bits to the right end of the buffer
47
48         this->buffer = (this->buffer << 8) | newBits;
49         this->bitsInBuffer += 8;
50
51         assert(this->bitsInBuffer <= 12);
52     }
53
54     void
55     shiftIn6Bits(unsigned char const newBits) {
56         // Shift in 6 bits to the right end of the buffer
57
58         this->buffer = (this->buffer << 6) | newBits;
59         this->bitsInBuffer += 6;
60
61         assert(this->bitsInBuffer <= 12);
62     }
63
64     void
65     shiftOut6Bits(unsigned char * const outputP) {
66         // Shift out 6 bits from the left end of the buffer
67
68         assert(bitsInBuffer >= 6);
69
70         *outputP = (this->buffer >> (this->bitsInBuffer - 6)) & 0x3f;
71         this->bitsInBuffer -= 6;
72     }
73
74     void
75     shiftOut8Bits(unsigned char * const outputP) {
76         // Shift out 8 bits from the left end of the buffer
77
78         assert(bitsInBuffer >= 8);
79
80         *outputP = (this->buffer >> (this->bitsInBuffer - 8)) & 0x3f;
81         this->bitsInBuffer -= 8;
82     }
83
84     void
85     shiftOutResidue(unsigned char * const outputP) {
86         // Shift out the residual 2 or 4 bits, padded on the right with 0
87         // to 6 bits.
88
89         while (this->bitsInBuffer < 6) {
90             this->buffer <<= 2;
91             this->bitsInBuffer += 2;
92         }
93         
94         this->shiftOut6Bits(outputP);
95     }
96
97     void
98     discardResidue() {
99         assert(bitsInBuffer < 8);
100         
101         this->bitsInBuffer = 0;
102     }
103     
104     unsigned int
105     bitCount() {
106         return bitsInBuffer;
107     }
108
109 private:
110     unsigned int buffer;
111     unsigned int bitsInBuffer;
112 };
113
114
115 namespace xmlrpc_c {
116
117
118 static void
119 encodeChunk(vector<unsigned char> const& bytes,
120             size_t                const  lineStart,
121             size_t                const  chunkSize,
122             string *              const  outputP) {
123     
124     bitBuffer buffer;
125         // A buffer in which we accumulate bits (up to 12 bits)
126         // until we have enough (6) for a base64 character.
127
128     // I suppose this would be more efficient with an iterator on
129     // 'bytes' and/or *outputP.  I'd have to find out how to use one.
130
131     for (size_t linePos = 0; linePos < chunkSize; ++linePos) {
132         // Shift the data into our buffer
133         buffer.shiftIn8Bits(bytes[lineStart + linePos]);
134         
135         // Encode any complete 6 bit groups
136         while (buffer.bitCount() >= 6) {
137             unsigned char theseBits;
138             buffer.shiftOut6Bits(&theseBits);
139             outputP->append(1, table_b2a_base64[theseBits]);
140         }
141     }
142     if (buffer.bitCount() > 0) {
143         // Handle residual bits in the buffer
144         unsigned char theseBits;
145         buffer.shiftOutResidue(&theseBits);
146     
147         outputP->append(1, table_b2a_base64[theseBits]);
148
149         // Pad to a multiple of 4 characters (24 bits)
150         assert(outputP->length() % 4 > 0);
151         outputP->append(4-outputP->length() % 4, base64Pad);
152     } else {
153         assert(outputP->length() % 4 == 0);
154     }
155 }
156
157
158
159 string
160 base64FromBytes(vector<unsigned char> const& bytes,
161                 newlineCtl            const  newlineCtl) {
162
163     string retval;
164
165     if (bytes.size() == 0) {
166         if (newlineCtl == NEWLINE_YES)
167             retval = "\r\n";
168         else
169             retval = "";
170     } else {
171         // It would be good to preallocate retval.  Need to look up
172         // how to do that.
173         for (size_t chunkStart = 0;
174              chunkStart < bytes.size();
175              chunkStart += base64MaxChunkSize) {
176
177             size_t const chunkSize(
178                 min(base64MaxChunkSize, bytes.size() - chunkStart));
179     
180             encodeChunk(bytes, chunkStart, chunkSize, &retval);
181
182             if (newlineCtl == NEWLINE_YES)
183                 // Append a courtesy crlf
184                 retval += "\r\n";
185         }
186     }
187     return retval;
188 }
189
190
191
192 vector<unsigned char>
193 bytesFromBase64(string const& base64) {
194
195     vector<unsigned char> retval;
196     bitBuffer buffer;
197     unsigned int npad;
198
199     npad = 0;  // No pad characters seen yet
200
201     for (unsigned int cursor = 0; cursor < base64.length(); ++cursor) {
202         char const thisChar(base64[cursor] & 0x7f);
203
204         if (thisChar == '\r' || thisChar == '\n' || thisChar == ' ') {
205             // ignore this punctuation
206         } else {
207             if (thisChar == base64Pad) {
208                 // This pad character is here to synchronize a chunk to 
209                 // a multiple of 24 bits (4 base64 characters; 3 bytes).
210                 buffer.discardResidue();
211             } else {
212                 unsigned int const tableIndex(thisChar);
213                 if (table_a2b_base64[tableIndex] == -1)
214                     throwf("Contains non-base64 character "
215                            "with ASCII code 0x%02x", thisChar);
216                 
217                 buffer.shiftIn6Bits(table_a2b_base64[tableIndex]);
218             
219                 if (buffer.bitCount() >= 8) {
220                     unsigned char thisByte;
221                     buffer.shiftOut8Bits(&thisByte);
222                     retval.push_back(thisByte);
223                 }
224             }
225         }
226     }
227
228     if (buffer.bitCount() > 0)
229         throwf("Not a multiple of 4 characters");
230
231     return retval;
232 }
233
234 } //namespace