#ifndef PBASE64_H #define PBASE64_H #ifdef __cplusplus extern "C" { #endif #include /* Guarded to allow inclusion of pstdint.h first, if stdint.h is not supported. */ #ifndef UINT8_MAX #include #endif #define BASE64_EOK 0 /* 0 or mure full blocks decoded, remaining content may be parsed with fresh buffer. */ #define BASE64_EMORE 1 /* The `src_len` argument is required when encoding. */ #define BASE64_EARGS 2 /* Unsupported mode, or modifier not supported by mode when encoding. */ #define BASE64_EMODE 3 /* Decoding ends at invalid tail length - either by source length or by non-alphabet symbol. */ #define BASE64_ETAIL 4 /* Decoding ends at valid tail length but last byte has non-zero bits where it shouldn't have. */ #define BASE64_EDIRTY 5 static inline const char *base64_strerror(int err); /* All codecs are URL safe. Only Crockford allow for non-canocical decoding. */ enum { /* Most common base64 codec, but not url friendly. */ base64_mode_rfc4648 = 0, /* URL safe version, '+' -> '-', '/' -> '_'. */ base64_mode_url = 1, /* * Skip ' ', '\r', and '\n' - we do not allow tab because common * uses of base64 such as PEM do not allow tab. */ base64_dec_modifier_skipspace = 32, /* Padding is excluded by default. Not allowed for zbase64. */ base64_enc_modifier_padding = 128, /* For internal use or to decide codec of mode. */ base64_modifier_mask = 32 + 64 + 128, }; /* Encoded size with or without padding. */ static inline size_t base64_encoded_size(size_t len, int mode); /* * Decoded size assuming no padding. * If `len` does include padding, the actual size may be less * when decoding, but never more. */ static inline size_t base64_decoded_size(size_t len); /* * `dst` must hold ceil(len * 4 / 3) bytes. * `src_len` points to length of source and is updated with length of * parse on both success and failure. If `dst_len` is not null * it is used to store resulting output lengt withh length of decoded * output on both success and failure. * If `hyphen` is non-zero a hyphen is encoded every `hyphen` output bytes. * `mode` selects encoding alphabet defaulting to Crockfords base64. * Returns 0 on success. * * A terminal space can be added with `dst[dst_len++] = ' '` after the * encode call. All non-alphabet can be used as terminators except the * padding character '='. The following characters will work as * terminator for all modes: { '\0', '\n', ' ', '\t' }. A terminator is * optional when the source length is given to the decoder. Note that * crockford also reserves a few extra characters for checksum but the * checksum must be separate from the main buffer and is not supported * by this library. */ static inline int base64_encode(uint8_t *dst, const uint8_t *src, size_t *dst_len, size_t *src_len, int mode); /* * Decodes according to mode while ignoring encoding modifiers. * `src_len` and `dst_len` are optional pointers. If `src_len` is set it * must contain the length of the input, otherwise the input must be * terminated with a non-alphabet character or valid padding (a single * padding character is accepted) - if the src_len output is needed but * not the input due to guaranteed termination, then set it to * (size_t)-1. `dst_len` must contain length of output buffer if present * and parse will fail with BASE64_EMORE after decoding a block multiple * if dst_len is exhausted - the parse can thus be resumed after * draining destination. `src_len` and `dst_len` are updated with parsed * and decoded length, when present, on both success and failure. * Returns 0 on success. Invalid characters are not considered errors - * they simply terminate the parse, however, if the termination is not * at a block multiple or a valid partial block length then BASE64_ETAIL * without output holding the last full block, if any. BASE64_ETAIL is also * returned if the a valid length holds non-zero unused tail bits. */ static inline int base64_decode(uint8_t *dst, const uint8_t *src, size_t *dst_len, size_t *src_len, int mode); static inline const char *base64_strerror(int err) { switch (err) { case BASE64_EOK: return "ok"; case BASE64_EARGS: return "invalid argument"; case BASE64_EMODE: return "invalid mode"; case BASE64_EMORE: return "destination full"; case BASE64_ETAIL: return "invalid tail length"; case BASE64_EDIRTY: return "invalid tail content"; default: return "unknown error"; } } static inline size_t base64_encoded_size(size_t len, int mode) { size_t k = len % 3; size_t n = (len * 4 / 3 + 3) & ~(size_t)3; int pad = mode & base64_enc_modifier_padding; if (!pad) { switch (k) { case 2: n -= 1; break; case 1: n -= 2; break; default: break; } } return n; } static inline size_t base64_decoded_size(size_t len) { size_t k = len % 4; size_t n = len / 4 * 3; switch (k) { case 3: return n + 2; case 2: return n + 1; case 1: /* Not valid without padding. */ case 0: default: return n; } } static inline int base64_encode(uint8_t *dst, const uint8_t *src, size_t *dst_len, size_t *src_len, int mode) { const uint8_t *rfc4648_alphabet = (const uint8_t *) "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; const uint8_t *url_alphabet = (const uint8_t *) "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"; const uint8_t *T; uint8_t *dst_base = dst; int pad = mode & base64_enc_modifier_padding; size_t len = 0; int ret = BASE64_EMODE; if (!src_len) { ret = BASE64_EARGS; goto done; } len = *src_len; mode = mode & ~base64_modifier_mask; switch (mode) { case base64_mode_rfc4648: T = rfc4648_alphabet; break; case base64_mode_url: T = url_alphabet; break; default: /* Invalid mode. */ goto done; } ret = BASE64_EOK; /* Encodes 4 destination bytes from 3 source bytes. */ while (len >= 3) { dst[0] = T[((src[0] >> 2))]; dst[1] = T[((src[0] << 4) & 0x30) | (src[1] >> 4)]; dst[2] = T[((src[1] << 2) & 0x3c) | (src[2] >> 6)]; dst[3] = T[((src[2] & 0x3f))]; len -= 3; dst += 4; src += 3; } /* Encodes 8 destination bytes from 1 to 4 source bytes, if any. */ switch(len) { case 2: dst[0] = T[((src[0] >> 2))]; dst[1] = T[((src[0] << 4) & 0x30) | (src[1] >> 4)]; dst[2] = T[((src[1] << 2) & 0x3c)]; dst += 3; if (pad) { *dst++ = '='; } break; case 1: dst[0] = T[((src[0] >> 2))]; dst[1] = T[((src[0] << 4) & 0x30)]; dst += 2; if (pad) { *dst++ = '='; *dst++ = '='; } break; default: pad = 0; break; } len = 0; done: if (dst_len) { *dst_len = (size_t)(dst - dst_base); } if (src_len) { *src_len -= len; } return ret; } static inline int base64_decode(uint8_t *dst, const uint8_t *src, size_t *dst_len, size_t *src_len, int mode) { static const uint8_t cinvalid = 64; static const uint8_t cignore = 65; static const uint8_t cpadding = 66; /* * 0..63: 6-bit encoded value. * 64: flags non-alphabet symbols. * 65: codes for ignored symbols. * 66: codes for pad symbol '='. * All codecs consider padding an optional terminator and if present * consumes as many pad bytes as possible up to block termination, * but does not fail if a block is not full. * * We do not currently have any ignored characters but we might * add spaces as per MIME spec, but assuming spaces only happen * at block boundaries this is probalby better handled by repeated * parsing. */ static const uint8_t base64rfc4648_decode[256] = { 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 62, 64, 64, 64, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 64, 64, 64, 66, 64, 64, 64, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 64, 64, 64, 64, 64, 64, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64 }; static const uint8_t base64url_decode[256] = { 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 62, 64, 64, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 64, 64, 64, 66, 64, 64, 64, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 64, 64, 64, 64, 63, 64, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64 }; static const uint8_t base64rfc4648_decode_skipspace[256] = { 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 65, 64, 64, 65, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 65, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 62, 64, 64, 64, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 64, 64, 64, 66, 64, 64, 64, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 64, 64, 64, 64, 64, 64, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64 }; static const uint8_t base64url_decode_skipspace[256] = { 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 65, 64, 64, 65, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 65, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 62, 64, 64, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 64, 64, 64, 66, 64, 64, 64, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 64, 64, 64, 64, 63, 64, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64 }; int ret = BASE64_EOK; size_t i, k; uint8_t hold[4]; uint8_t *dst_base = dst; size_t limit = (size_t)-1; size_t len = (size_t)-1, mark; const uint8_t *T = base64rfc4648_decode; int skipspace = mode & base64_dec_modifier_skipspace; if (src_len) { len = *src_len; } mark = len; mode = mode & ~base64_modifier_mask; switch (mode) { case base64_mode_rfc4648: T = skipspace ? base64rfc4648_decode_skipspace : base64rfc4648_decode; break; case base64_mode_url: T = skipspace ? base64url_decode_skipspace : base64url_decode; break; default: ret = BASE64_EMODE; goto done; } if (dst_len && *dst_len > 0) { limit = *dst_len; } while(limit > 0) { for (i = 0; i < 4; ++i) { if (len == i) { k = i; len -= i; goto tail; } if ((hold[i] = T[src[i]]) >= cinvalid) { if (hold[i] == cignore) { ++src; --len; --i; continue; } k = i; /* Strip padding and ignore hyphen in padding, if present. */ if (hold[i] == cpadding) { ++i; while (i < len && i < 8) { if (T[src[i]] != cpadding && T[src[i]] != cignore) { break; } ++i; } } len -= i; goto tail; } } if (limit < 3) { goto more; } dst[0] = (uint8_t)((hold[0] << 2) | (hold[1] >> 4)); dst[1] = (uint8_t)((hold[1] << 4) | (hold[2] >> 2)); dst[2] = (uint8_t)((hold[2] << 6) | (hold[3])); dst += 3; src += 4; limit -= 3; len -= 4; mark = len; } done: if (dst_len) { *dst_len = (size_t)(dst - dst_base); } if (src_len) { *src_len -= mark; } return ret; tail: switch (k) { case 0: break; case 2: if ((hold[1] << 4) & 0xff) { goto dirty; } if (limit < 1) { goto more; } dst[0] = (uint8_t)((hold[0] << 2) | (hold[1] >> 4)); dst += 1; break; case 3: if ((hold[2] << 6) & 0xff) { goto dirty; } if (limit < 2) { goto more; } dst[0] = (uint8_t)((hold[0] << 2) | (hold[1] >> 4)); dst[1] = (uint8_t)((hold[1] << 4) | (hold[2] >> 2)); dst += 2; break; default: ret = BASE64_ETAIL; goto done; } mark = len; goto done; dirty: ret = BASE64_EDIRTY; goto done; more: ret = BASE64_EMORE; goto done; } #ifdef __cplusplus } #endif #endif /* PBASE64_H */