* Began (but did not finish) applying GPL3+ declarations to each source file. This...
[citadel.git] / citadel / base64.c
1 /*
2  * $Id$
3  *
4  * Encode or decode file as MIME base64 (RFC 1341)
5  * Public domain by John Walker, August 11 1997
6  * Modified slightly for the Citadel system, June 1999
7  *
8  *  This program is free software; you can redistribute it and/or modify
9  *  it under the terms of the GNU General Public License as published by
10  *  the Free Software Foundation; either version 3 of the License, or
11  *  (at your option) any later version.
12  *
13  *  This program is distributed in the hope that it will be useful,
14  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
15  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  *  GNU General Public License for more details.
17  *
18  *  You should have received a copy of the GNU General Public License
19  *  along with this program; if not, write to the Free Software
20  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
21  */
22
23
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <ctype.h>
27 #include <string.h>
28
29 #define TRUE  1
30 #define FALSE 0
31
32 #define LINELEN 72                    /* Encoded line length (max 76) */
33
34 typedef unsigned char byte;           /* Byte type */
35
36 FILE *fi;                             /* Input file */
37 FILE *fo;                             /* Output file */
38 static byte iobuf[256];               /* I/O buffer */
39 static int iolen = 0;                 /* Bytes left in I/O buffer */
40 static int iocp = 256;                /* Character removal pointer */
41 static int ateof = FALSE;             /* EOF encountered */
42 static byte dtable[256];              /* Encode / decode table */
43 static int linelength = 0;            /* Length of encoded output line */
44 static char eol[] = "\r\n";           /* End of line sequence */
45 static int errcheck = TRUE;           /* Check decode input for errors ? */
46
47 /*  INBUF  --  Fill input buffer with data  */
48
49 static int inbuf(void)
50 {
51     int l;
52
53     if (ateof) {
54         return FALSE;
55     }
56     l = fread(iobuf, 1, sizeof iobuf, fi);     /* Read input buffer */
57     if (l <= 0) {
58         if (ferror(fi)) {
59             exit(1);
60         }
61         ateof = TRUE;
62         return FALSE;
63     }
64     iolen = l;
65     iocp = 0;
66     return TRUE;
67 }
68
69 /*  INCHAR  --  Return next character from input  */
70
71 static int inchar(void)
72 {
73     if (iocp >= iolen) {
74        if (!inbuf()) {
75           return EOF;
76         }
77     }
78
79     return iobuf[iocp++];
80 }
81
82 /*  OCHAR  --  Output an encoded character, inserting line breaks
83                where required.  */
84
85 static void ochar(int c)
86 {
87     if (linelength >= LINELEN) {
88         if (fputs(eol, fo) == EOF) {
89             exit(1);
90         }
91         linelength = 0;
92     }
93     if (putc(((byte) c), fo) == EOF) {
94         exit(1);
95     }
96     linelength++;
97 }
98
99 /*  ENCODE  --  Encode binary file into base64.  */
100
101 static void encode(void)
102 {
103     int i, hiteof = FALSE;
104
105     /*  Fill dtable with character encodings.  */
106
107     for (i = 0; i < 26; i++) {
108         dtable[i] = 'A' + i;
109         dtable[26 + i] = 'a' + i;
110     }
111     for (i = 0; i < 10; i++) {
112         dtable[52 + i] = '0' + i;
113     }
114     dtable[62] = '+';
115     dtable[63] = '/';
116
117     while (!hiteof) {
118         byte igroup[3], ogroup[4];
119         int c, n;
120
121         igroup[0] = igroup[1] = igroup[2] = 0;
122         for (n = 0; n < 3; n++) {
123             c = inchar();
124             if (c == EOF) {
125                 hiteof = TRUE;
126                 break;
127             }
128             igroup[n] = (byte) c;
129         }
130         if (n > 0) {
131             ogroup[0] = dtable[igroup[0] >> 2];
132             ogroup[1] = dtable[((igroup[0] & 3) << 4) | (igroup[1] >> 4)];
133             ogroup[2] = dtable[((igroup[1] & 0xF) << 2) | (igroup[2] >> 6)];
134             ogroup[3] = dtable[igroup[2] & 0x3F];
135
136             /* Replace characters in output stream with "=" pad
137                characters if fewer than three characters were
138                read from the end of the input stream. */
139
140             if (n < 3) {
141                 ogroup[3] = '=';
142                 if (n < 2) {
143                     ogroup[2] = '=';
144                 }
145             }
146             for (i = 0; i < 4; i++) {
147                 ochar(ogroup[i]);
148             }
149         }
150     }
151     if (fputs(eol, fo) == EOF) {
152         exit(1);
153     }
154 }
155
156 /*  INSIG  --  Return next significant input  */
157
158 static int insig(void)
159 {
160     int c;
161
162     /*CONSTANTCONDITION*/
163     while (TRUE) {
164         c = inchar();
165         if (c == EOF || (c > ' ')) {
166             return c;
167         }
168     }
169     /*NOTREACHED*/
170 }
171
172 /*  DECODE  --  Decode base64.  */
173
174 static void decode(void)
175 {
176     int i;
177
178     for (i = 0; i < 255; i++) {
179         dtable[i] = 0x80;
180     }
181     for (i = 'A'; i <= 'Z'; i++) {
182         dtable[i] = 0 + (i - 'A');
183     }
184     for (i = 'a'; i <= 'z'; i++) {
185         dtable[i] = 26 + (i - 'a');
186     }
187     for (i = '0'; i <= '9'; i++) {
188         dtable[i] = 52 + (i - '0');
189     }
190     dtable['+'] = 62;
191     dtable['/'] = 63;
192     dtable['='] = 0;
193
194     /*CONSTANTCONDITION*/
195     while (TRUE) {
196         byte a[4], b[4], o[3];
197
198         for (i = 0; i < 4; i++) {
199             int c = insig();
200
201             if (c == EOF) {
202                 if (errcheck && (i > 0)) {
203                     fprintf(stderr, "Input file incomplete.\n");
204                     exit(1);
205                 }
206                 return;
207             }
208             if (dtable[c] & 0x80) {
209                 if (errcheck) {
210                     fprintf(stderr, "Illegal character '%c' in input file.\n", c);
211                     exit(1);
212                 }
213                 /* Ignoring errors: discard invalid character. */
214                 i--;
215                 continue;
216             }
217             a[i] = (byte) c;
218             b[i] = (byte) dtable[c];
219         }
220         o[0] = (b[0] << 2) | (b[1] >> 4);
221         o[1] = (b[1] << 4) | (b[2] >> 2);
222         o[2] = (b[2] << 6) | b[3];
223         i = a[2] == '=' ? 1 : (a[3] == '=' ? 2 : 3);
224         if (fwrite(o, i, 1, fo) == EOF) {
225             exit(1);
226         }
227         if (i < 3) {
228             return;
229         }
230     }
231 }
232
233 /*  USAGE  --  Print how-to-call information.  */
234
235 static void usage(char *pname)
236 {
237     fprintf(stderr, "%s  --  Encode/decode file as base64.  Call:\n", pname);
238     fprintf(stderr,
239     "            %s [-e[ncode] / -d[ecode]] [-n] [infile] [outfile]\n", pname);
240     fprintf(stderr, "\n");
241     fprintf(stderr, "Options:\n");
242     fprintf(stderr, "           -D         Decode base64 encoded file\n");
243     fprintf(stderr, "           -E         Encode file into base64\n");
244     fprintf(stderr, "           -N         Ignore errors when decoding\n");
245     fprintf(stderr, "           -U         Print this message\n");
246     fprintf(stderr, "\n");
247     fprintf(stderr, "by John Walker\n");
248     fprintf(stderr, "   WWW:    http://www.fourmilab.ch/\n");
249 }
250
251 /*  Main program  */
252
253 int main(int argc, char *argv[])
254 {
255     int i, f = 0, decoding = FALSE;
256     char *cp, opt;
257
258     fi = stdin;
259     fo = stdout;
260
261     for (i = 1; i < argc; i++) {
262         cp = argv[i];
263         if (*cp == '-') {
264             opt = *(++cp);
265             if (islower(opt)) {
266                 opt = toupper(opt);
267             }
268             switch (opt) {
269
270                 case 'D':             /* -D  Decode */
271                     decoding = TRUE;
272                     break;
273
274                 case 'E':             /* -E  Encode */
275                     decoding = FALSE;
276                     break;
277
278                 case 'N':             /* -N  Suppress error checking */
279                     errcheck = FALSE;
280                     break;
281
282                 case 'U':             /* -U  Print how-to-call information */
283                 case '?':
284                     usage(argv[0]);
285                     return 0;
286            }
287         } else {
288             switch (f) {
289
290                 /** Warning!  On systems which distinguish text mode and
291                     binary I/O (MS-DOS, Macintosh, etc.) the modes in these
292                     open statements will have to be made conditional based
293                     upon whether an encode or decode is being done, which
294                     will have to be specified earlier.  But it's worse: if
295                     input or output is from standard input or output, the 
296                     mode will have to be changed on the fly, which is
297                     generally system and compiler dependent.  'Twasn't me
298                     who couldn't conform to Unix CR/LF convention, so 
299                     don't ask me to write the code to work around
300                     Apple and Microsoft's incompatible standards. **/
301
302                 case 0:
303                     if (strcmp(cp, "-") != 0) {
304                         if ((fi = fopen(cp, "r")) == NULL) {
305                             fprintf(stderr, "Cannot open input file %s\n", cp);
306                             return 2;
307                         }
308                     }
309                     f++;
310                     break;
311
312                 case 1:
313                     if (strcmp(cp, "-") != 0) {
314                         if ((fo = fopen(cp, "w")) == NULL) {
315                             fprintf(stderr, "Cannot open output file %s\n", cp);
316                             return 2;
317                         }
318                     }
319                     f++;
320                     break;
321
322                 default:
323                     fprintf(stderr, "Too many file names specified.\n");
324                     usage(argv[0]);
325                     return 2;
326             }
327        }
328     }
329
330     if (decoding) {
331        decode();
332     } else {
333        encode();
334     }
335     return 0;
336 }