diff options
Diffstat (limited to 'subprojects/exess/src/base64.c')
-rw-r--r-- | subprojects/exess/src/base64.c | 163 |
1 files changed, 163 insertions, 0 deletions
diff --git a/subprojects/exess/src/base64.c b/subprojects/exess/src/base64.c new file mode 100644 index 00000000..de64ee68 --- /dev/null +++ b/subprojects/exess/src/base64.c @@ -0,0 +1,163 @@ +/* + Copyright 2011-2021 David Robillard <d@drobilla.net> + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#include "macros.h" +#include "read_utils.h" +#include "string_utils.h" +#include "write_utils.h" + +#include "exess/exess.h" + +#include <assert.h> +#include <stdint.h> +#include <string.h> + +// Map a 6-bit base64 group to a base64 digit +static inline uint8_t +map(const unsigned group) +{ + assert(group < 64); + + // See <http://tools.ietf.org/html/rfc3548#section-3>. + static const uint8_t b64_map[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + + return b64_map[group]; +} + +// Unmap a base64 digit to the numeric value used for decoding +static inline uint8_t +unmap(const uint8_t in) +{ + /* Table indexed by encoded characters that contains the numeric value used + for decoding, shifted up by 47 to be in the range of printable ASCII. A + '$' is a placeholder for characters not in the base64 alphabet. */ + static const uint8_t b64_unmap[] = + "$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$m$$$ncdefghijkl$$$$$$" + "$/0123456789:;<=>?@ABCDEFGH$$$$$$IJKLMNOPQRSTUVWXYZ[\\]^_`ab$$$$" + "$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$" + "$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$"; + + return (uint8_t)(b64_unmap[in] - 47u); +} + +static char +next_char(const char* const str, size_t* const i) +{ + *i += skip_whitespace(str + *i); + + return str[*i]; +} + +size_t +exess_base64_decoded_size(const size_t length) +{ + return (length * 3) / 4 + 2; +} + +ExessResult +exess_read_base64(ExessBlob* const out, const char* const str) +{ + uint8_t* const uout = (uint8_t*)out->data; + const uint8_t* const ustr = (const uint8_t*)str; + size_t i = 0u; + size_t o = 0u; + + while (str[i]) { + // Skip leading whitespace + i += skip_whitespace(str + i); + if (!str[i]) { + break; + } + + // Read next chunk of 4 input characters + uint8_t in[] = "===="; + for (size_t j = 0; j < 4; ++j) { + const char c = next_char(str, &i); + if (!is_base64(c)) { + return result(EXESS_EXPECTED_BASE64, i); + } + + in[j] = ustr[i++]; + } + + if (in[0] == '=' || in[1] == '=' || (in[2] == '=' && in[3] != '=')) { + return result(EXESS_BAD_VALUE, i); + } + + const size_t n_bytes = 1u + (in[2] != '=') + (in[3] != '='); + if (o + n_bytes > out->size) { + return result(EXESS_NO_SPACE, i); + } + + const uint8_t a1 = (uint8_t)(unmap(in[0]) << 2u); + const uint8_t a2 = unmap(in[1]) >> 4u; + + uout[o++] = a1 | a2; + + if (in[2] != '=') { + const uint8_t b1 = (uint8_t)(unmap(in[1]) << 4u) & 0xF0u; + const uint8_t b2 = unmap(in[2]) >> 2u; + + uout[o++] = b1 | b2; + } + + if (in[3] != '=') { + const uint8_t c1 = (uint8_t)(unmap(in[2]) << 6u) & 0xC0u; + const uint8_t c2 = unmap(in[3]); + + uout[o++] = c1 | c2; + } + } + + out->size = o; + return result(EXESS_SUCCESS, i); +} + +ExessResult +exess_write_base64(const size_t data_size, + const void* const data, + const size_t buf_size, + char* const buf) +{ + const size_t length = (data_size + 2) / 3 * 4; + if (!buf) { + return result(EXESS_SUCCESS, length); + } + + if (buf_size < length + 1) { + return result(EXESS_NO_SPACE, 0); + } + + uint8_t* const out = (uint8_t*)buf; + + size_t o = 0; + for (size_t i = 0; i < data_size; i += 3, o += 4) { + uint8_t in[4] = {0, 0, 0, 0}; + const size_t n_in = MIN(3, data_size - i); + memcpy(in, (const uint8_t*)data + i, n_in); + + out[o] = map(in[0] >> 2u); + out[o + 1] = map(((in[0] & 0x03u) << 4u) | ((in[1] & 0xF0u) >> 4u)); + out[o + 2] = + ((n_in > 1u) ? map(((in[1] & 0x0Fu) << 2u) | ((in[2] & 0xC0u) >> 6u)) + : '='); + + out[o + 3] = ((n_in > 2u) ? map(in[2] & 0x3Fu) : '='); + } + + return end_write(EXESS_SUCCESS, buf_size, buf, o); +} |