mirror of
https://gitlab.freedesktop.org/NetworkManager/NetworkManager.git
synced 2026-01-01 00:10:11 +01:00
glib-aux: add nm_unbase64{char,mem,mem_full}() helpers
These are taken from systemd code. We want to stop using systemd code, so we can eventually drop it.
This commit is contained in:
parent
c95fd646bc
commit
0aa7d59557
3 changed files with 405 additions and 0 deletions
|
|
@ -6714,3 +6714,227 @@ nm_g_main_context_can_acquire(GMainContext *context)
|
|||
g_main_context_release(context);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/*****************************************************************************/
|
||||
|
||||
int
|
||||
nm_unbase64char(char c)
|
||||
{
|
||||
unsigned offset;
|
||||
|
||||
/* copied from systemd's unbase64char():
|
||||
* https://github.com/systemd/systemd/blob/688efe7703328c5a0251fafac55757b8864a9f9a/src/basic/hexdecoct.c#L539 */
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
#define WHITESPACE " \t\n\r"
|
||||
|
||||
static int
|
||||
unbase64_next(const char **p, size_t *l)
|
||||
{
|
||||
int ret;
|
||||
|
||||
assert(p);
|
||||
assert(l);
|
||||
|
||||
/* copied from systemd's unbase64_next():
|
||||
* https://github.com/systemd/systemd/blob/688efe7703328c5a0251fafac55757b8864a9f9a/src/basic/hexdecoct.c#L709 */
|
||||
|
||||
/* Find the next non-whitespace character, and decode it. If we find padding, we return it as INT_MAX. We
|
||||
* greedily skip all preceding and all following whitespace. */
|
||||
|
||||
for (;;) {
|
||||
if (*l == 0)
|
||||
return -EPIPE;
|
||||
|
||||
if (!strchr(WHITESPACE, **p))
|
||||
break;
|
||||
|
||||
/* Skip leading whitespace */
|
||||
(*p)++, (*l)--;
|
||||
}
|
||||
|
||||
if (**p == '=')
|
||||
ret = INT_MAX; /* return padding as INT_MAX */
|
||||
else {
|
||||
ret = nm_unbase64char(**p);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
}
|
||||
|
||||
for (;;) {
|
||||
(*p)++, (*l)--;
|
||||
|
||||
if (*l == 0)
|
||||
break;
|
||||
if (!strchr(WHITESPACE, **p))
|
||||
break;
|
||||
|
||||
/* Skip following whitespace */
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* nm_unbase64mem_full:
|
||||
* @p: a valid base64 string. Whitespace is ignored, but invalid encodings
|
||||
* will cause the function to fail.
|
||||
* @l: the length of @p. @p is not treated as NUL terminated string but
|
||||
* merely as a buffer of ascii characters.
|
||||
* @secure: whether the temporary memory will be cleared to avoid leaving
|
||||
* secrets in memory (see also nm_explicit_bzero()).
|
||||
* @mem: (transfer full): the decoded buffer on success.
|
||||
* @len: the length of @mem on success.
|
||||
*
|
||||
* glib provides g_base64_decode(), but that does not report any errors
|
||||
* from invalid encodings. Our own implementation (based on systemd code)
|
||||
* rejects invalid inputs.
|
||||
*
|
||||
* Returns: a non-negative code on success. Invalid encoding let the
|
||||
* function fail.
|
||||
*/
|
||||
int
|
||||
nm_unbase64mem_full(const char *p, gsize l, gboolean secure, guint8 **ret, gsize *ret_size)
|
||||
{
|
||||
gs_free uint8_t *buf = NULL;
|
||||
const char *x;
|
||||
guint8 *z;
|
||||
gsize len;
|
||||
int r;
|
||||
|
||||
/* copied from systemd's unbase64mem_full():
|
||||
* https://github.com/systemd/systemd/blob/688efe7703328c5a0251fafac55757b8864a9f9a/src/basic/hexdecoct.c#L751 */
|
||||
|
||||
assert(p || l == 0);
|
||||
|
||||
if (l == SIZE_MAX)
|
||||
l = strlen(p);
|
||||
|
||||
/* A group of four input bytes needs three output bytes, in case of padding we need to add two or three extra
|
||||
* bytes. Note that this calculation is an upper boundary, as we ignore whitespace while decoding */
|
||||
len = (l / 4) * 3 + (l % 4 != 0 ? (l % 4) - 1 : 0);
|
||||
|
||||
buf = g_malloc(len + 1);
|
||||
|
||||
for (x = p, z = buf;;) {
|
||||
int a, b, c, d; /* a == 00XXXXXX; b == 00YYYYYY; c == 00ZZZZZZ; d == 00WWWWWW */
|
||||
|
||||
a = unbase64_next(&x, &l);
|
||||
if (a == -EPIPE) /* End of string */
|
||||
break;
|
||||
if (a < 0) {
|
||||
r = a;
|
||||
goto on_failure;
|
||||
}
|
||||
if (a == INT_MAX) { /* Padding is not allowed at the beginning of a 4ch block */
|
||||
r = -EINVAL;
|
||||
goto on_failure;
|
||||
}
|
||||
|
||||
b = unbase64_next(&x, &l);
|
||||
if (b < 0) {
|
||||
r = b;
|
||||
goto on_failure;
|
||||
}
|
||||
if (b
|
||||
== INT_MAX) { /* Padding is not allowed at the second character of a 4ch block either */
|
||||
r = -EINVAL;
|
||||
goto on_failure;
|
||||
}
|
||||
|
||||
c = unbase64_next(&x, &l);
|
||||
if (c < 0) {
|
||||
r = c;
|
||||
goto on_failure;
|
||||
}
|
||||
|
||||
d = unbase64_next(&x, &l);
|
||||
if (d < 0) {
|
||||
r = d;
|
||||
goto on_failure;
|
||||
}
|
||||
|
||||
if (c == INT_MAX) { /* Padding at the third character */
|
||||
|
||||
if (d != INT_MAX) { /* If the third character is padding, the fourth must be too */
|
||||
r = -EINVAL;
|
||||
goto on_failure;
|
||||
}
|
||||
|
||||
/* b == 00YY0000 */
|
||||
if (b & 15) {
|
||||
r = -EINVAL;
|
||||
goto on_failure;
|
||||
}
|
||||
|
||||
if (l > 0) { /* Trailing rubbish? */
|
||||
r = -ENAMETOOLONG;
|
||||
goto on_failure;
|
||||
}
|
||||
|
||||
*(z++) = (uint8_t) a << 2 | (uint8_t) (b >> 4); /* XXXXXXYY */
|
||||
break;
|
||||
}
|
||||
|
||||
if (d == INT_MAX) {
|
||||
/* c == 00ZZZZ00 */
|
||||
if (c & 3) {
|
||||
r = -EINVAL;
|
||||
goto on_failure;
|
||||
}
|
||||
|
||||
if (l > 0) { /* Trailing rubbish? */
|
||||
r = -ENAMETOOLONG;
|
||||
goto on_failure;
|
||||
}
|
||||
|
||||
*(z++) = (uint8_t) a << 2 | (uint8_t) b >> 4; /* XXXXXXYY */
|
||||
*(z++) = (uint8_t) b << 4 | (uint8_t) c >> 2; /* YYYYZZZZ */
|
||||
break;
|
||||
}
|
||||
|
||||
*(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 */
|
||||
}
|
||||
|
||||
*z = 0;
|
||||
|
||||
if (ret_size)
|
||||
*ret_size = (size_t) (z - buf);
|
||||
if (ret)
|
||||
*ret = g_steal_pointer(&buf);
|
||||
|
||||
return 0;
|
||||
|
||||
on_failure:
|
||||
if (secure)
|
||||
nm_explicit_bzero(buf, len);
|
||||
|
||||
return r;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3308,4 +3308,9 @@ gboolean nm_utils_validate_hostname(const char *hostname);
|
|||
|
||||
void nm_utils_thread_local_register_destroy(gpointer tls_data, GDestroyNotify destroy_notify);
|
||||
|
||||
/*****************************************************************************/
|
||||
|
||||
int nm_unbase64char(char c);
|
||||
int nm_unbase64mem_full(const char *p, gsize l, gboolean secure, guint8 **ret, gsize *ret_size);
|
||||
|
||||
#endif /* __NM_SHARED_UTILS_H__ */
|
||||
|
|
|
|||
|
|
@ -1556,6 +1556,179 @@ test_parse_env_file(void)
|
|||
|
||||
/*****************************************************************************/
|
||||
|
||||
static void
|
||||
test_unbase64char(void)
|
||||
{
|
||||
static const int expected[128] = {
|
||||
[0] = -1, [1] = -1, [2] = -1, [3] = -1, [4] = -1, [5] = -1, [6] = -1,
|
||||
[7] = -1, [8] = -1, [9] = -1, [10] = -1, [11] = -1, [12] = -1, [13] = -1,
|
||||
[14] = -1, [15] = -1, [16] = -1, [17] = -1, [18] = -1, [19] = -1, [20] = -1,
|
||||
[21] = -1, [22] = -1, [23] = -1, [24] = -1, [25] = -1, [26] = -1, [27] = -1,
|
||||
[28] = -1, [29] = -1, [30] = -1, [31] = -1, [32] = -1, [33] = -1, [34] = -1,
|
||||
[35] = -1, [36] = -1, [37] = -1, [38] = -1, [39] = -1, [40] = -1, [41] = -1,
|
||||
[42] = -1, ['+'] = 62, [44] = -1, [45] = -1, [46] = -1, ['/'] = 63, ['0'] = 52,
|
||||
['1'] = 53, ['2'] = 54, ['3'] = 55, ['4'] = 56, ['5'] = 57, ['6'] = 58, ['7'] = 59,
|
||||
['8'] = 60, ['9'] = 61, [58] = -1, [59] = -1, [60] = -1, [61] = -1, [62] = -1,
|
||||
[63] = -1, [64] = -1, ['A'] = 0, ['B'] = 1, ['C'] = 2, ['D'] = 3, ['E'] = 4,
|
||||
['F'] = 5, ['G'] = 6, ['H'] = 7, ['I'] = 8, ['J'] = 9, ['K'] = 10, ['L'] = 11,
|
||||
['M'] = 12, ['N'] = 13, ['O'] = 14, ['P'] = 15, ['Q'] = 16, ['R'] = 17, ['S'] = 18,
|
||||
['T'] = 19, ['U'] = 20, ['V'] = 21, ['W'] = 22, ['X'] = 23, ['Y'] = 24, ['Z'] = 25,
|
||||
[91] = -1, [92] = -1, [93] = -1, [94] = -1, [95] = -1, [96] = -1, ['a'] = 26,
|
||||
['b'] = 27, ['c'] = 28, ['d'] = 29, ['e'] = 30, ['f'] = 31, ['g'] = 32, ['h'] = 33,
|
||||
['i'] = 34, ['j'] = 35, ['k'] = 36, ['l'] = 37, ['m'] = 38, ['n'] = 39, ['o'] = 40,
|
||||
['p'] = 41, ['q'] = 42, ['r'] = 43, ['s'] = 44, ['t'] = 45, ['u'] = 46, ['v'] = 47,
|
||||
['w'] = 48, ['x'] = 49, ['y'] = 50, ['z'] = 51, [123] = -1, [124] = -1, [125] = -1,
|
||||
[126] = -1, [127] = -1,
|
||||
};
|
||||
int i;
|
||||
|
||||
/* Copied from systemd's TEST(unbase64char)
|
||||
* https://github.com/systemd/systemd/blob/688efe7703328c5a0251fafac55757b8864a9f9a/src/test/test-hexdecoct.c#L44 */
|
||||
|
||||
g_assert_cmpint(nm_unbase64char('A'), ==, 0);
|
||||
g_assert_cmpint(nm_unbase64char('Z'), ==, 25);
|
||||
g_assert_cmpint(nm_unbase64char('a'), ==, 26);
|
||||
g_assert_cmpint(nm_unbase64char('z'), ==, 51);
|
||||
g_assert_cmpint(nm_unbase64char('0'), ==, 52);
|
||||
g_assert_cmpint(nm_unbase64char('9'), ==, 61);
|
||||
g_assert_cmpint(nm_unbase64char('+'), ==, 62);
|
||||
g_assert_cmpint(nm_unbase64char('/'), ==, 63);
|
||||
g_assert_cmpint(nm_unbase64char('='), ==, -EINVAL);
|
||||
g_assert_cmpint(nm_unbase64char('\0'), ==, -EINVAL);
|
||||
g_assert_cmpint(nm_unbase64char('\1'), ==, -EINVAL);
|
||||
g_assert_cmpint(nm_unbase64char('\x7F'), ==, -EINVAL);
|
||||
g_assert_cmpint(nm_unbase64char('\x80'), ==, -EINVAL);
|
||||
g_assert_cmpint(nm_unbase64char('\xFF'), ==, -EINVAL);
|
||||
|
||||
for (i = 0; i < 256; i++) {
|
||||
int base64;
|
||||
|
||||
base64 = nm_unbase64char((char) i);
|
||||
|
||||
if (base64 < 0) {
|
||||
g_assert_cmpint(base64, ==, -EINVAL);
|
||||
base64 = -1;
|
||||
}
|
||||
|
||||
if (i >= G_N_ELEMENTS(expected)) {
|
||||
g_assert_cmpint(base64, ==, -1);
|
||||
continue;
|
||||
}
|
||||
g_assert_cmpint(base64, ==, expected[i]);
|
||||
}
|
||||
}
|
||||
|
||||
/*****************************************************************************/
|
||||
|
||||
static void
|
||||
test_unbase64mem1(void)
|
||||
{
|
||||
nm_auto_str_buf NMStrBuf encoded_wrapped = NM_STR_BUF_INIT(400, FALSE);
|
||||
uint8_t data[4096];
|
||||
int i_run;
|
||||
|
||||
/* Copied from systemd's TEST(base64mem_linebreak)
|
||||
* https://github.com/systemd/systemd/blob/688efe7703328c5a0251fafac55757b8864a9f9a/src/test/test-hexdecoct.c#L280 */
|
||||
|
||||
for (i_run = 0; i_run < 20; i_run++) {
|
||||
gs_free char *encoded = NULL;
|
||||
gs_free guint8 *decoded = NULL;
|
||||
gsize decoded_size;
|
||||
guint64 n;
|
||||
guint64 m;
|
||||
guint64 i;
|
||||
guint64 j;
|
||||
gssize l;
|
||||
int r;
|
||||
|
||||
/* Try a bunch of differently sized blobs */
|
||||
n = nmtst_get_rand_uint64() % sizeof(data);
|
||||
nmtst_rand_buf(NULL, data, n);
|
||||
|
||||
/* Break at various different columns */
|
||||
m = 1 + (nmtst_get_rand_uint64() % (n + 5));
|
||||
|
||||
encoded = g_base64_encode(data, n);
|
||||
g_assert(encoded);
|
||||
l = strlen(encoded);
|
||||
|
||||
nm_str_buf_reset(&encoded_wrapped);
|
||||
for (i = 0, j = 0; i < l; i++, j++) {
|
||||
if (j == m) {
|
||||
nm_str_buf_append_c(&encoded_wrapped, '\n');
|
||||
j = 0;
|
||||
}
|
||||
nm_str_buf_append_c(&encoded_wrapped, encoded[i]);
|
||||
}
|
||||
|
||||
g_assert_cmpint(strlen(nm_str_buf_get_str(&encoded_wrapped)), ==, encoded_wrapped.len);
|
||||
|
||||
r = nm_unbase64mem_full(nm_str_buf_get_str(&encoded_wrapped),
|
||||
nmtst_get_rand_bool() ? SIZE_MAX : encoded_wrapped.len,
|
||||
nmtst_get_rand_bool(),
|
||||
&decoded,
|
||||
&decoded_size);
|
||||
g_assert_cmpint(r, >=, 0);
|
||||
g_assert_cmpmem(data, n, decoded, decoded_size);
|
||||
|
||||
for (j = 0; j < encoded_wrapped.len; j++)
|
||||
g_assert((nm_str_buf_get_str(&encoded_wrapped)[j] == '\n') == (j % (m + 1) == m));
|
||||
}
|
||||
}
|
||||
|
||||
/*****************************************************************************/
|
||||
|
||||
static void
|
||||
_assert_unbase64mem(const char *input, const char *output, int ret)
|
||||
{
|
||||
gs_free guint8 *buffer = NULL;
|
||||
gsize size = 0;
|
||||
int r;
|
||||
|
||||
r = nm_unbase64mem_full(input, SIZE_MAX, nmtst_get_rand_bool(), &buffer, &size);
|
||||
g_assert_cmpint(r, ==, ret);
|
||||
|
||||
if (ret >= 0) {
|
||||
g_assert_cmpmem(buffer, size, output, strlen(output));
|
||||
g_assert_cmpint(((const char *) buffer)[size], ==, '\0');
|
||||
} else {
|
||||
g_assert(!buffer);
|
||||
g_assert_cmpint(size, ==, 0);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
test_unbase64mem2(void)
|
||||
{
|
||||
/* Copied from systemd's TEST(unbase64mem)
|
||||
* https://github.com/systemd/systemd/blob/688efe7703328c5a0251fafac55757b8864a9f9a/src/test/test-hexdecoct.c#L324 */
|
||||
|
||||
_assert_unbase64mem("", "", 0);
|
||||
_assert_unbase64mem("Zg==", "f", 0);
|
||||
_assert_unbase64mem("Zm8=", "fo", 0);
|
||||
_assert_unbase64mem("Zm9v", "foo", 0);
|
||||
_assert_unbase64mem("Zm9vYg==", "foob", 0);
|
||||
_assert_unbase64mem("Zm9vYmE=", "fooba", 0);
|
||||
_assert_unbase64mem("Zm9vYmFy", "foobar", 0);
|
||||
|
||||
_assert_unbase64mem(" ", "", 0);
|
||||
_assert_unbase64mem(" \n\r ", "", 0);
|
||||
_assert_unbase64mem(" Zg\n== ", "f", 0);
|
||||
_assert_unbase64mem(" Zm 8=\r", "fo", 0);
|
||||
_assert_unbase64mem(" Zm9\n\r\r\nv ", "foo", 0);
|
||||
_assert_unbase64mem(" Z m9vYg==\n\r", "foob", 0);
|
||||
_assert_unbase64mem(" Zm 9vYmE= ", "fooba", 0);
|
||||
_assert_unbase64mem(" Z m9v YmFy ", "foobar", 0);
|
||||
|
||||
_assert_unbase64mem("A", NULL, -EPIPE);
|
||||
_assert_unbase64mem("A====", NULL, -EINVAL);
|
||||
_assert_unbase64mem("AAB==", NULL, -EINVAL);
|
||||
_assert_unbase64mem(" A A A B = ", NULL, -EINVAL);
|
||||
_assert_unbase64mem(" Z m 8 = q u u x ", NULL, -ENAMETOOLONG);
|
||||
}
|
||||
|
||||
/*****************************************************************************/
|
||||
|
||||
NMTST_DEFINE();
|
||||
|
||||
int
|
||||
|
|
@ -1590,6 +1763,9 @@ main(int argc, char **argv)
|
|||
g_test_add_func("/general/test_nm_g_source_sentinel", test_nm_g_source_sentinel);
|
||||
g_test_add_func("/general/test_nm_ascii", test_nm_ascii);
|
||||
g_test_add_func("/general/test_parse_env_file", test_parse_env_file);
|
||||
g_test_add_func("/general/test_unbase64char", test_unbase64char);
|
||||
g_test_add_func("/general/test_unbase64mem1", test_unbase64mem1);
|
||||
g_test_add_func("/general/test_unbase64mem2", test_unbase64mem2);
|
||||
|
||||
return g_test_run();
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue