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 }