Rewrote our local implementation of a Base64 encoder/decoder as specified
[citadel.git] / libcitadel / lib / base64.c
1 // This is a simple implementation of a Base64 encoder/decoder as specified
2 // in RFC 2045 section 6.8.   In the past, someone tried to make this "elegant"
3 // and in the process they made it broken when certain conditions exist.  If
4 // you are reading this and it isn't broken, don't try to improve it.  It works
5 // and I don't want to fix it again.  I don't care how many nanoseconds you think
6 // you can shave off the execution time.  Don't fucking touch it.
7 //
8 // Copyright (c) 1987-2022 by the citadel.org team
9 //
10 // This program is open source software; you can redistribute it and/or modify
11 // it under the terms of the GNU General Public License as published by
12 // the Free Software Foundation; either version 3 of the License, or
13 // (at your option) any later version.
14 //
15 // This program is distributed in the hope that it will be useful,
16 // but WITHOUT ANY WARRANTY; without even the implied warranty of
17 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18 // GNU General Public License for more details.
19 //
20 // You should have received a copy of the GNU General Public License
21 // along with this program; if not, write to the Free Software
22 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
23
24 #define _GNU_SOURCE
25 #include "sysdep.h"
26 #include <ctype.h>
27 #include <errno.h>
28 #include <string.h>
29 #include <unistd.h>
30 #include <string.h>
31 #include <stdio.h>
32 #include <sys/select.h>
33 #include <fcntl.h>
34 #include <sys/types.h>
35 #include "libcitadel.h"
36
37
38 // Encode raw binary data into base64
39 // dest         Destination buffer supplied by the caller
40 // source       Source binary data to be encoded
41 // sourcelen    Stop after reading this many bytes
42 // linebreaks   If nonzero, insert CRLF after every 76 bytes
43 // return value The length of the encoded data, not including the null terminator
44 size_t CtdlEncodeBase64(char *dest, const char *source, size_t sourcelen, int linebreaks) {
45         static char *alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
46         size_t bytes_processed = 0;
47         char *ptr = (char *)source;
48         char encodebuf[3];
49         size_t bytes_output = 0;
50
51         while (bytes_processed < sourcelen) {
52                 size_t remain = sourcelen - bytes_processed;
53                 if (remain >= 3) {
54                         memcpy(encodebuf, ptr, 3);
55                         ptr += 3;
56                         bytes_processed += 3;
57                         sprintf(&dest[bytes_output], "%c%c%c%c",
58                                 alphabet[((encodebuf[0] & 0xFC) >> 2)],
59                                 alphabet[( ((encodebuf[0] & 0x03) << 4) | ((encodebuf[1] & 0xF0) >> 4) )],
60                                 alphabet[( ((encodebuf[1] & 0x0F) << 2) | ((encodebuf[2] & 0xC0) >> 6) )],
61                                 alphabet[(encodebuf[2] & 0x3F)] 
62                         );
63                         bytes_output += 4;
64                                 
65                 }
66                 else if (remain == 2) {
67                         memcpy(encodebuf, ptr, 2);
68                         encodebuf[2] = 0;
69                         ptr += 2;
70                         bytes_processed += 2;
71                         sprintf(&dest[bytes_output], "%c%c%c=",
72                                 alphabet[((encodebuf[0] & 0xFC) >> 2)],
73                                 alphabet[( ((encodebuf[0] & 0x03) << 4) | ((encodebuf[1] & 0xF0) >> 4) )],
74                                 alphabet[( ((encodebuf[1] & 0x0F) << 2) )]
75                         );
76                         bytes_output += 4;
77                 }
78                 else if (remain == 1) {
79                         memcpy(encodebuf, ptr, 1);
80                         encodebuf[1] = 0;
81                         encodebuf[2] = 0;
82                         ptr += 1;
83                         bytes_processed += 1;
84                         sprintf(&dest[bytes_output], "%c%c==",
85                                 alphabet[((encodebuf[0] & 0xFC) >> 2)],
86                                 alphabet[( ((encodebuf[0] & 0x03) << 4) )]
87                         );
88                         bytes_output += 4;
89                 }
90                 if ( ((bytes_processed % 57) == 0) || (bytes_processed >= sourcelen) ) {
91                         sprintf(&dest[bytes_output], "\r\n");
92                         bytes_output += 2;
93                 }
94
95         }
96
97         return bytes_output;
98 }
99
100
101 // convert base64 alphabet characters to 6-bit decimal values
102 char unalphabet(char ch) {
103         if (isupper(ch)) {
104                 return(ch - 'A');
105         }
106         else if (islower(ch)) {
107                 return(ch - 'a' + 26);
108         }
109         else if (isdigit(ch)) {
110                 return(ch - '0' + 52);
111         }
112         else if (ch == '+') {
113                 return(62);
114         }
115         else if (ch == '/') {
116                 return(63);
117         }
118         else if (ch == '=') {                   // this character marks the end of an encoded block
119                 return(64);
120         }
121         else {                                  // anything else is an invalid character
122                 return(65);
123         }
124 }
125
126
127 // Decode base64 back to binary
128 // dest         Destination buffer
129 // source       Source base64-encoded buffer
130 // source_len   Stop after parsing this many bytes
131 // return value Decoded length
132 size_t CtdlDecodeBase64(char *dest, const char *source, size_t source_len) {
133         size_t bytes_read = 0;
134         size_t bytes_decoded = 0;
135         int decodepos = 0;
136         char decodebuf[4];
137
138         while (bytes_read < source_len) {
139
140                 char ch = unalphabet(source[bytes_read++]);
141                 if (ch < 65) {
142                         decodebuf[decodepos++] = ch;
143                 }
144                 if (decodepos == 4) {                   // we have a quartet of encoded bytes now
145                         decodepos = 0;
146                         int increment = 0;              // number of decoded bytes found in this encoded quartet
147
148                         ch = decodebuf[0];
149                         if (ch != 64) {
150                                 dest[bytes_decoded] = ch << 2;
151                         }
152
153                         ch = decodebuf[1];
154                         if (ch != 64) {
155                                 dest[bytes_decoded] |= ((ch & 0x30) >> 4);
156                                 dest[bytes_decoded+1] = ((ch & 0x0F) << 4);
157                                 increment = 1;
158                         }
159
160                         ch = decodebuf[2];
161                         if (ch != 64) {
162                                 dest[bytes_decoded+1] |= ((ch & 0x3C) >> 2);
163                                 dest[bytes_decoded+2] = ((ch & 0x03) << 6);
164                                 increment = 2;
165                         }
166
167                         ch = decodebuf[3];
168                         if (ch != 64) {
169                                 dest[bytes_decoded+2] |= (ch & 0x3F);
170                                 increment = 3;
171                         }
172
173                         bytes_decoded += increment;
174                 }
175         }
176
177         return bytes_decoded;
178 }