Commit | Line | Data |
---|---|---|
23e7e3ae JVH |
1 | /** |
2 | * Copyright (C) 2011-2012 Juho Vähä-Herttua | |
3 | * | |
4 | * This library is free software; you can redistribute it and/or | |
5 | * modify it under the terms of the GNU Lesser General Public | |
6 | * License as published by the Free Software Foundation; either | |
7 | * version 2.1 of the License, or (at your option) any later version. | |
8 | * | |
9 | * This library is distributed in the hope that it will be useful, | |
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
12 | * Lesser General Public License for more details. | |
13 | */ | |
14 | ||
2340bcd3 JVH |
15 | #include <stdlib.h> |
16 | #include <string.h> | |
17 | #include <ctype.h> | |
18 | ||
19 | #include "base64.h" | |
20 | ||
21 | #define DEFAULT_CHARLIST "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" | |
22 | ||
23 | #define BASE64_PADDING 0x40 | |
24 | #define BASE64_INVALID 0x80 | |
25 | ||
26 | struct base64_s { | |
27 | char charlist[65]; | |
28 | char charmap[256]; | |
29 | int charmap_inited; | |
30 | ||
31 | int use_padding; | |
32 | int skip_spaces; | |
33 | }; | |
34 | ||
35 | static base64_t default_base64 = | |
36 | { .charlist = DEFAULT_CHARLIST, | |
37 | .use_padding = 1 }; | |
38 | ||
39 | static void | |
40 | initialize_charmap(base64_t *base64) | |
41 | { | |
42 | int i; | |
43 | ||
44 | memset(base64->charmap, BASE64_INVALID, sizeof(base64->charmap)); | |
45 | for (i=0; i<64; i++) { | |
46 | base64->charmap[(int)base64->charlist[i]] = i; | |
47 | } | |
48 | base64->charmap['='] = BASE64_PADDING; | |
49 | base64->charmap_inited = 1; | |
50 | } | |
51 | ||
52 | base64_t * | |
53 | base64_init(const char *charlist, int use_padding, int skip_spaces) | |
54 | { | |
55 | base64_t *base64; | |
56 | int i; | |
57 | ||
58 | if (!charlist) { | |
59 | charlist = DEFAULT_CHARLIST; | |
60 | } | |
61 | if (strlen(charlist) != 64) { | |
62 | return NULL; | |
63 | } | |
64 | for (i=0; i<64; i++) { | |
65 | switch (charlist[i]) { | |
66 | case '\r': | |
67 | case '\n': | |
68 | case '=': | |
69 | return NULL; | |
70 | } | |
71 | } | |
72 | ||
73 | base64 = calloc(1, sizeof(base64_t)); | |
74 | if (!base64) { | |
75 | return NULL; | |
76 | } | |
77 | strncpy(base64->charlist, charlist, sizeof(base64->charlist)-1); | |
78 | base64->use_padding = use_padding; | |
79 | base64->skip_spaces = skip_spaces; | |
80 | ||
81 | return base64; | |
82 | } | |
83 | ||
84 | void | |
85 | base64_destroy(base64_t *base64) | |
86 | { | |
87 | free(base64); | |
88 | } | |
89 | ||
90 | int | |
91 | base64_encoded_length(base64_t *base64, int srclen) | |
92 | { | |
93 | if (!base64) { | |
94 | base64 = &default_base64; | |
95 | } | |
96 | ||
97 | if (base64->use_padding) { | |
98 | return ((srclen+2)/3*4)+1; | |
99 | } else { | |
100 | int strlen = 0; | |
101 | switch (srclen % 3) { | |
102 | case 2: | |
103 | strlen += 1; | |
104 | case 1: | |
105 | strlen += 2; | |
106 | default: | |
107 | strlen += srclen/3*4; | |
108 | break; | |
109 | } | |
110 | return strlen+1; | |
111 | } | |
112 | } | |
113 | ||
114 | int | |
115 | base64_encode(base64_t *base64, char *dst, const unsigned char *src, int srclen) | |
116 | { | |
117 | int src_idx, dst_idx; | |
118 | int residue; | |
119 | ||
120 | if (!base64) { | |
121 | base64 = &default_base64; | |
122 | } | |
123 | ||
124 | residue = 0; | |
125 | for (src_idx=dst_idx=0; src_idx<srclen; src_idx++) { | |
126 | residue |= src[src_idx]; | |
127 | ||
128 | switch (src_idx%3) { | |
129 | case 0: | |
130 | dst[dst_idx++] = base64->charlist[(residue>>2)%64]; | |
131 | residue &= 0x03; | |
132 | break; | |
133 | case 1: | |
134 | dst[dst_idx++] = base64->charlist[residue>>4]; | |
135 | residue &= 0x0f; | |
136 | break; | |
137 | case 2: | |
138 | dst[dst_idx++] = base64->charlist[residue>>6]; | |
139 | dst[dst_idx++] = base64->charlist[residue&0x3f]; | |
140 | residue = 0; | |
141 | break; | |
142 | } | |
143 | residue <<= 8; | |
144 | } | |
145 | ||
146 | /* Add padding */ | |
147 | if (src_idx%3 == 1) { | |
148 | dst[dst_idx++] = base64->charlist[residue>>4]; | |
149 | if (base64->use_padding) { | |
150 | dst[dst_idx++] = '='; | |
151 | dst[dst_idx++] = '='; | |
152 | } | |
153 | } else if (src_idx%3 == 2) { | |
154 | dst[dst_idx++] = base64->charlist[residue>>6]; | |
155 | if (base64->use_padding) { | |
156 | dst[dst_idx++] = '='; | |
157 | } | |
158 | } | |
159 | dst[dst_idx] = '\0'; | |
160 | return dst_idx; | |
161 | } | |
162 | ||
163 | int | |
164 | base64_decode(base64_t *base64, unsigned char **dst, const char *src, int srclen) | |
165 | { | |
166 | char *inbuf; | |
167 | int inbuflen; | |
168 | unsigned char *outbuf; | |
169 | int outbuflen; | |
170 | char *srcptr; | |
171 | int index; | |
172 | ||
173 | if (!base64) { | |
174 | base64 = &default_base64; | |
175 | } | |
176 | if (!base64->charmap_inited) { | |
177 | initialize_charmap(base64); | |
178 | } | |
179 | ||
180 | inbuf = malloc(srclen+4); | |
181 | if (!inbuf) { | |
182 | return -1; | |
183 | } | |
184 | memcpy(inbuf, src, srclen); | |
185 | inbuf[srclen] = '\0'; | |
186 | ||
187 | /* Remove all whitespaces from inbuf */ | |
188 | if (base64->skip_spaces) { | |
189 | int i, inbuflen = strlen(inbuf); | |
190 | for (i=0; i<inbuflen; i++) { | |
191 | if (inbuf[i] == '\0') { | |
192 | break; | |
193 | } else if (isspace(inbuf[i])) { | |
194 | memmove(inbuf+i, inbuf+i+1, inbuflen-i); | |
195 | inbuflen -= 1; | |
196 | i -= 1; | |
197 | } | |
198 | } | |
199 | } | |
200 | ||
201 | /* Add padding to inbuf if required */ | |
202 | inbuflen = strlen(inbuf); | |
203 | if (!base64->use_padding) { | |
204 | if (inbuflen%4 == 1) { | |
205 | free(inbuf); | |
206 | return -2; | |
207 | } | |
208 | if (inbuflen%4 == 2) { | |
209 | inbuf[inbuflen] = '='; | |
210 | inbuf[inbuflen+1] = '='; | |
211 | inbuf[inbuflen+2] = '\0'; | |
212 | inbuflen += 2; | |
213 | } else if (inbuflen%4 == 3) { | |
214 | inbuf[inbuflen] = '='; | |
215 | inbuf[inbuflen+1] = '\0'; | |
216 | inbuflen += 1; | |
217 | } | |
218 | } | |
219 | ||
220 | /* Make sure data is divisible by 4 */ | |
221 | if (inbuflen%4 != 0) { | |
222 | free(inbuf); | |
223 | return -3; | |
224 | } | |
225 | ||
226 | /* Calculate the output length without padding */ | |
227 | outbuflen = inbuflen/4*3; | |
228 | if (inbuflen >= 4 && inbuf[inbuflen-1] == '=') { | |
229 | outbuflen -= 1; | |
230 | if (inbuf[inbuflen-2] == '=') { | |
231 | outbuflen -= 1; | |
232 | } | |
233 | } | |
234 | ||
235 | /* Allocate buffer for outputting data */ | |
236 | outbuf = malloc(outbuflen); | |
237 | if (!outbuf) { | |
238 | free(inbuf); | |
239 | return -4; | |
240 | } | |
241 | ||
242 | index = 0; | |
243 | srcptr = inbuf; | |
244 | while (*srcptr) { | |
245 | unsigned char a = base64->charmap[(unsigned char)*(srcptr++)]; | |
246 | unsigned char b = base64->charmap[(unsigned char)*(srcptr++)]; | |
247 | unsigned char c = base64->charmap[(unsigned char)*(srcptr++)]; | |
248 | unsigned char d = base64->charmap[(unsigned char)*(srcptr++)]; | |
249 | ||
250 | if (a == BASE64_INVALID || b == BASE64_INVALID || | |
251 | c == BASE64_INVALID || d == BASE64_INVALID) { | |
252 | return -5; | |
253 | } | |
254 | if (a == BASE64_PADDING || b == BASE64_PADDING) { | |
255 | return -6; | |
256 | } | |
257 | ||
258 | /* Update the first byte */ | |
259 | outbuf[index++] = (a << 2) | ((b & 0x30) >> 4); | |
260 | ||
261 | /* Update the second byte */ | |
262 | if (c == BASE64_PADDING) { | |
263 | break; | |
264 | } | |
265 | outbuf[index++] = ((b & 0x0f) << 4) | ((c & 0x3c) >> 2); | |
266 | ||
267 | /* Update the third byte */ | |
268 | if (d == BASE64_PADDING) { | |
269 | break; | |
270 | } | |
271 | outbuf[index++] = ((c & 0x03) << 6) | d; | |
272 | } | |
273 | if (index != outbuflen) { | |
274 | free(inbuf); | |
275 | free(outbuf); | |
276 | return -7; | |
277 | } | |
278 | ||
279 | free(inbuf); | |
280 | *dst = outbuf; | |
281 | return outbuflen; | |
282 | } |