Also, extend support of offsets and times to the range INT_MIN to
INT_MAX. tzfile already supports offsets in that range because the
TZif format uses 32-bit signed numbers for offsets, so we might as
well be consistent here, if only to allow easier testing via
traditional POSIX TZ strings.
* time/tzset.c: Include stdckdint.h, not stdio.h.
(compute_offset): Remove.
(parse_int, parse_hhmmss): New functions.
(parse_offset): Use parse_hhmmss instead of sscanf to have well-defined
behavior with outlandish input, such as numbers so large they
overflow, or whose textual representations have more than
INT_MAX bytes. Consistently treat overflow as 0 rather than as
some randomish value. Treat values outside the POSIX range
as overflows, as the rest of the code cannot deal with times
greater than week or so or offsets greater than a day or so.
(parse_rule): Use parse_int and parse_hhmmss instead of strtoul and
sscanf, for similar reasons.
---
time/tzset.c | 177 +++++++++++++++++++++++++++++----------------------
1 file changed, 102 insertions(+), 75 deletions(-)
@@ -17,8 +17,8 @@
#include <ctype.h>
#include <stdbool.h>
+#include <stdckdint.h>
#include <stddef.h>
-#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
@@ -133,18 +133,6 @@ update_vars (void)
}
-static unsigned int
-compute_offset (unsigned int ss, unsigned int mm, unsigned int hh)
-{
- if (ss > 59)
- ss = 59;
- if (mm > 59)
- mm = 59;
- if (hh > 24)
- hh = 24;
- return ss + mm * 60 + hh * 60 * 60;
-}
-
/* Parses the time zone abbreviation at *TZP, and writes a pointer to an
interned string to tz_rules[WHICHRULE].name. On success, advances
*TZP, and returns true. Returns false otherwise. */
@@ -182,44 +170,90 @@ parse_tzname (const char **tzp, int whichrule)
return true;
}
-/* Parses the time zone offset at *TZP, and writes it to
- tz_rules[WHICHRULE].offset. Returns true if the parse was
+/* Parse the prefix of *TZP into an int, and update *TZP.
+ Return the unsigned decimal integer represented by a prefix of S; however,
+ return -1 if the integer is missing or if exceeds INT_MAX. */
+static int
+parse_int (char const **tzp)
+{
+ char const *tz = *tzp;
+ if (!isdigit (*tz))
+ return -1;
+ int r = 0;
+ bool v = false;
+ do
+ {
+ v |= ckd_mul (&r, r, 10);
+ v |= ckd_add (&r, r, *tz - '0');
+ tz++;
+ }
+ while (isdigit (*tz));
+
+ *tzp = tz;
+ return v ? -1 : r;
+}
+
+/* Parse an optional [+-]HH[:MM[:SS]] prefix into an integer number of seconds.
+ It is a prefix of the text string *TZP; advance *TZP past the prefix.
+ HH, MM, and SS can be any positive number of digits.
+ The absolute value of the offset must be less than ABS_TOOLARGE.
+ Ordinarily, store the result into *OFFSET and return 0.
+ If the prefix is missing, store 0 and return a negative number.
+ If the result overflows, store 0 and return a positive number. */
+static int
+parse_hhmmss (char const **tzp, int abs_toolarge, int *offset)
+{
+ const char *tz = *tzp;
+ char tz0 = *tz;
+ tz += tz0 == '-' || tz0 == '+';
+ if (!isdigit (*tz))
+ {
+ *offset = 0;
+ return -1;
+ }
+
+ int hh = parse_int (&tz), mm = 0, ss = 0;
+ if (tz[0] == ':' && isdigit (tz[1]))
+ {
+ tz++, mm = parse_int (&tz);
+ if (tz[0] == ':' && isdigit (tz[1]))
+ tz++, ss = parse_int (&tz);
+ }
+
+ bool v = (hh < 0) | (mm < 0) | (ss < 0);
+ int off, mm60;
+ v |= ckd_mul (&off, hh, 60 * 60);
+ v |= ckd_mul (&mm60, mm, 60);
+ v |= ckd_add (&off, off, mm60);
+ v |= ckd_add (&off, off, ss);
+ v |= abs_toolarge <= off;
+ if (tz0 == '-')
+ ckd_sub (&off, 0, off); /* Ignore any overflow. */
+ *tzp = tz;
+ *offset = v ? 0 : off;
+ return v;
+}
+
+/* Parse the time zone offset at *TZP, and write it to
+ tz_rules[WHICHRULE].offset. Return true if the parse was
successful. */
static bool
parse_offset (const char **tzp, int whichrule)
{
- const char *tz = *tzp;
- bool leading_sign = *tz == '-' || *tz == '+';
- if (whichrule == 0
- && (!leading_sign && !isdigit (*tz)))
- return false;
+ int offset;
+ int status = parse_hhmmss (tzp, (24 + 1) * 60 * 60, &offset);
- int sign;
- if (leading_sign)
- sign = *tz++ == '-' ? 1 : -1;
+ if (status == 0)
+ offset = -offset;
+ else if (status < 0 && whichrule != 0)
+ {
+ /* A missing DST stands for one hour east of standard time. */
+ offset = tz_rules[0].offset + 60 * 60;
+ }
else
- sign = -1;
- *tzp = tz;
+ return false;
- unsigned short int hh;
- unsigned short mm = 0;
- unsigned short ss = 0;
- int consumed = 0;
- if (sscanf (tz, "%hu%n:%hu%n:%hu%n",
- &hh, &consumed, &mm, &consumed, &ss, &consumed) > 0)
- tz_rules[whichrule].offset = sign * compute_offset (ss, mm, hh);
- else
- /* Nothing could be parsed. */
- if (whichrule == 0)
- {
- /* Standard time defaults to offset zero. */
- tz_rules[0].offset = 0;
- return false;
- }
- else
- /* DST defaults to one hour later than standard time. */
- tz_rules[1].offset = tz_rules[0].offset + (60 * 60);
- *tzp = tz + consumed;
+ tz_rules[whichrule].offset = offset;
return true;
}
@@ -237,30 +271,32 @@ parse_rule (const char **tzp, int whichrule)
tz += *tz == ',';
/* Get the date of the change. */
- if (*tz == 'J' || isdigit (*tz))
+ bool is_J = *tz == 'J';
+ if (is_J || isdigit (*tz))
{
- char *end;
- tzr->type = *tz == 'J' ? J1 : J0;
- if (tzr->type == J1 && !isdigit (*++tz))
- return false;
- unsigned long int d = strtoul (tz, &end, 10);
- if (end == tz || d > 365)
- return false;
- if (tzr->type == J1 && d == 0)
+ tzr->type = is_J ? J1 : J0;
+ tz += is_J;
+ int d = parse_int (&tz);
+ if (! (is_J <= d && d <= 365))
return false;
tzr->d = d;
- tz = end;
}
else if (*tz == 'M')
{
tzr->type = M;
- int consumed;
- if (sscanf (tz, "M%hu.%hu.%hu%n",
- &tzr->m, &tzr->n, &tzr->d, &consumed) != 3
- || tzr->m < 1 || tzr->m > 12
- || tzr->n < 1 || tzr->n > 5 || tzr->d > 6)
+ tz++;
+ int i = parse_int (&tz);
+ if (! (1 <= i && i <= 12 && *tz++ == '.'))
+ return false;
+ tzr->m = i;
+ i = parse_int (&tz);
+ if (! (1 <= i && i <= 5 && *tz++ == '.'))
return false;
- tz += consumed;
+ tzr->n = i;
+ i = parse_int (&tz);
+ if (! (0 <= i && i <= 6))
+ return false;
+ tzr->d = i;
}
else if (*tz == '\0')
{
@@ -287,31 +323,22 @@ parse_rule (const char **tzp, int whichrule)
else
return false;
+ int secs;
+
if (*tz != '\0' && *tz != '/' && *tz != ',')
return false;
else if (*tz == '/')
{
/* Get the time of day of the change. */
- int negative;
++tz;
- if (*tz == '\0')
+ if (parse_hhmmss (&tz, (167 + 1) * 60 * 60, &secs) != 0)
return false;
- negative = *tz == '-';
- tz += negative;
- /* Default to 2:00 AM. */
- unsigned short hh = 2;
- unsigned short mm = 0;
- unsigned short ss = 0;
- int consumed = 0;
- sscanf (tz, "%hu%n:%hu%n:%hu%n",
- &hh, &consumed, &mm, &consumed, &ss, &consumed);;
- tz += consumed;
- tzr->secs = (negative ? -1 : 1) * ((hh * 60 * 60) + (mm * 60) + ss);
}
else
/* Default to 2:00 AM. */
- tzr->secs = 2 * 60 * 60;
+ secs = 2 * 60 * 60;
+ tzr->secs = secs;
tzr->computed_for = -1;
*tzp = tz;
return true;
@@ -491,7 +518,7 @@ compute_change (tz_rule *rule, int year)
}
/* T is now the Epoch-relative time of 0:00:00 GMT on the day we want.
- Just add the time of day and local offset from GMT, and we're done. */
+ Just subtract the UT offset and add the time of day, and we're done. */
rule->change = t - rule->offset + rule->secs;
rule->computed_for = year;