File Coverage

b64.c
Criterion Covered Total %
statement 170 184 92.3
branch 131 202 64.8
condition n/a
subroutine n/a
pod n/a
total 301 386 77.9


line stmt bran cond sub pod time code
1             /*
2             * b64.c - Base64 encode/decode for File::Raw::Base64.
3             *
4             * Standard alphabet from RFC 4648,
5             * URL-safe variant via opts.urlsafe. PEM envelope handling on top.
6             *
7             * Strict C89 compliant: every declaration at the top of its block
8             * scope, no inline for-loop initialisers, no // comments. The dist
9             * targets perl 5.8.3+ where the platform compiler may default to
10             * C89 (e.g. GCC 4.2.1 on FreeBSD 9.x).
11             */
12              
13             #include "b64.h"
14              
15             #include
16             #include
17             #include
18              
19             /* ------------------------------------------------------------
20             * Alphabet tables
21             * ------------------------------------------------------------ */
22              
23             static const char ALPHA_STD[64] =
24             "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
25              
26             static const char ALPHA_URL[64] =
27             "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
28              
29             /* Reverse-lookup tables. -1 = not in alphabet. -2 = padding ('='). */
30             #define INVAL ((signed char)-1)
31             #define PAD ((signed char)-2)
32              
33             static signed char REV_STD[256];
34             static signed char REV_URL[256];
35             static int rev_initialised = 0;
36              
37             static void
38 8           rev_init(void)
39             {
40             int i;
41 2056 100         for (i = 0; i < 256; i++) { REV_STD[i] = INVAL; REV_URL[i] = INVAL; }
42 520 100         for (i = 0; i < 64; i++) {
43 512           REV_STD[(unsigned char)ALPHA_STD[i]] = (signed char)i;
44 512           REV_URL[(unsigned char)ALPHA_URL[i]] = (signed char)i;
45             }
46 8           REV_STD[(unsigned char)'='] = PAD;
47 8           REV_URL[(unsigned char)'='] = PAD;
48 8           rev_initialised = 1;
49 8           }
50              
51             /* ------------------------------------------------------------
52             * Output buffer growth
53             * ------------------------------------------------------------ */
54              
55             static int
56 92           out_reserve(void **out, size_t *out_cap, size_t need)
57             {
58             size_t new_cap;
59             void *new_buf;
60              
61 92 100         if (need <= *out_cap) return 0;
62              
63 66 100         new_cap = *out_cap ? *out_cap : 64;
64 156 100         while (new_cap < need) {
65 90 50         if (new_cap > (SIZE_MAX / 2)) { new_cap = need; break; }
66 90           new_cap *= 2;
67             }
68 66           new_buf = realloc(*out, new_cap);
69 66 50         if (!new_buf) return -1;
70 66           *out = new_buf;
71 66           *out_cap = new_cap;
72 66           return 0;
73             }
74              
75             /* ------------------------------------------------------------
76             * Defaults
77             * ------------------------------------------------------------ */
78              
79             void
80 73           b64_options_init(b64_options_t *opts)
81             {
82 73           memset(opts, 0, sizeof *opts);
83 73           opts->padding = 1;
84 73           opts->pem_label = "DATA";
85 73           opts->eol = "\n";
86             /* urlsafe, wrap, pem, strict default to 0 via memset */
87 73           }
88              
89             /* ------------------------------------------------------------
90             * Encode
91             * ------------------------------------------------------------ */
92              
93             static int
94 23           emit_eol(char **out, size_t *cap, size_t *len, const char *eol)
95             {
96 23           size_t elen = strlen(eol);
97 23 50         if (out_reserve((void **)out, cap, *len + elen) < 0) return B64_ERR_NOMEM;
98 23           memcpy(*out + *len, eol, elen);
99 23           *len += elen;
100 23           return 0;
101             }
102              
103             static int
104 4           emit_pem_header(char **out, size_t *cap, size_t *len,
105             const char *kind, const char *label, const char *eol)
106             {
107             /* "-----BEGIN $label-----$eol" */
108 4           size_t llen = strlen(label);
109 4           size_t elen = strlen(eol);
110 4           size_t klen = strlen(kind);
111 4           size_t need = *len + 5 + 1 + klen + 1 + llen + 5 + elen;
112             char *p;
113              
114 4 50         if (out_reserve((void **)out, cap, need) < 0) return B64_ERR_NOMEM;
115 4           p = *out + *len;
116 4           memcpy(p, "-----", 5); p += 5;
117 4           memcpy(p, kind, klen); p += klen;
118 4           *p++ = ' ';
119 4           memcpy(p, label, llen); p += llen;
120 4           memcpy(p, "-----", 5); p += 5;
121 4           memcpy(p, eol, elen); p += elen;
122 4           *len = (size_t)(p - *out);
123 4           return 0;
124             }
125              
126             int
127 33           b64_encode(const unsigned char *in, size_t in_len,
128             const b64_options_t *opts,
129             char **out, size_t *out_cap, size_t *out_len,
130             size_t *err_offset)
131             {
132             const char *alpha;
133             const char *eol;
134             int rc;
135             size_t encoded_chars;
136 33           size_t wrap_eols = 0;
137             int wrap;
138             size_t i;
139             size_t tail;
140 33           int wrap_col = 0;
141             char *o;
142              
143 33 100         if (!rev_initialised) rev_init();
144 33 50         if (err_offset) *err_offset = 0;
145              
146 33 100         alpha = opts->urlsafe ? ALPHA_URL : ALPHA_STD;
147 33 50         eol = opts->eol ? opts->eol : "\n";
148 33           wrap = opts->wrap;
149              
150             /* PEM begin. */
151 33 100         if (opts->pem) {
152 2           rc = emit_pem_header(out, out_cap, out_len,
153 2           "BEGIN", opts->pem_label, eol);
154 2 50         if (rc) return rc;
155             }
156              
157             /* Worst-case sizing: 4 chars per 3 input bytes, plus wrap eols. */
158 33           encoded_chars = ((in_len + 2) / 3) * 4;
159 33 100         if (wrap > 0 && encoded_chars > 0) {
    50          
160 6           size_t elen = strlen(eol);
161 6           wrap_eols = ((encoded_chars + (size_t)wrap - 1) / (size_t)wrap) * elen;
162             }
163 33 50         if (out_reserve((void **)out, out_cap,
164 33           *out_len + encoded_chars + wrap_eols) < 0)
165 0           return B64_ERR_NOMEM;
166              
167 33           o = *out + *out_len;
168              
169             #define EMIT(c) do { \
170             *o++ = (c); \
171             if (wrap > 0 && ++wrap_col == wrap) { \
172             size_t cur = (size_t)(o - (*out)); \
173             *out_len = cur; \
174             rc = emit_eol(out, out_cap, out_len, eol); \
175             if (rc) return rc; \
176             o = *out + *out_len; \
177             wrap_col = 0; \
178             } \
179             } while (0)
180              
181             /* Main loop: 3 bytes -> 4 chars. */
182 197430 100         for (i = 0; i + 3 <= in_len; i += 3) {
183 197397           uint32_t triplet = ((uint32_t)in[i] << 16)
184 197397           | ((uint32_t)in[i+1] << 8)
185 197397           | (uint32_t)in[i+2];
186 197397 100         EMIT(alpha[(triplet >> 18) & 0x3F]);
    50          
    0          
187 197397 100         EMIT(alpha[(triplet >> 12) & 0x3F]);
    50          
    0          
188 197397 100         EMIT(alpha[(triplet >> 6) & 0x3F]);
    50          
    0          
189 197397 100         EMIT(alpha[ triplet & 0x3F]);
    100          
    50          
190             }
191              
192             /* Tail: 1 or 2 leftover bytes. */
193 33           tail = in_len - i;
194 33 100         if (tail == 1) {
195 13           uint32_t b = (uint32_t)in[i] << 16;
196 13 100         EMIT(alpha[(b >> 18) & 0x3F]);
    50          
    0          
197 13 100         EMIT(alpha[(b >> 12) & 0x3F]);
    50          
    0          
198 13 100         if (opts->padding) { EMIT('='); EMIT('='); }
    100          
    50          
    0          
    100          
    50          
    0          
199 20 100         } else if (tail == 2) {
200 10           uint32_t b = ((uint32_t)in[i] << 16) | ((uint32_t)in[i+1] << 8);
201 10 100         EMIT(alpha[(b >> 18) & 0x3F]);
    50          
    0          
202 10 100         EMIT(alpha[(b >> 12) & 0x3F]);
    50          
    0          
203 10 100         EMIT(alpha[(b >> 6) & 0x3F]);
    50          
    0          
204 10 100         if (opts->padding) EMIT('=');
    100          
    50          
    0          
205             }
206              
207             #undef EMIT
208              
209             /* Final EOL after the body if we wrapped (so the trailing line
210             * terminates) and no eol is already pending - wrap loop only emits
211             * an eol when it just hit `wrap_col == wrap`, so a partial last
212             * line still needs one. PEM mode also wants an EOL before END. */
213 33           *out_len = (size_t)(o - *out);
214 33 100         if ((wrap > 0 && wrap_col != 0) || opts->pem) {
    50          
    50          
215 6           rc = emit_eol(out, out_cap, out_len, eol);
216 6 50         if (rc) return rc;
217             }
218              
219 33 100         if (opts->pem) {
220 2           rc = emit_pem_header(out, out_cap, out_len,
221 2           "END", opts->pem_label, eol);
222 2 50         if (rc) return rc;
223             }
224              
225 33           return B64_OK;
226             }
227              
228             /* ------------------------------------------------------------
229             * Decode
230             *
231             * On PEM input, find the BEGIN/END markers and slice out the body
232             * before running the regular decode loop on it.
233             * ------------------------------------------------------------ */
234              
235             /* Find a substring in [haystack, haystack+haystack_len). NULL if not
236             * found. Standard memmem isn't portable, so roll our own. */
237             static const char *
238 19           mem_find(const char *haystack, size_t haystack_len,
239             const char *needle, size_t needle_len)
240             {
241             const char *limit;
242             const char *p;
243              
244 19 50         if (needle_len == 0 || haystack_len < needle_len) return NULL;
    50          
245 19           limit = haystack + (haystack_len - needle_len);
246 280 100         for (p = haystack; p <= limit; p++) {
247 279 100         if (memcmp(p, needle, needle_len) == 0) return p;
248             }
249 1           return NULL;
250             }
251              
252             /* Locate PEM body. Returns 0 on success and writes [body_start,body_end)
253             * into the out params. Returns negative b64_err_t on failure. */
254             static int
255 5           find_pem_body(const char *in, size_t in_len,
256             const char **body_start, size_t *body_len,
257             size_t *err_offset)
258             {
259             static const char BEGIN_MARK[] = "-----BEGIN ";
260             static const char END_MARK[] = "-----END ";
261             static const char DASH5[] = "-----";
262              
263             const char *begin;
264             const char *begin_label;
265             const char *begin_close;
266             const char *body;
267             const char *end;
268             const char *end_label;
269             const char *end_close;
270             size_t blab_len;
271             size_t elab_len;
272              
273 5           begin = mem_find(in, in_len, BEGIN_MARK, sizeof BEGIN_MARK - 1);
274 5 50         if (!begin) {
275 0 0         if (err_offset) *err_offset = 0;
276 0           return B64_ERR_PEM_NO_BEGIN;
277             }
278 5           begin_label = begin + sizeof BEGIN_MARK - 1;
279 5           begin_close = mem_find(begin_label,
280 5           in_len - (size_t)(begin_label - in),
281             DASH5, 5);
282 5 50         if (!begin_close) {
283 0 0         if (err_offset) *err_offset = (size_t)(begin - in);
284 0           return B64_ERR_PEM_NO_BEGIN;
285             }
286              
287             /* Body starts right after BEGIN line's trailing dashes. Scan past
288             * any immediate \r/\n. */
289 5           body = begin_close + 5;
290 10 50         while (body < in + in_len && (*body == '\r' || *body == '\n')) body++;
    50          
    100          
291              
292 5           end = mem_find(body, in_len - (size_t)(body - in),
293             END_MARK, sizeof END_MARK - 1);
294 5 100         if (!end) {
295 1 50         if (err_offset) *err_offset = (size_t)(begin - in);
296 1           return B64_ERR_PEM_NO_END;
297             }
298 4           end_label = end + sizeof END_MARK - 1;
299 4           end_close = mem_find(end_label,
300 4           in_len - (size_t)(end_label - in),
301             DASH5, 5);
302 4 50         if (!end_close) {
303 0 0         if (err_offset) *err_offset = (size_t)(end - in);
304 0           return B64_ERR_PEM_NO_END;
305             }
306              
307             /* Verify BEGIN and END labels match. */
308 4           blab_len = (size_t)(begin_close - begin_label);
309 4           elab_len = (size_t)(end_close - end_label);
310 4 50         if (blab_len != elab_len
311 4 100         || memcmp(begin_label, end_label, blab_len) != 0)
312             {
313 1 50         if (err_offset) *err_offset = (size_t)(end - in);
314 1           return B64_ERR_PEM_LABEL;
315             }
316              
317 3           *body_start = body;
318 3           *body_len = (size_t)(end - body);
319 3           return 0;
320             }
321              
322             int
323 34           b64_decode(const char *in, size_t in_len,
324             const b64_options_t *opts,
325             unsigned char **out, size_t *out_cap, size_t *out_len,
326             size_t *err_offset)
327             {
328             const signed char *rev;
329             const char *body;
330             size_t body_len;
331             unsigned char *o;
332 34           uint32_t buf = 0;
333 34           int bits = 0;
334 34           int saw_pad = 0;
335             size_t i;
336              
337 34 100         if (!rev_initialised) rev_init();
338 34 50         if (err_offset) *err_offset = 0;
339              
340 34 100         rev = opts->urlsafe ? REV_URL : REV_STD;
341              
342             /* PEM: peel envelope. */
343 34           body = in;
344 34           body_len = in_len;
345 34 100         if (opts->pem) {
346 5           int rc = find_pem_body(in, in_len, &body, &body_len, err_offset);
347 5 100         if (rc) return rc;
348             }
349              
350             /* Worst-case decode size: 3 bytes per 4 input chars. */
351 32 50         if (out_reserve((void **)out, out_cap,
352 32           *out_len + (body_len / 4 + 1) * 3) < 0)
353 0           return B64_ERR_NOMEM;
354              
355 32           o = *out + *out_len;
356              
357 788946 100         for (i = 0; i < body_len; i++) {
358 788916           unsigned char c = (unsigned char)body[i];
359 788916           signed char v = rev[c];
360              
361 788916 100         if (v == PAD) {
362 28           saw_pad++;
363 28           continue;
364             }
365 788888 100         if (v == INVAL) {
366 34 100         if (opts->strict) {
367 2 50         if (err_offset) *err_offset =
368 2           (size_t)(body - in) + i;
369 2           return B64_ERR_BAD_BYTE;
370             }
371             /* Lenient: skip silently. */
372 32           continue;
373             }
374 788854 50         if (saw_pad && opts->strict) {
    0          
375 0 0         if (err_offset) *err_offset = (size_t)(body - in) + i;
376 0           return B64_ERR_BAD_BYTE; /* alphabet byte after padding */
377             }
378              
379 788854           buf = (buf << 6) | (uint32_t)v;
380 788854           bits += 6;
381 788854 100         if (bits >= 8) {
382 591632           bits -= 8;
383 591632           *o++ = (unsigned char)((buf >> bits) & 0xFF);
384             }
385             }
386              
387             /* Final bits: if exactly 8 bits remain we accept (one trailing
388             * byte). 0 bits is fine. Anything else is truncation. */
389 30 100         if (bits != 0 && bits != 2 && bits != 4) {
    100          
    100          
390             /* 6 bits left over means an incomplete quartet (e.g. one
391             * stray base64 char with no partner). 2/4 bits are normal
392             * tails for length%3 == 1 / length%3 == 2. */
393 1           return B64_ERR_TRUNCATED;
394             }
395              
396 29           *out_len = (size_t)(o - *out);
397 29           return B64_OK;
398             }
399              
400             /* ------------------------------------------------------------
401             * strerror
402             * ------------------------------------------------------------ */
403              
404             const char *
405 5           b64_strerror(int err)
406             {
407 5           switch ((b64_err_t)err) {
408 0           case B64_OK: return "ok";
409 0           case B64_ERR_NOMEM: return "out of memory";
410 2           case B64_ERR_BAD_BYTE: return "non-alphabet byte in strict mode";
411 1           case B64_ERR_TRUNCATED: return "truncated base64 input";
412 0           case B64_ERR_PEM_NO_BEGIN: return "PEM input missing BEGIN marker";
413 1           case B64_ERR_PEM_NO_END: return "PEM input missing END marker";
414 1           case B64_ERR_PEM_LABEL: return "PEM BEGIN and END labels disagree";
415             }
416 0           return "unknown error";
417             }