tor

The Tor anonymity network
git clone https://git.dasho.dev/tor.git
Log | Files | Refs | README | LICENSE

time_fmt.c (16276B)


      1 /* Copyright (c) 2001, Matej Pfajfar.
      2 * Copyright (c) 2001-2004, Roger Dingledine.
      3 * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
      4 * Copyright (c) 2007-2021, The Tor Project, Inc. */
      5 /* See LICENSE for licensing information */
      6 
      7 /**
      8 * \file time_fmt.c
      9 *
     10 * \brief Encode and decode time in various formats.
     11 *
     12 * This module is higher-level than the conversion functions in "wallclock",
     13 * and handles a larger variety of types.  It converts between different time
     14 * formats, and encodes and decodes them from strings.
     15 **/
     16 #define TIME_FMT_PRIVATE
     17 
     18 #include "lib/encoding/time_fmt.h"
     19 #include "lib/log/log.h"
     20 #include "lib/log/escape.h"
     21 #include "lib/log/util_bug.h"
     22 #include "lib/malloc/malloc.h"
     23 #include "lib/string/printf.h"
     24 #include "lib/string/scanf.h"
     25 #include "lib/wallclock/time_to_tm.h"
     26 
     27 #include <string.h>
     28 #include <time.h>
     29 #include <errno.h>
     30 
     31 #ifdef HAVE_SYS_TIME_H
     32 #include <sys/time.h>
     33 #endif
     34 
     35 #ifdef _WIN32
     36 /* For struct timeval */
     37 #include <winsock2.h>
     38 #endif
     39 
     40 /** As localtime_r, but defined for platforms that don't have it:
     41 *
     42 * Convert *<b>timep</b> to a struct tm in local time, and store the value in
     43 * *<b>result</b>.  Return the result on success, or NULL on failure.
     44 *
     45 * Treat malformatted inputs localtime outputs as a BUG.
     46 */
     47 struct tm *
     48 tor_localtime_r(const time_t *timep, struct tm *result)
     49 {
     50  char *err = NULL;
     51  struct tm *r = tor_localtime_r_msg(timep, result, &err);
     52  if (err) {
     53    log_warn(LD_BUG, "%s", err);
     54    tor_free(err);
     55  }
     56  return r;
     57 }
     58 
     59 /** As gmtime_r, but defined for platforms that don't have it:
     60 *
     61 * Convert *<b>timep</b> to a struct tm in UTC, and store the value in
     62 * *<b>result</b>.  Return the result on success, or NULL on failure.
     63 *
     64 * Treat malformatted inputs or gmtime outputs as a BUG.
     65 */
     66 struct tm *
     67 tor_gmtime_r(const time_t *timep, struct tm *result)
     68 {
     69  char *err = NULL;
     70  struct tm *r = tor_gmtime_r_msg(timep, result, &err);
     71  if (err) {
     72    log_warn(LD_BUG, "%s", err);
     73    tor_free(err);
     74  }
     75  return r;
     76 }
     77 
     78 /** Yield true iff <b>y</b> is a leap-year. */
     79 #define IS_LEAPYEAR(y) (!(y % 4) && ((y % 100) || !(y % 400)))
     80 /** Helper: Return the number of leap-days between Jan 1, y1 and Jan 1, y2. */
     81 static int
     82 n_leapdays(int year1, int year2)
     83 {
     84  --year1;
     85  --year2;
     86  return (year2/4 - year1/4) - (year2/100 - year1/100)
     87    + (year2/400 - year1/400);
     88 }
     89 /** Number of days per month in non-leap year; used by tor_timegm and
     90 * parse_rfc1123_time. */
     91 static const int days_per_month[] =
     92  { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
     93 
     94 /** Compute a time_t given a struct tm.  The result is given in UTC, and
     95 * does not account for leap seconds.  Return 0 on success, -1 on failure.
     96 */
     97 ATTR_UNUSED STATIC int
     98 tor_timegm_impl(const struct tm *tm, time_t *time_out)
     99 {
    100  /* This is a pretty ironclad timegm implementation, snarfed from Python2.2.
    101   * It's way more brute-force than fiddling with tzset().
    102   *
    103   * We use int64_t rather than time_t to avoid overflow on multiplication on
    104   * platforms with 32-bit time_t. Since year is clipped to INT32_MAX, and
    105   * since 365 * 24 * 60 * 60 is approximately 31 million, it's not possible
    106   * for INT32_MAX years to overflow int64_t when converted to seconds. */
    107  int64_t year, days, hours, minutes, seconds;
    108  int i, invalid_year, dpm;
    109 
    110  /* Initialize time_out to 0 for now, to avoid bad usage in case this function
    111     fails and the caller ignores the return value. */
    112  tor_assert(time_out);
    113  *time_out = 0;
    114 
    115  /* avoid int overflow on addition */
    116  if (tm->tm_year < INT32_MAX-1900) {
    117    year = tm->tm_year + 1900;
    118  } else {
    119    /* clamp year */
    120    year = INT32_MAX;
    121  }
    122  invalid_year = (year < 1970 || tm->tm_year >= INT32_MAX-1900);
    123 
    124  if (tm->tm_mon >= 0 && tm->tm_mon <= 11) {
    125    dpm = days_per_month[tm->tm_mon];
    126    if (tm->tm_mon == 1 && !invalid_year && IS_LEAPYEAR(tm->tm_year)) {
    127      dpm = 29;
    128    }
    129  } else {
    130    /* invalid month - default to 0 days per month */
    131    dpm = 0;
    132  }
    133 
    134  if (invalid_year ||
    135      tm->tm_mon < 0 || tm->tm_mon > 11 ||
    136      tm->tm_mday < 1 || tm->tm_mday > dpm ||
    137      tm->tm_hour < 0 || tm->tm_hour > 23 ||
    138      tm->tm_min < 0 || tm->tm_min > 59 ||
    139      tm->tm_sec < 0 || tm->tm_sec > 60) {
    140    log_warn(LD_BUG, "Out-of-range argument to tor_timegm");
    141    return -1;
    142  }
    143  days = 365 * (year-1970) + n_leapdays(1970,(int)year);
    144  for (i = 0; i < tm->tm_mon; ++i)
    145    days += days_per_month[i];
    146  if (tm->tm_mon > 1 && IS_LEAPYEAR(year))
    147    ++days;
    148  days += tm->tm_mday - 1;
    149  hours = days*24 + tm->tm_hour;
    150 
    151  minutes = hours*60 + tm->tm_min;
    152  seconds = minutes*60 + tm->tm_sec;
    153  /* Check that "seconds" will fit in a time_t. On platforms where time_t is
    154   * 32-bit, this check will fail for dates in and after 2038.
    155   *
    156   * We already know that "seconds" can't be negative because "year" >= 1970 */
    157 #if SIZEOF_TIME_T < 8
    158  if (seconds < TIME_MIN || seconds > TIME_MAX) {
    159    log_warn(LD_BUG, "Result does not fit in tor_timegm");
    160    return -1;
    161  }
    162 #endif /* SIZEOF_TIME_T < 8 */
    163  *time_out = (time_t)seconds;
    164  return 0;
    165 }
    166 
    167 /** Compute a time_t given a struct tm.  The result here should be an inverse
    168 * of the system's gmtime() function.  Return 0 on success, -1 on failure.
    169 */
    170 int
    171 tor_timegm(const struct tm *tm, time_t *time_out)
    172 {
    173 #ifdef HAVE_TIMEGM
    174  /* If the system gives us a timegm(), use it: if the system's time_t
    175   * includes leap seconds, then we can hope that its timegm() knows too.
    176   *
    177   * https://k5wiki.kerberos.org/wiki/Leap_second_handling says the in
    178   * general we can rely on any system with leap seconds also having a
    179   * timegm implementation.  Let's hope it's right!
    180   * */
    181  time_t result = timegm((struct tm *) tm);
    182  if (result == -1) {
    183    log_warn(LD_BUG, "timegm() could not convert time: %s", strerror(errno));
    184    *time_out = 0;
    185    return -1;
    186  } else {
    187    *time_out = result;
    188    return 0;
    189  }
    190 #else
    191  /* The system doesn't have timegm; we'll have to use our own. */
    192  return tor_timegm_impl(tm, time_out);
    193 #endif
    194 }
    195 
    196 /* strftime is locale-specific, so we need to replace those parts */
    197 
    198 /** A c-locale array of 3-letter names of weekdays, starting with Sun. */
    199 static const char *WEEKDAY_NAMES[] =
    200  { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };
    201 /** A c-locale array of 3-letter names of months, starting with Jan. */
    202 static const char *MONTH_NAMES[] =
    203  { "Jan", "Feb", "Mar", "Apr", "May", "Jun",
    204    "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
    205 
    206 /** Set <b>buf</b> to the RFC1123 encoding of the UTC value of <b>t</b>.
    207 * The buffer must be at least RFC1123_TIME_LEN+1 bytes long.
    208 *
    209 * (RFC1123 format is "Fri, 29 Sep 2006 15:54:20 GMT". Note the "GMT"
    210 * rather than "UTC".)
    211 */
    212 void
    213 format_rfc1123_time(char *buf, time_t t)
    214 {
    215  struct tm tm;
    216 
    217  tor_gmtime_r(&t, &tm);
    218 
    219  strftime(buf, RFC1123_TIME_LEN+1, "___, %d ___ %Y %H:%M:%S GMT", &tm);
    220  tor_assert(tm.tm_wday >= 0);
    221  tor_assert(tm.tm_wday <= 6);
    222  memcpy(buf, WEEKDAY_NAMES[tm.tm_wday], 3);
    223  tor_assert(tm.tm_mon >= 0);
    224  tor_assert(tm.tm_mon <= 11);
    225  memcpy(buf+8, MONTH_NAMES[tm.tm_mon], 3);
    226 }
    227 
    228 /** Parse the (a subset of) the RFC1123 encoding of some time (in UTC) from
    229 * <b>buf</b>, and store the result in *<b>t</b>.
    230 *
    231 * Note that we only accept the subset generated by format_rfc1123_time above,
    232 * not the full range of formats suggested by RFC 1123.
    233 *
    234 * Return 0 on success, -1 on failure.
    235 */
    236 int
    237 parse_rfc1123_time(const char *buf, time_t *t)
    238 {
    239  struct tm tm;
    240  char month[4];
    241  char weekday[4];
    242  int i, m, invalid_year;
    243  unsigned tm_mday, tm_year, tm_hour, tm_min, tm_sec;
    244  unsigned dpm;
    245 
    246  if (strlen(buf) != RFC1123_TIME_LEN)
    247    return -1;
    248  memset(&tm, 0, sizeof(tm));
    249  if (tor_sscanf(buf, "%3s, %2u %3s %u %2u:%2u:%2u GMT", weekday,
    250             &tm_mday, month, &tm_year, &tm_hour,
    251             &tm_min, &tm_sec) < 7) {
    252    char *esc = esc_for_log(buf);
    253    log_warn(LD_GENERAL, "Got invalid RFC1123 time %s", esc);
    254    tor_free(esc);
    255    return -1;
    256  }
    257 
    258  m = -1;
    259  for (i = 0; i < 12; ++i) {
    260    if (!strcmp(month, MONTH_NAMES[i])) {
    261      m = i;
    262      break;
    263    }
    264  }
    265  if (m<0) {
    266    char *esc = esc_for_log(buf);
    267    log_warn(LD_GENERAL, "Got invalid RFC1123 time %s: No such month", esc);
    268    tor_free(esc);
    269    return -1;
    270  }
    271  tm.tm_mon = m;
    272 
    273  invalid_year = (tm_year >= INT32_MAX || tm_year < 1970);
    274  tor_assert(m >= 0 && m <= 11);
    275  dpm = days_per_month[m];
    276  if (m == 1 && !invalid_year && IS_LEAPYEAR(tm_year)) {
    277    dpm = 29;
    278  }
    279 
    280  if (invalid_year || tm_mday < 1 || tm_mday > dpm ||
    281      tm_hour > 23 || tm_min > 59 || tm_sec > 60) {
    282    char *esc = esc_for_log(buf);
    283    log_warn(LD_GENERAL, "Got invalid RFC1123 time %s", esc);
    284    tor_free(esc);
    285    return -1;
    286  }
    287  tm.tm_mday = (int)tm_mday;
    288  tm.tm_year = (int)tm_year;
    289  tm.tm_hour = (int)tm_hour;
    290  tm.tm_min = (int)tm_min;
    291  tm.tm_sec = (int)tm_sec;
    292 
    293  if (tm.tm_year < 1970) {
    294    /* LCOV_EXCL_START
    295     * XXXX I think this is dead code; we already checked for
    296     *      invalid_year above. */
    297    tor_assert_nonfatal_unreached();
    298    char *esc = esc_for_log(buf);
    299    log_warn(LD_GENERAL,
    300             "Got invalid RFC1123 time %s. (Before 1970)", esc);
    301    tor_free(esc);
    302    return -1;
    303    /* LCOV_EXCL_STOP */
    304  }
    305  tm.tm_year -= 1900;
    306 
    307  return tor_timegm(&tm, t);
    308 }
    309 
    310 /** Set <b>buf</b> to the ISO8601 encoding of the local value of <b>t</b>.
    311 * The buffer must be at least ISO_TIME_LEN+1 bytes long.
    312 *
    313 * (ISO8601 format is 2006-10-29 10:57:20)
    314 */
    315 void
    316 format_local_iso_time(char *buf, time_t t)
    317 {
    318  struct tm tm;
    319  strftime(buf, ISO_TIME_LEN+1, "%Y-%m-%d %H:%M:%S", tor_localtime_r(&t, &tm));
    320 }
    321 
    322 /** Set <b>buf</b> to the ISO8601 encoding of the GMT value of <b>t</b>.
    323 * The buffer must be at least ISO_TIME_LEN+1 bytes long.
    324 */
    325 void
    326 format_iso_time(char *buf, time_t t)
    327 {
    328  struct tm tm;
    329  strftime(buf, ISO_TIME_LEN+1, "%Y-%m-%d %H:%M:%S", tor_gmtime_r(&t, &tm));
    330 }
    331 
    332 /** As format_local_iso_time, but use the yyyy-mm-ddThh:mm:ss format to avoid
    333 * embedding an internal space. */
    334 void
    335 format_local_iso_time_nospace(char *buf, time_t t)
    336 {
    337  format_local_iso_time(buf, t);
    338  buf[10] = 'T';
    339 }
    340 
    341 /** As format_iso_time, but use the yyyy-mm-ddThh:mm:ss format to avoid
    342 * embedding an internal space. */
    343 void
    344 format_iso_time_nospace(char *buf, time_t t)
    345 {
    346  format_iso_time(buf, t);
    347  buf[10] = 'T';
    348 }
    349 
    350 /** As format_iso_time_nospace, but include microseconds in decimal
    351 * fixed-point format.  Requires that buf be at least ISO_TIME_USEC_LEN+1
    352 * bytes long. */
    353 void
    354 format_iso_time_nospace_usec(char *buf, const struct timeval *tv)
    355 {
    356  tor_assert(tv);
    357  format_iso_time_nospace(buf, (time_t)tv->tv_sec);
    358  tor_snprintf(buf+ISO_TIME_LEN, 8, ".%06d", (int)tv->tv_usec);
    359 }
    360 
    361 /** Given an ISO-formatted UTC time value (after the epoch) in <b>cp</b>,
    362 * parse it and store its value in *<b>t</b>.  Return 0 on success, -1 on
    363 * failure.  Ignore extraneous stuff in <b>cp</b> after the end of the time
    364 * string, unless <b>strict</b> is set. If <b>nospace</b> is set,
    365 * expect the YYYY-MM-DDTHH:MM:SS format. */
    366 int
    367 parse_iso_time_(const char *cp, time_t *t, int strict, int nospace)
    368 {
    369  struct tm st_tm;
    370  unsigned int year=0, month=0, day=0, hour=0, minute=0, second=0;
    371  int n_fields;
    372  char extra_char, separator_char;
    373  n_fields = tor_sscanf(cp, "%u-%2u-%2u%c%2u:%2u:%2u%c",
    374                        &year, &month, &day,
    375                        &separator_char,
    376                        &hour, &minute, &second, &extra_char);
    377  if (strict ? (n_fields != 7) : (n_fields < 7)) {
    378    char *esc = esc_for_log(cp);
    379    log_warn(LD_GENERAL, "ISO time %s was unparseable", esc);
    380    tor_free(esc);
    381    return -1;
    382  }
    383  if (separator_char != (nospace ? 'T' : ' ')) {
    384    char *esc = esc_for_log(cp);
    385    log_warn(LD_GENERAL, "ISO time %s was unparseable", esc);
    386    tor_free(esc);
    387    return -1;
    388  }
    389  if (year < 1970 || month < 1 || month > 12 || day < 1 || day > 31 ||
    390          hour > 23 || minute > 59 || second > 60 || year >= INT32_MAX) {
    391    char *esc = esc_for_log(cp);
    392    log_warn(LD_GENERAL, "ISO time %s was nonsensical", esc);
    393    tor_free(esc);
    394    return -1;
    395  }
    396  st_tm.tm_year = (int)year-1900;
    397  st_tm.tm_mon = month-1;
    398  st_tm.tm_mday = day;
    399  st_tm.tm_hour = hour;
    400  st_tm.tm_min = minute;
    401  st_tm.tm_sec = second;
    402  st_tm.tm_wday = 0; /* Should be ignored. */
    403 
    404  if (st_tm.tm_year < 70) {
    405    /* LCOV_EXCL_START
    406     * XXXX I think this is dead code; we already checked for
    407     *      year < 1970 above. */
    408    tor_assert_nonfatal_unreached();
    409    char *esc = esc_for_log(cp);
    410    log_warn(LD_GENERAL, "Got invalid ISO time %s. (Before 1970)", esc);
    411    tor_free(esc);
    412    return -1;
    413    /* LCOV_EXCL_STOP */
    414  }
    415  return tor_timegm(&st_tm, t);
    416 }
    417 
    418 /** Given an ISO-formatted UTC time value (after the epoch) in <b>cp</b>,
    419 * parse it and store its value in *<b>t</b>.  Return 0 on success, -1 on
    420 * failure. Reject the string if any characters are present after the time.
    421 */
    422 int
    423 parse_iso_time(const char *cp, time_t *t)
    424 {
    425  return parse_iso_time_(cp, t, 1, 0);
    426 }
    427 
    428 /**
    429 * As parse_iso_time, but parses a time encoded by format_iso_time_nospace().
    430 */
    431 int
    432 parse_iso_time_nospace(const char *cp, time_t *t)
    433 {
    434  return parse_iso_time_(cp, t, 1, 1);
    435 }
    436 
    437 /** Given a <b>date</b> in one of the three formats allowed by HTTP (ugh),
    438 * parse it into <b>tm</b>.  Return 0 on success, negative on failure. */
    439 int
    440 parse_http_time(const char *date, struct tm *tm)
    441 {
    442  const char *cp;
    443  char month[4];
    444  char wkday[4];
    445  int i;
    446  unsigned tm_mday, tm_year, tm_hour, tm_min, tm_sec;
    447 
    448  tor_assert(tm);
    449  memset(tm, 0, sizeof(*tm));
    450 
    451  /* First, try RFC1123 or RFC850 format: skip the weekday.  */
    452  if ((cp = strchr(date, ','))) {
    453    ++cp;
    454    if (*cp != ' ')
    455      return -1;
    456    ++cp;
    457    if (tor_sscanf(cp, "%2u %3s %4u %2u:%2u:%2u GMT",
    458               &tm_mday, month, &tm_year,
    459               &tm_hour, &tm_min, &tm_sec) == 6) {
    460      /* rfc1123-date */
    461      tm_year -= 1900;
    462    } else if (tor_sscanf(cp, "%2u-%3s-%2u %2u:%2u:%2u GMT",
    463                      &tm_mday, month, &tm_year,
    464                      &tm_hour, &tm_min, &tm_sec) == 6) {
    465      /* rfc850-date */
    466    } else {
    467      return -1;
    468    }
    469  } else {
    470    /* No comma; possibly asctime() format. */
    471    if (tor_sscanf(date, "%3s %3s %2u %2u:%2u:%2u %4u",
    472               wkday, month, &tm_mday,
    473               &tm_hour, &tm_min, &tm_sec, &tm_year) == 7) {
    474      tm_year -= 1900;
    475    } else {
    476      return -1;
    477    }
    478  }
    479  tm->tm_mday = (int)tm_mday;
    480  tm->tm_year = (int)tm_year;
    481  tm->tm_hour = (int)tm_hour;
    482  tm->tm_min = (int)tm_min;
    483  tm->tm_sec = (int)tm_sec;
    484  tm->tm_wday = 0; /* Leave this unset. */
    485 
    486  month[3] = '\0';
    487  /* Okay, now decode the month. */
    488  /* set tm->tm_mon to dummy value so the check below fails. */
    489  tm->tm_mon = -1;
    490  for (i = 0; i < 12; ++i) {
    491    if (!strcasecmp(MONTH_NAMES[i], month)) {
    492      tm->tm_mon = i;
    493    }
    494  }
    495 
    496  if (tm->tm_year < 0 ||
    497      tm->tm_mon < 0  || tm->tm_mon > 11 ||
    498      tm->tm_mday < 1 || tm->tm_mday > 31 ||
    499      tm->tm_hour < 0 || tm->tm_hour > 23 ||
    500      tm->tm_min < 0  || tm->tm_min > 59 ||
    501      tm->tm_sec < 0  || tm->tm_sec > 60)
    502    return -1; /* Out of range, or bad month. */
    503 
    504  return 0;
    505 }
    506 
    507 /** Given an <b>interval</b> in seconds, try to write it to the
    508 * <b>out_len</b>-byte buffer in <b>out</b> in a human-readable form.
    509 * Returns a non-negative integer on success, -1 on failure.
    510 */
    511 int
    512 format_time_interval(char *out, size_t out_len, long interval)
    513 {
    514  /* We only report seconds if there's no hours. */
    515  long sec = 0, min = 0, hour = 0, day = 0;
    516 
    517  /* -LONG_MIN is LONG_MAX + 1, which causes signed overflow */
    518  if (interval < -LONG_MAX)
    519    interval = LONG_MAX;
    520  else if (interval < 0)
    521    interval = -interval;
    522 
    523  if (interval >= 86400) {
    524    day = interval / 86400;
    525    interval %= 86400;
    526  }
    527  if (interval >= 3600) {
    528    hour = interval / 3600;
    529    interval %= 3600;
    530  }
    531  if (interval >= 60) {
    532    min = interval / 60;
    533    interval %= 60;
    534  }
    535  sec = interval;
    536 
    537  if (day) {
    538    return tor_snprintf(out, out_len, "%ld days, %ld hours, %ld minutes",
    539                        day, hour, min);
    540  } else if (hour) {
    541    return tor_snprintf(out, out_len, "%ld hours, %ld minutes", hour, min);
    542  } else if (min) {
    543    return tor_snprintf(out, out_len, "%ld minutes, %ld seconds", min, sec);
    544  } else {
    545    return tor_snprintf(out, out_len, "%ld seconds", sec);
    546  }
    547 }