CtdlEncodeBase64() - only add linebreaks if told to by the caller.
[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                         if (linebreaks) {
92                                 sprintf(&dest[bytes_output], "\r\n");
93                                 bytes_output += 2;
94                         }
95                 }
96
97         }
98
99         return bytes_output;
100 }
101
102
103 // convert base64 alphabet characters to 6-bit decimal values
104 char unalphabet(char ch) {
105         if (isupper(ch)) {
106                 return(ch - 'A');
107         }
108         else if (islower(ch)) {
109                 return(ch - 'a' + 26);
110         }
111         else if (isdigit(ch)) {
112                 return(ch - '0' + 52);
113         }
114         else if (ch == '+') {
115                 return(62);
116         }
117         else if (ch == '/') {
118                 return(63);
119         }
120         else if (ch == '=') {                   // this character marks the end of an encoded block
121                 return(64);
122         }
123         else {                                  // anything else is an invalid character
124                 return(65);
125         }
126 }
127
128
129 // Decode base64 back to binary
130 // dest         Destination buffer
131 // source       Source base64-encoded buffer
132 // source_len   Stop after parsing this many bytes
133 // return value Decoded length
134 size_t CtdlDecodeBase64(char *dest, const char *source, size_t source_len) {
135         size_t bytes_read = 0;
136         size_t bytes_decoded = 0;
137         int decodepos = 0;
138         char decodebuf[4];
139
140         while (bytes_read < source_len) {
141
142                 char ch = unalphabet(source[bytes_read++]);
143                 if (ch < 65) {
144                         decodebuf[decodepos++] = ch;
145                 }
146                 if (decodepos == 4) {                   // we have a quartet of encoded bytes now
147                         decodepos = 0;
148                         int increment = 0;              // number of decoded bytes found in this encoded quartet
149
150                         ch = decodebuf[0];
151                         if (ch != 64) {
152                                 dest[bytes_decoded] = ch << 2;
153                         }
154
155                         ch = decodebuf[1];
156                         if (ch != 64) {
157                                 dest[bytes_decoded] |= ((ch & 0x30) >> 4);
158                                 dest[bytes_decoded+1] = ((ch & 0x0F) << 4);
159                                 increment = 1;
160                         }
161
162                         ch = decodebuf[2];
163                         if (ch != 64) {
164                                 dest[bytes_decoded+1] |= ((ch & 0x3C) >> 2);
165                                 dest[bytes_decoded+2] = ((ch & 0x03) << 6);
166                                 increment = 2;
167                         }
168
169                         ch = decodebuf[3];
170                         if (ch != 64) {
171                                 dest[bytes_decoded+2] |= (ch & 0x3F);
172                                 increment = 3;
173                         }
174
175                         bytes_decoded += increment;
176                 }
177         }
178
179         return bytes_decoded;
180 }