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