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