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