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