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