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 | ||
566c9bf8 | 35 | static base64_t default_base64 = {DEFAULT_CHARLIST, "", 0, 1, 0}; |
2340bcd3 JVH |
36 | |
37 | static void | |
38 | initialize_charmap(base64_t *base64) | |
39 | { | |
40 | int i; | |
41 | ||
42 | memset(base64->charmap, BASE64_INVALID, sizeof(base64->charmap)); | |
43 | for (i=0; i<64; i++) { | |
44 | base64->charmap[(int)base64->charlist[i]] = i; | |
45 | } | |
46 | base64->charmap['='] = BASE64_PADDING; | |
47 | base64->charmap_inited = 1; | |
48 | } | |
49 | ||
50 | base64_t * | |
51 | base64_init(const char *charlist, int use_padding, int skip_spaces) | |
52 | { | |
53 | base64_t *base64; | |
54 | int i; | |
55 | ||
56 | if (!charlist) { | |
57 | charlist = DEFAULT_CHARLIST; | |
58 | } | |
59 | if (strlen(charlist) != 64) { | |
60 | return NULL; | |
61 | } | |
62 | for (i=0; i<64; i++) { | |
63 | switch (charlist[i]) { | |
64 | case '\r': | |
65 | case '\n': | |
66 | case '=': | |
67 | return NULL; | |
68 | } | |
69 | } | |
70 | ||
71 | base64 = calloc(1, sizeof(base64_t)); | |
72 | if (!base64) { | |
73 | return NULL; | |
74 | } | |
75 | strncpy(base64->charlist, charlist, sizeof(base64->charlist)-1); | |
76 | base64->use_padding = use_padding; | |
77 | base64->skip_spaces = skip_spaces; | |
78 | ||
79 | return base64; | |
80 | } | |
81 | ||
82 | void | |
83 | base64_destroy(base64_t *base64) | |
84 | { | |
85 | free(base64); | |
86 | } | |
87 | ||
88 | int | |
89 | base64_encoded_length(base64_t *base64, int srclen) | |
90 | { | |
91 | if (!base64) { | |
92 | base64 = &default_base64; | |
93 | } | |
94 | ||
95 | if (base64->use_padding) { | |
96 | return ((srclen+2)/3*4)+1; | |
97 | } else { | |
98 | int strlen = 0; | |
99 | switch (srclen % 3) { | |
100 | case 2: | |
101 | strlen += 1; | |
102 | case 1: | |
103 | strlen += 2; | |
104 | default: | |
105 | strlen += srclen/3*4; | |
106 | break; | |
107 | } | |
108 | return strlen+1; | |
109 | } | |
110 | } | |
111 | ||
112 | int | |
113 | base64_encode(base64_t *base64, char *dst, const unsigned char *src, int srclen) | |
114 | { | |
115 | int src_idx, dst_idx; | |
116 | int residue; | |
117 | ||
118 | if (!base64) { | |
119 | base64 = &default_base64; | |
120 | } | |
121 | ||
122 | residue = 0; | |
123 | for (src_idx=dst_idx=0; src_idx<srclen; src_idx++) { | |
124 | residue |= src[src_idx]; | |
125 | ||
126 | switch (src_idx%3) { | |
127 | case 0: | |
128 | dst[dst_idx++] = base64->charlist[(residue>>2)%64]; | |
129 | residue &= 0x03; | |
130 | break; | |
131 | case 1: | |
132 | dst[dst_idx++] = base64->charlist[residue>>4]; | |
133 | residue &= 0x0f; | |
134 | break; | |
135 | case 2: | |
136 | dst[dst_idx++] = base64->charlist[residue>>6]; | |
137 | dst[dst_idx++] = base64->charlist[residue&0x3f]; | |
138 | residue = 0; | |
139 | break; | |
140 | } | |
141 | residue <<= 8; | |
142 | } | |
143 | ||
144 | /* Add padding */ | |
145 | if (src_idx%3 == 1) { | |
146 | dst[dst_idx++] = base64->charlist[residue>>4]; | |
147 | if (base64->use_padding) { | |
148 | dst[dst_idx++] = '='; | |
149 | dst[dst_idx++] = '='; | |
150 | } | |
151 | } else if (src_idx%3 == 2) { | |
152 | dst[dst_idx++] = base64->charlist[residue>>6]; | |
153 | if (base64->use_padding) { | |
154 | dst[dst_idx++] = '='; | |
155 | } | |
156 | } | |
157 | dst[dst_idx] = '\0'; | |
158 | return dst_idx; | |
159 | } | |
160 | ||
161 | int | |
162 | base64_decode(base64_t *base64, unsigned char **dst, const char *src, int srclen) | |
163 | { | |
164 | char *inbuf; | |
165 | int inbuflen; | |
166 | unsigned char *outbuf; | |
167 | int outbuflen; | |
168 | char *srcptr; | |
169 | int index; | |
170 | ||
171 | if (!base64) { | |
172 | base64 = &default_base64; | |
173 | } | |
174 | if (!base64->charmap_inited) { | |
175 | initialize_charmap(base64); | |
176 | } | |
177 | ||
178 | inbuf = malloc(srclen+4); | |
179 | if (!inbuf) { | |
180 | return -1; | |
181 | } | |
182 | memcpy(inbuf, src, srclen); | |
183 | inbuf[srclen] = '\0'; | |
184 | ||
185 | /* Remove all whitespaces from inbuf */ | |
186 | if (base64->skip_spaces) { | |
187 | int i, inbuflen = strlen(inbuf); | |
188 | for (i=0; i<inbuflen; i++) { | |
189 | if (inbuf[i] == '\0') { | |
190 | break; | |
191 | } else if (isspace(inbuf[i])) { | |
192 | memmove(inbuf+i, inbuf+i+1, inbuflen-i); | |
193 | inbuflen -= 1; | |
194 | i -= 1; | |
195 | } | |
196 | } | |
197 | } | |
198 | ||
199 | /* Add padding to inbuf if required */ | |
200 | inbuflen = strlen(inbuf); | |
201 | if (!base64->use_padding) { | |
202 | if (inbuflen%4 == 1) { | |
203 | free(inbuf); | |
204 | return -2; | |
205 | } | |
206 | if (inbuflen%4 == 2) { | |
207 | inbuf[inbuflen] = '='; | |
208 | inbuf[inbuflen+1] = '='; | |
209 | inbuf[inbuflen+2] = '\0'; | |
210 | inbuflen += 2; | |
211 | } else if (inbuflen%4 == 3) { | |
212 | inbuf[inbuflen] = '='; | |
213 | inbuf[inbuflen+1] = '\0'; | |
214 | inbuflen += 1; | |
215 | } | |
216 | } | |
217 | ||
218 | /* Make sure data is divisible by 4 */ | |
219 | if (inbuflen%4 != 0) { | |
220 | free(inbuf); | |
221 | return -3; | |
222 | } | |
223 | ||
224 | /* Calculate the output length without padding */ | |
225 | outbuflen = inbuflen/4*3; | |
226 | if (inbuflen >= 4 && inbuf[inbuflen-1] == '=') { | |
227 | outbuflen -= 1; | |
228 | if (inbuf[inbuflen-2] == '=') { | |
229 | outbuflen -= 1; | |
230 | } | |
231 | } | |
232 | ||
233 | /* Allocate buffer for outputting data */ | |
234 | outbuf = malloc(outbuflen); | |
235 | if (!outbuf) { | |
236 | free(inbuf); | |
237 | return -4; | |
238 | } | |
239 | ||
240 | index = 0; | |
241 | srcptr = inbuf; | |
242 | while (*srcptr) { | |
243 | unsigned char a = base64->charmap[(unsigned char)*(srcptr++)]; | |
244 | unsigned char b = base64->charmap[(unsigned char)*(srcptr++)]; | |
245 | unsigned char c = base64->charmap[(unsigned char)*(srcptr++)]; | |
246 | unsigned char d = base64->charmap[(unsigned char)*(srcptr++)]; | |
247 | ||
248 | if (a == BASE64_INVALID || b == BASE64_INVALID || | |
249 | c == BASE64_INVALID || d == BASE64_INVALID) { | |
250 | return -5; | |
251 | } | |
252 | if (a == BASE64_PADDING || b == BASE64_PADDING) { | |
253 | return -6; | |
254 | } | |
255 | ||
256 | /* Update the first byte */ | |
257 | outbuf[index++] = (a << 2) | ((b & 0x30) >> 4); | |
258 | ||
259 | /* Update the second byte */ | |
260 | if (c == BASE64_PADDING) { | |
261 | break; | |
262 | } | |
263 | outbuf[index++] = ((b & 0x0f) << 4) | ((c & 0x3c) >> 2); | |
264 | ||
265 | /* Update the third byte */ | |
266 | if (d == BASE64_PADDING) { | |
267 | break; | |
268 | } | |
269 | outbuf[index++] = ((c & 0x03) << 6) | d; | |
270 | } | |
271 | if (index != outbuflen) { | |
272 | free(inbuf); | |
273 | free(outbuf); | |
274 | return -7; | |
275 | } | |
276 | ||
277 | free(inbuf); | |
278 | *dst = outbuf; | |
279 | return outbuflen; | |
280 | } |