The 'strtoul' man page has this note: Negative values are considered valid input and are silently converted to the equivalent unsigned long value.
That is, where many callers of strtoul() only want a conversion like "231" -> 231 strtoul() also a sign before the number: "+231" -> 231 "-231" -> 18446744073709551385 In the parse-duration module, both the '+' and the '-' sign are undesired, but are accepted just because strtoul() does too much under the hood. This patch fixes it. 2025-03-21 Bruno Haible <br...@clisp.org> parse-duration: Work around an strtoul() misfeature. * lib/parse-duration.c (str_const_to_ul): Reject a + or - sign between the optional whitespace and the digits. * tests/test-parse-duration.sh: Add some tests with expected failure. * tests/test-parse-duration.c (main): Fix usage message. diff --git a/lib/parse-duration.c b/lib/parse-duration.c index 6c5d7e0251..1fe3f845b3 100644 --- a/lib/parse-duration.c +++ b/lib/parse-duration.c @@ -56,11 +56,25 @@ typedef enum { #undef MAX_DURATION #define MAX_DURATION TYPE_MAXIMUM(time_t) -/* Wrapper around strtoul that does not require a cast. */ +/* Wrapper around strtoul that does not allow a sign. */ static unsigned long str_const_to_ul (cch_t * str, cch_t ** ppz, int base) { - return strtoul (str, (char **)ppz, base); + cch_t * orig_str = str; + while (isspace ((unsigned char) *str)) + str++; + if (isdigit ((unsigned char) *str)) + { + unsigned long ret = strtoul (str, (char **)ppz, base); + if (*ppz == str) + *ppz = orig_str; + return ret; + } + else + { + *ppz = orig_str; + return 0; + } } /* Wrapper around strtol that does not require a cast. */ diff --git a/tests/test-parse-duration.c b/tests/test-parse-duration.c index 36c000856f..29ac7732ea 100644 --- a/tests/test-parse-duration.c +++ b/tests/test-parse-duration.c @@ -30,7 +30,7 @@ main (int argc, char *argv[]) { if (--argc <= 0) { - fprintf (stderr, "USAGE: %s <time-spec> [...]", argv[0]); + fprintf (stderr, "USAGE: %s <time-spec> [...]\n", argv[0]); return 1; } diff --git a/tests/test-parse-duration.sh b/tests/test-parse-duration.sh index cf45913261..5b4e01bb21 100755 --- a/tests/test-parse-duration.sh +++ b/tests/test-parse-duration.sh @@ -47,6 +47,7 @@ func_tmpdir trap 'rm -rf "${tmp}"' EXIT tmpf="${tmp}/tests.txt" +# Tests where we expect success. cat > "${tmpf}" <<- _EOF_ 1 Y 2 M 3 W 4 d 5 h 6 m 7 s P 00010225 T 05:06:07 @@ -65,3 +66,26 @@ do test $v -eq 38898367 || die $v is not 38898367 done exec 3>&- + +# Tests where we expect failure. +cat > "${tmpf}" <<- _EOF_ + T-00302 + T-0:3:2 + T-0 H 3 M 2 S + P+04 Y 03 M 02 D + P04 Y +03 M 02 D + P04 Y 03 M +02 D + _EOF_ + +fail=0 +exec 3< "${tmpf}" +while read line <&3 +do + if ${CHECKER} ${exe} "${line}"; then + echo "Succeeded: ${exe} '${line}'" 1>&2 + fail=1 + fi +done +exec 3>&- + +exit $fail