From afb38f31abd27d5b4ce08023f8bbbaeeb353c55f Mon Sep 17 00:00:00 2001 From: Florian Forster Date: Thu, 15 Sep 2016 08:59:16 +0200 Subject: [PATCH] src/daemon/common.[ch]: Reimplement strjoin(). This new implementation truncates fields rather than aborting when there is more space in the output buffer. Since strjoin() is mostly used to fill plugin and type instances, which are otherwise usually filled with sstrncpy(), i.e. also truncate the string rather than erroring out. The unit test has also been rewritten to test the new functionality. The new functions have been formatted with clang-format. Fixes: #1792 --- src/daemon/common.c | 82 +++++++++++++++++++++------------------ src/daemon/common.h | 13 ++++--- src/daemon/common_test.c | 84 ++++++++++++++++++++++------------------ 3 files changed, 99 insertions(+), 80 deletions(-) diff --git a/src/daemon/common.c b/src/daemon/common.c index d1d8bf15..d53f1ec0 100644 --- a/src/daemon/common.c +++ b/src/daemon/common.c @@ -334,52 +334,60 @@ int strsplit (char *string, char **fields, size_t size) return ((int) i); } -int strjoin (char *buffer, size_t buffer_size, - char **fields, size_t fields_num, - const char *sep) -{ - size_t avail; - char *ptr; - size_t sep_len; +int strjoin(char *buffer, size_t buffer_size, char **fields, size_t fields_num, + const char *sep) { + size_t avail = 0; + char *ptr = buffer; + size_t sep_len = 0; - if ((buffer_size < 1) || (fields_num == 0)) - return (-1); + size_t buffer_req = 0; - memset (buffer, 0, buffer_size); - ptr = buffer; - avail = buffer_size - 1; + if (((fields_num != 0) && (fields == NULL)) || + ((buffer_size != 0) && (buffer == NULL))) + return (-EINVAL); - sep_len = 0; - if (sep != NULL) - sep_len = strlen (sep); + if (buffer != NULL) + buffer[0] = 0; - for (size_t i = 0; i < fields_num; i++) - { - size_t field_len; + if (buffer_size != 0) + avail = buffer_size - 1; - if ((i > 0) && (sep_len > 0)) - { - if (avail < sep_len) - return (-1); + if (sep != NULL) + sep_len = strlen(sep); - memcpy (ptr, sep, sep_len); - ptr += sep_len; - avail -= sep_len; - } - if (avail == 0) - return (-1); + for (size_t i = 0; i < fields_num; i++) { + size_t field_len = strlen(fields[i]); - field_len = strlen (fields[i]); - if (avail < field_len) - field_len = avail; + if (i != 0) + buffer_req += sep_len; + buffer_req += field_len; - memcpy (ptr, fields[i], field_len); - ptr += field_len; - avail -= field_len; - } + if ((i != 0) && (sep_len > 0)) { + if (sep_len >= avail) { + /* prevent subsequent iterations from writing to the + * buffer. */ + avail = 0; + continue; + } + + memcpy(ptr, sep, sep_len); + + ptr += sep_len; + avail -= sep_len; + } + + if (field_len > avail) + field_len = avail; + + memcpy(ptr, fields[i], field_len); + ptr += field_len; + + avail -= field_len; + if (ptr != NULL) + *ptr = 0; + } - assert (buffer[buffer_size - 1] == 0); - return ((int) ((buffer_size - 1) - avail)); + return (int)buffer_req; } int escape_string (char *buffer, size_t buffer_size) diff --git a/src/daemon/common.h b/src/daemon/common.h index 720e5f1b..9a9ae0d3 100644 --- a/src/daemon/common.h +++ b/src/daemon/common.h @@ -147,10 +147,12 @@ int strsplit (char *string, char **fields, size_t size); * is equivalent to the Perl built-in `join'. * * PARAMETERS - * `dst' Buffer where the result is stored. + * `dst' Buffer where the result is stored. Can be NULL if you need to + * determine the required buffer size only. * `dst_len' Length of the destination buffer. No more than this many * bytes will be written to the memory pointed to by `dst', - * including the trailing null-byte. + * including the trailing null-byte. Must be zero if dst is + * NULL. * `fields' Array of strings to be joined. * `fields_num' Number of elements in the `fields' array. * `sep' String to be inserted between any two elements of `fields'. @@ -158,9 +160,10 @@ int strsplit (char *string, char **fields, size_t size); * Instead of passing "" (empty string) one can pass NULL. * * RETURN VALUE - * Returns the number of characters in `dst', NOT including the trailing - * null-byte. If an error occurred (empty array or `dst' too small) a value - * smaller than zero will be returned. + * Returns the number of characters in the resulting string, excluding a + * tailing null byte. If this value is greater than or equal to "dst_len", the + * result in "dst" is truncated (but still null terminated). On error a + * negative value is returned. */ int strjoin (char *dst, size_t dst_len, char **fields, size_t fields_num, const char *sep); diff --git a/src/daemon/common_test.c b/src/daemon/common_test.c index 202ddf6c..44e198dd 100644 --- a/src/daemon/common_test.c +++ b/src/daemon/common_test.c @@ -148,46 +148,54 @@ DEF_TEST(strsplit) return (0); } -DEF_TEST(strjoin) -{ - char buffer[16]; - char *fields[4]; - int status; - - fields[0] = "foo"; - fields[1] = "bar"; - fields[2] = "baz"; - fields[3] = "qux"; - - status = strjoin (buffer, sizeof (buffer), fields, 2, "!"); - OK(status == 7); - EXPECT_EQ_STR ("foo!bar", buffer); - - status = strjoin (buffer, sizeof (buffer), fields, 1, "!"); - OK(status == 3); - EXPECT_EQ_STR ("foo", buffer); - - status = strjoin (buffer, sizeof (buffer), fields, 0, "!"); - OK(status < 0); - - status = strjoin (buffer, sizeof (buffer), fields, 2, "rcht"); - OK(status == 10); - EXPECT_EQ_STR ("foorchtbar", buffer); - - status = strjoin (buffer, sizeof (buffer), fields, 4, ""); - OK(status == 12); - EXPECT_EQ_STR ("foobarbazqux", buffer); +DEF_TEST(strjoin) { + struct { + char **fields; + size_t fields_num; + char *separator; + + int want_return; + char *want_buffer; + } cases + [] = { + /* Normal case. */ + {(char *[]){"foo", "bar"}, 2, "!", 7, "foo!bar"}, + /* One field only. */ + {(char *[]){"foo"}, 1, "!", 3, "foo"}, + /* No fields at all. */ + {NULL, 0, "!", 0, ""}, + /* Longer separator. */ + {(char *[]){"foo", "bar"}, 2, "rcht", 10, "foorchtbar"}, + /* Empty separator. */ + {(char *[]){"foo", "bar"}, 2, "", 6, "foobar"}, + /* NULL separator. */ + {(char *[]){"foo", "bar"}, 2, NULL, 6, "foobar"}, + /* buffer not large enough -> string is truncated. */ + {(char *[]){"aaaaaa", "bbbbbb", "c!"}, 3, "-", 16, "aaaaaa-bbbbbb-c"}, + /* buffer not large enough -> last field fills buffer completely. */ + {(char *[]){"aaaaaaa", "bbbbbbb", "!"}, 3, "-", 17, + "aaaaaaa-bbbbbbb"}, + /* buffer not large enough -> string does *not* end in separator. */ + {(char *[]){"aaaa", "bbbb", "cccc", "!"}, 4, "-", 16, + "aaaa-bbbb-cccc"}, + /* buffer not large enough -> string does not end with partial + separator. */ + {(char *[]){"aaaaaa", "bbbbbb", "!"}, 3, "+-", 17, "aaaaaa+-bbbbbb"}, + }; + + for (size_t i = 0; i < STATIC_ARRAY_SIZE(cases); i++) { + char buffer[16]; + int status; - status = strjoin (buffer, sizeof (buffer), fields, 4, "!"); - OK(status == 15); - EXPECT_EQ_STR ("foo!bar!baz!qux", buffer); + memset(buffer, 0xFF, sizeof(buffer)); + status = strjoin(buffer, sizeof(buffer), cases[i].fields, + cases[i].fields_num, cases[i].separator); + EXPECT_EQ_INT(cases[i].want_return, status); + EXPECT_EQ_STR(cases[i].want_buffer, buffer); + } - fields[0] = "0123"; - fields[1] = "4567"; - fields[2] = "8901"; - fields[3] = "2345"; - status = strjoin (buffer, sizeof (buffer), fields, 4, "-"); - OK(status < 0); + /* use (NULL, 0) to determine required buffer size. */ + EXPECT_EQ_INT(3, strjoin(NULL, 0, (char *[]){"a", "b"}, 2, "-")); return (0); } -- 2.30.2