From 13a5d76b3277a2a499345cc24facc21eb17ccdae Mon Sep 17 00:00:00 2001 From: Tom Gundersen Date: Fri, 10 Jul 2015 14:38:19 +0200 Subject: basic: util - add base64mem() function similar to hexmem() This implements RFC4648 for a slightly more compact representation of binary data compared to hex (6 bits per character rather than 4). --- src/basic/util.c | 174 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/basic/util.h | 5 ++ src/test/test-util.c | 100 +++++++++++++++++++++++++++++ 3 files changed, 279 insertions(+) diff --git a/src/basic/util.c b/src/basic/util.c index 61dd6c4227..bc917ae574 100644 --- a/src/basic/util.c +++ b/src/basic/util.c @@ -954,6 +954,180 @@ int unhexmem(const char *p, size_t l, void **mem, size_t *len) { return 0; } +/* https://tools.ietf.org/html/rfc4648#section-4 */ +char base64char(int x) { + static const char table[64] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789+/"; + return table[x & 63]; +} + +int unbase64char(char c) { + unsigned offset; + + if (c >= 'A' && c <= 'Z') + return c - 'A'; + + offset = 'Z' - 'A' + 1; + + if (c >= 'a' && c <= 'z') + return c - 'a' + offset; + + offset += 'z' - 'a' + 1; + + if (c >= '0' && c <= '9') + return c - '0' + offset; + + offset += '9' - '0' + 1; + + if (c == '+') + return offset; + + offset ++; + + if (c == '/') + return offset; + + return -EINVAL; +} + +char *base64mem(const void *p, size_t l) { + char *r, *z; + const uint8_t *x; + + /* three input bytes makes four output bytes, padding is added so we must round up */ + z = r = malloc(4 * (l + 2) / 3 + 1); + if (!r) + return NULL; + + for (x = p; x < (const uint8_t*) p + (l / 3) * 3; x += 3) { + /* x[0] == XXXXXXXX; x[1] == YYYYYYYY; x[2] == ZZZZZZZZ */ + *(z++) = base64char(x[0] >> 2); /* 00XXXXXX */ + *(z++) = base64char((x[0] & 3) << 4 | x[1] >> 4); /* 00XXYYYY */ + *(z++) = base64char((x[1] & 15) << 2 | x[2] >> 6); /* 00YYYYZZ */ + *(z++) = base64char(x[2] & 63); /* 00ZZZZZZ */ + } + + switch (l % 3) { + case 2: + *(z++) = base64char(x[0] >> 2); /* 00XXXXXX */ + *(z++) = base64char((x[0] & 3) << 4 | x[1] >> 4); /* 00XXYYYY */ + *(z++) = base64char((x[1] & 15) << 2); /* 00YYYY00 */ + *(z++) = '='; + + break; + case 1: + *(z++) = base64char(x[0] >> 2); /* 00XXXXXX */ + *(z++) = base64char((x[0] & 3) << 4); /* 00XX0000 */ + *(z++) = '='; + *(z++) = '='; + + break; + } + + *z = 0; + return r; +} + +int unbase64mem(const char *p, size_t l, void **mem, size_t *_len) { + _cleanup_free_ uint8_t *t = NULL; + int a, b, c, d; + uint8_t *r, *z; + const char *x; + size_t len; + + assert(p); + + /* padding ensures any base63 input has input divisible by 4 */ + if (l % 4 != 0) + return -EINVAL; + + /* strip the padding */ + if (l > 0 && p[l - 1] == '=') + l --; + if (l > 0 && p[l - 1] == '=') + l --; + + /* a group of four input bytes needs three output bytes, in case of + padding we need to add two or three extra bytes */ + len = (l / 4) * 3 + (l % 4 ? (l % 4) - 1 : 0); + + z = r = malloc(len + 1); + if (!r) + return -ENOMEM; + + for (x = p; x < p + (l / 4) * 4; x += 4) { + /* a == 00XXXXXX; b == 00YYYYYY; c == 00ZZZZZZ; d == 00WWWWWW */ + a = unbase64char(x[0]); + if (a < 0) + return -EINVAL; + + b = unbase64char(x[1]); + if (b < 0) + return -EINVAL; + + c = unbase64char(x[2]); + if (c < 0) + return -EINVAL; + + d = unbase64char(x[3]); + if (d < 0) + return -EINVAL; + + *(z++) = (uint8_t) a << 2 | (uint8_t) b >> 4; /* XXXXXXYY */ + *(z++) = (uint8_t) b << 4 | (uint8_t) c >> 2; /* YYYYZZZZ */ + *(z++) = (uint8_t) c << 6 | (uint8_t) d; /* ZZWWWWWW */ + } + + switch (l % 4) { + case 3: + a = unbase64char(x[0]); + if (a < 0) + return -EINVAL; + + b = unbase64char(x[1]); + if (b < 0) + return -EINVAL; + + c = unbase64char(x[2]); + if (c < 0) + return -EINVAL; + + /* c == 00ZZZZ00 */ + if (c & 3) + return -EINVAL; + + *(z++) = (uint8_t) a << 2 | (uint8_t) b >> 4; /* XXXXXXYY */ + *(z++) = (uint8_t) b << 4 | (uint8_t) c >> 2; /* YYYYZZZZ */ + + break; + case 2: + a = unbase64char(x[0]); + if (a < 0) + return -EINVAL; + + b = unbase64char(x[1]); + if (b < 0) + return -EINVAL; + + /* b == 00YY0000 */ + if (b & 15) + return -EINVAL; + + *(z++) = (uint8_t) a << 2 | (uint8_t) (b >> 4); /* XXXXXXYY */ + + break; + } + + *z = 0; + + *mem = r; + r = NULL; + *_len = len; + + return 0; +} + char octchar(int x) { return '0' + (x & 7); } diff --git a/src/basic/util.h b/src/basic/util.h index ea77907f7f..dae43006e4 100644 --- a/src/basic/util.h +++ b/src/basic/util.h @@ -240,6 +240,8 @@ char octchar(int x) _const_; int unoctchar(char c) _const_; char decchar(int x) _const_; int undecchar(char c) _const_; +char base64char(int x) _const_; +int unbase64char(char c) _const_; char *cescape(const char *s); size_t cescape_char(char c, char *buf); @@ -616,6 +618,9 @@ static inline void *mempset(void *s, int c, size_t n) { char *hexmem(const void *p, size_t l); int unhexmem(const char *p, size_t l, void **mem, size_t *len); +char *base64mem(const void *p, size_t l); +int unbase64mem(const char *p, size_t l, void **mem, size_t *len); + char *strextend(char **x, ...) _sentinel_; char *strrep(const char *s, unsigned n); diff --git a/src/test/test-util.c b/src/test/test-util.c index f7949ebaac..72fbc345c2 100644 --- a/src/test/test-util.c +++ b/src/test/test-util.c @@ -390,6 +390,24 @@ static void test_unhexchar(void) { assert_se(unhexchar('0') == 0x0); } +static void test_base64char(void) { + assert_se(base64char(0) == 'A'); + assert_se(base64char(26) == 'a'); + assert_se(base64char(63) == '/'); +} + +static void test_unbase64char(void) { + assert_se(unbase64char('A') == 0); + assert_se(unbase64char('Z') == 25); + assert_se(unbase64char('a') == 26); + assert_se(unbase64char('z') == 51); + assert_se(unbase64char('0') == 52); + assert_se(unbase64char('9') == 61); + assert_se(unbase64char('+') == 62); + assert_se(unbase64char('/') == 63); + assert_se(unbase64char('=') == -EINVAL); +} + static void test_octchar(void) { assert_se(octchar(00) == '0'); assert_se(octchar(07) == '7'); @@ -434,6 +452,84 @@ static void test_unhexmem(void) { assert_se(memcmp(hex, hex2, strlen(hex) - 1) == 0); } +/* https://tools.ietf.org/html/rfc4648#section-10 */ +static void test_base64mem(void) { + char *b64; + + b64 = base64mem("", strlen("")); + assert_se(b64); + assert_se(streq(b64, "")); + free(b64); + + b64 = base64mem("f", strlen("f")); + assert_se(b64); + assert_se(streq(b64, "Zg==")); + free(b64); + + b64 = base64mem("fo", strlen("fo")); + assert_se(b64); + assert_se(streq(b64, "Zm8=")); + free(b64); + + b64 = base64mem("foo", strlen("foo")); + assert_se(b64); + assert_se(streq(b64, "Zm9v")); + free(b64); + + b64 = base64mem("foob", strlen("foob")); + assert_se(b64); + assert_se(streq(b64, "Zm9vYg==")); + free(b64); + + b64 = base64mem("fooba", strlen("fooba")); + assert_se(b64); + assert_se(streq(b64, "Zm9vYmE=")); + free(b64); + + b64 = base64mem("foobar", strlen("foobar")); + assert_se(b64); + assert_se(streq(b64, "Zm9vYmFy")); + free(b64); +} + +static void test_unbase64mem(void) { + void *mem; + size_t len; + + assert_se(unbase64mem("", strlen(""), &mem, &len) == 0); + assert_se(streq(strndupa(mem, len), "")); + free(mem); + + assert_se(unbase64mem("Zg==", strlen("Zg=="), &mem, &len) == 0); + assert_se(streq(strndupa(mem, len), "f")); + free(mem); + + assert_se(unbase64mem("Zm8=", strlen("Zm8="), &mem, &len) == 0); + assert_se(streq(strndupa(mem, len), "fo")); + free(mem); + + assert_se(unbase64mem("Zm9v", strlen("Zm9v"), &mem, &len) == 0); + assert_se(streq(strndupa(mem, len), "foo")); + free(mem); + + assert_se(unbase64mem("Zm9vYg==", strlen("Zm9vYg=="), &mem, &len) == 0); + assert_se(streq(strndupa(mem, len), "foob")); + free(mem); + + assert_se(unbase64mem("Zm9vYmE=", strlen("Zm9vYmE="), &mem, &len) == 0); + assert_se(streq(strndupa(mem, len), "fooba")); + free(mem); + + assert_se(unbase64mem("Zm9vYmFy", strlen("Zm9vYmFy"), &mem, &len) == 0); + assert_se(streq(strndupa(mem, len), "foobar")); + free(mem); + + assert_se(unbase64mem("A", strlen("A"), &mem, &len) == -EINVAL); + assert_se(unbase64mem("A====", strlen("A===="), &mem, &len) == -EINVAL); + assert_se(unbase64mem("AAB==", strlen("AAB=="), &mem, &len) == -EINVAL); + assert_se(unbase64mem("AAAB=", strlen("AAAB="), &mem, &len) == -EINVAL); +} + static void test_cescape(void) { _cleanup_free_ char *escaped; @@ -1828,11 +1924,15 @@ int main(int argc, char *argv[]) { test_in_charset(); test_hexchar(); test_unhexchar(); + test_base64char(); + test_unbase64char(); test_octchar(); test_unoctchar(); test_decchar(); test_undecchar(); test_unhexmem(); + test_base64mem(); + test_unbase64mem(); test_cescape(); test_cunescape(); test_foreach_word(); -- cgit v1.2.3-54-g00ecf