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