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