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




Reply via email to