1 /* $Id$ */
2 /*
3 * Copyright (c) 2016, 2017, 2020 Kristaps Dzonsons <kristaps@bsd.lv>
4 *
5 * Permission to use, copy, modify, and distribute this software for any
6 * purpose with or without fee is hereby granted, provided that the above
7 * copyright notice and this permission notice appear in all copies.
8 *
9 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16 */
17 #include "config.h"
18
19 #include <assert.h> /* debug */
20 #include <inttypes.h>
21 #include <limits.h>
22 #include <stdarg.h>
23 #include <stdint.h>
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <string.h>
27 #include <time.h>
28
29 #include "kcgi.h"
30 #include "extern.h"
31
32 /*
33 * We'll never have khttp_epoch2str or khttp_epoch2ustr larger than
34 * this, even with 64-bit time.
35 */
36 #define MAX_TIME_STRING 64
37
38 struct tm64 {
39 int64_t tm_sec; /* seconds after the minute [0-60] */
40 int64_t tm_min; /* minutes after the hour [0-59] */
41 int64_t tm_hour; /* hours since midnight [0-23] */
42 int64_t tm_mday; /* day of the month [1-31] */
43 int64_t tm_mon; /* months since January [0-11] */
44 int64_t tm_year; /* years since 1900 */
45 int64_t tm_wday; /* days since Sunday [0-6] */
46 int64_t tm_yday; /* days since January 1 [0-365] */
47 };
48
49 /*
50 * The following code is modified from newlib, the relevant parts being
51 * licensed as follows:
52 *
53 * Copyright (c) 1994-2009 Red Hat, Inc. All rights reserved.
54 *
55 * This copyrighted material is made available to anyone wishing to use,
56 * modify, copy, or redistribute it subject to the terms and conditions
57 * of the BSD License. This program is distributed in the hope that
58 * it will be useful, but WITHOUT ANY WARRANTY expressed or implied,
59 * including the implied warranties of MERCHANTABILITY or FITNESS FOR
60 * A PARTICULAR PURPOSE. A copy of this license is available at
61 * http://www.opensource.org/licenses. Any Red Hat trademarks that are
62 * incorporated in the source code or documentation are not subject to
63 * the BSD License and may only be used or replicated with the express
64 * permission of Red Hat, Inc.
65 */
66 #define _SEC_IN_MINUTE 60L
67 #define _SEC_IN_HOUR 3600L
68 #define _SEC_IN_DAY 86400L
69
70 static const int DAYS_IN_MONTH[12] =
71 {31, 28, 31, 30, 31, 30,
72 31, 31, 30, 31, 30, 31};
73
74 #define _DAYS_IN_MONTH(x) \
75 ((x == 1) ? days_in_feb : DAYS_IN_MONTH[x])
76
77 static const int _DAYS_BEFORE_MONTH[12] =
78 {0, 31, 59, 90, 120, 151,
79 181, 212, 243, 273, 304, 334};
80
81 #define _ISLEAP(y) \
82 (((y) % 4) == 0 && \
83 (((y) % 100) != 0 || (((y)+1900) % 400) == 0))
84 #define _DAYS_IN_YEAR(year) \
85 (_ISLEAP(year) ? 366 : 365)
86
87 /*
88 * There are 97 leap years in 400-year periods. ((400 - 97) * 365 + 97 *
89 * 366).
90 */
91 #define DAYS_PER_ERA 146097L
92
93 /*
94 * Make sure that all values are sane.
95 * Return zero on failure, non-zero on success.
96 */
97 static int
khttp_validate_time(const struct tm64 * tim_p)98 khttp_validate_time(const struct tm64 *tim_p)
99 {
100 int64_t days_in_feb = 28;
101
102 if (tim_p->tm_sec < 0 || tim_p->tm_sec > 59)
103 return 0;
104 if (tim_p->tm_min < 0 || tim_p->tm_min > 59)
105 return 0;
106 if (tim_p->tm_hour < 0 || tim_p->tm_hour > 23)
107 return 0;
108 if (tim_p->tm_mon < 0 || tim_p->tm_mon > 11)
109 return 0;
110
111 /*
112 * This magic number is (more or less) the maximum number of
113 * years that we can consider in a 64-bit year.
114 * Outside of this we'll get overflow or underflow.
115 */
116
117 if (tim_p->tm_year > 292277026596 ||
118 tim_p->tm_year < -292277024557)
119 return 0;
120
121 if (_DAYS_IN_YEAR(tim_p->tm_year) == 366)
122 days_in_feb = 29;
123 if (tim_p->tm_mday <= 0 ||
124 tim_p->tm_mday > _DAYS_IN_MONTH(tim_p->tm_mon))
125 return 0;
126
127 return 1;
128 }
129
130 /*
131 * Convert broken-down time to the UNIX epoch.
132 * Returns zero if the broken-dwon values are not sane, non-zero
133 * otherwise.
134 * See khttp_validate_time().
135 */
136 static int
khttp_mktime(int64_t * res,struct tm64 * tim_p)137 khttp_mktime(int64_t *res, struct tm64 *tim_p)
138 {
139 int64_t tim = 0, days = 0, year, maxyear, era;
140
141 /* Validate structure. */
142
143 if (!khttp_validate_time(tim_p))
144 return 0;
145
146 /* Compute hours, minutes, seconds. */
147
148 tim += tim_p->tm_sec +
149 (tim_p->tm_min * _SEC_IN_MINUTE) +
150 (tim_p->tm_hour * _SEC_IN_HOUR);
151
152 /* Compute days in year. */
153
154 days += tim_p->tm_mday - 1;
155 days += _DAYS_BEFORE_MONTH[tim_p->tm_mon];
156
157 if (tim_p->tm_mon > 1 && _DAYS_IN_YEAR(tim_p->tm_year) == 366)
158 days++;
159
160 /* Compute day of the year. */
161
162 tim_p->tm_yday = days;
163
164 /* Compute days in other years. */
165 /* WARNING: THIS IS VERY SLOW. */
166
167 if ((year = tim_p->tm_year) > 70) {
168 maxyear = tim_p->tm_year > 400 ? 400 : tim_p->tm_year;
169 for (year = 70; year < maxyear; year++)
170 days += _DAYS_IN_YEAR(year);
171 era = (tim_p->tm_year - year) / 400;
172 days += era * DAYS_PER_ERA;
173 year += era * 400;
174 for ( ; year < tim_p->tm_year; year++)
175 days += _DAYS_IN_YEAR(year);
176 } else if (year < 70) {
177 maxyear = tim_p->tm_year < -400 ? -400 : tim_p->tm_year;
178 for (year = 69; year > maxyear; year--)
179 days -= _DAYS_IN_YEAR(year);
180 era = (tim_p->tm_year - year) / 400;
181 assert(era <= 0);
182 days += era * DAYS_PER_ERA;
183 year += era * 400;
184 for ( ; year > tim_p->tm_year; year--)
185 days -= _DAYS_IN_YEAR(year);
186 days -= _DAYS_IN_YEAR(year);
187 }
188
189 /* Compute total seconds. */
190
191 tim += days * _SEC_IN_DAY;
192
193 /* Compute day of the week. */
194
195 if ((tim_p->tm_wday = (days + 4) % 7) < 0)
196 tim_p->tm_wday += 7;
197
198 *res = tim;
199 return 1;
200 }
201
202 /*
203 * Move epoch from 01.01.1970 to 01.03.0000 (yes, Year 0) - this is the
204 * first day of a 400-year long "era", right after additional day of
205 * leap year. This adjustment is required only for date calculation, so
206 * instead of modifying time_t value (which would require 64-bit
207 * operations to work correctly) it's enough to adjust the calculated
208 * number of days since epoch.
209 */
210 #define EPOCH_ADJUSTMENT_DAYS 719468L
211
212 /* Year to which the adjustment was made. */
213 #define ADJUSTED_EPOCH_YEAR 0
214
215 /* 1st March of year 0 is Wednesday. */
216 #define ADJUSTED_EPOCH_WDAY 3
217
218 /*
219 * There are 24 leap years in 100-year periods. ((100 - 24) * 365 + 24 *
220 * 366).
221 */
222 #define DAYS_PER_CENTURY 36524L
223
224 /* There is one leap year every 4 years. */
225 #define DAYS_PER_4_YEARS (3 * 365 + 366)
226
227 /* Number of days in a non-leap year. */
228 #define DAYS_PER_YEAR 365
229
230 /* Number of days in January. */
231 #define DAYS_IN_JANUARY 31
232
233 /* Number of days in non-leap February. */
234 #define DAYS_IN_FEBRUARY 28
235
236 /* Number of years per era. */
237 #define YEARS_PER_ERA 400
238
239 /* Various constants. */
240 #define SECSPERMIN 60L
241 #define MINSPERHOUR 60L
242 #define HOURSPERDAY 24L
243 #define SECSPERHOUR (SECSPERMIN * MINSPERHOUR)
244 #define SECSPERDAY (SECSPERHOUR * HOURSPERDAY)
245 #define YEAR_BASE 1900
246 #define DAYSPERWEEK 7
247 #define MONSPERYEAR 12
248
249 /*
250 * This form of _ISLEAP doesn't adjust the year.
251 */
252 #define _ISLEAP2(y) \
253 (((y) % 4) == 0 && \
254 (((y) % 100) != 0 || ((y) % 400) == 0))
255
256 /*
257 * Convert UNIX epoch to values in "res".
258 */
259 static void
khttp_gmtime_r(int64_t lcltime,struct tm64 * res)260 khttp_gmtime_r(int64_t lcltime, struct tm64 *res)
261 {
262 int64_t days, rem, era, weekday, year;
263 uint64_t erayear, yearday, month, day, eraday;
264
265 memset(res, 0, sizeof(struct tm64));
266
267 days = lcltime / SECSPERDAY + EPOCH_ADJUSTMENT_DAYS;
268 rem = lcltime % SECSPERDAY;
269
270 if (rem < 0) {
271 rem += SECSPERDAY;
272 --days;
273 }
274
275 /* Compute hour, min, and sec. */
276
277 res->tm_hour = (int64_t)(rem / SECSPERHOUR);
278 rem %= SECSPERHOUR;
279 res->tm_min = (int64_t)(rem / SECSPERMIN);
280 res->tm_sec = (int64_t)(rem % SECSPERMIN);
281
282 /* Compute day of week. */
283
284 if ((weekday = ((ADJUSTED_EPOCH_WDAY + days) % DAYSPERWEEK)) < 0)
285 weekday += DAYSPERWEEK;
286
287 res->tm_wday = weekday;
288
289 /*
290 * Compute year, month, day & day of year.
291 * For description of this algorithm see
292 * http://howardhinnant.github.io/date_algorithms.html#civil_from_days
293 */
294
295 era = (days >= 0 ? days : days - (DAYS_PER_ERA - 1)) / DAYS_PER_ERA;
296
297 /* [0, 146096] */
298 eraday = days - era * DAYS_PER_ERA;
299
300 /* [0, 399] */
301 erayear = (eraday - eraday / (DAYS_PER_4_YEARS - 1) +
302 eraday / DAYS_PER_CENTURY - eraday /
303 (DAYS_PER_ERA - 1)) / 365;
304
305 /* [0, 365] */
306 yearday = eraday - (DAYS_PER_YEAR * erayear +
307 erayear / 4 - erayear / 100);
308
309 /* [0, 11] */
310 month = (5 * yearday + 2) / 153;
311
312 /* [1, 31] */
313 day = yearday - (153 * month + 2) / 5 + 1;
314
315 month += month < 10 ? 2 : -10;
316 year = ADJUSTED_EPOCH_YEAR + erayear +
317 era * YEARS_PER_ERA + (month <= 1);
318
319 res->tm_yday =
320 yearday >= DAYS_PER_YEAR -
321 DAYS_IN_JANUARY - DAYS_IN_FEBRUARY ?
322 yearday - (DAYS_PER_YEAR -
323 DAYS_IN_JANUARY - DAYS_IN_FEBRUARY) :
324 yearday + DAYS_IN_JANUARY +
325 DAYS_IN_FEBRUARY + _ISLEAP2(erayear);
326 res->tm_year = year - YEAR_BASE;
327 res->tm_mon = month;
328 res->tm_mday = day;
329 }
330
331 char *
khttp_epoch2str(int64_t tt,char * buf,size_t sz)332 khttp_epoch2str(int64_t tt, char *buf, size_t sz)
333 {
334 struct tm64 tm;
335 char rbuf[MAX_TIME_STRING];
336 const char *days[7] = {
337 "Sun",
338 "Mon",
339 "Tue",
340 "Wed",
341 "Thu",
342 "Fri",
343 "Sat"
344 };
345 const char *months[12] = {
346 "Jan",
347 "Feb",
348 "Mar",
349 "Apr",
350 "May",
351 "Jun",
352 "Jul",
353 "Aug",
354 "Sep",
355 "Oct",
356 "Nov",
357 "Dec"
358 };
359
360 if (buf == NULL || sz == 0)
361 return NULL;
362
363 khttp_gmtime_r(tt, &tm);
364
365 if (snprintf(rbuf, sizeof(rbuf),
366 "%s, %.2" PRId64 " %s %.4" PRId64 " "
367 "%.2" PRId64 ":%.2" PRId64 ":%.2" PRId64 " GMT",
368 days[tm.tm_wday], tm.tm_mday,
369 months[tm.tm_mon], tm.tm_year + 1900,
370 tm.tm_hour, tm.tm_min, tm.tm_sec) == -1) {
371 kutil_warn(NULL, NULL, "snprintf");
372 return NULL;
373 }
374
375 strlcpy(buf, rbuf, sz);
376 return buf;
377 }
378
379 /*
380 * Deprecated interface simply zeroes the time structure if it's
381 * negative. The original implementation did not do this (it set the
382 * "struct tm" to zero), but the documentation still said otherwise.
383 * This uses the original documented behaviour.
384 */
385 char *
kutil_epoch2str(int64_t tt,char * buf,size_t sz)386 kutil_epoch2str(int64_t tt, char *buf, size_t sz)
387 {
388
389 return khttp_epoch2str(tt < 0 ? 0 : tt, buf, sz);
390 }
391
392 char *
khttp_epoch2ustr(int64_t tt,char * buf,size_t sz)393 khttp_epoch2ustr(int64_t tt, char *buf, size_t sz)
394 {
395 char rbuf[MAX_TIME_STRING];
396 struct tm64 tm;
397
398 if (buf == NULL || sz == 0)
399 return NULL;
400
401 khttp_gmtime_r(tt, &tm);
402
403 if (snprintf(rbuf, sizeof(rbuf),
404 "%.4" PRId64 "-%.2" PRId64 "-%.2" PRId64
405 "T%.2" PRId64 ":%.2" PRId64 ":%.2" PRId64 "Z",
406 tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
407 tm.tm_hour, tm.tm_min, tm.tm_sec) == -1) {
408 kutil_warn(NULL, NULL, "snprintf");
409 return NULL;
410 }
411
412 strlcpy(buf, rbuf, sz);
413 return buf;
414 }
415
416 /*
417 * Deprecated.
418 * See kutil_epoch2str() for behaviour notes.
419 */
420 char *
kutil_epoch2utcstr(int64_t tt,char * buf,size_t sz)421 kutil_epoch2utcstr(int64_t tt, char *buf, size_t sz)
422 {
423
424 return khttp_epoch2ustr(tt < 0 ? 0 : tt, buf, sz);
425 }
426
427 void
khttp_epoch2datetime(int64_t tt,int64_t * tm_sec,int64_t * tm_min,int64_t * tm_hour,int64_t * tm_mday,int64_t * tm_mon,int64_t * tm_year,int64_t * tm_wday,int64_t * tm_yday)428 khttp_epoch2datetime(int64_t tt, int64_t *tm_sec, int64_t *tm_min,
429 int64_t *tm_hour, int64_t *tm_mday, int64_t *tm_mon,
430 int64_t *tm_year, int64_t *tm_wday, int64_t *tm_yday)
431 {
432 struct tm64 tm;
433
434 khttp_gmtime_r(tt, &tm);
435
436 if (tm_sec != NULL)
437 *tm_sec = tm.tm_sec;
438 if (tm_min != NULL)
439 *tm_min = tm.tm_min;
440 if (tm_hour != NULL)
441 *tm_hour = tm.tm_hour;
442 if (tm_mday != NULL)
443 *tm_mday = tm.tm_mday;
444 if (tm_mon != NULL)
445 *tm_mon = tm.tm_mon + 1;
446 if (tm_year != NULL)
447 *tm_year = tm.tm_year + 1900;
448 if (tm_wday != NULL)
449 *tm_wday = tm.tm_wday;
450 if (tm_yday != NULL)
451 *tm_yday = tm.tm_yday;
452 }
453
454 int
khttp_epoch2tms(int64_t tt,int * tm_sec,int * tm_min,int * tm_hour,int * tm_mday,int * tm_mon,int * tm_year,int * tm_wday,int * tm_yday)455 khttp_epoch2tms(int64_t tt, int *tm_sec, int *tm_min,
456 int *tm_hour, int *tm_mday, int *tm_mon,
457 int *tm_year, int *tm_wday, int *tm_yday)
458 {
459 struct tm64 tm;
460
461 khttp_gmtime_r(tt, &tm);
462
463 if (tm.tm_year > INT_MAX || tm.tm_year < -INT_MAX)
464 return 0;
465 if (tm_sec != NULL)
466 *tm_sec = tm.tm_sec;
467 if (tm_min != NULL)
468 *tm_min = tm.tm_min;
469 if (tm_hour != NULL)
470 *tm_hour = tm.tm_hour;
471 if (tm_mday != NULL)
472 *tm_mday = tm.tm_mday;
473 if (tm_mon != NULL)
474 *tm_mon = tm.tm_mon;
475 if (tm_year != NULL)
476 *tm_year = tm.tm_year;
477 if (tm_wday != NULL)
478 *tm_wday = tm.tm_wday;
479 if (tm_yday != NULL)
480 *tm_yday = tm.tm_yday;
481
482 return 1;
483 }
484
485 /*
486 * Deprecated.
487 * This has a bad corner case where gmtime() returns NULL and we just
488 * assume the epoch---this wasn't present in the earlier version of this
489 * because it was using a hand-rolled gmtime() that didn't fail. This
490 * is suitably unlikely that it's ok, as it's deprecated.
491 */
492 void
kutil_epoch2tmvals(int64_t tt,int * tm_sec,int * tm_min,int * tm_hour,int * tm_mday,int * tm_mon,int * tm_year,int * tm_wday,int * tm_yday)493 kutil_epoch2tmvals(int64_t tt, int *tm_sec, int *tm_min,
494 int *tm_hour, int *tm_mday, int *tm_mon,
495 int *tm_year, int *tm_wday, int *tm_yday)
496 {
497
498 khttp_epoch2tms(tt < 0 ? 0 : tt, tm_sec, tm_min,
499 tm_hour, tm_mday, tm_mon, tm_year, tm_wday, tm_yday);
500 }
501
502 int
khttp_datetime2epoch(int64_t * res,int64_t day,int64_t mon,int64_t year,int64_t hour,int64_t min,int64_t sec)503 khttp_datetime2epoch(int64_t *res, int64_t day, int64_t mon,
504 int64_t year, int64_t hour, int64_t min, int64_t sec)
505 {
506 struct tm64 tm;
507 int64_t val;
508
509 if (res == NULL)
510 res = &val;
511
512 memset(&tm, 0, sizeof(struct tm64));
513 tm.tm_sec = sec;
514 tm.tm_min = min;
515 tm.tm_hour = hour;
516 tm.tm_mday = day;
517 tm.tm_mon = mon - 1;
518 tm.tm_year = year - 1900;
519 return khttp_mktime(res, &tm);
520 }
521
522 int
khttp_date2epoch(int64_t * res,int64_t day,int64_t mon,int64_t year)523 khttp_date2epoch(int64_t *res, int64_t day, int64_t mon, int64_t year)
524 {
525
526 return khttp_datetime2epoch(res, day, mon, year, 0, 0, 0);
527 }
528
529 /*
530 * Deprecated interface.
531 */
532 int64_t
kutil_date2epoch(int64_t day,int64_t mon,int64_t year)533 kutil_date2epoch(int64_t day, int64_t mon, int64_t year)
534 {
535 int64_t res;
536
537 if (!khttp_date2epoch(&res, day, mon, year))
538 return -1;
539
540 return res;
541 }
542
543 /*
544 * Deprecated interface.
545 */
546 int
kutil_datetime_check(int64_t day,int64_t mon,int64_t year,int64_t hour,int64_t min,int64_t sec)547 kutil_datetime_check(int64_t day, int64_t mon, int64_t year,
548 int64_t hour, int64_t min, int64_t sec)
549 {
550
551 return khttp_datetime2epoch
552 (NULL, day, mon, year, hour, min, sec);
553 }
554
555 /*
556 * Deprecated interface.
557 */
558 int
kutil_date_check(int64_t day,int64_t mon,int64_t year)559 kutil_date_check(int64_t day, int64_t mon, int64_t year)
560 {
561
562 return khttp_date2epoch(NULL, day, mon, year);
563 }
564
565 /*
566 * Deprecated interface.
567 */
568 int64_t
kutil_datetime2epoch(int64_t day,int64_t mon,int64_t year,int64_t hour,int64_t min,int64_t sec)569 kutil_datetime2epoch(int64_t day, int64_t mon, int64_t year,
570 int64_t hour, int64_t min, int64_t sec)
571 {
572 int64_t res;
573
574 if (!khttp_datetime2epoch
575 (&res, day, mon, year, hour, min, sec))
576 return -1;
577
578 return res;
579 }
580