tor  0.4.0.1-alpha
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-2019, The Tor Project, Inc. */
5 /* See LICENSE for licensing information */
6 
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 
43 struct tm *
44 tor_localtime_r(const time_t *timep, struct tm *result)
45 {
46  char *err = NULL;
47  struct tm *r = tor_localtime_r_msg(timep, result, &err);
48  if (err) {
49  log_warn(LD_BUG, "%s", err);
50  tor_free(err);
51  }
52  return r;
53 }
54 
60 struct tm *
61 tor_gmtime_r(const time_t *timep, struct tm *result)
62 {
63  char *err = NULL;
64  struct tm *r = tor_gmtime_r_msg(timep, result, &err);
65  if (err) {
66  log_warn(LD_BUG, "%s", err);
67  tor_free(err);
68  }
69  return r;
70 }
71 
73 #define IS_LEAPYEAR(y) (!(y % 4) && ((y % 100) || !(y % 400)))
74 
75 static int
76 n_leapdays(int year1, int year2)
77 {
78  --year1;
79  --year2;
80  return (year2/4 - year1/4) - (year2/100 - year1/100)
81  + (year2/400 - year1/400);
82 }
85 static const int days_per_month[] =
86  { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
87 
91 int
92 tor_timegm(const struct tm *tm, time_t *time_out)
93 {
94  /* This is a pretty ironclad timegm implementation, snarfed from Python2.2.
95  * It's way more brute-force than fiddling with tzset().
96  *
97  * We use int64_t rather than time_t to avoid overflow on multiplication on
98  * platforms with 32-bit time_t. Since year is clipped to INT32_MAX, and
99  * since 365 * 24 * 60 * 60 is approximately 31 million, it's not possible
100  * for INT32_MAX years to overflow int64_t when converted to seconds. */
101  int64_t year, days, hours, minutes, seconds;
102  int i, invalid_year, dpm;
103 
104  /* Initialize time_out to 0 for now, to avoid bad usage in case this function
105  fails and the caller ignores the return value. */
106  tor_assert(time_out);
107  *time_out = 0;
108 
109  /* avoid int overflow on addition */
110  if (tm->tm_year < INT32_MAX-1900) {
111  year = tm->tm_year + 1900;
112  } else {
113  /* clamp year */
114  year = INT32_MAX;
115  }
116  invalid_year = (year < 1970 || tm->tm_year >= INT32_MAX-1900);
117 
118  if (tm->tm_mon >= 0 && tm->tm_mon <= 11) {
119  dpm = days_per_month[tm->tm_mon];
120  if (tm->tm_mon == 1 && !invalid_year && IS_LEAPYEAR(tm->tm_year)) {
121  dpm = 29;
122  }
123  } else {
124  /* invalid month - default to 0 days per month */
125  dpm = 0;
126  }
127 
128  if (invalid_year ||
129  tm->tm_mon < 0 || tm->tm_mon > 11 ||
130  tm->tm_mday < 1 || tm->tm_mday > dpm ||
131  tm->tm_hour < 0 || tm->tm_hour > 23 ||
132  tm->tm_min < 0 || tm->tm_min > 59 ||
133  tm->tm_sec < 0 || tm->tm_sec > 60) {
134  log_warn(LD_BUG, "Out-of-range argument to tor_timegm");
135  return -1;
136  }
137  days = 365 * (year-1970) + n_leapdays(1970,(int)year);
138  for (i = 0; i < tm->tm_mon; ++i)
139  days += days_per_month[i];
140  if (tm->tm_mon > 1 && IS_LEAPYEAR(year))
141  ++days;
142  days += tm->tm_mday - 1;
143  hours = days*24 + tm->tm_hour;
144 
145  minutes = hours*60 + tm->tm_min;
146  seconds = minutes*60 + tm->tm_sec;
147  /* Check that "seconds" will fit in a time_t. On platforms where time_t is
148  * 32-bit, this check will fail for dates in and after 2038.
149  *
150  * We already know that "seconds" can't be negative because "year" >= 1970 */
151 #if SIZEOF_TIME_T < 8
152  if (seconds < TIME_MIN || seconds > TIME_MAX) {
153  log_warn(LD_BUG, "Result does not fit in tor_timegm");
154  return -1;
155  }
156 #endif /* SIZEOF_TIME_T < 8 */
157  *time_out = (time_t)seconds;
158  return 0;
159 }
160 
161 /* strftime is locale-specific, so we need to replace those parts */
162 
164 static const char *WEEKDAY_NAMES[] =
165  { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };
167 static const char *MONTH_NAMES[] =
168  { "Jan", "Feb", "Mar", "Apr", "May", "Jun",
169  "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
170 
177 void
178 format_rfc1123_time(char *buf, time_t t)
179 {
180  struct tm tm;
181 
182  tor_gmtime_r(&t, &tm);
183 
184  strftime(buf, RFC1123_TIME_LEN+1, "___, %d ___ %Y %H:%M:%S GMT", &tm);
185  tor_assert(tm.tm_wday >= 0);
186  tor_assert(tm.tm_wday <= 6);
187  memcpy(buf, WEEKDAY_NAMES[tm.tm_wday], 3);
188  tor_assert(tm.tm_mon >= 0);
189  tor_assert(tm.tm_mon <= 11);
190  memcpy(buf+8, MONTH_NAMES[tm.tm_mon], 3);
191 }
192 
201 int
202 parse_rfc1123_time(const char *buf, time_t *t)
203 {
204  struct tm tm;
205  char month[4];
206  char weekday[4];
207  int i, m, invalid_year;
208  unsigned tm_mday, tm_year, tm_hour, tm_min, tm_sec;
209  unsigned dpm;
210 
211  if (strlen(buf) != RFC1123_TIME_LEN)
212  return -1;
213  memset(&tm, 0, sizeof(tm));
214  if (tor_sscanf(buf, "%3s, %2u %3s %u %2u:%2u:%2u GMT", weekday,
215  &tm_mday, month, &tm_year, &tm_hour,
216  &tm_min, &tm_sec) < 7) {
217  char *esc = esc_for_log(buf);
218  log_warn(LD_GENERAL, "Got invalid RFC1123 time %s", esc);
219  tor_free(esc);
220  return -1;
221  }
222 
223  m = -1;
224  for (i = 0; i < 12; ++i) {
225  if (!strcmp(month, MONTH_NAMES[i])) {
226  m = i;
227  break;
228  }
229  }
230  if (m<0) {
231  char *esc = esc_for_log(buf);
232  log_warn(LD_GENERAL, "Got invalid RFC1123 time %s: No such month", esc);
233  tor_free(esc);
234  return -1;
235  }
236  tm.tm_mon = m;
237 
238  invalid_year = (tm_year >= INT32_MAX || tm_year < 1970);
239  tor_assert(m >= 0 && m <= 11);
240  dpm = days_per_month[m];
241  if (m == 1 && !invalid_year && IS_LEAPYEAR(tm_year)) {
242  dpm = 29;
243  }
244 
245  if (invalid_year || tm_mday < 1 || tm_mday > dpm ||
246  tm_hour > 23 || tm_min > 59 || tm_sec > 60) {
247  char *esc = esc_for_log(buf);
248  log_warn(LD_GENERAL, "Got invalid RFC1123 time %s", esc);
249  tor_free(esc);
250  return -1;
251  }
252  tm.tm_mday = (int)tm_mday;
253  tm.tm_year = (int)tm_year;
254  tm.tm_hour = (int)tm_hour;
255  tm.tm_min = (int)tm_min;
256  tm.tm_sec = (int)tm_sec;
257 
258  if (tm.tm_year < 1970) {
259  /* LCOV_EXCL_START
260  * XXXX I think this is dead code; we already checked for
261  * invalid_year above. */
262  tor_assert_nonfatal_unreached();
263  char *esc = esc_for_log(buf);
264  log_warn(LD_GENERAL,
265  "Got invalid RFC1123 time %s. (Before 1970)", esc);
266  tor_free(esc);
267  return -1;
268  /* LCOV_EXCL_STOP */
269  }
270  tm.tm_year -= 1900;
271 
272  return tor_timegm(&tm, t);
273 }
274 
280 void
281 format_local_iso_time(char *buf, time_t t)
282 {
283  struct tm tm;
284  strftime(buf, ISO_TIME_LEN+1, "%Y-%m-%d %H:%M:%S", tor_localtime_r(&t, &tm));
285 }
286 
290 void
291 format_iso_time(char *buf, time_t t)
292 {
293  struct tm tm;
294  strftime(buf, ISO_TIME_LEN+1, "%Y-%m-%d %H:%M:%S", tor_gmtime_r(&t, &tm));
295 }
296 
299 void
300 format_local_iso_time_nospace(char *buf, time_t t)
301 {
302  format_local_iso_time(buf, t);
303  buf[10] = 'T';
304 }
305 
308 void
309 format_iso_time_nospace(char *buf, time_t t)
310 {
311  format_iso_time(buf, t);
312  buf[10] = 'T';
313 }
314 
318 void
319 format_iso_time_nospace_usec(char *buf, const struct timeval *tv)
320 {
321  tor_assert(tv);
322  format_iso_time_nospace(buf, (time_t)tv->tv_sec);
323  tor_snprintf(buf+ISO_TIME_LEN, 8, ".%06d", (int)tv->tv_usec);
324 }
325 
331 int
332 parse_iso_time_(const char *cp, time_t *t, int strict, int nospace)
333 {
334  struct tm st_tm;
335  unsigned int year=0, month=0, day=0, hour=0, minute=0, second=0;
336  int n_fields;
337  char extra_char, separator_char;
338  n_fields = tor_sscanf(cp, "%u-%2u-%2u%c%2u:%2u:%2u%c",
339  &year, &month, &day,
340  &separator_char,
341  &hour, &minute, &second, &extra_char);
342  if (strict ? (n_fields != 7) : (n_fields < 7)) {
343  char *esc = esc_for_log(cp);
344  log_warn(LD_GENERAL, "ISO time %s was unparseable", esc);
345  tor_free(esc);
346  return -1;
347  }
348  if (separator_char != (nospace ? 'T' : ' ')) {
349  char *esc = esc_for_log(cp);
350  log_warn(LD_GENERAL, "ISO time %s was unparseable", esc);
351  tor_free(esc);
352  return -1;
353  }
354  if (year < 1970 || month < 1 || month > 12 || day < 1 || day > 31 ||
355  hour > 23 || minute > 59 || second > 60 || year >= INT32_MAX) {
356  char *esc = esc_for_log(cp);
357  log_warn(LD_GENERAL, "ISO time %s was nonsensical", esc);
358  tor_free(esc);
359  return -1;
360  }
361  st_tm.tm_year = (int)year-1900;
362  st_tm.tm_mon = month-1;
363  st_tm.tm_mday = day;
364  st_tm.tm_hour = hour;
365  st_tm.tm_min = minute;
366  st_tm.tm_sec = second;
367  st_tm.tm_wday = 0; /* Should be ignored. */
368 
369  if (st_tm.tm_year < 70) {
370  /* LCOV_EXCL_START
371  * XXXX I think this is dead code; we already checked for
372  * year < 1970 above. */
373  tor_assert_nonfatal_unreached();
374  char *esc = esc_for_log(cp);
375  log_warn(LD_GENERAL, "Got invalid ISO time %s. (Before 1970)", esc);
376  tor_free(esc);
377  return -1;
378  /* LCOV_EXCL_STOP */
379  }
380  return tor_timegm(&st_tm, t);
381 }
382 
387 int
388 parse_iso_time(const char *cp, time_t *t)
389 {
390  return parse_iso_time_(cp, t, 1, 0);
391 }
392 
396 int
397 parse_iso_time_nospace(const char *cp, time_t *t)
398 {
399  return parse_iso_time_(cp, t, 1, 1);
400 }
401 
404 int
405 parse_http_time(const char *date, struct tm *tm)
406 {
407  const char *cp;
408  char month[4];
409  char wkday[4];
410  int i;
411  unsigned tm_mday, tm_year, tm_hour, tm_min, tm_sec;
412 
413  tor_assert(tm);
414  memset(tm, 0, sizeof(*tm));
415 
416  /* First, try RFC1123 or RFC850 format: skip the weekday. */
417  if ((cp = strchr(date, ','))) {
418  ++cp;
419  if (*cp != ' ')
420  return -1;
421  ++cp;
422  if (tor_sscanf(cp, "%2u %3s %4u %2u:%2u:%2u GMT",
423  &tm_mday, month, &tm_year,
424  &tm_hour, &tm_min, &tm_sec) == 6) {
425  /* rfc1123-date */
426  tm_year -= 1900;
427  } else if (tor_sscanf(cp, "%2u-%3s-%2u %2u:%2u:%2u GMT",
428  &tm_mday, month, &tm_year,
429  &tm_hour, &tm_min, &tm_sec) == 6) {
430  /* rfc850-date */
431  } else {
432  return -1;
433  }
434  } else {
435  /* No comma; possibly asctime() format. */
436  if (tor_sscanf(date, "%3s %3s %2u %2u:%2u:%2u %4u",
437  wkday, month, &tm_mday,
438  &tm_hour, &tm_min, &tm_sec, &tm_year) == 7) {
439  tm_year -= 1900;
440  } else {
441  return -1;
442  }
443  }
444  tm->tm_mday = (int)tm_mday;
445  tm->tm_year = (int)tm_year;
446  tm->tm_hour = (int)tm_hour;
447  tm->tm_min = (int)tm_min;
448  tm->tm_sec = (int)tm_sec;
449  tm->tm_wday = 0; /* Leave this unset. */
450 
451  month[3] = '\0';
452  /* Okay, now decode the month. */
453  /* set tm->tm_mon to dummy value so the check below fails. */
454  tm->tm_mon = -1;
455  for (i = 0; i < 12; ++i) {
456  if (!strcasecmp(MONTH_NAMES[i], month)) {
457  tm->tm_mon = i;
458  }
459  }
460 
461  if (tm->tm_year < 0 ||
462  tm->tm_mon < 0 || tm->tm_mon > 11 ||
463  tm->tm_mday < 1 || tm->tm_mday > 31 ||
464  tm->tm_hour < 0 || tm->tm_hour > 23 ||
465  tm->tm_min < 0 || tm->tm_min > 59 ||
466  tm->tm_sec < 0 || tm->tm_sec > 60)
467  return -1; /* Out of range, or bad month. */
468 
469  return 0;
470 }
471 
476 int
477 format_time_interval(char *out, size_t out_len, long interval)
478 {
479  /* We only report seconds if there's no hours. */
480  long sec = 0, min = 0, hour = 0, day = 0;
481 
482  /* -LONG_MIN is LONG_MAX + 1, which causes signed overflow */
483  if (interval < -LONG_MAX)
484  interval = LONG_MAX;
485  else if (interval < 0)
486  interval = -interval;
487 
488  if (interval >= 86400) {
489  day = interval / 86400;
490  interval %= 86400;
491  }
492  if (interval >= 3600) {
493  hour = interval / 3600;
494  interval %= 3600;
495  }
496  if (interval >= 60) {
497  min = interval / 60;
498  interval %= 60;
499  }
500  sec = interval;
501 
502  if (day) {
503  return tor_snprintf(out, out_len, "%ld days, %ld hours, %ld minutes",
504  day, hour, min);
505  } else if (hour) {
506  return tor_snprintf(out, out_len, "%ld hours, %ld minutes", hour, min);
507  } else if (min) {
508  return tor_snprintf(out, out_len, "%ld minutes, %ld seconds", min, sec);
509  } else {
510  return tor_snprintf(out, out_len, "%ld seconds", sec);
511  }
512 }
int tor_sscanf(const char *buf, const char *pattern,...)
Definition: scanf.c:309
void format_local_iso_time_nospace(char *buf, time_t t)
Definition: time_fmt.c:300
Header for printf.c.
int tor_timegm(const struct tm *tm, time_t *time_out)
Definition: time_fmt.c:92
void format_local_iso_time(char *buf, time_t t)
Definition: time_fmt.c:281
Definitions for timing-related constants.
#define LD_GENERAL
Definition: log.h:58
int parse_http_time(const char *date, struct tm *tm)
Definition: time_fmt.c:405
struct tm * tor_localtime_r_msg(const time_t *timep, struct tm *result, char **err_out)
Definition: time_to_tm.c:133
void format_iso_time_nospace_usec(char *buf, const struct timeval *tv)
Definition: time_fmt.c:319
int format_time_interval(char *out, size_t out_len, long interval)
Definition: time_fmt.c:477
Header for time_fmt.c.
#define tor_free(p)
Definition: malloc.h:52
int parse_iso_time_nospace(const char *cp, time_t *t)
Definition: time_fmt.c:397
Header for time_to_tm.c.
Headers for util_malloc.c.
struct tm * tor_localtime_r(const time_t *timep, struct tm *result)
Definition: time_fmt.c:44
int parse_iso_time_(const char *cp, time_t *t, int strict, int nospace)
Definition: time_fmt.c:332
static int n_leapdays(int year1, int year2)
Definition: time_fmt.c:76
tor_assert(buffer)
void format_iso_time_nospace(char *buf, time_t t)
Definition: time_fmt.c:309
Header for scanf.c.
void format_rfc1123_time(char *buf, time_t t)
Definition: time_fmt.c:178
static const int days_per_month[]
Definition: time_fmt.c:85
int tor_snprintf(char *str, size_t size, const char *format,...)
Definition: printf.c:27
void format_iso_time(char *buf, time_t t)
Definition: time_fmt.c:291
struct tm * tor_gmtime_r(const time_t *timep, struct tm *result)
Definition: time_fmt.c:61
#define IS_LEAPYEAR(y)
Definition: time_fmt.c:73
Header for escape.c.
int parse_rfc1123_time(const char *buf, time_t *t)
Definition: time_fmt.c:202
struct tm * tor_gmtime_r_msg(const time_t *timep, struct tm *result, char **err_out)
Definition: time_to_tm.c:176
static const char * WEEKDAY_NAMES[]
Definition: time_fmt.c:164
char * esc_for_log(const char *s)
Definition: escape.c:30
int parse_iso_time(const char *cp, time_t *t)
Definition: time_fmt.c:388
Headers for log.c.
Macros to manage assertions, fatal and non-fatal.
#define LD_BUG
Definition: log.h:82
static const char * MONTH_NAMES[]
Definition: time_fmt.c:167