diff --git a/str.c b/str.c index 9b293408..b70a5f67 100644 --- a/str.c +++ b/str.c @@ -663,14 +663,29 @@ int str_to_int32(const char *str, unsigned base, int32_t *result, const char **a int str_to_uint32(const char *str, unsigned base, uint32_t *result, const char **afterp) { - if (isspace(*str)) - return 0; - const char *end = str; - errno = 0; - unsigned long value = strtoul(str, (char**)&end, base); + return strn_to_uint32(str, 0, base, result, afterp); +} + +int strn_to_uint32(const char *str, size_t strlen, unsigned base, uint32_t *result, const char **afterp) +{ + assert(base > 0); + assert(base <= 16); + uint32_t value = 0; + uint32_t newvalue = 0; + const char *const end = str + strlen; + const char *s; + for (s = str; strlen ? s < end : *s; ++s) { + int digit = hexvalue(*s); + if (digit < 0 || (unsigned)digit >= base) + break; + newvalue = value * base + digit; + if (newvalue < value) // overflow + break; + value = newvalue; + } if (afterp) - *afterp = end; - if (errno == ERANGE || end == str || value > UINT32_MAX || isdigit(*end) || (!afterp && *end)) + *afterp = s; + if (s == str || value > UINT32_MAX || value != newvalue || (!afterp && (strlen ? s != end : *s))) return 0; if (result) *result = value; diff --git a/str.h b/str.h index b445e087..08b0ce97 100644 --- a/str.h +++ b/str.h @@ -396,6 +396,19 @@ int str_to_uint32(const char *str, unsigned base, uint32_t *result, const char * int str_to_int64(const char *str, unsigned base, int64_t *result, const char **afterp); int str_to_uint64(const char *str, unsigned base, uint64_t *result, const char **afterp); +/* Parse a length-bound string as an integer in ASCII radix notation in the given 'base' (eg, + * base=10 means decimal). + * + * Returns 1 if a valid integer is parsed, storing the value in *result (unless result is NULL) and + * storing a pointer to the immediately succeeding character in *afterp. If afterp is NULL then + * returns 0 unless all 'strlen' characters of the string were consumed. If no integer is parsed or + * if the integer overflows (too many digits), then returns 0, leaving *result unchanged and setting + * setting *afterp to point to the character where parsing failed. + * + * @author Andrew Bettison + */ +int strn_to_uint32(const char *str, size_t strlen, unsigned base, uint32_t *result, const char **afterp); + /* Parse a string as an integer in ASCII radix notation in the given 'base' (eg, base=10 means * decimal) and scale the result by a factor given by an optional suffix "scaling" character in the * set {kKmMgG}: 'k' = 1e3, 'K' = 1<<10, 'm' = 1e6, 'M' = 1<<20, 'g' = 1e9, 'G' = * 1<<30.