From 5f67a463545d2004d6cdb29aa85ba48cd918ccde Mon Sep 17 00:00:00 2001 From: Otavio Pontes Date: Tue, 28 Apr 2020 12:13:28 -0700 Subject: [PATCH] string: Add function to convert from string to unsigned ints Signed-off-by: Otavio Pontes --- src/lib/strings.c | 39 +++++++++++++ src/lib/strings.h | 28 +++++++++ test/unit/test_strings.c | 119 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 186 insertions(+) diff --git a/src/lib/strings.c b/src/lib/strings.c index f604fd29..f3bf5551 100644 --- a/src/lib/strings.c +++ b/src/lib/strings.c @@ -165,6 +165,45 @@ int str_to_int(const char *str, int *value) return 0; } +int str_to_uint_endptr(const char *str, char **endptr, unsigned int *value) +{ + long num; + int err; + + errno = 0; + num = strtol(str, endptr, 10); + err = -errno; + + /* When the return value of strtol overflows the int type, don't overflow + * and return an overflow error code. */ + if (num > UINT_MAX) { + num = UINT_MAX; + err = -ERANGE; + } else if (num < 0) { + num = 0; + err = -ERANGE; + } + *value = (unsigned int)num; + + return err; +} + +int str_to_uint(const char *str, unsigned int *value) +{ + char *endptr; + int err = str_to_uint_endptr(str, &endptr, value); + + if (err) { + return err; + } + + if (*endptr != '\0' && !isspace(*endptr)) { + return -EINVAL; + } + + return 0; +} + char *str_to_lower(const char *str) { char *str_lower = malloc_or_die(str_len(str) + 1); diff --git a/src/lib/strings.h b/src/lib/strings.h index d3ed790d..2658ac71 100644 --- a/src/lib/strings.h +++ b/src/lib/strings.h @@ -103,6 +103,34 @@ int str_to_int(const char *str, int *value); */ int str_to_int_endptr(const char *str, char **endptr, int *value); +/** + * Safely convert string to an unsigned integer avoiding overflows + * + * The strtol function is commonly used to convert a string to a number and + * the result is frequently stored in an int type, but type casting a long to + * an int can cause overflows. + * + * This function returns negative error codes based on the errno table: + * -ERANGE is returned when the string is out of range for int value + * -EINVAL is returned when the string isn't a valid number or has any invalid + * trailing character. + */ +int str_to_uint(const char *str, unsigned int *value); + +/** + * Safely convert and string to unsigned integer avoiding overflows. + * + * The strtol function is commonly used to convert a string to a number and + * the result is frequently stored in an int type, but type casting a long to + * an int can cause overflows. + * + * This function returns negative error codes based on the errno table: + * -ERANGE is returned when the string is out of range for int value + * + * endptr is set with the value of the first invalid character in the string. + */ +int str_to_uint_endptr(const char *str, char **endptr, unsigned int *value); + /** * Creates a new string converting all characters from the original string * to its lower case values. diff --git a/test/unit/test_strings.c b/test/unit/test_strings.c index c7ddc0db..10dddc1b 100644 --- a/test/unit/test_strings.c +++ b/test/unit/test_strings.c @@ -239,6 +239,123 @@ static void test_str_to_int_endptr() } +static void test_str_to_uint() +{ + char tmp_str[100]; + int err; + unsigned int value = 0; + size_t i; + + // test some standard values + char *str_values[] = { "0", "-0", "123", "000123" }; + unsigned int values[] = { 0, 0, 123, 123 }; + + for (i = 0; i < sizeof(values) / sizeof(int); i++) { + value = 0xffffff; + err = str_to_uint(str_values[i], &value); + check(err == 0); + check(value == values[i]); + } + + // test limits + value = 0xffffff; + snprintf(tmp_str, sizeof(tmp_str), "%u", UINT_MAX - 1); + err = str_to_uint(tmp_str, &value); + check(err == 0); + check(value == UINT_MAX - 1); + + value = 0xffffff; + snprintf(tmp_str, sizeof(tmp_str), "%u", UINT_MAX); + err = str_to_uint(tmp_str, &value); + check(err == 0); + check(value == UINT_MAX); + + // test errors + value = 0xffffff; + snprintf(tmp_str, sizeof(tmp_str), "%ld", (long)UINT_MAX + 1); + err = str_to_uint(tmp_str, &value); + check(err == -ERANGE); + check(value == UINT_MAX); + + value = 0xffffff; + snprintf(tmp_str, sizeof(tmp_str), "%ld", (long)UINT_MAX + 1000); + err = str_to_uint(tmp_str, &value); + check(err == -ERANGE); + check(value == UINT_MAX); + + // Negative + value = 0xffffff; + snprintf(tmp_str, sizeof(tmp_str), "%d", -1); + err = str_to_uint(tmp_str, &value); + check(err == -ERANGE); + check(value == 0); + + value = 0xffffff; + snprintf(tmp_str, sizeof(tmp_str), "%d", INT_MIN); + err = str_to_uint(tmp_str, &value); + check(err == -ERANGE); + check(value == 0); + + // test EINVAL errors + char *einval_errors[] = { "string", "123.456", "123a", "a123a", "456b123", "0x12345", "4f"}; + + for (i = 0; i < sizeof(einval_errors) / sizeof(char *); i++) { + err = str_to_uint(einval_errors[i], &value); + check(err == -EINVAL); + } +} + +static void test_str_to_uint_endptr() +{ + char *endptr; + int err; + unsigned int value = 0; + + // Test endptr trailing chars + value = 0xffffff; + endptr = NULL; + err = str_to_uint_endptr("123.456", &endptr, &value); + check(err == 0); + check(value == 123); + check(endptr != NULL); + check(*endptr == '.'); + + value = 0xffffff; + endptr = NULL; + err = str_to_uint_endptr("123a456", &endptr, &value); + check(err == 0); + check(value == 123); + check(endptr != NULL); + check(*endptr == 'a'); + + value = 0xffffff; + endptr = NULL; + err = str_to_uint_endptr("123testing456", &endptr, &value); + check(err == 0); + check(value == 123); + check(endptr != NULL); + check(*endptr == 't'); + + value = 0xffffff; + endptr = NULL; + err = str_to_uint_endptr("123-456", &endptr, &value); + check(err == 0); + check(value == 123); + check(endptr != NULL); + check(*endptr == '-'); + + value = 0xffffff; + endptr = NULL; + err = str_to_uint_endptr("-123.456", &endptr, &value); + check(err == -ERANGE); + + value = 0xffffff; + endptr = NULL; + err = str_to_uint_endptr("-123a456", &endptr, &value); + check(err == -ERANGE); + +} + void test_str_join() { struct list *str_list = NULL; @@ -280,6 +397,8 @@ int main() { test_str_to_int(); test_str_to_int_endptr(); test_str_join(); + test_str_to_uint(); + test_str_to_uint_endptr(); return 0; }