chiark / gitweb /
New feature: watch a file for changes.
[mLib] / base64.c
1 /* -*-c-*-
2  *
3  * $Id: base64.c,v 1.4 1999/10/15 21:08:46 mdw Exp $
4  *
5  * Base64 encoding and decoding.
6  *
7  * (c) 1997 Straylight/Edgeware
8  */
9
10 /*----- Licensing notice --------------------------------------------------* 
11  *
12  * This file is part of the mLib utilities library.
13  *
14  * mLib is free software; you can redistribute it and/or modify
15  * it under the terms of the GNU Library General Public License as
16  * published by the Free Software Foundation; either version 2 of the
17  * License, or (at your option) any later version.
18  * 
19  * mLib is distributed in the hope that it will be useful,
20  * but WITHOUT ANY WARRANTY; without even the implied warranty of
21  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
22  * GNU Library General Public License for more details.
23  * 
24  * You should have received a copy of the GNU Library General Public
25  * License along with mLib; if not, write to the Free
26  * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
27  * MA 02111-1307, USA.
28  */
29
30 /*----- Revision history --------------------------------------------------*
31  *
32  * $Log: base64.c,v $
33  * Revision 1.4  1999/10/15 21:08:46  mdw
34  * Change support for erroneous Base64 streams with length 1 mod 4.
35  *
36  * Revision 1.3  1999/05/21 22:14:30  mdw
37  * Take advantage of the new dynamic string macros.
38  *
39  * Revision 1.2  1999/05/18 21:45:27  mdw
40  * Allow Base64 encode and decode of arbitrary rubbish.
41  *
42  * Revision 1.1  1999/05/17 20:35:00  mdw
43  * Base64 encoding and decoding support.
44  *
45  */
46
47 /*----- Header files ------------------------------------------------------*/
48
49 #include <stdio.h>
50 #include <stdlib.h>
51 #include <string.h>
52
53 #include "base64.h"
54 #include "dstr.h"
55
56 /*----- Important tables --------------------------------------------------*/
57
58 static const char base64_encodeMap[] = { "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
59                                          "abcdefghijklmnopqrstuvwxyz" 
60                                          "0123456789+/" };
61
62 static const signed char base64_decodeMap[] = {
63   -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,  /* 0x */
64   -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,  /* 1x */
65   -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63,  /* 2x */
66   52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1,  /* 3x */
67   -1,  0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14,  /* 4x */
68   15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1,  /* 5x */
69   -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36 ,37, 38, 39, 40,  /* 6x */
70   41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1   /* 7x */
71 };  
72
73 /*----- Main code ---------------------------------------------------------*/
74
75 /* --- @base64_encode@ --- *
76  *
77  * Arguments:   @base64_ctx *ctx@ = pointer to a context block
78  *              @const void *p@ = pointer to a source buffer
79  *              @size_t sz@ = size of the source buffer
80  *              @dstr *d@ = pointer to destination string
81  *
82  * Returns:     ---
83  *
84  * Use:         Encodes a binary string in base64.  To flush out the final
85  *              few characters (if necessary), pass a null source pointer.
86  */
87
88 void base64_encode(base64_ctx *ctx,
89                    const void *p, size_t sz,
90                    dstr *d)
91 {
92   if (p) {
93     unsigned long acc = ctx->acc;
94     unsigned qsz = ctx->qsz;
95     const unsigned char *src = p;
96
97     while (sz) {
98       acc = (acc << 8) | *src++;
99       qsz++;
100       sz--;
101       if (qsz == 3) {
102         DPUTC(d, base64_encodeMap[(acc >> 18) & 0x3f]);
103         DPUTC(d, base64_encodeMap[(acc >> 12) & 0x3f]);
104         DPUTC(d, base64_encodeMap[(acc >>  6) & 0x3f]);
105         DPUTC(d, base64_encodeMap[(acc >>  0) & 0x3f]);
106         ctx->lnlen += 4;
107         if (ctx->maxline && ctx->lnlen >= ctx->maxline) {
108           dstr_puts(d, ctx->indent);
109           ctx->lnlen = 0;
110         }
111         qsz = 0;
112         acc = 0;
113       }
114     }
115
116     ctx->acc = acc;
117     ctx->qsz = qsz;
118   } else {
119     unsigned long acc = ctx->acc;
120     unsigned qsz = ctx->qsz;
121
122     switch (qsz) {
123       case 0:
124         break;
125       case 1:
126         acc <<= 16;
127         DPUTC(d, base64_encodeMap[(acc >> 18) & 0x3f]);
128         DPUTC(d, base64_encodeMap[(acc >> 12) & 0x3f]);
129         DPUTC(d, '=');
130         DPUTC(d, '=');
131         ctx->lnlen += 4;
132         break;
133       case 2:
134         acc <<= 8;
135         DPUTC(d, base64_encodeMap[(acc >> 18) & 0x3f]);
136         DPUTC(d, base64_encodeMap[(acc >> 12) & 0x3f]);
137         DPUTC(d, base64_encodeMap[(acc >>  6) & 0x3f]);
138         DPUTC(d, '=');
139         ctx->lnlen += 4;
140         break;
141     }
142     ctx->qsz = 0;
143     ctx->acc = 0;
144   }
145 }
146
147 /* --- @base64_decode@ --- *
148  *
149  * Arguments:   @base64_ctx *ctx@ = pointer to a context block
150  *              @const void *p@ = pointer to a source buffer
151  *              @size_t sz@ = size of the source buffer
152  *              @dstr *d@ = pointer to destination string
153  *
154  * Returns:     ---
155  *
156  * Use:         Decodes a binary string in base64.  To flush out the final
157  *              few characters (if necessary), pass a null source pointer.
158  */
159
160 void base64_decode(base64_ctx *ctx,
161                    const void *p, size_t sz,
162                    dstr *d)
163 {
164   if (p) {
165     unsigned long acc = ctx->acc;
166     unsigned qsz = ctx->qsz;
167     const char *src = p;
168     int ch;
169
170     while (sz) {
171
172       /* --- Get the next character and convert it --- */
173
174       ch = *src++;
175       if (ch >= 128 || ch < 0)
176         ch = -1;
177       else
178         ch = base64_decodeMap[ch];
179       sz--;
180       if (ch == -1)
181         continue;
182
183       /* --- Bung it in the accumulator --- */
184
185       acc = (acc << 6) | ch;
186       qsz++;
187
188       /* --- Maybe write out a completed triplet --- */
189
190       if (qsz == 4) {
191         DPUTC(d, (acc >> 16) & 0xff);
192         DPUTC(d, (acc >>  8) & 0xff);
193         DPUTC(d, (acc >>  0) & 0xff);
194         acc = 0;
195         qsz = 0;
196       }
197     }
198
199     ctx->acc = acc;
200     ctx->qsz = qsz;
201   } else {
202
203     /* --- Notes about the tail-end bits --- *
204      *
205      * Ending Base64 decoding is messy.  The reference I'm using to define
206      * the encoding, RFC1521 section 5.2, is a little hazy on exactly what to
207      * do at the end.  It explains that I'm meant to ignore spurious `='
208      * characters, and points out that I'm not guaranteed to see anything
209      * interesting at the end.  I'll play safe here, and ignore all `='
210      * characters, relying on my client to work out when to stop feeding me
211      * data.  I'll use the queue size to work out how many tail-end bytes
212      * I ought to write.
213      */
214
215     unsigned long acc = ctx->acc;
216     unsigned qsz = ctx->qsz;
217
218     /* --- Now fiddle with everything else --- *
219      *
220      * There's a bodge here for invalid encodings which have only one hextet
221      * in the final group.  I'm not sure this is really worth having, but it
222      * might save some unexpected behaviour.  (Not that you won't still get
223      * unexpected behaviour if the stream is completely empty, of course.)
224      */
225
226     if (qsz) {
227       acc <<= 6 * (4 - qsz);
228       qsz *= 6;
229       if (qsz < 8)
230         qsz = 8;
231       while (qsz >= 8) {
232         DPUTC(d, (acc >> 16) & 0xff);
233         acc <<= 8;
234         qsz -= 8;
235       }
236     }
237
238     /* --- That seems to be good enough --- */
239
240     ctx->qsz = 0;
241     ctx->acc = 0;
242   }
243 }
244
245 /* --- @base64_init@ --- *
246  *
247  * Arguments:   @base64_ctx *ctx@ = pointer to context block to initialize
248  *
249  * Returns:     ---
250  *
251  * Use:         Initializes a base64 context properly.
252  */
253
254 void base64_init(base64_ctx *ctx)
255 {
256   ctx->acc = 0;
257   ctx->qsz = 0;
258   ctx->lnlen = 0;
259   ctx->indent = "\n";
260   ctx->maxline = 72;
261 }
262
263 /*----- Test driver code --------------------------------------------------*/
264
265 #ifdef TEST_RIG
266
267 int main(int argc, char *argv[])
268 {
269   unsigned char buf[BUFSIZ];
270   dstr d = DSTR_INIT;
271   base64_ctx ctx;
272   void (*proc)(base64_ctx *, const unsigned char *, size_t, dstr *);
273   size_t sz;
274
275   base64_init(&ctx);
276
277   if (argc > 1 && strcmp(argv[1], "-d") == 0)
278     proc = base64_decode;
279   else {
280     proc = base64_encode;
281     putchar('\t');
282     ctx.indent = "\n\t";
283     ctx.maxline = 64;
284   }
285
286   do {
287     sz = fread(buf, 1, sizeof(buf), stdin);
288     if (sz) {
289       proc(&ctx, buf, sz, &d);
290       dstr_write(&d, stdout);
291       dstr_destroy(&d);
292     }
293   } while (sz == sizeof(buf));
294
295   proc(&ctx, 0, 0, &d);
296   dstr_write(&d, stdout);
297
298   if (proc == base64_encode)
299     putchar('\n');
300
301   return (0);
302 }
303
304 #endif
305
306 /*----- That's all, folks -------------------------------------------------*/