changeset 10398:c640552acc85

getdate.y: reject an out-of-range timezone value * lib/getdate.y (time_zone_hhmm): Reject any TZ offset that is outside the range [-24...+24]. When specified with only one or two digits, * tests/test-getdate.c: Tests for the fix. * doc/getdate.texi: Document this change.
author Ondřej Vašík <ovasik@redhat.com>
date Sat, 30 Aug 2008 15:04:04 +0200
parents ebacc22075c3
children d409a1cc4085
files ChangeLog doc/getdate.texi lib/getdate.y tests/test-getdate.c
diffstat 4 files changed, 104 insertions(+), 13 deletions(-) [+]
line wrap: on
line diff
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,11 @@
+2008-09-04  Ondřej Vašík  <ovasik@redhat.com>
+
+	getdate.y: reject an out-of-range timezone value
+	* lib/getdate.y (time_zone_hhmm): Reject any TZ offset that is outside
+	the range [-24...+24].  When specified with only one or two digits,
+	* tests/test-getdate.c: Tests for the fix.
+	* doc/getdate.texi: Document this change.
+
 2008-09-03  Bruno Haible  <bruno@clisp.org>
 
 	* doc/glibc-functions/strverscmp.texi: Mention the strverscmp module.
--- a/doc/getdate.texi
+++ b/doc/getdate.texi
@@ -265,16 +265,19 @@
 The time may alternatively be followed by a time zone correction,
 expressed as @samp{@var{s}@var{hh}@var{mm}}, where @var{s} is @samp{+}
 or @samp{-}, @var{hh} is a number of zone hours and @var{mm} is a number
-of zone minutes.  You can also separate @var{hh} from @var{mm} with a colon.
+of zone minutes.
+The zone minutes term, @var{mm}, may be omitted, in which case
+the one- or two-digit correction is interpreted as a number of hours.
+You can also separate @var{hh} from @var{mm} with a colon.
 When a time zone correction is given this way, it
 forces interpretation of the time relative to
 Coordinated Universal Time (@sc{utc}), overriding any previous
 specification for the time zone or the local time zone.  For example,
 @samp{+0530} and @samp{+05:30} both stand for the time zone 5.5 hours
-ahead of @sc{utc} (e.g., India).  The @var{minute}
-part of the time of day may not be elided when a time zone correction
-is used.  This is the best way to specify a time zone correction by
-fractional parts of an hour.
+ahead of @sc{utc} (e.g., India).
+This is the best way to
+specify a time zone correction by fractional parts of an hour.
+The maximum zone correction is 24 hours.
 
 Either @samp{am}/@samp{pm} or a time zone correction may be specified,
 but not both.
--- a/lib/getdate.y
+++ b/lib/getdate.y
@@ -205,7 +205,7 @@
 union YYSTYPE;
 static int yylex (union YYSTYPE *, parser_control *);
 static int yyerror (parser_control const *, char const *);
-static long int time_zone_hhmm (textint, long int);
+static long int time_zone_hhmm (parser_control *, textint, long int);
 
 /* Extract into *PC any date and time info from a string of digits
    of the form e.g., YYYYMMDD, YYMMDD, HHMM, HH (and sometimes YYY,
@@ -358,7 +358,7 @@
 	set_hhmmss (pc, $1.value, $3.value, 0, 0);
 	pc->meridian = MER24;
 	pc->zones_seen++;
-	pc->time_zone = time_zone_hhmm ($4, $5);
+	pc->time_zone = time_zone_hhmm (pc, $4, $5);
       }
   | tUNUMBER ':' tUNUMBER ':' unsigned_seconds o_merid
       {
@@ -370,7 +370,7 @@
 	set_hhmmss (pc, $1.value, $3.value, $5.tv_sec, $5.tv_nsec);
 	pc->meridian = MER24;
 	pc->zones_seen++;
-	pc->time_zone = time_zone_hhmm ($6, $7);
+	pc->time_zone = time_zone_hhmm (pc, $6, $7);
       }
   ;
 
@@ -394,7 +394,7 @@
       { pc->time_zone = $1;
 	apply_relative_time (pc, $2, 1); }
   | tZONE tSNUMBER o_colon_minutes
-      { pc->time_zone = $1 + time_zone_hhmm ($2, $3); }
+      { pc->time_zone = $1 + time_zone_hhmm (pc, $2, $3); }
   | tDAYZONE
       { pc->time_zone = $1 + 60; }
   | tZONE tDST
@@ -795,15 +795,33 @@
 
 /* Convert a time zone expressed as HH:MM into an integer count of
    minutes.  If MM is negative, then S is of the form HHMM and needs
-   to be picked apart; otherwise, S is of the form HH.  */
+   to be picked apart; otherwise, S is of the form HH.  As specified in
+   http://www.opengroup.org/susv3xbd/xbd_chap08.html#tag_08_03, allow
+   only valid TZ range, and consider first two digits as hours, if no
+   minutes specified.  */
 
 static long int
-time_zone_hhmm (textint s, long int mm)
+time_zone_hhmm (parser_control *pc, textint s, long int mm)
 {
+  long int n_minutes;
+
+  /* If the length of S is 1 or 2 and no minutes are specified,
+     interpret it as a number of hours.  */
+  if (s.digits <= 2 && mm < 0)
+    s.value *= 100;
+
   if (mm < 0)
-    return (s.value / 100) * 60 + s.value % 100;
+    n_minutes = (s.value / 100) * 60 + s.value % 100;
   else
-    return s.value * 60 + (s.negative ? -mm : mm);
+    n_minutes = s.value * 60 + (s.negative ? -mm : mm);
+
+  /* If the absolute number of minutes is larger than 24 hours,
+     arrange to reject it by incrementing pc->zones_seen.  Thus,
+     we allow only values in the range UTC-24:00 to UTC+24:00.  */
+  if (24 * 60 < abs (n_minutes))
+    pc->zones_seen++;
+
+  return n_minutes;
 }
 
 static int
--- a/tests/test-getdate.c
+++ b/tests/test-getdate.c
@@ -98,5 +98,67 @@
   ASSERT (result.tv_sec == result2.tv_sec
 	  && result.tv_nsec == result2.tv_nsec);
 
+  /* test if several time zones formats are handled same way */
+  now.tv_sec = 4711;
+  now.tv_nsec = 1267;
+  p = "UTC+14:00";
+  ASSERT (get_date (&result, p, &now));
+  LOG (p, now, result);
+  p = "UTC+14";
+  ASSERT (get_date (&result2, p, &now));
+  LOG (p, now, result2);
+  ASSERT (result.tv_sec == result2.tv_sec
+	  && result.tv_nsec == result2.tv_nsec);
+  p = "UTC+1400";
+  ASSERT (get_date (&result2, p, &now));
+  LOG (p, now, result2);
+  ASSERT (result.tv_sec == result2.tv_sec
+	  && result.tv_nsec == result2.tv_nsec);
+
+  now.tv_sec = 4711;
+  now.tv_nsec = 1267;
+  p = "UTC-14:00";
+  ASSERT (get_date (&result, p, &now));
+  LOG (p, now, result);
+  p = "UTC-14";
+  ASSERT (get_date (&result2, p, &now));
+  LOG (p, now, result2);
+  ASSERT (result.tv_sec == result2.tv_sec
+	  && result.tv_nsec == result2.tv_nsec);
+  p = "UTC-1400";
+  ASSERT (get_date (&result2, p, &now));
+  LOG (p, now, result2);
+  ASSERT (result.tv_sec == result2.tv_sec
+	  && result.tv_nsec == result2.tv_nsec);
+
+  now.tv_sec = 4711;
+  now.tv_nsec = 1267;
+  p = "UTC+0:15";
+  ASSERT (get_date (&result, p, &now));
+  LOG (p, now, result);
+  p = "UTC+0015";
+  ASSERT (get_date (&result2, p, &now));
+  LOG (p, now, result2);
+  ASSERT (result.tv_sec == result2.tv_sec
+	  && result.tv_nsec == result2.tv_nsec);
+
+  now.tv_sec = 4711;
+  now.tv_nsec = 1267;
+  p = "UTC-1:30";
+  ASSERT (get_date (&result, p, &now));
+  LOG (p, now, result);
+  p = "UTC-130";
+  ASSERT (get_date (&result2, p, &now));
+  LOG (p, now, result2);
+  ASSERT (result.tv_sec == result2.tv_sec
+	  && result.tv_nsec == result2.tv_nsec);
+
+
+  /* TZ out of range should cause get_date failure */
+  now.tv_sec = 4711;
+  now.tv_nsec = 1267;
+  p = "UTC+25:00";
+  ASSERT (!get_date (&result, p, &now));
+
   return 0;
 }