]> git.ipfire.org Git - thirdparty/freeradius-server.git/commitdiff
add parser for ISO8601 date formats
authorAlan T. DeKok <aland@freeradius.org>
Wed, 21 Aug 2019 12:54:51 +0000 (08:54 -0400)
committerAlan T. DeKok <aland@freeradius.org>
Wed, 21 Aug 2019 12:54:51 +0000 (08:54 -0400)
Which incidentally also gets the time zone conversions correct.
The old parser just ignored the time zone.

src/lib/util/misc.c
src/tests/unit/data_types.txt

index e0c002046c48fb6fbc0827935c9075839d528abc..870d55525ba5b66007d599ce119e55b286ce18d9 100644 (file)
@@ -708,6 +708,31 @@ static char *mystrtok(char **ptr, char const *sep)
        return res;
 }
 
+/*
+ *     Helper function to get a 2-digit date. With a maximum value,
+ *     and a terminating character.
+ */
+static int get_part(char **str, int *date, int max, char term)
+{
+       char *p = *str;
+
+       if (!isdigit((int) *p) || !isdigit((int) p[1])) return -1;
+       *date = (p[0] - '0') * 10  + (p[1] - '0');
+       if (*date > max) return -1;
+
+       p += 2;
+       if (!term) {
+               *str = p;
+               return 0;
+       }
+
+       if (*p != term) return -1;
+       p++;
+
+       *str = p;
+       return 0;
+}
+
 /** Convert string in various formats to a time_t
  *
  * @param date_str input date string.
@@ -740,6 +765,86 @@ int fr_time_from_str(fr_time_t *date, char const *date_str)
        memset(tm, 0, sizeof(*tm));
        tm->tm_isdst = -1;      /* don't know, and don't care about DST */
 
+       /*
+        *      Check for RFC 3339 dates.  Note that we only support
+        *      dates in a ~1000 year period.  If the server is being
+        *      used after 3000AD, someone can patch it then.
+        *
+        *      %Y-%m-%dT%H:%M:%S
+        *      [.%d] sub-seconds
+        *      Z | (+/-)%H:%M time zone offset
+        *
+        */
+       if ((t > 1900) && (t < 3000) && *tail == '-') {
+               unsigned long subseconds;
+               int tz, tz_hour, tz_min;
+
+               p = tail + 1;
+               s_tm.tm_year = t - 1900; /* 'struct tm' starts years in 1900 */
+
+               if (get_part(&p, &s_tm.tm_mon, 13, '-') < 0) return -1;
+               if (s_tm.tm_mon == 0) return -1;
+               s_tm.tm_mon--;  /* ISO is 1..12, where 'struct tm' is 0..11 */
+
+               if (get_part(&p, &s_tm.tm_mday, 31, 'T') < 0) return -1;
+               if (get_part(&p, &s_tm.tm_hour, 23, ':') < 0) return -1;
+               if (get_part(&p, &s_tm.tm_min, 59, ':') < 0) return -1;
+               if (get_part(&p, &s_tm.tm_sec, 60, '\0') < 0) return -1; /* leap seconds */
+
+               if (*p == '.') {
+                       subseconds = strtoul(p, &tail, 10);
+                       if (subseconds > NSEC) return -1;
+                       p = tail;
+               } else {
+                       subseconds = 0;
+               }
+
+               /*
+                *      Time zone is GMT.  Leave well enough
+                *      alone.
+                */
+               if (*p == 'Z') {
+                       if (p[1] != '\0') return -1;
+                       tz = 0;
+                       goto done;
+               }
+
+               if ((*p != '+') && (*p != '-')) return -1;
+               tail = p;       /* remember sign for later */
+               p++;
+
+               if (get_part(&p, &tz_hour, 23, ':') < 0) return -1;
+               if (get_part(&p, &tz_min, 59, '\0') < 0) return -1;
+
+               if (*p != '\0') return -1;
+
+               /*
+                *      We set this, but the timegm() function ignores
+                *      it.  Note also that mktime() ignores it too,
+                *      and treats the time zone as local.
+                *
+                *      We can't store this value in s_tm.gtmoff,
+                *      because the timegm() function helpfully zeros
+                *      it out.
+                */
+               tz = tz_hour * 3600 + tz_min;
+               if (*tail == '-') tz *= -1;
+
+       done:
+               t = timegm(tm);
+               if (t == (time_t) -1) return -1;
+
+               /*
+                *      Add in the time zone offset, which the posix
+                *      functions are too stupid to do.
+                */
+               t += tz;
+
+               *date = fr_time_from_timeval(&(struct timeval) { .tv_sec = t });
+               *date += subseconds;
+               return 0;
+       }
+
        strlcpy(buf, date_str, sizeof(buf));
 
        p = buf;
index 6b5ad108192be0f8ecf7ef47bb25c7ac35657b38..b113f1bafe80ebefc8424ac08a61f054386d2384 100644 (file)
@@ -73,3 +73,21 @@ data Sep 27 1972 00:00:00 GMT
 # 10,000 days
 date 864000000
 data May 19 1997 00:00:00 GMT
+
+#
+#  Input can be whatever.  But for the tests, output is GMT.
+#
+#  Note that this test is wrong, because the HH:MM are the
+#  same, even though they are in different time zones.
+#  Our parse function ignores time zones, which is bad.
+#
+date Aug 21 2019 07:43:03 EDT
+data Aug 21 2019 07:43:03 GMT
+
+#
+#  Can't yet test this as the parse / print / parse / cmp
+#  functions don't yet allow for choosing ISO8601 vs the
+#  traditional FreeRADIUS time.
+#
+#date 2019-08-21T07:40:31-04:00
+#data foo