Line data Source code
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"
24 : #include "lib/wallclock/time_to_tm.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 236 : tor_localtime_r(const time_t *timep, struct tm *result)
47 : {
48 236 : char *err = NULL;
49 236 : struct tm *r = tor_localtime_r_msg(timep, result, &err);
50 236 : if (err) {
51 2 : log_warn(LD_BUG, "%s", err);
52 2 : tor_free(err);
53 : }
54 236 : 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 1739 : tor_gmtime_r(const time_t *timep, struct tm *result)
66 : {
67 1739 : char *err = NULL;
68 1739 : struct tm *r = tor_gmtime_r_msg(timep, result, &err);
69 1739 : if (err) {
70 2 : log_warn(LD_BUG, "%s", err);
71 2 : tor_free(err);
72 : }
73 1739 : 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 9722 : n_leapdays(int year1, int year2)
81 : {
82 9722 : --year1;
83 9722 : --year2;
84 9722 : return (year2/4 - year1/4) - (year2/100 - year1/100)
85 9722 : + (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 9744 : 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 9744 : int64_t year, days, hours, minutes, seconds;
106 9744 : 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 9744 : tor_assert(time_out);
111 9744 : *time_out = 0;
112 :
113 : /* avoid int overflow on addition */
114 9744 : if (tm->tm_year < INT32_MAX-1900) {
115 9742 : year = tm->tm_year + 1900;
116 : } else {
117 : /* clamp year */
118 : year = INT32_MAX;
119 : }
120 9744 : invalid_year = (year < 1970 || tm->tm_year >= INT32_MAX-1900);
121 :
122 9744 : if (tm->tm_mon >= 0 && tm->tm_mon <= 11) {
123 9742 : dpm = days_per_month[tm->tm_mon];
124 9742 : if (tm->tm_mon == 1 && !invalid_year && IS_LEAPYEAR(tm->tm_year)) {
125 1468 : dpm = 29;
126 : }
127 : } else {
128 : /* invalid month - default to 0 days per month */
129 : dpm = 0;
130 : }
131 :
132 9744 : if (invalid_year ||
133 9736 : tm->tm_mon < 0 || tm->tm_mon > 11 ||
134 9735 : tm->tm_mday < 1 || tm->tm_mday > dpm ||
135 9728 : tm->tm_hour < 0 || tm->tm_hour > 23 ||
136 9726 : tm->tm_min < 0 || tm->tm_min > 59 ||
137 9724 : tm->tm_sec < 0 || tm->tm_sec > 60) {
138 22 : log_warn(LD_BUG, "Out-of-range argument to tor_timegm");
139 22 : return -1;
140 : }
141 9722 : days = 365 * (year-1970) + n_leapdays(1970,(int)year);
142 85856 : for (i = 0; i < tm->tm_mon; ++i)
143 76134 : days += days_per_month[i];
144 9722 : if (tm->tm_mon > 1 && IS_LEAPYEAR(year))
145 4496 : ++days;
146 9722 : days += tm->tm_mday - 1;
147 9722 : hours = days*24 + tm->tm_hour;
148 :
149 9722 : minutes = hours*60 + tm->tm_min;
150 9722 : 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 9722 : *time_out = (time_t)seconds;
162 9722 : 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 59 : format_rfc1123_time(char *buf, time_t t)
183 : {
184 59 : struct tm tm;
185 :
186 59 : tor_gmtime_r(&t, &tm);
187 :
188 59 : strftime(buf, RFC1123_TIME_LEN+1, "___, %d ___ %Y %H:%M:%S GMT", &tm);
189 59 : tor_assert(tm.tm_wday >= 0);
190 59 : tor_assert(tm.tm_wday <= 6);
191 59 : memcpy(buf, WEEKDAY_NAMES[tm.tm_wday], 3);
192 59 : tor_assert(tm.tm_mon >= 0);
193 59 : tor_assert(tm.tm_mon <= 11);
194 59 : memcpy(buf+8, MONTH_NAMES[tm.tm_mon], 3);
195 59 : }
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 176 : parse_rfc1123_time(const char *buf, time_t *t)
207 : {
208 176 : struct tm tm;
209 176 : char month[4];
210 176 : char weekday[4];
211 176 : int i, m, invalid_year;
212 176 : unsigned tm_mday, tm_year, tm_hour, tm_min, tm_sec;
213 176 : unsigned dpm;
214 :
215 176 : if (strlen(buf) != RFC1123_TIME_LEN)
216 : return -1;
217 175 : memset(&tm, 0, sizeof(tm));
218 175 : 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 1 : char *esc = esc_for_log(buf);
222 1 : log_warn(LD_GENERAL, "Got invalid RFC1123 time %s", esc);
223 1 : tor_free(esc);
224 1 : return -1;
225 : }
226 :
227 1265 : m = -1;
228 1265 : for (i = 0; i < 12; ++i) {
229 1264 : if (!strcmp(month, MONTH_NAMES[i])) {
230 : m = i;
231 : break;
232 : }
233 : }
234 174 : if (m<0) {
235 1 : char *esc = esc_for_log(buf);
236 1 : log_warn(LD_GENERAL, "Got invalid RFC1123 time %s: No such month", esc);
237 1 : tor_free(esc);
238 1 : return -1;
239 : }
240 173 : tm.tm_mon = m;
241 :
242 173 : invalid_year = (tm_year >= INT32_MAX || tm_year < 1970);
243 173 : tor_assert(m >= 0 && m <= 11);
244 173 : dpm = days_per_month[m];
245 173 : if (m == 1 && !invalid_year && IS_LEAPYEAR(tm_year)) {
246 1 : dpm = 29;
247 : }
248 :
249 173 : if (invalid_year || tm_mday < 1 || tm_mday > dpm ||
250 169 : tm_hour > 23 || tm_min > 59 || tm_sec > 60) {
251 8 : char *esc = esc_for_log(buf);
252 8 : log_warn(LD_GENERAL, "Got invalid RFC1123 time %s", esc);
253 8 : tor_free(esc);
254 8 : return -1;
255 : }
256 165 : tm.tm_mday = (int)tm_mday;
257 165 : tm.tm_year = (int)tm_year;
258 165 : tm.tm_hour = (int)tm_hour;
259 165 : tm.tm_min = (int)tm_min;
260 165 : tm.tm_sec = (int)tm_sec;
261 :
262 165 : 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. */
266 : tor_assert_nonfatal_unreached();
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 165 : tm.tm_year -= 1900;
275 :
276 165 : 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 221 : format_local_iso_time(char *buf, time_t t)
286 : {
287 221 : struct tm tm;
288 221 : strftime(buf, ISO_TIME_LEN+1, "%Y-%m-%d %H:%M:%S", tor_localtime_r(&t, &tm));
289 221 : }
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 1380 : format_iso_time(char *buf, time_t t)
296 : {
297 1380 : struct tm tm;
298 1380 : strftime(buf, ISO_TIME_LEN+1, "%Y-%m-%d %H:%M:%S", tor_gmtime_r(&t, &tm));
299 1380 : }
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 2 : format_local_iso_time_nospace(char *buf, time_t t)
305 : {
306 2 : format_local_iso_time(buf, t);
307 2 : buf[10] = 'T';
308 2 : }
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 134 : format_iso_time_nospace(char *buf, time_t t)
314 : {
315 134 : format_iso_time(buf, t);
316 134 : buf[10] = 'T';
317 134 : }
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 1 : format_iso_time_nospace_usec(char *buf, const struct timeval *tv)
324 : {
325 1 : tor_assert(tv);
326 1 : format_iso_time_nospace(buf, (time_t)tv->tv_sec);
327 1 : tor_snprintf(buf+ISO_TIME_LEN, 8, ".%06d", (int)tv->tv_usec);
328 1 : }
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 9349 : parse_iso_time_(const char *cp, time_t *t, int strict, int nospace)
337 : {
338 9349 : struct tm st_tm;
339 9349 : unsigned int year=0, month=0, day=0, hour=0, minute=0, second=0;
340 9349 : int n_fields;
341 9349 : char extra_char, separator_char;
342 9349 : 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 9349 : if (strict ? (n_fields != 7) : (n_fields < 7)) {
347 67 : char *esc = esc_for_log(cp);
348 67 : log_warn(LD_GENERAL, "ISO time %s was unparseable", esc);
349 67 : tor_free(esc);
350 67 : return -1;
351 : }
352 18311 : if (separator_char != (nospace ? 'T' : ' ')) {
353 6 : char *esc = esc_for_log(cp);
354 6 : log_warn(LD_GENERAL, "ISO time %s was unparseable", esc);
355 6 : tor_free(esc);
356 6 : return -1;
357 : }
358 9276 : if (year < 1970 || month < 1 || month > 12 || day < 1 || day > 31 ||
359 9258 : hour > 23 || minute > 59 || second > 60 || year >= INT32_MAX) {
360 30 : char *esc = esc_for_log(cp);
361 30 : log_warn(LD_GENERAL, "ISO time %s was nonsensical", esc);
362 30 : tor_free(esc);
363 30 : return -1;
364 : }
365 9246 : st_tm.tm_year = (int)year-1900;
366 9246 : st_tm.tm_mon = month-1;
367 9246 : st_tm.tm_mday = day;
368 9246 : st_tm.tm_hour = hour;
369 9246 : st_tm.tm_min = minute;
370 9246 : st_tm.tm_sec = second;
371 9246 : st_tm.tm_wday = 0; /* Should be ignored. */
372 :
373 9246 : 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. */
377 : tor_assert_nonfatal_unreached();
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 9246 : 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 9092 : parse_iso_time(const char *cp, time_t *t)
393 : {
394 9092 : 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 256 : parse_iso_time_nospace(const char *cp, time_t *t)
402 : {
403 256 : 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 28 : parse_http_time(const char *date, struct tm *tm)
410 : {
411 28 : const char *cp;
412 28 : char month[4];
413 28 : char wkday[4];
414 28 : int i;
415 28 : unsigned tm_mday, tm_year, tm_hour, tm_min, tm_sec;
416 :
417 28 : tor_assert(tm);
418 28 : memset(tm, 0, sizeof(*tm));
419 :
420 : /* First, try RFC1123 or RFC850 format: skip the weekday. */
421 28 : if ((cp = strchr(date, ','))) {
422 16 : ++cp;
423 16 : if (*cp != ' ')
424 : return -1;
425 15 : ++cp;
426 15 : 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 9 : tm_year -= 1900;
431 6 : } 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 12 : 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 3 : tm_year -= 1900;
444 : } else {
445 : return -1;
446 : }
447 : }
448 17 : tm->tm_mday = (int)tm_mday;
449 17 : tm->tm_year = (int)tm_year;
450 17 : tm->tm_hour = (int)tm_hour;
451 17 : tm->tm_min = (int)tm_min;
452 17 : tm->tm_sec = (int)tm_sec;
453 17 : tm->tm_wday = 0; /* Leave this unset. */
454 :
455 17 : month[3] = '\0';
456 : /* Okay, now decode the month. */
457 : /* set tm->tm_mon to dummy value so the check below fails. */
458 17 : tm->tm_mon = -1;
459 221 : for (i = 0; i < 12; ++i) {
460 204 : if (!strcasecmp(MONTH_NAMES[i], month)) {
461 16 : tm->tm_mon = i;
462 : }
463 : }
464 :
465 17 : if (tm->tm_year < 0 ||
466 16 : tm->tm_mon < 0 || tm->tm_mon > 11 ||
467 15 : tm->tm_mday < 1 || tm->tm_mday > 31 ||
468 13 : tm->tm_hour < 0 || tm->tm_hour > 23 ||
469 13 : tm->tm_min < 0 || tm->tm_min > 59 ||
470 13 : tm->tm_sec < 0 || tm->tm_sec > 60)
471 4 : 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 36 : format_time_interval(char *out, size_t out_len, long interval)
482 : {
483 : /* We only report seconds if there's no hours. */
484 36 : long sec = 0, min = 0, hour = 0, day = 0;
485 :
486 : /* -LONG_MIN is LONG_MAX + 1, which causes signed overflow */
487 36 : if (interval < -LONG_MAX)
488 : interval = LONG_MAX;
489 35 : else if (interval < 0)
490 : interval = -interval;
491 :
492 35 : if (interval >= 86400) {
493 11 : day = interval / 86400;
494 11 : interval %= 86400;
495 : }
496 36 : if (interval >= 3600) {
497 15 : hour = interval / 3600;
498 15 : interval %= 3600;
499 : }
500 36 : if (interval >= 60) {
501 24 : min = interval / 60;
502 24 : interval %= 60;
503 : }
504 36 : sec = interval;
505 :
506 36 : if (day) {
507 11 : return tor_snprintf(out, out_len, "%ld days, %ld hours, %ld minutes",
508 : day, hour, min);
509 25 : } else if (hour) {
510 7 : return tor_snprintf(out, out_len, "%ld hours, %ld minutes", hour, min);
511 18 : } else if (min) {
512 12 : return tor_snprintf(out, out_len, "%ld minutes, %ld seconds", min, sec);
513 : } else {
514 6 : return tor_snprintf(out, out_len, "%ld seconds", sec);
515 : }
516 : }
|