Tor  0.4.8.0-alpha-dev
time_fmt.c
Go to the documentation of this file.
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"
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. */
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. */
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 }
char * esc_for_log(const char *s)
Definition: escape.c:30
Header for escape.c.
Headers for log.c.
#define LD_BUG
Definition: log.h:86
#define LD_GENERAL
Definition: log.h:62
Headers for util_malloc.c.
#define tor_free(p)
Definition: malloc.h:56
int tor_snprintf(char *str, size_t size, const char *format,...)
Definition: printf.c:27
Header for printf.c.
int tor_sscanf(const char *buf, const char *pattern,...)
Definition: scanf.c:309
Header for scanf.c.
#define STATIC
Definition: testsupport.h:32
Definitions for timing-related constants.
static const char * MONTH_NAMES[]
Definition: time_fmt.c:202
void format_iso_time_nospace(char *buf, time_t t)
Definition: time_fmt.c:344
void format_rfc1123_time(char *buf, time_t t)
Definition: time_fmt.c:213
int parse_iso_time(const char *cp, time_t *t)
Definition: time_fmt.c:423
int parse_http_time(const char *date, struct tm *tm)
Definition: time_fmt.c:440
void format_iso_time_nospace_usec(char *buf, const struct timeval *tv)
Definition: time_fmt.c:354
void format_iso_time(char *buf, time_t t)
Definition: time_fmt.c:326
int parse_iso_time_(const char *cp, time_t *t, int strict, int nospace)
Definition: time_fmt.c:367
static const char * WEEKDAY_NAMES[]
Definition: time_fmt.c:199
void format_local_iso_time_nospace(char *buf, time_t t)
Definition: time_fmt.c:335
int parse_rfc1123_time(const char *buf, time_t *t)
Definition: time_fmt.c:237
ATTR_UNUSED STATIC int tor_timegm_impl(const struct tm *tm, time_t *time_out)
Definition: time_fmt.c:98
void format_local_iso_time(char *buf, time_t t)
Definition: time_fmt.c:316
static const int days_per_month[]
Definition: time_fmt.c:91
int tor_timegm(const struct tm *tm, time_t *time_out)
Definition: time_fmt.c:171
struct tm * tor_gmtime_r(const time_t *timep, struct tm *result)
Definition: time_fmt.c:67
static int n_leapdays(int year1, int year2)
Definition: time_fmt.c:82
int format_time_interval(char *out, size_t out_len, long interval)
Definition: time_fmt.c:512
int parse_iso_time_nospace(const char *cp, time_t *t)
Definition: time_fmt.c:432
#define IS_LEAPYEAR(y)
Definition: time_fmt.c:79
struct tm * tor_localtime_r(const time_t *timep, struct tm *result)
Definition: time_fmt.c:48
Header for time_fmt.c.
struct tm * tor_localtime_r_msg(const time_t *timep, struct tm *result, char **err_out)
Definition: time_to_tm.c:133
struct tm * tor_gmtime_r_msg(const time_t *timep, struct tm *result, char **err_out)
Definition: time_to_tm.c:176
Header for time_to_tm.c.
Macros to manage assertions, fatal and non-fatal.
#define tor_assert_nonfatal_unreached()
Definition: util_bug.h:176
#define tor_assert(expr)
Definition: util_bug.h:102