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 <[email protected]>
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