[tor-commits] [tor/master] Extract time encoding functions into lib/encoding

nickm at torproject.org nickm at torproject.org
Wed Jun 27 21:22:39 UTC 2018


commit 194a34cdc28c6b309dd9a32f5446409810b1d32b
Author: Nick Mathewson <nickm at torproject.org>
Date:   Wed Jun 27 16:17:46 2018 -0400

    Extract time encoding functions into lib/encoding
---
 src/common/compat.c           |  34 ---
 src/common/compat.h           |   3 -
 src/common/util.c             | 442 -------------------------------------
 src/common/util.h             |  17 +-
 src/lib/encoding/.may_include |   1 +
 src/lib/encoding/include.am   |   6 +-
 src/lib/encoding/time_fmt.c   | 493 ++++++++++++++++++++++++++++++++++++++++++
 src/lib/encoding/time_fmt.h   |  38 ++++
 8 files changed, 537 insertions(+), 497 deletions(-)

diff --git a/src/common/compat.c b/src/common/compat.c
index cdc53175d..48e706456 100644
--- a/src/common/compat.c
+++ b/src/common/compat.c
@@ -965,40 +965,6 @@ compute_num_cpus(void)
   return num_cpus;
 }
 
-/** As localtime_r, but defined for platforms that don't have it:
- *
- * Convert *<b>timep</b> to a struct tm in local time, and store the value in
- * *<b>result</b>.  Return the result on success, or NULL on failure.
- */
-struct tm *
-tor_localtime_r(const time_t *timep, struct tm *result)
-{
-  char *err = NULL;
-  struct tm *r = tor_localtime_r_msg(timep, result, &err);
-  if (err) {
-    log_warn(LD_BUG, "%s", err);
-    tor_free(err);
-  }
-  return r;
-}
-
-/** As gmtime_r, but defined for platforms that don't have it:
- *
- * Convert *<b>timep</b> to a struct tm in UTC, and store the value in
- * *<b>result</b>.  Return the result on success, or NULL on failure.
- */
-struct tm *
-tor_gmtime_r(const time_t *timep, struct tm *result)
-{
-  char *err = NULL;
-  struct tm *r = tor_gmtime_r_msg(timep, result, &err);
-  if (err) {
-    log_warn(LD_BUG, "%s", err);
-    tor_free(err);
-  }
-  return r;
-}
-
 #if defined(HAVE_MLOCKALL) && HAVE_DECL_MLOCKALL && defined(RLIMIT_MEMLOCK)
 #define HAVE_UNIX_MLOCKALL
 #endif
diff --git a/src/common/compat.h b/src/common/compat.h
index 399badb6a..95447d3b8 100644
--- a/src/common/compat.h
+++ b/src/common/compat.h
@@ -65,9 +65,6 @@
 
 /* ===== Time compatibility */
 
-struct tm *tor_localtime_r(const time_t *timep, struct tm *result);
-struct tm *tor_gmtime_r(const time_t *timep, struct tm *result);
-
 #ifndef timeradd
 /** Replacement for timeradd on platforms that do not have it: sets tvout to
  * the sum of tv1 and tv2. */
diff --git a/src/common/util.c b/src/common/util.c
index 92742a814..574b017ba 100644
--- a/src/common/util.c
+++ b/src/common/util.c
@@ -460,448 +460,6 @@ tv_to_msec(const struct timeval *tv)
   return conv;
 }
 
-/** Yield true iff <b>y</b> is a leap-year. */
-#define IS_LEAPYEAR(y) (!(y % 4) && ((y % 100) || !(y % 400)))
-/** Helper: Return the number of leap-days between Jan 1, y1 and Jan 1, y2. */
-static int
-n_leapdays(int year1, int year2)
-{
-  --year1;
-  --year2;
-  return (year2/4 - year1/4) - (year2/100 - year1/100)
-    + (year2/400 - year1/400);
-}
-/** Number of days per month in non-leap year; used by tor_timegm and
- * parse_rfc1123_time. */
-static const int days_per_month[] =
-  { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
-
-/** Compute a time_t given a struct tm.  The result is given in UTC, and
- * does not account for leap seconds.  Return 0 on success, -1 on failure.
- */
-int
-tor_timegm(const struct tm *tm, time_t *time_out)
-{
-  /* This is a pretty ironclad timegm implementation, snarfed from Python2.2.
-   * It's way more brute-force than fiddling with tzset().
-   *
-   * We use int64_t rather than time_t to avoid overflow on multiplication on
-   * platforms with 32-bit time_t. Since year is clipped to INT32_MAX, and
-   * since 365 * 24 * 60 * 60 is approximately 31 million, it's not possible
-   * for INT32_MAX years to overflow int64_t when converted to seconds. */
-  int64_t year, days, hours, minutes, seconds;
-  int i, invalid_year, dpm;
-
-  /* Initialize time_out to 0 for now, to avoid bad usage in case this function
-     fails and the caller ignores the return value. */
-  tor_assert(time_out);
-  *time_out = 0;
-
-  /* avoid int overflow on addition */
-  if (tm->tm_year < INT32_MAX-1900) {
-    year = tm->tm_year + 1900;
-  } else {
-    /* clamp year */
-    year = INT32_MAX;
-  }
-  invalid_year = (year < 1970 || tm->tm_year >= INT32_MAX-1900);
-
-  if (tm->tm_mon >= 0 && tm->tm_mon <= 11) {
-    dpm = days_per_month[tm->tm_mon];
-    if (tm->tm_mon == 1 && !invalid_year && IS_LEAPYEAR(tm->tm_year)) {
-      dpm = 29;
-    }
-  } else {
-    /* invalid month - default to 0 days per month */
-    dpm = 0;
-  }
-
-  if (invalid_year ||
-      tm->tm_mon < 0 || tm->tm_mon > 11 ||
-      tm->tm_mday < 1 || tm->tm_mday > dpm ||
-      tm->tm_hour < 0 || tm->tm_hour > 23 ||
-      tm->tm_min < 0 || tm->tm_min > 59 ||
-      tm->tm_sec < 0 || tm->tm_sec > 60) {
-    log_warn(LD_BUG, "Out-of-range argument to tor_timegm");
-    return -1;
-  }
-  days = 365 * (year-1970) + n_leapdays(1970,(int)year);
-  for (i = 0; i < tm->tm_mon; ++i)
-    days += days_per_month[i];
-  if (tm->tm_mon > 1 && IS_LEAPYEAR(year))
-    ++days;
-  days += tm->tm_mday - 1;
-  hours = days*24 + tm->tm_hour;
-
-  minutes = hours*60 + tm->tm_min;
-  seconds = minutes*60 + tm->tm_sec;
-  /* Check that "seconds" will fit in a time_t. On platforms where time_t is
-   * 32-bit, this check will fail for dates in and after 2038.
-   *
-   * We already know that "seconds" can't be negative because "year" >= 1970 */
-#if SIZEOF_TIME_T < 8
-  if (seconds < TIME_MIN || seconds > TIME_MAX) {
-    log_warn(LD_BUG, "Result does not fit in tor_timegm");
-    return -1;
-  }
-#endif /* SIZEOF_TIME_T < 8 */
-  *time_out = (time_t)seconds;
-  return 0;
-}
-
-/* strftime is locale-specific, so we need to replace those parts */
-
-/** A c-locale array of 3-letter names of weekdays, starting with Sun. */
-static const char *WEEKDAY_NAMES[] =
-  { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };
-/** A c-locale array of 3-letter names of months, starting with Jan. */
-static const char *MONTH_NAMES[] =
-  { "Jan", "Feb", "Mar", "Apr", "May", "Jun",
-    "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
-
-/** Set <b>buf</b> to the RFC1123 encoding of the UTC value of <b>t</b>.
- * The buffer must be at least RFC1123_TIME_LEN+1 bytes long.
- *
- * (RFC1123 format is "Fri, 29 Sep 2006 15:54:20 GMT". Note the "GMT"
- * rather than "UTC".)
- */
-void
-format_rfc1123_time(char *buf, time_t t)
-{
-  struct tm tm;
-
-  tor_gmtime_r(&t, &tm);
-
-  strftime(buf, RFC1123_TIME_LEN+1, "___, %d ___ %Y %H:%M:%S GMT", &tm);
-  tor_assert(tm.tm_wday >= 0);
-  tor_assert(tm.tm_wday <= 6);
-  memcpy(buf, WEEKDAY_NAMES[tm.tm_wday], 3);
-  tor_assert(tm.tm_mon >= 0);
-  tor_assert(tm.tm_mon <= 11);
-  memcpy(buf+8, MONTH_NAMES[tm.tm_mon], 3);
-}
-
-/** Parse the (a subset of) the RFC1123 encoding of some time (in UTC) from
- * <b>buf</b>, and store the result in *<b>t</b>.
- *
- * Note that we only accept the subset generated by format_rfc1123_time above,
- * not the full range of formats suggested by RFC 1123.
- *
- * Return 0 on success, -1 on failure.
-*/
-int
-parse_rfc1123_time(const char *buf, time_t *t)
-{
-  struct tm tm;
-  char month[4];
-  char weekday[4];
-  int i, m, invalid_year;
-  unsigned tm_mday, tm_year, tm_hour, tm_min, tm_sec;
-  unsigned dpm;
-
-  if (strlen(buf) != RFC1123_TIME_LEN)
-    return -1;
-  memset(&tm, 0, sizeof(tm));
-  if (tor_sscanf(buf, "%3s, %2u %3s %u %2u:%2u:%2u GMT", weekday,
-             &tm_mday, month, &tm_year, &tm_hour,
-             &tm_min, &tm_sec) < 7) {
-    char *esc = esc_for_log(buf);
-    log_warn(LD_GENERAL, "Got invalid RFC1123 time %s", esc);
-    tor_free(esc);
-    return -1;
-  }
-
-  m = -1;
-  for (i = 0; i < 12; ++i) {
-    if (!strcmp(month, MONTH_NAMES[i])) {
-      m = i;
-      break;
-    }
-  }
-  if (m<0) {
-    char *esc = esc_for_log(buf);
-    log_warn(LD_GENERAL, "Got invalid RFC1123 time %s: No such month", esc);
-    tor_free(esc);
-    return -1;
-  }
-  tm.tm_mon = m;
-
-  invalid_year = (tm_year >= INT32_MAX || tm_year < 1970);
-  tor_assert(m >= 0 && m <= 11);
-  dpm = days_per_month[m];
-  if (m == 1 && !invalid_year && IS_LEAPYEAR(tm_year)) {
-    dpm = 29;
-  }
-
-  if (invalid_year || tm_mday < 1 || tm_mday > dpm ||
-      tm_hour > 23 || tm_min > 59 || tm_sec > 60) {
-    char *esc = esc_for_log(buf);
-    log_warn(LD_GENERAL, "Got invalid RFC1123 time %s", esc);
-    tor_free(esc);
-    return -1;
-  }
-  tm.tm_mday = (int)tm_mday;
-  tm.tm_year = (int)tm_year;
-  tm.tm_hour = (int)tm_hour;
-  tm.tm_min = (int)tm_min;
-  tm.tm_sec = (int)tm_sec;
-
-  if (tm.tm_year < 1970) {
-    /* LCOV_EXCL_START
-     * XXXX I think this is dead code; we already checked for
-     *      invalid_year above. */
-    tor_assert_nonfatal_unreached();
-    char *esc = esc_for_log(buf);
-    log_warn(LD_GENERAL,
-             "Got invalid RFC1123 time %s. (Before 1970)", esc);
-    tor_free(esc);
-    return -1;
-    /* LCOV_EXCL_STOP */
-  }
-  tm.tm_year -= 1900;
-
-  return tor_timegm(&tm, t);
-}
-
-/** Set <b>buf</b> to the ISO8601 encoding of the local value of <b>t</b>.
- * The buffer must be at least ISO_TIME_LEN+1 bytes long.
- *
- * (ISO8601 format is 2006-10-29 10:57:20)
- */
-void
-format_local_iso_time(char *buf, time_t t)
-{
-  struct tm tm;
-  strftime(buf, ISO_TIME_LEN+1, "%Y-%m-%d %H:%M:%S", tor_localtime_r(&t, &tm));
-}
-
-/** Set <b>buf</b> to the ISO8601 encoding of the GMT value of <b>t</b>.
- * The buffer must be at least ISO_TIME_LEN+1 bytes long.
- */
-void
-format_iso_time(char *buf, time_t t)
-{
-  struct tm tm;
-  strftime(buf, ISO_TIME_LEN+1, "%Y-%m-%d %H:%M:%S", tor_gmtime_r(&t, &tm));
-}
-
-/** As format_local_iso_time, but use the yyyy-mm-ddThh:mm:ss format to avoid
- * embedding an internal space. */
-void
-format_local_iso_time_nospace(char *buf, time_t t)
-{
-  format_local_iso_time(buf, t);
-  buf[10] = 'T';
-}
-
-/** As format_iso_time, but use the yyyy-mm-ddThh:mm:ss format to avoid
- * embedding an internal space. */
-void
-format_iso_time_nospace(char *buf, time_t t)
-{
-  format_iso_time(buf, t);
-  buf[10] = 'T';
-}
-
-/** As format_iso_time_nospace, but include microseconds in decimal
- * fixed-point format.  Requires that buf be at least ISO_TIME_USEC_LEN+1
- * bytes long. */
-void
-format_iso_time_nospace_usec(char *buf, const struct timeval *tv)
-{
-  tor_assert(tv);
-  format_iso_time_nospace(buf, (time_t)tv->tv_sec);
-  tor_snprintf(buf+ISO_TIME_LEN, 8, ".%06d", (int)tv->tv_usec);
-}
-
-/** Given an ISO-formatted UTC time value (after the epoch) in <b>cp</b>,
- * parse it and store its value in *<b>t</b>.  Return 0 on success, -1 on
- * failure.  Ignore extraneous stuff in <b>cp</b> after the end of the time
- * string, unless <b>strict</b> is set. If <b>nospace</b> is set,
- * expect the YYYY-MM-DDTHH:MM:SS format. */
-int
-parse_iso_time_(const char *cp, time_t *t, int strict, int nospace)
-{
-  struct tm st_tm;
-  unsigned int year=0, month=0, day=0, hour=0, minute=0, second=0;
-  int n_fields;
-  char extra_char, separator_char;
-  n_fields = tor_sscanf(cp, "%u-%2u-%2u%c%2u:%2u:%2u%c",
-                        &year, &month, &day,
-                        &separator_char,
-                        &hour, &minute, &second, &extra_char);
-  if (strict ? (n_fields != 7) : (n_fields < 7)) {
-    char *esc = esc_for_log(cp);
-    log_warn(LD_GENERAL, "ISO time %s was unparseable", esc);
-    tor_free(esc);
-    return -1;
-  }
-  if (separator_char != (nospace ? 'T' : ' ')) {
-    char *esc = esc_for_log(cp);
-    log_warn(LD_GENERAL, "ISO time %s was unparseable", esc);
-    tor_free(esc);
-    return -1;
-  }
-  if (year < 1970 || month < 1 || month > 12 || day < 1 || day > 31 ||
-          hour > 23 || minute > 59 || second > 60 || year >= INT32_MAX) {
-    char *esc = esc_for_log(cp);
-    log_warn(LD_GENERAL, "ISO time %s was nonsensical", esc);
-    tor_free(esc);
-    return -1;
-  }
-  st_tm.tm_year = (int)year-1900;
-  st_tm.tm_mon = month-1;
-  st_tm.tm_mday = day;
-  st_tm.tm_hour = hour;
-  st_tm.tm_min = minute;
-  st_tm.tm_sec = second;
-  st_tm.tm_wday = 0; /* Should be ignored. */
-
-  if (st_tm.tm_year < 70) {
-    /* LCOV_EXCL_START
-     * XXXX I think this is dead code; we already checked for
-     *      year < 1970 above. */
-    tor_assert_nonfatal_unreached();
-    char *esc = esc_for_log(cp);
-    log_warn(LD_GENERAL, "Got invalid ISO time %s. (Before 1970)", esc);
-    tor_free(esc);
-    return -1;
-    /* LCOV_EXCL_STOP */
-  }
-  return tor_timegm(&st_tm, t);
-}
-
-/** Given an ISO-formatted UTC time value (after the epoch) in <b>cp</b>,
- * parse it and store its value in *<b>t</b>.  Return 0 on success, -1 on
- * failure. Reject the string if any characters are present after the time.
- */
-int
-parse_iso_time(const char *cp, time_t *t)
-{
-  return parse_iso_time_(cp, t, 1, 0);
-}
-
-/**
- * As parse_iso_time, but parses a time encoded by format_iso_time_nospace().
- */
-int
-parse_iso_time_nospace(const char *cp, time_t *t)
-{
-  return parse_iso_time_(cp, t, 1, 1);
-}
-
-/** Given a <b>date</b> in one of the three formats allowed by HTTP (ugh),
- * parse it into <b>tm</b>.  Return 0 on success, negative on failure. */
-int
-parse_http_time(const char *date, struct tm *tm)
-{
-  const char *cp;
-  char month[4];
-  char wkday[4];
-  int i;
-  unsigned tm_mday, tm_year, tm_hour, tm_min, tm_sec;
-
-  tor_assert(tm);
-  memset(tm, 0, sizeof(*tm));
-
-  /* First, try RFC1123 or RFC850 format: skip the weekday.  */
-  if ((cp = strchr(date, ','))) {
-    ++cp;
-    if (*cp != ' ')
-      return -1;
-    ++cp;
-    if (tor_sscanf(cp, "%2u %3s %4u %2u:%2u:%2u GMT",
-               &tm_mday, month, &tm_year,
-               &tm_hour, &tm_min, &tm_sec) == 6) {
-      /* rfc1123-date */
-      tm_year -= 1900;
-    } else if (tor_sscanf(cp, "%2u-%3s-%2u %2u:%2u:%2u GMT",
-                      &tm_mday, month, &tm_year,
-                      &tm_hour, &tm_min, &tm_sec) == 6) {
-      /* rfc850-date */
-    } else {
-      return -1;
-    }
-  } else {
-    /* No comma; possibly asctime() format. */
-    if (tor_sscanf(date, "%3s %3s %2u %2u:%2u:%2u %4u",
-               wkday, month, &tm_mday,
-               &tm_hour, &tm_min, &tm_sec, &tm_year) == 7) {
-      tm_year -= 1900;
-    } else {
-      return -1;
-    }
-  }
-  tm->tm_mday = (int)tm_mday;
-  tm->tm_year = (int)tm_year;
-  tm->tm_hour = (int)tm_hour;
-  tm->tm_min = (int)tm_min;
-  tm->tm_sec = (int)tm_sec;
-  tm->tm_wday = 0; /* Leave this unset. */
-
-  month[3] = '\0';
-  /* Okay, now decode the month. */
-  /* set tm->tm_mon to dummy value so the check below fails. */
-  tm->tm_mon = -1;
-  for (i = 0; i < 12; ++i) {
-    if (!strcasecmp(MONTH_NAMES[i], month)) {
-      tm->tm_mon = i;
-    }
-  }
-
-  if (tm->tm_year < 0 ||
-      tm->tm_mon < 0  || tm->tm_mon > 11 ||
-      tm->tm_mday < 1 || tm->tm_mday > 31 ||
-      tm->tm_hour < 0 || tm->tm_hour > 23 ||
-      tm->tm_min < 0  || tm->tm_min > 59 ||
-      tm->tm_sec < 0  || tm->tm_sec > 60)
-    return -1; /* Out of range, or bad month. */
-
-  return 0;
-}
-
-/** Given an <b>interval</b> in seconds, try to write it to the
- * <b>out_len</b>-byte buffer in <b>out</b> in a human-readable form.
- * Returns a non-negative integer on success, -1 on failure.
- */
-int
-format_time_interval(char *out, size_t out_len, long interval)
-{
-  /* We only report seconds if there's no hours. */
-  long sec = 0, min = 0, hour = 0, day = 0;
-
-  /* -LONG_MIN is LONG_MAX + 1, which causes signed overflow */
-  if (interval < -LONG_MAX)
-    interval = LONG_MAX;
-  else if (interval < 0)
-    interval = -interval;
-
-  if (interval >= 86400) {
-    day = interval / 86400;
-    interval %= 86400;
-  }
-  if (interval >= 3600) {
-    hour = interval / 3600;
-    interval %= 3600;
-  }
-  if (interval >= 60) {
-    min = interval / 60;
-    interval %= 60;
-  }
-  sec = interval;
-
-  if (day) {
-    return tor_snprintf(out, out_len, "%ld days, %ld hours, %ld minutes",
-                        day, hour, min);
-  } else if (hour) {
-    return tor_snprintf(out, out_len, "%ld hours, %ld minutes", hour, min);
-  } else if (min) {
-    return tor_snprintf(out, out_len, "%ld minutes, %ld seconds", min, sec);
-  } else {
-    return tor_snprintf(out, out_len, "%ld seconds", sec);
-  }
-}
-
 /* =====
  * File helpers
  * ===== */
diff --git a/src/common/util.h b/src/common/util.h
index fa7886d0f..f9018ff88 100644
--- a/src/common/util.h
+++ b/src/common/util.h
@@ -38,6 +38,7 @@
 #include "lib/fs/dir.h"
 #include "lib/fs/files.h"
 #include "lib/fs/path.h"
+#include "lib/encoding/time_fmt.h"
 
 void tor_log_mallinfo(int severity);
 
@@ -81,22 +82,6 @@ char *tor_escape_str_for_pt_args(const char *string,
 long tv_udiff(const struct timeval *start, const struct timeval *end);
 long tv_mdiff(const struct timeval *start, const struct timeval *end);
 int64_t tv_to_msec(const struct timeval *tv);
-int tor_timegm(const struct tm *tm, time_t *time_out);
-#define RFC1123_TIME_LEN 29
-void format_rfc1123_time(char *buf, time_t t);
-int parse_rfc1123_time(const char *buf, time_t *t);
-#define ISO_TIME_LEN 19
-#define ISO_TIME_USEC_LEN (ISO_TIME_LEN+7)
-void format_local_iso_time(char *buf, time_t t);
-void format_iso_time(char *buf, time_t t);
-void format_local_iso_time_nospace(char *buf, time_t t);
-void format_iso_time_nospace(char *buf, time_t t);
-void format_iso_time_nospace_usec(char *buf, const struct timeval *tv);
-int parse_iso_time_(const char *cp, time_t *t, int strict, int nospace);
-int parse_iso_time(const char *buf, time_t *t);
-int parse_iso_time_nospace(const char *cp, time_t *t);
-int parse_http_time(const char *buf, struct tm *tm);
-int format_time_interval(char *out, size_t out_len, long interval);
 
 /* File helpers */
 
diff --git a/src/lib/encoding/.may_include b/src/lib/encoding/.may_include
index 299e0fc4f..92231b513 100644
--- a/src/lib/encoding/.may_include
+++ b/src/lib/encoding/.may_include
@@ -6,3 +6,4 @@ lib/log/*.h
 lib/malloc/*.h
 lib/string/*.h
 lib/testsupport/*.h
+lib/wallclock/*.h
diff --git a/src/lib/encoding/include.am b/src/lib/encoding/include.am
index fcd9baf3d..345fe14e1 100644
--- a/src/lib/encoding/include.am
+++ b/src/lib/encoding/include.am
@@ -5,7 +5,8 @@ noinst_LIBRARIES += src/lib/libtor-encoding-testing.a
 endif
 
 src_lib_libtor_encoding_a_SOURCES =			\
-	src/lib/encoding/binascii.c
+	src/lib/encoding/binascii.c			\
+	src/lib/encoding/time_fmt.c
 
 src_lib_libtor_encoding_testing_a_SOURCES = \
 	$(src_lib_libtor_encoding_a_SOURCES)
@@ -13,4 +14,5 @@ src_lib_libtor_encoding_testing_a_CPPFLAGS = $(AM_CPPFLAGS) $(TEST_CPPFLAGS)
 src_lib_libtor_encoding_testing_a_CFLAGS = $(AM_CFLAGS) $(TEST_CFLAGS)
 
 noinst_HEADERS +=					\
-	src/lib/encoding/binascii.h
+	src/lib/encoding/binascii.h			\
+	src/lib/encoding/time_fmt.h
diff --git a/src/lib/encoding/time_fmt.c b/src/lib/encoding/time_fmt.c
new file mode 100644
index 000000000..ef60f17e3
--- /dev/null
+++ b/src/lib/encoding/time_fmt.c
@@ -0,0 +1,493 @@
+/* Copyright (c) 2001, Matej Pfajfar.
+ * Copyright (c) 2001-2004, Roger Dingledine.
+ * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
+ * Copyright (c) 2007-2018, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+#include "lib/encoding/time_fmt.h"
+#include "lib/log/torlog.h"
+#include "lib/log/escape.h"
+#include "lib/log/util_bug.h"
+#include "lib/malloc/util_malloc.h"
+#include "lib/string/printf.h"
+#include "lib/string/scanf.h"
+#include "lib/wallclock/tm_cvt.h"
+
+#include <string.h>
+#include <time.h>
+
+/** As localtime_r, but defined for platforms that don't have it:
+ *
+ * Convert *<b>timep</b> to a struct tm in local time, and store the value in
+ * *<b>result</b>.  Return the result on success, or NULL on failure.
+ */
+struct tm *
+tor_localtime_r(const time_t *timep, struct tm *result)
+{
+  char *err = NULL;
+  struct tm *r = tor_localtime_r_msg(timep, result, &err);
+  if (err) {
+    log_warn(LD_BUG, "%s", err);
+    tor_free(err);
+  }
+  return r;
+}
+
+/** As gmtime_r, but defined for platforms that don't have it:
+ *
+ * Convert *<b>timep</b> to a struct tm in UTC, and store the value in
+ * *<b>result</b>.  Return the result on success, or NULL on failure.
+ */
+struct tm *
+tor_gmtime_r(const time_t *timep, struct tm *result)
+{
+  char *err = NULL;
+  struct tm *r = tor_gmtime_r_msg(timep, result, &err);
+  if (err) {
+    log_warn(LD_BUG, "%s", err);
+    tor_free(err);
+  }
+  return r;
+}
+
+/** Yield true iff <b>y</b> is a leap-year. */
+#define IS_LEAPYEAR(y) (!(y % 4) && ((y % 100) || !(y % 400)))
+/** Helper: Return the number of leap-days between Jan 1, y1 and Jan 1, y2. */
+static int
+n_leapdays(int year1, int year2)
+{
+  --year1;
+  --year2;
+  return (year2/4 - year1/4) - (year2/100 - year1/100)
+    + (year2/400 - year1/400);
+}
+/** Number of days per month in non-leap year; used by tor_timegm and
+ * parse_rfc1123_time. */
+static const int days_per_month[] =
+  { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
+
+/** Compute a time_t given a struct tm.  The result is given in UTC, and
+ * does not account for leap seconds.  Return 0 on success, -1 on failure.
+ */
+int
+tor_timegm(const struct tm *tm, time_t *time_out)
+{
+  /* This is a pretty ironclad timegm implementation, snarfed from Python2.2.
+   * It's way more brute-force than fiddling with tzset().
+   *
+   * We use int64_t rather than time_t to avoid overflow on multiplication on
+   * platforms with 32-bit time_t. Since year is clipped to INT32_MAX, and
+   * since 365 * 24 * 60 * 60 is approximately 31 million, it's not possible
+   * for INT32_MAX years to overflow int64_t when converted to seconds. */
+  int64_t year, days, hours, minutes, seconds;
+  int i, invalid_year, dpm;
+
+  /* Initialize time_out to 0 for now, to avoid bad usage in case this function
+     fails and the caller ignores the return value. */
+  tor_assert(time_out);
+  *time_out = 0;
+
+  /* avoid int overflow on addition */
+  if (tm->tm_year < INT32_MAX-1900) {
+    year = tm->tm_year + 1900;
+  } else {
+    /* clamp year */
+    year = INT32_MAX;
+  }
+  invalid_year = (year < 1970 || tm->tm_year >= INT32_MAX-1900);
+
+  if (tm->tm_mon >= 0 && tm->tm_mon <= 11) {
+    dpm = days_per_month[tm->tm_mon];
+    if (tm->tm_mon == 1 && !invalid_year && IS_LEAPYEAR(tm->tm_year)) {
+      dpm = 29;
+    }
+  } else {
+    /* invalid month - default to 0 days per month */
+    dpm = 0;
+  }
+
+  if (invalid_year ||
+      tm->tm_mon < 0 || tm->tm_mon > 11 ||
+      tm->tm_mday < 1 || tm->tm_mday > dpm ||
+      tm->tm_hour < 0 || tm->tm_hour > 23 ||
+      tm->tm_min < 0 || tm->tm_min > 59 ||
+      tm->tm_sec < 0 || tm->tm_sec > 60) {
+    log_warn(LD_BUG, "Out-of-range argument to tor_timegm");
+    return -1;
+  }
+  days = 365 * (year-1970) + n_leapdays(1970,(int)year);
+  for (i = 0; i < tm->tm_mon; ++i)
+    days += days_per_month[i];
+  if (tm->tm_mon > 1 && IS_LEAPYEAR(year))
+    ++days;
+  days += tm->tm_mday - 1;
+  hours = days*24 + tm->tm_hour;
+
+  minutes = hours*60 + tm->tm_min;
+  seconds = minutes*60 + tm->tm_sec;
+  /* Check that "seconds" will fit in a time_t. On platforms where time_t is
+   * 32-bit, this check will fail for dates in and after 2038.
+   *
+   * We already know that "seconds" can't be negative because "year" >= 1970 */
+#if SIZEOF_TIME_T < 8
+  if (seconds < TIME_MIN || seconds > TIME_MAX) {
+    log_warn(LD_BUG, "Result does not fit in tor_timegm");
+    return -1;
+  }
+#endif /* SIZEOF_TIME_T < 8 */
+  *time_out = (time_t)seconds;
+  return 0;
+}
+
+/* strftime is locale-specific, so we need to replace those parts */
+
+/** A c-locale array of 3-letter names of weekdays, starting with Sun. */
+static const char *WEEKDAY_NAMES[] =
+  { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };
+/** A c-locale array of 3-letter names of months, starting with Jan. */
+static const char *MONTH_NAMES[] =
+  { "Jan", "Feb", "Mar", "Apr", "May", "Jun",
+    "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
+
+/** Set <b>buf</b> to the RFC1123 encoding of the UTC value of <b>t</b>.
+ * The buffer must be at least RFC1123_TIME_LEN+1 bytes long.
+ *
+ * (RFC1123 format is "Fri, 29 Sep 2006 15:54:20 GMT". Note the "GMT"
+ * rather than "UTC".)
+ */
+void
+format_rfc1123_time(char *buf, time_t t)
+{
+  struct tm tm;
+
+  tor_gmtime_r(&t, &tm);
+
+  strftime(buf, RFC1123_TIME_LEN+1, "___, %d ___ %Y %H:%M:%S GMT", &tm);
+  tor_assert(tm.tm_wday >= 0);
+  tor_assert(tm.tm_wday <= 6);
+  memcpy(buf, WEEKDAY_NAMES[tm.tm_wday], 3);
+  tor_assert(tm.tm_mon >= 0);
+  tor_assert(tm.tm_mon <= 11);
+  memcpy(buf+8, MONTH_NAMES[tm.tm_mon], 3);
+}
+
+/** Parse the (a subset of) the RFC1123 encoding of some time (in UTC) from
+ * <b>buf</b>, and store the result in *<b>t</b>.
+ *
+ * Note that we only accept the subset generated by format_rfc1123_time above,
+ * not the full range of formats suggested by RFC 1123.
+ *
+ * Return 0 on success, -1 on failure.
+*/
+int
+parse_rfc1123_time(const char *buf, time_t *t)
+{
+  struct tm tm;
+  char month[4];
+  char weekday[4];
+  int i, m, invalid_year;
+  unsigned tm_mday, tm_year, tm_hour, tm_min, tm_sec;
+  unsigned dpm;
+
+  if (strlen(buf) != RFC1123_TIME_LEN)
+    return -1;
+  memset(&tm, 0, sizeof(tm));
+  if (tor_sscanf(buf, "%3s, %2u %3s %u %2u:%2u:%2u GMT", weekday,
+             &tm_mday, month, &tm_year, &tm_hour,
+             &tm_min, &tm_sec) < 7) {
+    char *esc = esc_for_log(buf);
+    log_warn(LD_GENERAL, "Got invalid RFC1123 time %s", esc);
+    tor_free(esc);
+    return -1;
+  }
+
+  m = -1;
+  for (i = 0; i < 12; ++i) {
+    if (!strcmp(month, MONTH_NAMES[i])) {
+      m = i;
+      break;
+    }
+  }
+  if (m<0) {
+    char *esc = esc_for_log(buf);
+    log_warn(LD_GENERAL, "Got invalid RFC1123 time %s: No such month", esc);
+    tor_free(esc);
+    return -1;
+  }
+  tm.tm_mon = m;
+
+  invalid_year = (tm_year >= INT32_MAX || tm_year < 1970);
+  tor_assert(m >= 0 && m <= 11);
+  dpm = days_per_month[m];
+  if (m == 1 && !invalid_year && IS_LEAPYEAR(tm_year)) {
+    dpm = 29;
+  }
+
+  if (invalid_year || tm_mday < 1 || tm_mday > dpm ||
+      tm_hour > 23 || tm_min > 59 || tm_sec > 60) {
+    char *esc = esc_for_log(buf);
+    log_warn(LD_GENERAL, "Got invalid RFC1123 time %s", esc);
+    tor_free(esc);
+    return -1;
+  }
+  tm.tm_mday = (int)tm_mday;
+  tm.tm_year = (int)tm_year;
+  tm.tm_hour = (int)tm_hour;
+  tm.tm_min = (int)tm_min;
+  tm.tm_sec = (int)tm_sec;
+
+  if (tm.tm_year < 1970) {
+    /* LCOV_EXCL_START
+     * XXXX I think this is dead code; we already checked for
+     *      invalid_year above. */
+    tor_assert_nonfatal_unreached();
+    char *esc = esc_for_log(buf);
+    log_warn(LD_GENERAL,
+             "Got invalid RFC1123 time %s. (Before 1970)", esc);
+    tor_free(esc);
+    return -1;
+    /* LCOV_EXCL_STOP */
+  }
+  tm.tm_year -= 1900;
+
+  return tor_timegm(&tm, t);
+}
+
+/** Set <b>buf</b> to the ISO8601 encoding of the local value of <b>t</b>.
+ * The buffer must be at least ISO_TIME_LEN+1 bytes long.
+ *
+ * (ISO8601 format is 2006-10-29 10:57:20)
+ */
+void
+format_local_iso_time(char *buf, time_t t)
+{
+  struct tm tm;
+  strftime(buf, ISO_TIME_LEN+1, "%Y-%m-%d %H:%M:%S", tor_localtime_r(&t, &tm));
+}
+
+/** Set <b>buf</b> to the ISO8601 encoding of the GMT value of <b>t</b>.
+ * The buffer must be at least ISO_TIME_LEN+1 bytes long.
+ */
+void
+format_iso_time(char *buf, time_t t)
+{
+  struct tm tm;
+  strftime(buf, ISO_TIME_LEN+1, "%Y-%m-%d %H:%M:%S", tor_gmtime_r(&t, &tm));
+}
+
+/** As format_local_iso_time, but use the yyyy-mm-ddThh:mm:ss format to avoid
+ * embedding an internal space. */
+void
+format_local_iso_time_nospace(char *buf, time_t t)
+{
+  format_local_iso_time(buf, t);
+  buf[10] = 'T';
+}
+
+/** As format_iso_time, but use the yyyy-mm-ddThh:mm:ss format to avoid
+ * embedding an internal space. */
+void
+format_iso_time_nospace(char *buf, time_t t)
+{
+  format_iso_time(buf, t);
+  buf[10] = 'T';
+}
+
+/** As format_iso_time_nospace, but include microseconds in decimal
+ * fixed-point format.  Requires that buf be at least ISO_TIME_USEC_LEN+1
+ * bytes long. */
+void
+format_iso_time_nospace_usec(char *buf, const struct timeval *tv)
+{
+  tor_assert(tv);
+  format_iso_time_nospace(buf, (time_t)tv->tv_sec);
+  tor_snprintf(buf+ISO_TIME_LEN, 8, ".%06d", (int)tv->tv_usec);
+}
+
+/** Given an ISO-formatted UTC time value (after the epoch) in <b>cp</b>,
+ * parse it and store its value in *<b>t</b>.  Return 0 on success, -1 on
+ * failure.  Ignore extraneous stuff in <b>cp</b> after the end of the time
+ * string, unless <b>strict</b> is set. If <b>nospace</b> is set,
+ * expect the YYYY-MM-DDTHH:MM:SS format. */
+int
+parse_iso_time_(const char *cp, time_t *t, int strict, int nospace)
+{
+  struct tm st_tm;
+  unsigned int year=0, month=0, day=0, hour=0, minute=0, second=0;
+  int n_fields;
+  char extra_char, separator_char;
+  n_fields = tor_sscanf(cp, "%u-%2u-%2u%c%2u:%2u:%2u%c",
+                        &year, &month, &day,
+                        &separator_char,
+                        &hour, &minute, &second, &extra_char);
+  if (strict ? (n_fields != 7) : (n_fields < 7)) {
+    char *esc = esc_for_log(cp);
+    log_warn(LD_GENERAL, "ISO time %s was unparseable", esc);
+    tor_free(esc);
+    return -1;
+  }
+  if (separator_char != (nospace ? 'T' : ' ')) {
+    char *esc = esc_for_log(cp);
+    log_warn(LD_GENERAL, "ISO time %s was unparseable", esc);
+    tor_free(esc);
+    return -1;
+  }
+  if (year < 1970 || month < 1 || month > 12 || day < 1 || day > 31 ||
+          hour > 23 || minute > 59 || second > 60 || year >= INT32_MAX) {
+    char *esc = esc_for_log(cp);
+    log_warn(LD_GENERAL, "ISO time %s was nonsensical", esc);
+    tor_free(esc);
+    return -1;
+  }
+  st_tm.tm_year = (int)year-1900;
+  st_tm.tm_mon = month-1;
+  st_tm.tm_mday = day;
+  st_tm.tm_hour = hour;
+  st_tm.tm_min = minute;
+  st_tm.tm_sec = second;
+  st_tm.tm_wday = 0; /* Should be ignored. */
+
+  if (st_tm.tm_year < 70) {
+    /* LCOV_EXCL_START
+     * XXXX I think this is dead code; we already checked for
+     *      year < 1970 above. */
+    tor_assert_nonfatal_unreached();
+    char *esc = esc_for_log(cp);
+    log_warn(LD_GENERAL, "Got invalid ISO time %s. (Before 1970)", esc);
+    tor_free(esc);
+    return -1;
+    /* LCOV_EXCL_STOP */
+  }
+  return tor_timegm(&st_tm, t);
+}
+
+/** Given an ISO-formatted UTC time value (after the epoch) in <b>cp</b>,
+ * parse it and store its value in *<b>t</b>.  Return 0 on success, -1 on
+ * failure. Reject the string if any characters are present after the time.
+ */
+int
+parse_iso_time(const char *cp, time_t *t)
+{
+  return parse_iso_time_(cp, t, 1, 0);
+}
+
+/**
+ * As parse_iso_time, but parses a time encoded by format_iso_time_nospace().
+ */
+int
+parse_iso_time_nospace(const char *cp, time_t *t)
+{
+  return parse_iso_time_(cp, t, 1, 1);
+}
+
+/** Given a <b>date</b> in one of the three formats allowed by HTTP (ugh),
+ * parse it into <b>tm</b>.  Return 0 on success, negative on failure. */
+int
+parse_http_time(const char *date, struct tm *tm)
+{
+  const char *cp;
+  char month[4];
+  char wkday[4];
+  int i;
+  unsigned tm_mday, tm_year, tm_hour, tm_min, tm_sec;
+
+  tor_assert(tm);
+  memset(tm, 0, sizeof(*tm));
+
+  /* First, try RFC1123 or RFC850 format: skip the weekday.  */
+  if ((cp = strchr(date, ','))) {
+    ++cp;
+    if (*cp != ' ')
+      return -1;
+    ++cp;
+    if (tor_sscanf(cp, "%2u %3s %4u %2u:%2u:%2u GMT",
+               &tm_mday, month, &tm_year,
+               &tm_hour, &tm_min, &tm_sec) == 6) {
+      /* rfc1123-date */
+      tm_year -= 1900;
+    } else if (tor_sscanf(cp, "%2u-%3s-%2u %2u:%2u:%2u GMT",
+                      &tm_mday, month, &tm_year,
+                      &tm_hour, &tm_min, &tm_sec) == 6) {
+      /* rfc850-date */
+    } else {
+      return -1;
+    }
+  } else {
+    /* No comma; possibly asctime() format. */
+    if (tor_sscanf(date, "%3s %3s %2u %2u:%2u:%2u %4u",
+               wkday, month, &tm_mday,
+               &tm_hour, &tm_min, &tm_sec, &tm_year) == 7) {
+      tm_year -= 1900;
+    } else {
+      return -1;
+    }
+  }
+  tm->tm_mday = (int)tm_mday;
+  tm->tm_year = (int)tm_year;
+  tm->tm_hour = (int)tm_hour;
+  tm->tm_min = (int)tm_min;
+  tm->tm_sec = (int)tm_sec;
+  tm->tm_wday = 0; /* Leave this unset. */
+
+  month[3] = '\0';
+  /* Okay, now decode the month. */
+  /* set tm->tm_mon to dummy value so the check below fails. */
+  tm->tm_mon = -1;
+  for (i = 0; i < 12; ++i) {
+    if (!strcasecmp(MONTH_NAMES[i], month)) {
+      tm->tm_mon = i;
+    }
+  }
+
+  if (tm->tm_year < 0 ||
+      tm->tm_mon < 0  || tm->tm_mon > 11 ||
+      tm->tm_mday < 1 || tm->tm_mday > 31 ||
+      tm->tm_hour < 0 || tm->tm_hour > 23 ||
+      tm->tm_min < 0  || tm->tm_min > 59 ||
+      tm->tm_sec < 0  || tm->tm_sec > 60)
+    return -1; /* Out of range, or bad month. */
+
+  return 0;
+}
+
+/** Given an <b>interval</b> in seconds, try to write it to the
+ * <b>out_len</b>-byte buffer in <b>out</b> in a human-readable form.
+ * Returns a non-negative integer on success, -1 on failure.
+ */
+int
+format_time_interval(char *out, size_t out_len, long interval)
+{
+  /* We only report seconds if there's no hours. */
+  long sec = 0, min = 0, hour = 0, day = 0;
+
+  /* -LONG_MIN is LONG_MAX + 1, which causes signed overflow */
+  if (interval < -LONG_MAX)
+    interval = LONG_MAX;
+  else if (interval < 0)
+    interval = -interval;
+
+  if (interval >= 86400) {
+    day = interval / 86400;
+    interval %= 86400;
+  }
+  if (interval >= 3600) {
+    hour = interval / 3600;
+    interval %= 3600;
+  }
+  if (interval >= 60) {
+    min = interval / 60;
+    interval %= 60;
+  }
+  sec = interval;
+
+  if (day) {
+    return tor_snprintf(out, out_len, "%ld days, %ld hours, %ld minutes",
+                        day, hour, min);
+  } else if (hour) {
+    return tor_snprintf(out, out_len, "%ld hours, %ld minutes", hour, min);
+  } else if (min) {
+    return tor_snprintf(out, out_len, "%ld minutes, %ld seconds", min, sec);
+  } else {
+    return tor_snprintf(out, out_len, "%ld seconds", sec);
+  }
+}
diff --git a/src/lib/encoding/time_fmt.h b/src/lib/encoding/time_fmt.h
new file mode 100644
index 000000000..41508ce5e
--- /dev/null
+++ b/src/lib/encoding/time_fmt.h
@@ -0,0 +1,38 @@
+/* Copyright (c) 2001, Matej Pfajfar.
+ * Copyright (c) 2001-2004, Roger Dingledine.
+ * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
+ * Copyright (c) 2007-2018, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+#ifndef TOR_TIME_FMT_H
+#define TOR_TIME_FMT_H
+
+#include "orconfig.h"
+#ifdef HAVE_SYS_TYPES_H
+#include <sys/types.h>
+#endif
+
+struct tm;
+struct timeval;
+
+struct tm *tor_localtime_r(const time_t *timep, struct tm *result);
+struct tm *tor_gmtime_r(const time_t *timep, struct tm *result);
+int tor_timegm(const struct tm *tm, time_t *time_out);
+
+#define RFC1123_TIME_LEN 29
+void format_rfc1123_time(char *buf, time_t t);
+int parse_rfc1123_time(const char *buf, time_t *t);
+#define ISO_TIME_LEN 19
+#define ISO_TIME_USEC_LEN (ISO_TIME_LEN+7)
+void format_local_iso_time(char *buf, time_t t);
+void format_iso_time(char *buf, time_t t);
+void format_local_iso_time_nospace(char *buf, time_t t);
+void format_iso_time_nospace(char *buf, time_t t);
+void format_iso_time_nospace_usec(char *buf, const struct timeval *tv);
+int parse_iso_time_(const char *cp, time_t *t, int strict, int nospace);
+int parse_iso_time(const char *buf, time_t *t);
+int parse_iso_time_nospace(const char *cp, time_t *t);
+int parse_http_time(const char *buf, struct tm *tm);
+int format_time_interval(char *out, size_t out_len, long interval);
+
+#endif





More information about the tor-commits mailing list